summaryrefslogtreecommitdiffstats
path: root/dom/system/gonk
diff options
context:
space:
mode:
authorMatt A. Tobin <mattatobin@localhost.localdomain>2018-02-02 04:16:08 -0500
committerMatt A. Tobin <mattatobin@localhost.localdomain>2018-02-02 04:16:08 -0500
commit5f8de423f190bbb79a62f804151bc24824fa32d8 (patch)
tree10027f336435511475e392454359edea8e25895d /dom/system/gonk
parent49ee0794b5d912db1f95dce6eb52d781dc210db5 (diff)
downloadUXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.gz
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.lz
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.xz
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.zip
Add m-esr52 at 52.6.0
Diffstat (limited to 'dom/system/gonk')
-rw-r--r--dom/system/gonk/AudioChannelManager.cpp181
-rw-r--r--dom/system/gonk/AudioChannelManager.h87
-rw-r--r--dom/system/gonk/AudioManager.cpp1412
-rw-r--r--dom/system/gonk/AudioManager.h180
-rw-r--r--dom/system/gonk/AutoMounter.cpp1496
-rw-r--r--dom/system/gonk/AutoMounter.h101
-rw-r--r--dom/system/gonk/AutoMounterSetting.cpp284
-rw-r--r--dom/system/gonk/AutoMounterSetting.h38
-rw-r--r--dom/system/gonk/DataCallInterfaceService.js276
-rw-r--r--dom/system/gonk/DataCallInterfaceService.manifest6
-rw-r--r--dom/system/gonk/DataCallManager.js1726
-rw-r--r--dom/system/gonk/DataCallManager.manifest4
-rw-r--r--dom/system/gonk/GeolocationUtil.cpp28
-rw-r--r--dom/system/gonk/GeolocationUtil.h13
-rw-r--r--dom/system/gonk/GonkGPSGeolocationProvider.cpp706
-rw-r--r--dom/system/gonk/GonkGPSGeolocationProvider.h103
-rw-r--r--dom/system/gonk/MozMtpCommon.h56
-rw-r--r--dom/system/gonk/MozMtpDatabase.cpp1542
-rw-r--r--dom/system/gonk/MozMtpDatabase.h288
-rw-r--r--dom/system/gonk/MozMtpServer.cpp263
-rw-r--r--dom/system/gonk/MozMtpServer.h61
-rw-r--r--dom/system/gonk/MozMtpStorage.cpp135
-rw-r--r--dom/system/gonk/MozMtpStorage.h47
-rw-r--r--dom/system/gonk/NetIdManager.cpp68
-rw-r--r--dom/system/gonk/NetIdManager.h45
-rw-r--r--dom/system/gonk/NetworkInterfaceListService.js110
-rw-r--r--dom/system/gonk/NetworkInterfaceListService.manifest17
-rw-r--r--dom/system/gonk/NetworkManager.js1219
-rw-r--r--dom/system/gonk/NetworkManager.manifest3
-rw-r--r--dom/system/gonk/NetworkService.js862
-rw-r--r--dom/system/gonk/NetworkService.manifest3
-rw-r--r--dom/system/gonk/NetworkUtils.cpp2973
-rw-r--r--dom/system/gonk/NetworkUtils.h498
-rw-r--r--dom/system/gonk/NetworkWorker.cpp271
-rw-r--r--dom/system/gonk/NetworkWorker.h37
-rw-r--r--dom/system/gonk/OpenFileFinder.cpp251
-rw-r--r--dom/system/gonk/OpenFileFinder.h63
-rw-r--r--dom/system/gonk/RILSystemMessenger.jsm338
-rw-r--r--dom/system/gonk/RILSystemMessengerHelper.js169
-rw-r--r--dom/system/gonk/RILSystemMessengerHelper.manifest6
-rw-r--r--dom/system/gonk/RadioInterfaceLayer.js1324
-rw-r--r--dom/system/gonk/RadioInterfaceLayer.manifest18
-rw-r--r--dom/system/gonk/SystemProperty.cpp98
-rw-r--r--dom/system/gonk/SystemProperty.h39
-rw-r--r--dom/system/gonk/SystemWorkerManager.cpp214
-rw-r--r--dom/system/gonk/SystemWorkerManager.h75
-rw-r--r--dom/system/gonk/TetheringService.js891
-rw-r--r--dom/system/gonk/TetheringService.manifest4
-rw-r--r--dom/system/gonk/TimeZoneSettingObserver.cpp239
-rw-r--r--dom/system/gonk/TimeZoneSettingObserver.h20
-rw-r--r--dom/system/gonk/Volume.cpp596
-rw-r--r--dom/system/gonk/Volume.h157
-rw-r--r--dom/system/gonk/VolumeCommand.cpp85
-rw-r--r--dom/system/gonk/VolumeCommand.h204
-rw-r--r--dom/system/gonk/VolumeManager.cpp591
-rw-r--r--dom/system/gonk/VolumeManager.h192
-rw-r--r--dom/system/gonk/VolumeManagerLog.h27
-rw-r--r--dom/system/gonk/VolumeServiceIOThread.cpp82
-rw-r--r--dom/system/gonk/VolumeServiceIOThread.h49
-rw-r--r--dom/system/gonk/VolumeServiceTest.cpp202
-rw-r--r--dom/system/gonk/VolumeServiceTest.h19
-rw-r--r--dom/system/gonk/android_audio/AudioSystem.h1134
-rw-r--r--dom/system/gonk/android_audio/AudioTrack.h489
-rw-r--r--dom/system/gonk/android_audio/EffectApi.h798
-rw-r--r--dom/system/gonk/android_audio/IAudioFlinger.h184
-rw-r--r--dom/system/gonk/android_audio/IAudioFlingerClient.h55
-rw-r--r--dom/system/gonk/android_audio/IAudioRecord.h68
-rw-r--r--dom/system/gonk/android_audio/IAudioTrack.h89
-rw-r--r--dom/system/gonk/android_audio/IEffect.h60
-rw-r--r--dom/system/gonk/android_audio/IEffectClient.h54
-rw-r--r--dom/system/gonk/moz.build107
-rw-r--r--dom/system/gonk/mozstumbler/MozStumbler.cpp426
-rw-r--r--dom/system/gonk/mozstumbler/MozStumbler.h47
-rw-r--r--dom/system/gonk/mozstumbler/StumblerLogging.cpp13
-rw-r--r--dom/system/gonk/mozstumbler/StumblerLogging.h18
-rw-r--r--dom/system/gonk/mozstumbler/UploadStumbleRunnable.cpp151
-rw-r--r--dom/system/gonk/mozstumbler/UploadStumbleRunnable.h46
-rw-r--r--dom/system/gonk/mozstumbler/WriteStumbleOnThread.cpp321
-rw-r--r--dom/system/gonk/mozstumbler/WriteStumbleOnThread.h91
-rw-r--r--dom/system/gonk/nsIAudioManager.idl58
-rw-r--r--dom/system/gonk/nsIDataCallInterfaceService.idl268
-rw-r--r--dom/system/gonk/nsIDataCallManager.idl81
-rw-r--r--dom/system/gonk/nsIGonkDataCallInterfaceService.idl18
-rw-r--r--dom/system/gonk/nsINetworkInterface.idl108
-rw-r--r--dom/system/gonk/nsINetworkInterfaceListService.idl40
-rw-r--r--dom/system/gonk/nsINetworkManager.idl135
-rw-r--r--dom/system/gonk/nsINetworkService.idl619
-rw-r--r--dom/system/gonk/nsINetworkWorker.idl18
-rw-r--r--dom/system/gonk/nsIRadioInterfaceLayer.idl53
-rw-r--r--dom/system/gonk/nsISystemWorkerManager.idl16
-rw-r--r--dom/system/gonk/nsITetheringService.idl39
-rw-r--r--dom/system/gonk/nsIVolume.idl114
-rw-r--r--dom/system/gonk/nsIVolumeMountLock.idl12
-rw-r--r--dom/system/gonk/nsIVolumeService.idl36
-rw-r--r--dom/system/gonk/nsIVolumeStat.idl12
-rw-r--r--dom/system/gonk/nsIWorkerHolder.idl11
-rw-r--r--dom/system/gonk/nsVolume.cpp467
-rw-r--r--dom/system/gonk/nsVolume.h114
-rw-r--r--dom/system/gonk/nsVolumeMountLock.cpp171
-rw-r--r--dom/system/gonk/nsVolumeMountLock.h56
-rw-r--r--dom/system/gonk/nsVolumeService.cpp553
-rw-r--r--dom/system/gonk/nsVolumeService.h78
-rw-r--r--dom/system/gonk/nsVolumeStat.cpp33
-rw-r--r--dom/system/gonk/nsVolumeStat.h33
-rw-r--r--dom/system/gonk/ril_consts.js3338
-rw-r--r--dom/system/gonk/ril_worker.js15206
-rw-r--r--dom/system/gonk/ril_worker_buf_object.js168
-rw-r--r--dom/system/gonk/ril_worker_telephony_request_queue.js157
-rw-r--r--dom/system/gonk/systemlibs.js201
-rw-r--r--dom/system/gonk/tests/header_helpers.js217
-rw-r--r--dom/system/gonk/tests/marionette/head.js345
-rw-r--r--dom/system/gonk/tests/marionette/manifest.ini19
-rw-r--r--dom/system/gonk/tests/marionette/ril_jshint/README.md9
-rw-r--r--dom/system/gonk/tests/marionette/ril_jshint/jshint.js11096
-rw-r--r--dom/system/gonk/tests/marionette/ril_jshint/jshintrc118
-rw-r--r--dom/system/gonk/tests/marionette/test_all_network_info.js106
-rw-r--r--dom/system/gonk/tests/marionette/test_data_connection.js70
-rw-r--r--dom/system/gonk/tests/marionette/test_data_connection_proxy.js99
-rw-r--r--dom/system/gonk/tests/marionette/test_dsds_numRadioInterfaces.js43
-rw-r--r--dom/system/gonk/tests/marionette/test_fakevolume.js25
-rw-r--r--dom/system/gonk/tests/marionette/test_geolocation.js117
-rw-r--r--dom/system/gonk/tests/marionette/test_multiple_data_connection.js89
-rw-r--r--dom/system/gonk/tests/marionette/test_network_active_changed.js52
-rw-r--r--dom/system/gonk/tests/marionette/test_network_interface_list_service.js95
-rw-r--r--dom/system/gonk/tests/marionette/test_network_interface_mtu.js100
-rw-r--r--dom/system/gonk/tests/marionette/test_ril_code_quality.py371
-rw-r--r--dom/system/gonk/tests/marionette/test_screen_state.js47
-rw-r--r--dom/system/gonk/tests/marionette/test_timezone_changes.js135
-rw-r--r--dom/system/gonk/tests/test_ril_system_messenger.js1187
-rw-r--r--dom/system/gonk/tests/test_ril_worker_barring_password.js61
-rw-r--r--dom/system/gonk/tests/test_ril_worker_buf.js187
-rw-r--r--dom/system/gonk/tests/test_ril_worker_cdma_info_rec.js234
-rw-r--r--dom/system/gonk/tests/test_ril_worker_cellbroadcast_config.js470
-rw-r--r--dom/system/gonk/tests/test_ril_worker_cellbroadcast_gsm.js230
-rw-r--r--dom/system/gonk/tests/test_ril_worker_cellbroadcast_umts.js105
-rw-r--r--dom/system/gonk/tests/test_ril_worker_cf.js126
-rw-r--r--dom/system/gonk/tests/test_ril_worker_clip.js59
-rw-r--r--dom/system/gonk/tests/test_ril_worker_clir.js122
-rw-r--r--dom/system/gonk/tests/test_ril_worker_cw.js104
-rw-r--r--dom/system/gonk/tests/test_ril_worker_ecm.js168
-rw-r--r--dom/system/gonk/tests/test_ril_worker_icc_BerTlvHelper.js87
-rw-r--r--dom/system/gonk/tests/test_ril_worker_icc_CardLock.js282
-rw-r--r--dom/system/gonk/tests/test_ril_worker_icc_CardState.js210
-rw-r--r--dom/system/gonk/tests/test_ril_worker_icc_GsmPDUHelper.js79
-rw-r--r--dom/system/gonk/tests/test_ril_worker_icc_ICCContactHelper.js1042
-rw-r--r--dom/system/gonk/tests/test_ril_worker_icc_ICCIOHelper.js173
-rw-r--r--dom/system/gonk/tests/test_ril_worker_icc_ICCPDUHelper.js652
-rw-r--r--dom/system/gonk/tests/test_ril_worker_icc_ICCRecordHelper.js1080
-rw-r--r--dom/system/gonk/tests/test_ril_worker_icc_ICCUtilsHelper.js326
-rw-r--r--dom/system/gonk/tests/test_ril_worker_icc_IconLoader.js771
-rw-r--r--dom/system/gonk/tests/test_ril_worker_icc_SimRecordHelper.js1648
-rw-r--r--dom/system/gonk/tests/test_ril_worker_ruim.js328
-rw-r--r--dom/system/gonk/tests/test_ril_worker_sms.js273
-rw-r--r--dom/system/gonk/tests/test_ril_worker_sms_cdma.js298
-rw-r--r--dom/system/gonk/tests/test_ril_worker_sms_cdmapduhelper.js210
-rw-r--r--dom/system/gonk/tests/test_ril_worker_sms_gsmpduhelper.js282
-rw-r--r--dom/system/gonk/tests/test_ril_worker_sms_nl_tables.js77
-rw-r--r--dom/system/gonk/tests/test_ril_worker_sms_segment_info.js115
-rw-r--r--dom/system/gonk/tests/test_ril_worker_smsc_address.js112
-rw-r--r--dom/system/gonk/tests/test_ril_worker_ssn.js104
-rw-r--r--dom/system/gonk/tests/test_ril_worker_stk.js1698
-rw-r--r--dom/system/gonk/tests/test_ril_worker_voiceprivacy.js94
-rw-r--r--dom/system/gonk/tests/xpcshell.ini43
-rw-r--r--dom/system/gonk/worker_buf.js623
164 files changed, 74269 insertions, 0 deletions
diff --git a/dom/system/gonk/AudioChannelManager.cpp b/dom/system/gonk/AudioChannelManager.cpp
new file mode 100644
index 000000000..977715a29
--- /dev/null
+++ b/dom/system/gonk/AudioChannelManager.cpp
@@ -0,0 +1,181 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIDocument.h"
+#include "nsIDOMClassInfo.h"
+#include "nsIDOMEvent.h"
+#include "nsIDOMEventListener.h"
+#include "nsPIDOMWindow.h"
+#include "nsIDocShell.h"
+#include "nsIPermissionManager.h"
+#include "nsIInterfaceRequestorUtils.h"
+#include "AudioChannelManager.h"
+#include "mozilla/dom/AudioChannelManagerBinding.h"
+#include "mozilla/dom/nsBrowserElement.h"
+#include "mozilla/Services.h"
+
+namespace mozilla {
+namespace dom {
+namespace system {
+
+NS_IMPL_QUERY_INTERFACE_INHERITED(AudioChannelManager, DOMEventTargetHelper,
+ nsIDOMEventListener)
+NS_IMPL_ADDREF_INHERITED(AudioChannelManager, DOMEventTargetHelper)
+NS_IMPL_RELEASE_INHERITED(AudioChannelManager, DOMEventTargetHelper)
+
+AudioChannelManager::AudioChannelManager()
+ : mVolumeChannel(-1)
+{
+ hal::RegisterSwitchObserver(hal::SWITCH_HEADPHONES, this);
+}
+
+AudioChannelManager::~AudioChannelManager()
+{
+ hal::UnregisterSwitchObserver(hal::SWITCH_HEADPHONES, this);
+
+ nsCOMPtr<EventTarget> target = do_QueryInterface(GetOwner());
+ NS_ENSURE_TRUE_VOID(target);
+
+ target->RemoveSystemEventListener(NS_LITERAL_STRING("visibilitychange"),
+ this,
+ /* useCapture = */ true);
+}
+
+void
+AudioChannelManager::Init(nsPIDOMWindowInner* aWindow)
+{
+ BindToOwner(aWindow);
+
+ nsCOMPtr<nsIDOMEventTarget> target = do_QueryInterface(GetOwner());
+ NS_ENSURE_TRUE_VOID(target);
+
+ target->AddSystemEventListener(NS_LITERAL_STRING("visibilitychange"),
+ this,
+ /* useCapture = */ true,
+ /* wantsUntrusted = */ false);
+}
+
+JSObject*
+AudioChannelManager::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
+{
+ return AudioChannelManagerBinding::Wrap(aCx, this, aGivenProto);
+}
+
+void
+AudioChannelManager::Notify(const hal::SwitchEvent& aEvent)
+{
+ mState = Some(aEvent.status());
+
+ DispatchTrustedEvent(NS_LITERAL_STRING("headphoneschange"));
+}
+
+bool
+AudioChannelManager::SetVolumeControlChannel(const nsAString& aChannel)
+{
+ if (aChannel.EqualsASCII("publicnotification")) {
+ return false;
+ }
+
+ AudioChannel newChannel = AudioChannelService::GetAudioChannel(aChannel);
+
+ // Only normal channel doesn't need permission.
+ if (newChannel != AudioChannel::Normal) {
+ nsCOMPtr<nsIPermissionManager> permissionManager =
+ services::GetPermissionManager();
+ if (!permissionManager) {
+ return false;
+ }
+ uint32_t perm = nsIPermissionManager::UNKNOWN_ACTION;
+ permissionManager->TestPermissionFromWindow(GetOwner(),
+ nsCString(NS_LITERAL_CSTRING("audio-channel-") +
+ NS_ConvertUTF16toUTF8(aChannel)).get(), &perm);
+ if (perm != nsIPermissionManager::ALLOW_ACTION) {
+ return false;
+ }
+ }
+
+ if (mVolumeChannel == (int32_t)newChannel) {
+ return true;
+ }
+
+ mVolumeChannel = (int32_t)newChannel;
+
+ NotifyVolumeControlChannelChanged();
+ return true;
+}
+
+bool
+AudioChannelManager::GetVolumeControlChannel(nsAString & aChannel)
+{
+ if (mVolumeChannel >= 0) {
+ AudioChannelService::GetAudioChannelString(
+ static_cast<AudioChannel>(mVolumeChannel),
+ aChannel);
+ } else {
+ aChannel.AssignASCII("");
+ }
+
+ return true;
+}
+
+void
+AudioChannelManager::NotifyVolumeControlChannelChanged()
+{
+ nsCOMPtr<nsIDocShell> docshell = do_GetInterface(GetOwner());
+ NS_ENSURE_TRUE_VOID(docshell);
+
+ bool isActive = false;
+ docshell->GetIsActive(&isActive);
+
+ RefPtr<AudioChannelService> service = AudioChannelService::GetOrCreate();
+ if (!service) {
+ return;
+ }
+
+ if (isActive) {
+ service->SetDefaultVolumeControlChannel(mVolumeChannel, isActive);
+ } else {
+ service->SetDefaultVolumeControlChannel(-1, isActive);
+ }
+}
+
+NS_IMETHODIMP
+AudioChannelManager::HandleEvent(nsIDOMEvent* aEvent)
+{
+ nsAutoString type;
+ aEvent->GetType(type);
+
+ if (type.EqualsLiteral("visibilitychange")) {
+ NotifyVolumeControlChannelChanged();
+ }
+ return NS_OK;
+}
+
+void
+AudioChannelManager::GetAllowedAudioChannels(
+ nsTArray<RefPtr<BrowserElementAudioChannel>>& aAudioChannels,
+ ErrorResult& aRv)
+{
+ MOZ_ASSERT(aAudioChannels.IsEmpty());
+
+ // Only main process is supported.
+ if (XRE_GetProcessType() != GeckoProcessType_Default) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return;
+ }
+
+ nsCOMPtr<nsPIDOMWindowInner> window = GetOwner();
+ if (NS_WARN_IF(!window)) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return;
+ }
+
+ nsBrowserElement::GenerateAllowedAudioChannels(window, nullptr, nullptr,
+ aAudioChannels, aRv);
+ NS_WARNING_ASSERTION(!aRv.Failed(), "GenerateAllowedAudioChannels failed");
+}
+
+} // namespace system
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/system/gonk/AudioChannelManager.h b/dom/system/gonk/AudioChannelManager.h
new file mode 100644
index 000000000..a460651e7
--- /dev/null
+++ b/dom/system/gonk/AudioChannelManager.h
@@ -0,0 +1,87 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_system_AudioChannelManager_h
+#define mozilla_dom_system_AudioChannelManager_h
+
+#include "mozilla/dom/BrowserElementAudioChannel.h"
+#include "mozilla/DOMEventTargetHelper.h"
+#include "mozilla/Hal.h"
+#include "mozilla/HalTypes.h"
+#include "mozilla/Maybe.h"
+#include "AudioChannelService.h"
+
+namespace mozilla {
+namespace hal {
+class SwitchEvent;
+typedef Observer<SwitchEvent> SwitchObserver;
+} // namespace hal
+
+namespace dom {
+namespace system {
+
+class AudioChannelManager final
+ : public DOMEventTargetHelper
+ , public hal::SwitchObserver
+ , public nsIDOMEventListener
+{
+public:
+ AudioChannelManager();
+
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_NSIDOMEVENTLISTENER
+
+ void Notify(const hal::SwitchEvent& aEvent);
+
+ void Init(nsPIDOMWindowInner* aWindow);
+
+ /**
+ * WebIDL Interface
+ */
+
+ nsPIDOMWindowInner* GetParentObject() const
+ {
+ return GetOwner();
+ }
+
+ virtual JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
+
+ bool Headphones()
+ {
+ // Bug 929139 - Remove the assert check for SWITCH_STATE_UNKNOWN.
+ // If any devices (ex: emulator) didn't have the corresponding sys node for
+ // headset switch state then GonkSwitch will report the unknown state.
+ // So it is possible to get unknown state here.
+ if (mState.isNothing()) {
+ mState = Some(hal::GetCurrentSwitchState(hal::SWITCH_HEADPHONES));
+ }
+ return mState.value() != hal::SWITCH_STATE_OFF &&
+ mState.value() != hal::SWITCH_STATE_UNKNOWN;
+ }
+
+ bool SetVolumeControlChannel(const nsAString& aChannel);
+
+ bool GetVolumeControlChannel(nsAString& aChannel);
+
+ IMPL_EVENT_HANDLER(headphoneschange)
+
+ void GetAllowedAudioChannels(
+ nsTArray<RefPtr<mozilla::dom::BrowserElementAudioChannel>>& aAudioChannels,
+ mozilla::ErrorResult& aRv);
+
+protected:
+ virtual ~AudioChannelManager();
+
+private:
+ void NotifyVolumeControlChannelChanged();
+
+ Maybe<hal::SwitchState> mState;
+ int32_t mVolumeChannel;
+};
+
+} // namespace system
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_system_AudioChannelManager_h
diff --git a/dom/system/gonk/AudioManager.cpp b/dom/system/gonk/AudioManager.cpp
new file mode 100644
index 000000000..88dff13f7
--- /dev/null
+++ b/dom/system/gonk/AudioManager.cpp
@@ -0,0 +1,1412 @@
+/* Copyright 2012 Mozilla Foundation and Mozilla contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <android/log.h>
+#include <cutils/properties.h>
+#include <binder/IServiceManager.h>
+
+#include "AudioChannelService.h"
+#include "AudioManager.h"
+
+#include "nsIObserverService.h"
+#include "nsISettingsService.h"
+#include "nsPrintfCString.h"
+
+#include "mozilla/Hal.h"
+#include "mozilla/Services.h"
+#include "mozilla/StaticPtr.h"
+#include "mozilla/ClearOnShutdown.h"
+#include "mozilla/MozPromise.h"
+#include "mozilla/dom/ScriptSettings.h"
+#include "base/message_loop.h"
+
+#include "BluetoothCommon.h"
+#include "BluetoothHfpManagerBase.h"
+
+#include "nsJSUtils.h"
+#include "nsThreadUtils.h"
+#include "nsServiceManagerUtils.h"
+#include "nsComponentManagerUtils.h"
+#include "nsContentUtils.h"
+#include "nsXULAppAPI.h"
+#include "mozilla/dom/BindingUtils.h"
+#include "mozilla/dom/SettingChangeNotificationBinding.h"
+
+using namespace mozilla::dom;
+using namespace mozilla::dom::gonk;
+using namespace android;
+using namespace mozilla;
+using namespace mozilla::dom::bluetooth;
+
+#undef LOG
+#define LOG(args...) __android_log_print(ANDROID_LOG_INFO, "AudioManager" , ## args)
+
+#define HEADPHONES_STATUS_HEADSET u"headset"
+#define HEADPHONES_STATUS_HEADPHONE u"headphone"
+#define HEADPHONES_STATUS_OFF u"off"
+#define HEADPHONES_STATUS_UNKNOWN u"unknown"
+#define HEADPHONES_STATUS_CHANGED "headphones-status-changed"
+#define MOZ_SETTINGS_CHANGE_ID "mozsettings-changed"
+#define AUDIO_CHANNEL_PROCESS_CHANGED "audio-channel-process-changed"
+#define AUDIO_POLICY_SERVICE_NAME "media.audio_policy"
+#define SETTINGS_SERVICE "@mozilla.org/settingsService;1"
+
+// Refer AudioService.java from Android
+static const uint32_t sMaxStreamVolumeTbl[AUDIO_STREAM_CNT] = {
+ 5, // voice call
+ 15, // system
+ 15, // ring
+ 15, // music
+ 15, // alarm
+ 15, // notification
+ 15, // BT SCO
+ 15, // enforced audible
+ 15, // DTMF
+ 15, // TTS
+#if ANDROID_VERSION < 19
+ 15, // FM
+#endif
+};
+
+static const uint32_t sDefaultStreamVolumeTbl[AUDIO_STREAM_CNT] = {
+ 3, // voice call
+ 8, // system
+ 8, // ring
+ 8, // music
+ 8, // alarm
+ 8, // notification
+ 8, // BT SCO
+ 15, // enforced audible // XXX Handle as fixed maximum audio setting
+ 8, // DTMF
+ 8, // TTS
+#if ANDROID_VERSION < 19
+ 8, // FM
+#endif
+};
+
+static const int32_t sStreamVolumeAliasTbl[AUDIO_STREAM_CNT] = {
+ AUDIO_STREAM_VOICE_CALL, // voice call
+ AUDIO_STREAM_NOTIFICATION, // system
+ AUDIO_STREAM_NOTIFICATION, // ring
+ AUDIO_STREAM_MUSIC, // music
+ AUDIO_STREAM_ALARM, // alarm
+ AUDIO_STREAM_NOTIFICATION, // notification
+ AUDIO_STREAM_BLUETOOTH_SCO, // BT SCO
+ AUDIO_STREAM_ENFORCED_AUDIBLE,// enforced audible
+ AUDIO_STREAM_DTMF, // DTMF
+ AUDIO_STREAM_TTS, // TTS
+#if ANDROID_VERSION < 19
+ AUDIO_STREAM_MUSIC, // FM
+#endif
+};
+
+static const uint32_t sChannelStreamTbl[NUMBER_OF_AUDIO_CHANNELS] = {
+ AUDIO_STREAM_MUSIC, // AudioChannel::Normal
+ AUDIO_STREAM_MUSIC, // AudioChannel::Content
+ AUDIO_STREAM_NOTIFICATION, // AudioChannel::Notification
+ AUDIO_STREAM_ALARM, // AudioChannel::Alarm
+ AUDIO_STREAM_VOICE_CALL, // AudioChannel::Telephony
+ AUDIO_STREAM_RING, // AudioChannel::Ringer
+ AUDIO_STREAM_ENFORCED_AUDIBLE,// AudioChannel::Publicnotification
+ AUDIO_STREAM_SYSTEM, // AudioChannel::System
+};
+
+
+struct AudioDeviceInfo {
+ /** The string the value maps to */
+ const char* tag;
+ /** The enum value that maps to this string */
+ uint32_t value;
+};
+
+// Mappings audio output devices to strings.
+static const AudioDeviceInfo kAudioDeviceInfos[] = {
+ { "earpiece", AUDIO_DEVICE_OUT_EARPIECE },
+ { "speaker", AUDIO_DEVICE_OUT_SPEAKER },
+ { "wired_headset", AUDIO_DEVICE_OUT_WIRED_HEADSET },
+ { "wired_headphone", AUDIO_DEVICE_OUT_WIRED_HEADPHONE },
+ { "bt_scoheadset", AUDIO_DEVICE_OUT_BLUETOOTH_SCO_HEADSET },
+ { "bt_a2dp", AUDIO_DEVICE_OUT_BLUETOOTH_A2DP },
+};
+
+static const int kBtSampleRate = 8000;
+
+typedef MozPromise<bool, const char*, true> VolumeInitPromise;
+
+namespace mozilla {
+namespace dom {
+namespace gonk {
+
+/**
+ * We have five sound volume settings from UX spec,
+ * You can see more informations in Bug1068219.
+ * (1) Media : music, video, FM ...
+ * (2) Notification : ringer, notification ...
+ * (3) Alarm : alarm
+ * (4) Telephony : GSM call, WebRTC call
+ * (5) Bluetooth SCO : SCO call
+ **/
+struct VolumeData {
+ const char* mChannelName;
+ int32_t mStreamType;
+};
+
+static const VolumeData gVolumeData[] = {
+ {"audio.volume.content", AUDIO_STREAM_MUSIC},
+ {"audio.volume.notification", AUDIO_STREAM_NOTIFICATION},
+ {"audio.volume.alarm", AUDIO_STREAM_ALARM},
+ {"audio.volume.telephony", AUDIO_STREAM_VOICE_CALL},
+ {"audio.volume.bt_sco", AUDIO_STREAM_BLUETOOTH_SCO}
+};
+
+class RunnableCallTask : public Runnable
+{
+public:
+ explicit RunnableCallTask(nsIRunnable* aRunnable)
+ : mRunnable(aRunnable) {}
+
+ NS_IMETHOD Run() override
+ {
+ return mRunnable->Run();
+ }
+protected:
+ nsCOMPtr<nsIRunnable> mRunnable;
+};
+
+nsCOMPtr<nsISettingsServiceLock>
+GetSettingServiceLock()
+{
+ nsresult rv;
+ nsCOMPtr<nsISettingsService> service = do_GetService(SETTINGS_SERVICE, &rv);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return nullptr;
+ }
+
+ nsCOMPtr<nsISettingsServiceLock> lock;
+ rv = service->CreateLock(nullptr, getter_AddRefs(lock));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return nullptr;
+ }
+ return lock.forget();
+}
+
+#if ANDROID_VERSION >= 21
+class GonkAudioPortCallback : public AudioSystem::AudioPortCallback
+{
+public:
+ virtual void onAudioPortListUpdate()
+ {
+ nsCOMPtr<nsIRunnable> runnable =
+ NS_NewRunnableFunction([]() {
+ MOZ_ASSERT(NS_IsMainThread());
+ RefPtr<AudioManager> audioManager = AudioManager::GetInstance();
+ NS_ENSURE_TRUE(audioManager.get(), );
+ audioManager->UpdateCachedActiveDevicesForStreams();
+ audioManager->MaybeUpdateVolumeSettingToDatabase();
+ });
+ NS_DispatchToMainThread(runnable);
+ }
+ virtual void onAudioPatchListUpdate() { }
+ virtual void onServiceDied() { }
+};
+#endif
+
+void
+AudioManager::HandleAudioFlingerDied()
+{
+ //Disable volume change notification
+ mIsVolumeInited = false;
+
+ uint32_t attempt;
+ for (attempt = 0; attempt < 50; attempt++) {
+ if (defaultServiceManager()->checkService(String16(AUDIO_POLICY_SERVICE_NAME)) != 0) {
+ break;
+ }
+ LOG("AudioPolicyService is dead! attempt=%d", attempt);
+ usleep(1000 * 200);
+ }
+
+ MOZ_RELEASE_ASSERT(attempt < 50);
+
+ // Indicate to audio HAL that we start the reconfiguration phase after a media
+ // server crash
+ AudioSystem::setParameters(0, String8("restarting=true"));
+
+ // Restore device connection states
+ SetAllDeviceConnectionStates();
+
+ // Restore call state
+#if ANDROID_VERSION < 17
+ AudioSystem::setPhoneState(mPhoneState);
+#else
+ AudioSystem::setPhoneState(static_cast<audio_mode_t>(mPhoneState));
+#endif
+
+ // Restore master volume
+ AudioSystem::setMasterVolume(1.0);
+
+ // Restore stream volumes
+ for (uint32_t streamType = 0; streamType < AUDIO_STREAM_CNT; ++streamType) {
+ mStreamStates[streamType]->InitStreamVolume();
+ mStreamStates[streamType]->RestoreVolumeIndexToAllDevices();
+ }
+
+ // Indicate the end of reconfiguration phase to audio HAL
+ AudioSystem::setParameters(0, String8("restarting=true"));
+
+ // Enable volume change notification
+ mIsVolumeInited = true;
+ mAudioOutDevicesUpdated = 0;
+ MaybeUpdateVolumeSettingToDatabase(true);
+}
+
+class VolumeInitCallback final : public nsISettingsServiceCallback
+{
+public:
+ NS_DECL_ISUPPORTS
+
+ VolumeInitCallback()
+ : mInitCounter(0)
+ {
+ mPromise = mPromiseHolder.Ensure(__func__);
+ }
+
+ RefPtr<VolumeInitPromise> GetPromise() const
+ {
+ return mPromise;
+ }
+
+ NS_IMETHOD Handle(const nsAString& aName, JS::Handle<JS::Value> aResult)
+ {
+ RefPtr<AudioManager> audioManager = AudioManager::GetInstance();
+ MOZ_ASSERT(audioManager);
+ for (uint32_t idx = 0; idx < MOZ_ARRAY_LENGTH(gVolumeData); ++idx) {
+ NS_ConvertASCIItoUTF16 volumeType(gVolumeData[idx].mChannelName);
+ if (StringBeginsWith(aName, volumeType)) {
+ uint32_t device = GetDeviceFromSettingName(aName);
+ MOZ_ASSERT(device != AUDIO_DEVICE_NONE);
+ if (aResult.isInt32()) {
+ int32_t stream = gVolumeData[idx].mStreamType;
+ uint32_t volIndex = aResult.toInt32();
+ nsresult rv = audioManager->ValidateVolumeIndex(stream, volIndex);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ mPromiseHolder.Reject("Error : invalid volume index.", __func__);
+ return rv;
+ }
+ audioManager->SetStreamVolumeForDevice(stream, volIndex, device);
+ }
+
+ if (++mInitCounter == MOZ_ARRAY_LENGTH(kAudioDeviceInfos) * MOZ_ARRAY_LENGTH(gVolumeData)) {
+ mPromiseHolder.Resolve(true, __func__);
+ }
+ return NS_OK;
+ }
+ }
+ mPromiseHolder.Reject("Error : unexpected audio init event.", __func__);
+ return NS_OK;
+ }
+
+ NS_IMETHOD HandleError(const nsAString& aName)
+ {
+ mPromiseHolder.Reject(NS_ConvertUTF16toUTF8(aName).get(), __func__);
+ return NS_OK;
+ }
+
+protected:
+ ~VolumeInitCallback() {}
+
+ uint32_t GetDeviceFromSettingName(const nsAString& aName) const
+ {
+ for (uint32_t idx = 0; idx < MOZ_ARRAY_LENGTH(kAudioDeviceInfos); ++idx) {
+ NS_ConvertASCIItoUTF16 device(kAudioDeviceInfos[idx].tag);
+ if (StringEndsWith(aName, device)) {
+ return kAudioDeviceInfos[idx].value;
+ }
+ }
+ return AUDIO_DEVICE_NONE;
+ }
+
+ RefPtr<VolumeInitPromise> mPromise;
+ MozPromiseHolder<VolumeInitPromise> mPromiseHolder;
+ uint32_t mInitCounter;
+};
+
+NS_IMPL_ISUPPORTS(VolumeInitCallback, nsISettingsServiceCallback)
+
+static void
+BinderDeadCallback(status_t aErr)
+{
+ if (aErr != DEAD_OBJECT) {
+ return;
+ }
+
+ nsCOMPtr<nsIRunnable> runnable =
+ NS_NewRunnableFunction([]() {
+ MOZ_ASSERT(NS_IsMainThread());
+ RefPtr<AudioManager> audioManager = AudioManager::GetInstance();
+ NS_ENSURE_TRUE(audioManager.get(), );
+ audioManager->HandleAudioFlingerDied();
+ });
+
+ NS_DispatchToMainThread(runnable);
+}
+
+bool
+AudioManager::IsFmOutConnected()
+{
+ return mConnectedDevices.Get(AUDIO_DEVICE_OUT_FM, nullptr);
+}
+
+NS_IMPL_ISUPPORTS(AudioManager, nsIAudioManager, nsIObserver)
+
+void
+AudioManager::AudioOutDeviceUpdated(uint32_t aDevice)
+{
+ MOZ_ASSERT(audio_is_output_device(aDevice));
+ mAudioOutDevicesUpdated |= aDevice;
+}
+
+void
+AudioManager::UpdateHeadsetConnectionState(hal::SwitchState aState)
+{
+ bool headphoneConnected = mConnectedDevices.Get(AUDIO_DEVICE_OUT_WIRED_HEADPHONE,
+ nullptr);
+ bool headsetConnected = mConnectedDevices.Get(AUDIO_DEVICE_OUT_WIRED_HEADSET,
+ nullptr);
+ if (aState == hal::SWITCH_STATE_HEADSET) {
+ UpdateDeviceConnectionState(true,
+ AUDIO_DEVICE_OUT_WIRED_HEADSET,
+ NS_LITERAL_CSTRING(""));
+ } else if (aState == hal::SWITCH_STATE_HEADPHONE) {
+ UpdateDeviceConnectionState(true,
+ AUDIO_DEVICE_OUT_WIRED_HEADPHONE,
+ NS_LITERAL_CSTRING(""));
+ } else if (aState == hal::SWITCH_STATE_OFF) {
+ if (headsetConnected) {
+ UpdateDeviceConnectionState(false,
+ AUDIO_DEVICE_OUT_WIRED_HEADSET,
+ NS_LITERAL_CSTRING(""));
+ }
+ if (headphoneConnected) {
+ UpdateDeviceConnectionState(false,
+ AUDIO_DEVICE_OUT_WIRED_HEADPHONE,
+ NS_LITERAL_CSTRING(""));
+ }
+ }
+}
+
+void
+AudioManager::UpdateDeviceConnectionState(bool aIsConnected, uint32_t aDevice, const nsCString& aDeviceName)
+{
+#if ANDROID_VERSION >= 15
+ bool isConnected = mConnectedDevices.Get(aDevice, nullptr);
+ if (isConnected && !aIsConnected) {
+ AudioSystem::setDeviceConnectionState(static_cast<audio_devices_t>(aDevice),
+ AUDIO_POLICY_DEVICE_STATE_UNAVAILABLE,
+ aDeviceName.get());
+ mConnectedDevices.Remove(aDevice);
+ } else if(!isConnected && aIsConnected) {
+ AudioSystem::setDeviceConnectionState(static_cast<audio_devices_t>(aDevice),
+ AUDIO_POLICY_DEVICE_STATE_AVAILABLE,
+ aDeviceName.get());
+ mConnectedDevices.Put(aDevice, aDeviceName);
+ }
+#if ANDROID_VERSION < 21
+ // Manually call it, since AudioPortCallback is not supported.
+ // Current volumes might be changed by updating active devices in android
+ // AudioPolicyManager.
+ MaybeUpdateVolumeSettingToDatabase();
+#endif
+#else
+ NS_NOTREACHED("Doesn't support audio routing on GB version");
+#endif
+}
+
+void
+AudioManager::SetAllDeviceConnectionStates()
+{
+ for (auto iter = mConnectedDevices.Iter(); !iter.Done(); iter.Next()) {
+ const uint32_t& device = iter.Key();
+ nsCString& deviceAddress = iter.Data();
+ AudioSystem::setDeviceConnectionState(static_cast<audio_devices_t>(device),
+ AUDIO_POLICY_DEVICE_STATE_AVAILABLE,
+ deviceAddress.get());
+ }
+#if ANDROID_VERSION < 21
+ // Manually call it, since AudioPortCallback is not supported.
+ // Current volumes might be changed by updating active devices in android
+ // AudioPolicyManager.
+ MaybeUpdateVolumeSettingToDatabase(true);
+#endif
+}
+
+void
+AudioManager::HandleBluetoothStatusChanged(nsISupports* aSubject,
+ const char* aTopic,
+ const nsCString aAddress)
+{
+#ifdef MOZ_B2G_BT
+ bool isConnected = false;
+ if (!strcmp(aTopic, BLUETOOTH_SCO_STATUS_CHANGED_ID)) {
+ BluetoothHfpManagerBase* hfp =
+ static_cast<BluetoothHfpManagerBase*>(aSubject);
+ isConnected = hfp->IsScoConnected();
+ } else {
+ BluetoothProfileManagerBase* profile =
+ static_cast<BluetoothProfileManagerBase*>(aSubject);
+ isConnected = profile->IsConnected();
+ }
+
+ if (!strcmp(aTopic, BLUETOOTH_SCO_STATUS_CHANGED_ID)) {
+ if (isConnected) {
+ String8 cmd;
+ cmd.appendFormat("bt_samplerate=%d", kBtSampleRate);
+ AudioSystem::setParameters(0, cmd);
+ SetForceForUse(nsIAudioManager::USE_COMMUNICATION, nsIAudioManager::FORCE_BT_SCO);
+ } else {
+ int32_t force;
+ GetForceForUse(nsIAudioManager::USE_COMMUNICATION, &force);
+ if (force == nsIAudioManager::FORCE_BT_SCO) {
+ SetForceForUse(nsIAudioManager::USE_COMMUNICATION, nsIAudioManager::FORCE_NONE);
+ }
+ }
+ } else if (!strcmp(aTopic, BLUETOOTH_A2DP_STATUS_CHANGED_ID)) {
+ if (!isConnected && mA2dpSwitchDone) {
+ RefPtr<AudioManager> self = this;
+ nsCOMPtr<nsIRunnable> runnable =
+ NS_NewRunnableFunction([self, isConnected, aAddress]() {
+ if (self->mA2dpSwitchDone) {
+ return;
+ }
+ self->UpdateDeviceConnectionState(isConnected,
+ AUDIO_DEVICE_OUT_BLUETOOTH_A2DP,
+ aAddress);
+
+ String8 cmd("bluetooth_enabled=false");
+ AudioSystem::setParameters(0, cmd);
+ cmd.setTo("A2dpSuspended=true");
+ AudioSystem::setParameters(0, cmd);
+ self->mA2dpSwitchDone = true;
+ });
+ MessageLoop::current()->PostDelayedTask(
+ MakeAndAddRef<RunnableCallTask>(runnable), 1000);
+
+ mA2dpSwitchDone = false;
+ } else {
+ UpdateDeviceConnectionState(isConnected,
+ AUDIO_DEVICE_OUT_BLUETOOTH_A2DP,
+ aAddress);
+ String8 cmd("bluetooth_enabled=true");
+ AudioSystem::setParameters(0, cmd);
+ cmd.setTo("A2dpSuspended=false");
+ AudioSystem::setParameters(0, cmd);
+ mA2dpSwitchDone = true;
+#if ANDROID_VERSION >= 17
+ if (AudioSystem::getForceUse(AUDIO_POLICY_FORCE_FOR_MEDIA) == AUDIO_POLICY_FORCE_NO_BT_A2DP) {
+ SetForceForUse(AUDIO_POLICY_FORCE_FOR_MEDIA, AUDIO_POLICY_FORCE_NONE);
+ }
+#endif
+ }
+ mBluetoothA2dpEnabled = isConnected;
+ } else if (!strcmp(aTopic, BLUETOOTH_HFP_STATUS_CHANGED_ID)) {
+ UpdateDeviceConnectionState(isConnected,
+ AUDIO_DEVICE_OUT_BLUETOOTH_SCO_HEADSET,
+ aAddress);
+ UpdateDeviceConnectionState(isConnected,
+ AUDIO_DEVICE_IN_BLUETOOTH_SCO_HEADSET,
+ aAddress);
+ } else if (!strcmp(aTopic, BLUETOOTH_HFP_NREC_STATUS_CHANGED_ID)) {
+ String8 cmd;
+ BluetoothHfpManagerBase* hfp =
+ static_cast<BluetoothHfpManagerBase*>(aSubject);
+ if (hfp->IsNrecEnabled()) {
+ cmd.setTo("bt_headset_name=<unknown>;bt_headset_nrec=on");
+ AudioSystem::setParameters(0, cmd);
+ } else {
+ cmd.setTo("bt_headset_name=<unknown>;bt_headset_nrec=off");
+ AudioSystem::setParameters(0, cmd);
+ }
+ }
+#endif
+}
+
+void
+AudioManager::HandleAudioChannelProcessChanged()
+{
+ // Note: If the user answers a VoIP call (e.g. WebRTC calls) during the
+ // telephony call (GSM/CDMA calls) the audio manager won't set the
+ // PHONE_STATE_IN_COMMUNICATION audio state. Once the telephony call finishes
+ // the RIL plumbing sets the PHONE_STATE_NORMAL audio state. This seems to be
+ // an issue for the VoIP call but it is not. Once the RIL plumbing sets the
+ // the PHONE_STATE_NORMAL audio state the AudioManager::mPhoneAudioAgent
+ // member will call the NotifyStoppedPlaying() method causing that this function will
+ // be called again and therefore the audio manager sets the
+ // PHONE_STATE_IN_COMMUNICATION audio state.
+
+ if ((mPhoneState == PHONE_STATE_IN_CALL) ||
+ (mPhoneState == PHONE_STATE_RINGTONE)) {
+ return;
+ }
+
+ RefPtr<AudioChannelService> service = AudioChannelService::GetOrCreate();
+ bool telephonyChannelIsActive = service && service->TelephonyChannelIsActive();
+ telephonyChannelIsActive ? SetPhoneState(PHONE_STATE_IN_COMMUNICATION) :
+ SetPhoneState(PHONE_STATE_NORMAL);
+}
+
+nsresult
+AudioManager::Observe(nsISupports* aSubject,
+ const char* aTopic,
+ const char16_t* aData)
+{
+ if ((strcmp(aTopic, BLUETOOTH_SCO_STATUS_CHANGED_ID) == 0) ||
+ (strcmp(aTopic, BLUETOOTH_HFP_STATUS_CHANGED_ID) == 0) ||
+ (strcmp(aTopic, BLUETOOTH_HFP_NREC_STATUS_CHANGED_ID) == 0) ||
+ (strcmp(aTopic, BLUETOOTH_A2DP_STATUS_CHANGED_ID) == 0)) {
+ nsCString address = NS_ConvertUTF16toUTF8(nsDependentString(aData));
+ if (address.IsEmpty()) {
+ NS_WARNING(nsPrintfCString("Invalid address of %s", aTopic).get());
+ return NS_ERROR_FAILURE;
+ }
+
+ HandleBluetoothStatusChanged(aSubject, aTopic, address);
+ return NS_OK;
+ }
+
+ else if (!strcmp(aTopic, AUDIO_CHANNEL_PROCESS_CHANGED)) {
+ HandleAudioChannelProcessChanged();
+ return NS_OK;
+ }
+
+ // To process the volume control on each volume categories according to
+ // change of settings
+ else if (!strcmp(aTopic, MOZ_SETTINGS_CHANGE_ID)) {
+ RootedDictionary<dom::SettingChangeNotification> setting(RootingCx());
+ if (!WrappedJSToDictionary(aSubject, setting)) {
+ return NS_OK;
+ }
+ if (!StringBeginsWith(setting.mKey, NS_LITERAL_STRING("audio.volume."))) {
+ return NS_OK;
+ }
+ if (!setting.mValue.isNumber()) {
+ return NS_OK;
+ }
+
+ uint32_t volIndex = setting.mValue.toNumber();
+ for (uint32_t idx = 0; idx < MOZ_ARRAY_LENGTH(gVolumeData); ++idx) {
+ if (setting.mKey.EqualsASCII(gVolumeData[idx].mChannelName)) {
+ SetStreamVolumeIndex(gVolumeData[idx].mStreamType, volIndex);
+ return NS_OK;
+ }
+ }
+ }
+
+ NS_WARNING("Unexpected topic in AudioManager");
+ return NS_ERROR_FAILURE;
+}
+
+static void
+NotifyHeadphonesStatus(hal::SwitchState aState)
+{
+ nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
+ if (obs) {
+ if (aState == hal::SWITCH_STATE_HEADSET) {
+ obs->NotifyObservers(nullptr, HEADPHONES_STATUS_CHANGED, HEADPHONES_STATUS_HEADSET);
+ } else if (aState == hal::SWITCH_STATE_HEADPHONE) {
+ obs->NotifyObservers(nullptr, HEADPHONES_STATUS_CHANGED, HEADPHONES_STATUS_HEADPHONE);
+ } else if (aState == hal::SWITCH_STATE_OFF) {
+ obs->NotifyObservers(nullptr, HEADPHONES_STATUS_CHANGED, HEADPHONES_STATUS_OFF);
+ } else {
+ obs->NotifyObservers(nullptr, HEADPHONES_STATUS_CHANGED, HEADPHONES_STATUS_UNKNOWN);
+ }
+ }
+}
+
+class HeadphoneSwitchObserver : public hal::SwitchObserver
+{
+public:
+ void Notify(const hal::SwitchEvent& aEvent) {
+ RefPtr<AudioManager> audioManager = AudioManager::GetInstance();
+ MOZ_ASSERT(audioManager);
+ audioManager->HandleHeadphoneSwitchEvent(aEvent);
+ }
+};
+
+void
+AudioManager::HandleHeadphoneSwitchEvent(const hal::SwitchEvent& aEvent)
+{
+ NotifyHeadphonesStatus(aEvent.status());
+ // When user pulled out the headset, a delay of routing here can avoid the leakage of audio from speaker.
+ if (aEvent.status() == hal::SWITCH_STATE_OFF && mSwitchDone) {
+
+ RefPtr<AudioManager> self = this;
+ nsCOMPtr<nsIRunnable> runnable =
+ NS_NewRunnableFunction([self]() {
+ if (self->mSwitchDone) {
+ return;
+ }
+ self->UpdateHeadsetConnectionState(hal::SWITCH_STATE_OFF);
+ self->mSwitchDone = true;
+ });
+ MessageLoop::current()->PostDelayedTask(
+ MakeAndAddRef<RunnableCallTask>(runnable), 1000);
+ mSwitchDone = false;
+ } else if (aEvent.status() != hal::SWITCH_STATE_OFF) {
+ UpdateHeadsetConnectionState(aEvent.status());
+ mSwitchDone = true;
+ }
+ // Handle the coexistence of a2dp / headset device, latest one wins.
+#if ANDROID_VERSION >= 17
+ int32_t forceUse = 0;
+ GetForceForUse(AUDIO_POLICY_FORCE_FOR_MEDIA, &forceUse);
+ if (aEvent.status() != hal::SWITCH_STATE_OFF && mBluetoothA2dpEnabled) {
+ SetForceForUse(AUDIO_POLICY_FORCE_FOR_MEDIA, AUDIO_POLICY_FORCE_NO_BT_A2DP);
+ } else if (forceUse == AUDIO_POLICY_FORCE_NO_BT_A2DP) {
+ SetForceForUse(AUDIO_POLICY_FORCE_FOR_MEDIA, AUDIO_POLICY_FORCE_NONE);
+ }
+#endif
+}
+
+AudioManager::AudioManager()
+ : mPhoneState(PHONE_STATE_CURRENT)
+ , mIsVolumeInited(false)
+ , mAudioOutDevicesUpdated(0)
+ , mSwitchDone(true)
+#if defined(MOZ_B2G_BT) || ANDROID_VERSION >= 17
+ , mBluetoothA2dpEnabled(false)
+#endif
+#ifdef MOZ_B2G_BT
+ , mA2dpSwitchDone(true)
+#endif
+ , mObserver(new HeadphoneSwitchObserver())
+{
+ for (uint32_t idx = 0; idx < MOZ_ARRAY_LENGTH(kAudioDeviceInfos); ++idx) {
+ mAudioDeviceTableIdMaps.Put(kAudioDeviceInfos[idx].value, idx);
+ }
+
+ AudioSystem::setErrorCallback(BinderDeadCallback);
+#if ANDROID_VERSION >= 21
+ android::sp<GonkAudioPortCallback> callback = new GonkAudioPortCallback();
+ AudioSystem::setAudioPortCallback(callback);
+#endif
+
+ // Create VolumeStreamStates
+ for (uint32_t loop = 0; loop < AUDIO_STREAM_CNT; ++loop) {
+ VolumeStreamState* streamState =
+ new VolumeStreamState(*this, static_cast<audio_stream_type_t>(loop));
+ mStreamStates.AppendElement(streamState);
+ }
+ // Initialize stream volumes with default values
+ for (int32_t streamType = 0; streamType < AUDIO_STREAM_MAX; streamType++) {
+ uint32_t volIndex = sDefaultStreamVolumeTbl[streamType];
+ SetStreamVolumeForDevice(streamType, volIndex, AUDIO_DEVICE_OUT_DEFAULT);
+ }
+ UpdateCachedActiveDevicesForStreams();
+
+ RegisterSwitchObserver(hal::SWITCH_HEADPHONES, mObserver);
+ // Initialize headhone/heaset status
+ UpdateHeadsetConnectionState(hal::GetCurrentSwitchState(hal::SWITCH_HEADPHONES));
+ NotifyHeadphonesStatus(hal::GetCurrentSwitchState(hal::SWITCH_HEADPHONES));
+
+ // Get the initial volume index from settings DB during boot up.
+ InitVolumeFromDatabase();
+
+ // Gecko only control stream volume not master so set to default value
+ // directly.
+ AudioSystem::setMasterVolume(1.0);
+
+ nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
+ NS_ENSURE_TRUE_VOID(obs);
+ if (NS_FAILED(obs->AddObserver(this, BLUETOOTH_SCO_STATUS_CHANGED_ID, false))) {
+ NS_WARNING("Failed to add bluetooth sco status changed observer!");
+ }
+ if (NS_FAILED(obs->AddObserver(this, BLUETOOTH_A2DP_STATUS_CHANGED_ID, false))) {
+ NS_WARNING("Failed to add bluetooth a2dp status changed observer!");
+ }
+ if (NS_FAILED(obs->AddObserver(this, BLUETOOTH_HFP_STATUS_CHANGED_ID, false))) {
+ NS_WARNING("Failed to add bluetooth hfp status changed observer!");
+ }
+ if (NS_FAILED(obs->AddObserver(this, BLUETOOTH_HFP_NREC_STATUS_CHANGED_ID, false))) {
+ NS_WARNING("Failed to add bluetooth hfp NREC status changed observer!");
+ }
+ if (NS_FAILED(obs->AddObserver(this, MOZ_SETTINGS_CHANGE_ID, false))) {
+ NS_WARNING("Failed to add mozsettings-changed observer!");
+ }
+ if (NS_FAILED(obs->AddObserver(this, AUDIO_CHANNEL_PROCESS_CHANGED, false))) {
+ NS_WARNING("Failed to add audio-channel-process-changed observer!");
+ }
+
+}
+
+AudioManager::~AudioManager() {
+ AudioSystem::setErrorCallback(nullptr);
+#if ANDROID_VERSION >= 21
+ AudioSystem::setAudioPortCallback(nullptr);
+#endif
+ hal::UnregisterSwitchObserver(hal::SWITCH_HEADPHONES, mObserver);
+
+ nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
+ NS_ENSURE_TRUE_VOID(obs);
+ if (NS_FAILED(obs->RemoveObserver(this, BLUETOOTH_SCO_STATUS_CHANGED_ID))) {
+ NS_WARNING("Failed to remove bluetooth sco status changed observer!");
+ }
+ if (NS_FAILED(obs->RemoveObserver(this, BLUETOOTH_A2DP_STATUS_CHANGED_ID))) {
+ NS_WARNING("Failed to remove bluetooth a2dp status changed observer!");
+ }
+ if (NS_FAILED(obs->RemoveObserver(this, BLUETOOTH_HFP_STATUS_CHANGED_ID))) {
+ NS_WARNING("Failed to remove bluetooth hfp status changed observer!");
+ }
+ if (NS_FAILED(obs->RemoveObserver(this, BLUETOOTH_HFP_NREC_STATUS_CHANGED_ID))) {
+ NS_WARNING("Failed to remove bluetooth hfp NREC status changed observer!");
+ }
+ if (NS_FAILED(obs->RemoveObserver(this, MOZ_SETTINGS_CHANGE_ID))) {
+ NS_WARNING("Failed to remove mozsettings-changed observer!");
+ }
+ if (NS_FAILED(obs->RemoveObserver(this, AUDIO_CHANNEL_PROCESS_CHANGED))) {
+ NS_WARNING("Failed to remove audio-channel-process-changed!");
+ }
+}
+
+static StaticRefPtr<AudioManager> sAudioManager;
+
+already_AddRefed<AudioManager>
+AudioManager::GetInstance()
+{
+ // Avoid createing AudioManager from content process.
+ if (!XRE_IsParentProcess()) {
+ MOZ_CRASH("Non-chrome processes should not get here.");
+ }
+
+ // Avoid createing multiple AudioManager instance inside main process.
+ if (!sAudioManager) {
+ sAudioManager = new AudioManager();
+ ClearOnShutdown(&sAudioManager);
+ }
+
+ RefPtr<AudioManager> audioMgr = sAudioManager.get();
+ return audioMgr.forget();
+}
+
+NS_IMETHODIMP
+AudioManager::GetMicrophoneMuted(bool* aMicrophoneMuted)
+{
+
+ if (AudioSystem::isMicrophoneMuted(aMicrophoneMuted)) {
+ return NS_ERROR_FAILURE;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+AudioManager::SetMicrophoneMuted(bool aMicrophoneMuted)
+{
+ if (!AudioSystem::muteMicrophone(aMicrophoneMuted)) {
+ return NS_OK;
+ }
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+AudioManager::GetPhoneState(int32_t* aState)
+{
+ *aState = mPhoneState;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+AudioManager::SetPhoneState(int32_t aState)
+{
+ if (mPhoneState == aState) {
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
+ if (obs) {
+ nsString state;
+ state.AppendInt(aState);
+ obs->NotifyObservers(nullptr, "phone-state-changed", state.get());
+ }
+
+#if ANDROID_VERSION < 17
+ if (AudioSystem::setPhoneState(aState)) {
+#else
+ if (AudioSystem::setPhoneState(static_cast<audio_mode_t>(aState))) {
+#endif
+ return NS_ERROR_FAILURE;
+ }
+
+#if ANDROID_VERSION < 21
+ // Manually call it, since AudioPortCallback is not supported.
+ // Current volumes might be changed by updating active devices in android
+ // AudioPolicyManager.
+ MaybeUpdateVolumeSettingToDatabase();
+#endif
+ mPhoneState = aState;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+AudioManager::SetForceForUse(int32_t aUsage, int32_t aForce)
+{
+#if ANDROID_VERSION >= 15
+ status_t status = AudioSystem::setForceUse(
+ (audio_policy_force_use_t)aUsage,
+ (audio_policy_forced_cfg_t)aForce);
+#if ANDROID_VERSION < 21
+ // Manually call it, since AudioPortCallback is not supported.
+ // Current volumes might be changed by updating active devices in android
+ // AudioPolicyManager.
+ MaybeUpdateVolumeSettingToDatabase();
+#endif
+ return status ? NS_ERROR_FAILURE : NS_OK;
+#else
+ NS_NOTREACHED("Doesn't support force routing on GB version");
+ return NS_ERROR_UNEXPECTED;
+#endif
+}
+
+NS_IMETHODIMP
+AudioManager::GetForceForUse(int32_t aUsage, int32_t* aForce) {
+#if ANDROID_VERSION >= 15
+ *aForce = AudioSystem::getForceUse((audio_policy_force_use_t)aUsage);
+ return NS_OK;
+#else
+ NS_NOTREACHED("Doesn't support force routing on GB version");
+ return NS_ERROR_UNEXPECTED;
+#endif
+}
+
+NS_IMETHODIMP
+AudioManager::SetAudioChannelVolume(uint32_t aChannel, uint32_t aIndex)
+{
+ if (aChannel >= NUMBER_OF_AUDIO_CHANNELS) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ return SetStreamVolumeIndex(sChannelStreamTbl[aChannel], aIndex);
+}
+
+NS_IMETHODIMP
+AudioManager::GetAudioChannelVolume(uint32_t aChannel, uint32_t* aIndex)
+{
+ if (aChannel >= NUMBER_OF_AUDIO_CHANNELS) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ if (!aIndex) {
+ return NS_ERROR_NULL_POINTER;
+ }
+
+ return GetStreamVolumeIndex(sChannelStreamTbl[aChannel], aIndex);
+}
+
+NS_IMETHODIMP
+AudioManager::GetMaxAudioChannelVolume(uint32_t aChannel, uint32_t* aMaxIndex)
+{
+ if (aChannel >= NUMBER_OF_AUDIO_CHANNELS) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ if (!aMaxIndex) {
+ return NS_ERROR_NULL_POINTER;
+ }
+
+ *aMaxIndex = mStreamStates[sChannelStreamTbl[aChannel]]->GetMaxIndex();
+ return NS_OK;
+}
+
+nsresult
+AudioManager::ValidateVolumeIndex(int32_t aStream, uint32_t aIndex) const
+{
+ if (aStream <= AUDIO_STREAM_DEFAULT || aStream >= AUDIO_STREAM_MAX) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ uint32_t maxIndex = mStreamStates[aStream]->GetMaxIndex();
+ if (aIndex > maxIndex) {
+ return NS_ERROR_FAILURE;
+ }
+ return NS_OK;
+}
+
+nsresult
+AudioManager::SetStreamVolumeForDevice(int32_t aStream,
+ uint32_t aIndex,
+ uint32_t aDevice)
+{
+ if (aStream <= AUDIO_STREAM_DEFAULT || aStream >= AUDIO_STREAM_MAX) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ int32_t streamAlias = sStreamVolumeAliasTbl[aStream];
+ VolumeStreamState* streamState = mStreamStates[streamAlias].get();
+ return streamState->SetVolumeIndexToAliasStreams(aIndex, aDevice);
+}
+
+nsresult
+AudioManager::SetStreamVolumeIndex(int32_t aStream, uint32_t aIndex)
+{
+ if (aStream <= AUDIO_STREAM_DEFAULT || aStream >= AUDIO_STREAM_MAX) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ int32_t streamAlias = sStreamVolumeAliasTbl[aStream];
+
+ nsresult rv;
+ for (int32_t streamType = 0; streamType < AUDIO_STREAM_MAX; streamType++) {
+ if (streamAlias == sStreamVolumeAliasTbl[streamType]) {
+ rv = mStreamStates[streamType]->SetVolumeIndexToActiveDevices(aIndex);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ }
+ }
+
+ // AUDIO_STREAM_FM is not used on recent gonk.
+ // AUDIO_STREAM_MUSIC is used for FM radio volume control.
+#if ANDROID_VERSION < 19
+ if (streamAlias == AUDIO_STREAM_MUSIC && IsFmOutConnected()) {
+ rv = mStreamStates[AUDIO_STREAM_FM]->
+ SetVolumeIndex(aIndex, AUDIO_DEVICE_OUT_FM);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ }
+#endif
+
+ MaybeUpdateVolumeSettingToDatabase();
+ return NS_OK;
+}
+
+nsresult
+AudioManager::GetStreamVolumeIndex(int32_t aStream, uint32_t *aIndex)
+{
+ if (!aIndex) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ if (aStream <= AUDIO_STREAM_DEFAULT || aStream >= AUDIO_STREAM_MAX) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ *aIndex = mStreamStates[aStream]->GetVolumeIndex();
+ return NS_OK;
+}
+
+nsAutoCString
+AudioManager::AppendDeviceToVolumeSetting(const char* aName, uint32_t aDevice)
+{
+ nsAutoCString topic;
+ topic.Assign(aName);
+ topic.Append(".");
+ uint32_t index = 0;
+ DebugOnly<bool> exist = mAudioDeviceTableIdMaps.Get(aDevice, &index);
+ MOZ_ASSERT(exist);
+ topic.Append(kAudioDeviceInfos[index].tag);
+ return topic;
+}
+
+void
+AudioManager::InitVolumeFromDatabase()
+{
+ nsresult rv;
+ nsCOMPtr<nsISettingsService> service = do_GetService(SETTINGS_SERVICE, &rv);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return;
+ }
+
+ nsCOMPtr<nsISettingsServiceLock> lock;
+ rv = service->CreateLock(nullptr, getter_AddRefs(lock));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return;
+ }
+
+ RefPtr<VolumeInitCallback> callback = new VolumeInitCallback();
+ MOZ_ASSERT(callback);
+ callback->GetPromise()->Then(AbstractThread::MainThread(), __func__, this,
+ &AudioManager::InitDeviceVolumeSucceeded,
+ &AudioManager::InitDeviceVolumeFailed);
+
+ for (uint32_t idx = 0; idx < MOZ_ARRAY_LENGTH(gVolumeData); ++idx) {
+ for (uint32_t idx2 = 0; idx2 < MOZ_ARRAY_LENGTH(kAudioDeviceInfos); ++idx2) {
+ lock->Get(AppendDeviceToVolumeSetting(gVolumeData[idx].mChannelName,
+ kAudioDeviceInfos[idx2].value).get(),
+ callback);
+ }
+ }
+}
+
+void
+AudioManager::InitDeviceVolumeSucceeded()
+{
+ mIsVolumeInited = true;
+ MaybeUpdateVolumeSettingToDatabase(true);
+}
+
+void
+AudioManager::InitDeviceVolumeFailed(const char* aError)
+{
+ // Default volume of AUDIO_DEVICE_OUT_DEFAULT is already set.
+ mIsVolumeInited = true;
+ MaybeUpdateVolumeSettingToDatabase(true);
+ NS_WARNING(aError);
+}
+
+void
+AudioManager::MaybeUpdateVolumeSettingToDatabase(bool aForce)
+{
+ if (!mIsVolumeInited) {
+ return;
+ }
+
+ nsCOMPtr<nsISettingsServiceLock> lock = GetSettingServiceLock();
+ if (NS_WARN_IF(!lock)) {
+ return;
+ }
+
+ // Send events to update the Gaia volumes
+ JS::Rooted<JS::Value> value(RootingCx());
+ uint32_t volume = 0;
+ for (uint32_t idx = 0; idx < MOZ_ARRAY_LENGTH(gVolumeData); ++idx) {
+ int32_t streamType = gVolumeData[idx].mStreamType;
+ VolumeStreamState* streamState = mStreamStates[streamType].get();
+ if(!aForce && !streamState->IsDevicesChanged()) {
+ continue;
+ }
+ // Get volume index of active device.
+ volume = streamState->GetVolumeIndex();
+ value.setInt32(volume);
+ lock->Set(gVolumeData[idx].mChannelName, value, nullptr, nullptr);
+ }
+
+ // For reducing the code dependency, Gaia doesn't need to know the
+ // device volume, it only need to care about different volume categories.
+ // However, we need to send the setting volume to the permanent database,
+ // so that we can store the volume setting even if the phone reboots.
+
+ for (uint32_t idx = 0; idx < MOZ_ARRAY_LENGTH(gVolumeData); ++idx) {
+ int32_t streamType = gVolumeData[idx].mStreamType;
+ VolumeStreamState* streamState = mStreamStates[streamType].get();
+
+ if(!streamState->IsVolumeIndexesChanged()) {
+ continue;
+ }
+
+ uint32_t remainingDevices = mAudioOutDevicesUpdated;
+ for (uint32_t i = 0; remainingDevices != 0; i++) {
+ uint32_t device = (1 << i);
+ if ((device & remainingDevices) == 0) {
+ continue;
+ }
+ remainingDevices &= ~device;
+ if (!mAudioDeviceTableIdMaps.Get(device, nullptr)) {
+ continue;
+ }
+ volume = streamState->GetVolumeIndex(device);
+ value.setInt32(volume);
+ lock->Set(AppendDeviceToVolumeSetting(gVolumeData[idx].mChannelName,
+ device).get(),
+ value, nullptr, nullptr);
+ }
+ }
+
+ // Clear changed flags
+ for (uint32_t idx = 0; idx < MOZ_ARRAY_LENGTH(gVolumeData); ++idx) {
+ int32_t streamType = gVolumeData[idx].mStreamType;
+ mStreamStates[streamType]->ClearDevicesChanged();
+ mStreamStates[streamType]->ClearVolumeIndexesChanged();
+ }
+ // Clear mAudioOutDevicesUpdated
+ mAudioOutDevicesUpdated = 0;
+}
+
+void
+AudioManager::UpdateCachedActiveDevicesForStreams()
+{
+ // This function updates cached active devices for streams.
+ // It is used for optimization of GetDevicesForStream() since L.
+ // AudioManager could know when active devices
+ // are changed in AudioPolicyManager by onAudioPortListUpdate().
+ // Except it, AudioManager normally do not need to ask AuidoPolicyManager
+ // about current active devices of streams and could use cached values.
+ // Before L, onAudioPortListUpdate() does not exist and GetDevicesForStream()
+ // does not use the cache. Therefore this function do nothing.
+#if ANDROID_VERSION >= 21
+ for (int32_t streamType = 0; streamType < AUDIO_STREAM_MAX; streamType++) {
+ // Update cached active devices of stream
+ mStreamStates[streamType]->IsDevicesChanged(false /* aFromCache */);
+ }
+#endif
+}
+
+uint32_t
+AudioManager::GetDevicesForStream(int32_t aStream, bool aFromCache)
+{
+#if ANDROID_VERSION >= 21
+ // Since Lollipop, devices update could be notified by AudioPortCallback.
+ // Cached values can be used if there is no update.
+ if (aFromCache) {
+ return mStreamStates[aStream]->GetLastDevices();
+ }
+#endif
+
+#if ANDROID_VERSION >= 17
+ audio_devices_t devices =
+ AudioSystem::getDevicesForStream(static_cast<audio_stream_type_t>(aStream));
+
+ return static_cast<uint32_t>(devices);
+#else
+ // Per audio out device volume is not supported.
+ // Use AUDIO_DEVICE_OUT_SPEAKER just to store audio volume to DB.
+ return AUDIO_DEVICE_OUT_SPEAKER;
+#endif
+}
+
+uint32_t
+AudioManager::GetDeviceForStream(int32_t aStream)
+{
+ uint32_t devices =
+ GetDevicesForStream(static_cast<audio_stream_type_t>(aStream));
+ uint32_t device = SelectDeviceFromDevices(devices);
+ return device;
+}
+
+/* static */ uint32_t
+AudioManager::SelectDeviceFromDevices(uint32_t aOutDevices)
+{
+ uint32_t device = aOutDevices;
+
+ // See android AudioService.getDeviceForStream().
+ // AudioPolicyManager expects it.
+ // See also android AudioPolicyManager::getDeviceForVolume().
+ if ((device & (device - 1)) != 0) {
+ // Multiple device selection.
+ if ((device & AUDIO_DEVICE_OUT_SPEAKER) != 0) {
+ device = AUDIO_DEVICE_OUT_SPEAKER;
+#if ANDROID_VERSION >= 21
+ } else if ((device & AUDIO_DEVICE_OUT_HDMI_ARC) != 0) {
+ device = AUDIO_DEVICE_OUT_HDMI_ARC;
+ } else if ((device & AUDIO_DEVICE_OUT_SPDIF) != 0) {
+ device = AUDIO_DEVICE_OUT_SPDIF;
+ } else if ((device & AUDIO_DEVICE_OUT_AUX_LINE) != 0) {
+ device = AUDIO_DEVICE_OUT_AUX_LINE;
+#endif
+ } else {
+ device &= AUDIO_DEVICE_OUT_ALL_A2DP;
+ }
+ }
+ MOZ_ASSERT(audio_is_output_device(device));
+ return device;
+}
+AudioManager::VolumeStreamState::VolumeStreamState(AudioManager& aManager,
+ int32_t aStreamType)
+ : mManager(aManager)
+ , mStreamType(aStreamType)
+ , mLastDevices(0)
+ , mIsDevicesChanged(true)
+ , mIsVolumeIndexesChanged(true)
+{
+ InitStreamVolume();
+}
+
+bool
+AudioManager::VolumeStreamState::IsDevicesChanged(bool aFromCache)
+{
+ uint32_t devices = mManager.GetDevicesForStream(mStreamType, aFromCache);
+ if (devices != mLastDevices) {
+ mLastDevices = devices;
+ mIsDevicesChanged = true;
+ }
+ return mIsDevicesChanged;
+}
+
+void
+AudioManager::VolumeStreamState::ClearDevicesChanged()
+{
+ mIsDevicesChanged = false;
+}
+
+bool
+AudioManager::VolumeStreamState::IsVolumeIndexesChanged()
+{
+ return mIsVolumeIndexesChanged;
+}
+
+void
+AudioManager::VolumeStreamState::ClearVolumeIndexesChanged()
+{
+ mIsVolumeIndexesChanged = false;
+}
+
+void
+AudioManager::VolumeStreamState::InitStreamVolume()
+{
+ AudioSystem::initStreamVolume(static_cast<audio_stream_type_t>(mStreamType),
+ 0,
+ GetMaxIndex());
+}
+
+uint32_t
+AudioManager::VolumeStreamState::GetMaxIndex()
+{
+ return sMaxStreamVolumeTbl[mStreamType];
+}
+
+uint32_t
+AudioManager::VolumeStreamState::GetDefaultIndex()
+{
+ return sDefaultStreamVolumeTbl[mStreamType];
+}
+
+uint32_t
+AudioManager::VolumeStreamState::GetVolumeIndex()
+{
+ uint32_t device = mManager.GetDeviceForStream(mStreamType);
+ return GetVolumeIndex(device);
+}
+
+uint32_t
+AudioManager::VolumeStreamState::GetVolumeIndex(uint32_t aDevice)
+{
+ uint32_t index = 0;
+ bool ret = mVolumeIndexes.Get(aDevice, &index);
+ if (!ret) {
+ index = mVolumeIndexes.Get(AUDIO_DEVICE_OUT_DEFAULT);
+ }
+ return index;
+}
+
+nsresult
+AudioManager::VolumeStreamState::SetVolumeIndexToActiveDevices(uint32_t aIndex)
+{
+ uint32_t device = mManager.GetDeviceForStream(mStreamType);
+
+ // Update volume index for device
+ uint32_t oldVolumeIndex = 0;
+ bool exist = mVolumeIndexes.Get(device, &oldVolumeIndex);
+ if (exist && aIndex == oldVolumeIndex) {
+ // No update
+ return NS_OK;
+ }
+
+ // AudioPolicyManager::setStreamVolumeIndex() set volumes of all active
+ // devices for stream.
+ nsresult rv;
+ rv = SetVolumeIndexToConsistentDeviceIfNeeded(aIndex, device);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ return NS_OK;
+}
+
+nsresult
+AudioManager::VolumeStreamState::SetVolumeIndexToAliasStreams(uint32_t aIndex,
+ uint32_t aDevice)
+{
+ uint32_t oldVolumeIndex = 0;
+ bool exist = mVolumeIndexes.Get(aDevice, &oldVolumeIndex);
+ if (exist && aIndex == oldVolumeIndex) {
+ // No update
+ return NS_OK;
+ }
+
+ nsresult rv = SetVolumeIndexToConsistentDeviceIfNeeded(aIndex, aDevice);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ for (int32_t streamType = 0; streamType < AUDIO_STREAM_MAX; streamType++) {
+ if ((streamType != mStreamType) &&
+ sStreamVolumeAliasTbl[streamType] == mStreamType) {
+ // Rescaling of index is not necessary.
+ rv = mManager.mStreamStates[streamType]->
+ SetVolumeIndexToAliasStreams(aIndex, aDevice);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ }
+ }
+
+ return NS_OK;
+}
+
+nsresult
+AudioManager::VolumeStreamState::SetVolumeIndexToConsistentDeviceIfNeeded(uint32_t aIndex, uint32_t aDevice)
+{
+ nsresult rv;
+ if (aDevice == AUDIO_DEVICE_OUT_SPEAKER || aDevice == AUDIO_DEVICE_OUT_EARPIECE) {
+ // Set AUDIO_DEVICE_OUT_SPEAKER and AUDIO_DEVICE_OUT_EARPIECE to same volume.
+ rv = SetVolumeIndex(aIndex, AUDIO_DEVICE_OUT_SPEAKER);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ rv = SetVolumeIndex(aIndex, AUDIO_DEVICE_OUT_EARPIECE);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ } else {
+ // No alias device
+ rv = SetVolumeIndex(aIndex, aDevice);
+ }
+ return rv;
+}
+
+nsresult
+AudioManager::VolumeStreamState::SetVolumeIndex(uint32_t aIndex,
+ uint32_t aDevice,
+ bool aUpdateCache)
+{
+ status_t rv;
+#if ANDROID_VERSION >= 17
+ if (aUpdateCache) {
+ mVolumeIndexes.Put(aDevice, aIndex);
+ mIsVolumeIndexesChanged = true;
+ mManager.AudioOutDeviceUpdated(aDevice);
+ }
+
+ rv = AudioSystem::setStreamVolumeIndex(
+ static_cast<audio_stream_type_t>(mStreamType),
+ aIndex,
+ aDevice);
+ return rv ? NS_ERROR_FAILURE : NS_OK;
+#else
+ if (aUpdateCache) {
+ // Per audio out device volume is not supported.
+ // Use AUDIO_DEVICE_OUT_SPEAKER just to store audio volume to DB.
+ mVolumeIndexes.Put(AUDIO_DEVICE_OUT_SPEAKER, aIndex);
+ mIsVolumeIndexesChanged = true;
+ mManager.AudioOutDeviceUpdated(AUDIO_DEVICE_OUT_SPEAKER);
+ }
+ rv = AudioSystem::setStreamVolumeIndex(
+ static_cast<audio_stream_type_t>(mStreamType),
+ aIndex);
+ return rv ? NS_ERROR_FAILURE : NS_OK;
+#endif
+}
+
+void
+AudioManager::VolumeStreamState::RestoreVolumeIndexToAllDevices()
+{
+ for (auto iter = mVolumeIndexes.Iter(); !iter.Done(); iter.Next()) {
+ const uint32_t& key = iter.Key();
+ uint32_t& index = iter.Data();
+ SetVolumeIndex(key, index, /* aUpdateCache */ false);
+ }
+}
+
+} /* namespace gonk */
+} /* namespace dom */
+} /* namespace mozilla */
diff --git a/dom/system/gonk/AudioManager.h b/dom/system/gonk/AudioManager.h
new file mode 100644
index 000000000..f56eaad6c
--- /dev/null
+++ b/dom/system/gonk/AudioManager.h
@@ -0,0 +1,180 @@
+/* Copyright 2012 Mozilla Foundation and Mozilla contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef mozilla_dom_system_b2g_audiomanager_h__
+#define mozilla_dom_system_b2g_audiomanager_h__
+
+#include "mozilla/HalTypes.h"
+#include "mozilla/Observer.h"
+#include "mozilla/UniquePtr.h"
+#include "nsAutoPtr.h"
+#include "nsDataHashtable.h"
+#include "nsIAudioManager.h"
+#include "nsIObserver.h"
+#include "android_audio/AudioSystem.h"
+
+// {b2b51423-502d-4d77-89b3-7786b562b084}
+#define NS_AUDIOMANAGER_CID {0x94f6fd70, 0x7615, 0x4af9, \
+ {0x89, 0x10, 0xf9, 0x3c, 0x55, 0xe6, 0x62, 0xec}}
+#define NS_AUDIOMANAGER_CONTRACTID "@mozilla.org/telephony/audiomanager;1"
+
+class nsISettingsServiceLock;
+
+namespace mozilla {
+namespace hal {
+class SwitchEvent;
+typedef Observer<SwitchEvent> SwitchObserver;
+} // namespace hal
+
+namespace dom {
+namespace gonk {
+
+class VolumeInitCallback;
+
+class AudioManager final : public nsIAudioManager
+ , public nsIObserver
+{
+public:
+ static already_AddRefed<AudioManager> GetInstance();
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIAUDIOMANAGER
+ NS_DECL_NSIOBSERVER
+
+ // Validate whether the volume index is within the range
+ nsresult ValidateVolumeIndex(int32_t aStream, uint32_t aIndex) const;
+
+ // Called when android AudioFlinger in mediaserver is died
+ void HandleAudioFlingerDied();
+
+ void HandleHeadphoneSwitchEvent(const hal::SwitchEvent& aEvent);
+
+ class VolumeStreamState {
+ public:
+ explicit VolumeStreamState(AudioManager& aManager, int32_t aStreamType);
+ int32_t GetStreamType()
+ {
+ return mStreamType;
+ }
+ bool IsDevicesChanged(bool aFromCache = true);
+ void ClearDevicesChanged();
+ uint32_t GetLastDevices()
+ {
+ return mLastDevices;
+ }
+ bool IsVolumeIndexesChanged();
+ void ClearVolumeIndexesChanged();
+ void InitStreamVolume();
+ uint32_t GetMaxIndex();
+ uint32_t GetDefaultIndex();
+ uint32_t GetVolumeIndex();
+ uint32_t GetVolumeIndex(uint32_t aDevice);
+ void ClearCurrentVolumeUpdated();
+ // Set volume index to all active devices.
+ // Active devices are chosen by android AudioPolicyManager.
+ nsresult SetVolumeIndexToActiveDevices(uint32_t aIndex);
+ // Set volume index to all alias streams for device. Alias streams have same volume.
+ nsresult SetVolumeIndexToAliasStreams(uint32_t aIndex, uint32_t aDevice);
+ nsresult SetVolumeIndexToConsistentDeviceIfNeeded(uint32_t aIndex, uint32_t aDevice);
+ nsresult SetVolumeIndex(uint32_t aIndex, uint32_t aDevice, bool aUpdateCache = true);
+ // Restore volume index to all devices. Called when AudioFlinger is restarted.
+ void RestoreVolumeIndexToAllDevices();
+ private:
+ AudioManager& mManager;
+ const int32_t mStreamType;
+ uint32_t mLastDevices;
+ bool mIsDevicesChanged;
+ bool mIsVolumeIndexesChanged;
+ nsDataHashtable<nsUint32HashKey, uint32_t> mVolumeIndexes;
+ };
+
+protected:
+ int32_t mPhoneState;
+
+ bool mIsVolumeInited;
+
+ // A bitwise variable for volume update of audio output devices,
+ // clear it after store the value into database.
+ uint32_t mAudioOutDevicesUpdated;
+
+ // Connected devices that are controlled by setDeviceConnectionState()
+ nsDataHashtable<nsUint32HashKey, nsCString> mConnectedDevices;
+
+ nsDataHashtable<nsUint32HashKey, uint32_t> mAudioDeviceTableIdMaps;
+
+ bool mSwitchDone;
+
+#if defined(MOZ_B2G_BT) || ANDROID_VERSION >= 17
+ bool mBluetoothA2dpEnabled;
+#endif
+#ifdef MOZ_B2G_BT
+ bool mA2dpSwitchDone;
+#endif
+ nsTArray<UniquePtr<VolumeStreamState> > mStreamStates;
+ uint32_t mLastChannelVolume[AUDIO_STREAM_CNT];
+
+ bool IsFmOutConnected();
+
+ nsresult SetStreamVolumeForDevice(int32_t aStream,
+ uint32_t aIndex,
+ uint32_t aDevice);
+ nsresult SetStreamVolumeIndex(int32_t aStream, uint32_t aIndex);
+ nsresult GetStreamVolumeIndex(int32_t aStream, uint32_t* aIndex);
+
+ void UpdateCachedActiveDevicesForStreams();
+ uint32_t GetDevicesForStream(int32_t aStream, bool aFromCache = true);
+ uint32_t GetDeviceForStream(int32_t aStream);
+ // Choose one device as representative of active devices.
+ static uint32_t SelectDeviceFromDevices(uint32_t aOutDevices);
+
+private:
+ nsAutoPtr<mozilla::hal::SwitchObserver> mObserver;
+
+ void HandleBluetoothStatusChanged(nsISupports* aSubject,
+ const char* aTopic,
+ const nsCString aAddress);
+ void HandleAudioChannelProcessChanged();
+
+ // Append the audio output device to the volume setting string.
+ nsAutoCString AppendDeviceToVolumeSetting(const char* aName,
+ uint32_t aDevice);
+
+ // We store the volume setting in the database, these are related functions.
+ void InitVolumeFromDatabase();
+ void MaybeUpdateVolumeSettingToDatabase(bool aForce = false);
+
+ // Promise functions.
+ void InitDeviceVolumeSucceeded();
+ void InitDeviceVolumeFailed(const char* aError);
+
+ void AudioOutDeviceUpdated(uint32_t aDevice);
+
+ void UpdateHeadsetConnectionState(hal::SwitchState aState);
+ void UpdateDeviceConnectionState(bool aIsConnected, uint32_t aDevice, const nsCString& aDeviceName);
+ void SetAllDeviceConnectionStates();
+
+ AudioManager();
+ ~AudioManager();
+
+ friend class VolumeInitCallback;
+ friend class VolumeStreamState;
+ friend class GonkAudioPortCallback;
+};
+
+} /* namespace gonk */
+} /* namespace dom */
+} /* namespace mozilla */
+
+#endif // mozilla_dom_system_b2g_audiomanager_h__
diff --git a/dom/system/gonk/AutoMounter.cpp b/dom/system/gonk/AutoMounter.cpp
new file mode 100644
index 000000000..52c4554fb
--- /dev/null
+++ b/dom/system/gonk/AutoMounter.cpp
@@ -0,0 +1,1496 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include <errno.h>
+#include <fcntl.h>
+#include <pthread.h>
+#include <signal.h>
+#include <string.h>
+#include <strings.h>
+#include <unistd.h>
+#include <sys/statfs.h>
+
+#include <arpa/inet.h>
+#include <linux/types.h>
+#include <linux/netlink.h>
+#include <netinet/in.h>
+#include <sys/socket.h>
+#include <android/log.h>
+#include <cutils/properties.h>
+
+#include "AutoMounter.h"
+#include "nsVolumeService.h"
+#include "AutoMounterSetting.h"
+#include "base/message_loop.h"
+#include "base/task.h"
+#include "mozilla/AutoRestore.h"
+#include "mozilla/FileUtils.h"
+#include "mozilla/Hal.h"
+#include "mozilla/StaticPtr.h"
+#include "MozMtpServer.h"
+#include "MozMtpStorage.h"
+#include "nsCharSeparatedTokenizer.h"
+#include "nsMemory.h"
+#include "nsString.h"
+#include "nsThreadUtils.h"
+#include "nsXULAppAPI.h"
+#include "OpenFileFinder.h"
+#include "Volume.h"
+#include "VolumeManager.h"
+#include "nsIStatusReporter.h"
+
+USING_MTP_NAMESPACE
+
+/**************************************************************************
+*
+* The following "switch" files are available for monitoring usb
+* connections:
+*
+* /sys/devices/virtual/switch/usb_connected/state
+* /sys/devices/virtual/switch/usb_configuration/state
+*
+* Under gingerbread, only the usb_configuration seems to be available.
+* Starting with honeycomb, usb_connected was also added.
+*
+* When a cable insertion/removal occurs, then a uevent similar to the
+* following will be generted:
+*
+* change@/devices/virtual/switch/usb_configuration
+* ACTION=change
+* DEVPATH=/devices/virtual/switch/usb_configuration
+* SUBSYSTEM=switch
+* SWITCH_NAME=usb_configuration
+* SWITCH_STATE=0
+* SEQNUM=5038
+*
+* SWITCH_STATE will be 0 after a removal and 1 after an insertion
+*
+**************************************************************************/
+
+#define USB_CONFIGURATION_SWITCH_NAME NS_LITERAL_STRING("usb_configuration")
+
+#define GB_SYS_UMS_ENABLE "/sys/devices/virtual/usb_composite/usb_mass_storage/enable"
+#define GB_SYS_USB_CONFIGURED "/sys/devices/virtual/switch/usb_configuration/state"
+
+#define ICS_SYS_USB_FUNCTIONS "/sys/devices/virtual/android_usb/android0/functions"
+#define ICS_SYS_UMS_DIRECTORY "/sys/devices/virtual/android_usb/android0/f_mass_storage"
+#define ICS_SYS_MTP_DIRECTORY "/sys/devices/virtual/android_usb/android0/f_mtp"
+#define ICS_SYS_USB_STATE "/sys/devices/virtual/android_usb/android0/state"
+
+#undef USE_DEBUG // MozMtpDatabase.h also defines USE_DEBUG
+#define USE_DEBUG 0
+
+#undef LOG
+#undef LOGW
+#undef ERR
+#define LOG(args...) __android_log_print(ANDROID_LOG_INFO, "AutoMounter", ## args)
+#define LOGW(args...) __android_log_print(ANDROID_LOG_WARN, "AutoMounter", ## args)
+#define ERR(args...) __android_log_print(ANDROID_LOG_ERROR, "AutoMounter", ## args)
+
+#undef DBG
+#if USE_DEBUG
+#define DBG(args...) __android_log_print(ANDROID_LOG_DEBUG, "AutoMounter" , ## args)
+#else
+#define DBG(args...)
+#endif
+
+namespace mozilla {
+namespace system {
+
+#define SYS_USB_CONFIG "sys.usb.config"
+#define PERSIST_SYS_USB_CONFIG "persist.sys.usb.config"
+
+#define USB_FUNC_ADB "adb"
+#define USB_FUNC_MTP "mtp"
+#define USB_FUNC_NONE "none"
+#define USB_FUNC_RNDIS "rndis"
+#define USB_FUNC_UMS "mass_storage"
+#define USB_FUNC_DEFAULT "default"
+
+class AutoMounter;
+
+static void SetAutoMounterStatus(int32_t aStatus);
+
+/***************************************************************************/
+
+inline const char* SwitchStateStr(const hal::SwitchEvent& aEvent)
+{
+ return aEvent.status() == hal::SWITCH_STATE_ON ? "plugged" : "unplugged";
+}
+
+/***************************************************************************/
+
+static bool
+IsUsbCablePluggedIn()
+{
+#if 0
+ // Use this code when bug 745078 gets fixed (or use whatever the
+ // appropriate method is)
+ return GetCurrentSwitchEvent(SWITCH_USB) == hal::SWITCH_STATE_ON;
+#else
+ // Until then, just go read the file directly
+ if (access(ICS_SYS_USB_STATE, F_OK) == 0) {
+ char usbState[20];
+ if (ReadSysFile(ICS_SYS_USB_STATE, usbState, sizeof(usbState))) {
+ DBG("IsUsbCablePluggedIn: state = '%s'", usbState);
+ return strcmp(usbState, "CONFIGURED") == 0 ||
+ strcmp(usbState, "CONNECTED") == 0;
+ }
+ ERR("Error reading file '%s': %s", ICS_SYS_USB_STATE, strerror(errno));
+ return false;
+ }
+ bool configured;
+ if (ReadSysFile(GB_SYS_USB_CONFIGURED, &configured)) {
+ return configured;
+ }
+ ERR("Error reading file '%s': %s", GB_SYS_USB_CONFIGURED, strerror(errno));
+ return false;
+#endif
+}
+
+static bool
+IsUsbConfigured()
+{
+ if (access(ICS_SYS_USB_STATE, F_OK) == 0) {
+ char usbState[20];
+ if (ReadSysFile(ICS_SYS_USB_STATE, usbState, sizeof(usbState))) {
+ DBG("IsUsbConfigured: state = '%s'", usbState);
+ return strcmp(usbState, "CONFIGURED") == 0;
+ }
+ ERR("Error reading file '%s': %s", ICS_SYS_USB_STATE, strerror(errno));
+ return false;
+ }
+ bool configured;
+ if (ReadSysFile(GB_SYS_USB_CONFIGURED, &configured)) {
+ return configured;
+ }
+ ERR("Error reading file '%s': %s", GB_SYS_USB_CONFIGURED, strerror(errno));
+ return false;
+}
+
+/***************************************************************************/
+
+// The AutoVolumeManagerStateObserver allows the AutoMounter to know when
+// the volume manager changes state (i.e. it has finished initialization)
+class AutoVolumeManagerStateObserver : public VolumeManager::StateObserver
+{
+public:
+ virtual void Notify(const VolumeManager::StateChangedEvent& aEvent);
+};
+
+// The AutoVolumeEventObserver allows the AutoMounter to know about card
+// insertion and removal, as well as state changes in the volume.
+class AutoVolumeEventObserver : public Volume::EventObserver
+{
+public:
+ virtual void Notify(Volume* const& aEvent);
+};
+
+class AutoMounterResponseCallback : public VolumeResponseCallback
+{
+public:
+ AutoMounterResponseCallback()
+ : mErrorCount(0)
+ {
+ }
+
+protected:
+ virtual void ResponseReceived(const VolumeCommand* aCommand);
+
+private:
+ const static int kMaxErrorCount = 3; // Max number of errors before we give up
+
+ int mErrorCount;
+};
+
+/***************************************************************************/
+
+class AutoMounter
+{
+public:
+ NS_INLINE_DECL_REFCOUNTING(AutoMounter)
+
+ typedef nsTArray<RefPtr<Volume>> VolumeArray;
+
+ AutoMounter()
+ : mState(STATE_IDLE),
+ mResponseCallback(new AutoMounterResponseCallback),
+ mMode(AUTOMOUNTER_DISABLE)
+ {
+ VolumeManager::RegisterStateObserver(&mVolumeManagerStateObserver);
+ Volume::RegisterVolumeObserver(&mVolumeEventObserver, "AutoMounter");
+
+ // It's possible that the VolumeManager is already in the READY state,
+ // so we call CheckVolumeSettings here to cover that case. Otherwise,
+ // we'll pick it up when the VolumeManage state changes to VOLUMES_READY.
+ CheckVolumeSettings();
+
+ DBG("Calling UpdateState from constructor");
+ UpdateState();
+ }
+
+ void CheckVolumeSettings()
+ {
+ if (VolumeManager::State() != VolumeManager::VOLUMES_READY) {
+ DBG("CheckVolumeSettings: VolumeManager is NOT READY yet");
+ return;
+ }
+ DBG("CheckVolumeSettings: VolumeManager is READY");
+
+ // The VolumeManager knows about all of the volumes from vold. We now
+ // know the names of all of the volumes, so we can find out what the
+ // initial sharing settings are set to.
+
+ VolumeManager::VolumeArray::size_type numVolumes = VolumeManager::NumVolumes();
+ VolumeManager::VolumeArray::index_type i;
+ for (i = 0; i < numVolumes; i++) {
+ RefPtr<Volume> vol = VolumeManager::GetVolume(i);
+ if (vol) {
+ // We need to pick up the intial value of the
+ // ums.volume.NAME.enabled setting.
+ AutoMounterSetting::CheckVolumeSettings(vol->Name());
+
+ // Note: eventually CheckVolumeSettings will call
+ // AutoMounter::SetSharingMode, which will in turn call
+ // UpdateState if needed.
+ }
+ }
+ }
+
+ void UpdateState();
+ void GetStatus(bool& umsAvail, bool& umsConfigured, bool& umsEnabled, bool& mtpAvail,
+ bool& mtpConfigured, bool& mtpEnabled, bool& rndisConfigured);
+
+ nsresult Dump(nsACString& desc);
+
+ void ConfigureUsbFunction(const char* aUsbFunc);
+
+ bool StartMtpServer();
+ void StopMtpServer();
+
+ void StartUmsSharing();
+ void StopUmsSharing();
+
+
+ const char* ModeStr(int32_t aMode)
+ {
+ switch (aMode) {
+ case AUTOMOUNTER_DISABLE: return "Disable";
+ case AUTOMOUNTER_ENABLE_UMS: return "Enable-UMS";
+ case AUTOMOUNTER_DISABLE_WHEN_UNPLUGGED: return "DisableWhenUnplugged";
+ case AUTOMOUNTER_ENABLE_MTP: return "Enable-MTP";
+ }
+ return "??? Unknown ???";
+ }
+
+ bool IsModeEnabled(int32_t aMode)
+ {
+ return aMode == AUTOMOUNTER_ENABLE_MTP ||
+ aMode == AUTOMOUNTER_ENABLE_UMS;
+ }
+
+ void SetMode(int32_t aMode)
+ {
+ if ((aMode == AUTOMOUNTER_DISABLE_WHEN_UNPLUGGED) &&
+ (mMode == AUTOMOUNTER_DISABLE)) {
+ // If it's already disabled, then leave it as disabled.
+ // AUTOMOUNTER_DISABLE_WHEN_UNPLUGGED implies "enabled until unplugged"
+ aMode = AUTOMOUNTER_DISABLE;
+ }
+
+ if (aMode == AUTOMOUNTER_DISABLE &&
+ mMode == AUTOMOUNTER_ENABLE_UMS && IsUsbCablePluggedIn()) {
+ // On many devices (esp non-Samsung), we can't force the disable, so we
+ // need to defer until the USB cable is actually unplugged.
+ // See bug 777043.
+ //
+ // Otherwise our attempt to disable it will fail, and we'll wind up in a bad
+ // state where the AutoMounter thinks that Sharing has been turned off, but
+ // the files are actually still being Shared because the attempt to unshare
+ // failed.
+ LOG("Attempting to disable UMS. Deferring until USB cable is unplugged.");
+ aMode = AUTOMOUNTER_DISABLE_WHEN_UNPLUGGED;
+ }
+
+ if (aMode != mMode) {
+ LOG("Changing mode from '%s' to '%s'", ModeStr(mMode), ModeStr(aMode));
+ mMode = aMode;
+ DBG("Calling UpdateState due to mode set to %d", mMode);
+ UpdateState();
+ }
+ }
+
+ void SetSharingMode(const nsACString& aVolumeName, bool aAllowSharing)
+ {
+ RefPtr<Volume> vol = VolumeManager::FindVolumeByName(aVolumeName);
+ if (!vol) {
+ return;
+ }
+ if (vol->IsSharingEnabled() == aAllowSharing) {
+ return;
+ }
+ vol->SetUnmountRequested(false);
+ vol->SetMountRequested(false);
+ vol->SetSharingEnabled(aAllowSharing);
+ DBG("Calling UpdateState due to volume %s sharing set to %d",
+ vol->NameStr(), (int)aAllowSharing);
+ UpdateState();
+ }
+
+ void FormatVolume(const nsACString& aVolumeName)
+ {
+ RefPtr<Volume> vol = VolumeManager::FindVolumeByName(aVolumeName);
+ if (!vol) {
+ return;
+ }
+ if (vol->IsFormatRequested()) {
+ return;
+ }
+ vol->SetUnmountRequested(false);
+ vol->SetMountRequested(false);
+ vol->SetFormatRequested(true);
+ DBG("Calling UpdateState due to volume %s formatting set to %d",
+ vol->NameStr(), (int)vol->IsFormatRequested());
+ UpdateState();
+ }
+
+ void MountVolume(const nsACString& aVolumeName)
+ {
+ RefPtr<Volume> vol = VolumeManager::FindVolumeByName(aVolumeName);
+ if (!vol) {
+ return;
+ }
+ vol->SetUnmountRequested(false);
+ if (vol->IsMountRequested() || vol->mState == nsIVolume::STATE_MOUNTED) {
+ return;
+ }
+ vol->SetMountRequested(true);
+ DBG("Calling UpdateState due to volume %s mounting set to %d",
+ vol->NameStr(), (int)vol->IsMountRequested());
+ UpdateState();
+ }
+
+ void UnmountVolume(const nsACString& aVolumeName)
+ {
+ RefPtr<Volume> vol = VolumeManager::FindVolumeByName(aVolumeName);
+ if (!vol) {
+ return;
+ }
+ if (vol->IsUnmountRequested()) {
+ return;
+ }
+ vol->SetMountRequested(false);
+ vol->SetUnmountRequested(true);
+ DBG("Calling UpdateState due to volume %s unmounting set to %d",
+ vol->NameStr(), (int)vol->IsUnmountRequested());
+ UpdateState();
+ }
+
+protected:
+ ~AutoMounter()
+ {
+ Volume::UnregisterVolumeObserver(&mVolumeEventObserver, "AutoMounter");
+ VolumeManager::UnregisterStateObserver(&mVolumeManagerStateObserver);
+ }
+
+private:
+
+ enum STATE
+ {
+ // IDLE - Nothing is being shared
+ STATE_IDLE,
+
+ // We've detected that conditions are right to enable mtp. So we've
+ // set sys.usb.config to include mtp, and we're waiting for the USB
+ // subsystem to be "configured". Once mtp shows up in
+ // then we know
+ // that its been configured and we can open /dev/mtp_usb
+ STATE_MTP_CONFIGURING,
+
+ // mtp has been configured (i.e. mtp now shows up in
+ // /sys/devices/virtual/android_usb/android0/functions so we can start
+ // the mtp server.
+ STATE_MTP_STARTED,
+
+ // The mtp server has reported sessionStarted. We'll leave this state
+ // when we receive sessionEnded.
+ STATE_MTP_CONNECTED,
+
+ // We've added mass_storage (aka UMS) to sys.usb.config and we're waiting for
+ // mass_storage to appear in /sys/devices/virtual/android_usb/android0/functions
+ STATE_UMS_CONFIGURING,
+
+ // mass_storage has been configured and we can start sharing once the user
+ // enables it.
+ STATE_UMS_CONFIGURED,
+
+ // USB Tethering is enabled
+ STATE_RNDIS_CONFIGURED,
+ };
+
+ const char *StateStr(STATE aState)
+ {
+ switch (aState) {
+ case STATE_IDLE: return "IDLE";
+ case STATE_MTP_CONFIGURING: return "MTP_CONFIGURING";
+ case STATE_MTP_CONNECTED: return "MTP_CONNECTED";
+ case STATE_MTP_STARTED: return "MTP_STARTED";
+ case STATE_UMS_CONFIGURING: return "UMS_CONFIGURING";
+ case STATE_UMS_CONFIGURED: return "UMS_CONFIGURED";
+ case STATE_RNDIS_CONFIGURED: return "RNDIS_CONFIGURED";
+ }
+ return "STATE_???";
+ }
+
+ void SetState(STATE aState)
+ {
+ const char *oldStateStr = StateStr(mState);
+ mState = aState;
+ const char *newStateStr = StateStr(mState);
+ LOG("AutoMounter state changed from %s to %s", oldStateStr, newStateStr);
+ }
+
+ STATE mState;
+
+ AutoVolumeEventObserver mVolumeEventObserver;
+ AutoVolumeManagerStateObserver mVolumeManagerStateObserver;
+ RefPtr<VolumeResponseCallback> mResponseCallback;
+ int32_t mMode;
+ MozMtpStorage::Array mMozMtpStorage;
+};
+
+static StaticRefPtr<AutoMounter> sAutoMounter;
+static StaticRefPtr<MozMtpServer> sMozMtpServer;
+
+// The following is for status reporter
+enum STATE_REPORTER_STATE
+{
+ REPORTER_UNREGISTERED,
+ REPORTER_REGISTERED
+};
+
+static int status_reporter_progress = REPORTER_UNREGISTERED;
+nsresult getState(nsACString &desc)
+{
+ sAutoMounter->Dump(desc);
+ return NS_OK;
+}
+NS_STATUS_REPORTER_IMPLEMENT(AutoMounter, "AutoMounter", getState);
+
+/***************************************************************************/
+
+void
+AutoVolumeManagerStateObserver::Notify(const VolumeManager::StateChangedEvent &)
+{
+ LOG("VolumeManager state changed event: %s", VolumeManager::StateStr());
+
+ if (!sAutoMounter) {
+ return;
+ }
+
+ // In the event that the VolumeManager just entered the VOLUMES_READY state,
+ // we call CheckVolumeSettings here (it's possible that this method never
+ // gets called if the VolumeManager was already in the VOLUMES_READY state
+ // by the time the AutoMounter was constructed).
+ sAutoMounter->CheckVolumeSettings();
+
+ DBG("Calling UpdateState due to VolumeManagerStateObserver");
+ sAutoMounter->UpdateState();
+}
+
+void
+AutoVolumeEventObserver::Notify(Volume * const &)
+{
+ if (!sAutoMounter) {
+ return;
+ }
+ DBG("Calling UpdateState due to VolumeEventStateObserver");
+ sAutoMounter->UpdateState();
+}
+
+void
+AutoMounterResponseCallback::ResponseReceived(const VolumeCommand* aCommand)
+{
+
+ if (WasSuccessful()) {
+ DBG("Calling UpdateState due to Volume::OnSuccess");
+ mErrorCount = 0;
+ sAutoMounter->UpdateState();
+ return;
+ }
+ ERR("Command '%s' failed: %d '%s'",
+ aCommand->CmdStr(), ResponseCode(), ResponseStr().get());
+
+ if (++mErrorCount < kMaxErrorCount) {
+ DBG("Calling UpdateState due to VolumeResponseCallback::OnError");
+ sAutoMounter->UpdateState();
+ }
+}
+
+static bool
+IsUsbFunctionEnabled(const char* aConfig, const char* aUsbFunc)
+{
+ nsAutoCString config(aConfig);
+ nsCCharSeparatedTokenizer tokenizer(config, ',');
+
+ while (tokenizer.hasMoreTokens()) {
+ nsAutoCString token(tokenizer.nextToken());
+ if (token.Equals(aUsbFunc)) {
+ DBG("IsUsbFunctionEnabled('%s', '%s'): returning true", aConfig, aUsbFunc);
+ return true;
+ }
+ }
+ DBG("IsUsbFunctionEnabled('%s', '%s'): returning false", aConfig, aUsbFunc);
+ return false;
+}
+
+static void
+SetUsbFunction(const char* aUsbFunc)
+{
+ char oldSysUsbConfig[PROPERTY_VALUE_MAX];
+ property_get(SYS_USB_CONFIG, oldSysUsbConfig, "");
+
+ if (strcmp(oldSysUsbConfig, USB_FUNC_NONE) == 0) {
+ // It's quite possible that sys.usb.config may have the value "none". We
+ // convert that to an empty string here, and at the end we convert the
+ // empty string back to "none".
+ oldSysUsbConfig[0] = '\0';
+ }
+
+ if (IsUsbFunctionEnabled(oldSysUsbConfig, aUsbFunc)) {
+ // The function is already configured. Nothing else to do.
+ DBG("SetUsbFunction('%s') - already set - nothing to do", aUsbFunc);
+ return;
+ }
+
+ char newSysUsbConfig[PROPERTY_VALUE_MAX];
+
+ if (strcmp(aUsbFunc, USB_FUNC_MTP) == 0) {
+ // We're enabling MTP. For this we'll wind up using mtp, or mtp,adb
+ strlcpy(newSysUsbConfig, USB_FUNC_MTP, sizeof(newSysUsbConfig));
+ } else if (strcmp(aUsbFunc, USB_FUNC_UMS) == 0) {
+ // We're enabling UMS. For this we make the assumption that the persisted
+ // property has mass_storage enabled.
+ property_get(PERSIST_SYS_USB_CONFIG, newSysUsbConfig, "");
+ } else if (strcmp(aUsbFunc, USB_FUNC_DEFAULT) == 0) {
+ // Set the property as PERSIST_SYS_USB_CONFIG
+ property_get(PERSIST_SYS_USB_CONFIG, newSysUsbConfig, "");
+ } else {
+ printf_stderr("AutoMounter::SetUsbFunction Unrecognized aUsbFunc '%s'\n", aUsbFunc);
+ MOZ_ASSERT(0);
+ return;
+ }
+
+ // Make sure the new value that we write into sys.usb.config keeps the adb
+ // (or non-adb) of the current string.
+
+ if (IsUsbFunctionEnabled(oldSysUsbConfig, USB_FUNC_ADB)) {
+ // ADB was turned on - keep it on.
+ if (!IsUsbFunctionEnabled(newSysUsbConfig, USB_FUNC_ADB)) {
+ // Add adb to the new string
+ strlcat(newSysUsbConfig, ",", sizeof(newSysUsbConfig));
+ strlcat(newSysUsbConfig, USB_FUNC_ADB, sizeof(newSysUsbConfig));
+ }
+ } else {
+ // ADB was turned off - keep it off
+ if (IsUsbFunctionEnabled(newSysUsbConfig, USB_FUNC_ADB)) {
+ // Remove ADB from the new string.
+ if (strcmp(newSysUsbConfig, USB_FUNC_ADB) == 0) {
+ newSysUsbConfig[0] = '\0';
+ } else {
+ nsAutoCString withoutAdb(newSysUsbConfig);
+ withoutAdb.ReplaceSubstring( "," USB_FUNC_ADB, "");
+ strlcpy(newSysUsbConfig, withoutAdb.get(), sizeof(newSysUsbConfig));
+ }
+ }
+ }
+
+ // If the persisted function didn't have mass_storage (this happens on
+ // the nexus 4/5, then we can get to here and have oldSysUsbConfig equal
+ // to newSysUsbConfig. So we need to check for that.
+
+ if (strcmp(oldSysUsbConfig, newSysUsbConfig) == 0) {
+ DBG("SetUsbFunction('%s') %s is already set to '%s' - nothing to do",
+ aUsbFunc, SYS_USB_CONFIG, newSysUsbConfig);
+ return;
+ }
+
+ if (newSysUsbConfig[0] == '\0') {
+ // Convert the empty string back to the string "none"
+ strlcpy(newSysUsbConfig, USB_FUNC_NONE, sizeof(newSysUsbConfig));
+ }
+ LOG("SetUsbFunction(%s) %s from '%s' to '%s'", aUsbFunc, SYS_USB_CONFIG,
+ oldSysUsbConfig, newSysUsbConfig);
+ property_set(SYS_USB_CONFIG, newSysUsbConfig);
+}
+
+bool
+AutoMounter::StartMtpServer()
+{
+ if (sMozMtpServer) {
+ // Mtp Server is already running - nothing to do
+ return true;
+ }
+ LOG("Starting MtpServer");
+
+ // For debugging, Change the #if 0 to #if 1, and then attach gdb during
+ // the 5 second interval below. Otherwise, configuring MTP will cause adb
+ // (and thus gdb) to get bounced.
+#if 0
+ LOG("Sleeping");
+ PRTime now = PR_Now();
+ PRTime stopTime = now + 5000000;
+ while (PR_Now() < stopTime) {
+ LOG("Sleeping...");
+ sleep(1);
+ }
+ LOG("Sleep done");
+#endif
+
+ sMozMtpServer = new MozMtpServer();
+ if (!sMozMtpServer->Init()) {
+ sMozMtpServer = nullptr;
+ return false;
+ }
+
+ VolumeArray::index_type volIndex;
+ VolumeArray::size_type numVolumes = VolumeManager::NumVolumes();
+ for (volIndex = 0; volIndex < numVolumes; volIndex++) {
+ RefPtr<Volume> vol = VolumeManager::GetVolume(volIndex);
+ RefPtr<MozMtpStorage> storage = new MozMtpStorage(vol, sMozMtpServer);
+ mMozMtpStorage.AppendElement(storage);
+ }
+
+ sMozMtpServer->Run();
+ return true;
+}
+
+void
+AutoMounter::StopMtpServer()
+{
+ LOG("Stopping MtpServer");
+
+ mMozMtpStorage.Clear();
+ sMozMtpServer = nullptr;
+}
+
+/***************************************************************************/
+
+void
+AutoMounter::UpdateState()
+{
+ static bool inUpdateState = false;
+ if (inUpdateState) {
+ // When UpdateState calls SetISharing, this causes a volume state
+ // change to occur, which would normally cause UpdateState to be called
+ // again. We want the volume state change to go out (so that device
+ // storage will see the change in sharing state), but since we're
+ // already in UpdateState we just want to prevent recursion from screwing
+ // things up.
+ return;
+ }
+ AutoRestore<bool> inUpdateStateDetector(inUpdateState);
+ inUpdateState = true;
+
+ MOZ_ASSERT(MessageLoop::current() == XRE_GetIOMessageLoop());
+
+ // If the following preconditions are met:
+ // - UMS is available (i.e. compiled into the kernel)
+ // - UMS is enabled
+ // - AutoMounter is enabled
+ // - USB cable is plugged in
+ // then we will try to unmount and share
+ // otherwise we will try to unshare and mount.
+
+ if (VolumeManager::State() != VolumeManager::VOLUMES_READY) {
+ // The volume manager isn't in a ready state, so there
+ // isn't anything else that we can do.
+ LOG("UpdateState: VolumeManager not ready yet");
+ return;
+ }
+
+ if (mResponseCallback->IsPending()) {
+ // We only deal with one outstanding volume command at a time,
+ // so we need to wait for it to finish.
+ return;
+ }
+
+ // Calling setprop sys.usb.config mtp,adb (or adding mass_storage) will
+ // cause /sys/devices/virtual/android_usb/android0/state to go:
+ // CONFIGURED -> DISCONNECTED -> CONNECTED -> CONFIGURED
+ //
+ // Since IsUsbCablePluggedIn returns state == CONFIGURED, it will look
+ // like a cable pull and replugin.
+ bool umsAvail, umsConfigured, umsEnabled;
+ bool mtpAvail, mtpConfigured, mtpEnabled;
+ bool rndisConfigured;
+ bool usbCablePluggedIn = IsUsbCablePluggedIn();
+ GetStatus(umsAvail, umsConfigured, umsEnabled, mtpAvail,
+ mtpConfigured, mtpEnabled, rndisConfigured);
+ bool enabled = mtpEnabled || umsEnabled;
+
+ if (mMode == AUTOMOUNTER_DISABLE_WHEN_UNPLUGGED) {
+ // DISABLE_WHEN_UNPLUGGED implies already enabled.
+ enabled = usbCablePluggedIn;
+ if (!usbCablePluggedIn) {
+ mMode = AUTOMOUNTER_DISABLE;
+ mtpEnabled = false;
+ umsEnabled = false;
+ }
+ }
+
+ DBG("UpdateState: ums:A%dC%dE%d mtp:A%dC%dE%d rndis:%d mode:%d usb:%d mState:%s",
+ umsAvail, umsConfigured, umsEnabled,
+ mtpAvail, mtpConfigured, mtpEnabled, rndisConfigured,
+ mMode, usbCablePluggedIn, StateStr(mState));
+
+ switch (mState) {
+
+ case STATE_IDLE:
+ if (!usbCablePluggedIn) {
+ // Stay in the IDLE state. We'll get a CONNECTED or CONFIGURED
+ // UEvent when the usb cable is plugged in.
+ break;
+ }
+ if (rndisConfigured) {
+ // USB Tethering uses RNDIS. We'll just wait until its turned off.
+ SetState(STATE_RNDIS_CONFIGURED);
+ break;
+ }
+ if (mtpEnabled) {
+ if (mtpConfigured) {
+ // The USB layer has already been configured. Now we can go ahead
+ // and start the MTP server. This particular codepath will not
+ // normally be taken, but it could happen if you stop and restart
+ // b2g while sys.usb.config is set to enable mtp.
+ if (StartMtpServer()) {
+ SetState(STATE_MTP_STARTED);
+ } else {
+ if (umsAvail) {
+ // Unable to start MTP. Go back to UMS.
+ LOG("UpdateState: StartMtpServer failed, switch to UMS");
+ SetUsbFunction(USB_FUNC_UMS);
+ SetState(STATE_UMS_CONFIGURING);
+ } else {
+ LOG("UpdateState: StartMtpServer failed, keep idle state");
+ SetUsbFunction(USB_FUNC_DEFAULT);
+ }
+ }
+ } else {
+ // We need to configure USB to use mtp. Wait for it to be configured
+ // before we start the MTP server.
+ SetUsbFunction(USB_FUNC_MTP);
+ SetState(STATE_MTP_CONFIGURING);
+ }
+ } else if (umsConfigured) {
+ // UMS is already configured.
+ SetState(STATE_UMS_CONFIGURED);
+ } else if (umsAvail) {
+ // We do this whether or not UMS is enabled. With UMS, it's the
+ // sharing of the volume which is significant. What is important
+ // is that we don't leave it in MTP mode when MTP isn't enabled.
+ SetUsbFunction(USB_FUNC_UMS);
+ SetState(STATE_UMS_CONFIGURING);
+ }
+ break;
+
+ case STATE_MTP_CONFIGURING:
+ // While configuring, the USB configuration state will change from
+ // CONFIGURED -> CONNECTED -> DISCONNECTED -> CONNECTED -> CONFIGURED
+ // so we don't check for cable unplugged here.
+ if (mtpEnabled && mtpConfigured) {
+ // The USB layer has been configured. Now we can go ahead and start
+ // the MTP server.
+ if (StartMtpServer()) {
+ SetState(STATE_MTP_STARTED);
+ } else {
+ // Unable to start MTP. Go back to UMS.
+ SetUsbFunction(USB_FUNC_UMS);
+ SetState(STATE_UMS_CONFIGURING);
+ }
+ break;
+ }
+ if (rndisConfigured) {
+ SetState(STATE_RNDIS_CONFIGURED);
+ break;
+ }
+ break;
+
+ case STATE_MTP_STARTED:
+ if (usbCablePluggedIn && mtpConfigured && mtpEnabled) {
+ // Everything is still good. Leave the MTP server running
+ break;
+ }
+ DBG("STATE_MTP_STARTED: About to StopMtpServer "
+ "mtpConfigured = %d mtpEnabled = %d usbCablePluggedIn: %d",
+ mtpConfigured, mtpEnabled, usbCablePluggedIn);
+ StopMtpServer();
+ if (rndisConfigured) {
+ SetState(STATE_RNDIS_CONFIGURED);
+ break;
+ }
+ if (umsAvail) {
+ // Switch back to UMS
+ SetUsbFunction(USB_FUNC_UMS);
+ SetState(STATE_UMS_CONFIGURING);
+ break;
+ }
+
+ // if ums/rndis is not available and mtp is disable,
+ // restore the usb function as PERSIST_SYS_USB_CONFIG.
+ SetUsbFunction(USB_FUNC_DEFAULT);
+ SetState(STATE_IDLE);
+ break;
+
+ case STATE_UMS_CONFIGURING:
+ if (mtpEnabled) {
+ // MTP was enabled. Start reconfiguring.
+ SetState(STATE_MTP_CONFIGURING);
+ SetUsbFunction(USB_FUNC_MTP);
+ break;
+ }
+ if (rndisConfigured) {
+ SetState(STATE_RNDIS_CONFIGURED);
+ break;
+ }
+ // While configuring, the USB configuration state will change from
+ // CONFIGURED -> CONNECTED -> DISCONNECTED -> CONNECTED -> CONFIGURED
+ // so we don't check for cable unplugged here. However, having said
+ // that, we'll often sit in this state while the cable is unplugged,
+ // since we might not get any events until the cable gets plugged back
+ // in. This is why we need to check for mtpEnabled once we get the
+ // configured event.
+ if (umsConfigured) {
+ SetState(STATE_UMS_CONFIGURED);
+ }
+ break;
+
+ case STATE_UMS_CONFIGURED:
+ if (usbCablePluggedIn) {
+ if (mtpEnabled) {
+ // MTP was enabled. Start reconfiguring.
+ SetState(STATE_MTP_CONFIGURING);
+ SetUsbFunction(USB_FUNC_MTP);
+ break;
+ }
+ if (umsConfigured && umsEnabled) {
+ // This is the normal state when UMS is enabled.
+ break;
+ }
+ }
+ if (rndisConfigured) {
+ SetState(STATE_RNDIS_CONFIGURED);
+ break;
+ }
+ SetState(STATE_IDLE);
+ break;
+
+ case STATE_RNDIS_CONFIGURED:
+ if (usbCablePluggedIn && rndisConfigured) {
+ // Normal state when RNDIS is enabled.
+ break;
+ }
+ SetState(STATE_IDLE);
+ break;
+
+ default:
+ SetState(STATE_IDLE);
+ break;
+ }
+
+ bool tryToShare = umsEnabled && usbCablePluggedIn;
+ LOG("UpdateState: ums:A%dC%dE%d mtp:A%dC%dE%d mode:%d usb:%d tryToShare:%d state:%s",
+ umsAvail, umsConfigured, umsEnabled,
+ mtpAvail, mtpConfigured, mtpEnabled,
+ mMode, usbCablePluggedIn, tryToShare, StateStr(mState));
+
+ bool filesOpen = false;
+ static unsigned filesOpenDelayCount = 0;
+ VolumeArray::index_type volIndex;
+ VolumeArray::size_type numVolumes = VolumeManager::NumVolumes();
+ for (volIndex = 0; volIndex < numVolumes; volIndex++) {
+ RefPtr<Volume> vol = VolumeManager::GetVolume(volIndex);
+ Volume::STATE volState = vol->State();
+
+ if (vol->State() == nsIVolume::STATE_MOUNTED) {
+ LOG("UpdateState: Volume %s is %s and %s @ %s gen %d locked %d sharing %s",
+ vol->NameStr(), vol->StateStr(),
+ vol->MediaPresent() ? "inserted" : "missing",
+ vol->MountPoint().get(), vol->MountGeneration(),
+ (int)vol->IsMountLocked(),
+ vol->CanBeShared() ? (vol->IsSharingEnabled() ?
+ (vol->IsSharing() ? "en-y" : "en-n") : "dis") : "x");
+ if (vol->IsSharing() && !usbCablePluggedIn) {
+ // We call SetIsSharing(true) below to indicate intent to share. This
+ // causes a state change which notifys apps, and they'll close any
+ // open files, which will initiate the change away from the mounted
+ // state and into the sharing state. Normally, when the volume
+ // transitions back to the mounted state, then vol->mIsSharing gets set
+ // to false. However, if the user pulls the USB cable before we
+ // actually start sharing, then the volume never actually leaves
+ // the mounted state (and hence never transitions from
+ // sharing -> mounted), and mIsSharing never gets set back to false.
+ // So we clear mIsSharing here.
+ vol->SetIsSharing(false);
+ }
+ } else {
+ LOG("UpdateState: Volume %s is %s and %s", vol->NameStr(), vol->StateStr(),
+ vol->MediaPresent() ? "inserted" : "missing");
+ }
+ if (!vol->MediaPresent()) {
+ // No media - nothing we can do
+ continue;
+ }
+
+ if (vol->State() == nsIVolume::STATE_CHECKMNT) {
+ // vold reports the volume is "Mounted". Need to check if the volume is
+ // accessible by statfs(). Once it can be accessed, set the volume as
+ // STATE_MOUNTED, otherwise, post a delay task of UpdateState to check it
+ // again.
+ struct statfs fsbuf;
+ int rc = MOZ_TEMP_FAILURE_RETRY(statfs(vol->MountPoint().get(), &fsbuf));
+ if (rc == -1) {
+ // statfs() failed. Stay in STATE_CHECKMNT. Any failures here
+ // are probably non-recoverable, so we need to wait until
+ // something else changes the state back to IDLE/UNMOUNTED, etc.
+ ERR("statfs failed for '%s': errno = %d (%s)", vol->NameStr(), errno, strerror(errno));
+ continue;
+ }
+ static int delay = 250;
+ if (fsbuf.f_blocks == 0) {
+ if (delay <= 4000) {
+ LOG("UpdateState: Volume '%s' is inaccessible, checking again in %d msec", vol->NameStr(), delay);
+ MessageLoopForIO::current()->
+ PostDelayedTask(NewRunnableMethod(this, &AutoMounter::UpdateState),
+ delay);
+ delay *= 2;
+ } else {
+ LOG("UpdateState: Volume '%s' is inaccessible, giving up", vol->NameStr());
+ }
+ continue;
+ } else {
+ delay = 250;
+ vol->SetState(nsIVolume::STATE_MOUNTED);
+ }
+ }
+
+ if ((tryToShare && vol->IsSharingEnabled()) ||
+ vol->IsFormatRequested() ||
+ vol->IsUnmountRequested()) {
+ switch (volState) {
+ // We're going to try to unmount the volume
+ case nsIVolume::STATE_MOUNTED: {
+ if (vol->IsMountLocked()) {
+ // The volume is currently locked, so leave it in the mounted
+ // state.
+ LOGW("UpdateState: Mounted volume %s is locked, not sharing or formatting",
+ vol->NameStr());
+ break;
+ }
+
+ // Mark the volume as if we've started sharing/formatting/unmmounting.
+ // This will cause apps which watch device storage notifications to see
+ // the volume go into the different state, and prompt them to close any
+ // open files that they might have.
+ if (tryToShare && vol->IsSharingEnabled()) {
+ vol->SetIsSharing(true);
+ } else if (vol->IsFormatRequested()){
+ vol->SetIsFormatting(true);
+ } else if (vol->IsUnmountRequested()){
+ vol->SetIsUnmounting(true);
+ }
+
+ // Check to see if there are any open files on the volume and
+ // don't initiate the unmount while there are open files.
+ OpenFileFinder::Info fileInfo;
+ OpenFileFinder fileFinder(vol->MountPoint());
+ if (fileFinder.First(&fileInfo)) {
+ LOGW("The following files are open under '%s'",
+ vol->MountPoint().get());
+ do {
+ LOGW(" PID: %d file: '%s' app: '%s' comm: '%s' exe: '%s'\n",
+ fileInfo.mPid,
+ fileInfo.mFileName.get(),
+ fileInfo.mAppName.get(),
+ fileInfo.mComm.get(),
+ fileInfo.mExe.get());
+ } while (fileFinder.Next(&fileInfo));
+ LOGW("UpdateState: Mounted volume %s has open files, not sharing or formatting",
+ vol->NameStr());
+
+ // Check again in a few seconds to see if the files are closed.
+ // Since we're trying to share the volume, this implies that we're
+ // plugged into the PC via USB and this in turn implies that the
+ // battery is charging, so we don't need to be too concerned about
+ // wasting battery here.
+ //
+ // If we just detected that there were files open, then we use
+ // a short timer. We will have told the apps that we're trying
+ // trying to share, and they'll be closing their files. This makes
+ // the sharing more responsive. If after a few seconds, the apps
+ // haven't closed their files, then we back off.
+
+ int delay = 1000;
+ if (filesOpenDelayCount > 10) {
+ delay = 5000;
+ }
+ MessageLoopForIO::current()->
+ PostDelayedTask(NewRunnableMethod(this, &AutoMounter::UpdateState),
+ delay);
+ filesOpen = true;
+ break;
+ }
+
+ // Volume is mounted, we need to unmount before
+ // we can share.
+ LOG("UpdateState: Unmounting %s", vol->NameStr());
+ vol->StartUnmount(mResponseCallback);
+ return; // UpdateState will be called again when the Unmount command completes
+ }
+ case nsIVolume::STATE_IDLE:
+ case nsIVolume::STATE_MOUNT_FAIL: {
+ LOG("UpdateState: Volume %s is %s", vol->NameStr(), vol->StateStr());
+ if (vol->IsFormatting() && !vol->IsFormatRequested()) {
+ vol->SetFormatRequested(false);
+ if (!(tryToShare && vol->IsSharingEnabled()) && volState == nsIVolume::STATE_IDLE) {
+ LOG("UpdateState: Mounting %s", vol->NameStr());
+ vol->StartMount(mResponseCallback);
+ break;
+ }
+ }
+
+ // If there are format and share requests in the same time,
+ // we should do format first then share.
+ if (vol->IsFormatRequested()) {
+ // Volume is unmounted. We can go ahead and format.
+ LOG("UpdateState: Formatting %s", vol->NameStr());
+ vol->StartFormat(mResponseCallback);
+ } else if (tryToShare && vol->IsSharingEnabled() && volState == nsIVolume::STATE_IDLE) {
+ // Volume is unmounted. We can go ahead and share.
+ LOG("UpdateState: Sharing %s", vol->NameStr());
+ vol->StartShare(mResponseCallback);
+ }
+ return; // UpdateState will be called again when the Share/Format command completes
+ }
+ default: {
+ // Not in a state that we can do anything about.
+ break;
+ }
+ }
+ } else {
+ // We're going to try and unshare and remount the volumes
+ switch (volState) {
+ case nsIVolume::STATE_SHARED: {
+ // Volume is shared. We can go ahead and unshare.
+ LOG("UpdateState: Unsharing %s", vol->NameStr());
+ vol->StartUnshare(mResponseCallback);
+ return; // UpdateState will be called again when the Unshare command completes
+ }
+ case nsIVolume::STATE_IDLE: {
+ if (!vol->IsUnmountRequested()) {
+ // Volume is unmounted and mount-requested, try to mount.
+
+ LOG("UpdateState: Mounting %s", vol->NameStr());
+ vol->StartMount(mResponseCallback);
+ }
+ return; // UpdateState will be called again when Mount command completes
+ }
+ default: {
+ // Not in a state that we can do anything about.
+ break;
+ }
+ }
+ }
+ }
+
+ int32_t status = AUTOMOUNTER_STATUS_DISABLED;
+ if (filesOpen) {
+ filesOpenDelayCount++;
+ status = AUTOMOUNTER_STATUS_FILES_OPEN;
+ } else if (enabled) {
+ filesOpenDelayCount = 0;
+ status = AUTOMOUNTER_STATUS_ENABLED;
+ }
+ SetAutoMounterStatus(status);
+}
+
+/***************************************************************************/
+
+void AutoMounter::GetStatus(bool& umsAvail, bool& umsConfigured, bool& umsEnabled,
+ bool& mtpAvail, bool& mtpConfigured, bool& mtpEnabled,
+ bool& rndisConfigured)
+{
+ umsAvail = false;
+ umsConfigured = false;
+ umsEnabled = false;
+ mtpAvail = false;
+ mtpConfigured = false;
+ mtpEnabled = false;
+ rndisConfigured = false;
+
+ if (access(ICS_SYS_USB_FUNCTIONS, F_OK) != 0) {
+ return;
+ }
+
+ char functionsStr[60];
+ if (!ReadSysFile(ICS_SYS_USB_FUNCTIONS, functionsStr, sizeof(functionsStr))) {
+ ERR("Error reading file '%s': %s", ICS_SYS_USB_FUNCTIONS, strerror(errno));
+ functionsStr[0] = '\0';
+ }
+ DBG("GetStatus: USB functions: '%s'", functionsStr);
+
+ bool usbConfigured = IsUsbConfigured();
+
+ // On the Nexus 4/5, it advertises that the UMS usb function is available,
+ // but we have a further requirement that mass_storage be in the
+ // persist.sys.usb.config property.
+ char persistSysUsbConfig[PROPERTY_VALUE_MAX];
+ property_get(PERSIST_SYS_USB_CONFIG, persistSysUsbConfig, "");
+ if (IsUsbFunctionEnabled(persistSysUsbConfig, USB_FUNC_UMS)) {
+ umsAvail = (access(ICS_SYS_UMS_DIRECTORY, F_OK) == 0);
+ }
+ if (umsAvail) {
+ umsConfigured = usbConfigured && strstr(functionsStr, USB_FUNC_UMS) != nullptr;
+ umsEnabled = (mMode == AUTOMOUNTER_ENABLE_UMS) ||
+ ((mMode == AUTOMOUNTER_DISABLE_WHEN_UNPLUGGED) && umsConfigured);
+ } else {
+ umsConfigured = false;
+ umsEnabled = false;
+ }
+
+ mtpAvail = (access(ICS_SYS_MTP_DIRECTORY, F_OK) == 0);
+ if (mtpAvail) {
+ mtpConfigured = usbConfigured && strstr(functionsStr, USB_FUNC_MTP) != nullptr;
+ mtpEnabled = (mMode == AUTOMOUNTER_ENABLE_MTP) ||
+ ((mMode == AUTOMOUNTER_DISABLE_WHEN_UNPLUGGED) && mtpConfigured);
+ } else {
+ mtpConfigured = false;
+ mtpEnabled = false;
+ }
+
+ rndisConfigured = strstr(functionsStr, USB_FUNC_RNDIS) != nullptr;
+}
+
+
+nsresult AutoMounter::Dump(nsACString& desc)
+{
+ DBG("GetState!");
+ bool umsAvail, umsConfigured, umsEnabled;
+ bool mtpAvail, mtpConfigured, mtpEnabled;
+ bool rndisConfigured;
+ GetStatus(umsAvail, umsConfigured, umsEnabled, mtpAvail,
+ mtpConfigured, mtpEnabled, rndisConfigured);
+ if (mMode == AUTOMOUNTER_DISABLE_WHEN_UNPLUGGED) {
+ // DISABLE_WHEN_UNPLUGGED implies already enabled.
+ if (!IsUsbCablePluggedIn()) {
+ mMode = AUTOMOUNTER_DISABLE;
+ mtpEnabled = false;
+ umsEnabled = false;
+ }
+ }
+
+ // Automounter information
+ desc += "Current Mode:";
+ desc += ModeStr(mMode);
+ desc += "|";
+
+
+ desc += "Current State:";
+ desc += StateStr(mState);
+ desc += "|";
+
+ desc += "UMS Status:";
+ if (umsAvail) {
+ desc += "Available";
+ } else {
+ desc += "UnAvailable";
+ }
+ desc += ",";
+ if (umsConfigured) {
+ desc += "Configured";
+ } else {
+ desc += "Un-Configured";
+ }
+ desc += ",";
+ if (umsEnabled) {
+ desc += "Enabled";
+ } else {
+ desc += "Disabled";
+ }
+ desc += "|";
+
+
+ desc += "MTP Status:";
+ if (mtpAvail) {
+ desc += "Available";
+ } else {
+ desc += "UnAvailable";
+ }
+ desc += ",";
+ if (mtpConfigured) {
+ desc += "Configured";
+ } else {
+ desc += "Un-Configured";
+ }
+ desc += ",";
+ if (mtpEnabled) {
+ desc += "Enabled";
+ } else {
+ desc += "Disabled";
+ }
+
+
+ // Volume information
+ VolumeArray::index_type volIndex;
+ VolumeArray::size_type numVolumes = VolumeManager::NumVolumes();
+ for (volIndex = 0; volIndex < numVolumes; volIndex++) {
+ RefPtr<Volume> vol = VolumeManager::GetVolume(volIndex);
+
+ desc += "|";
+ desc += vol->NameStr();
+ desc += ":";
+ desc += vol->StateStr();
+ desc += "@";
+ desc += vol->MountPoint().get();
+
+ if (!vol->MediaPresent()) {
+ continue;
+ }
+
+ if (vol->CanBeShared()) {
+ desc += ",CanBeShared";
+ }
+ if (vol->CanBeFormatted()) {
+ desc += ",CanBeFormatted";
+ }
+ if (vol->CanBeMounted()) {
+ desc += ",CanBeMounted";
+ }
+ if (vol->IsRemovable()) {
+ desc += ",Removable";
+ }
+ if (vol->IsHotSwappable()) {
+ desc += ",HotSwappable";
+ }
+ }
+
+ return NS_OK;
+}
+
+
+static void
+InitAutoMounterIOThread()
+{
+ MOZ_ASSERT(MessageLoop::current() == XRE_GetIOMessageLoop());
+ MOZ_ASSERT(!sAutoMounter);
+
+ sAutoMounter = new AutoMounter();
+}
+
+static void
+ShutdownAutoMounterIOThread()
+{
+ MOZ_ASSERT(MessageLoop::current() == XRE_GetIOMessageLoop());
+
+ sAutoMounter = nullptr;
+ ShutdownVolumeManager();
+}
+
+static void
+SetAutoMounterModeIOThread(const int32_t& aMode)
+{
+ MOZ_ASSERT(XRE_IsParentProcess());
+ MOZ_ASSERT(MessageLoop::current() == XRE_GetIOMessageLoop());
+ MOZ_ASSERT(sAutoMounter);
+
+ sAutoMounter->SetMode(aMode);
+}
+
+static void
+SetAutoMounterSharingModeIOThread(const nsCString& aVolumeName, const bool& aAllowSharing)
+{
+ MOZ_ASSERT(XRE_IsParentProcess());
+ MOZ_ASSERT(MessageLoop::current() == XRE_GetIOMessageLoop());
+ MOZ_ASSERT(sAutoMounter);
+
+ sAutoMounter->SetSharingMode(aVolumeName, aAllowSharing);
+}
+
+static void
+AutoMounterFormatVolumeIOThread(const nsCString& aVolumeName)
+{
+ MOZ_ASSERT(XRE_IsParentProcess());
+ MOZ_ASSERT(MessageLoop::current() == XRE_GetIOMessageLoop());
+ MOZ_ASSERT(sAutoMounter);
+
+ sAutoMounter->FormatVolume(aVolumeName);
+}
+
+static void
+AutoMounterMountVolumeIOThread(const nsCString& aVolumeName)
+{
+ MOZ_ASSERT(XRE_IsParentProcess());
+ MOZ_ASSERT(MessageLoop::current() == XRE_GetIOMessageLoop());
+ MOZ_ASSERT(sAutoMounter);
+
+ sAutoMounter->MountVolume(aVolumeName);
+}
+
+static void
+AutoMounterUnmountVolumeIOThread(const nsCString& aVolumeName)
+{
+ MOZ_ASSERT(XRE_IsParentProcess());
+ MOZ_ASSERT(MessageLoop::current() == XRE_GetIOMessageLoop());
+ MOZ_ASSERT(sAutoMounter);
+
+ sAutoMounter->UnmountVolume(aVolumeName);
+}
+
+static void
+UsbCableEventIOThread()
+{
+ MOZ_ASSERT(MessageLoop::current() == XRE_GetIOMessageLoop());
+
+ if (!sAutoMounter) {
+ return;
+ }
+ DBG("Calling UpdateState due to USBCableEvent");
+ sAutoMounter->UpdateState();
+}
+
+/**************************************************************************
+*
+* Public API
+*
+* Since the AutoMounter runs in IO Thread context, we need to switch
+* to IOThread context before we can do anything.
+*
+**************************************************************************/
+
+class UsbCableObserver final : public hal::SwitchObserver
+{
+ ~UsbCableObserver()
+ {
+ hal::UnregisterSwitchObserver(hal::SWITCH_USB, this);
+ }
+
+public:
+ NS_INLINE_DECL_REFCOUNTING(UsbCableObserver)
+
+ UsbCableObserver()
+ {
+ hal::RegisterSwitchObserver(hal::SWITCH_USB, this);
+ }
+
+ virtual void Notify(const hal::SwitchEvent& aEvent)
+ {
+ DBG("UsbCable switch device: %d state: %s\n",
+ aEvent.device(), SwitchStateStr(aEvent));
+ XRE_GetIOMessageLoop()->PostTask(
+ NewRunnableFunction(UsbCableEventIOThread));
+ }
+};
+
+static StaticRefPtr<UsbCableObserver> sUsbCableObserver;
+static StaticRefPtr<AutoMounterSetting> sAutoMounterSetting;
+
+void
+InitAutoMounter()
+{
+ InitVolumeManager();
+ sAutoMounterSetting = new AutoMounterSetting();
+
+ XRE_GetIOMessageLoop()->PostTask(
+ NewRunnableFunction(InitAutoMounterIOThread));
+
+ // Switch Observers need to run on the main thread, so we need to
+ // start it here and have it send events to the AutoMounter running
+ // on the IO Thread.
+ sUsbCableObserver = new UsbCableObserver();
+
+ // Register status reporter into reporter manager
+ if(status_reporter_progress == REPORTER_UNREGISTERED) {
+ NS_RegisterStatusReporter(new NS_STATUS_REPORTER_NAME(AutoMounter));
+ }
+ status_reporter_progress = REPORTER_REGISTERED;
+}
+
+int32_t
+GetAutoMounterStatus()
+{
+ if (sAutoMounterSetting) {
+ return sAutoMounterSetting->GetStatus();
+ }
+ return AUTOMOUNTER_STATUS_DISABLED;
+}
+
+//static
+void
+SetAutoMounterStatus(int32_t aStatus)
+{
+ if (sAutoMounterSetting) {
+ sAutoMounterSetting->SetStatus(aStatus);
+ }
+}
+
+void
+SetAutoMounterMode(int32_t aMode)
+{
+ XRE_GetIOMessageLoop()->PostTask(
+ NewRunnableFunction(SetAutoMounterModeIOThread, aMode));
+}
+
+void
+SetAutoMounterSharingMode(const nsCString& aVolumeName, bool aAllowSharing)
+{
+ XRE_GetIOMessageLoop()->PostTask(
+ NewRunnableFunction(SetAutoMounterSharingModeIOThread,
+ aVolumeName, aAllowSharing));
+}
+
+void
+AutoMounterFormatVolume(const nsCString& aVolumeName)
+{
+ XRE_GetIOMessageLoop()->PostTask(
+ NewRunnableFunction(AutoMounterFormatVolumeIOThread,
+ aVolumeName));
+}
+
+void
+AutoMounterMountVolume(const nsCString& aVolumeName)
+{
+ XRE_GetIOMessageLoop()->PostTask(
+ NewRunnableFunction(AutoMounterMountVolumeIOThread,
+ aVolumeName));
+}
+
+void
+AutoMounterUnmountVolume(const nsCString& aVolumeName)
+{
+ XRE_GetIOMessageLoop()->PostTask(
+ NewRunnableFunction(AutoMounterUnmountVolumeIOThread,
+ aVolumeName));
+}
+
+void
+ShutdownAutoMounter()
+{
+ if (sAutoMounter) {
+ DBG("ShutdownAutoMounter: About to StopMtpServer");
+ sAutoMounter->StopMtpServer();
+ // Unregister status reporter into reporter manager
+ if(status_reporter_progress == REPORTER_REGISTERED) {
+ NS_UnregisterStatusReporter(new NS_STATUS_REPORTER_NAME(AutoMounter));
+ }
+ status_reporter_progress = REPORTER_UNREGISTERED;
+ }
+ sAutoMounterSetting = nullptr;
+ sUsbCableObserver = nullptr;
+
+ XRE_GetIOMessageLoop()->PostTask(
+ NewRunnableFunction(ShutdownAutoMounterIOThread));
+}
+
+} // system
+} // mozilla
diff --git a/dom/system/gonk/AutoMounter.h b/dom/system/gonk/AutoMounter.h
new file mode 100644
index 000000000..ea98cadf1
--- /dev/null
+++ b/dom/system/gonk/AutoMounter.h
@@ -0,0 +1,101 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_system_automounter_h__
+#define mozilla_system_automounter_h__
+
+#include <stdint.h>
+
+class nsCString;
+
+namespace mozilla {
+namespace system {
+
+// AutoMounter modes
+#define AUTOMOUNTER_DISABLE 0
+#define AUTOMOUNTER_ENABLE_UMS 1
+#define AUTOMOUNTER_DISABLE_WHEN_UNPLUGGED 2
+#define AUTOMOUNTER_ENABLE_MTP 3
+
+// Automounter statuses
+#define AUTOMOUNTER_STATUS_DISABLED 0
+#define AUTOMOUNTER_STATUS_ENABLED 1
+#define AUTOMOUNTER_STATUS_FILES_OPEN 2
+
+/**
+ * Initialize the automounter. This causes some of the phone's
+ * directories to show up on the host when the phone is plugged
+ * into the host via USB.
+ *
+ * When the AutoMounter starts, it will poll the current state
+ * of affairs (usb cable plugged in, automounter enabled, etc)
+ * and try to make the state of the volumes match.
+ */
+void
+InitAutoMounter();
+
+/**
+ * Sets the enabled state of the automounter.
+ *
+ * This will in turn cause the automounter to re-evaluate
+ * whether it should mount/unmount/share/unshare volumes.
+ */
+void
+SetAutoMounterMode(int32_t aMode);
+
+/**
+ * Reports the status of the automounter.
+ */
+int32_t
+GetAutoMounterStatus();
+
+/**
+ * Sets the sharing mode of an individual volume.
+ *
+ * If a volume is enabled for sharing, and the autmounter
+ * is in a state to share, then the volume will be shared
+ * with the PC.
+ */
+void
+SetAutoMounterSharingMode(const nsCString& aVolumeName, bool aAllowSharing);
+
+/**
+ * Formats the volume with specified volume name.
+ *
+ * If the volume is ready to format, automounter
+ * will unmount it, format it and then mount it again.
+ */
+void
+AutoMounterFormatVolume(const nsCString& aVolumeName);
+
+/**
+ * Mounts the volume with specified volume name.
+ *
+ * If the volume is already unmounted, automounter
+ * will mount it. Otherwise automounter will skip this.
+ */
+void
+AutoMounterMountVolume(const nsCString& aVolumeName);
+
+/**
+ * Unmounts the volume with specified volume name.
+ *
+ * If the volume is already mounted, automounter
+ * will unmount it. Otherwise automounter will skip this.
+ */
+void
+AutoMounterUnmountVolume(const nsCString& aVolumeName);
+
+/**
+ * Shuts down the automounter.
+ *
+ * This leaves the volumes in whatever state they're in.
+ */
+void
+ShutdownAutoMounter();
+
+} // system
+} // mozilla
+
+#endif // mozilla_system_automounter_h__
diff --git a/dom/system/gonk/AutoMounterSetting.cpp b/dom/system/gonk/AutoMounterSetting.cpp
new file mode 100644
index 000000000..606bcce04
--- /dev/null
+++ b/dom/system/gonk/AutoMounterSetting.cpp
@@ -0,0 +1,284 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "AutoMounter.h"
+#include "AutoMounterSetting.h"
+
+#include "base/message_loop.h"
+#include "jsapi.h"
+#include "mozilla/Services.h"
+#include "nsCOMPtr.h"
+#include "nsContentUtils.h"
+#include "nsDebug.h"
+#include "nsIObserverService.h"
+#include "nsISettingsService.h"
+#include "nsJSUtils.h"
+#include "nsPrintfCString.h"
+#include "nsServiceManagerUtils.h"
+#include "nsString.h"
+#include "nsThreadUtils.h"
+#include "xpcpublic.h"
+#include "mozilla/dom/ScriptSettings.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/dom/BindingUtils.h"
+#include "mozilla/dom/SettingChangeNotificationBinding.h"
+
+#undef LOG
+#undef ERR
+#define LOG(args...) __android_log_print(ANDROID_LOG_INFO, "AutoMounterSetting" , ## args)
+#define ERR(args...) __android_log_print(ANDROID_LOG_ERROR, "AutoMounterSetting" , ## args)
+
+#define UMS_MODE "ums.mode"
+#define UMS_STATUS "ums.status"
+#define UMS_VOLUME_ENABLED_PREFIX "ums.volume."
+#define UMS_VOLUME_ENABLED_SUFFIX ".enabled"
+#define MOZSETTINGS_CHANGED "mozsettings-changed"
+
+using namespace mozilla::dom;
+
+namespace mozilla {
+namespace system {
+
+class SettingsServiceCallback final : public nsISettingsServiceCallback
+{
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ SettingsServiceCallback() {}
+
+ NS_IMETHOD Handle(const nsAString& aName, JS::Handle<JS::Value> aResult)
+ {
+ if (aResult.isInt32()) {
+ int32_t mode = aResult.toInt32();
+ SetAutoMounterMode(mode);
+ }
+ return NS_OK;
+ }
+
+ NS_IMETHOD HandleError(const nsAString& aName)
+ {
+ ERR("SettingsCallback::HandleError: %s\n", NS_LossyConvertUTF16toASCII(aName).get());
+ return NS_OK;
+ }
+
+protected:
+ ~SettingsServiceCallback() {}
+};
+
+NS_IMPL_ISUPPORTS(SettingsServiceCallback, nsISettingsServiceCallback)
+
+class CheckVolumeSettingsCallback final : public nsISettingsServiceCallback
+{
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ CheckVolumeSettingsCallback(const nsACString& aVolumeName)
+ : mVolumeName(aVolumeName) {}
+
+ NS_IMETHOD Handle(const nsAString& aName, JS::Handle<JS::Value> aResult)
+ {
+ if (aResult.isBoolean()) {
+ bool isSharingEnabled = aResult.toBoolean();
+ SetAutoMounterSharingMode(mVolumeName, isSharingEnabled);
+ }
+ return NS_OK;
+ }
+
+ NS_IMETHOD HandleError(const nsAString& aName)
+ {
+ ERR("CheckVolumeSettingsCallback::HandleError: %s\n", NS_LossyConvertUTF16toASCII(aName).get());
+ return NS_OK;
+ }
+
+protected:
+ ~CheckVolumeSettingsCallback() {}
+
+private:
+ nsCString mVolumeName;
+};
+
+NS_IMPL_ISUPPORTS(CheckVolumeSettingsCallback, nsISettingsServiceCallback)
+
+AutoMounterSetting::AutoMounterSetting()
+ : mStatus(AUTOMOUNTER_STATUS_DISABLED)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ // Setup an observer to watch changes to the setting
+ nsCOMPtr<nsIObserverService> observerService =
+ mozilla::services::GetObserverService();
+ if (!observerService) {
+ ERR("GetObserverService failed");
+ return;
+ }
+ nsresult rv;
+ rv = observerService->AddObserver(this, MOZSETTINGS_CHANGED, false);
+ if (NS_FAILED(rv)) {
+ ERR("AddObserver failed");
+ return;
+ }
+
+ // Force ums.mode to be 0 initially. We do this because settings are persisted.
+ // We don't want UMS to be enabled until such time as the phone is unlocked,
+ // and gaia/apps/system/js/storage.js takes care of detecting when the phone
+ // becomes unlocked and changes ums.mode appropriately.
+ nsCOMPtr<nsISettingsService> settingsService =
+ do_GetService("@mozilla.org/settingsService;1");
+ if (!settingsService) {
+ ERR("Failed to get settingsLock service!");
+ return;
+ }
+ nsCOMPtr<nsISettingsServiceLock> lock;
+ settingsService->CreateLock(nullptr, getter_AddRefs(lock));
+ nsCOMPtr<nsISettingsServiceCallback> callback = new SettingsServiceCallback();
+ JS::Rooted<JS::Value> value(RootingCx());
+ value.setInt32(AUTOMOUNTER_DISABLE);
+ lock->Set(UMS_MODE, value, callback, nullptr);
+ value.setInt32(mStatus);
+ lock->Set(UMS_STATUS, value, nullptr, nullptr);
+}
+
+AutoMounterSetting::~AutoMounterSetting()
+{
+ nsCOMPtr<nsIObserverService> observerService =
+ mozilla::services::GetObserverService();
+ if (observerService) {
+ observerService->RemoveObserver(this, MOZSETTINGS_CHANGED);
+ }
+}
+
+NS_IMPL_ISUPPORTS(AutoMounterSetting, nsIObserver)
+
+const char *
+AutoMounterSetting::StatusStr(int32_t aStatus)
+{
+ switch (aStatus) {
+ case AUTOMOUNTER_STATUS_DISABLED: return "Disabled";
+ case AUTOMOUNTER_STATUS_ENABLED: return "Enabled";
+ case AUTOMOUNTER_STATUS_FILES_OPEN: return "FilesOpen";
+ }
+ return "??? Unknown ???";
+}
+
+class CheckVolumeSettingsRunnable : public Runnable
+{
+public:
+ CheckVolumeSettingsRunnable(const nsACString& aVolumeName)
+ : mVolumeName(aVolumeName) {}
+
+ NS_IMETHOD Run() override
+ {
+ MOZ_ASSERT(NS_IsMainThread());
+ nsCOMPtr<nsISettingsService> settingsService =
+ do_GetService("@mozilla.org/settingsService;1");
+ NS_ENSURE_TRUE(settingsService, NS_ERROR_FAILURE);
+ nsCOMPtr<nsISettingsServiceLock> lock;
+ settingsService->CreateLock(nullptr, getter_AddRefs(lock));
+ nsCOMPtr<nsISettingsServiceCallback> callback =
+ new CheckVolumeSettingsCallback(mVolumeName);
+ nsPrintfCString setting(UMS_VOLUME_ENABLED_PREFIX "%s" UMS_VOLUME_ENABLED_SUFFIX,
+ mVolumeName.get());
+ lock->Get(setting.get(), callback);
+ return NS_OK;
+ }
+
+private:
+ nsCString mVolumeName;
+};
+
+//static
+void
+AutoMounterSetting::CheckVolumeSettings(const nsACString& aVolumeName)
+{
+ NS_DispatchToMainThread(new CheckVolumeSettingsRunnable(aVolumeName));
+}
+
+class SetStatusRunnable : public Runnable
+{
+public:
+ SetStatusRunnable(int32_t aStatus) : mStatus(aStatus) {}
+
+ NS_IMETHOD Run() override
+ {
+ MOZ_ASSERT(NS_IsMainThread());
+ nsCOMPtr<nsISettingsService> settingsService =
+ do_GetService("@mozilla.org/settingsService;1");
+ NS_ENSURE_TRUE(settingsService, NS_ERROR_FAILURE);
+ nsCOMPtr<nsISettingsServiceLock> lock;
+ settingsService->CreateLock(nullptr, getter_AddRefs(lock));
+ // lock may be null if this gets called during shutdown.
+ if (lock) {
+ JS::Rooted<JS::Value> value(RootingCx(),
+ JS::Int32Value(mStatus));
+ lock->Set(UMS_STATUS, value, nullptr, nullptr);
+ }
+ return NS_OK;
+ }
+
+private:
+ int32_t mStatus;
+};
+
+//static
+void
+AutoMounterSetting::SetStatus(int32_t aStatus)
+{
+ if (aStatus != mStatus) {
+ LOG("Changing status from '%s' to '%s'",
+ StatusStr(mStatus), StatusStr(aStatus));
+ mStatus = aStatus;
+ NS_DispatchToMainThread(new SetStatusRunnable(aStatus));
+ }
+}
+
+NS_IMETHODIMP
+AutoMounterSetting::Observe(nsISupports* aSubject,
+ const char* aTopic,
+ const char16_t* aData)
+{
+ if (strcmp(aTopic, MOZSETTINGS_CHANGED) != 0) {
+ return NS_OK;
+ }
+
+ // Note that this function gets called for any and all settings changes,
+ // so we need to carefully check if we have the one we're interested in.
+ //
+ // The string that we're interested in will be a JSON string that looks like:
+ // {"key":"ums.autoMount","value":true}
+
+ RootedDictionary<SettingChangeNotification> setting(RootingCx());
+ if (!WrappedJSToDictionary(aSubject, setting)) {
+ return NS_OK;
+ }
+
+ // Check for ums.mode changes
+ if (setting.mKey.EqualsASCII(UMS_MODE)) {
+ if (!setting.mValue.isInt32()) {
+ return NS_OK;
+ }
+ int32_t mode = setting.mValue.toInt32();
+ SetAutoMounterMode(mode);
+ return NS_OK;
+ }
+
+ // Check for ums.volume.NAME.enabled
+ if (StringBeginsWith(setting.mKey, NS_LITERAL_STRING(UMS_VOLUME_ENABLED_PREFIX)) &&
+ StringEndsWith(setting.mKey, NS_LITERAL_STRING(UMS_VOLUME_ENABLED_SUFFIX))) {
+ if (!setting.mValue.isBoolean()) {
+ return NS_OK;
+ }
+ const size_t prefixLen = sizeof(UMS_VOLUME_ENABLED_PREFIX) - 1;
+ const size_t suffixLen = sizeof(UMS_VOLUME_ENABLED_SUFFIX) - 1;
+ nsDependentSubstring volumeName =
+ Substring(setting.mKey, prefixLen, setting.mKey.Length() - prefixLen - suffixLen);
+ bool isSharingEnabled = setting.mValue.toBoolean();
+ SetAutoMounterSharingMode(NS_LossyConvertUTF16toASCII(volumeName), isSharingEnabled);
+ return NS_OK;
+ }
+
+ return NS_OK;
+}
+
+} // namespace system
+} // namespace mozilla
diff --git a/dom/system/gonk/AutoMounterSetting.h b/dom/system/gonk/AutoMounterSetting.h
new file mode 100644
index 000000000..7972de379
--- /dev/null
+++ b/dom/system/gonk/AutoMounterSetting.h
@@ -0,0 +1,38 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_system_automountersetting_h__
+#define mozilla_system_automountersetting_h__
+
+#include "nsIObserver.h"
+
+namespace mozilla {
+namespace system {
+
+class AutoMounterSetting : public nsIObserver
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIOBSERVER
+
+ AutoMounterSetting();
+
+ static void CheckVolumeSettings(const nsACString& aVolumeName);
+
+ int32_t GetStatus() { return mStatus; }
+ void SetStatus(int32_t aStatus);
+ const char *StatusStr(int32_t aStatus);
+
+protected:
+ virtual ~AutoMounterSetting();
+
+private:
+ int32_t mStatus;
+};
+
+} // namespace system
+} // namespace mozilla
+
+#endif // mozilla_system_automountersetting_h__
+
diff --git a/dom/system/gonk/DataCallInterfaceService.js b/dom/system/gonk/DataCallInterfaceService.js
new file mode 100644
index 000000000..0f0e7101c
--- /dev/null
+++ b/dom/system/gonk/DataCallInterfaceService.js
@@ -0,0 +1,276 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
+
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
+const DATACALLINTERFACE_CONTRACTID = "@mozilla.org/datacall/interface;1";
+const DATACALLINTERFACESERVICE_CONTRACTID =
+ "@mozilla.org/datacall/interfaceservice;1";
+const DATACALLINTERFACE_CID =
+ Components.ID("{ff669306-4390-462a-989b-ba37fc42153f}");
+const DATACALLINTERFACESERVICE_CID =
+ Components.ID("{e23e9337-592d-40b9-8cef-7bd47c28b72e}");
+
+const TOPIC_XPCOM_SHUTDOWN = "xpcom-shutdown";
+const TOPIC_PREF_CHANGED = "nsPref:changed";
+const PREF_RIL_DEBUG_ENABLED = "ril.debugging.enabled";
+
+XPCOMUtils.defineLazyGetter(this, "RIL", function () {
+ let obj = {};
+ Cu.import("resource://gre/modules/ril_consts.js", obj);
+ return obj;
+});
+
+XPCOMUtils.defineLazyServiceGetter(this, "gRil",
+ "@mozilla.org/ril;1",
+ "nsIRadioInterfaceLayer");
+
+XPCOMUtils.defineLazyServiceGetter(this, "gMobileConnectionService",
+ "@mozilla.org/mobileconnection/mobileconnectionservice;1",
+ "nsIMobileConnectionService");
+
+var DEBUG = RIL.DEBUG_RIL;
+
+function updateDebugFlag() {
+ // Read debug setting from pref
+ let debugPref;
+ try {
+ debugPref = Services.prefs.getBoolPref(PREF_RIL_DEBUG_ENABLED);
+ } catch (e) {
+ debugPref = false;
+ }
+ DEBUG = debugPref || RIL.DEBUG_RIL;
+}
+updateDebugFlag();
+
+function DataCall(aAttributes) {
+ for (let key in aAttributes) {
+ if (key === "pdpType") {
+ // Convert pdp type into constant int value.
+ this[key] = RIL.RIL_DATACALL_PDP_TYPES.indexOf(aAttributes[key]);
+ continue;
+ }
+
+ this[key] = aAttributes[key];
+ }
+}
+DataCall.prototype = {
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIDataCall]),
+
+ failCause: Ci.nsIDataCallInterface.DATACALL_FAIL_NONE,
+ suggestedRetryTime: -1,
+ cid: -1,
+ active: -1,
+ pdpType: -1,
+ ifname: null,
+ addreses: null,
+ dnses: null,
+ gateways: null,
+ pcscf: null,
+ mtu: -1
+};
+
+function DataCallInterfaceService() {
+ this._dataCallInterfaces = [];
+
+ let numClients = gRil.numRadioInterfaces;
+ for (let i = 0; i < numClients; i++) {
+ this._dataCallInterfaces.push(new DataCallInterface(i));
+ }
+
+ Services.obs.addObserver(this, TOPIC_XPCOM_SHUTDOWN, false);
+ Services.prefs.addObserver(PREF_RIL_DEBUG_ENABLED, this, false);
+}
+DataCallInterfaceService.prototype = {
+ classID: DATACALLINTERFACESERVICE_CID,
+ classInfo: XPCOMUtils.generateCI({
+ classID: DATACALLINTERFACESERVICE_CID,
+ contractID: DATACALLINTERFACESERVICE_CONTRACTID,
+ classDescription: "Data Call Interface Service",
+ interfaces: [Ci.nsIDataCallInterfaceService,
+ Ci.nsIGonkDataCallInterfaceService]
+ }),
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIDataCallInterfaceService,
+ Ci.nsIGonkDataCallInterfaceService],
+ Ci.nsIObserver),
+
+ // An array of DataCallInterface instances.
+ _dataCallInterfaces: null,
+
+ debug: function(aMessage) {
+ dump("-*- DataCallInterfaceService: " + aMessage + "\n");
+ },
+
+ // nsIDataCallInterfaceService
+
+ getDataCallInterface: function(aClientId) {
+ let dataCallInterface = this._dataCallInterfaces[aClientId];
+ if (!dataCallInterface) {
+ throw Cr.NS_ERROR_UNEXPECTED;
+ }
+
+ return dataCallInterface;
+ },
+
+ // nsIGonkDataCallInterfaceService
+
+ notifyDataCallListChanged: function(aClientId, aCount, aDataCalls) {
+ let dataCallInterface = this.getDataCallInterface(aClientId);
+ dataCallInterface.handleDataCallListChanged(aCount, aDataCalls);
+ },
+
+ // nsIObserver
+
+ observe: function(aSubject, aTopic, aData) {
+ switch (aTopic) {
+ case TOPIC_PREF_CHANGED:
+ if (aData === PREF_RIL_DEBUG_ENABLED) {
+ updateDebugFlag();
+ }
+ break;
+ case TOPIC_XPCOM_SHUTDOWN:
+ Services.prefs.removeObserver(PREF_RIL_DEBUG_ENABLED, this);
+ Services.obs.removeObserver(this, TOPIC_XPCOM_SHUTDOWN);
+ break;
+ }
+ },
+};
+
+function DataCallInterface(aClientId) {
+ this._clientId = aClientId;
+ this._radioInterface = gRil.getRadioInterface(aClientId);
+ this._listeners = [];
+
+ if (DEBUG) this.debug("DataCallInterface: " + aClientId);
+}
+DataCallInterface.prototype = {
+ classID: DATACALLINTERFACE_CID,
+ classInfo: XPCOMUtils.generateCI({classID: DATACALLINTERFACE_CID,
+ contractID: DATACALLINTERFACE_CONTRACTID,
+ classDescription: "Data Call Interface",
+ interfaces: [Ci.nsIDataCallInterface]}),
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIDataCallInterface]),
+
+ debug: function(aMessage) {
+ dump("-*- DataCallInterface[" + this._clientId + "]: " + aMessage + "\n");
+ },
+
+ _clientId: -1,
+
+ _radioInterface: null,
+
+ _listeners: null,
+
+ // nsIDataCallInterface
+
+ setupDataCall: function(aApn, aUsername, aPassword, aAuthType, aPdpType,
+ aCallback) {
+ let connection =
+ gMobileConnectionService.getItemByServiceId(this._clientId);
+ let dataInfo = connection && connection.data;
+ let radioTechType = dataInfo.type;
+ let radioTechnology = RIL.GECKO_RADIO_TECH.indexOf(radioTechType);
+ // Convert pdp type into string value.
+ let pdpType = RIL.RIL_DATACALL_PDP_TYPES[aPdpType];
+
+ this._radioInterface.sendWorkerMessage("setupDataCall", {
+ radioTech: radioTechnology,
+ apn: aApn,
+ user: aUsername,
+ passwd: aPassword,
+ chappap: aAuthType,
+ pdptype: pdpType
+ }, (aResponse) => {
+ if (aResponse.errorMsg) {
+ aCallback.notifyError(aResponse.errorMsg);
+ } else {
+ let dataCall = new DataCall(aResponse);
+ aCallback.notifySetupDataCallSuccess(dataCall);
+ }
+ });
+ },
+
+ deactivateDataCall: function(aCid, aReason, aCallback) {
+ this._radioInterface.sendWorkerMessage("deactivateDataCall", {
+ cid: aCid,
+ reason: aReason
+ }, (aResponse) => {
+ if (aResponse.errorMsg) {
+ aCallback.notifyError(aResponse.errorMsg);
+ } else {
+ aCallback.notifySuccess();
+ }
+ });
+ },
+
+ getDataCallList: function(aCallback) {
+ this._radioInterface.sendWorkerMessage("getDataCallList", null,
+ (aResponse) => {
+ if (aResponse.errorMsg) {
+ aCallback.notifyError(aResponse.errorMsg);
+ } else {
+ let dataCalls = aResponse.datacalls.map(
+ dataCall => new DataCall(dataCall));
+ aCallback.notifyGetDataCallListSuccess(dataCalls.length, dataCalls);
+ }
+ });
+ },
+
+ setDataRegistration: function(aAttach, aCallback) {
+ this._radioInterface.sendWorkerMessage("setDataRegistration", {
+ attach: aAttach
+ }, (aResponse) => {
+ if (aResponse.errorMsg) {
+ aCallback.notifyError(aResponse.errorMsg);
+ } else {
+ aCallback.notifySuccess();
+ }
+ });
+ },
+
+ handleDataCallListChanged: function(aCount, aDataCalls) {
+ this._notifyAllListeners("notifyDataCallListChanged", [aCount, aDataCalls]);
+ },
+
+ _notifyAllListeners: function(aMethodName, aArgs) {
+ let listeners = this._listeners.slice();
+ for (let listener of listeners) {
+ if (this._listeners.indexOf(listener) == -1) {
+ // Listener has been unregistered in previous run.
+ continue;
+ }
+
+ let handler = listener[aMethodName];
+ try {
+ handler.apply(listener, aArgs);
+ } catch (e) {
+ if (DEBUG) {
+ this.debug("listener for " + aMethodName + " threw an exception: " + e);
+ }
+ }
+ }
+ },
+
+ registerListener: function(aListener) {
+ if (this._listeners.indexOf(aListener) >= 0) {
+ return;
+ }
+
+ this._listeners.push(aListener);
+ },
+
+ unregisterListener: function(aListener) {
+ let index = this._listeners.indexOf(aListener);
+ if (index >= 0) {
+ this._listeners.splice(index, 1);
+ }
+ },
+};
+
+this.NSGetFactory = XPCOMUtils.generateNSGetFactory([DataCallInterfaceService]); \ No newline at end of file
diff --git a/dom/system/gonk/DataCallInterfaceService.manifest b/dom/system/gonk/DataCallInterfaceService.manifest
new file mode 100644
index 000000000..bf062c7e9
--- /dev/null
+++ b/dom/system/gonk/DataCallInterfaceService.manifest
@@ -0,0 +1,6 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+component {e23e9337-592d-40b9-8cef-7bd47c28b72e} DataCallInterfaceService.js
+contract @mozilla.org/datacall/interfaceservice;1 {e23e9337-592d-40b9-8cef-7bd47c28b72e} \ No newline at end of file
diff --git a/dom/system/gonk/DataCallManager.js b/dom/system/gonk/DataCallManager.js
new file mode 100644
index 000000000..5411987cd
--- /dev/null
+++ b/dom/system/gonk/DataCallManager.js
@@ -0,0 +1,1726 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/systemlibs.js");
+
+XPCOMUtils.defineLazyServiceGetter(this, "gSettingsService",
+ "@mozilla.org/settingsService;1",
+ "nsISettingsService");
+
+XPCOMUtils.defineLazyServiceGetter(this, "gNetworkManager",
+ "@mozilla.org/network/manager;1",
+ "nsINetworkManager");
+
+XPCOMUtils.defineLazyServiceGetter(this, "gMobileConnectionService",
+ "@mozilla.org/mobileconnection/mobileconnectionservice;1",
+ "nsIMobileConnectionService");
+
+XPCOMUtils.defineLazyServiceGetter(this, "gIccService",
+ "@mozilla.org/icc/iccservice;1",
+ "nsIIccService");
+
+XPCOMUtils.defineLazyServiceGetter(this, "gDataCallInterfaceService",
+ "@mozilla.org/datacall/interfaceservice;1",
+ "nsIDataCallInterfaceService");
+
+XPCOMUtils.defineLazyGetter(this, "RIL", function() {
+ let obj = {};
+ Cu.import("resource://gre/modules/ril_consts.js", obj);
+ return obj;
+});
+
+// Ril quirk to attach data registration on demand.
+var RILQUIRKS_DATA_REGISTRATION_ON_DEMAND =
+ libcutils.property_get("ro.moz.ril.data_reg_on_demand", "false") == "true";
+
+// Ril quirk to control the uicc/data subscription.
+var RILQUIRKS_SUBSCRIPTION_CONTROL =
+ libcutils.property_get("ro.moz.ril.subscription_control", "false") == "true";
+
+// Ril quirk to enable IPv6 protocol/roaming protocol in APN settings.
+var RILQUIRKS_HAVE_IPV6 =
+ libcutils.property_get("ro.moz.ril.ipv6", "false") == "true";
+
+const DATACALLMANAGER_CID =
+ Components.ID("{35b9efa2-e42c-45ce-8210-0a13e6f4aadc}");
+const DATACALLHANDLER_CID =
+ Components.ID("{132b650f-c4d8-4731-96c5-83785cb31dee}");
+const RILNETWORKINTERFACE_CID =
+ Components.ID("{9574ee84-5d0d-4814-b9e6-8b279e03dcf4}");
+const RILNETWORKINFO_CID =
+ Components.ID("{dd6cf2f0-f0e3-449f-a69e-7c34fdcb8d4b}");
+
+const TOPIC_XPCOM_SHUTDOWN = "xpcom-shutdown";
+const TOPIC_MOZSETTINGS_CHANGED = "mozsettings-changed";
+const TOPIC_PREF_CHANGED = "nsPref:changed";
+const TOPIC_DATA_CALL_ERROR = "data-call-error";
+const PREF_RIL_DEBUG_ENABLED = "ril.debugging.enabled";
+
+const NETWORK_TYPE_UNKNOWN = Ci.nsINetworkInfo.NETWORK_TYPE_UNKNOWN;
+const NETWORK_TYPE_WIFI = Ci.nsINetworkInfo.NETWORK_TYPE_WIFI;
+const NETWORK_TYPE_MOBILE = Ci.nsINetworkInfo.NETWORK_TYPE_MOBILE;
+const NETWORK_TYPE_MOBILE_MMS = Ci.nsINetworkInfo.NETWORK_TYPE_MOBILE_MMS;
+const NETWORK_TYPE_MOBILE_SUPL = Ci.nsINetworkInfo.NETWORK_TYPE_MOBILE_SUPL;
+const NETWORK_TYPE_MOBILE_IMS = Ci.nsINetworkInfo.NETWORK_TYPE_MOBILE_IMS;
+const NETWORK_TYPE_MOBILE_DUN = Ci.nsINetworkInfo.NETWORK_TYPE_MOBILE_DUN;
+const NETWORK_TYPE_MOBILE_FOTA = Ci.nsINetworkInfo.NETWORK_TYPE_MOBILE_FOTA;
+
+const NETWORK_STATE_UNKNOWN = Ci.nsINetworkInfo.NETWORK_STATE_UNKNOWN;
+const NETWORK_STATE_CONNECTING = Ci.nsINetworkInfo.NETWORK_STATE_CONNECTING;
+const NETWORK_STATE_CONNECTED = Ci.nsINetworkInfo.NETWORK_STATE_CONNECTED;
+const NETWORK_STATE_DISCONNECTING = Ci.nsINetworkInfo.NETWORK_STATE_DISCONNECTING;
+const NETWORK_STATE_DISCONNECTED = Ci.nsINetworkInfo.NETWORK_STATE_DISCONNECTED;
+
+const INT32_MAX = 2147483647;
+
+// set to true in ril_consts.js to see debug messages
+var DEBUG = RIL.DEBUG_RIL;
+
+function updateDebugFlag() {
+ // Read debug setting from pref
+ let debugPref;
+ try {
+ debugPref = Services.prefs.getBoolPref(PREF_RIL_DEBUG_ENABLED);
+ } catch (e) {
+ debugPref = false;
+ }
+ DEBUG = debugPref || RIL.DEBUG_RIL;
+}
+updateDebugFlag();
+
+function DataCallManager() {
+ this._connectionHandlers = [];
+
+ let numRadioInterfaces = gMobileConnectionService.numItems;
+ for (let clientId = 0; clientId < numRadioInterfaces; clientId++) {
+ this._connectionHandlers.push(new DataCallHandler(clientId));
+ }
+
+ let lock = gSettingsService.createLock();
+ // Read the APN data from the settings DB.
+ lock.get("ril.data.apnSettings", this);
+ // Read the data enabled setting from DB.
+ lock.get("ril.data.enabled", this);
+ lock.get("ril.data.roaming_enabled", this);
+ // Read the default client id for data call.
+ lock.get("ril.data.defaultServiceId", this);
+
+ Services.obs.addObserver(this, TOPIC_XPCOM_SHUTDOWN, false);
+ Services.obs.addObserver(this, TOPIC_MOZSETTINGS_CHANGED, false);
+ Services.prefs.addObserver(PREF_RIL_DEBUG_ENABLED, this, false);
+}
+DataCallManager.prototype = {
+ classID: DATACALLMANAGER_CID,
+ classInfo: XPCOMUtils.generateCI({classID: DATACALLMANAGER_CID,
+ classDescription: "Data Call Manager",
+ interfaces: [Ci.nsIDataCallManager]}),
+
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIDataCallManager,
+ Ci.nsIObserver,
+ Ci.nsISettingsServiceCallback]),
+
+ _connectionHandlers: null,
+
+ // Flag to determine the data state to start with when we boot up. It
+ // corresponds to the 'ril.data.enabled' setting from the UI.
+ _dataEnabled: false,
+
+ // Flag to record the default client id for data call. It corresponds to
+ // the 'ril.data.defaultServiceId' setting from the UI.
+ _dataDefaultClientId: -1,
+
+ // Flag to record the current default client id for data call.
+ // It differs from _dataDefaultClientId in that it is set only when
+ // the switch of client id process is done.
+ _currentDataClientId: -1,
+
+ // Pending function to execute when we are notified that another data call has
+ // been disconnected.
+ _pendingDataCallRequest: null,
+
+ debug: function(aMsg) {
+ dump("-*- DataCallManager: " + aMsg + "\n");
+ },
+
+ get dataDefaultServiceId() {
+ return this._dataDefaultClientId;
+ },
+
+ getDataCallHandler: function(aClientId) {
+ let handler = this._connectionHandlers[aClientId]
+ if (!handler) {
+ throw Cr.NS_ERROR_UNEXPECTED;
+ }
+
+ return handler;
+ },
+
+ _setDataRegistration: function(aDataCallInterface, aAttach) {
+ return new Promise(function(aResolve, aReject) {
+ let callback = {
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIDataCallCallback]),
+ notifySuccess: function() {
+ aResolve();
+ },
+ notifyError: function(aErrorMsg) {
+ aReject(aErrorMsg);
+ }
+ };
+
+ aDataCallInterface.setDataRegistration(aAttach, callback);
+ });
+ },
+
+ _handleDataClientIdChange: function(aNewClientId) {
+ if (this._dataDefaultClientId === aNewClientId) {
+ return;
+ }
+ this._dataDefaultClientId = aNewClientId;
+
+ // This is to handle boot up stage.
+ if (this._currentDataClientId == -1) {
+ this._currentDataClientId = this._dataDefaultClientId;
+ let connHandler = this._connectionHandlers[this._currentDataClientId];
+ let dcInterface = connHandler.dataCallInterface;
+ if (RILQUIRKS_DATA_REGISTRATION_ON_DEMAND ||
+ RILQUIRKS_SUBSCRIPTION_CONTROL) {
+ this._setDataRegistration(dcInterface, true);
+ }
+ if (this._dataEnabled) {
+ let settings = connHandler.dataCallSettings;
+ settings.oldEnabled = settings.enabled;
+ settings.enabled = true;
+ connHandler.updateRILNetworkInterface();
+ }
+ return;
+ }
+
+ let oldConnHandler = this._connectionHandlers[this._currentDataClientId];
+ let oldIface = oldConnHandler.dataCallInterface;
+ let oldSettings = oldConnHandler.dataCallSettings;
+ let newConnHandler = this._connectionHandlers[this._dataDefaultClientId];
+ let newIface = newConnHandler.dataCallInterface;
+ let newSettings = newConnHandler.dataCallSettings;
+
+ let applyPendingDataSettings = () => {
+ if (DEBUG) {
+ this.debug("Apply pending data registration and settings.");
+ }
+
+ if (RILQUIRKS_DATA_REGISTRATION_ON_DEMAND ||
+ RILQUIRKS_SUBSCRIPTION_CONTROL) {
+ this._setDataRegistration(oldIface, false).then(() => {
+ if (this._dataEnabled) {
+ newSettings.oldEnabled = newSettings.enabled;
+ newSettings.enabled = true;
+ }
+ this._currentDataClientId = this._dataDefaultClientId;
+
+ this._setDataRegistration(newIface, true).then(() => {
+ newConnHandler.updateRILNetworkInterface();
+ });
+ });
+ return;
+ }
+
+ if (this._dataEnabled) {
+ newSettings.oldEnabled = newSettings.enabled;
+ newSettings.enabled = true;
+ }
+
+ this._currentDataClientId = this._dataDefaultClientId;
+ newConnHandler.updateRILNetworkInterface();
+ };
+
+ if (this._dataEnabled) {
+ oldSettings.oldEnabled = oldSettings.enabled;
+ oldSettings.enabled = false;
+ }
+
+ oldConnHandler.deactivateDataCallsAndWait().then(() => {
+ applyPendingDataSettings();
+ });
+ },
+
+ _shutdown: function() {
+ for (let handler of this._connectionHandlers) {
+ handler.shutdown();
+ }
+ this._connectionHandlers = null;
+ Services.prefs.removeObserver(PREF_RIL_DEBUG_ENABLED, this);
+ Services.obs.removeObserver(this, TOPIC_XPCOM_SHUTDOWN);
+ Services.obs.removeObserver(this, TOPIC_MOZSETTINGS_CHANGED);
+ },
+
+ /**
+ * nsISettingsServiceCallback
+ */
+ handle: function(aName, aResult) {
+ switch (aName) {
+ case "ril.data.apnSettings":
+ if (DEBUG) {
+ this.debug("'ril.data.apnSettings' is now " +
+ JSON.stringify(aResult));
+ }
+ if (!aResult) {
+ break;
+ }
+ for (let clientId in this._connectionHandlers) {
+ let handler = this._connectionHandlers[clientId];
+ let apnSetting = aResult[clientId];
+ if (handler && apnSetting) {
+ handler.updateApnSettings(apnSetting);
+ }
+ }
+ break;
+ case "ril.data.enabled":
+ if (DEBUG) {
+ this.debug("'ril.data.enabled' is now " + aResult);
+ }
+ if (this._dataEnabled === aResult) {
+ break;
+ }
+ this._dataEnabled = aResult;
+
+ if (DEBUG) {
+ this.debug("Default id for data call: " + this._dataDefaultClientId);
+ }
+ if (this._dataDefaultClientId === -1) {
+ // We haven't got the default id for data from db.
+ break;
+ }
+
+ let connHandler = this._connectionHandlers[this._dataDefaultClientId];
+ let settings = connHandler.dataCallSettings;
+ settings.oldEnabled = settings.enabled;
+ settings.enabled = aResult;
+ connHandler.updateRILNetworkInterface();
+ break;
+ case "ril.data.roaming_enabled":
+ if (DEBUG) {
+ this.debug("'ril.data.roaming_enabled' is now " + aResult);
+ this.debug("Default id for data call: " + this._dataDefaultClientId);
+ }
+ for (let clientId = 0; clientId < this._connectionHandlers.length; clientId++) {
+ let connHandler = this._connectionHandlers[clientId];
+ let settings = connHandler.dataCallSettings;
+ settings.roamingEnabled = Array.isArray(aResult) ? aResult[clientId]
+ : aResult;
+ }
+ if (this._dataDefaultClientId === -1) {
+ // We haven't got the default id for data from db.
+ break;
+ }
+ this._connectionHandlers[this._dataDefaultClientId].updateRILNetworkInterface();
+ break;
+ case "ril.data.defaultServiceId":
+ aResult = aResult || 0;
+ if (DEBUG) {
+ this.debug("'ril.data.defaultServiceId' is now " + aResult);
+ }
+ this._handleDataClientIdChange(aResult);
+ break;
+ }
+ },
+
+ handleError: function(aErrorMessage) {
+ if (DEBUG) {
+ this.debug("There was an error while reading RIL settings.");
+ }
+ },
+
+ /**
+ * nsIObserver interface methods.
+ */
+ observe: function(aSubject, aTopic, aData) {
+ switch (aTopic) {
+ case TOPIC_MOZSETTINGS_CHANGED:
+ if ("wrappedJSObject" in aSubject) {
+ aSubject = aSubject.wrappedJSObject;
+ }
+ this.handle(aSubject.key, aSubject.value);
+ break;
+ case TOPIC_PREF_CHANGED:
+ if (aData === PREF_RIL_DEBUG_ENABLED) {
+ updateDebugFlag();
+ }
+ break;
+ case TOPIC_XPCOM_SHUTDOWN:
+ this._shutdown();
+ break;
+ }
+ },
+};
+
+function DataCallHandler(aClientId) {
+ // Initial owning attributes.
+ this.clientId = aClientId;
+ this.dataCallSettings = {
+ oldEnabled: false,
+ enabled: false,
+ roamingEnabled: false
+ };
+ this._dataCalls = [];
+ this._listeners = [];
+
+ // This map is used to collect all the apn types and its corresponding
+ // RILNetworkInterface.
+ this.dataNetworkInterfaces = new Map();
+
+ this.dataCallInterface = gDataCallInterfaceService.getDataCallInterface(aClientId);
+ this.dataCallInterface.registerListener(this);
+
+ let mobileConnection = gMobileConnectionService.getItemByServiceId(aClientId);
+ mobileConnection.registerListener(this);
+
+ this._dataInfo = {
+ state: mobileConnection.data.state,
+ type: mobileConnection.data.type,
+ roaming: mobileConnection.data.roaming
+ }
+}
+DataCallHandler.prototype = {
+ classID: DATACALLHANDLER_CID,
+ classInfo: XPCOMUtils.generateCI({classID: DATACALLHANDLER_CID,
+ classDescription: "Data Call Handler",
+ interfaces: [Ci.nsIDataCallHandler]}),
+
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIDataCallHandler,
+ Ci.nsIDataCallInterfaceListener,
+ Ci.nsIMobileConnectionListener]),
+
+ clientId: 0,
+ dataCallInterface: null,
+ dataCallSettings: null,
+ dataNetworkInterfaces: null,
+ _dataCalls: null,
+ _dataInfo: null,
+
+ // Apn settings to be setup after data call are cleared.
+ _pendingApnSettings: null,
+
+ debug: function(aMsg) {
+ dump("-*- DataCallHandler[" + this.clientId + "]: " + aMsg + "\n");
+ },
+
+ shutdown: function() {
+ // Shutdown all RIL network interfaces
+ this.dataNetworkInterfaces.forEach(function(networkInterface) {
+ gNetworkManager.unregisterNetworkInterface(networkInterface);
+ networkInterface.shutdown();
+ networkInterface = null;
+ });
+ this.dataNetworkInterfaces.clear();
+ this._dataCalls = [];
+ this.clientId = null;
+
+ this.dataCallInterface.unregisterListener(this);
+ this.dataCallInterface = null;
+
+ let mobileConnection =
+ gMobileConnectionService.getItemByServiceId(this.clientId);
+ mobileConnection.unregisterListener(this);
+ },
+
+ /**
+ * Check if we get all necessary APN data.
+ */
+ _validateApnSetting: function(aApnSetting) {
+ return (aApnSetting &&
+ aApnSetting.apn &&
+ aApnSetting.types &&
+ aApnSetting.types.length);
+ },
+
+ _convertApnType: function(aApnType) {
+ switch (aApnType) {
+ case "default":
+ return NETWORK_TYPE_MOBILE;
+ case "mms":
+ return NETWORK_TYPE_MOBILE_MMS;
+ case "supl":
+ return NETWORK_TYPE_MOBILE_SUPL;
+ case "ims":
+ return NETWORK_TYPE_MOBILE_IMS;
+ case "dun":
+ return NETWORK_TYPE_MOBILE_DUN;
+ case "fota":
+ return NETWORK_TYPE_MOBILE_FOTA;
+ default:
+ return NETWORK_TYPE_UNKNOWN;
+ }
+ },
+
+ _compareDataCallOptions: function(aDataCall, aNewDataCall) {
+ return aDataCall.apnProfile.apn == aNewDataCall.apnProfile.apn &&
+ aDataCall.apnProfile.user == aNewDataCall.apnProfile.user &&
+ aDataCall.apnProfile.password == aNewDataCall.apnProfile.passwd &&
+ aDataCall.apnProfile.authType == aNewDataCall.apnProfile.authType &&
+ aDataCall.apnProfile.protocol == aNewDataCall.apnProfile.protocol &&
+ aDataCall.apnProfile.roaming_protocol == aNewDataCall.apnProfile.roaming_protocol;
+ },
+
+ /**
+ * This function will do the following steps:
+ * 1. Clear the cached APN settings in the RIL.
+ * 2. Combine APN, user name, and password as the key of |byApn| object to
+ * refer to the corresponding APN setting.
+ * 3. Use APN type as the index of |byType| object to refer to the
+ * corresponding APN setting.
+ * 4. Create RilNetworkInterface for each APN setting created at step 2.
+ */
+ _setupApnSettings: function(aNewApnSettings) {
+ if (!aNewApnSettings) {
+ return;
+ }
+ if (DEBUG) this.debug("setupApnSettings: " + JSON.stringify(aNewApnSettings));
+
+ // Shutdown all network interfaces and clear data calls.
+ this.dataNetworkInterfaces.forEach(function(networkInterface) {
+ gNetworkManager.unregisterNetworkInterface(networkInterface);
+ networkInterface.shutdown();
+ networkInterface = null;
+ });
+ this.dataNetworkInterfaces.clear();
+ this._dataCalls = [];
+
+ // Cache the APN settings by APNs and by types in the RIL.
+ for (let inputApnSetting of aNewApnSettings) {
+ if (!this._validateApnSetting(inputApnSetting)) {
+ continue;
+ }
+
+ // Use APN type as the key of dataNetworkInterfaces to refer to the
+ // corresponding RILNetworkInterface.
+ for (let i = 0; i < inputApnSetting.types.length; i++) {
+ let apnType = inputApnSetting.types[i];
+ let networkType = this._convertApnType(apnType);
+ if (networkType === NETWORK_TYPE_UNKNOWN) {
+ if (DEBUG) this.debug("Invalid apn type: " + apnType);
+ continue;
+ }
+
+ if (DEBUG) this.debug("Preparing RILNetworkInterface for type: " + apnType);
+ // Create DataCall for RILNetworkInterface or reuse one that is shareable.
+ let dataCall;
+ for (let i = 0; i < this._dataCalls.length; i++) {
+ if (this._dataCalls[i].canHandleApn(inputApnSetting)) {
+ if (DEBUG) this.debug("Found shareable DataCall, reusing it.");
+ dataCall = this._dataCalls[i];
+ break;
+ }
+ }
+
+ if (!dataCall) {
+ if (DEBUG) this.debug("No shareable DataCall found, creating one.");
+ dataCall = new DataCall(this.clientId, inputApnSetting, this);
+ this._dataCalls.push(dataCall);
+ }
+
+ try {
+ let networkInterface = new RILNetworkInterface(this, networkType,
+ inputApnSetting,
+ dataCall);
+ gNetworkManager.registerNetworkInterface(networkInterface);
+ this.dataNetworkInterfaces.set(networkType, networkInterface);
+ } catch (e) {
+ if (DEBUG) {
+ this.debug("Error setting up RILNetworkInterface for type " +
+ apnType + ": " + e);
+ }
+ }
+ }
+ }
+ },
+
+ /**
+ * Check if all data is disconnected.
+ */
+ allDataDisconnected: function() {
+ for (let i = 0; i < this._dataCalls.length; i++) {
+ let dataCall = this._dataCalls[i];
+ if (dataCall.state != NETWORK_STATE_UNKNOWN &&
+ dataCall.state != NETWORK_STATE_DISCONNECTED) {
+ return false;
+ }
+ }
+ return true;
+ },
+
+ deactivateDataCallsAndWait: function() {
+ return new Promise((aResolve, aReject) => {
+ this.deactivateDataCalls({
+ notifyDataCallsDisconnected: function() {
+ aResolve();
+ }
+ });
+ });
+ },
+
+ updateApnSettings: function(aNewApnSettings) {
+ if (!aNewApnSettings) {
+ return;
+ }
+ if (this._pendingApnSettings) {
+ // Change of apn settings in process, just update to the newest.
+ this._pengingApnSettings = aNewApnSettings;
+ return;
+ }
+
+ this._pendingApnSettings = aNewApnSettings;
+ this.deactivateDataCallsAndWait().then(() => {
+ this._setupApnSettings(this._pendingApnSettings);
+ this._pendingApnSettings = null;
+ this.updateRILNetworkInterface();
+ });
+ },
+
+ updateRILNetworkInterface: function() {
+ let networkInterface = this.dataNetworkInterfaces.get(NETWORK_TYPE_MOBILE);
+ if (!networkInterface) {
+ if (DEBUG) {
+ this.debug("No network interface for default data.");
+ }
+ return;
+ }
+
+ let connection =
+ gMobileConnectionService.getItemByServiceId(this.clientId);
+
+ // This check avoids data call connection if the radio is not ready
+ // yet after toggling off airplane mode.
+ let radioState = connection && connection.radioState;
+ if (radioState != Ci.nsIMobileConnection.MOBILE_RADIO_STATE_ENABLED) {
+ if (DEBUG) {
+ this.debug("RIL is not ready for data connection: radio's not ready");
+ }
+ return;
+ }
+
+ // We only watch at "ril.data.enabled" flag changes for connecting or
+ // disconnecting the data call. If the value of "ril.data.enabled" is
+ // true and any of the remaining flags change the setting application
+ // should turn this flag to false and then to true in order to reload
+ // the new values and reconnect the data call.
+ if (this.dataCallSettings.oldEnabled === this.dataCallSettings.enabled) {
+ if (DEBUG) {
+ this.debug("No changes for ril.data.enabled flag. Nothing to do.");
+ }
+ return;
+ }
+
+ let dataInfo = connection && connection.data;
+ let isRegistered =
+ dataInfo &&
+ dataInfo.state == RIL.GECKO_MOBILE_CONNECTION_STATE_REGISTERED;
+ let haveDataConnection =
+ dataInfo &&
+ dataInfo.type != RIL.GECKO_MOBILE_CONNECTION_STATE_UNKNOWN;
+ if (!isRegistered || !haveDataConnection) {
+ if (DEBUG) {
+ this.debug("RIL is not ready for data connection: Phone's not " +
+ "registered or doesn't have data connection.");
+ }
+ return;
+ }
+ let wifi_active = false;
+ if (gNetworkManager.activeNetworkInfo &&
+ gNetworkManager.activeNetworkInfo.type == NETWORK_TYPE_WIFI) {
+ wifi_active = true;
+ }
+
+ let defaultDataCallConnected = networkInterface.connected;
+
+ // We have moved part of the decision making into DataCall, the rest will be
+ // moved after Bug 904514 - [meta] NetworkManager enhancement.
+ if (networkInterface.enabled &&
+ (!this.dataCallSettings.enabled ||
+ (dataInfo.roaming && !this.dataCallSettings.roamingEnabled))) {
+ if (DEBUG) {
+ this.debug("Data call settings: disconnect data call.");
+ }
+ networkInterface.disconnect();
+ return;
+ }
+
+ if (networkInterface.enabled && wifi_active) {
+ if (DEBUG) {
+ this.debug("Disconnect data call when Wifi is connected.");
+ }
+ networkInterface.disconnect();
+ return;
+ }
+
+ if (!this.dataCallSettings.enabled || defaultDataCallConnected) {
+ if (DEBUG) {
+ this.debug("Data call settings: nothing to do.");
+ }
+ return;
+ }
+ if (dataInfo.roaming && !this.dataCallSettings.roamingEnabled) {
+ if (DEBUG) {
+ this.debug("We're roaming, but data roaming is disabled.");
+ }
+ return;
+ }
+ if (wifi_active) {
+ if (DEBUG) {
+ this.debug("Don't connect data call when Wifi is connected.");
+ }
+ return;
+ }
+ if (this._pendingApnSettings) {
+ if (DEBUG) this.debug("We're changing apn settings, ignore any changes.");
+ return;
+ }
+
+ if (this._deactivatingDataCalls) {
+ if (DEBUG) this.debug("We're deactivating all data calls, ignore any changes.");
+ return;
+ }
+
+ if (DEBUG) {
+ this.debug("Data call settings: connect data call.");
+ }
+ networkInterface.connect();
+ },
+
+ _isMobileNetworkType: function(aNetworkType) {
+ if (aNetworkType === NETWORK_TYPE_MOBILE ||
+ aNetworkType === NETWORK_TYPE_MOBILE_MMS ||
+ aNetworkType === NETWORK_TYPE_MOBILE_SUPL ||
+ aNetworkType === NETWORK_TYPE_MOBILE_IMS ||
+ aNetworkType === NETWORK_TYPE_MOBILE_DUN ||
+ aNetworkType === NETWORK_TYPE_MOBILE_FOTA) {
+ return true;
+ }
+
+ return false;
+ },
+
+ getDataCallStateByType: function(aNetworkType) {
+ if (!this._isMobileNetworkType(aNetworkType)) {
+ if (DEBUG) this.debug(aNetworkType + " is not a mobile network type!");
+ throw Cr.NS_ERROR_INVALID_ARG;
+ }
+
+ let networkInterface = this.dataNetworkInterfaces.get(aNetworkType);
+ if (!networkInterface) {
+ return NETWORK_STATE_UNKNOWN;
+ }
+ return networkInterface.info.state;
+ },
+
+ setupDataCallByType: function(aNetworkType) {
+ if (DEBUG) {
+ this.debug("setupDataCallByType: " + aNetworkType);
+ }
+
+ if (!this._isMobileNetworkType(aNetworkType)) {
+ if (DEBUG) this.debug(aNetworkType + " is not a mobile network type!");
+ throw Cr.NS_ERROR_INVALID_ARG;
+ }
+
+ let networkInterface = this.dataNetworkInterfaces.get(aNetworkType);
+ if (!networkInterface) {
+ if (DEBUG) {
+ this.debug("No network interface for type: " + aNetworkType);
+ }
+ return;
+ }
+
+ networkInterface.connect();
+ },
+
+ deactivateDataCallByType: function(aNetworkType) {
+ if (DEBUG) {
+ this.debug("deactivateDataCallByType: " + aNetworkType);
+ }
+
+ if (!this._isMobileNetworkType(aNetworkType)) {
+ if (DEBUG) this.debug(aNetworkType + " is not a mobile network type!");
+ throw Cr.NS_ERROR_INVALID_ARG;
+ }
+
+ let networkInterface = this.dataNetworkInterfaces.get(aNetworkType);
+ if (!networkInterface) {
+ if (DEBUG) {
+ this.debug("No network interface for type: " + aNetworkType);
+ }
+ return;
+ }
+
+ networkInterface.disconnect();
+ },
+
+ _deactivatingDataCalls: false,
+
+ deactivateDataCalls: function(aCallback) {
+ let dataDisconnecting = false;
+ this.dataNetworkInterfaces.forEach(function(networkInterface) {
+ if (networkInterface.enabled) {
+ if (networkInterface.info.state != NETWORK_STATE_UNKNOWN &&
+ networkInterface.info.state != NETWORK_STATE_DISCONNECTED) {
+ dataDisconnecting = true;
+ }
+ networkInterface.disconnect();
+ }
+ });
+
+ this._deactivatingDataCalls = dataDisconnecting;
+ if (!dataDisconnecting) {
+ aCallback.notifyDataCallsDisconnected();
+ return;
+ }
+
+ let callback = {
+ notifyAllDataDisconnected: () => {
+ this._unregisterListener(callback);
+ aCallback.notifyDataCallsDisconnected();
+ }
+ };
+ this._registerListener(callback);
+ },
+
+ _listeners: null,
+
+ _notifyListeners: function(aMethodName, aArgs) {
+ let listeners = this._listeners.slice();
+ for (let listener of listeners) {
+ if (this._listeners.indexOf(listener) == -1) {
+ // Listener has been unregistered in previous run.
+ continue;
+ }
+
+ let handler = listener[aMethodName];
+ try {
+ handler.apply(listener, aArgs);
+ } catch (e) {
+ this.debug("listener for " + aMethodName + " threw an exception: " + e);
+ }
+ }
+ },
+
+ _registerListener: function(aListener) {
+ if (this._listeners.indexOf(aListener) >= 0) {
+ return;
+ }
+
+ this._listeners.push(aListener);
+ },
+
+ _unregisterListener: function(aListener) {
+ let index = this._listeners.indexOf(aListener);
+ if (index >= 0) {
+ this._listeners.splice(index, 1);
+ }
+ },
+
+ _findDataCallByCid: function(aCid) {
+ if (aCid === undefined || aCid < 0) {
+ return -1;
+ }
+
+ for (let i = 0; i < this._dataCalls.length; i++) {
+ let datacall = this._dataCalls[i];
+ if (datacall.linkInfo.cid != null &&
+ datacall.linkInfo.cid == aCid) {
+ return i;
+ }
+ }
+
+ return -1;
+ },
+
+ /**
+ * Notify about data call setup error, called from DataCall.
+ */
+ notifyDataCallError: function(aDataCall, aErrorMsg) {
+ // Notify data call error only for data APN
+ let networkInterface = this.dataNetworkInterfaces.get(NETWORK_TYPE_MOBILE);
+ if (networkInterface && networkInterface.enabled) {
+ let dataCall = networkInterface.dataCall;
+ if (this._compareDataCallOptions(dataCall, aDataCall)) {
+ Services.obs.notifyObservers(networkInterface.info,
+ TOPIC_DATA_CALL_ERROR, aErrorMsg);
+ }
+ }
+ },
+
+ /**
+ * Notify about data call changed, called from DataCall.
+ */
+ notifyDataCallChanged: function(aUpdatedDataCall) {
+ // Process pending radio power off request after all data calls
+ // are disconnected.
+ if (aUpdatedDataCall.state == NETWORK_STATE_DISCONNECTED ||
+ aUpdatedDataCall.state == NETWORK_STATE_UNKNOWN &&
+ this.allDataDisconnected() && this._deactivatingDataCalls) {
+ this._deactivatingDataCalls = false;
+ this._notifyListeners("notifyAllDataDisconnected", {
+ clientId: this.clientId
+ });
+ }
+ },
+
+ // nsIDataCallInterfaceListener
+
+ notifyDataCallListChanged: function(aCount, aDataCallList) {
+ let currentDataCalls = this._dataCalls.slice();
+ for (let i = 0; i < aDataCallList.length; i++) {
+ let dataCall = aDataCallList[i];
+ let index = this._findDataCallByCid(dataCall.cid);
+ if (index == -1) {
+ if (DEBUG) {
+ this.debug("Unexpected new data call: " + JSON.stringify(dataCall));
+ }
+ continue;
+ }
+ currentDataCalls[index].onDataCallChanged(dataCall);
+ currentDataCalls[index] = null;
+ }
+
+ // If there is any CONNECTED DataCall left in currentDataCalls, means that
+ // it is missing in dataCallList, we should send a DISCONNECTED event to
+ // notify about this.
+ for (let i = 0; i < currentDataCalls.length; i++) {
+ let currentDataCall = currentDataCalls[i];
+ if (currentDataCall && currentDataCall.linkInfo.cid != null &&
+ currentDataCall.state == NETWORK_STATE_CONNECTED) {
+ if (DEBUG) {
+ this.debug("Expected data call missing: " + JSON.stringify(
+ currentDataCall.apnProfile) + ", must have been DISCONNECTED.");
+ }
+ currentDataCall.onDataCallChanged({
+ state: NETWORK_STATE_DISCONNECTED
+ });
+ }
+ }
+ },
+
+ // nsIMobileConnectionListener
+
+ notifyVoiceChanged: function() {},
+
+ notifyDataChanged: function () {
+ let connection = gMobileConnectionService.getItemByServiceId(this.clientId);
+ let newDataInfo = connection.data;
+
+ if (this._dataInfo.state == newDataInfo.state &&
+ this._dataInfo.type == newDataInfo.type &&
+ this._dataInfo.roaming == newDataInfo.roaming) {
+ return;
+ }
+
+ this._dataInfo.state = newDataInfo.state;
+ this._dataInfo.type = newDataInfo.type;
+ this._dataInfo.roaming = newDataInfo.roaming;
+ this.updateRILNetworkInterface();
+ },
+
+ notifyDataError: function (aMessage) {},
+
+ notifyCFStateChanged: function(aAction, aReason, aNumber, aTimeSeconds, aServiceClass) {},
+
+ notifyEmergencyCbModeChanged: function(aActive, aTimeoutMs) {},
+
+ notifyOtaStatusChanged: function(aStatus) {},
+
+ notifyRadioStateChanged: function() {},
+
+ notifyClirModeChanged: function(aMode) {},
+
+ notifyLastKnownNetworkChanged: function() {},
+
+ notifyLastKnownHomeNetworkChanged: function() {},
+
+ notifyNetworkSelectionModeChanged: function() {},
+
+ notifyDeviceIdentitiesChanged: function() {}
+};
+
+function DataCall(aClientId, aApnSetting, aDataCallHandler) {
+ this.clientId = aClientId;
+ this.dataCallHandler = aDataCallHandler;
+ this.apnProfile = {
+ apn: aApnSetting.apn,
+ user: aApnSetting.user,
+ password: aApnSetting.password,
+ authType: aApnSetting.authtype,
+ protocol: aApnSetting.protocol,
+ roaming_protocol: aApnSetting.roaming_protocol
+ };
+ this.linkInfo = {
+ cid: null,
+ ifname: null,
+ addresses: [],
+ dnses: [],
+ gateways: [],
+ pcscf: [],
+ mtu: null
+ };
+ this.state = NETWORK_STATE_UNKNOWN;
+ this.requestedNetworkIfaces = [];
+}
+DataCall.prototype = {
+ /**
+ * Standard values for the APN connection retry process
+ * Retry funcion: time(secs) = A * numer_of_retries^2 + B
+ */
+ NETWORK_APNRETRY_FACTOR: 8,
+ NETWORK_APNRETRY_ORIGIN: 3,
+ NETWORK_APNRETRY_MAXRETRIES: 10,
+
+ dataCallHandler: null,
+
+ // Event timer for connection retries
+ timer: null,
+
+ // APN failed connections. Retry counter
+ apnRetryCounter: 0,
+
+ // Array to hold RILNetworkInterfaces that requested this DataCall.
+ requestedNetworkIfaces: null,
+
+ /**
+ * @return "deactivate" if <ifname> changes or one of the aCurrentDataCall
+ * addresses is missing in updatedDataCall, or "identical" if no
+ * changes found, or "changed" otherwise.
+ */
+ _compareDataCallLink: function(aUpdatedDataCall, aCurrentDataCall) {
+ // If network interface is changed, report as "deactivate".
+ if (aUpdatedDataCall.ifname != aCurrentDataCall.ifname) {
+ return "deactivate";
+ }
+
+ // If any existing address is missing, report as "deactivate".
+ for (let i = 0; i < aCurrentDataCall.addresses.length; i++) {
+ let address = aCurrentDataCall.addresses[i];
+ if (aUpdatedDataCall.addresses.indexOf(address) < 0) {
+ return "deactivate";
+ }
+ }
+
+ if (aCurrentDataCall.addresses.length != aUpdatedDataCall.addresses.length) {
+ // Since now all |aCurrentDataCall.addresses| are found in
+ // |aUpdatedDataCall.addresses|, this means one or more new addresses are
+ // reported.
+ return "changed";
+ }
+
+ let fields = ["gateways", "dnses"];
+ for (let i = 0; i < fields.length; i++) {
+ // Compare <datacall>.<field>.
+ let field = fields[i];
+ let lhs = aUpdatedDataCall[field], rhs = aCurrentDataCall[field];
+ if (lhs.length != rhs.length) {
+ return "changed";
+ }
+ for (let i = 0; i < lhs.length; i++) {
+ if (lhs[i] != rhs[i]) {
+ return "changed";
+ }
+ }
+ }
+
+ if (aCurrentDataCall.mtu != aUpdatedDataCall.mtu) {
+ return "changed";
+ }
+
+ return "identical";
+ },
+
+ _getGeckoDataCallState:function (aDataCall) {
+ if (aDataCall.active == Ci.nsIDataCallInterface.DATACALL_STATE_ACTIVE_UP ||
+ aDataCall.active == Ci.nsIDataCallInterface.DATACALL_STATE_ACTIVE_DOWN) {
+ return NETWORK_STATE_CONNECTED;
+ }
+
+ return NETWORK_STATE_DISCONNECTED;
+ },
+
+ onSetupDataCallResult: function(aDataCall) {
+ this.debug("onSetupDataCallResult: " + JSON.stringify(aDataCall));
+ let errorMsg = aDataCall.errorMsg;
+ if (aDataCall.failCause &&
+ aDataCall.failCause != Ci.nsIDataCallInterface.DATACALL_FAIL_NONE) {
+ errorMsg =
+ RIL.RIL_DATACALL_FAILCAUSE_TO_GECKO_DATACALL_ERROR[aDataCall.failCause];
+ }
+
+ if (errorMsg) {
+ if (DEBUG) {
+ this.debug("SetupDataCall error for apn " + this.apnProfile.apn + ": " +
+ errorMsg + " (" + aDataCall.failCause + "), retry time: " +
+ aDataCall.suggestedRetryTime);
+ }
+
+ this.state = NETWORK_STATE_DISCONNECTED;
+
+ if (this.requestedNetworkIfaces.length === 0) {
+ if (DEBUG) this.debug("This DataCall is not requested anymore.");
+ return;
+ }
+
+ // Let DataCallHandler notify MobileConnectionService
+ this.dataCallHandler.notifyDataCallError(this, errorMsg);
+
+ // For suggestedRetryTime, the value of INT32_MAX(0x7fffffff) means no retry.
+ if (aDataCall.suggestedRetryTime === INT32_MAX ||
+ this.isPermanentFail(aDataCall.failCause, errorMsg)) {
+ if (DEBUG) this.debug("Data call error: no retry needed.");
+ return;
+ }
+
+ this.retry(aDataCall.suggestedRetryTime);
+ return;
+ }
+
+ this.apnRetryCounter = 0;
+ this.linkInfo.cid = aDataCall.cid;
+
+ if (this.requestedNetworkIfaces.length === 0) {
+ if (DEBUG) {
+ this.debug("State is connected, but no network interface requested" +
+ " this DataCall");
+ }
+ this.deactivate();
+ return;
+ }
+
+ this.linkInfo.ifname = aDataCall.ifname;
+ this.linkInfo.addresses = aDataCall.addresses ? aDataCall.addresses.split(" ") : [];
+ this.linkInfo.gateways = aDataCall.gateways ? aDataCall.gateways.split(" ") : [];
+ this.linkInfo.dnses = aDataCall.dnses ? aDataCall.dnses.split(" ") : [];
+ this.linkInfo.pcscf = aDataCall.pcscf ? aDataCall.pcscf.split(" ") : [];
+ this.linkInfo.mtu = aDataCall.mtu > 0 ? aDataCall.mtu : 0;
+ this.state = this._getGeckoDataCallState(aDataCall);
+
+ // Notify DataCallHandler about data call connected.
+ this.dataCallHandler.notifyDataCallChanged(this);
+
+ for (let i = 0; i < this.requestedNetworkIfaces.length; i++) {
+ this.requestedNetworkIfaces[i].notifyRILNetworkInterface();
+ }
+ },
+
+ onDeactivateDataCallResult: function() {
+ if (DEBUG) this.debug("onDeactivateDataCallResult");
+
+ this.reset();
+
+ if (this.requestedNetworkIfaces.length > 0) {
+ if (DEBUG) {
+ this.debug("State is disconnected/unknown, but this DataCall is" +
+ " requested.");
+ }
+ this.setup();
+ return;
+ }
+
+ // Notify DataCallHandler about data call disconnected.
+ this.dataCallHandler.notifyDataCallChanged(this);
+ },
+
+ onDataCallChanged: function(aUpdatedDataCall) {
+ if (DEBUG) {
+ this.debug("onDataCallChanged: " + JSON.stringify(aUpdatedDataCall));
+ }
+
+ if (this.state == NETWORK_STATE_CONNECTING ||
+ this.state == NETWORK_STATE_DISCONNECTING) {
+ if (DEBUG) {
+ this.debug("We are in connecting/disconnecting state, ignore any " +
+ "unsolicited event for now.");
+ }
+ return;
+ }
+
+ let dataCallState = this._getGeckoDataCallState(aUpdatedDataCall);
+ if (this.state == dataCallState &&
+ dataCallState != NETWORK_STATE_CONNECTED) {
+ return;
+ }
+
+ let newLinkInfo = {
+ ifname: aUpdatedDataCall.ifname,
+ addresses: aUpdatedDataCall.addresses ? aUpdatedDataCall.addresses.split(" ") : [],
+ dnses: aUpdatedDataCall.dnses ? aUpdatedDataCall.dnses.split(" ") : [],
+ gateways: aUpdatedDataCall.gateways ? aUpdatedDataCall.gateways.split(" ") : [],
+ pcscf: aUpdatedDataCall.pcscf ? aUpdatedDataCall.pcscf.split(" ") : [],
+ mtu: aUpdatedDataCall.mtu > 0 ? aUpdatedDataCall.mtu : 0
+ };
+
+ switch (dataCallState) {
+ case NETWORK_STATE_CONNECTED:
+ if (this.state == NETWORK_STATE_CONNECTED) {
+ let result =
+ this._compareDataCallLink(newLinkInfo, this.linkInfo);
+
+ if (result == "identical") {
+ if (DEBUG) this.debug("No changes in data call.");
+ return;
+ }
+ if (result == "deactivate") {
+ if (DEBUG) this.debug("Data link changed, cleanup.");
+ this.deactivate();
+ return;
+ }
+ // Minor change, just update and notify.
+ if (DEBUG) {
+ this.debug("Data link minor change, just update and notify.");
+ }
+
+ this.linkInfo.addresses = newLinkInfo.addresses.slice();
+ this.linkInfo.gateways = newLinkInfo.gateways.slice();
+ this.linkInfo.dnses = newLinkInfo.dnses.slice();
+ this.linkInfo.pcscf = newLinkInfo.pcscf.slice();
+ this.linkInfo.mtu = newLinkInfo.mtu;
+ }
+ break;
+ case NETWORK_STATE_DISCONNECTED:
+ case NETWORK_STATE_UNKNOWN:
+ if (this.state == NETWORK_STATE_CONNECTED) {
+ // Notify first on unexpected data call disconnection.
+ this.state = dataCallState;
+ for (let i = 0; i < this.requestedNetworkIfaces.length; i++) {
+ this.requestedNetworkIfaces[i].notifyRILNetworkInterface();
+ }
+ }
+ this.reset();
+
+ if (this.requestedNetworkIfaces.length > 0) {
+ if (DEBUG) {
+ this.debug("State is disconnected/unknown, but this DataCall is" +
+ " requested.");
+ }
+ this.setup();
+ return;
+ }
+ break;
+ }
+
+ this.state = dataCallState;
+
+ // Notify DataCallHandler about data call changed.
+ this.dataCallHandler.notifyDataCallChanged(this);
+
+ for (let i = 0; i < this.requestedNetworkIfaces.length; i++) {
+ this.requestedNetworkIfaces[i].notifyRILNetworkInterface();
+ }
+ },
+
+ // Helpers
+
+ debug: function(aMsg) {
+ dump("-*- DataCall[" + this.clientId + ":" + this.apnProfile.apn + "]: " +
+ aMsg + "\n");
+ },
+
+ get connected() {
+ return this.state == NETWORK_STATE_CONNECTED;
+ },
+
+ isPermanentFail: function(aDataFailCause, aErrorMsg) {
+ // Check ril.h for 'no retry' data call fail causes.
+ if (aErrorMsg === RIL.GECKO_ERROR_RADIO_NOT_AVAILABLE ||
+ aErrorMsg === RIL.GECKO_ERROR_INVALID_PARAMETER ||
+ aDataFailCause === Ci.nsIDataCallInterface.DATACALL_FAIL_OPERATOR_BARRED ||
+ aDataFailCause === Ci.nsIDataCallInterface.DATACALL_FAIL_MISSING_UKNOWN_APN ||
+ aDataFailCause === Ci.nsIDataCallInterface.DATACALL_FAIL_UNKNOWN_PDP_ADDRESS_TYPE ||
+ aDataFailCause === Ci.nsIDataCallInterface.DATACALL_FAIL_USER_AUTHENTICATION ||
+ aDataFailCause === Ci.nsIDataCallInterface.DATACALL_FAIL_ACTIVATION_REJECT_GGSN ||
+ aDataFailCause === Ci.nsIDataCallInterface.DATACALL_FAIL_SERVICE_OPTION_NOT_SUPPORTED ||
+ aDataFailCause === Ci.nsIDataCallInterface.DATACALL_FAIL_SERVICE_OPTION_NOT_SUBSCRIBED ||
+ aDataFailCause === Ci.nsIDataCallInterface.DATACALL_FAIL_NSAPI_IN_USE ||
+ aDataFailCause === Ci.nsIDataCallInterface.DATACALL_FAIL_ONLY_IPV4_ALLOWED ||
+ aDataFailCause === Ci.nsIDataCallInterface.DATACALL_FAIL_ONLY_IPV6_ALLOWED ||
+ aDataFailCause === Ci.nsIDataCallInterface.DATACALL_FAIL_PROTOCOL_ERRORS ||
+ aDataFailCause === Ci.nsIDataCallInterface.DATACALL_FAIL_RADIO_POWER_OFF ||
+ aDataFailCause === Ci.nsIDataCallInterface.DATACALL_FAIL_TETHERED_CALL_ACTIVE) {
+ return true;
+ }
+
+ return false;
+ },
+
+ inRequestedTypes: function(aType) {
+ for (let i = 0; i < this.requestedNetworkIfaces.length; i++) {
+ if (this.requestedNetworkIfaces[i].info.type == aType) {
+ return true;
+ }
+ }
+ return false;
+ },
+
+ canHandleApn: function(aApnSetting) {
+ let isIdentical = this.apnProfile.apn == aApnSetting.apn &&
+ (this.apnProfile.user || '') == (aApnSetting.user || '') &&
+ (this.apnProfile.password || '') == (aApnSetting.password || '') &&
+ (this.apnProfile.authType || '') == (aApnSetting.authtype || '');
+
+ if (RILQUIRKS_HAVE_IPV6) {
+ isIdentical = isIdentical &&
+ (this.apnProfile.protocol || '') == (aApnSetting.protocol || '') &&
+ (this.apnProfile.roaming_protocol || '') == (aApnSetting.roaming_protocol || '');
+ }
+
+ return isIdentical;
+ },
+
+ resetLinkInfo: function() {
+ this.linkInfo.cid = null;
+ this.linkInfo.ifname = null;
+ this.linkInfo.addresses = [];
+ this.linkInfo.dnses = [];
+ this.linkInfo.gateways = [];
+ this.linkInfo.pcscf = [];
+ this.linkInfo.mtu = null;
+ },
+
+ reset: function() {
+ this.resetLinkInfo();
+
+ this.state = NETWORK_STATE_UNKNOWN;
+ },
+
+ connect: function(aNetworkInterface) {
+ if (DEBUG) this.debug("connect: " + aNetworkInterface.info.type);
+
+ if (this.requestedNetworkIfaces.indexOf(aNetworkInterface) == -1) {
+ this.requestedNetworkIfaces.push(aNetworkInterface);
+ }
+
+ if (this.state == NETWORK_STATE_CONNECTING ||
+ this.state == NETWORK_STATE_DISCONNECTING) {
+ return;
+ }
+ if (this.state == NETWORK_STATE_CONNECTED) {
+ // This needs to run asynchronously, to behave the same way as the case of
+ // non-shared apn, see bug 1059110.
+ Services.tm.currentThread.dispatch(() => {
+ // Do not notify if state changed while this event was being dispatched,
+ // the state probably was notified already or need not to be notified.
+ if (aNetworkInterface.info.state == RIL.GECKO_NETWORK_STATE_CONNECTED) {
+ aNetworkInterface.notifyRILNetworkInterface();
+ }
+ }, Ci.nsIEventTarget.DISPATCH_NORMAL);
+ return;
+ }
+
+ // If retry mechanism is running on background, stop it since we are going
+ // to setup data call now.
+ if (this.timer) {
+ this.timer.cancel();
+ }
+
+ this.setup();
+ },
+
+ setup: function() {
+ if (DEBUG) {
+ this.debug("Going to set up data connection with APN " +
+ this.apnProfile.apn);
+ }
+
+ let connection =
+ gMobileConnectionService.getItemByServiceId(this.clientId);
+ let dataInfo = connection && connection.data;
+ if (dataInfo == null ||
+ dataInfo.state != RIL.GECKO_MOBILE_CONNECTION_STATE_REGISTERED ||
+ dataInfo.type == RIL.GECKO_MOBILE_CONNECTION_STATE_UNKNOWN) {
+ return;
+ }
+
+ let radioTechType = dataInfo.type;
+ let radioTechnology = RIL.GECKO_RADIO_TECH.indexOf(radioTechType);
+ let authType = RIL.RIL_DATACALL_AUTH_TO_GECKO.indexOf(this.apnProfile.authType);
+ // Use the default authType if the value in database is invalid.
+ // For the case that user might not select the authentication type.
+ if (authType == -1) {
+ if (DEBUG) {
+ this.debug("Invalid authType '" + this.apnProfile.authtype +
+ "', using '" + RIL.GECKO_DATACALL_AUTH_DEFAULT + "'");
+ }
+ authType = RIL.RIL_DATACALL_AUTH_TO_GECKO.indexOf(RIL.GECKO_DATACALL_AUTH_DEFAULT);
+ }
+
+ let pdpType = Ci.nsIDataCallInterface.DATACALL_PDP_TYPE_IPV4;
+ if (RILQUIRKS_HAVE_IPV6) {
+ pdpType = !dataInfo.roaming
+ ? RIL.RIL_DATACALL_PDP_TYPES.indexOf(this.apnProfile.protocol)
+ : RIL.RIL_DATACALL_PDP_TYPES.indexOf(this.apnProfile.roaming_protocol);
+ if (pdpType == -1) {
+ if (DEBUG) {
+ this.debug("Invalid pdpType '" + (!dataInfo.roaming
+ ? this.apnProfile.protocol
+ : this.apnProfile.roaming_protocol) +
+ "', using '" + RIL.GECKO_DATACALL_PDP_TYPE_DEFAULT + "'");
+ }
+ pdpType = RIL.RIL_DATACALL_PDP_TYPES.indexOf(RIL.GECKO_DATACALL_PDP_TYPE_DEFAULT);
+ }
+ }
+
+ let dcInterface = this.dataCallHandler.dataCallInterface;
+ dcInterface.setupDataCall(
+ this.apnProfile.apn, this.apnProfile.user, this.apnProfile.password,
+ authType, pdpType, {
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIDataCallCallback]),
+ notifySetupDataCallSuccess: (aDataCall) => {
+ this.onSetupDataCallResult(aDataCall);
+ },
+ notifyError: (aErrorMsg) => {
+ this.onSetupDataCallResult({errorMsg: aErrorMsg});
+ }
+ });
+ this.state = NETWORK_STATE_CONNECTING;
+ },
+
+ retry: function(aSuggestedRetryTime) {
+ let apnRetryTimer;
+
+ // We will retry the connection in increasing times
+ // based on the function: time = A * numer_of_retries^2 + B
+ if (this.apnRetryCounter >= this.NETWORK_APNRETRY_MAXRETRIES) {
+ this.apnRetryCounter = 0;
+ this.timer = null;
+ if (DEBUG) this.debug("Too many APN Connection retries - STOP retrying");
+ return;
+ }
+
+ // If there is a valid aSuggestedRetryTime, override the retry timer.
+ if (aSuggestedRetryTime !== undefined && aSuggestedRetryTime >= 0) {
+ apnRetryTimer = aSuggestedRetryTime / 1000;
+ } else {
+ apnRetryTimer = this.NETWORK_APNRETRY_FACTOR *
+ (this.apnRetryCounter * this.apnRetryCounter) +
+ this.NETWORK_APNRETRY_ORIGIN;
+ }
+ this.apnRetryCounter++;
+ if (DEBUG) {
+ this.debug("Data call - APN Connection Retry Timer (secs-counter): " +
+ apnRetryTimer + "-" + this.apnRetryCounter);
+ }
+
+ if (this.timer == null) {
+ // Event timer for connection retries
+ this.timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
+ }
+ this.timer.initWithCallback(this, apnRetryTimer * 1000,
+ Ci.nsITimer.TYPE_ONE_SHOT);
+ },
+
+ disconnect: function(aNetworkInterface) {
+ if (DEBUG) this.debug("disconnect: " + aNetworkInterface.info.type);
+
+ let index = this.requestedNetworkIfaces.indexOf(aNetworkInterface);
+ if (index != -1) {
+ this.requestedNetworkIfaces.splice(index, 1);
+
+ if (this.state == NETWORK_STATE_DISCONNECTED ||
+ this.state == NETWORK_STATE_UNKNOWN) {
+ if (this.timer) {
+ this.timer.cancel();
+ }
+ this.reset();
+ return;
+ }
+
+ // Notify the DISCONNECTED event immediately after network interface is
+ // removed from requestedNetworkIfaces, to make the DataCall, shared or
+ // not, to have the same behavior.
+ Services.tm.currentThread.dispatch(() => {
+ // Do not notify if state changed while this event was being dispatched,
+ // the state probably was notified already or need not to be notified.
+ if (aNetworkInterface.info.state == RIL.GECKO_NETWORK_STATE_DISCONNECTED) {
+ aNetworkInterface.notifyRILNetworkInterface();
+
+ // Clear link info after notifying NetworkManager.
+ if (this.requestedNetworkIfaces.length === 0) {
+ this.resetLinkInfo();
+ }
+ }
+ }, Ci.nsIEventTarget.DISPATCH_NORMAL);
+ }
+
+ // Only deactivate data call if no more network interface needs this
+ // DataCall and if state is CONNECTED, for other states, we simply remove
+ // the network interface from requestedNetworkIfaces.
+ if (this.requestedNetworkIfaces.length > 0 ||
+ this.state != NETWORK_STATE_CONNECTED) {
+ return;
+ }
+
+ this.deactivate();
+ },
+
+ deactivate: function() {
+ let reason = Ci.nsIDataCallInterface.DATACALL_DEACTIVATE_NO_REASON;
+ if (DEBUG) {
+ this.debug("Going to disconnect data connection cid " + this.linkInfo.cid);
+ }
+
+ let dcInterface = this.dataCallHandler.dataCallInterface;
+ dcInterface.deactivateDataCall(this.linkInfo.cid, reason, {
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIDataCallCallback]),
+ notifySuccess: () => {
+ this.onDeactivateDataCallResult();
+ },
+ notifyError: (aErrorMsg) => {
+ this.onDeactivateDataCallResult();
+ }
+ });
+
+ this.state = NETWORK_STATE_DISCONNECTING;
+ },
+
+ // Entry method for timer events. Used to reconnect to a failed APN
+ notify: function(aTimer) {
+ this.setup();
+ },
+
+ shutdown: function() {
+ if (this.timer) {
+ this.timer.cancel();
+ this.timer = null;
+ }
+ }
+};
+
+function RILNetworkInfo(aClientId, aType, aNetworkInterface)
+{
+ this.serviceId = aClientId;
+ this.type = aType;
+
+ this.networkInterface = aNetworkInterface;
+}
+RILNetworkInfo.prototype = {
+ classID: RILNETWORKINFO_CID,
+ classInfo: XPCOMUtils.generateCI({classID: RILNETWORKINFO_CID,
+ classDescription: "RILNetworkInfo",
+ interfaces: [Ci.nsINetworkInfo,
+ Ci.nsIRilNetworkInfo]}),
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsINetworkInfo,
+ Ci.nsIRilNetworkInfo]),
+
+ networkInterface: null,
+
+ getDataCall: function() {
+ return this.networkInterface.dataCall;
+ },
+
+ getApnSetting: function() {
+ return this.networkInterface.apnSetting;
+ },
+
+ debug: function(aMsg) {
+ dump("-*- RILNetworkInfo[" + this.serviceId + ":" + this.type + "]: " +
+ aMsg + "\n");
+ },
+
+ /**
+ * nsINetworkInfo Implementation
+ */
+ get state() {
+ let dataCall = this.getDataCall();
+ if (!dataCall.inRequestedTypes(this.type)) {
+ return NETWORK_STATE_DISCONNECTED;
+ }
+ return dataCall.state;
+ },
+
+ type: null,
+
+ get name() {
+ return this.getDataCall().linkInfo.ifname;
+ },
+
+ getAddresses: function(aIps, aPrefixLengths) {
+ let addresses = this.getDataCall().linkInfo.addresses;
+
+ let ips = [];
+ let prefixLengths = [];
+ for (let i = 0; i < addresses.length; i++) {
+ let [ip, prefixLength] = addresses[i].split("/");
+ ips.push(ip);
+ prefixLengths.push(prefixLength);
+ }
+
+ aIps.value = ips.slice();
+ aPrefixLengths.value = prefixLengths.slice();
+
+ return ips.length;
+ },
+
+ getGateways: function(aCount) {
+ let linkInfo = this.getDataCall().linkInfo;
+
+ if (aCount) {
+ aCount.value = linkInfo.gateways.length;
+ }
+
+ return linkInfo.gateways.slice();
+ },
+
+ getDnses: function(aCount) {
+ let linkInfo = this.getDataCall().linkInfo;
+
+ if (aCount) {
+ aCount.value = linkInfo.dnses.length;
+ }
+
+ return linkInfo.dnses.slice();
+ },
+
+ /**
+ * nsIRilNetworkInfo Implementation
+ */
+
+ serviceId: 0,
+
+ get iccId() {
+ let icc = gIccService.getIccByServiceId(this.serviceId);
+ let iccInfo = icc && icc.iccInfo;
+
+ return iccInfo && iccInfo.iccid;
+ },
+
+ get mmsc() {
+ if (this.type != NETWORK_TYPE_MOBILE_MMS) {
+ if (DEBUG) this.debug("Error! Only MMS network can get MMSC.");
+ throw Cr.NS_ERROR_UNEXPECTED;
+ }
+
+ return this.getApnSetting().mmsc || "";
+ },
+
+ get mmsProxy() {
+ if (this.type != NETWORK_TYPE_MOBILE_MMS) {
+ if (DEBUG) this.debug("Error! Only MMS network can get MMS proxy.");
+ throw Cr.NS_ERROR_UNEXPECTED;
+ }
+
+ return this.getApnSetting().mmsproxy || "";
+ },
+
+ get mmsPort() {
+ if (this.type != NETWORK_TYPE_MOBILE_MMS) {
+ if (DEBUG) this.debug("Error! Only MMS network can get MMS port.");
+ throw Cr.NS_ERROR_UNEXPECTED;
+ }
+
+ // Note: Port 0 is reserved, so we treat it as invalid as well.
+ // See http://www.iana.org/assignments/port-numbers
+ return this.getApnSetting().mmsport || -1;
+ },
+
+ getPcscf: function(aCount) {
+ if (this.type != NETWORK_TYPE_MOBILE_IMS) {
+ if (DEBUG) this.debug("Error! Only IMS network can get pcscf.");
+ throw Cr.NS_ERROR_UNEXPECTED;
+ }
+
+ let linkInfo = this.getDataCall().linkInfo;
+
+ if (aCount) {
+ aCount.value = linkInfo.pcscf.length;
+ }
+ return linkInfo.pcscf.slice();
+ },
+};
+
+function RILNetworkInterface(aDataCallHandler, aType, aApnSetting, aDataCall) {
+ if (!aDataCall) {
+ throw new Error("No dataCall for RILNetworkInterface: " + type);
+ }
+
+ this.dataCallHandler = aDataCallHandler;
+ this.enabled = false;
+ this.dataCall = aDataCall;
+ this.apnSetting = aApnSetting;
+
+ this.info = new RILNetworkInfo(aDataCallHandler.clientId, aType, this);
+}
+
+RILNetworkInterface.prototype = {
+ classID: RILNETWORKINTERFACE_CID,
+ classInfo: XPCOMUtils.generateCI({classID: RILNETWORKINTERFACE_CID,
+ classDescription: "RILNetworkInterface",
+ interfaces: [Ci.nsINetworkInterface]}),
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsINetworkInterface]),
+
+ // If this RILNetworkInterface type is enabled or not.
+ enabled: null,
+
+ apnSetting: null,
+
+ dataCall: null,
+
+ /**
+ * nsINetworkInterface Implementation
+ */
+
+ info: null,
+
+ get httpProxyHost() {
+ return this.apnSetting.proxy || "";
+ },
+
+ get httpProxyPort() {
+ return this.apnSetting.port || "";
+ },
+
+ get mtu() {
+ // Value provided by network has higher priority than apn settings.
+ return this.dataCall.linkInfo.mtu || this.apnSetting.mtu || -1;
+ },
+
+ // Helpers
+
+ debug: function(aMsg) {
+ dump("-*- RILNetworkInterface[" + this.dataCallHandler.clientId + ":" +
+ this.info.type + "]: " + aMsg + "\n");
+ },
+
+ get connected() {
+ return this.info.state == NETWORK_STATE_CONNECTED;
+ },
+
+ notifyRILNetworkInterface: function() {
+ if (DEBUG) {
+ this.debug("notifyRILNetworkInterface type: " + this.info.type +
+ ", state: " + this.info.state);
+ }
+
+ gNetworkManager.updateNetworkInterface(this);
+ },
+
+ connect: function() {
+ this.enabled = true;
+
+ this.dataCall.connect(this);
+ },
+
+ disconnect: function() {
+ if (!this.enabled) {
+ return;
+ }
+ this.enabled = false;
+
+ this.dataCall.disconnect(this);
+ },
+
+ shutdown: function() {
+ this.dataCall.shutdown();
+ this.dataCall = null;
+ }
+};
+
+this.NSGetFactory = XPCOMUtils.generateNSGetFactory([DataCallManager]); \ No newline at end of file
diff --git a/dom/system/gonk/DataCallManager.manifest b/dom/system/gonk/DataCallManager.manifest
new file mode 100644
index 000000000..2a982415e
--- /dev/null
+++ b/dom/system/gonk/DataCallManager.manifest
@@ -0,0 +1,4 @@
+# DataCallManager.js
+component {35b9efa2-e42c-45ce-8210-0a13e6f4aadc} DataCallManager.js
+contract @mozilla.org/datacall/manager;1 {35b9efa2-e42c-45ce-8210-0a13e6f4aadc}
+category profile-after-change DataCallManager @mozilla.org/datacall/manager;1 \ No newline at end of file
diff --git a/dom/system/gonk/GeolocationUtil.cpp b/dom/system/gonk/GeolocationUtil.cpp
new file mode 100644
index 000000000..99d484a19
--- /dev/null
+++ b/dom/system/gonk/GeolocationUtil.cpp
@@ -0,0 +1,28 @@
+/* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "GeolocationUtil.h"
+
+double CalculateDeltaInMeter(double aLat, double aLon, double aLastLat, double aLastLon)
+{
+ // Use spherical law of cosines to calculate difference
+ // Not quite as correct as the Haversine but simpler and cheaper
+ const double radsInDeg = M_PI / 180.0;
+ const double rNewLat = aLat * radsInDeg;
+ const double rNewLon = aLon * radsInDeg;
+ const double rOldLat = aLastLat * radsInDeg;
+ const double rOldLon = aLastLon * radsInDeg;
+ // WGS84 equatorial radius of earth = 6378137m
+ double cosDelta = (sin(rNewLat) * sin(rOldLat)) +
+ (cos(rNewLat) * cos(rOldLat) * cos(rOldLon - rNewLon));
+ if (cosDelta > 1.0) {
+ cosDelta = 1.0;
+ } else if (cosDelta < -1.0) {
+ cosDelta = -1.0;
+ }
+ return acos(cosDelta) * 6378137;
+}
+
diff --git a/dom/system/gonk/GeolocationUtil.h b/dom/system/gonk/GeolocationUtil.h
new file mode 100644
index 000000000..fde337fb8
--- /dev/null
+++ b/dom/system/gonk/GeolocationUtil.h
@@ -0,0 +1,13 @@
+/* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef GEOLOCATIONUTIL_H
+#define GEOLOCATIONUTIL_H
+
+double CalculateDeltaInMeter(double aLat, double aLon, double aLastLat, double aLastLon);
+
+#endif
+
diff --git a/dom/system/gonk/GonkGPSGeolocationProvider.cpp b/dom/system/gonk/GonkGPSGeolocationProvider.cpp
new file mode 100644
index 000000000..9ce6ce2e5
--- /dev/null
+++ b/dom/system/gonk/GonkGPSGeolocationProvider.cpp
@@ -0,0 +1,706 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* Copyright 2012 Mozilla Foundation and Mozilla contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "GonkGPSGeolocationProvider.h"
+
+#include <cmath>
+#include <pthread.h>
+#include <hardware/gps.h>
+
+#include "base/task.h"
+#include "GeolocationUtil.h"
+#include "mozstumbler/MozStumbler.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/Services.h"
+#include "nsContentUtils.h"
+#include "nsGeoPosition.h"
+#include "nsIInterfaceRequestorUtils.h"
+#include "nsINetworkInterface.h"
+#include "nsIObserverService.h"
+#include "nsJSUtils.h"
+#include "nsPrintfCString.h"
+#include "nsServiceManagerUtils.h"
+#include "nsThreadUtils.h"
+#include "prtime.h"
+#include "mozilla/dom/BindingUtils.h"
+#include "mozilla/dom/ScriptSettings.h"
+#include "mozilla/dom/SettingChangeNotificationBinding.h"
+
+#ifdef AGPS_TYPE_INVALID
+#define AGPS_HAVE_DUAL_APN
+#endif
+
+#define FLUSH_AIDE_DATA 0
+
+#undef LOG
+#undef ERR
+#undef DBG
+#define LOG(args...) __android_log_print(ANDROID_LOG_INFO, "GonkGPSGeolocationProvider", ## args)
+#define ERR(args...) __android_log_print(ANDROID_LOG_ERROR, "GonkGPSGeolocationProvider", ## args)
+#define DBG(args...) __android_log_print(ANDROID_LOG_DEBUG, "GonkGPSGeolocationProvider" , ## args)
+
+using namespace mozilla;
+using namespace mozilla::dom;
+
+static const int kDefaultPeriod = 1000; // ms
+static bool gDebug_isLoggingEnabled = false;
+static bool gDebug_isGPSLocationIgnored = false;
+static const char* kMozSettingsChangedTopic = "mozsettings-changed";
+// Both of these settings can be toggled in the Gaia Developer settings screen.
+static const char* kSettingDebugEnabled = "geolocation.debugging.enabled";
+static const char* kSettingDebugGpsIgnored = "geolocation.debugging.gps-locations-ignored";
+
+// While most methods of GonkGPSGeolocationProvider should only be
+// called from main thread, we deliberately put the Init and ShutdownGPS
+// methods off main thread to avoid blocking.
+NS_IMPL_ISUPPORTS(GonkGPSGeolocationProvider,
+ nsIGeolocationProvider,
+ nsIObserver,
+ nsISettingsServiceCallback)
+
+/* static */ GonkGPSGeolocationProvider* GonkGPSGeolocationProvider::sSingleton = nullptr;
+GpsCallbacks GonkGPSGeolocationProvider::mCallbacks;
+
+
+void
+GonkGPSGeolocationProvider::LocationCallback(GpsLocation* location)
+{
+ if (gDebug_isGPSLocationIgnored) {
+ return;
+ }
+
+ class UpdateLocationEvent : public Runnable {
+ public:
+ UpdateLocationEvent(nsGeoPosition* aPosition)
+ : mPosition(aPosition)
+ {}
+ NS_IMETHOD Run() override {
+ RefPtr<GonkGPSGeolocationProvider> provider =
+ GonkGPSGeolocationProvider::GetSingleton();
+ nsCOMPtr<nsIGeolocationUpdate> callback = provider->mLocationCallback;
+ provider->mLastGPSPosition = mPosition;
+ if (callback) {
+ callback->Update(mPosition);
+ }
+ return NS_OK;
+ }
+ private:
+ RefPtr<nsGeoPosition> mPosition;
+ };
+
+ MOZ_ASSERT(location);
+
+ const float kImpossibleAccuracy_m = 0.001;
+ if (location->accuracy < kImpossibleAccuracy_m) {
+ return;
+ }
+
+ RefPtr<nsGeoPosition> somewhere = new nsGeoPosition(location->latitude,
+ location->longitude,
+ location->altitude,
+ location->accuracy,
+ location->accuracy,
+ location->bearing,
+ location->speed,
+ PR_Now() / PR_USEC_PER_MSEC);
+ // Note above: Can't use location->timestamp as the time from the satellite is a
+ // minimum of 16 secs old (see http://leapsecond.com/java/gpsclock.htm).
+ // All code from this point on expects the gps location to be timestamped with the
+ // current time, most notably: the geolocation service which respects maximumAge
+ // set in the DOM JS.
+
+ if (gDebug_isLoggingEnabled) {
+ DBG("geo: GPS got a fix (%f, %f). accuracy: %f",
+ location->latitude,
+ location->longitude,
+ location->accuracy);
+ }
+
+ RefPtr<UpdateLocationEvent> event = new UpdateLocationEvent(somewhere);
+ NS_DispatchToMainThread(event);
+
+}
+
+class NotifyObserversGPSTask final : public Runnable
+{
+public:
+ explicit NotifyObserversGPSTask(const char16_t* aData)
+ : mData(aData)
+ {}
+ NS_IMETHOD Run() override {
+ RefPtr<nsIGeolocationProvider> provider =
+ GonkGPSGeolocationProvider::GetSingleton();
+ nsCOMPtr<nsIObserverService> obsService = services::GetObserverService();
+ obsService->NotifyObservers(provider, "geolocation-device-events", mData);
+ return NS_OK;
+ }
+private:
+ const char16_t* mData;
+};
+
+void
+GonkGPSGeolocationProvider::StatusCallback(GpsStatus* status)
+{
+ const char* msgStream=0;
+ switch (status->status) {
+ case GPS_STATUS_NONE:
+ msgStream = "geo: GPS_STATUS_NONE\n";
+ break;
+ case GPS_STATUS_SESSION_BEGIN:
+ msgStream = "geo: GPS_STATUS_SESSION_BEGIN\n";
+ break;
+ case GPS_STATUS_SESSION_END:
+ msgStream = "geo: GPS_STATUS_SESSION_END\n";
+ break;
+ case GPS_STATUS_ENGINE_ON:
+ msgStream = "geo: GPS_STATUS_ENGINE_ON\n";
+ NS_DispatchToMainThread(new NotifyObserversGPSTask(u"GPSStarting"));
+ break;
+ case GPS_STATUS_ENGINE_OFF:
+ msgStream = "geo: GPS_STATUS_ENGINE_OFF\n";
+ NS_DispatchToMainThread(new NotifyObserversGPSTask(u"GPSShutdown"));
+ break;
+ default:
+ msgStream = "geo: Unknown GPS status\n";
+ break;
+ }
+ if (gDebug_isLoggingEnabled){
+ DBG("%s", msgStream);
+ }
+}
+
+void
+GonkGPSGeolocationProvider::SvStatusCallback(GpsSvStatus* sv_info)
+{
+ if (gDebug_isLoggingEnabled) {
+ static int numSvs = 0;
+ static uint32_t numEphemeris = 0;
+ static uint32_t numAlmanac = 0;
+ static uint32_t numUsedInFix = 0;
+
+ unsigned int i = 1;
+ uint32_t svAlmanacCount = 0;
+ for (i = 1; i > 0; i <<= 1) {
+ if (i & sv_info->almanac_mask) {
+ svAlmanacCount++;
+ }
+ }
+
+ uint32_t svEphemerisCount = 0;
+ for (i = 1; i > 0; i <<= 1) {
+ if (i & sv_info->ephemeris_mask) {
+ svEphemerisCount++;
+ }
+ }
+
+ uint32_t svUsedCount = 0;
+ for (i = 1; i > 0; i <<= 1) {
+ if (i & sv_info->used_in_fix_mask) {
+ svUsedCount++;
+ }
+ }
+
+ // Log the message only if the the status changed.
+ if (sv_info->num_svs != numSvs ||
+ svAlmanacCount != numAlmanac ||
+ svEphemerisCount != numEphemeris ||
+ svUsedCount != numUsedInFix) {
+
+ LOG(
+ "geo: Number of SVs have (visibility, almanac, ephemeris): (%d, %d, %d)."
+ " %d of these SVs were used in fix.\n",
+ sv_info->num_svs, svAlmanacCount, svEphemerisCount, svUsedCount);
+
+ numSvs = sv_info->num_svs;
+ numAlmanac = svAlmanacCount;
+ numEphemeris = svEphemerisCount;
+ numUsedInFix = svUsedCount;
+ }
+ }
+}
+
+void
+GonkGPSGeolocationProvider::NmeaCallback(GpsUtcTime timestamp, const char* nmea, int length)
+{
+ if (gDebug_isLoggingEnabled) {
+ DBG("NMEA: timestamp:\t%lld, length: %d, %s", timestamp, length, nmea);
+ }
+}
+
+void
+GonkGPSGeolocationProvider::SetCapabilitiesCallback(uint32_t capabilities)
+{
+ class UpdateCapabilitiesEvent : public Runnable {
+ public:
+ UpdateCapabilitiesEvent(uint32_t aCapabilities)
+ : mCapabilities(aCapabilities)
+ {}
+ NS_IMETHOD Run() override {
+ RefPtr<GonkGPSGeolocationProvider> provider =
+ GonkGPSGeolocationProvider::GetSingleton();
+
+ provider->mSupportsScheduling = mCapabilities & GPS_CAPABILITY_SCHEDULING;
+ provider->mSupportsSingleShot = mCapabilities & GPS_CAPABILITY_SINGLE_SHOT;
+#ifdef GPS_CAPABILITY_ON_DEMAND_TIME
+ provider->mSupportsTimeInjection = mCapabilities & GPS_CAPABILITY_ON_DEMAND_TIME;
+#endif
+ return NS_OK;
+ }
+ private:
+ uint32_t mCapabilities;
+ };
+
+ NS_DispatchToMainThread(new UpdateCapabilitiesEvent(capabilities));
+}
+
+void
+GonkGPSGeolocationProvider::AcquireWakelockCallback()
+{
+}
+
+void
+GonkGPSGeolocationProvider::ReleaseWakelockCallback()
+{
+}
+
+typedef void *(*pthread_func)(void *);
+
+/** Callback for creating a thread that can call into the JS codes.
+ */
+pthread_t
+GonkGPSGeolocationProvider::CreateThreadCallback(const char* name, void (*start)(void *), void* arg)
+{
+ pthread_t thread;
+ pthread_attr_t attr;
+
+ pthread_attr_init(&attr);
+
+ /* Unfortunately pthread_create and the callback disagreed on what
+ * start function should return.
+ */
+ pthread_create(&thread, &attr, reinterpret_cast<pthread_func>(start), arg);
+
+ return thread;
+}
+
+void
+GonkGPSGeolocationProvider::RequestUtcTimeCallback()
+{
+}
+
+GonkGPSGeolocationProvider::GonkGPSGeolocationProvider()
+ : mStarted(false)
+ , mSupportsScheduling(false)
+ , mObservingSettingsChange(false)
+ , mSupportsSingleShot(false)
+ , mSupportsTimeInjection(false)
+ , mGpsInterface(nullptr)
+{
+}
+
+GonkGPSGeolocationProvider::~GonkGPSGeolocationProvider()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(!mStarted, "Must call Shutdown before destruction");
+
+ sSingleton = nullptr;
+}
+
+already_AddRefed<GonkGPSGeolocationProvider>
+GonkGPSGeolocationProvider::GetSingleton()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (!sSingleton)
+ sSingleton = new GonkGPSGeolocationProvider();
+
+ RefPtr<GonkGPSGeolocationProvider> provider = sSingleton;
+ return provider.forget();
+}
+
+const GpsInterface*
+GonkGPSGeolocationProvider::GetGPSInterface()
+{
+ hw_module_t* module;
+
+ if (hw_get_module(GPS_HARDWARE_MODULE_ID, (hw_module_t const**)&module))
+ return nullptr;
+
+ hw_device_t* device;
+ if (module->methods->open(module, GPS_HARDWARE_MODULE_ID, &device))
+ return nullptr;
+
+ gps_device_t* gps_device = (gps_device_t *)device;
+ const GpsInterface* result = gps_device->get_gps_interface(gps_device);
+
+ if (result->size != sizeof(GpsInterface)) {
+ return nullptr;
+ }
+ return result;
+}
+
+void
+GonkGPSGeolocationProvider::RequestSettingValue(const char* aKey)
+{
+ MOZ_ASSERT(aKey);
+ nsCOMPtr<nsISettingsService> ss = do_GetService("@mozilla.org/settingsService;1");
+ if (!ss) {
+ MOZ_ASSERT(ss);
+ return;
+ }
+
+ nsCOMPtr<nsISettingsServiceLock> lock;
+ nsresult rv = ss->CreateLock(nullptr, getter_AddRefs(lock));
+ if (NS_FAILED(rv)) {
+ ERR("error while createLock setting '%s': %d\n", aKey, uint32_t(rv));
+ return;
+ }
+
+ rv = lock->Get(aKey, this);
+ if (NS_FAILED(rv)) {
+ ERR("error while get setting '%s': %d\n", aKey, uint32_t(rv));
+ return;
+ }
+}
+
+void
+GonkGPSGeolocationProvider::InjectLocation(double latitude,
+ double longitude,
+ float accuracy)
+{
+ if (gDebug_isLoggingEnabled) {
+ DBG("injecting location (%f, %f) accuracy: %f", latitude, longitude, accuracy);
+ }
+
+ MOZ_ASSERT(NS_IsMainThread());
+ if (!mGpsInterface) {
+ return;
+ }
+
+ mGpsInterface->inject_location(latitude, longitude, accuracy);
+}
+
+void
+GonkGPSGeolocationProvider::Init()
+{
+ // Must not be main thread. Some GPS driver's first init takes very long.
+ MOZ_ASSERT(!NS_IsMainThread());
+
+ mGpsInterface = GetGPSInterface();
+ if (!mGpsInterface) {
+ return;
+ }
+
+ if (!mCallbacks.size) {
+ mCallbacks.size = sizeof(GpsCallbacks);
+ mCallbacks.location_cb = LocationCallback;
+ mCallbacks.status_cb = StatusCallback;
+ mCallbacks.sv_status_cb = SvStatusCallback;
+ mCallbacks.nmea_cb = NmeaCallback;
+ mCallbacks.set_capabilities_cb = SetCapabilitiesCallback;
+ mCallbacks.acquire_wakelock_cb = AcquireWakelockCallback;
+ mCallbacks.release_wakelock_cb = ReleaseWakelockCallback;
+ mCallbacks.create_thread_cb = CreateThreadCallback;
+
+#ifdef GPS_CAPABILITY_ON_DEMAND_TIME
+ mCallbacks.request_utc_time_cb = RequestUtcTimeCallback;
+#endif
+
+ }
+
+ if (mGpsInterface->init(&mCallbacks) != 0) {
+ return;
+ }
+
+ NS_DispatchToMainThread(NewRunnableMethod(this, &GonkGPSGeolocationProvider::StartGPS));
+}
+
+void
+GonkGPSGeolocationProvider::StartGPS()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(mGpsInterface);
+
+ int32_t update = Preferences::GetInt("geo.default.update", kDefaultPeriod);
+
+ int positionMode = GPS_POSITION_MODE_STANDALONE;
+
+ if (!mSupportsScheduling) {
+ update = kDefaultPeriod;
+ }
+
+ mGpsInterface->set_position_mode(positionMode,
+ GPS_POSITION_RECURRENCE_PERIODIC,
+ update, 0, 0);
+#if FLUSH_AIDE_DATA
+ // Delete cached data
+ mGpsInterface->delete_aiding_data(GPS_DELETE_ALL);
+#endif
+
+ mGpsInterface->start();
+}
+
+
+NS_IMPL_ISUPPORTS(GonkGPSGeolocationProvider::NetworkLocationUpdate,
+ nsIGeolocationUpdate)
+
+NS_IMETHODIMP
+GonkGPSGeolocationProvider::NetworkLocationUpdate::Update(nsIDOMGeoPosition *position)
+{
+ RefPtr<GonkGPSGeolocationProvider> provider =
+ GonkGPSGeolocationProvider::GetSingleton();
+
+ nsCOMPtr<nsIDOMGeoPositionCoords> coords;
+ position->GetCoords(getter_AddRefs(coords));
+ if (!coords) {
+ return NS_ERROR_FAILURE;
+ }
+
+ double lat, lon, acc;
+ coords->GetLatitude(&lat);
+ coords->GetLongitude(&lon);
+ coords->GetAccuracy(&acc);
+
+ double delta = -1.0;
+
+ static double sLastMLSPosLat = 0;
+ static double sLastMLSPosLon = 0;
+
+ if (0 != sLastMLSPosLon || 0 != sLastMLSPosLat) {
+ delta = CalculateDeltaInMeter(lat, lon, sLastMLSPosLat, sLastMLSPosLon);
+ }
+
+ sLastMLSPosLat = lat;
+ sLastMLSPosLon = lon;
+
+ // if the MLS coord change is smaller than this arbitrarily small value
+ // assume the MLS coord is unchanged, and stick with the GPS location
+ const double kMinMLSCoordChangeInMeters = 10;
+
+ DOMTimeStamp time_ms = 0;
+ if (provider->mLastGPSPosition) {
+ provider->mLastGPSPosition->GetTimestamp(&time_ms);
+ }
+ const int64_t diff_ms = (PR_Now() / PR_USEC_PER_MSEC) - time_ms;
+
+ // We want to distinguish between the GPS being inactive completely
+ // and temporarily inactive. In the former case, we would use a low
+ // accuracy network location; in the latter, we only want a network
+ // location that appears to updating with movement.
+
+ const bool isGPSFullyInactive = diff_ms > 1000 * 60 * 2; // two mins
+ const bool isGPSTempInactive = diff_ms > 1000 * 10; // 10 secs
+
+ if (provider->mLocationCallback) {
+ if (isGPSFullyInactive ||
+ (isGPSTempInactive && delta > kMinMLSCoordChangeInMeters))
+ {
+ if (gDebug_isLoggingEnabled) {
+ DBG("Using MLS, GPS age:%fs, MLS Delta:%fm\n", diff_ms / 1000.0, delta);
+ }
+ provider->mLocationCallback->Update(position);
+ } else if (provider->mLastGPSPosition) {
+ if (gDebug_isLoggingEnabled) {
+ DBG("Using old GPS age:%fs\n", diff_ms / 1000.0);
+ }
+
+ // This is a fallback case so that the GPS provider responds with its last
+ // location rather than waiting for a more recent GPS or network location.
+ // The service decides if the location is too old, not the provider.
+ provider->mLocationCallback->Update(provider->mLastGPSPosition);
+ }
+ }
+ provider->InjectLocation(lat, lon, acc);
+ return NS_OK;
+}
+NS_IMETHODIMP
+GonkGPSGeolocationProvider::NetworkLocationUpdate::NotifyError(uint16_t error)
+{
+ return NS_OK;
+}
+NS_IMETHODIMP
+GonkGPSGeolocationProvider::Startup()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (mStarted) {
+ return NS_OK;
+ }
+
+ RequestSettingValue(kSettingDebugEnabled);
+ RequestSettingValue(kSettingDebugGpsIgnored);
+
+ // Setup an observer to watch changes to the setting.
+ nsCOMPtr<nsIObserverService> observerService = services::GetObserverService();
+ if (observerService) {
+ MOZ_ASSERT(!mObservingSettingsChange);
+ nsresult rv = observerService->AddObserver(this, kMozSettingsChangedTopic, false);
+ if (NS_FAILED(rv)) {
+ NS_WARNING("geo: Gonk GPS AddObserver failed");
+ } else {
+ mObservingSettingsChange = true;
+ }
+ }
+
+ if (!mInitThread) {
+ nsresult rv = NS_NewThread(getter_AddRefs(mInitThread));
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ mInitThread->Dispatch(NewRunnableMethod(this, &GonkGPSGeolocationProvider::Init),
+ NS_DISPATCH_NORMAL);
+
+ mNetworkLocationProvider = do_CreateInstance("@mozilla.org/geolocation/mls-provider;1");
+ if (mNetworkLocationProvider) {
+ nsresult rv = mNetworkLocationProvider->Startup();
+ if (NS_SUCCEEDED(rv)) {
+ RefPtr<NetworkLocationUpdate> update = new NetworkLocationUpdate();
+ mNetworkLocationProvider->Watch(update);
+ }
+ }
+
+ mStarted = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+GonkGPSGeolocationProvider::Watch(nsIGeolocationUpdate* aCallback)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ mLocationCallback = aCallback;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+GonkGPSGeolocationProvider::Shutdown()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (!mStarted) {
+ return NS_OK;
+ }
+
+ mStarted = false;
+ if (mNetworkLocationProvider) {
+ mNetworkLocationProvider->Shutdown();
+ mNetworkLocationProvider = nullptr;
+ }
+
+ nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
+ if (obs) {
+ nsresult rv;
+ rv = obs->RemoveObserver(this, kMozSettingsChangedTopic);
+ if (NS_FAILED(rv)) {
+ NS_WARNING("geo: Gonk GPS mozsettings RemoveObserver failed");
+ } else {
+ mObservingSettingsChange = false;
+ }
+ }
+
+ mInitThread->Dispatch(NewRunnableMethod(this, &GonkGPSGeolocationProvider::ShutdownGPS),
+ NS_DISPATCH_NORMAL);
+
+ return NS_OK;
+}
+
+void
+GonkGPSGeolocationProvider::ShutdownGPS()
+{
+ MOZ_ASSERT(!mStarted, "Should only be called after Shutdown");
+
+ if (mGpsInterface) {
+ mGpsInterface->stop();
+ mGpsInterface->cleanup();
+ }
+}
+
+NS_IMETHODIMP
+GonkGPSGeolocationProvider::SetHighAccuracy(bool)
+{
+ return NS_OK;
+}
+
+namespace {
+int
+ConvertToGpsNetworkType(int aNetworkInterfaceType)
+{
+ switch (aNetworkInterfaceType) {
+ case nsINetworkInfo::NETWORK_TYPE_WIFI:
+ return AGPS_RIL_NETWORK_TYPE_WIFI;
+ case nsINetworkInfo::NETWORK_TYPE_MOBILE:
+ return AGPS_RIL_NETWORK_TYPE_MOBILE;
+ case nsINetworkInfo::NETWORK_TYPE_MOBILE_MMS:
+ return AGPS_RIL_NETWORK_TYPE_MOBILE_MMS;
+ case nsINetworkInfo::NETWORK_TYPE_MOBILE_SUPL:
+ return AGPS_RIL_NETWORK_TYPE_MOBILE_SUPL;
+ case nsINetworkInfo::NETWORK_TYPE_MOBILE_DUN:
+ return AGPS_RIL_NETWORK_TTYPE_MOBILE_DUN;
+ default:
+ NS_WARNING(nsPrintfCString("Unknown network type mapping %d",
+ aNetworkInterfaceType).get());
+ return -1;
+ }
+}
+} // namespace
+
+NS_IMETHODIMP
+GonkGPSGeolocationProvider::Observe(nsISupports* aSubject,
+ const char* aTopic,
+ const char16_t* aData)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (!strcmp(aTopic, kMozSettingsChangedTopic)) {
+ // Read changed setting value
+ RootedDictionary<SettingChangeNotification> setting(RootingCx());
+ if (!WrappedJSToDictionary(aSubject, setting)) {
+ return NS_OK;
+ }
+
+ if (setting.mKey.EqualsASCII(kSettingDebugGpsIgnored)) {
+ LOG("received mozsettings-changed: ignoring\n");
+ gDebug_isGPSLocationIgnored =
+ setting.mValue.isBoolean() ? setting.mValue.toBoolean() : false;
+ if (gDebug_isLoggingEnabled) {
+ DBG("GPS ignored %d\n", gDebug_isGPSLocationIgnored);
+ }
+ return NS_OK;
+ } else if (setting.mKey.EqualsASCII(kSettingDebugEnabled)) {
+ LOG("received mozsettings-changed: logging\n");
+ gDebug_isLoggingEnabled =
+ setting.mValue.isBoolean() ? setting.mValue.toBoolean() : false;
+ return NS_OK;
+ }
+ }
+
+ return NS_OK;
+}
+
+/** nsISettingsServiceCallback **/
+
+NS_IMETHODIMP
+GonkGPSGeolocationProvider::Handle(const nsAString& aName,
+ JS::Handle<JS::Value> aResult)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+GonkGPSGeolocationProvider::HandleError(const nsAString& aErrorMessage)
+{
+ return NS_OK;
+}
diff --git a/dom/system/gonk/GonkGPSGeolocationProvider.h b/dom/system/gonk/GonkGPSGeolocationProvider.h
new file mode 100644
index 000000000..514398edf
--- /dev/null
+++ b/dom/system/gonk/GonkGPSGeolocationProvider.h
@@ -0,0 +1,103 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* Copyright 2012 Mozilla Foundation and Mozilla contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef GonkGPSGeolocationProvider_h
+#define GonkGPSGeolocationProvider_h
+
+#include <hardware/gps.h> // for GpsInterface
+#include "nsCOMPtr.h"
+#include "nsIGeolocationProvider.h"
+#include "nsIObserver.h"
+#include "nsIDOMGeoPosition.h"
+#include "nsISettingsService.h"
+
+class nsIThread;
+
+#define GONK_GPS_GEOLOCATION_PROVIDER_CID \
+{ 0x48525ec5, 0x5a7f, 0x490a, { 0x92, 0x77, 0xba, 0x66, 0xe0, 0xd2, 0x2c, 0x8b } }
+
+#define GONK_GPS_GEOLOCATION_PROVIDER_CONTRACTID \
+"@mozilla.org/gonk-gps-geolocation-provider;1"
+
+class GonkGPSGeolocationProvider : public nsIGeolocationProvider
+ , public nsIObserver
+ , public nsISettingsServiceCallback
+{
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIGEOLOCATIONPROVIDER
+ NS_DECL_NSIOBSERVER
+ NS_DECL_NSISETTINGSSERVICECALLBACK
+
+ static already_AddRefed<GonkGPSGeolocationProvider> GetSingleton();
+
+private:
+
+ /* Client should use GetSingleton() to get the provider instance. */
+ GonkGPSGeolocationProvider();
+ GonkGPSGeolocationProvider(const GonkGPSGeolocationProvider &);
+ GonkGPSGeolocationProvider & operator = (const GonkGPSGeolocationProvider &);
+ virtual ~GonkGPSGeolocationProvider();
+
+ static void LocationCallback(GpsLocation* location);
+ static void StatusCallback(GpsStatus* status);
+ static void SvStatusCallback(GpsSvStatus* sv_info);
+ static void NmeaCallback(GpsUtcTime timestamp, const char* nmea, int length);
+ static void SetCapabilitiesCallback(uint32_t capabilities);
+ static void AcquireWakelockCallback();
+ static void ReleaseWakelockCallback();
+ static pthread_t CreateThreadCallback(const char* name, void (*start)(void*), void* arg);
+ static void RequestUtcTimeCallback();
+
+ static GpsCallbacks mCallbacks;
+
+ void Init();
+ void StartGPS();
+ void ShutdownGPS();
+ void InjectLocation(double latitude, double longitude, float accuracy);
+ void RequestSettingValue(const char* aKey);
+
+ const GpsInterface* GetGPSInterface();
+
+ static GonkGPSGeolocationProvider* sSingleton;
+
+ bool mStarted;
+
+ bool mSupportsScheduling;
+ bool mObservingSettingsChange;
+ bool mSupportsSingleShot;
+ bool mSupportsTimeInjection;
+
+ const GpsInterface* mGpsInterface;
+ nsCOMPtr<nsIGeolocationUpdate> mLocationCallback;
+ nsCOMPtr<nsIThread> mInitThread;
+ nsCOMPtr<nsIGeolocationProvider> mNetworkLocationProvider;
+ nsCOMPtr<nsIDOMGeoPosition> mLastGPSPosition;
+
+ class NetworkLocationUpdate : public nsIGeolocationUpdate
+ {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIGEOLOCATIONUPDATE
+
+ NetworkLocationUpdate() {}
+
+ private:
+ virtual ~NetworkLocationUpdate() {}
+ };
+};
+
+#endif /* GonkGPSGeolocationProvider_h */
diff --git a/dom/system/gonk/MozMtpCommon.h b/dom/system/gonk/MozMtpCommon.h
new file mode 100644
index 000000000..81c0a3a74
--- /dev/null
+++ b/dom/system/gonk/MozMtpCommon.h
@@ -0,0 +1,56 @@
+/* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_system_mozmtpcommon_h__
+#define mozilla_system_mozmtpcommon_h__
+
+#include "mozilla/Types.h"
+#include <android/log.h>
+
+#define USE_DEBUG 0
+
+#if USE_DEBUG
+#define MTP_DBG(msg, ...) \
+ __android_log_print(ANDROID_LOG_DEBUG, "MozMtp", \
+ "%s: " msg, __FUNCTION__, ##__VA_ARGS__)
+#else
+#define MTP_DBG(msg, ...)
+#endif
+
+#define MTP_LOG(msg, ...) \
+ __android_log_print(ANDROID_LOG_INFO, "MozMtp", \
+ "%s: " msg, __FUNCTION__, ##__VA_ARGS__)
+
+#define MTP_ERR(msg, ...) \
+ __android_log_print(ANDROID_LOG_ERROR, "MozMtp", \
+ "%s: " msg, __FUNCTION__, ##__VA_ARGS__)
+
+#define BEGIN_MTP_NAMESPACE \
+ namespace mozilla { namespace system { namespace mtp {
+#define END_MTP_NAMESPACE \
+ } /* namespace mtp */ } /* namespace system */ } /* namespace mozilla */
+#define USING_MTP_NAMESPACE \
+ using namespace mozilla::system::mtp;
+
+namespace android {
+ class MOZ_EXPORT MtpServer;
+ class MOZ_EXPORT MtpStorage;
+ class MOZ_EXPORT MtpStringBuffer;
+ class MOZ_EXPORT MtpDatabase;
+ class MOZ_EXPORT MtpDataPacket;
+ class MOZ_EXPORT MtpProperty;
+}
+
+#include <mtp.h>
+#include <MtpDatabase.h>
+#include <MtpObjectInfo.h>
+#include <MtpProperty.h>
+#include <MtpServer.h>
+#include <MtpStorage.h>
+#include <MtpStringBuffer.h>
+#include <MtpTypes.h>
+
+#endif // mozilla_system_mtpcommon_h__
diff --git a/dom/system/gonk/MozMtpDatabase.cpp b/dom/system/gonk/MozMtpDatabase.cpp
new file mode 100644
index 000000000..29fe23e8d
--- /dev/null
+++ b/dom/system/gonk/MozMtpDatabase.cpp
@@ -0,0 +1,1542 @@
+/* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "MozMtpDatabase.h"
+#include "MozMtpServer.h"
+
+#include "base/message_loop.h"
+#include "DeviceStorage.h"
+#include "mozilla/ArrayUtils.h"
+#include "mozilla/AutoRestore.h"
+#include "mozilla/Scoped.h"
+#include "mozilla/Services.h"
+#include "nsIFile.h"
+#include "nsIObserverService.h"
+#include "nsPrintfCString.h"
+#include "nsString.h"
+#include "prio.h"
+
+#include <dirent.h>
+#include <libgen.h>
+#include <utime.h>
+#include <sys/stat.h>
+
+using namespace android;
+using namespace mozilla;
+
+namespace mozilla {
+MOZ_TYPE_SPECIFIC_SCOPED_POINTER_TEMPLATE(ScopedCloseDir, PRDir, PR_CloseDir)
+}
+
+BEGIN_MTP_NAMESPACE
+
+static const char* kMtpWatcherNotify = "mtp-watcher-notify";
+
+#if 0
+// Some debug code for figuring out deadlocks, if you happen to run into
+// that scenario
+
+class DebugMutexAutoLock: public MutexAutoLock
+{
+public:
+ DebugMutexAutoLock(mozilla::Mutex& aMutex)
+ : MutexAutoLock(aMutex)
+ {
+ MTP_LOG("Mutex acquired");
+ }
+
+ ~DebugMutexAutoLock()
+ {
+ MTP_LOG("Releasing mutex");
+ }
+};
+#define MutexAutoLock MTP_LOG("About to enter mutex"); DebugMutexAutoLock
+
+#endif
+
+static const char *
+ObjectPropertyAsStr(MtpObjectProperty aProperty)
+{
+ switch (aProperty) {
+ case MTP_PROPERTY_STORAGE_ID: return "MTP_PROPERTY_STORAGE_ID";
+ case MTP_PROPERTY_OBJECT_FORMAT: return "MTP_PROPERTY_OBJECT_FORMAT";
+ case MTP_PROPERTY_PROTECTION_STATUS: return "MTP_PROPERTY_PROTECTION_STATUS";
+ case MTP_PROPERTY_OBJECT_SIZE: return "MTP_PROPERTY_OBJECT_SIZE";
+ case MTP_PROPERTY_OBJECT_FILE_NAME: return "MTP_PROPERTY_OBJECT_FILE_NAME";
+ case MTP_PROPERTY_DATE_CREATED: return "MTP_PROPERTY_DATE_CREATED";
+ case MTP_PROPERTY_DATE_MODIFIED: return "MTP_PROPERTY_DATE_MODIFIED";
+ case MTP_PROPERTY_PARENT_OBJECT: return "MTP_PROPERTY_PARENT_OBJECT";
+ case MTP_PROPERTY_PERSISTENT_UID: return "MTP_PROPERTY_PERSISTENT_UID";
+ case MTP_PROPERTY_NAME: return "MTP_PROPERTY_NAME";
+ case MTP_PROPERTY_DATE_ADDED: return "MTP_PROPERTY_DATE_ADDED";
+ case MTP_PROPERTY_WIDTH: return "MTP_PROPERTY_WIDTH";
+ case MTP_PROPERTY_HEIGHT: return "MTP_PROPERTY_HEIGHT";
+ case MTP_PROPERTY_IMAGE_BIT_DEPTH: return "MTP_PROPERTY_IMAGE_BIT_DEPTH";
+ case MTP_PROPERTY_DISPLAY_NAME: return "MTP_PROPERTY_DISPLAY_NAME";
+ }
+ return "MTP_PROPERTY_???";
+}
+
+static char*
+FormatDate(time_t aTime, char *aDateStr, size_t aDateStrSize)
+{
+ struct tm tm;
+ localtime_r(&aTime, &tm);
+ MTP_LOG("(%ld) tm_zone = %s off = %ld", aTime, tm.tm_zone, tm.tm_gmtoff);
+ strftime(aDateStr, aDateStrSize, "%Y%m%dT%H%M%S", &tm);
+ return aDateStr;
+}
+
+MozMtpDatabase::MozMtpDatabase()
+ : mMutex("MozMtpDatabase::mMutex"),
+ mDb(mMutex),
+ mStorage(mMutex),
+ mBeginSendObjectCalled(false)
+{
+ MOZ_ASSERT(MessageLoop::current() == XRE_GetIOMessageLoop());
+
+ // We use the index into the array as the handle. Since zero isn't a valid
+ // index, we stick a dummy entry there.
+
+ RefPtr<DbEntry> dummy;
+
+ MutexAutoLock lock(mMutex);
+ mDb.AppendElement(dummy);
+}
+
+//virtual
+MozMtpDatabase::~MozMtpDatabase()
+{
+ MOZ_ASSERT(MessageLoop::current() == XRE_GetIOMessageLoop());
+}
+
+void
+MozMtpDatabase::AddEntry(DbEntry *entry)
+{
+ MutexAutoLock lock(mMutex);
+
+ entry->mHandle = GetNextHandle();
+ MOZ_ASSERT(mDb.Length() == entry->mHandle);
+ mDb.AppendElement(entry);
+
+ MTP_DBG("Handle: 0x%08x Parent: 0x%08x Path:'%s'",
+ entry->mHandle, entry->mParent, entry->mPath.get());
+}
+
+void
+MozMtpDatabase::AddEntryAndNotify(DbEntry* entry, RefCountedMtpServer* aMtpServer)
+{
+ AddEntry(entry);
+ aMtpServer->sendObjectAdded(entry->mHandle);
+}
+
+void
+MozMtpDatabase::DumpEntries(const char* aLabel)
+{
+ MutexAutoLock lock(mMutex);
+
+ ProtectedDbArray::size_type numEntries = mDb.Length();
+ MTP_LOG("%s: numEntries = %d", aLabel, numEntries);
+ ProtectedDbArray::index_type entryIndex;
+ for (entryIndex = 1; entryIndex < numEntries; entryIndex++) {
+ RefPtr<DbEntry> entry = mDb[entryIndex];
+ if (entry) {
+ MTP_LOG("%s: mDb[%d]: mHandle: 0x%08x mParent: 0x%08x StorageID: 0x%08x path: '%s'",
+ aLabel, entryIndex, entry->mHandle, entry->mParent, entry->mStorageID, entry->mPath.get());
+ } else {
+ MTP_LOG("%s: mDb[%2d]: entry is NULL", aLabel, entryIndex);
+ }
+ }
+}
+
+MtpObjectHandle
+MozMtpDatabase::FindEntryByPath(const nsACString& aPath)
+{
+ MutexAutoLock lock(mMutex);
+
+ ProtectedDbArray::size_type numEntries = mDb.Length();
+ ProtectedDbArray::index_type entryIndex;
+ for (entryIndex = 1; entryIndex < numEntries; entryIndex++) {
+ RefPtr<DbEntry> entry = mDb[entryIndex];
+ if (entry && entry->mPath.Equals(aPath)) {
+ return entryIndex;
+ }
+ }
+ return 0;
+}
+
+already_AddRefed<MozMtpDatabase::DbEntry>
+MozMtpDatabase::GetEntry(MtpObjectHandle aHandle)
+{
+ MutexAutoLock lock(mMutex);
+
+ RefPtr<DbEntry> entry;
+
+ if (aHandle > 0 && aHandle < mDb.Length()) {
+ entry = mDb[aHandle];
+ }
+ return entry.forget();
+}
+
+void
+MozMtpDatabase::RemoveEntry(MtpObjectHandle aHandle)
+{
+ MutexAutoLock lock(mMutex);
+ if (!IsValidHandle(aHandle)) {
+ return;
+ }
+
+ RefPtr<DbEntry> removedEntry = mDb[aHandle];
+ mDb[aHandle] = nullptr;
+ MTP_DBG("0x%08x removed", aHandle);
+ // if the entry is not a folder, just return.
+ if (removedEntry->mObjectFormat != MTP_FORMAT_ASSOCIATION) {
+ return;
+ }
+
+ // Find out and remove the children of aHandle.
+ // Since the index for a directory will always be less than the index of any of its children,
+ // we can remove the entire subtree in one pass.
+ ProtectedDbArray::size_type numEntries = mDb.Length();
+ ProtectedDbArray::index_type entryIndex;
+ for (entryIndex = aHandle+1; entryIndex < numEntries; entryIndex++) {
+ RefPtr<DbEntry> entry = mDb[entryIndex];
+ if (entry && IsValidHandle(entry->mParent) && !mDb[entry->mParent]) {
+ mDb[entryIndex] = nullptr;
+ MTP_DBG("0x%08x removed", aHandle);
+ }
+ }
+}
+
+void
+MozMtpDatabase::RemoveEntryAndNotify(MtpObjectHandle aHandle, RefCountedMtpServer* aMtpServer)
+{
+ RemoveEntry(aHandle);
+ aMtpServer->sendObjectRemoved(aHandle);
+}
+
+void
+MozMtpDatabase::UpdateEntryAndNotify(MtpObjectHandle aHandle, DeviceStorageFile* aFile, RefCountedMtpServer* aMtpServer)
+{
+ UpdateEntry(aHandle, aFile);
+ aMtpServer->sendObjectAdded(aHandle);
+}
+
+
+void
+MozMtpDatabase::UpdateEntry(MtpObjectHandle aHandle, DeviceStorageFile* aFile)
+{
+ MutexAutoLock lock(mMutex);
+
+ RefPtr<DbEntry> entry = mDb[aHandle];
+
+ int64_t fileSize = 0;
+ aFile->mFile->GetFileSize(&fileSize);
+ entry->mObjectSize = fileSize;
+
+ PRTime dateModifiedMsecs;
+ // GetLastModifiedTime returns msecs
+ aFile->mFile->GetLastModifiedTime(&dateModifiedMsecs);
+ entry->mDateModified = dateModifiedMsecs / PR_MSEC_PER_SEC;
+ entry->mDateCreated = entry->mDateModified;
+ entry->mDateAdded = entry->mDateModified;
+
+ #if USE_DEBUG
+ char dateStr[20];
+ MTP_DBG("UpdateEntry (0x%08x file %s) modified (%ld) %s",
+ entry->mHandle, entry->mPath.get(),
+ entry->mDateModified,
+ FormatDate(entry->mDateModified, dateStr, sizeof(dateStr)));
+ #endif
+}
+
+
+class MtpWatcherNotifyRunnable final : public Runnable
+{
+public:
+ MtpWatcherNotifyRunnable(nsACString& aStorageName,
+ nsACString& aPath,
+ const char* aEventType)
+ : mStorageName(aStorageName),
+ mPath(aPath),
+ mEventType(aEventType)
+ {}
+
+ NS_IMETHOD Run() override
+ {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ NS_ConvertUTF8toUTF16 storageName(mStorageName);
+ NS_ConvertUTF8toUTF16 path(mPath);
+
+ RefPtr<DeviceStorageFile> dsf(
+ new DeviceStorageFile(NS_LITERAL_STRING(DEVICESTORAGE_SDCARD),
+ storageName, path));
+ NS_ConvertUTF8toUTF16 eventType(mEventType);
+ nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
+
+ MTP_DBG("Sending mtp-watcher-notify %s %s %s",
+ mEventType.get(), mStorageName.get(), mPath.get());
+
+ obs->NotifyObservers(dsf, kMtpWatcherNotify, eventType.get());
+ return NS_OK;
+ }
+
+private:
+ nsCString mStorageName;
+ nsCString mPath;
+ nsCString mEventType;
+};
+
+// MtpWatcherNotify is used to tell DeviceStorage when a file was changed
+// through the MTP server.
+void
+MozMtpDatabase::MtpWatcherNotify(DbEntry* aEntry, const char* aEventType)
+{
+ // This function gets called from the MozMtpServer::mServerThread
+ MOZ_ASSERT(!NS_IsMainThread());
+
+ MTP_DBG("file: %s %s", aEntry->mPath.get(), aEventType);
+
+ // Tell interested parties that a file was created, deleted, or modified.
+
+ RefPtr<StorageEntry> storageEntry;
+ {
+ MutexAutoLock lock(mMutex);
+
+ // FindStorage and the mStorage[] access both need to have the mutex held.
+ StorageArray::index_type storageIndex = FindStorage(aEntry->mStorageID);
+ if (storageIndex == StorageArray::NoIndex) {
+ return;
+ }
+ storageEntry = mStorage[storageIndex];
+ }
+
+ // DeviceStorage wants the storageName and the path relative to the root
+ // of the storage area, so we need to strip off the storagePath
+
+ nsAutoCString relPath(Substring(aEntry->mPath,
+ storageEntry->mStoragePath.Length() + 1));
+
+ RefPtr<MtpWatcherNotifyRunnable> r =
+ new MtpWatcherNotifyRunnable(storageEntry->mStorageName, relPath, aEventType);
+ DebugOnly<nsresult> rv = NS_DispatchToMainThread(r);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+}
+
+// Called to tell the MTP server about new or deleted files,
+void
+MozMtpDatabase::MtpWatcherUpdate(RefCountedMtpServer* aMtpServer,
+ DeviceStorageFile* aFile,
+ const nsACString& aEventType)
+{
+ // Runs on the MtpWatcherUpdate->mIOThread (see MozMtpServer.cpp)
+ MOZ_ASSERT(!NS_IsMainThread());
+
+ // Figure out which storage the belongs to (if any)
+
+ if (!aFile->mFile) {
+ // No path - don't bother looking.
+ return;
+ }
+ nsString wideFilePath;
+ aFile->mFile->GetPath(wideFilePath);
+ NS_ConvertUTF16toUTF8 filePath(wideFilePath);
+
+ nsCString evtType(aEventType);
+ MTP_LOG("file %s %s", filePath.get(), evtType.get());
+
+ MtpObjectHandle entryHandle = FindEntryByPath(filePath);
+
+ if (aEventType.EqualsLiteral("modified")) {
+ // To update the file information to the newest, we remove the entry for
+ // the existing file, then re-add the entry for the file.
+
+ if (entryHandle != 0) {
+ // Update entry for the file and tell MTP.
+ MTP_LOG("About to update handle 0x%08x file %s", entryHandle, filePath.get());
+ UpdateEntryAndNotify(entryHandle, aFile, aMtpServer);
+ }
+ else {
+ // Create entry for the file and tell MTP.
+ CreateEntryForFileAndNotify(filePath, aFile, aMtpServer);
+ }
+ return;
+ }
+
+ if (aEventType.EqualsLiteral("deleted")) {
+ if (entryHandle == 0) {
+ // The entry has already been removed. We can't tell MTP.
+ return;
+ }
+ MTP_LOG("About to call sendObjectRemoved Handle 0x%08x file %s", entryHandle, filePath.get());
+ RemoveEntryAndNotify(entryHandle, aMtpServer);
+ return;
+ }
+}
+
+nsCString
+MozMtpDatabase::BaseName(const nsCString& path)
+{
+ nsCOMPtr<nsIFile> file;
+ NS_NewNativeLocalFile(path, false, getter_AddRefs(file));
+ if (file) {
+ nsCString leafName;
+ file->GetNativeLeafName(leafName);
+ return leafName;
+ }
+ return path;
+}
+
+static nsCString
+GetPathWithoutFileName(const nsCString& aFullPath)
+{
+ nsCString path;
+
+ int32_t offset = aFullPath.RFindChar('/');
+ if (offset != kNotFound) {
+ // The trailing slash will be as part of 'path'
+ path = StringHead(aFullPath, offset + 1);
+ }
+
+ MTP_LOG("returning '%s'", path.get());
+
+ return path;
+}
+
+void
+MozMtpDatabase::CreateEntryForFileAndNotify(const nsACString& aPath,
+ DeviceStorageFile* aFile,
+ RefCountedMtpServer* aMtpServer)
+{
+ // Find the StorageID that this path corresponds to.
+
+ nsCString remainder;
+ MtpStorageID storageID = FindStorageIDFor(aPath, remainder);
+ if (storageID == 0) {
+ // The path in question isn't for a storage area we're monitoring.
+ nsCString path(aPath);
+ return;
+ }
+
+ bool exists = false;
+ aFile->mFile->Exists(&exists);
+ if (!exists) {
+ // File doesn't exist, no sense telling MTP about it.
+ // This could happen if Device Storage created and deleted a file right
+ // away. Since the notifications wind up being async, the file might
+ // not exist any more.
+ return;
+ }
+
+ // Now walk the remaining directories, finding or creating as required.
+
+ MtpObjectHandle parent = MTP_PARENT_ROOT;
+ bool doFind = true;
+ int32_t offset = aPath.Length() - remainder.Length();
+ int32_t slash;
+
+ do {
+ nsDependentCSubstring component;
+ slash = aPath.FindChar('/', offset);
+ if (slash == kNotFound) {
+ component.Rebind(aPath, 0, aPath.Length());
+ } else {
+ component.Rebind(aPath, 0 , slash);
+ }
+ if (doFind) {
+ MtpObjectHandle entryHandle = FindEntryByPath(component);
+ if (entryHandle != 0) {
+ // We found an entry.
+ parent = entryHandle;
+ offset = slash + 1 ;
+ continue;
+ }
+ }
+
+ // We've got a directory component that doesn't exist. This means that all
+ // further subdirectories won't exist either, so we can skip searching
+ // for them.
+ doFind = false;
+
+ // This directory and the file don't exist, create them
+
+ RefPtr<DbEntry> entry = new DbEntry;
+
+ entry->mStorageID = storageID;
+ entry->mObjectName = Substring(aPath, offset, slash - offset);
+ entry->mParent = parent;
+ entry->mDisplayName = entry->mObjectName;
+ entry->mPath = component;
+
+ if (slash == kNotFound) {
+ // No slash - this is the file component
+ entry->mObjectFormat = MTP_FORMAT_DEFINED;
+
+ int64_t fileSize = 0;
+ aFile->mFile->GetFileSize(&fileSize);
+ entry->mObjectSize = fileSize;
+
+ // Note: Even though PRTime records usec, GetLastModifiedTime returns
+ // msecs.
+ PRTime dateModifiedMsecs;
+ aFile->mFile->GetLastModifiedTime(&dateModifiedMsecs);
+ entry->mDateModified = dateModifiedMsecs / PR_MSEC_PER_SEC;
+ } else {
+ // Found a slash, this makes this a directory component
+ entry->mObjectFormat = MTP_FORMAT_ASSOCIATION;
+ entry->mObjectSize = 0;
+ time(&entry->mDateModified);
+ }
+ entry->mDateCreated = entry->mDateModified;
+ entry->mDateAdded = entry->mDateModified;
+
+ AddEntryAndNotify(entry, aMtpServer);
+ MTP_LOG("About to call sendObjectAdded Handle 0x%08x file %s", entry->mHandle, entry->mPath.get());
+
+ parent = entry->mHandle;
+ offset = slash + 1;
+ } while (slash != kNotFound);
+
+ return;
+}
+
+void
+MozMtpDatabase::AddDirectory(MtpStorageID aStorageID,
+ const char* aPath,
+ MtpObjectHandle aParent)
+{
+ MOZ_ASSERT(MessageLoop::current() == XRE_GetIOMessageLoop());
+
+ ScopedCloseDir dir;
+
+ if (!(dir = PR_OpenDir(aPath))) {
+ MTP_ERR("Unable to open directory '%s'", aPath);
+ return;
+ }
+
+ PRDirEntry* dirEntry;
+ while ((dirEntry = PR_ReadDir(dir, PR_SKIP_BOTH))) {
+ nsPrintfCString filename("%s/%s", aPath, dirEntry->name);
+ PRFileInfo64 fileInfo;
+ if (PR_GetFileInfo64(filename.get(), &fileInfo) != PR_SUCCESS) {
+ MTP_ERR("Unable to retrieve file information for '%s'", filename.get());
+ continue;
+ }
+
+ RefPtr<DbEntry> entry = new DbEntry;
+
+ entry->mStorageID = aStorageID;
+ entry->mParent = aParent;
+ entry->mObjectName = dirEntry->name;
+ entry->mDisplayName = dirEntry->name;
+ entry->mPath = filename;
+
+ // PR_GetFileInfo64 returns timestamps in usecs
+ entry->mDateModified = fileInfo.modifyTime / PR_USEC_PER_SEC;
+ entry->mDateCreated = fileInfo.creationTime / PR_USEC_PER_SEC;
+ time(&entry->mDateAdded);
+
+ if (fileInfo.type == PR_FILE_FILE) {
+ entry->mObjectFormat = MTP_FORMAT_DEFINED;
+ //TODO: Check how 64-bit filesize are dealt with
+ entry->mObjectSize = fileInfo.size;
+ AddEntry(entry);
+ } else if (fileInfo.type == PR_FILE_DIRECTORY) {
+ entry->mObjectFormat = MTP_FORMAT_ASSOCIATION;
+ entry->mObjectSize = 0;
+ AddEntry(entry);
+ AddDirectory(aStorageID, filename.get(), entry->mHandle);
+ }
+ }
+}
+
+MozMtpDatabase::StorageArray::index_type
+MozMtpDatabase::FindStorage(MtpStorageID aStorageID)
+{
+ // Currently, this routine is called from MozMtpDatabase::RemoveStorage
+ // and MozMtpDatabase::MtpWatcherNotify, which both hold mMutex.
+
+ StorageArray::size_type numStorages = mStorage.Length();
+ StorageArray::index_type storageIndex;
+
+ for (storageIndex = 0; storageIndex < numStorages; storageIndex++) {
+ RefPtr<StorageEntry> storage = mStorage[storageIndex];
+ if (storage->mStorageID == aStorageID) {
+ return storageIndex;
+ }
+ }
+ return StorageArray::NoIndex;
+}
+
+// Find the storage ID for the storage area that contains aPath.
+MtpStorageID
+MozMtpDatabase::FindStorageIDFor(const nsACString& aPath, nsCSubstring& aRemainder)
+{
+ MutexAutoLock lock(mMutex);
+
+ aRemainder.Truncate();
+
+ StorageArray::size_type numStorages = mStorage.Length();
+ StorageArray::index_type storageIndex;
+
+ for (storageIndex = 0; storageIndex < numStorages; storageIndex++) {
+ RefPtr<StorageEntry> storage = mStorage[storageIndex];
+ if (StringHead(aPath, storage->mStoragePath.Length()).Equals(storage->mStoragePath)) {
+ if (aPath.Length() == storage->mStoragePath.Length()) {
+ return storage->mStorageID;
+ }
+ if (aPath[storage->mStoragePath.Length()] == '/') {
+ aRemainder = Substring(aPath, storage->mStoragePath.Length() + 1);
+ return storage->mStorageID;
+ }
+ }
+ }
+ return 0;
+}
+
+void
+MozMtpDatabase::AddStorage(MtpStorageID aStorageID,
+ const char* aPath,
+ const char* aName)
+{
+ // This is called on the IOThread from MozMtpStorage::StorageAvailable
+ MOZ_ASSERT(MessageLoop::current() == XRE_GetIOMessageLoop());
+
+ MTP_DBG("StorageID: 0x%08x aPath: '%s' aName: '%s'",
+ aStorageID, aPath, aName);
+
+ PRFileInfo fileInfo;
+ if (PR_GetFileInfo(aPath, &fileInfo) != PR_SUCCESS) {
+ MTP_ERR("'%s' doesn't exist", aPath);
+ return;
+ }
+ if (fileInfo.type != PR_FILE_DIRECTORY) {
+ MTP_ERR("'%s' isn't a directory", aPath);
+ return;
+ }
+
+ RefPtr<StorageEntry> storageEntry = new StorageEntry;
+
+ storageEntry->mStorageID = aStorageID;
+ storageEntry->mStoragePath = aPath;
+ storageEntry->mStorageName = aName;
+ {
+ MutexAutoLock lock(mMutex);
+ mStorage.AppendElement(storageEntry);
+ }
+
+ AddDirectory(aStorageID, aPath, MTP_PARENT_ROOT);
+ {
+ MutexAutoLock lock(mMutex);
+ MTP_LOG("added %d items from tree '%s'", mDb.Length(), aPath);
+ }
+}
+
+void
+MozMtpDatabase::RemoveStorage(MtpStorageID aStorageID)
+{
+ MutexAutoLock lock(mMutex);
+
+ // This is called on the IOThread from MozMtpStorage::StorageAvailable
+ MOZ_ASSERT(MessageLoop::current() == XRE_GetIOMessageLoop());
+
+ ProtectedDbArray::size_type numEntries = mDb.Length();
+ ProtectedDbArray::index_type entryIndex;
+ for (entryIndex = 1; entryIndex < numEntries; entryIndex++) {
+ RefPtr<DbEntry> entry = mDb[entryIndex];
+ if (entry && entry->mStorageID == aStorageID) {
+ mDb[entryIndex] = nullptr;
+ }
+ }
+ StorageArray::index_type storageIndex = FindStorage(aStorageID);
+ if (storageIndex != StorageArray::NoIndex) {
+ mStorage.RemoveElementAt(storageIndex);
+ }
+}
+
+// called from SendObjectInfo to reserve a database entry for the incoming file
+//virtual
+MtpObjectHandle
+MozMtpDatabase::beginSendObject(const char* aPath,
+ MtpObjectFormat aFormat,
+ MtpObjectHandle aParent,
+ MtpStorageID aStorageID,
+ uint64_t aSize,
+ time_t aModified)
+{
+ // If MtpServer::doSendObjectInfo receives a request with a parent of
+ // MTP_PARENT_ROOT, then it fills in aPath with the fully qualified path
+ // and then passes in a parent of zero.
+
+ if (aParent == 0) {
+ // Undo what doSendObjectInfo did
+ aParent = MTP_PARENT_ROOT;
+ }
+
+ RefPtr<DbEntry> entry = new DbEntry;
+
+ entry->mStorageID = aStorageID;
+ entry->mParent = aParent;
+ entry->mPath = aPath;
+ entry->mObjectName = BaseName(entry->mPath);
+ entry->mDisplayName = entry->mObjectName;
+ entry->mObjectFormat = aFormat;
+ entry->mObjectSize = aSize;
+
+ if (aModified != 0) {
+ // Currently, due to the way that parseDateTime is coded in
+ // frameworks/av/media/mtp/MtpUtils.cpp, aModified winds up being the number
+ // of seconds from the epoch in local time, rather than UTC time. So we
+ // need to convert it back to being relative to UTC since that's what linux
+ // expects time_t to contain.
+ //
+ // In more concrete testable terms, if the host parses 2015-08-02 02:22:00
+ // as a local time in the Pacific timezone, aModified will come to us as
+ // 1438482120.
+ //
+ // What we want is what mktime would pass us with the same date. Using python
+ // (because its simple) with the current timezone set to be America/Vancouver:
+ //
+ // >>> import time
+ // >>> time.mktime((2015, 8, 2, 2, 22, 0, 0, 0, -1))
+ // 1438507320.0
+ // >>> time.localtime(1438507320)
+ // time.struct_time(tm_year=2015, tm_mon=8, tm_mday=2, tm_hour=2, tm_min=22, tm_sec=0, tm_wday=6, tm_yday=214, tm_isdst=1)
+ //
+ // Currently, when a file has a modification time of 2015-08-22 02:22:00 PDT
+ // then aModified will come in as 1438482120 which corresponds to
+ // 2015-08-22 02:22:00 UTC
+
+ struct tm tm;
+ if (gmtime_r(&aModified, &tm) != NULL) {
+ // GMT always comes back with tm_isdst = 0, so we set it to -1 in order
+ // to have mktime figure out dst based on the date.
+ tm.tm_isdst = -1;
+ aModified = mktime(&tm);
+ if (aModified == (time_t)-1) {
+ aModified = 0;
+ }
+ } else {
+ aModified = 0;
+ }
+ }
+ if (aModified == 0) {
+ // The ubuntu host doesn't pass in the modified/created times in the
+ // SENDOBJECT packet, so aModified winds up being zero. About the best
+ // we can do with that is to use the current time.
+ time(&aModified);
+ }
+
+ // And just an FYI for anybody else looking at timestamps. Under OSX you
+ // need to use the Android File Transfer program to copy files into the
+ // phone. That utility passes in both date modified and date created
+ // timestamps, but they're both equal to the time that the file was copied
+ // and not the times that are associated with the files.
+
+ // Now we have aModified in a traditional time_t format, which is the number
+ // of seconds from the UTC epoch.
+
+ entry->mDateModified = aModified;
+ entry->mDateCreated = entry->mDateModified;
+ entry->mDateAdded = entry->mDateModified;
+
+ AddEntry(entry);
+
+ #if USE_DEBUG
+ char dateStr[20];
+ MTP_LOG("Handle: 0x%08x Parent: 0x%08x Path: '%s' aModified %ld %s",
+ entry->mHandle, aParent, aPath, aModified,
+ FormatDate(entry->mDateModified, dateStr, sizeof(dateStr)));
+ #endif
+
+ mBeginSendObjectCalled = true;
+ return entry->mHandle;
+}
+
+// called to report success or failure of the SendObject file transfer
+// success should signal a notification of the new object's creation,
+// failure should remove the database entry created in beginSendObject
+
+//virtual
+void
+MozMtpDatabase::endSendObject(const char* aPath,
+ MtpObjectHandle aHandle,
+ MtpObjectFormat aFormat,
+ bool aSucceeded)
+{
+ MTP_LOG("Handle: 0x%08x Path: '%s'", aHandle, aPath);
+
+ if (aSucceeded) {
+ RefPtr<DbEntry> entry = GetEntry(aHandle);
+ if (entry) {
+ // The android MTP server only copies the data in, it doesn't set the
+ // modified timestamp, so we do that here.
+
+ struct utimbuf new_times;
+ struct stat sb;
+
+ char dateStr[20];
+ MTP_LOG("Path: '%s' setting modified time to (%ld) %s",
+ entry->mPath.get(), entry->mDateModified,
+ FormatDate(entry->mDateModified, dateStr, sizeof(dateStr)));
+
+ stat(entry->mPath.get(), &sb);
+ new_times.actime = sb.st_atime; // Preserve atime
+ new_times.modtime = entry->mDateModified;
+ utime(entry->mPath.get(), &new_times);
+
+ MtpWatcherNotify(entry, "modified");
+ }
+ } else {
+ RemoveEntry(aHandle);
+ }
+ mBeginSendObjectCalled = false;
+}
+
+//virtual
+MtpObjectHandleList*
+MozMtpDatabase::getObjectList(MtpStorageID aStorageID,
+ MtpObjectFormat aFormat,
+ MtpObjectHandle aParent)
+{
+ MTP_LOG("StorageID: 0x%08x Format: 0x%04x Parent: 0x%08x",
+ aStorageID, aFormat, aParent);
+
+ // aStorageID == 0xFFFFFFFF for all storage
+ // aFormat == 0 for all formats
+ // aParent == 0xFFFFFFFF for objects with no parents
+ // aParent == 0 for all objects
+
+ //TODO: Optimize
+
+ UniquePtr<MtpObjectHandleList> list(new MtpObjectHandleList());
+
+ MutexAutoLock lock(mMutex);
+
+ ProtectedDbArray::size_type numEntries = mDb.Length();
+ ProtectedDbArray::index_type entryIndex;
+ for (entryIndex = 1; entryIndex < numEntries; entryIndex++) {
+ RefPtr<DbEntry> entry = mDb[entryIndex];
+ if (entry &&
+ (aStorageID == 0xFFFFFFFF || entry->mStorageID == aStorageID) &&
+ (aFormat == 0 || entry->mObjectFormat == aFormat) &&
+ (aParent == 0 || entry->mParent == aParent)) {
+ list->push(entry->mHandle);
+ }
+ }
+ MTP_LOG(" returning %d items", list->size());
+ return list.release();
+}
+
+//virtual
+int
+MozMtpDatabase::getNumObjects(MtpStorageID aStorageID,
+ MtpObjectFormat aFormat,
+ MtpObjectHandle aParent)
+{
+ MTP_LOG("");
+
+ // aStorageID == 0xFFFFFFFF for all storage
+ // aFormat == 0 for all formats
+ // aParent == 0xFFFFFFFF for objects with no parents
+ // aParent == 0 for all objects
+
+ int count = 0;
+
+ MutexAutoLock lock(mMutex);
+
+ ProtectedDbArray::size_type numEntries = mDb.Length();
+ ProtectedDbArray::index_type entryIndex;
+ for (entryIndex = 1; entryIndex < numEntries; entryIndex++) {
+ RefPtr<DbEntry> entry = mDb[entryIndex];
+ if (entry &&
+ (aStorageID == 0xFFFFFFFF || entry->mStorageID == aStorageID) &&
+ (aFormat == 0 || entry->mObjectFormat == aFormat) &&
+ (aParent == 0 || entry->mParent == aParent)) {
+ count++;
+ }
+ }
+
+ MTP_LOG(" returning %d items", count);
+ return count;
+}
+
+//virtual
+MtpObjectFormatList*
+MozMtpDatabase::getSupportedPlaybackFormats()
+{
+ static const uint16_t init_data[] = {MTP_FORMAT_UNDEFINED, MTP_FORMAT_ASSOCIATION,
+ MTP_FORMAT_TEXT, MTP_FORMAT_HTML, MTP_FORMAT_WAV,
+ MTP_FORMAT_MP3, MTP_FORMAT_MPEG, MTP_FORMAT_EXIF_JPEG,
+ MTP_FORMAT_TIFF_EP, MTP_FORMAT_BMP, MTP_FORMAT_GIF,
+ MTP_FORMAT_PNG, MTP_FORMAT_TIFF, MTP_FORMAT_WMA,
+ MTP_FORMAT_OGG, MTP_FORMAT_AAC, MTP_FORMAT_MP4_CONTAINER,
+ MTP_FORMAT_MP2, MTP_FORMAT_3GP_CONTAINER, MTP_FORMAT_FLAC};
+
+ MtpObjectFormatList *list = new MtpObjectFormatList();
+ list->appendArray(init_data, MOZ_ARRAY_LENGTH(init_data));
+
+ MTP_LOG("returning Supported Playback Formats");
+ return list;
+}
+
+//virtual
+MtpObjectFormatList*
+MozMtpDatabase::getSupportedCaptureFormats()
+{
+ static const uint16_t init_data[] = {MTP_FORMAT_ASSOCIATION, MTP_FORMAT_PNG};
+
+ MtpObjectFormatList *list = new MtpObjectFormatList();
+ list->appendArray(init_data, MOZ_ARRAY_LENGTH(init_data));
+ MTP_LOG("returning MTP_FORMAT_ASSOCIATION, MTP_FORMAT_PNG");
+ return list;
+}
+
+static const MtpObjectProperty sSupportedObjectProperties[] =
+{
+ MTP_PROPERTY_STORAGE_ID,
+ MTP_PROPERTY_OBJECT_FORMAT,
+ MTP_PROPERTY_PROTECTION_STATUS, // UINT16 - always 0
+ MTP_PROPERTY_OBJECT_SIZE,
+ MTP_PROPERTY_OBJECT_FILE_NAME, // just the filename - no directory
+ MTP_PROPERTY_NAME,
+ MTP_PROPERTY_DATE_CREATED,
+ MTP_PROPERTY_DATE_MODIFIED,
+ MTP_PROPERTY_PARENT_OBJECT,
+ MTP_PROPERTY_PERSISTENT_UID,
+ MTP_PROPERTY_DATE_ADDED,
+};
+
+//virtual
+MtpObjectPropertyList*
+MozMtpDatabase::getSupportedObjectProperties(MtpObjectFormat aFormat)
+{
+ MTP_LOG("");
+ MtpObjectPropertyList *list = new MtpObjectPropertyList();
+ list->appendArray(sSupportedObjectProperties,
+ MOZ_ARRAY_LENGTH(sSupportedObjectProperties));
+ return list;
+}
+
+//virtual
+MtpDevicePropertyList*
+MozMtpDatabase::getSupportedDeviceProperties()
+{
+ MTP_LOG("");
+ static const uint16_t init_data[] = { MTP_DEVICE_PROPERTY_UNDEFINED };
+
+ MtpDevicePropertyList *list = new MtpDevicePropertyList();
+ list->appendArray(init_data, MOZ_ARRAY_LENGTH(init_data));
+ return list;
+}
+
+//virtual
+MtpResponseCode
+MozMtpDatabase::getObjectPropertyValue(MtpObjectHandle aHandle,
+ MtpObjectProperty aProperty,
+ MtpDataPacket& aPacket)
+{
+ RefPtr<DbEntry> entry = GetEntry(aHandle);
+ if (!entry) {
+ MTP_ERR("Invalid Handle: 0x%08x", aHandle);
+ return MTP_RESPONSE_INVALID_OBJECT_HANDLE;
+ }
+
+ MTP_LOG("Handle: 0x%08x '%s' Property: %s 0x%08x",
+ aHandle, entry->mDisplayName.get(), ObjectPropertyAsStr(aProperty), aProperty);
+
+ switch (aProperty)
+ {
+ case MTP_PROPERTY_STORAGE_ID: aPacket.putUInt32(entry->mStorageID); break;
+ case MTP_PROPERTY_PARENT_OBJECT: aPacket.putUInt32(entry->mParent); break;
+ case MTP_PROPERTY_OBJECT_FORMAT: aPacket.putUInt16(entry->mObjectFormat); break;
+ case MTP_PROPERTY_OBJECT_SIZE: aPacket.putUInt64(entry->mObjectSize); break;
+ case MTP_PROPERTY_DISPLAY_NAME: aPacket.putString(entry->mDisplayName.get()); break;
+ case MTP_PROPERTY_PERSISTENT_UID:
+ // the same as aPacket.putUInt128
+ aPacket.putUInt64(entry->mHandle);
+ aPacket.putUInt64(entry->mStorageID);
+ break;
+ case MTP_PROPERTY_NAME: aPacket.putString(entry->mDisplayName.get()); break;
+
+ default:
+ MTP_LOG("Invalid Property: 0x%08x", aProperty);
+ return MTP_RESPONSE_INVALID_OBJECT_PROP_CODE;
+ }
+
+ return MTP_RESPONSE_OK;
+}
+
+static int
+GetTypeOfObjectProp(MtpObjectProperty aProperty)
+{
+ struct PropertyTableEntry {
+ MtpObjectProperty property;
+ int type;
+ };
+
+ static const PropertyTableEntry kObjectPropertyTable[] = {
+ {MTP_PROPERTY_STORAGE_ID, MTP_TYPE_UINT32 },
+ {MTP_PROPERTY_OBJECT_FORMAT, MTP_TYPE_UINT16 },
+ {MTP_PROPERTY_PROTECTION_STATUS, MTP_TYPE_UINT16 },
+ {MTP_PROPERTY_OBJECT_SIZE, MTP_TYPE_UINT64 },
+ {MTP_PROPERTY_OBJECT_FILE_NAME, MTP_TYPE_STR },
+ {MTP_PROPERTY_DATE_CREATED, MTP_TYPE_STR },
+ {MTP_PROPERTY_DATE_MODIFIED, MTP_TYPE_STR },
+ {MTP_PROPERTY_PARENT_OBJECT, MTP_TYPE_UINT32 },
+ {MTP_PROPERTY_DISPLAY_NAME, MTP_TYPE_STR },
+ {MTP_PROPERTY_NAME, MTP_TYPE_STR },
+ {MTP_PROPERTY_PERSISTENT_UID, MTP_TYPE_UINT128 },
+ {MTP_PROPERTY_DATE_ADDED, MTP_TYPE_STR },
+ };
+
+ int count = sizeof(kObjectPropertyTable) / sizeof(kObjectPropertyTable[0]);
+ const PropertyTableEntry* entryProp = kObjectPropertyTable;
+ int type = 0;
+
+ for (int i = 0; i < count; ++i, ++entryProp) {
+ if (entryProp->property == aProperty) {
+ type = entryProp->type;
+ break;
+ }
+ }
+
+ return type;
+}
+
+//virtual
+MtpResponseCode
+MozMtpDatabase::setObjectPropertyValue(MtpObjectHandle aHandle,
+ MtpObjectProperty aProperty,
+ MtpDataPacket& aPacket)
+{
+ MTP_LOG("Handle: 0x%08x Property: 0x%08x", aHandle, aProperty);
+
+ // Only support file name change
+ if (aProperty != MTP_PROPERTY_OBJECT_FILE_NAME) {
+ MTP_ERR("property 0x%x not supported", aProperty);
+ return MTP_RESPONSE_OBJECT_PROP_NOT_SUPPORTED;
+ }
+
+ if (GetTypeOfObjectProp(aProperty) != MTP_TYPE_STR) {
+ MTP_ERR("property type 0x%x not supported", GetTypeOfObjectProp(aProperty));
+ return MTP_RESPONSE_GENERAL_ERROR;
+ }
+
+ RefPtr<DbEntry> entry = GetEntry(aHandle);
+ if (!entry) {
+ MTP_ERR("Invalid Handle: 0x%08x", aHandle);
+ return MTP_RESPONSE_INVALID_OBJECT_HANDLE;
+ }
+
+ MtpStringBuffer buf;
+ aPacket.getString(buf);
+
+ nsDependentCString newFileName(buf);
+ nsCString newFileFullPath(GetPathWithoutFileName(entry->mPath) + newFileName);
+
+ if (PR_Rename(entry->mPath.get(), newFileFullPath.get()) != PR_SUCCESS) {
+ MTP_ERR("Failed to rename '%s' to '%s'",
+ entry->mPath.get(), newFileFullPath.get());
+ return MTP_RESPONSE_GENERAL_ERROR;
+ }
+
+ MTP_LOG("renamed '%s' to '%s'", entry->mPath.get(), newFileFullPath.get());
+
+ entry->mPath = newFileFullPath;
+ entry->mObjectName = BaseName(entry->mPath);
+ entry->mDisplayName = entry->mObjectName;
+
+ return MTP_RESPONSE_OK;
+}
+
+//virtual
+MtpResponseCode
+MozMtpDatabase::getDevicePropertyValue(MtpDeviceProperty aProperty,
+ MtpDataPacket& aPacket)
+{
+ MTP_LOG("(GENERAL ERROR)");
+ return MTP_RESPONSE_GENERAL_ERROR;
+}
+
+//virtual
+MtpResponseCode
+MozMtpDatabase::setDevicePropertyValue(MtpDeviceProperty aProperty,
+ MtpDataPacket& aPacket)
+{
+ MTP_LOG("(NOT SUPPORTED)");
+ return MTP_RESPONSE_OPERATION_NOT_SUPPORTED;
+}
+
+//virtual
+MtpResponseCode
+MozMtpDatabase::resetDeviceProperty(MtpDeviceProperty aProperty)
+{
+ MTP_LOG("(NOT SUPPORTED)");
+ return MTP_RESPONSE_OPERATION_NOT_SUPPORTED;
+}
+
+void
+MozMtpDatabase::QueryEntries(MozMtpDatabase::MatchType aMatchType,
+ uint32_t aMatchField1,
+ uint32_t aMatchField2,
+ UnprotectedDbArray &result)
+{
+ MutexAutoLock lock(mMutex);
+
+ ProtectedDbArray::size_type numEntries = mDb.Length();
+ ProtectedDbArray::index_type entryIdx;
+ RefPtr<DbEntry> entry;
+
+ result.Clear();
+
+ switch (aMatchType) {
+
+ case MatchAll:
+ for (entryIdx = 0; entryIdx < numEntries; entryIdx++) {
+ if (mDb[entryIdx]) {
+ result.AppendElement(mDb[entryIdx]);
+ }
+ }
+ break;
+
+ case MatchHandle:
+ for (entryIdx = 0; entryIdx < numEntries; entryIdx++) {
+ entry = mDb[entryIdx];
+ if (entry && entry->mHandle == aMatchField1) {
+ result.AppendElement(entry);
+ // Handles are unique - return the one that we found.
+ return;
+ }
+ }
+ break;
+
+ case MatchParent:
+ for (entryIdx = 0; entryIdx < numEntries; entryIdx++) {
+ entry = mDb[entryIdx];
+ if (entry && entry->mParent == aMatchField1) {
+ result.AppendElement(entry);
+ }
+ }
+ break;
+
+ case MatchFormat:
+ for (entryIdx = 0; entryIdx < numEntries; entryIdx++) {
+ entry = mDb[entryIdx];
+ if (entry && entry->mObjectFormat == aMatchField1) {
+ result.AppendElement(entry);
+ }
+ }
+ break;
+
+ case MatchHandleFormat:
+ for (entryIdx = 0; entryIdx < numEntries; entryIdx++) {
+ entry = mDb[entryIdx];
+ if (entry && entry->mHandle == aMatchField1) {
+ if (entry->mObjectFormat == aMatchField2) {
+ result.AppendElement(entry);
+ }
+ // Only 1 entry can match my aHandle. So we can return early.
+ return;
+ }
+ }
+ break;
+
+ case MatchParentFormat:
+ for (entryIdx = 0; entryIdx < numEntries; entryIdx++) {
+ entry = mDb[entryIdx];
+ if (entry && entry->mParent == aMatchField1 && entry->mObjectFormat == aMatchField2) {
+ result.AppendElement(entry);
+ }
+ }
+ break;
+
+ default:
+ MOZ_ASSERT(!"Invalid MatchType");
+ }
+}
+
+//virtual
+MtpResponseCode
+MozMtpDatabase::getObjectPropertyList(MtpObjectHandle aHandle,
+ uint32_t aFormat,
+ uint32_t aProperty,
+ int aGroupCode,
+ int aDepth,
+ MtpDataPacket& aPacket)
+{
+ MTP_LOG("Handle: 0x%08x Format: 0x%08x aProperty: 0x%08x aGroupCode: %d aDepth %d",
+ aHandle, aFormat, aProperty, aGroupCode, aDepth);
+
+ if (aDepth > 1) {
+ return MTP_RESPONSE_SPECIFICATION_BY_DEPTH_UNSUPPORTED;
+ }
+ if (aGroupCode != 0) {
+ return MTP_RESPONSE_SPECIFICATION_BY_GROUP_UNSUPPORTED;
+ }
+
+ MatchType matchType = MatchAll;
+ uint32_t matchField1 = 0;
+ uint32_t matchField2 = 0;
+
+ // aHandle == 0 implies all objects at the root level
+ // further specificed by aFormat and/or aDepth
+
+ if (aFormat == 0) {
+ if (aHandle == 0xffffffff) {
+ // select all objects
+ matchType = MatchAll;
+ } else {
+ if (aDepth == 1) {
+ // select objects whose Parent matches aHandle
+ matchType = MatchParent;
+ matchField1 = aHandle;
+ } else {
+ // select object whose handle matches aHandle
+ matchType = MatchHandle;
+ matchField1 = aHandle;
+ }
+ }
+ } else {
+ if (aHandle == 0xffffffff) {
+ // select all objects whose format matches aFormat
+ matchType = MatchFormat;
+ matchField1 = aFormat;
+ } else {
+ if (aDepth == 1) {
+ // select objects whose Parent is aHandle and format matches aFormat
+ matchType = MatchParentFormat;
+ matchField1 = aHandle;
+ matchField2 = aFormat;
+ } else {
+ // select objects whose handle is aHandle and format matches aFormat
+ matchType = MatchHandleFormat;
+ matchField1 = aHandle;
+ matchField2 = aFormat;
+ }
+ }
+ }
+
+ UnprotectedDbArray result;
+ QueryEntries(matchType, matchField1, matchField2, result);
+
+ const MtpObjectProperty *objectPropertyList;
+ size_t numObjectProperties = 0;
+ MtpObjectProperty objectProperty;
+
+ if (aProperty == 0xffffffff) {
+ // return all supported properties
+ numObjectProperties = MOZ_ARRAY_LENGTH(sSupportedObjectProperties);
+ objectPropertyList = sSupportedObjectProperties;
+ } else {
+ // return property indicated by aProperty
+ numObjectProperties = 1;
+ objectProperty = aProperty;
+ objectPropertyList = &objectProperty;
+ }
+
+ UnprotectedDbArray::size_type numEntries = result.Length();
+ UnprotectedDbArray::index_type entryIdx;
+
+ char dateStr[20];
+
+ aPacket.putUInt32(numObjectProperties * numEntries);
+ for (entryIdx = 0; entryIdx < numEntries; entryIdx++) {
+ RefPtr<DbEntry> entry = result[entryIdx];
+
+ for (size_t propertyIdx = 0; propertyIdx < numObjectProperties; propertyIdx++) {
+ aPacket.putUInt32(entry->mHandle);
+ MtpObjectProperty prop = objectPropertyList[propertyIdx];
+ aPacket.putUInt16(prop);
+ switch (prop) {
+
+ case MTP_PROPERTY_STORAGE_ID:
+ aPacket.putUInt16(MTP_TYPE_UINT32);
+ aPacket.putUInt32(entry->mStorageID);
+ break;
+
+ case MTP_PROPERTY_PARENT_OBJECT:
+ aPacket.putUInt16(MTP_TYPE_UINT32);
+ aPacket.putUInt32(entry->mParent);
+ break;
+
+ case MTP_PROPERTY_PERSISTENT_UID:
+ aPacket.putUInt16(MTP_TYPE_UINT128);
+ // the same as aPacket.putUInt128
+ aPacket.putUInt64(entry->mHandle);
+ aPacket.putUInt64(entry->mStorageID);
+ break;
+
+ case MTP_PROPERTY_OBJECT_FORMAT:
+ aPacket.putUInt16(MTP_TYPE_UINT16);
+ aPacket.putUInt16(entry->mObjectFormat);
+ break;
+
+ case MTP_PROPERTY_OBJECT_SIZE:
+ aPacket.putUInt16(MTP_TYPE_UINT64);
+ aPacket.putUInt64(entry->mObjectSize);
+ break;
+
+ case MTP_PROPERTY_OBJECT_FILE_NAME:
+ case MTP_PROPERTY_NAME:
+ aPacket.putUInt16(MTP_TYPE_STR);
+ aPacket.putString(entry->mObjectName.get());
+ break;
+
+ case MTP_PROPERTY_PROTECTION_STATUS:
+ aPacket.putUInt16(MTP_TYPE_UINT16);
+ aPacket.putUInt16(0); // 0 = No Protection
+ break;
+
+ case MTP_PROPERTY_DATE_CREATED: {
+ aPacket.putUInt16(MTP_TYPE_STR);
+ aPacket.putString(FormatDate(entry->mDateCreated, dateStr, sizeof(dateStr)));
+ MTP_LOG("mDateCreated: (%ld) %s", entry->mDateCreated, dateStr);
+ break;
+ }
+
+ case MTP_PROPERTY_DATE_MODIFIED: {
+ aPacket.putUInt16(MTP_TYPE_STR);
+ aPacket.putString(FormatDate(entry->mDateModified, dateStr, sizeof(dateStr)));
+ MTP_LOG("mDateModified: (%ld) %s", entry->mDateModified, dateStr);
+ break;
+ }
+
+ case MTP_PROPERTY_DATE_ADDED: {
+ aPacket.putUInt16(MTP_TYPE_STR);
+ aPacket.putString(FormatDate(entry->mDateAdded, dateStr, sizeof(dateStr)));
+ MTP_LOG("mDateAdded: (%ld) %s", entry->mDateAdded, dateStr);
+ break;
+ }
+
+ default:
+ MTP_ERR("Unrecognized property code: %u", prop);
+ return MTP_RESPONSE_GENERAL_ERROR;
+ }
+ }
+ }
+ return MTP_RESPONSE_OK;
+}
+
+//virtual
+MtpResponseCode
+MozMtpDatabase::getObjectInfo(MtpObjectHandle aHandle,
+ MtpObjectInfo& aInfo)
+{
+ RefPtr<DbEntry> entry = GetEntry(aHandle);
+ if (!entry) {
+ MTP_ERR("Handle 0x%08x is invalid", aHandle);
+ return MTP_RESPONSE_INVALID_OBJECT_HANDLE;
+ }
+
+ MTP_LOG("Handle: 0x%08x Display:'%s' Object:'%s'", aHandle, entry->mDisplayName.get(), entry->mObjectName.get());
+
+ aInfo.mHandle = aHandle;
+ aInfo.mStorageID = entry->mStorageID;
+ aInfo.mFormat = entry->mObjectFormat;
+ aInfo.mProtectionStatus = 0x0;
+
+ if (entry->mObjectSize > 0xFFFFFFFFuLL) {
+ aInfo.mCompressedSize = 0xFFFFFFFFuLL;
+ } else {
+ aInfo.mCompressedSize = entry->mObjectSize;
+ }
+
+ aInfo.mThumbFormat = MTP_FORMAT_UNDEFINED;
+ aInfo.mThumbCompressedSize = 0;
+ aInfo.mThumbPixWidth = 0;
+ aInfo.mThumbPixHeight = 0;
+ aInfo.mImagePixWidth = 0;
+ aInfo.mImagePixHeight = 0;
+ aInfo.mImagePixDepth = 0;
+ aInfo.mParent = entry->mParent;
+ aInfo.mAssociationType = 0;
+ aInfo.mAssociationDesc = 0;
+ aInfo.mSequenceNumber = 0;
+ aInfo.mName = ::strdup(entry->mObjectName.get());
+ aInfo.mDateCreated = entry->mDateCreated;
+ aInfo.mDateModified = entry->mDateModified;
+
+ MTP_LOG("aInfo.mDateCreated = %ld entry->mDateCreated = %ld",
+ aInfo.mDateCreated, entry->mDateCreated);
+ MTP_LOG("aInfo.mDateModified = %ld entry->mDateModified = %ld",
+ aInfo.mDateModified, entry->mDateModified);
+
+ aInfo.mKeywords = ::strdup("fxos,touch");
+
+ return MTP_RESPONSE_OK;
+}
+
+//virtual
+void*
+MozMtpDatabase::getThumbnail(MtpObjectHandle aHandle, size_t& aOutThumbSize)
+{
+ MTP_LOG("Handle: 0x%08x (returning nullptr)", aHandle);
+
+ aOutThumbSize = 0;
+
+ return nullptr;
+}
+
+//virtual
+MtpResponseCode
+MozMtpDatabase::getObjectFilePath(MtpObjectHandle aHandle,
+ MtpString& aOutFilePath,
+ int64_t& aOutFileLength,
+ MtpObjectFormat& aOutFormat)
+{
+ RefPtr<DbEntry> entry = GetEntry(aHandle);
+ if (!entry) {
+ MTP_ERR("Handle 0x%08x is invalid", aHandle);
+ return MTP_RESPONSE_INVALID_OBJECT_HANDLE;
+ }
+
+ MTP_LOG("Handle: 0x%08x FilePath: '%s'", aHandle, entry->mPath.get());
+
+ aOutFilePath = entry->mPath.get();
+ aOutFileLength = entry->mObjectSize;
+ aOutFormat = entry->mObjectFormat;
+
+ return MTP_RESPONSE_OK;
+}
+
+//virtual
+MtpResponseCode
+MozMtpDatabase::deleteFile(MtpObjectHandle aHandle)
+{
+ RefPtr<DbEntry> entry = GetEntry(aHandle);
+ if (!entry) {
+ MTP_ERR("Invalid Handle: 0x%08x", aHandle);
+ return MTP_RESPONSE_INVALID_OBJECT_HANDLE;
+ }
+
+ MTP_LOG("Handle: 0x%08x '%s'", aHandle, entry->mPath.get());
+
+ // File deletion will happen in lower level implementation.
+ // The only thing we need to do is removing the entry from the db.
+ RemoveEntry(aHandle);
+
+ // Tell Device Storage that the file is gone.
+ MtpWatcherNotify(entry, "deleted");
+
+ return MTP_RESPONSE_OK;
+}
+
+#if 0
+//virtual
+MtpResponseCode
+MozMtpDatabase::moveFile(MtpObjectHandle aHandle, MtpObjectHandle aNewParent)
+{
+ MTP_LOG("Handle: 0x%08x NewParent: 0x%08x", aHandle, aNewParent);
+
+ // change parent
+
+ return MTP_RESPONSE_OK
+}
+
+//virtual
+MtpResponseCode
+MozMtpDatabase::copyFile(MtpObjectHandle aHandle, MtpObjectHandle aNewParent)
+{
+ MTP_LOG("Handle: 0x%08x NewParent: 0x%08x", aHandle, aNewParent);
+
+ // duplicate DbEntry
+ // change parent
+
+ return MTP_RESPONSE_OK
+}
+#endif
+
+//virtual
+MtpObjectHandleList*
+MozMtpDatabase::getObjectReferences(MtpObjectHandle aHandle)
+{
+ MTP_LOG("Handle: 0x%08x (returning nullptr)", aHandle);
+ return nullptr;
+}
+
+//virtual
+MtpResponseCode
+MozMtpDatabase::setObjectReferences(MtpObjectHandle aHandle,
+ MtpObjectHandleList* aReferences)
+{
+ MTP_LOG("Handle: 0x%08x (NOT SUPPORTED)", aHandle);
+ return MTP_RESPONSE_OPERATION_NOT_SUPPORTED;
+}
+
+//virtual
+MtpProperty*
+MozMtpDatabase::getObjectPropertyDesc(MtpObjectProperty aProperty,
+ MtpObjectFormat aFormat)
+{
+ MTP_LOG("Property: %s 0x%08x", ObjectPropertyAsStr(aProperty), aProperty);
+
+ MtpProperty* result = nullptr;
+ switch (aProperty)
+ {
+ case MTP_PROPERTY_PROTECTION_STATUS:
+ result = new MtpProperty(aProperty, MTP_TYPE_UINT16);
+ break;
+ case MTP_PROPERTY_OBJECT_FORMAT:
+ result = new MtpProperty(aProperty, MTP_TYPE_UINT16, false, aFormat);
+ break;
+ case MTP_PROPERTY_STORAGE_ID:
+ case MTP_PROPERTY_PARENT_OBJECT:
+ case MTP_PROPERTY_WIDTH:
+ case MTP_PROPERTY_HEIGHT:
+ case MTP_PROPERTY_IMAGE_BIT_DEPTH:
+ result = new MtpProperty(aProperty, MTP_TYPE_UINT32);
+ break;
+ case MTP_PROPERTY_OBJECT_SIZE:
+ result = new MtpProperty(aProperty, MTP_TYPE_UINT64);
+ break;
+ case MTP_PROPERTY_DISPLAY_NAME:
+ case MTP_PROPERTY_NAME:
+ result = new MtpProperty(aProperty, MTP_TYPE_STR);
+ break;
+ case MTP_PROPERTY_OBJECT_FILE_NAME:
+ result = new MtpProperty(aProperty, MTP_TYPE_STR, true);
+ break;
+ case MTP_PROPERTY_DATE_CREATED:
+ case MTP_PROPERTY_DATE_MODIFIED:
+ case MTP_PROPERTY_DATE_ADDED:
+ result = new MtpProperty(aProperty, MTP_TYPE_STR);
+ result->setFormDateTime();
+ break;
+ case MTP_PROPERTY_PERSISTENT_UID:
+ result = new MtpProperty(aProperty, MTP_TYPE_UINT128);
+ break;
+ default:
+ break;
+ }
+
+ return result;
+}
+
+//virtual
+MtpProperty*
+MozMtpDatabase::getDevicePropertyDesc(MtpDeviceProperty aProperty)
+{
+ MTP_LOG("(returning MTP_DEVICE_PROPERTY_UNDEFINED)");
+ return new MtpProperty(MTP_DEVICE_PROPERTY_UNDEFINED, MTP_TYPE_UNDEFINED);
+}
+
+//virtual
+void
+MozMtpDatabase::sessionStarted()
+{
+ MTP_LOG("");
+}
+
+//virtual
+void
+MozMtpDatabase::sessionEnded()
+{
+ MTP_LOG("");
+}
+
+END_MTP_NAMESPACE
diff --git a/dom/system/gonk/MozMtpDatabase.h b/dom/system/gonk/MozMtpDatabase.h
new file mode 100644
index 000000000..8b308762e
--- /dev/null
+++ b/dom/system/gonk/MozMtpDatabase.h
@@ -0,0 +1,288 @@
+/* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_system_mozmtpdatabase_h__
+#define mozilla_system_mozmtpdatabase_h__
+
+#include "MozMtpCommon.h"
+
+#include "mozilla/Mutex.h"
+#include "mozilla/RefPtr.h"
+#include "nsCOMPtr.h"
+#include "nsString.h"
+#include "nsIThread.h"
+#include "nsTArray.h"
+
+class DeviceStorageFile;
+
+BEGIN_MTP_NAMESPACE // mozilla::system::mtp
+
+class RefCountedMtpServer;
+
+using namespace android;
+
+class MozMtpDatabase final : public MtpDatabase
+{
+public:
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(MozMtpDatabase)
+
+ MozMtpDatabase();
+
+ // called from SendObjectInfo to reserve a database entry for the incoming file
+ virtual MtpObjectHandle beginSendObject(const char* aPath,
+ MtpObjectFormat aFormat,
+ MtpObjectHandle aParent,
+ MtpStorageID aStorageID,
+ uint64_t aSize,
+ time_t aModified);
+
+ // called to report success or failure of the SendObject file transfer
+ // success should signal a notification of the new object's creation,
+ // failure should remove the database entry created in beginSendObject
+ virtual void endSendObject(const char* aPath,
+ MtpObjectHandle aHandle,
+ MtpObjectFormat aFormat,
+ bool aSucceeded);
+
+ virtual MtpObjectHandleList* getObjectList(MtpStorageID aStorageID,
+ MtpObjectFormat aFormat,
+ MtpObjectHandle aParent);
+
+ virtual int getNumObjects(MtpStorageID aStorageID,
+ MtpObjectFormat aFormat,
+ MtpObjectHandle aParent);
+
+ virtual MtpObjectFormatList* getSupportedPlaybackFormats();
+
+ virtual MtpObjectFormatList* getSupportedCaptureFormats();
+
+ virtual MtpObjectPropertyList* getSupportedObjectProperties(MtpObjectFormat aFormat);
+
+ virtual MtpDevicePropertyList* getSupportedDeviceProperties();
+
+ virtual MtpResponseCode getObjectPropertyValue(MtpObjectHandle aHandle,
+ MtpObjectProperty aProperty,
+ MtpDataPacket& aPacket);
+
+ virtual MtpResponseCode setObjectPropertyValue(MtpObjectHandle aHandle,
+ MtpObjectProperty aProperty,
+ MtpDataPacket& aPacket);
+
+ virtual MtpResponseCode getDevicePropertyValue(MtpDeviceProperty aProperty,
+ MtpDataPacket& aPacket);
+
+ virtual MtpResponseCode setDevicePropertyValue(MtpDeviceProperty aProperty,
+ MtpDataPacket& aPacket);
+
+ virtual MtpResponseCode resetDeviceProperty(MtpDeviceProperty aProperty);
+
+ virtual MtpResponseCode getObjectPropertyList(MtpObjectHandle aHandle,
+ uint32_t aFormat,
+ uint32_t aProperty,
+ int aGroupCode,
+ int aDepth,
+ MtpDataPacket& aPacket);
+
+ virtual MtpResponseCode getObjectInfo(MtpObjectHandle aHandle,
+ MtpObjectInfo& aInfo);
+
+ virtual void* getThumbnail(MtpObjectHandle aHandle, size_t& aOutThumbSize);
+
+ virtual MtpResponseCode getObjectFilePath(MtpObjectHandle aHandle,
+ MtpString& aOutFilePath,
+ int64_t& aOutFileLength,
+ MtpObjectFormat& aOutFormat);
+
+ virtual MtpResponseCode deleteFile(MtpObjectHandle aHandle);
+
+ virtual MtpObjectHandleList* getObjectReferences(MtpObjectHandle aHandle);
+
+ virtual MtpResponseCode setObjectReferences(MtpObjectHandle aHandle,
+ MtpObjectHandleList* aReferences);
+
+ virtual MtpProperty* getObjectPropertyDesc(MtpObjectProperty aProperty,
+ MtpObjectFormat aFormat);
+
+ virtual MtpProperty* getDevicePropertyDesc(MtpDeviceProperty aProperty);
+
+ virtual void sessionStarted();
+
+ virtual void sessionEnded();
+
+ void AddStorage(MtpStorageID aStorageID, const char* aPath, const char *aName);
+ void RemoveStorage(MtpStorageID aStorageID);
+
+ void MtpWatcherUpdate(RefCountedMtpServer* aMtpServer,
+ DeviceStorageFile* aFile,
+ const nsACString& aEventType);
+
+protected:
+ virtual ~MozMtpDatabase();
+
+private:
+
+ struct DbEntry final
+ {
+ DbEntry()
+ : mHandle(0),
+ mStorageID(0),
+ mObjectFormat(MTP_FORMAT_DEFINED),
+ mParent(0),
+ mObjectSize(0),
+ mDateCreated(0),
+ mDateModified(0),
+ mDateAdded(0) {}
+
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(DbEntry)
+
+ MtpObjectHandle mHandle; // uint32_t
+ MtpStorageID mStorageID; // uint32_t
+ nsCString mObjectName;
+ MtpObjectFormat mObjectFormat; // uint16_t
+ MtpObjectHandle mParent; // uint32_t
+ uint64_t mObjectSize;
+ nsCString mDisplayName;
+ nsCString mPath;
+ time_t mDateCreated;
+ time_t mDateModified;
+ time_t mDateAdded;
+
+ protected:
+ ~DbEntry() {}
+ };
+
+ template<class T>
+ class ProtectedTArray : private nsTArray<T>
+ {
+ public:
+ typedef T elem_type;
+ typedef typename nsTArray<T>::size_type size_type;
+ typedef typename nsTArray<T>::index_type index_type;
+ typedef nsTArray<T> base_type;
+
+ static const index_type NoIndex = base_type::NoIndex;
+
+ ProtectedTArray(mozilla::Mutex& aMutex)
+ : mMutex(aMutex)
+ {}
+
+ size_type Length() const
+ {
+ // GRR - This assert prints to stderr and won't show up in logcat.
+ mMutex.AssertCurrentThreadOwns();
+ return base_type::Length();
+ }
+
+ template <class Item>
+ elem_type* AppendElement(const Item& aItem)
+ {
+ mMutex.AssertCurrentThreadOwns();
+ return base_type::AppendElement(aItem);
+ }
+
+ void Clear()
+ {
+ mMutex.AssertCurrentThreadOwns();
+ base_type::Clear();
+ }
+
+ void RemoveElementAt(index_type aIndex)
+ {
+ mMutex.AssertCurrentThreadOwns();
+ base_type::RemoveElementAt(aIndex);
+ }
+
+ elem_type& operator[](index_type aIndex)
+ {
+ mMutex.AssertCurrentThreadOwns();
+ return base_type::ElementAt(aIndex);
+ }
+
+ const elem_type& operator[](index_type aIndex) const
+ {
+ mMutex.AssertCurrentThreadOwns();
+ return base_type::ElementAt(aIndex);
+ }
+
+ private:
+ mozilla::Mutex& mMutex;
+ };
+ typedef nsTArray<RefPtr<DbEntry> > UnprotectedDbArray;
+ typedef ProtectedTArray<RefPtr<DbEntry> > ProtectedDbArray;
+
+ struct StorageEntry final
+ {
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(StorageEntry)
+
+ MtpStorageID mStorageID;
+ nsCString mStoragePath;
+ nsCString mStorageName;
+
+ protected:
+ ~StorageEntry() {}
+ };
+ typedef ProtectedTArray<RefPtr<StorageEntry> > StorageArray;
+
+ enum MatchType
+ {
+ MatchAll,
+ MatchHandle,
+ MatchParent,
+ MatchFormat,
+ MatchHandleFormat,
+ MatchParentFormat,
+ };
+
+ bool IsValidHandle(MtpObjectHandle aHandle)
+ {
+ return aHandle > 0 && aHandle < mDb.Length();
+ }
+
+ void AddEntry(DbEntry* aEntry);
+ void AddEntryAndNotify(DbEntry* aEntr, RefCountedMtpServer* aMtpServer);
+ void DumpEntries(const char* aLabel);
+ MtpObjectHandle FindEntryByPath(const nsACString& aPath);
+ already_AddRefed<DbEntry> GetEntry(MtpObjectHandle aHandle);
+ void RemoveEntry(MtpObjectHandle aHandle);
+ void RemoveEntryAndNotify(MtpObjectHandle aHandle, RefCountedMtpServer* aMtpServer);
+ void UpdateEntry(MtpObjectHandle aHandle, DeviceStorageFile* aFile);
+ void UpdateEntryAndNotify(MtpObjectHandle aHandle, DeviceStorageFile* aFile,
+ RefCountedMtpServer* aMtpServer);
+ void QueryEntries(MatchType aMatchType, uint32_t aMatchField1,
+ uint32_t aMatchField2, UnprotectedDbArray& aResult);
+
+ nsCString BaseName(const nsCString& aPath);
+
+
+ MtpObjectHandle GetNextHandle()
+ {
+ return mDb.Length();
+ }
+
+ void AddDirectory(MtpStorageID aStorageID, const char *aPath, MtpObjectHandle aParent);
+
+ void CreateEntryForFileAndNotify(const nsACString& aPath,
+ DeviceStorageFile* aFile,
+ RefCountedMtpServer* aMtpServer);
+
+ StorageArray::index_type FindStorage(MtpStorageID aStorageID);
+ MtpStorageID FindStorageIDFor(const nsACString& aPath, nsCSubstring& aRemainder);
+ void MtpWatcherNotify(DbEntry* aEntry, const char* aEventType);
+
+ // We need a mutex to protext mDb and mStorage. The MTP server runs on a
+ // dedicated thread, and it updates/accesses mDb. When files are updated
+ // through DeviceStorage, we need to update/access mDb and mStorage as well
+ // (from a non-MTP server thread).
+ mozilla::Mutex mMutex;
+ ProtectedDbArray mDb;
+ StorageArray mStorage;
+
+ bool mBeginSendObjectCalled;
+};
+
+END_MTP_NAMESPACE
+
+#endif // mozilla_system_mozmtpdatabase_h__
diff --git a/dom/system/gonk/MozMtpServer.cpp b/dom/system/gonk/MozMtpServer.cpp
new file mode 100644
index 000000000..c26b6368b
--- /dev/null
+++ b/dom/system/gonk/MozMtpServer.cpp
@@ -0,0 +1,263 @@
+/* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "MozMtpServer.h"
+#include "MozMtpDatabase.h"
+
+#include <fcntl.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include <errno.h>
+#include <stdio.h>
+#include <unistd.h>
+
+#include <cutils/properties.h>
+#include <private/android_filesystem_config.h>
+
+#include "base/message_loop.h"
+#include "DeviceStorage.h"
+#include "mozilla/LazyIdleThread.h"
+#include "mozilla/Scoped.h"
+#include "mozilla/Services.h"
+#include "mozilla/StaticPtr.h"
+#include "nsIObserver.h"
+#include "nsIObserverService.h"
+#include "nsISupportsImpl.h"
+#include "nsThreadUtils.h"
+#include "nsXULAppAPI.h"
+
+#include "Volume.h"
+
+#define DEFAULT_THREAD_TIMEOUT_MS 30000
+
+using namespace android;
+using namespace mozilla;
+BEGIN_MTP_NAMESPACE
+
+static const char* kMtpWatcherUpdate = "mtp-watcher-update";
+
+class MtpWatcherUpdateRunnable final : public Runnable
+{
+public:
+ MtpWatcherUpdateRunnable(MozMtpDatabase* aMozMtpDatabase,
+ RefCountedMtpServer* aMtpServer,
+ DeviceStorageFile* aFile,
+ const nsACString& aEventType)
+ : mMozMtpDatabase(aMozMtpDatabase),
+ mMtpServer(aMtpServer),
+ mFile(aFile),
+ mEventType(aEventType)
+ {}
+
+ NS_IMETHOD Run() override
+ {
+ // Runs on the MtpWatcherUpdate->mIOThread
+ MOZ_ASSERT(!NS_IsMainThread());
+
+ mMozMtpDatabase->MtpWatcherUpdate(mMtpServer, mFile, mEventType);
+ return NS_OK;
+ }
+
+private:
+ RefPtr<MozMtpDatabase> mMozMtpDatabase;
+ RefPtr<RefCountedMtpServer> mMtpServer;
+ RefPtr<DeviceStorageFile> mFile;
+ nsCString mEventType;
+};
+
+// The MtpWatcherUpdate class listens for mtp-watcher-update events
+// and tells the MtpServer about changes made in device storage.
+class MtpWatcherUpdate final : public nsIObserver
+{
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ MtpWatcherUpdate(MozMtpServer* aMozMtpServer)
+ : mMozMtpServer(aMozMtpServer)
+ {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ mIOThread = new LazyIdleThread(
+ DEFAULT_THREAD_TIMEOUT_MS,
+ NS_LITERAL_CSTRING("MtpWatcherUpdate"));
+
+ nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
+ obs->AddObserver(this, kMtpWatcherUpdate, false);
+ }
+
+ NS_IMETHOD
+ Observe(nsISupports* aSubject, const char* aTopic, const char16_t* aData)
+ {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (strcmp(aTopic, kMtpWatcherUpdate)) {
+ // We're only interested in mtp-watcher-update events
+ return NS_OK;
+ }
+
+ NS_ConvertUTF16toUTF8 eventType(aData);
+ if (!eventType.EqualsLiteral("modified") && !eventType.EqualsLiteral("deleted")) {
+ // Bug 1074604: Needn't handle "created" event, once file operation
+ // finished, it would trigger "modified" event.
+ return NS_OK;
+ }
+
+ DeviceStorageFile* file = static_cast<DeviceStorageFile*>(aSubject);
+ file->Dump(kMtpWatcherUpdate);
+ MTP_LOG("%s: file %s %s", kMtpWatcherUpdate,
+ NS_LossyConvertUTF16toASCII(file->mPath).get(),
+ eventType.get());
+
+ RefPtr<MozMtpDatabase> mozMtpDatabase = mMozMtpServer->GetMozMtpDatabase();
+ RefPtr<RefCountedMtpServer> mtpServer = mMozMtpServer->GetMtpServer();
+
+ // We're not supposed to perform I/O on the main thread, so punt the
+ // notification (which will write to /dev/mtp_usb) to an I/O Thread.
+
+ RefPtr<MtpWatcherUpdateRunnable> r =
+ new MtpWatcherUpdateRunnable(mozMtpDatabase, mtpServer, file, eventType);
+ mIOThread->Dispatch(r, NS_DISPATCH_NORMAL);
+
+ return NS_OK;
+ }
+
+protected:
+ ~MtpWatcherUpdate()
+ {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
+ obs->RemoveObserver(this, kMtpWatcherUpdate);
+ }
+
+private:
+ RefPtr<MozMtpServer> mMozMtpServer;
+ nsCOMPtr<nsIThread> mIOThread;
+};
+NS_IMPL_ISUPPORTS(MtpWatcherUpdate, nsIObserver)
+static StaticRefPtr<MtpWatcherUpdate> sMtpWatcherUpdate;
+
+class AllocMtpWatcherUpdateRunnable final : public Runnable
+{
+public:
+ AllocMtpWatcherUpdateRunnable(MozMtpServer* aMozMtpServer)
+ : mMozMtpServer(aMozMtpServer)
+ {}
+
+ NS_IMETHOD Run() override
+ {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ sMtpWatcherUpdate = new MtpWatcherUpdate(mMozMtpServer);
+ return NS_OK;
+ }
+private:
+ RefPtr<MozMtpServer> mMozMtpServer;
+};
+
+class FreeMtpWatcherUpdateRunnable final : public Runnable
+{
+public:
+ FreeMtpWatcherUpdateRunnable(MozMtpServer* aMozMtpServer)
+ : mMozMtpServer(aMozMtpServer)
+ {}
+
+ NS_IMETHOD Run() override
+ {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ sMtpWatcherUpdate = nullptr;
+ return NS_OK;
+ }
+private:
+ RefPtr<MozMtpServer> mMozMtpServer;
+};
+
+class MtpServerRunnable : public Runnable
+{
+public:
+ MtpServerRunnable(int aMtpUsbFd, MozMtpServer* aMozMtpServer)
+ : mMozMtpServer(aMozMtpServer),
+ mMtpUsbFd(aMtpUsbFd)
+ {
+ }
+
+ nsresult Run()
+ {
+ RefPtr<RefCountedMtpServer> server = mMozMtpServer->GetMtpServer();
+
+ DebugOnly<nsresult> rv =
+ NS_DispatchToMainThread(new AllocMtpWatcherUpdateRunnable(mMozMtpServer));
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+
+ MTP_LOG("MozMtpServer started");
+ server->run();
+ MTP_LOG("MozMtpServer finished");
+
+ // server->run will have closed the file descriptor.
+ mMtpUsbFd.forget();
+
+ rv = NS_DispatchToMainThread(new FreeMtpWatcherUpdateRunnable(mMozMtpServer));
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+
+ return NS_OK;
+ }
+
+private:
+ RefPtr<MozMtpServer> mMozMtpServer;
+ ScopedClose mMtpUsbFd; // We want to hold this open while the server runs
+};
+
+already_AddRefed<RefCountedMtpServer>
+MozMtpServer::GetMtpServer()
+{
+ RefPtr<RefCountedMtpServer> server = mMtpServer;
+ return server.forget();
+}
+
+already_AddRefed<MozMtpDatabase>
+MozMtpServer::GetMozMtpDatabase()
+{
+ RefPtr<MozMtpDatabase> db = mMozMtpDatabase;
+ return db.forget();
+}
+
+bool
+MozMtpServer::Init()
+{
+ MOZ_ASSERT(MessageLoop::current() == XRE_GetIOMessageLoop());
+
+ const char *mtpUsbFilename = "/dev/mtp_usb";
+ mMtpUsbFd = open(mtpUsbFilename, O_RDWR);
+ if (mMtpUsbFd.get() < 0) {
+ MTP_ERR("open of '%s' failed((%s))", mtpUsbFilename, strerror(errno));
+ return false;
+ }
+ MTP_LOG("Opened '%s' fd %d", mtpUsbFilename, mMtpUsbFd.get());
+
+ mMozMtpDatabase = new MozMtpDatabase();
+ mMtpServer = new RefCountedMtpServer(mMtpUsbFd.get(), // fd
+ mMozMtpDatabase.get(), // MtpDatabase
+ false, // ptp?
+ AID_MEDIA_RW, // file group
+ 0664, // file permissions
+ 0775); // dir permissions
+ return true;
+}
+
+void
+MozMtpServer::Run()
+{
+ nsresult rv = NS_NewNamedThread("MtpServer", getter_AddRefs(mServerThread));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return;
+ }
+ MOZ_ASSERT(mServerThread);
+ mServerThread->Dispatch(new MtpServerRunnable(mMtpUsbFd.forget(), this), NS_DISPATCH_NORMAL);
+}
+
+END_MTP_NAMESPACE
diff --git a/dom/system/gonk/MozMtpServer.h b/dom/system/gonk/MozMtpServer.h
new file mode 100644
index 000000000..4989c25ef
--- /dev/null
+++ b/dom/system/gonk/MozMtpServer.h
@@ -0,0 +1,61 @@
+/* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_system_mozmtpserver_h__
+#define mozilla_system_mozmtpserver_h__
+
+#include "MozMtpCommon.h"
+#include "MozMtpDatabase.h"
+
+#include "mozilla/FileUtils.h"
+
+#include "nsCOMPtr.h"
+#include "nsIThread.h"
+
+BEGIN_MTP_NAMESPACE
+using namespace android;
+
+class RefCountedMtpServer : public MtpServer
+{
+public:
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(RefCountedMtpServer)
+
+ RefCountedMtpServer(int aFd, MtpDatabase* aDatabase, bool aPtp,
+ int aFileGroup, int aFilePerm, int aDirectoryPerm)
+ : MtpServer(aFd, aDatabase, aPtp, aFileGroup, aFilePerm, aDirectoryPerm)
+ {
+ }
+
+protected:
+ virtual ~RefCountedMtpServer() {}
+};
+
+class MozMtpServer
+{
+public:
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(MozMtpServer)
+
+ bool Init();
+ void Run();
+
+ already_AddRefed<RefCountedMtpServer> GetMtpServer();
+ already_AddRefed<MozMtpDatabase> GetMozMtpDatabase();
+
+protected:
+ virtual ~MozMtpServer() {}
+
+private:
+ RefPtr<RefCountedMtpServer> mMtpServer;
+ RefPtr<MozMtpDatabase> mMozMtpDatabase;
+ nsCOMPtr<nsIThread> mServerThread;
+ ScopedClose mMtpUsbFd;
+};
+
+END_MTP_NAMESPACE
+
+#endif // mozilla_system_mozmtpserver_h__
+
+
diff --git a/dom/system/gonk/MozMtpStorage.cpp b/dom/system/gonk/MozMtpStorage.cpp
new file mode 100644
index 000000000..9c358a132
--- /dev/null
+++ b/dom/system/gonk/MozMtpStorage.cpp
@@ -0,0 +1,135 @@
+/* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "MozMtpStorage.h"
+#include "MozMtpDatabase.h"
+#include "MozMtpServer.h"
+
+#include "base/message_loop.h"
+#include "nsXULAppAPI.h"
+
+BEGIN_MTP_NAMESPACE
+using namespace android;
+
+MozMtpStorage::MozMtpStorage(Volume* aVolume, MozMtpServer* aMozMtpServer)
+ : mMozMtpServer(aMozMtpServer),
+ mVolume(aVolume)
+{
+ MOZ_ASSERT(MessageLoop::current() == XRE_GetIOMessageLoop());
+
+ // The MtpStorageID has the physical volume in the top 16 bits, and the
+ // logical volumein the lower 16 bits. We treat each volume as a separate
+ // phsyical storage;
+ mStorageID = mVolume->Id() << 16 | 1;
+
+ MTP_LOG("Storage constructed for Volume %s mStorageID 0x%08x",
+ aVolume->NameStr(), mStorageID);
+
+ Volume::RegisterVolumeObserver(this, "MozMtpStorage");
+
+ // Get things in sync
+ Notify(mVolume);
+}
+
+MozMtpStorage::~MozMtpStorage()
+{
+ MOZ_ASSERT(MessageLoop::current() == XRE_GetIOMessageLoop());
+
+ MTP_LOG("Storage destructed for Volume %s mStorageID 0x%08x",
+ mVolume->NameStr(), mStorageID);
+
+ Volume::UnregisterVolumeObserver(this, "MozMtpStorage");
+ if (mMtpStorage) {
+ StorageUnavailable();
+ }
+}
+
+// virtual
+void
+MozMtpStorage::Notify(Volume* const& aVolume)
+{
+ MOZ_ASSERT(MessageLoop::current() == XRE_GetIOMessageLoop());
+
+ if (aVolume != mVolume) {
+ // Not our volume
+ return;
+ }
+ Volume::STATE volState = aVolume->State();
+
+ MTP_LOG("Volume %s mStorageID 0x%08x state changed to %s SharingEnabled: %d",
+ aVolume->NameStr(), mStorageID, aVolume->StateStr(),
+ aVolume->IsSharingEnabled());
+
+ // vol->IsSharingEnabled really only applies to UMS volumes. We assume that
+ // that as long as MTP is enabled, then all volumes will be shared. The UI
+ // currently doesn't give us anything more granular than on/off.
+
+ if (mMtpStorage) {
+ if (volState != nsIVolume::STATE_MOUNTED) {
+ // The volume is no longer accessible. We need to remove this storage
+ // from the MTP server
+ StorageUnavailable();
+ }
+ } else {
+ if (volState == nsIVolume::STATE_MOUNTED) {
+ // The volume is accessible. Tell the MTP server.
+ StorageAvailable();
+ }
+ }
+}
+
+void
+MozMtpStorage::StorageAvailable()
+{
+ MOZ_ASSERT(MessageLoop::current() == XRE_GetIOMessageLoop());
+
+ nsCString mountPoint = mVolume->MountPoint();
+
+ MTP_LOG("Adding Volume %s mStorageID 0x%08x mountPoint %s to MozMtpDatabase",
+ mVolume->NameStr(), mStorageID, mountPoint.get());
+
+ RefPtr<MozMtpDatabase> db = mMozMtpServer->GetMozMtpDatabase();
+ db->AddStorage(mStorageID, mountPoint.get(), mVolume->NameStr());
+
+ MOZ_ASSERT(!mMtpStorage);
+
+ //TODO: Figure out what to do about maxFileSize.
+
+ mMtpStorage.reset(new MtpStorage(mStorageID, // id
+ mountPoint.get(), // filePath
+ mVolume->NameStr(), // description
+ 1024uLL * 1024uLL, // reserveSpace
+ mVolume->IsHotSwappable(), // removable
+ 2uLL * 1024uLL * 1024uLL * 1024uLL)); // maxFileSize
+ RefPtr<RefCountedMtpServer> server = mMozMtpServer->GetMtpServer();
+
+ MTP_LOG("Adding Volume %s mStorageID 0x%08x mountPoint %s to MtpServer",
+ mVolume->NameStr(), mStorageID, mountPoint.get());
+ server->addStorage(mMtpStorage.get());
+}
+
+void
+MozMtpStorage::StorageUnavailable()
+{
+ MOZ_ASSERT(MessageLoop::current() == XRE_GetIOMessageLoop());
+ MOZ_ASSERT(mMtpStorage);
+
+ MTP_LOG("Removing mStorageID 0x%08x from MtpServer", mStorageID);
+
+ RefPtr<RefCountedMtpServer> server = mMozMtpServer->GetMtpServer();
+ server->removeStorage(mMtpStorage.get());
+
+ MTP_LOG("Removing mStorageID 0x%08x from MozMtpDatabse", mStorageID);
+
+ RefPtr<MozMtpDatabase> db = mMozMtpServer->GetMozMtpDatabase();
+ db->RemoveStorage(mStorageID);
+
+ mMtpStorage = nullptr;
+}
+
+END_MTP_NAMESPACE
+
+
diff --git a/dom/system/gonk/MozMtpStorage.h b/dom/system/gonk/MozMtpStorage.h
new file mode 100644
index 000000000..18d1e04ac
--- /dev/null
+++ b/dom/system/gonk/MozMtpStorage.h
@@ -0,0 +1,47 @@
+/* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_system_mozmtpstorage_h__
+#define mozilla_system_mozmtpstorage_h__
+
+#include "MozMtpCommon.h"
+
+#include "mozilla/UniquePtr.h"
+
+#include "Volume.h"
+
+BEGIN_MTP_NAMESPACE
+using namespace android;
+
+class MozMtpServer;
+
+class MozMtpStorage : public Volume::EventObserver
+{
+public:
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(MozMtpStorage)
+
+ MozMtpStorage(Volume* aVolume, MozMtpServer* aMozMtpServer);
+
+ typedef nsTArray<RefPtr<MozMtpStorage> > Array;
+
+private:
+ virtual ~MozMtpStorage();
+ virtual void Notify(Volume* const& aEvent);
+
+ void StorageAvailable();
+ void StorageUnavailable();
+
+ RefPtr<MozMtpServer> mMozMtpServer;
+ UniquePtr<MtpStorage> mMtpStorage;
+ RefPtr<Volume> mVolume;
+ MtpStorageID mStorageID;
+};
+
+END_MTP_NAMESPACE
+
+#endif // mozilla_system_mozmtpstorage_h__
+
+
diff --git a/dom/system/gonk/NetIdManager.cpp b/dom/system/gonk/NetIdManager.cpp
new file mode 100644
index 000000000..510ec8b22
--- /dev/null
+++ b/dom/system/gonk/NetIdManager.cpp
@@ -0,0 +1,68 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "NetIdManager.h"
+
+NetIdManager::NetIdManager()
+ : mNextNetId(MIN_NET_ID)
+{
+}
+
+int NetIdManager::getNextNetId()
+{
+ // Modified from
+ // http://androidxref.com/5.0.0_r2/xref/frameworks/base/services/
+ // core/java/com/android/server/ConnectivityService.java#764
+
+ int netId = mNextNetId;
+ if (++mNextNetId > MAX_NET_ID) {
+ mNextNetId = MIN_NET_ID;
+ }
+
+ return netId;
+}
+
+void NetIdManager::acquire(const nsString& aInterfaceName,
+ NetIdInfo* aNetIdInfo)
+{
+ // Lookup or create one.
+ if (!mInterfaceToNetIdHash.Get(aInterfaceName, aNetIdInfo)) {
+ aNetIdInfo->mNetId = getNextNetId();
+ aNetIdInfo->mRefCnt = 1;
+ } else {
+ aNetIdInfo->mRefCnt++;
+ }
+
+ // Update hash and return.
+ mInterfaceToNetIdHash.Put(aInterfaceName, *aNetIdInfo);
+
+ return;
+}
+
+bool NetIdManager::lookup(const nsString& aInterfaceName,
+ NetIdInfo* aNetIdInfo)
+{
+ return mInterfaceToNetIdHash.Get(aInterfaceName, aNetIdInfo);
+}
+
+bool NetIdManager::release(const nsString& aInterfaceName,
+ NetIdInfo* aNetIdInfo)
+{
+ if (!mInterfaceToNetIdHash.Get(aInterfaceName, aNetIdInfo)) {
+ return false; // No such key.
+ }
+
+ aNetIdInfo->mRefCnt--;
+
+ // Update the hash if still be referenced.
+ if (aNetIdInfo->mRefCnt > 0) {
+ mInterfaceToNetIdHash.Put(aInterfaceName, *aNetIdInfo);
+ return true;
+ }
+
+ // No longer be referenced. Remove the entry.
+ mInterfaceToNetIdHash.Remove(aInterfaceName);
+
+ return true;
+} \ No newline at end of file
diff --git a/dom/system/gonk/NetIdManager.h b/dom/system/gonk/NetIdManager.h
new file mode 100644
index 000000000..e35d0ecd2
--- /dev/null
+++ b/dom/system/gonk/NetIdManager.h
@@ -0,0 +1,45 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef NetIdManager_h
+#define NetIdManager_h
+
+#include "nsString.h"
+#include "nsDataHashtable.h"
+
+// NetId is a logical network identifier defined by netd.
+// A network is typically a physical one (i.e. PhysicalNetwork.cpp)
+// for netd but it could be a virtual network as well.
+// We currently use physical network only and use one-to-one
+// network-interface mapping.
+
+class NetIdManager {
+public:
+ // keep in sync with system/netd/NetworkController.cpp
+ enum {
+ MIN_NET_ID = 100,
+ MAX_NET_ID = 65535,
+ };
+
+ // We need to count the number of references since different
+ // application like data and mms may use the same interface.
+ struct NetIdInfo {
+ int mNetId;
+ int mRefCnt;
+ };
+
+public:
+ NetIdManager();
+
+ bool lookup(const nsString& aInterfaceName, NetIdInfo* aNetIdInfo);
+ void acquire(const nsString& aInterfaceName, NetIdInfo* aNetIdInfo);
+ bool release(const nsString& aInterfaceName, NetIdInfo* aNetIdInfo);
+
+private:
+ int getNextNetId();
+ int mNextNetId;
+ nsDataHashtable<nsStringHashKey, NetIdInfo> mInterfaceToNetIdHash;
+};
+
+#endif \ No newline at end of file
diff --git a/dom/system/gonk/NetworkInterfaceListService.js b/dom/system/gonk/NetworkInterfaceListService.js
new file mode 100644
index 000000000..62fe046aa
--- /dev/null
+++ b/dom/system/gonk/NetworkInterfaceListService.js
@@ -0,0 +1,110 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
+const NETWORKLISTSERVICE_CONTRACTID =
+ "@mozilla.org/network/interface-list-service;1";
+const NETWORKLISTSERVICE_CID =
+ Components.ID("{3780be6e-7012-4e53-ade6-15212fb88a0d}");
+
+XPCOMUtils.defineLazyServiceGetter(this, "cpmm",
+ "@mozilla.org/childprocessmessagemanager;1",
+ "nsISyncMessageSender");
+
+function NetworkInterfaceListService () {
+}
+
+NetworkInterfaceListService.prototype = {
+ classID: NETWORKLISTSERVICE_CID,
+
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsINetworkInterfaceListService]),
+
+ getDataInterfaceList: function(aConditions) {
+ return new NetworkInterfaceList(
+ cpmm.sendSyncMessage(
+ 'NetworkInterfaceList:ListInterface',
+ {
+ excludeSupl: (aConditions &
+ Ci.nsINetworkInterfaceListService.
+ LIST_NOT_INCLUDE_SUPL_INTERFACES) != 0,
+ excludeMms: (aConditions &
+ Ci.nsINetworkInterfaceListService.
+ LIST_NOT_INCLUDE_MMS_INTERFACES) != 0,
+ excludeIms: (aConditions &
+ Ci.nsINetworkInterfaceListService.
+ LIST_NOT_INCLUDE_IMS_INTERFACES) != 0,
+ excludeDun: (aConditions &
+ Ci.nsINetworkInterfaceListService.
+ LIST_NOT_INCLUDE_DUN_INTERFACES) != 0,
+ excludeFota: (aConditions &
+ Ci.nsINetworkInterfaceListService.
+ LIST_NOT_INCLUDE_FOTA_INTERFACES) != 0
+ }
+ )[0]);
+ }
+};
+
+function FakeNetworkInfo(aAttributes) {
+ this.state = aAttributes.state;
+ this.type = aAttributes.type;
+ this.name = aAttributes.name;
+ this.ips = aAttributes.ips;
+ this.prefixLengths = aAttributes.prefixLengths;
+ this.gateways = aAttributes.gateways;
+ this.dnses = aAttributes.dnses;
+}
+FakeNetworkInfo.prototype = {
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsINetworkInfo]),
+
+ getAddresses: function (ips, prefixLengths) {
+ ips.value = this.ips.slice();
+ prefixLengths.value = this.prefixLengths.slice();
+
+ return this.ips.length;
+ },
+
+ getGateways: function (count) {
+ if (count) {
+ count.value = this.gateways.length;
+ }
+ return this.gateways.slice();
+ },
+
+ getDnses: function (count) {
+ if (count) {
+ count.value = this.dnses.length;
+ }
+ return this.dnses.slice();
+ }
+};
+
+function NetworkInterfaceList (aInterfaceLiterals) {
+ this._interfaces = [];
+ for (let entry of aInterfaceLiterals) {
+ this._interfaces.push(new FakeNetworkInfo(entry));
+ }
+}
+
+NetworkInterfaceList.prototype = {
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsINetworkInterfaceList]),
+
+ getNumberOfInterface: function() {
+ return this._interfaces.length;
+ },
+
+ getInterfaceInfo: function(index) {
+ if (!this._interfaces) {
+ return null;
+ }
+ return this._interfaces[index];
+ }
+};
+
+this.NSGetFactory = XPCOMUtils.generateNSGetFactory([NetworkInterfaceListService]);
+
diff --git a/dom/system/gonk/NetworkInterfaceListService.manifest b/dom/system/gonk/NetworkInterfaceListService.manifest
new file mode 100644
index 000000000..a827e778f
--- /dev/null
+++ b/dom/system/gonk/NetworkInterfaceListService.manifest
@@ -0,0 +1,17 @@
+# Copyright 2012 Mozilla Foundation and Mozilla contributors
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# NetworkInterfaceListService.js
+component {3780be6e-7012-4e53-ade6-15212fb88a0d} NetworkInterfaceListService.js
+contract @mozilla.org/network/interface-list-service;1 {3780be6e-7012-4e53-ade6-15212fb88a0d}
diff --git a/dom/system/gonk/NetworkManager.js b/dom/system/gonk/NetworkManager.js
new file mode 100644
index 000000000..9d7a5683e
--- /dev/null
+++ b/dom/system/gonk/NetworkManager.js
@@ -0,0 +1,1219 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/FileUtils.jsm");
+Cu.import("resource://gre/modules/systemlibs.js");
+Cu.import("resource://gre/modules/Promise.jsm");
+
+const NETWORKMANAGER_CONTRACTID = "@mozilla.org/network/manager;1";
+const NETWORKMANAGER_CID =
+ Components.ID("{1ba9346b-53b5-4660-9dc6-58f0b258d0a6}");
+
+const DEFAULT_PREFERRED_NETWORK_TYPE = Ci.nsINetworkInterface.NETWORK_TYPE_ETHERNET;
+
+XPCOMUtils.defineLazyGetter(this, "ppmm", function() {
+ return Cc["@mozilla.org/parentprocessmessagemanager;1"]
+ .getService(Ci.nsIMessageBroadcaster);
+});
+
+XPCOMUtils.defineLazyServiceGetter(this, "gDNSService",
+ "@mozilla.org/network/dns-service;1",
+ "nsIDNSService");
+
+XPCOMUtils.defineLazyServiceGetter(this, "gNetworkService",
+ "@mozilla.org/network/service;1",
+ "nsINetworkService");
+
+XPCOMUtils.defineLazyServiceGetter(this, "gTetheringService",
+ "@mozilla.org/tethering/service;1",
+ "nsITetheringService");
+
+const TOPIC_INTERFACE_REGISTERED = "network-interface-registered";
+const TOPIC_INTERFACE_UNREGISTERED = "network-interface-unregistered";
+const TOPIC_ACTIVE_CHANGED = "network-active-changed";
+const TOPIC_PREF_CHANGED = "nsPref:changed";
+const TOPIC_XPCOM_SHUTDOWN = "xpcom-shutdown";
+const TOPIC_CONNECTION_STATE_CHANGED = "network-connection-state-changed";
+const PREF_MANAGE_OFFLINE_STATUS = "network.gonk.manage-offline-status";
+const PREF_NETWORK_DEBUG_ENABLED = "network.debugging.enabled";
+
+const IPV4_ADDRESS_ANY = "0.0.0.0";
+const IPV6_ADDRESS_ANY = "::0";
+
+const IPV4_MAX_PREFIX_LENGTH = 32;
+const IPV6_MAX_PREFIX_LENGTH = 128;
+
+// Connection Type for Network Information API
+const CONNECTION_TYPE_CELLULAR = 0;
+const CONNECTION_TYPE_BLUETOOTH = 1;
+const CONNECTION_TYPE_ETHERNET = 2;
+const CONNECTION_TYPE_WIFI = 3;
+const CONNECTION_TYPE_OTHER = 4;
+const CONNECTION_TYPE_NONE = 5;
+
+const MANUAL_PROXY_CONFIGURATION = 1;
+
+var debug;
+function updateDebug() {
+ let debugPref = false; // set default value here.
+ try {
+ debugPref = debugPref || Services.prefs.getBoolPref(PREF_NETWORK_DEBUG_ENABLED);
+ } catch (e) {}
+
+ if (debugPref) {
+ debug = function(s) {
+ dump("-*- NetworkManager: " + s + "\n");
+ };
+ } else {
+ debug = function(s) {};
+ }
+}
+updateDebug();
+
+function defineLazyRegExp(obj, name, pattern) {
+ obj.__defineGetter__(name, function() {
+ delete obj[name];
+ return obj[name] = new RegExp(pattern);
+ });
+}
+
+function ExtraNetworkInfo(aNetwork) {
+ let ips = {};
+ let prefixLengths = {};
+ aNetwork.info.getAddresses(ips, prefixLengths);
+
+ this.state = aNetwork.info.state;
+ this.type = aNetwork.info.type;
+ this.name = aNetwork.info.name;
+ this.ips = ips.value;
+ this.prefixLengths = prefixLengths.value;
+ this.gateways = aNetwork.info.getGateways();
+ this.dnses = aNetwork.info.getDnses();
+ this.httpProxyHost = aNetwork.httpProxyHost;
+ this.httpProxyPort = aNetwork.httpProxyPort;
+ this.mtu = aNetwork.mtu;
+}
+ExtraNetworkInfo.prototype = {
+ getAddresses: function(aIps, aPrefixLengths) {
+ aIps.value = this.ips.slice();
+ aPrefixLengths.value = this.prefixLengths.slice();
+
+ return this.ips.length;
+ },
+
+ getGateways: function(aCount) {
+ if (aCount) {
+ aCount.value = this.gateways.length;
+ }
+
+ return this.gateways.slice();
+ },
+
+ getDnses: function(aCount) {
+ if (aCount) {
+ aCount.value = this.dnses.length;
+ }
+
+ return this.dnses.slice();
+ }
+};
+
+function NetworkInterfaceLinks()
+{
+ this.resetLinks();
+}
+NetworkInterfaceLinks.prototype = {
+ linkRoutes: null,
+ gateways: null,
+ interfaceName: null,
+ extraRoutes: null,
+
+ setLinks: function(linkRoutes, gateways, interfaceName) {
+ this.linkRoutes = linkRoutes;
+ this.gateways = gateways;
+ this.interfaceName = interfaceName;
+ },
+
+ resetLinks: function() {
+ this.linkRoutes = [];
+ this.gateways = [];
+ this.interfaceName = "";
+ this.extraRoutes = [];
+ },
+
+ compareGateways: function(gateways) {
+ if (this.gateways.length != gateways.length) {
+ return false;
+ }
+
+ for (let i = 0; i < this.gateways.length; i++) {
+ if (this.gateways[i] != gateways[i]) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+};
+
+/**
+ * This component watches for network interfaces changing state and then
+ * adjusts routes etc. accordingly.
+ */
+function NetworkManager() {
+ this.networkInterfaces = {};
+ this.networkInterfaceLinks = {};
+
+ try {
+ this._manageOfflineStatus =
+ Services.prefs.getBoolPref(PREF_MANAGE_OFFLINE_STATUS);
+ } catch(ex) {
+ // Ignore.
+ }
+ Services.prefs.addObserver(PREF_MANAGE_OFFLINE_STATUS, this, false);
+ Services.prefs.addObserver(PREF_NETWORK_DEBUG_ENABLED, this, false);
+ Services.obs.addObserver(this, TOPIC_XPCOM_SHUTDOWN, false);
+
+ this.setAndConfigureActive();
+
+ ppmm.addMessageListener('NetworkInterfaceList:ListInterface', this);
+
+ // Used in resolveHostname().
+ defineLazyRegExp(this, "REGEXP_IPV4", "^\\d{1,3}(?:\\.\\d{1,3}){3}$");
+ defineLazyRegExp(this, "REGEXP_IPV6", "^[\\da-fA-F]{4}(?::[\\da-fA-F]{4}){7}$");
+}
+NetworkManager.prototype = {
+ classID: NETWORKMANAGER_CID,
+ classInfo: XPCOMUtils.generateCI({classID: NETWORKMANAGER_CID,
+ contractID: NETWORKMANAGER_CONTRACTID,
+ classDescription: "Network Manager",
+ interfaces: [Ci.nsINetworkManager]}),
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsINetworkManager,
+ Ci.nsISupportsWeakReference,
+ Ci.nsIObserver,
+ Ci.nsISettingsServiceCallback]),
+
+ // nsIObserver
+
+ observe: function(subject, topic, data) {
+ switch (topic) {
+ case TOPIC_PREF_CHANGED:
+ if (data === PREF_NETWORK_DEBUG_ENABLED) {
+ updateDebug();
+ } else if (data === PREF_MANAGE_OFFLINE_STATUS) {
+ this._manageOfflineStatus =
+ Services.prefs.getBoolPref(PREF_MANAGE_OFFLINE_STATUS);
+ debug(PREF_MANAGE_OFFLINE_STATUS + " has changed to " + this._manageOfflineStatus);
+ }
+ break;
+ case TOPIC_XPCOM_SHUTDOWN:
+ Services.obs.removeObserver(this, TOPIC_XPCOM_SHUTDOWN);
+ Services.prefs.removeObserver(PREF_MANAGE_OFFLINE_STATUS, this);
+ Services.prefs.removeObserver(PREF_NETWORK_DEBUG_ENABLED, this);
+ break;
+ }
+ },
+
+ receiveMessage: function(aMsg) {
+ switch (aMsg.name) {
+ case "NetworkInterfaceList:ListInterface": {
+ let excludeMms = aMsg.json.excludeMms;
+ let excludeSupl = aMsg.json.excludeSupl;
+ let excludeIms = aMsg.json.excludeIms;
+ let excludeDun = aMsg.json.excludeDun;
+ let excludeFota = aMsg.json.excludeFota;
+ let interfaces = [];
+
+ for (let key in this.networkInterfaces) {
+ let network = this.networkInterfaces[key];
+ let i = network.info;
+ if ((i.type == Ci.nsINetworkInfo.NETWORK_TYPE_MOBILE_MMS && excludeMms) ||
+ (i.type == Ci.nsINetworkInfo.NETWORK_TYPE_MOBILE_SUPL && excludeSupl) ||
+ (i.type == Ci.nsINetworkInfo.NETWORK_TYPE_MOBILE_IMS && excludeIms) ||
+ (i.type == Ci.nsINetworkInfo.NETWORK_TYPE_MOBILE_DUN && excludeDun) ||
+ (i.type == Ci.nsINetworkInfo.NETWORK_TYPE_MOBILE_FOTA && excludeFota)) {
+ continue;
+ }
+
+ let ips = {};
+ let prefixLengths = {};
+ i.getAddresses(ips, prefixLengths);
+
+ interfaces.push({
+ state: i.state,
+ type: i.type,
+ name: i.name,
+ ips: ips.value,
+ prefixLengths: prefixLengths.value,
+ gateways: i.getGateways(),
+ dnses: i.getDnses()
+ });
+ }
+ return interfaces;
+ }
+ }
+ },
+
+ getNetworkId: function(aNetworkInfo) {
+ let id = "device";
+ try {
+ if (aNetworkInfo instanceof Ci.nsIRilNetworkInfo) {
+ let rilInfo = aNetworkInfo.QueryInterface(Ci.nsIRilNetworkInfo);
+ id = "ril" + rilInfo.serviceId;
+ }
+ } catch (e) {}
+
+ return id + "-" + aNetworkInfo.type;
+ },
+
+ // nsINetworkManager
+
+ registerNetworkInterface: function(network) {
+ if (!(network instanceof Ci.nsINetworkInterface)) {
+ throw Components.Exception("Argument must be nsINetworkInterface.",
+ Cr.NS_ERROR_INVALID_ARG);
+ }
+ let networkId = this.getNetworkId(network.info);
+ if (networkId in this.networkInterfaces) {
+ throw Components.Exception("Network with that type already registered!",
+ Cr.NS_ERROR_INVALID_ARG);
+ }
+ this.networkInterfaces[networkId] = network;
+ this.networkInterfaceLinks[networkId] = new NetworkInterfaceLinks();
+
+ Services.obs.notifyObservers(network.info, TOPIC_INTERFACE_REGISTERED, null);
+ debug("Network '" + networkId + "' registered.");
+ },
+
+ _addSubnetRoutes: function(network) {
+ let ips = {};
+ let prefixLengths = {};
+ let length = network.getAddresses(ips, prefixLengths);
+ let promises = [];
+
+ for (let i = 0; i < length; i++) {
+ debug('Adding subnet routes: ' + ips.value[i] + '/' + prefixLengths.value[i]);
+ promises.push(
+ gNetworkService.modifyRoute(Ci.nsINetworkService.MODIFY_ROUTE_ADD,
+ network.name, ips.value[i], prefixLengths.value[i])
+ .catch(aError => {
+ debug("_addSubnetRoutes error: " + aError);
+ }));
+ }
+
+ return Promise.all(promises);
+ },
+
+ updateNetworkInterface: function(network) {
+ if (!(network instanceof Ci.nsINetworkInterface)) {
+ throw Components.Exception("Argument must be nsINetworkInterface.",
+ Cr.NS_ERROR_INVALID_ARG);
+ }
+ let networkId = this.getNetworkId(network.info);
+ if (!(networkId in this.networkInterfaces)) {
+ throw Components.Exception("No network with that type registered.",
+ Cr.NS_ERROR_INVALID_ARG);
+ }
+ debug("Network " + network.info.type + "/" + network.info.name +
+ " changed state to " + network.info.state);
+
+ // Keep a copy of network in case it is modified while we are updating.
+ let extNetworkInfo = new ExtraNetworkInfo(network);
+
+ // Note that since Lollipop we need to allocate and initialize
+ // something through netd, so we add createNetwork/destroyNetwork
+ // to deal with that explicitly.
+
+ switch (extNetworkInfo.state) {
+ case Ci.nsINetworkInfo.NETWORK_STATE_CONNECTED:
+
+ this._createNetwork(extNetworkInfo.name)
+ // Remove pre-created default route and let setAndConfigureActive()
+ // to set default route only on preferred network
+ .then(() => this._removeDefaultRoute(extNetworkInfo))
+ // Set DNS server as early as possible to prevent from
+ // premature domain name lookup.
+ .then(() => this._setDNS(extNetworkInfo))
+ .then(() => {
+ // Add host route for data calls
+ if (!this.isNetworkTypeMobile(extNetworkInfo.type)) {
+ return;
+ }
+
+ let currentInterfaceLinks = this.networkInterfaceLinks[networkId];
+ let newLinkRoutes = extNetworkInfo.getDnses().concat(
+ extNetworkInfo.httpProxyHost);
+ // If gateways have changed, remove all old routes first.
+ return this._handleGateways(networkId, extNetworkInfo.getGateways())
+ .then(() => this._updateRoutes(currentInterfaceLinks.linkRoutes,
+ newLinkRoutes,
+ extNetworkInfo.getGateways(),
+ extNetworkInfo.name))
+ .then(() => currentInterfaceLinks.setLinks(newLinkRoutes,
+ extNetworkInfo.getGateways(),
+ extNetworkInfo.name));
+ })
+ .then(() => {
+ if (extNetworkInfo.type !=
+ Ci.nsINetworkInfo.NETWORK_TYPE_MOBILE_DUN) {
+ return;
+ }
+ // Dun type is a special case where we add the default route to a
+ // secondary table.
+ return this.setSecondaryDefaultRoute(extNetworkInfo);
+ })
+ .then(() => this._addSubnetRoutes(extNetworkInfo))
+ .then(() => {
+ if (extNetworkInfo.mtu <= 0) {
+ return;
+ }
+
+ return this._setMtu(extNetworkInfo);
+ })
+ .then(() => this.setAndConfigureActive())
+ .then(() => {
+ // Update data connection when Wifi connected/disconnected
+ if (extNetworkInfo.type ==
+ Ci.nsINetworkInfo.NETWORK_TYPE_WIFI && this.mRil) {
+ for (let i = 0; i < this.mRil.numRadioInterfaces; i++) {
+ this.mRil.getRadioInterface(i).updateRILNetworkInterface();
+ }
+ }
+
+ // Probing the public network accessibility after routing table is ready
+ CaptivePortalDetectionHelper
+ .notify(CaptivePortalDetectionHelper.EVENT_CONNECT,
+ this.activeNetworkInfo);
+ })
+ .then(() => {
+ // Notify outer modules like MmsService to start the transaction after
+ // the configuration of the network interface is done.
+ Services.obs.notifyObservers(network.info,
+ TOPIC_CONNECTION_STATE_CHANGED,
+ this.convertConnectionType(network.info));
+ })
+ .catch(aError => {
+ debug("updateNetworkInterface error: " + aError);
+ });
+ break;
+ case Ci.nsINetworkInfo.NETWORK_STATE_DISCONNECTED:
+ Promise.resolve()
+ .then(() => {
+ if (!this.isNetworkTypeMobile(extNetworkInfo.type)) {
+ return;
+ }
+ // Remove host route for data calls
+ return this._cleanupAllHostRoutes(networkId);
+ })
+ .then(() => {
+ if (extNetworkInfo.type !=
+ Ci.nsINetworkInfo.NETWORK_TYPE_MOBILE_DUN) {
+ return;
+ }
+ // Remove secondary default route for dun.
+ return this.removeSecondaryDefaultRoute(extNetworkInfo);
+ })
+ .then(() => {
+ if (extNetworkInfo.type == Ci.nsINetworkInfo.NETWORK_TYPE_WIFI ||
+ extNetworkInfo.type == Ci.nsINetworkInfo.NETWORK_TYPE_ETHERNET) {
+ // Remove routing table in /proc/net/route
+ return this._resetRoutingTable(extNetworkInfo.name);
+ }
+ if (extNetworkInfo.type == Ci.nsINetworkInfo.NETWORK_TYPE_MOBILE) {
+ return this._removeDefaultRoute(extNetworkInfo)
+ }
+ })
+ .then(() => {
+ // Clear http proxy on active network.
+ if (this.activeNetworkInfo &&
+ extNetworkInfo.type == this.activeNetworkInfo.type) {
+ this.clearNetworkProxy();
+ }
+
+ // Abort ongoing captive portal detection on the wifi interface
+ CaptivePortalDetectionHelper
+ .notify(CaptivePortalDetectionHelper.EVENT_DISCONNECT, extNetworkInfo);
+ })
+ .then(() => this.setAndConfigureActive())
+ .then(() => {
+ // Update data connection when Wifi connected/disconnected
+ if (extNetworkInfo.type ==
+ Ci.nsINetworkInfo.NETWORK_TYPE_WIFI && this.mRil) {
+ for (let i = 0; i < this.mRil.numRadioInterfaces; i++) {
+ this.mRil.getRadioInterface(i).updateRILNetworkInterface();
+ }
+ }
+ })
+ .then(() => this._destroyNetwork(extNetworkInfo.name))
+ .then(() => {
+ // Notify outer modules like MmsService to start the transaction after
+ // the configuration of the network interface is done.
+ Services.obs.notifyObservers(network.info,
+ TOPIC_CONNECTION_STATE_CHANGED,
+ this.convertConnectionType(network.info));
+ })
+ .catch(aError => {
+ debug("updateNetworkInterface error: " + aError);
+ });
+ break;
+ }
+ },
+
+ unregisterNetworkInterface: function(network) {
+ if (!(network instanceof Ci.nsINetworkInterface)) {
+ throw Components.Exception("Argument must be nsINetworkInterface.",
+ Cr.NS_ERROR_INVALID_ARG);
+ }
+ let networkId = this.getNetworkId(network.info);
+ if (!(networkId in this.networkInterfaces)) {
+ throw Components.Exception("No network with that type registered.",
+ Cr.NS_ERROR_INVALID_ARG);
+ }
+
+ // This is for in case a network gets unregistered without being
+ // DISCONNECTED.
+ if (this.isNetworkTypeMobile(network.info.type)) {
+ this._cleanupAllHostRoutes(networkId);
+ }
+
+ delete this.networkInterfaces[networkId];
+
+ Services.obs.notifyObservers(network.info, TOPIC_INTERFACE_UNREGISTERED, null);
+ debug("Network '" + networkId + "' unregistered.");
+ },
+
+ _manageOfflineStatus: true,
+
+ networkInterfaces: null,
+
+ networkInterfaceLinks: null,
+
+ _networkTypePriorityList: [Ci.nsINetworkInterface.NETWORK_TYPE_ETHERNET,
+ Ci.nsINetworkInterface.NETWORK_TYPE_WIFI,
+ Ci.nsINetworkInterface.NETWORK_TYPE_MOBILE],
+ get networkTypePriorityList() {
+ return this._networkTypePriorityList;
+ },
+ set networkTypePriorityList(val) {
+ if (val.length != this._networkTypePriorityList.length) {
+ throw "Priority list length should equal to " +
+ this._networkTypePriorityList.length;
+ }
+
+ // Check if types in new priority list are valid and also make sure there
+ // are no duplicate types.
+ let list = [Ci.nsINetworkInterface.NETWORK_TYPE_ETHERNET,
+ Ci.nsINetworkInterface.NETWORK_TYPE_WIFI,
+ Ci.nsINetworkInterface.NETWORK_TYPE_MOBILE];
+ while (list.length) {
+ let type = list.shift();
+ if (val.indexOf(type) == -1) {
+ throw "There is missing network type";
+ }
+ }
+
+ this._networkTypePriorityList = val;
+ },
+
+ getPriority: function(type) {
+ if (this._networkTypePriorityList.indexOf(type) == -1) {
+ // 0 indicates the lowest priority.
+ return 0;
+ }
+
+ return this._networkTypePriorityList.length -
+ this._networkTypePriorityList.indexOf(type);
+ },
+
+ get allNetworkInfo() {
+ let allNetworkInfo = {};
+
+ for (let networkId in this.networkInterfaces) {
+ if (this.networkInterfaces.hasOwnProperty(networkId)) {
+ allNetworkInfo[networkId] = this.networkInterfaces[networkId].info;
+ }
+ }
+
+ return allNetworkInfo;
+ },
+
+ _preferredNetworkType: DEFAULT_PREFERRED_NETWORK_TYPE,
+ get preferredNetworkType() {
+ return this._preferredNetworkType;
+ },
+ set preferredNetworkType(val) {
+ if ([Ci.nsINetworkInterface.NETWORK_TYPE_WIFI,
+ Ci.nsINetworkInterface.NETWORK_TYPE_MOBILE,
+ Ci.nsINetworkInterface.NETWORK_TYPE_ETHERNET].indexOf(val) == -1) {
+ throw "Invalid network type";
+ }
+ this._preferredNetworkType = val;
+ },
+
+ _activeNetwork: null,
+
+ get activeNetworkInfo() {
+ return this._activeNetwork && this._activeNetwork.info;
+ },
+
+ _overriddenActive: null,
+
+ overrideActive: function(network) {
+ if ([Ci.nsINetworkInterface.NETWORK_TYPE_WIFI,
+ Ci.nsINetworkInterface.NETWORK_TYPE_MOBILE,
+ Ci.nsINetworkInterface.NETWORK_TYPE_ETHERNET].indexOf(val) == -1) {
+ throw "Invalid network type";
+ }
+
+ this._overriddenActive = network;
+ this.setAndConfigureActive();
+ },
+
+ _updateRoutes: function(oldLinks, newLinks, gateways, interfaceName) {
+ // Returns items that are in base but not in target.
+ function getDifference(base, target) {
+ return base.filter(function(i) { return target.indexOf(i) < 0; });
+ }
+
+ let addedLinks = getDifference(newLinks, oldLinks);
+ let removedLinks = getDifference(oldLinks, newLinks);
+
+ if (addedLinks.length === 0 && removedLinks.length === 0) {
+ return Promise.resolve();
+ }
+
+ return this._setHostRoutes(false, removedLinks, interfaceName, gateways)
+ .then(this._setHostRoutes(true, addedLinks, interfaceName, gateways));
+ },
+
+ _setHostRoutes: function(doAdd, ipAddresses, networkName, gateways) {
+ let getMaxPrefixLength = (aIp) => {
+ return aIp.match(this.REGEXP_IPV4) ? IPV4_MAX_PREFIX_LENGTH : IPV6_MAX_PREFIX_LENGTH;
+ }
+
+ let promises = [];
+
+ ipAddresses.forEach((aIpAddress) => {
+ let gateway = this.selectGateway(gateways, aIpAddress);
+ if (gateway) {
+ promises.push((doAdd)
+ ? gNetworkService.modifyRoute(Ci.nsINetworkService.MODIFY_ROUTE_ADD,
+ networkName, aIpAddress,
+ getMaxPrefixLength(aIpAddress), gateway)
+ : gNetworkService.modifyRoute(Ci.nsINetworkService.MODIFY_ROUTE_REMOVE,
+ networkName, aIpAddress,
+ getMaxPrefixLength(aIpAddress), gateway));
+ }
+ });
+
+ return Promise.all(promises);
+ },
+
+ isValidatedNetwork: function(aNetworkInfo) {
+ let isValid = false;
+ try {
+ isValid = (this.getNetworkId(aNetworkInfo) in this.networkInterfaces);
+ } catch (e) {
+ debug("Invalid network interface: " + e);
+ }
+
+ return isValid;
+ },
+
+ addHostRoute: function(aNetworkInfo, aHost) {
+ if (!this.isValidatedNetwork(aNetworkInfo)) {
+ return Promise.reject("Invalid network info.");
+ }
+
+ return this.resolveHostname(aNetworkInfo, aHost)
+ .then((ipAddresses) => {
+ let promises = [];
+ let networkId = this.getNetworkId(aNetworkInfo);
+
+ ipAddresses.forEach((aIpAddress) => {
+ let promise =
+ this._setHostRoutes(true, [aIpAddress], aNetworkInfo.name, aNetworkInfo.getGateways())
+ .then(() => this.networkInterfaceLinks[networkId].extraRoutes.push(aIpAddress));
+
+ promises.push(promise);
+ });
+
+ return Promise.all(promises);
+ });
+ },
+
+ removeHostRoute: function(aNetworkInfo, aHost) {
+ if (!this.isValidatedNetwork(aNetworkInfo)) {
+ return Promise.reject("Invalid network info.");
+ }
+
+ return this.resolveHostname(aNetworkInfo, aHost)
+ .then((ipAddresses) => {
+ let promises = [];
+ let networkId = this.getNetworkId(aNetworkInfo);
+
+ ipAddresses.forEach((aIpAddress) => {
+ let found = this.networkInterfaceLinks[networkId].extraRoutes.indexOf(aIpAddress);
+ if (found < 0) {
+ return; // continue
+ }
+
+ let promise =
+ this._setHostRoutes(false, [aIpAddress], aNetworkInfo.name, aNetworkInfo.getGateways())
+ .then(() => {
+ this.networkInterfaceLinks[networkId].extraRoutes.splice(found, 1);
+ }, () => {
+ // We should remove it even if the operation failed.
+ this.networkInterfaceLinks[networkId].extraRoutes.splice(found, 1);
+ });
+ promises.push(promise);
+ });
+
+ return Promise.all(promises);
+ });
+ },
+
+ isNetworkTypeSecondaryMobile: function(type) {
+ return (type == Ci.nsINetworkInfo.NETWORK_TYPE_MOBILE_MMS ||
+ type == Ci.nsINetworkInfo.NETWORK_TYPE_MOBILE_SUPL ||
+ type == Ci.nsINetworkInfo.NETWORK_TYPE_MOBILE_IMS ||
+ type == Ci.nsINetworkInfo.NETWORK_TYPE_MOBILE_DUN ||
+ type == Ci.nsINetworkInfo.NETWORK_TYPE_MOBILE_FOTA);
+ },
+
+ isNetworkTypeMobile: function(type) {
+ return (type == Ci.nsINetworkInfo.NETWORK_TYPE_MOBILE ||
+ this.isNetworkTypeSecondaryMobile(type));
+ },
+
+ _handleGateways: function(networkId, gateways) {
+ let currentNetworkLinks = this.networkInterfaceLinks[networkId];
+ if (currentNetworkLinks.gateways.length == 0 ||
+ currentNetworkLinks.compareGateways(gateways)) {
+ return Promise.resolve();
+ }
+
+ let currentExtraRoutes = currentNetworkLinks.extraRoutes;
+ return this._cleanupAllHostRoutes(networkId)
+ .then(() => {
+ // If gateways have changed, re-add extra host routes with new gateways.
+ if (currentExtraRoutes.length > 0) {
+ this._setHostRoutes(true,
+ currentExtraRoutes,
+ currentNetworkLinks.interfaceName,
+ gateways)
+ .then(() => {
+ currentNetworkLinks.extraRoutes = currentExtraRoutes;
+ });
+ }
+ });
+ },
+
+ _cleanupAllHostRoutes: function(networkId) {
+ let currentNetworkLinks = this.networkInterfaceLinks[networkId];
+ let hostRoutes = currentNetworkLinks.linkRoutes.concat(
+ currentNetworkLinks.extraRoutes);
+
+ if (hostRoutes.length === 0) {
+ return Promise.resolve();
+ }
+
+ return this._setHostRoutes(false,
+ hostRoutes,
+ currentNetworkLinks.interfaceName,
+ currentNetworkLinks.gateways)
+ .catch((aError) => {
+ debug("Error (" + aError + ") on _cleanupAllHostRoutes, keep proceeding.");
+ })
+ .then(() => currentNetworkLinks.resetLinks());
+ },
+
+ selectGateway: function(gateways, host) {
+ for (let i = 0; i < gateways.length; i++) {
+ let gateway = gateways[i];
+ if (gateway.match(this.REGEXP_IPV4) && host.match(this.REGEXP_IPV4) ||
+ gateway.indexOf(":") != -1 && host.indexOf(":") != -1) {
+ return gateway;
+ }
+ }
+ return null;
+ },
+
+ _setSecondaryRoute: function(aDoAdd, aInterfaceName, aRoute) {
+ return new Promise((aResolve, aReject) => {
+ if (aDoAdd) {
+ gNetworkService.addSecondaryRoute(aInterfaceName, aRoute,
+ (aSuccess) => {
+ if (!aSuccess) {
+ aReject("addSecondaryRoute failed");
+ return;
+ }
+ aResolve();
+ });
+ } else {
+ gNetworkService.removeSecondaryRoute(aInterfaceName, aRoute,
+ (aSuccess) => {
+ if (!aSuccess) {
+ debug("removeSecondaryRoute failed")
+ }
+ // Always resolve.
+ aResolve();
+ });
+ }
+ });
+ },
+
+ setSecondaryDefaultRoute: function(network) {
+ let gateways = network.getGateways();
+ let promises = [];
+
+ for (let i = 0; i < gateways.length; i++) {
+ let isIPv6 = (gateways[i].indexOf(":") != -1) ? true : false;
+ // First, we need to add a host route to the gateway in the secondary
+ // routing table to make the gateway reachable. Host route takes the max
+ // prefix and gateway address 'any'.
+ let hostRoute = {
+ ip: gateways[i],
+ prefix: isIPv6 ? IPV6_MAX_PREFIX_LENGTH : IPV4_MAX_PREFIX_LENGTH,
+ gateway: isIPv6 ? IPV6_ADDRESS_ANY : IPV4_ADDRESS_ANY
+ };
+ // Now we can add the default route through gateway. Default route takes the
+ // min prefix and destination ip 'any'.
+ let defaultRoute = {
+ ip: isIPv6 ? IPV6_ADDRESS_ANY : IPV4_ADDRESS_ANY,
+ prefix: 0,
+ gateway: gateways[i]
+ };
+
+ let promise = this._setSecondaryRoute(true, network.name, hostRoute)
+ .then(() => this._setSecondaryRoute(true, network.name, defaultRoute));
+
+ promises.push(promise);
+ }
+
+ return Promise.all(promises);
+ },
+
+ removeSecondaryDefaultRoute: function(network) {
+ let gateways = network.getGateways();
+ let promises = [];
+
+ for (let i = 0; i < gateways.length; i++) {
+ let isIPv6 = (gateways[i].indexOf(":") != -1) ? true : false;
+ // Remove both default route and host route.
+ let defaultRoute = {
+ ip: isIPv6 ? IPV6_ADDRESS_ANY : IPV4_ADDRESS_ANY,
+ prefix: 0,
+ gateway: gateways[i]
+ };
+ let hostRoute = {
+ ip: gateways[i],
+ prefix: isIPv6 ? IPV6_MAX_PREFIX_LENGTH : IPV4_MAX_PREFIX_LENGTH,
+ gateway: isIPv6 ? IPV6_ADDRESS_ANY : IPV4_ADDRESS_ANY
+ };
+
+ let promise = this._setSecondaryRoute(false, network.name, defaultRoute)
+ .then(() => this._setSecondaryRoute(false, network.name, hostRoute));
+
+ promises.push(promise);
+ }
+
+ return Promise.all(promises);
+ },
+
+ /**
+ * Determine the active interface and configure it.
+ */
+ setAndConfigureActive: function() {
+ debug("Evaluating whether active network needs to be changed.");
+ let oldActive = this._activeNetwork;
+
+ if (this._overriddenActive) {
+ debug("We have an override for the active network: " +
+ this._overriddenActive.info.name);
+ // The override was just set, so reconfigure the network.
+ if (this._activeNetwork != this._overriddenActive) {
+ this._activeNetwork = this._overriddenActive;
+ this._setDefaultRouteAndProxy(this._activeNetwork, oldActive);
+ Services.obs.notifyObservers(this.activeNetworkInfo,
+ TOPIC_ACTIVE_CHANGED, null);
+ }
+ return;
+ }
+
+ // The active network is already our preferred type.
+ if (this.activeNetworkInfo &&
+ this.activeNetworkInfo.state == Ci.nsINetworkInfo.NETWORK_STATE_CONNECTED &&
+ this.activeNetworkInfo.type == this._preferredNetworkType) {
+ debug("Active network is already our preferred type.");
+ return this._setDefaultRouteAndProxy(this._activeNetwork, oldActive);
+ }
+
+ // Find a suitable network interface to activate.
+ this._activeNetwork = null;
+ let anyConnected = false;
+
+ for (let key in this.networkInterfaces) {
+ let network = this.networkInterfaces[key];
+ if (network.info.state != Ci.nsINetworkInfo.NETWORK_STATE_CONNECTED) {
+ continue;
+ }
+ anyConnected = true;
+
+ // Set active only for default connections.
+ if (network.info.type != Ci.nsINetworkInfo.NETWORK_TYPE_WIFI &&
+ network.info.type != Ci.nsINetworkInfo.NETWORK_TYPE_MOBILE &&
+ network.info.type != Ci.nsINetworkInfo.NETWORK_TYPE_ETHERNET) {
+ continue;
+ }
+
+ if (network.info.type == this.preferredNetworkType) {
+ this._activeNetwork = network;
+ debug("Found our preferred type of network: " + network.info.name);
+ break;
+ }
+
+ // Initialize the active network with the first connected network.
+ if (!this._activeNetwork) {
+ this._activeNetwork = network;
+ continue;
+ }
+
+ // Compare the prioriy between two network types. If found incoming
+ // network with higher priority, replace the active network.
+ if (this.getPriority(this._activeNetwork.type) < this.getPriority(network.type)) {
+ this._activeNetwork = network;
+ }
+ }
+
+ return Promise.resolve()
+ .then(() => {
+ if (!this._activeNetwork) {
+ return Promise.resolve();
+ }
+
+ return this._setDefaultRouteAndProxy(this._activeNetwork, oldActive);
+ })
+ .then(() => {
+ if (this._activeNetwork != oldActive) {
+ Services.obs.notifyObservers(this.activeNetworkInfo,
+ TOPIC_ACTIVE_CHANGED, null);
+ }
+
+ if (this._manageOfflineStatus) {
+ Services.io.offline = !anyConnected &&
+ (gTetheringService.state ===
+ Ci.nsITetheringService.TETHERING_STATE_INACTIVE);
+ }
+ });
+ },
+
+ resolveHostname: function(aNetworkInfo, aHostname) {
+ // Sanity check for null, undefined and empty string... etc.
+ if (!aHostname) {
+ return Promise.reject(new Error("hostname is empty: " + aHostname));
+ }
+
+ if (aHostname.match(this.REGEXP_IPV4) ||
+ aHostname.match(this.REGEXP_IPV6)) {
+ return Promise.resolve([aHostname]);
+ }
+
+ // Wrap gDNSService.asyncResolveExtended to a promise, which
+ // resolves with an array of ip addresses or rejects with
+ // the reason otherwise.
+ let hostResolveWrapper = aNetId => {
+ return new Promise((aResolve, aReject) => {
+ // Callback for gDNSService.asyncResolveExtended.
+ let onLookupComplete = (aRequest, aRecord, aStatus) => {
+ if (!Components.isSuccessCode(aStatus)) {
+ aReject(new Error("Failed to resolve '" + aHostname +
+ "', with status: " + aStatus));
+ return;
+ }
+
+ let retval = [];
+ while (aRecord.hasMore()) {
+ retval.push(aRecord.getNextAddrAsString());
+ }
+
+ if (!retval.length) {
+ aReject(new Error("No valid address after DNS lookup!"));
+ return;
+ }
+
+ debug("hostname is resolved: " + aHostname);
+ debug("Addresses: " + JSON.stringify(retval));
+
+ aResolve(retval);
+ };
+
+ debug('Calling gDNSService.asyncResolveExtended: ' + aNetId + ', ' + aHostname);
+ gDNSService.asyncResolveExtended(aHostname,
+ 0,
+ aNetId,
+ onLookupComplete,
+ Services.tm.mainThread);
+ });
+ };
+
+ // TODO: |getNetId| will be implemented as a sync call in nsINetworkManager
+ // once Bug 1141903 is landed.
+ return gNetworkService.getNetId(aNetworkInfo.name)
+ .then(aNetId => hostResolveWrapper(aNetId));
+ },
+
+ convertConnectionType: function(aNetworkInfo) {
+ // If there is internal interface change (e.g., MOBILE_MMS, MOBILE_SUPL),
+ // the function will return null so that it won't trigger type change event
+ // in NetworkInformation API.
+ if (aNetworkInfo.type != Ci.nsINetworkInterface.NETWORK_TYPE_WIFI &&
+ aNetworkInfo.type != Ci.nsINetworkInterface.NETWORK_TYPE_MOBILE &&
+ aNetworkInfo.type != Ci.nsINetworkInterface.NETWORK_TYPE_ETHERNET) {
+ return null;
+ }
+
+ if (aNetworkInfo.state == Ci.nsINetworkInfo.NETWORK_STATE_DISCONNECTED) {
+ return CONNECTION_TYPE_NONE;
+ }
+
+ switch (aNetworkInfo.type) {
+ case Ci.nsINetworkInfo.NETWORK_TYPE_WIFI:
+ return CONNECTION_TYPE_WIFI;
+ case Ci.nsINetworkInfo.NETWORK_TYPE_MOBILE:
+ return CONNECTION_TYPE_CELLULAR;
+ case Ci.nsINetworkInterface.NETWORK_TYPE_ETHERNET:
+ return CONNECTION_TYPE_ETHERNET;
+ }
+ },
+
+ _setDNS: function(aNetworkInfo) {
+ return new Promise((aResolve, aReject) => {
+ let dnses = aNetworkInfo.getDnses();
+ let gateways = aNetworkInfo.getGateways();
+ gNetworkService.setDNS(aNetworkInfo.name, dnses.length, dnses,
+ gateways.length, gateways, (aError) => {
+ if (aError) {
+ aReject("setDNS failed");
+ return;
+ }
+ aResolve();
+ });
+ });
+ },
+
+ _setMtu: function(aNetworkInfo) {
+ return new Promise((aResolve, aReject) => {
+ gNetworkService.setMtu(aNetworkInfo.name, aNetworkInfo.mtu, (aSuccess) => {
+ if (!aSuccess) {
+ debug("setMtu failed");
+ }
+ // Always resolve.
+ aResolve();
+ });
+ });
+ },
+
+ _createNetwork: function(aInterfaceName) {
+ return new Promise((aResolve, aReject) => {
+ gNetworkService.createNetwork(aInterfaceName, (aSuccess) => {
+ if (!aSuccess) {
+ aReject("createNetwork failed");
+ return;
+ }
+ aResolve();
+ });
+ });
+ },
+
+ _destroyNetwork: function(aInterfaceName) {
+ return new Promise((aResolve, aReject) => {
+ gNetworkService.destroyNetwork(aInterfaceName, (aSuccess) => {
+ if (!aSuccess) {
+ debug("destroyNetwork failed")
+ }
+ // Always resolve.
+ aResolve();
+ });
+ });
+ },
+
+ _resetRoutingTable: function(aInterfaceName) {
+ return new Promise((aResolve, aReject) => {
+ gNetworkService.resetRoutingTable(aInterfaceName, (aSuccess) => {
+ if (!aSuccess) {
+ debug("resetRoutingTable failed");
+ }
+ // Always resolve.
+ aResolve();
+ });
+ });
+ },
+
+ _removeDefaultRoute: function(aNetworkInfo) {
+ return new Promise((aResolve, aReject) => {
+ let gateways = aNetworkInfo.getGateways();
+ gNetworkService.removeDefaultRoute(aNetworkInfo.name, gateways.length,
+ gateways, (aSuccess) => {
+ if (!aSuccess) {
+ debug("removeDefaultRoute failed");
+ }
+ // Always resolve.
+ aResolve();
+ });
+ });
+ },
+
+ _setDefaultRouteAndProxy: function(aNetwork, aOldNetwork) {
+ if (aOldNetwork) {
+ return this._removeDefaultRoute(aOldNetwork.info)
+ .then(() => this._setDefaultRouteAndProxy(aNetwork, null));
+ }
+
+ return new Promise((aResolve, aReject) => {
+ let networkInfo = aNetwork.info;
+ let gateways = networkInfo.getGateways();
+
+ gNetworkService.setDefaultRoute(networkInfo.name, gateways.length, gateways,
+ (aSuccess) => {
+ if (!aSuccess) {
+ gNetworkService.destroyNetwork(networkInfo.name, function() {
+ aReject("setDefaultRoute failed");
+ });
+ return;
+ }
+ this.setNetworkProxy(aNetwork);
+ aResolve();
+ });
+ });
+ },
+
+ setNetworkProxy: function(aNetwork) {
+ try {
+ if (!aNetwork.httpProxyHost || aNetwork.httpProxyHost === "") {
+ // Sets direct connection to internet.
+ this.clearNetworkProxy();
+
+ debug("No proxy support for " + aNetwork.info.name + " network interface.");
+ return;
+ }
+
+ debug("Going to set proxy settings for " + aNetwork.info.name + " network interface.");
+ // Sets manual proxy configuration.
+ Services.prefs.setIntPref("network.proxy.type", MANUAL_PROXY_CONFIGURATION);
+
+ // Do not use this proxy server for all protocols.
+ Services.prefs.setBoolPref("network.proxy.share_proxy_settings", false);
+ Services.prefs.setCharPref("network.proxy.http", aNetwork.httpProxyHost);
+ Services.prefs.setCharPref("network.proxy.ssl", aNetwork.httpProxyHost);
+ let port = aNetwork.httpProxyPort === 0 ? 8080 : aNetwork.httpProxyPort;
+ Services.prefs.setIntPref("network.proxy.http_port", port);
+ Services.prefs.setIntPref("network.proxy.ssl_port", port);
+ } catch(ex) {
+ debug("Exception " + ex + ". Unable to set proxy setting for " +
+ aNetwork.info.name + " network interface.");
+ }
+ },
+
+ clearNetworkProxy: function() {
+ debug("Going to clear all network proxy.");
+
+ Services.prefs.clearUserPref("network.proxy.type");
+ Services.prefs.clearUserPref("network.proxy.share_proxy_settings");
+ Services.prefs.clearUserPref("network.proxy.http");
+ Services.prefs.clearUserPref("network.proxy.http_port");
+ Services.prefs.clearUserPref("network.proxy.ssl");
+ Services.prefs.clearUserPref("network.proxy.ssl_port");
+ },
+};
+
+var CaptivePortalDetectionHelper = (function() {
+
+ const EVENT_CONNECT = "Connect";
+ const EVENT_DISCONNECT = "Disconnect";
+ let _ongoingInterface = null;
+ let _available = ("nsICaptivePortalDetector" in Ci);
+ let getService = function() {
+ return Cc['@mozilla.org/toolkit/captive-detector;1']
+ .getService(Ci.nsICaptivePortalDetector);
+ };
+
+ let _performDetection = function(interfaceName, callback) {
+ let capService = getService();
+ let capCallback = {
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsICaptivePortalCallback]),
+ prepare: function() {
+ capService.finishPreparation(interfaceName);
+ },
+ complete: function(success) {
+ _ongoingInterface = null;
+ callback(success);
+ }
+ };
+
+ // Abort any unfinished captive portal detection.
+ if (_ongoingInterface != null) {
+ capService.abort(_ongoingInterface);
+ _ongoingInterface = null;
+ }
+ try {
+ capService.checkCaptivePortal(interfaceName, capCallback);
+ _ongoingInterface = interfaceName;
+ } catch (e) {
+ debug('Fail to detect captive portal due to: ' + e.message);
+ }
+ };
+
+ let _abort = function(interfaceName) {
+ if (_ongoingInterface !== interfaceName) {
+ return;
+ }
+
+ let capService = getService();
+ capService.abort(_ongoingInterface);
+ _ongoingInterface = null;
+ };
+
+ return {
+ EVENT_CONNECT: EVENT_CONNECT,
+ EVENT_DISCONNECT: EVENT_DISCONNECT,
+ notify: function(eventType, network) {
+ switch (eventType) {
+ case EVENT_CONNECT:
+ // perform captive portal detection on wifi interface
+ if (_available && network &&
+ network.type == Ci.nsINetworkInfo.NETWORK_TYPE_WIFI) {
+ _performDetection(network.name, function() {
+ // TODO: bug 837600
+ // We can disconnect wifi in here if user abort the login procedure.
+ });
+ }
+
+ break;
+ case EVENT_DISCONNECT:
+ if (_available &&
+ network.type == Ci.nsINetworkInfo.NETWORK_TYPE_WIFI) {
+ _abort(network.name);
+ }
+ break;
+ }
+ }
+ };
+}());
+
+XPCOMUtils.defineLazyGetter(NetworkManager.prototype, "mRil", function() {
+ try {
+ return Cc["@mozilla.org/ril;1"].getService(Ci.nsIRadioInterfaceLayer);
+ } catch (e) {}
+
+ return null;
+});
+
+this.NSGetFactory = XPCOMUtils.generateNSGetFactory([NetworkManager]);
diff --git a/dom/system/gonk/NetworkManager.manifest b/dom/system/gonk/NetworkManager.manifest
new file mode 100644
index 000000000..995fa6559
--- /dev/null
+++ b/dom/system/gonk/NetworkManager.manifest
@@ -0,0 +1,3 @@
+# NetworkManager.js
+component {1ba9346b-53b5-4660-9dc6-58f0b258d0a6} NetworkManager.js
+contract @mozilla.org/network/manager;1 {1ba9346b-53b5-4660-9dc6-58f0b258d0a6}
diff --git a/dom/system/gonk/NetworkService.js b/dom/system/gonk/NetworkService.js
new file mode 100644
index 000000000..7147f40c7
--- /dev/null
+++ b/dom/system/gonk/NetworkService.js
@@ -0,0 +1,862 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+Cu.import("resource://gre/modules/FileUtils.jsm");
+Cu.import("resource://gre/modules/Promise.jsm");
+
+const NETWORKSERVICE_CONTRACTID = "@mozilla.org/network/service;1";
+const NETWORKSERVICE_CID = Components.ID("{48c13741-aec9-4a86-8962-432011708261}");
+
+const TOPIC_PREF_CHANGED = "nsPref:changed";
+const TOPIC_XPCOM_SHUTDOWN = "xpcom-shutdown";
+const PREF_NETWORK_DEBUG_ENABLED = "network.debugging.enabled";
+
+XPCOMUtils.defineLazyServiceGetter(this, "gNetworkWorker",
+ "@mozilla.org/network/worker;1",
+ "nsINetworkWorker");
+
+// 1xx - Requested action is proceeding
+const NETD_COMMAND_PROCEEDING = 100;
+// 2xx - Requested action has been successfully completed
+const NETD_COMMAND_OKAY = 200;
+// 4xx - The command is accepted but the requested action didn't
+// take place.
+const NETD_COMMAND_FAIL = 400;
+// 5xx - The command syntax or parameters error
+const NETD_COMMAND_ERROR = 500;
+// 6xx - Unsolicited broadcasts
+const NETD_COMMAND_UNSOLICITED = 600;
+
+const WIFI_CTRL_INTERFACE = "wl0.1";
+
+var debug;
+function updateDebug() {
+ let debugPref = false; // set default value here.
+ try {
+ debugPref = debugPref || Services.prefs.getBoolPref(PREF_NETWORK_DEBUG_ENABLED);
+ } catch (e) {}
+
+ if (debugPref) {
+ debug = function(s) {
+ dump("-*- NetworkService: " + s + "\n");
+ };
+ } else {
+ debug = function(s) {};
+ }
+}
+updateDebug();
+
+function netdResponseType(aCode) {
+ return Math.floor(aCode / 100) * 100;
+}
+
+function isError(aCode) {
+ let type = netdResponseType(aCode);
+ return (type !== NETD_COMMAND_PROCEEDING && type !== NETD_COMMAND_OKAY);
+}
+
+function Task(aId, aParams, aSetupFunction) {
+ this.id = aId;
+ this.params = aParams;
+ this.setupFunction = aSetupFunction;
+}
+
+function NetworkWorkerRequestQueue(aNetworkService) {
+ this.networkService = aNetworkService;
+ this.tasks = [];
+}
+NetworkWorkerRequestQueue.prototype = {
+ runQueue: function() {
+ if (this.tasks.length === 0) {
+ return;
+ }
+
+ let task = this.tasks[0];
+ debug("run task id: " + task.id);
+
+ if (typeof task.setupFunction === 'function') {
+ // If setupFunction returns false, skip sending to Network Worker but call
+ // handleWorkerMessage() directly with task id, as if the response was
+ // returned from Network Worker.
+ if (!task.setupFunction()) {
+ this.networkService.handleWorkerMessage({id: task.id});
+ return;
+ }
+ }
+
+ gNetworkWorker.postMessage(task.params);
+ },
+
+ enqueue: function(aId, aParams, aSetupFunction) {
+ debug("enqueue id: " + aId);
+ this.tasks.push(new Task(aId, aParams, aSetupFunction));
+
+ if (this.tasks.length === 1) {
+ this.runQueue();
+ }
+ },
+
+ dequeue: function(aId) {
+ debug("dequeue id: " + aId);
+
+ if (!this.tasks.length || this.tasks[0].id != aId) {
+ debug("Id " + aId + " is not on top of the queue");
+ return;
+ }
+
+ this.tasks.shift();
+ if (this.tasks.length > 0) {
+ // Run queue on the next tick.
+ Services.tm.currentThread.dispatch(() => {
+ this.runQueue();
+ }, Ci.nsIThread.DISPATCH_NORMAL);
+ }
+ }
+};
+
+
+/**
+ * This component watches for network interfaces changing state and then
+ * adjusts routes etc. accordingly.
+ */
+function NetworkService() {
+ debug("Starting NetworkService.");
+
+ let self = this;
+
+ if (gNetworkWorker) {
+ let networkListener = {
+ onEvent: function(aEvent) {
+ self.handleWorkerMessage(aEvent);
+ }
+ };
+ gNetworkWorker.start(networkListener);
+ }
+ // Callbacks to invoke when a reply arrives from the net_worker.
+ this.controlCallbacks = Object.create(null);
+
+ this.addedRoutes = new Map();
+ this.netWorkerRequestQueue = new NetworkWorkerRequestQueue(this);
+ this.shutdown = false;
+
+ Services.prefs.addObserver(PREF_NETWORK_DEBUG_ENABLED, this, false);
+ Services.obs.addObserver(this, TOPIC_XPCOM_SHUTDOWN, false);
+}
+
+NetworkService.prototype = {
+ classID: NETWORKSERVICE_CID,
+ classInfo: XPCOMUtils.generateCI({classID: NETWORKSERVICE_CID,
+ contractID: NETWORKSERVICE_CONTRACTID,
+ classDescription: "Network Service",
+ interfaces: [Ci.nsINetworkService]}),
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsINetworkService,
+ Ci.nsIObserver]),
+
+ addedRoutes: null,
+
+ shutdown: false,
+
+ // nsIObserver
+
+ observe: function(aSubject, aTopic, aData) {
+ switch (aTopic) {
+ case TOPIC_PREF_CHANGED:
+ if (aData === PREF_NETWORK_DEBUG_ENABLED) {
+ updateDebug();
+ }
+ break;
+ case TOPIC_XPCOM_SHUTDOWN:
+ debug("NetworkService shutdown");
+ this.shutdown = true;
+ if (gNetworkWorker) {
+ gNetworkWorker.shutdown();
+ gNetworkWorker = null;
+ }
+
+ Services.obs.removeObserver(this, TOPIC_XPCOM_SHUTDOWN);
+ Services.prefs.removeObserver(PREF_NETWORK_DEBUG_ENABLED, this);
+ break;
+ }
+ },
+
+ // Helpers
+
+ idgen: 0,
+ controlMessage: function(aParams, aCallback, aSetupFunction) {
+ if (this.shutdown) {
+ return;
+ }
+
+ let id = this.idgen++;
+ aParams.id = id;
+ if (aCallback) {
+ this.controlCallbacks[id] = aCallback;
+ }
+
+ // For now, we use aSetupFunction to determine if this command needs to be
+ // queued or not.
+ if (aSetupFunction) {
+ this.netWorkerRequestQueue.enqueue(id, aParams, aSetupFunction);
+ return;
+ }
+
+ if (gNetworkWorker) {
+ gNetworkWorker.postMessage(aParams);
+ }
+ },
+
+ handleWorkerMessage: function(aResponse) {
+ debug("NetworkManager received message from worker: " + JSON.stringify(aResponse));
+ let id = aResponse.id;
+ if (aResponse.broadcast === true) {
+ Services.obs.notifyObservers(null, aResponse.topic, aResponse.reason);
+ return;
+ }
+ let callback = this.controlCallbacks[id];
+ if (callback) {
+ callback.call(this, aResponse);
+ delete this.controlCallbacks[id];
+ }
+
+ this.netWorkerRequestQueue.dequeue(id);
+ },
+
+ // nsINetworkService
+
+ getNetworkInterfaceStats: function(aInterfaceName, aCallback) {
+ debug("getNetworkInterfaceStats for " + aInterfaceName);
+
+ let file = new FileUtils.File("/proc/net/dev");
+ if (!file) {
+ aCallback.networkStatsAvailable(false, 0, 0, Date.now());
+ return;
+ }
+
+ NetUtil.asyncFetch({
+ uri: NetUtil.newURI(file),
+ loadUsingSystemPrincipal: true
+ }, function(inputStream, status) {
+ let rxBytes = 0,
+ txBytes = 0,
+ now = Date.now();
+
+ if (Components.isSuccessCode(status)) {
+ // Find record for corresponding interface.
+ let statExpr = /(\S+): +(\d+) +\d+ +\d+ +\d+ +\d+ +\d+ +\d+ +\d+ +(\d+) +\d+ +\d+ +\d+ +\d+ +\d+ +\d+ +\d+/;
+ let data =
+ NetUtil.readInputStreamToString(inputStream, inputStream.available())
+ .split("\n");
+ for (let i = 2; i < data.length; i++) {
+ let parseResult = statExpr.exec(data[i]);
+ if (parseResult && parseResult[1] === aInterfaceName) {
+ rxBytes = parseInt(parseResult[2], 10);
+ txBytes = parseInt(parseResult[3], 10);
+ break;
+ }
+ }
+ }
+
+ // netd always return success even interface doesn't exist.
+ aCallback.networkStatsAvailable(true, rxBytes, txBytes, now);
+ });
+ },
+
+ setNetworkTetheringAlarm(aEnable, aInterface) {
+ // Method called when enabling disabling tethering, it checks if there is
+ // some alarm active and move from interfaceAlarm to globalAlarm because
+ // interfaceAlarm doens't work in tethering scenario due to forwarding.
+ debug("setNetworkTetheringAlarm for tethering" + aEnable);
+
+ let filename = aEnable ? "/proc/net/xt_quota/" + aInterface + "Alert" :
+ "/proc/net/xt_quota/globalAlert";
+
+ let file = new FileUtils.File(filename);
+ if (!file) {
+ return;
+ }
+
+ NetUtil.asyncFetch({
+ uri: NetUtil.newURI(file),
+ loadUsingSystemPrincipal: true
+ }, (inputStream, status) => {
+ if (Components.isSuccessCode(status)) {
+ let data = NetUtil.readInputStreamToString(inputStream, inputStream.available())
+ .split("\n");
+ if (data) {
+ let threshold = parseInt(data[0], 10);
+
+ this._setNetworkTetheringAlarm(aEnable, aInterface, threshold);
+ }
+ }
+ });
+ },
+
+ _setNetworkTetheringAlarm(aEnable, aInterface, aThreshold, aCallback) {
+ debug("_setNetworkTetheringAlarm for tethering" + aEnable);
+
+ let cmd = aEnable ? "setTetheringAlarm" : "removeTetheringAlarm";
+
+ let params = {
+ cmd: cmd,
+ ifname: aInterface,
+ threshold: aThreshold,
+ };
+
+ this.controlMessage(params, function(aData) {
+ let code = aData.resultCode;
+ let reason = aData.resultReason;
+ let enableString = aEnable ? "Enable" : "Disable";
+ debug(enableString + " tethering Alarm result: Code " + code + " reason " + reason);
+ if (aCallback) {
+ aCallback.networkUsageAlarmResult(null);
+ }
+ });
+ },
+
+ setNetworkInterfaceAlarm: function(aInterfaceName, aThreshold, aCallback) {
+ if (!aInterfaceName) {
+ aCallback.networkUsageAlarmResult(-1);
+ return;
+ }
+
+ let self = this;
+ this._disableNetworkInterfaceAlarm(aInterfaceName, function(aResult) {
+ if (aThreshold < 0) {
+ if (!isError(aResult.resultCode)) {
+ aCallback.networkUsageAlarmResult(null);
+ return;
+ }
+ aCallback.networkUsageAlarmResult(aResult.reason);
+ return
+ }
+
+ // Check if tethering is enabled
+ let params = {
+ cmd: "getTetheringStatus"
+ };
+
+ self.controlMessage(params, function(aResult) {
+ if (isError(aResult.resultCode)) {
+ aCallback.networkUsageAlarmResult(aResult.reason);
+ return;
+ }
+
+ if (aResult.resultReason.indexOf('started') == -1) {
+ // Tethering disabled, set interfaceAlarm
+ self._setNetworkInterfaceAlarm(aInterfaceName, aThreshold, aCallback);
+ return;
+ }
+
+ // Tethering enabled, set globalAlarm
+ self._setNetworkTetheringAlarm(true, aInterfaceName, aThreshold, aCallback);
+ });
+ });
+ },
+
+ _setNetworkInterfaceAlarm: function(aInterfaceName, aThreshold, aCallback) {
+ debug("setNetworkInterfaceAlarm for " + aInterfaceName + " at " + aThreshold + "bytes");
+
+ let params = {
+ cmd: "setNetworkInterfaceAlarm",
+ ifname: aInterfaceName,
+ threshold: aThreshold
+ };
+
+ params.report = true;
+
+ this.controlMessage(params, function(aResult) {
+ if (!isError(aResult.resultCode)) {
+ aCallback.networkUsageAlarmResult(null);
+ return;
+ }
+
+ this._enableNetworkInterfaceAlarm(aInterfaceName, aThreshold, aCallback);
+ });
+ },
+
+ _enableNetworkInterfaceAlarm: function(aInterfaceName, aThreshold, aCallback) {
+ debug("enableNetworkInterfaceAlarm for " + aInterfaceName + " at " + aThreshold + "bytes");
+
+ let params = {
+ cmd: "enableNetworkInterfaceAlarm",
+ ifname: aInterfaceName,
+ threshold: aThreshold
+ };
+
+ params.report = true;
+
+ this.controlMessage(params, function(aResult) {
+ if (!isError(aResult.resultCode)) {
+ aCallback.networkUsageAlarmResult(null);
+ return;
+ }
+ aCallback.networkUsageAlarmResult(aResult.reason);
+ });
+ },
+
+ _disableNetworkInterfaceAlarm: function(aInterfaceName, aCallback) {
+ debug("disableNetworkInterfaceAlarm for " + aInterfaceName);
+
+ let params = {
+ cmd: "disableNetworkInterfaceAlarm",
+ ifname: aInterfaceName,
+ };
+
+ params.report = true;
+
+ this.controlMessage(params, function(aResult) {
+ aCallback(aResult);
+ });
+ },
+
+ setWifiOperationMode: function(aInterfaceName, aMode, aCallback) {
+ debug("setWifiOperationMode on " + aInterfaceName + " to " + aMode);
+
+ let params = {
+ cmd: "setWifiOperationMode",
+ ifname: aInterfaceName,
+ mode: aMode
+ };
+
+ params.report = true;
+
+ this.controlMessage(params, function(aResult) {
+ if (isError(aResult.resultCode)) {
+ aCallback.wifiOperationModeResult("netd command error");
+ } else {
+ aCallback.wifiOperationModeResult(null);
+ }
+ });
+ },
+
+ resetRoutingTable: function(aInterfaceName, aCallback) {
+ let options = {
+ cmd: "removeNetworkRoute",
+ ifname: aInterfaceName
+ };
+
+ this.controlMessage(options, function(aResult) {
+ aCallback.nativeCommandResult(!aResult.error);
+ });
+ },
+
+ setDNS: function(aInterfaceName, aDnsesCount, aDnses, aGatewaysCount,
+ aGateways, aCallback) {
+ debug("Going to set DNS to " + aInterfaceName);
+ let options = {
+ cmd: "setDNS",
+ ifname: aInterfaceName,
+ domain: "mozilla." + aInterfaceName + ".domain",
+ dnses: aDnses,
+ gateways: aGateways
+ };
+ this.controlMessage(options, function(aResult) {
+ aCallback.setDnsResult(aResult.success ? null : aResult.reason);
+ });
+ },
+
+ setDefaultRoute: function(aInterfaceName, aCount, aGateways, aCallback) {
+ debug("Going to change default route to " + aInterfaceName);
+ let options = {
+ cmd: "setDefaultRoute",
+ ifname: aInterfaceName,
+ gateways: aGateways
+ };
+ this.controlMessage(options, function(aResult) {
+ aCallback.nativeCommandResult(!aResult.error);
+ });
+ },
+
+ removeDefaultRoute: function(aInterfaceName, aCount, aGateways, aCallback) {
+ debug("Remove default route for " + aInterfaceName);
+ let options = {
+ cmd: "removeDefaultRoute",
+ ifname: aInterfaceName,
+ gateways: aGateways
+ };
+ this.controlMessage(options, function(aResult) {
+ aCallback.nativeCommandResult(!aResult.error);
+ });
+ },
+
+ _routeToString: function(aInterfaceName, aHost, aPrefixLength, aGateway) {
+ return aHost + "-" + aPrefixLength + "-" + aGateway + "-" + aInterfaceName;
+ },
+
+ modifyRoute: function(aAction, aInterfaceName, aHost, aPrefixLength, aGateway) {
+ let command;
+
+ switch (aAction) {
+ case Ci.nsINetworkService.MODIFY_ROUTE_ADD:
+ command = 'addHostRoute';
+ break;
+ case Ci.nsINetworkService.MODIFY_ROUTE_REMOVE:
+ command = 'removeHostRoute';
+ break;
+ default:
+ debug('Unknown action: ' + aAction);
+ return Promise.reject();
+ }
+
+ let route = this._routeToString(aInterfaceName, aHost, aPrefixLength, aGateway);
+ let setupFunc = () => {
+ let count = this.addedRoutes.get(route);
+ debug(command + ": " + route + " -> " + count);
+
+ // Return false if there is no need to send the command to network worker.
+ if ((aAction == Ci.nsINetworkService.MODIFY_ROUTE_ADD && count) ||
+ (aAction == Ci.nsINetworkService.MODIFY_ROUTE_REMOVE &&
+ (!count || count > 1))) {
+ return false;
+ }
+
+ return true;
+ };
+
+ debug(command + " " + aHost + " on " + aInterfaceName);
+ let options = {
+ cmd: command,
+ ifname: aInterfaceName,
+ gateway: aGateway,
+ prefixLength: aPrefixLength,
+ ip: aHost
+ };
+
+ return new Promise((aResolve, aReject) => {
+ this.controlMessage(options, (aData) => {
+ let count = this.addedRoutes.get(route);
+
+ // Remove route from addedRoutes on success or failure.
+ if (aAction == Ci.nsINetworkService.MODIFY_ROUTE_REMOVE) {
+ if (count > 1) {
+ this.addedRoutes.set(route, count - 1);
+ } else {
+ this.addedRoutes.delete(route);
+ }
+ }
+
+ if (aData.error) {
+ aReject(aData.reason);
+ return;
+ }
+
+ if (aAction == Ci.nsINetworkService.MODIFY_ROUTE_ADD) {
+ this.addedRoutes.set(route, count ? count + 1 : 1);
+ }
+
+ aResolve();
+ }, setupFunc);
+ });
+ },
+
+ addSecondaryRoute: function(aInterfaceName, aRoute, aCallback) {
+ debug("Going to add route to secondary table on " + aInterfaceName);
+ let options = {
+ cmd: "addSecondaryRoute",
+ ifname: aInterfaceName,
+ ip: aRoute.ip,
+ prefix: aRoute.prefix,
+ gateway: aRoute.gateway
+ };
+ this.controlMessage(options, function(aResult) {
+ aCallback.nativeCommandResult(!aResult.error);
+ });
+ },
+
+ removeSecondaryRoute: function(aInterfaceName, aRoute, aCallback) {
+ debug("Going to remove route from secondary table on " + aInterfaceName);
+ let options = {
+ cmd: "removeSecondaryRoute",
+ ifname: aInterfaceName,
+ ip: aRoute.ip,
+ prefix: aRoute.prefix,
+ gateway: aRoute.gateway
+ };
+ this.controlMessage(options, function(aResult) {
+ aCallback.nativeCommandResult(!aResult.error);
+ });
+ },
+
+ // Enable/Disable DHCP server.
+ setDhcpServer: function(aEnabled, aConfig, aCallback) {
+ if (null === aConfig) {
+ aConfig = {};
+ }
+
+ aConfig.cmd = "setDhcpServer";
+ aConfig.enabled = aEnabled;
+
+ this.controlMessage(aConfig, function(aResponse) {
+ if (!aResponse.success) {
+ aCallback.dhcpServerResult('Set DHCP server error');
+ return;
+ }
+ aCallback.dhcpServerResult(null);
+ });
+ },
+
+ // Enable/disable WiFi tethering by sending commands to netd.
+ setWifiTethering: function(aEnable, aConfig, aCallback) {
+ // config should've already contained:
+ // .ifname
+ // .internalIfname
+ // .externalIfname
+ aConfig.wifictrlinterfacename = WIFI_CTRL_INTERFACE;
+ aConfig.cmd = "setWifiTethering";
+
+ // The callback function in controlMessage may not be fired immediately.
+ this.controlMessage(aConfig, (aData) => {
+ let code = aData.resultCode;
+ let reason = aData.resultReason;
+ let enable = aData.enable;
+ let enableString = aEnable ? "Enable" : "Disable";
+
+ debug(enableString + " Wifi tethering result: Code " + code + " reason " + reason);
+
+ this.setNetworkTetheringAlarm(aEnable, aConfig.externalIfname);
+
+ if (isError(code)) {
+ aCallback.wifiTetheringEnabledChange("netd command error");
+ } else {
+ aCallback.wifiTetheringEnabledChange(null);
+ }
+ });
+ },
+
+ // Enable/disable USB tethering by sending commands to netd.
+ setUSBTethering: function(aEnable, aConfig, aCallback) {
+ aConfig.cmd = "setUSBTethering";
+ // The callback function in controlMessage may not be fired immediately.
+ this.controlMessage(aConfig, (aData) => {
+ let code = aData.resultCode;
+ let reason = aData.resultReason;
+ let enable = aData.enable;
+ let enableString = aEnable ? "Enable" : "Disable";
+
+ debug(enableString + " USB tethering result: Code " + code + " reason " + reason);
+
+ this.setNetworkTetheringAlarm(aEnable, aConfig.externalIfname);
+
+ if (isError(code)) {
+ aCallback.usbTetheringEnabledChange("netd command error");
+ } else {
+ aCallback.usbTetheringEnabledChange(null);
+ }
+ });
+ },
+
+ // Switch usb function by modifying property of persist.sys.usb.config.
+ enableUsbRndis: function(aEnable, aCallback) {
+ debug("enableUsbRndis: " + aEnable);
+
+ let params = {
+ cmd: "enableUsbRndis",
+ enable: aEnable
+ };
+ // Ask net work to report the result when this value is set to true.
+ if (aCallback) {
+ params.report = true;
+ } else {
+ params.report = false;
+ }
+
+ // The callback function in controlMessage may not be fired immediately.
+ //this._usbTetheringAction = TETHERING_STATE_ONGOING;
+ this.controlMessage(params, function(aData) {
+ aCallback.enableUsbRndisResult(aData.result, aData.enable);
+ });
+ },
+
+ updateUpStream: function(aPrevious, aCurrent, aCallback) {
+ let params = {
+ cmd: "updateUpStream",
+ preInternalIfname: aPrevious.internalIfname,
+ preExternalIfname: aPrevious.externalIfname,
+ curInternalIfname: aCurrent.internalIfname,
+ curExternalIfname: aCurrent.externalIfname
+ };
+
+ this.controlMessage(params, function(aData) {
+ let code = aData.resultCode;
+ let reason = aData.resultReason;
+ debug("updateUpStream result: Code " + code + " reason " + reason);
+ aCallback.updateUpStreamResult(!isError(code), aData.curExternalIfname);
+ });
+ },
+
+ getInterfaces: function(callback) {
+ let params = {
+ cmd: "getInterfaces",
+ isAsync: true
+ };
+
+ this.controlMessage(params, function(data) {
+ debug("getInterfaces result: " + JSON.stringify(data));
+ let success = !isError(data.resultCode);
+ callback.getInterfacesResult(success, data.interfaceList);
+ });
+ },
+
+ getInterfaceConfig: function(ifname, callback) {
+ let params = {
+ cmd: "getInterfaceConfig",
+ ifname: ifname,
+ isAsync: true
+ };
+
+ this.controlMessage(params, function(data) {
+ debug("getInterfaceConfig result: " + JSON.stringify(data));
+ let success = !isError(data.resultCode);
+ let result = { ip: data.ipAddr,
+ prefix: data.prefixLength,
+ link: data.flag,
+ mac: data.macAddr };
+ callback.getInterfaceConfigResult(success, result);
+ });
+ },
+
+ setInterfaceConfig: function(config, callback) {
+ config.cmd = "setInterfaceConfig";
+ config.isAsync = true;
+
+ this.controlMessage(config, function(data) {
+ debug("setInterfaceConfig result: " + JSON.stringify(data));
+ let success = !isError(data.resultCode);
+ callback.setInterfaceConfigResult(success);
+ });
+ },
+
+ configureInterface: function(aConfig, aCallback) {
+ let params = {
+ cmd: "configureInterface",
+ ifname: aConfig.ifname,
+ ipaddr: aConfig.ipaddr,
+ mask: aConfig.mask,
+ gateway_long: aConfig.gateway,
+ dns1_long: aConfig.dns1,
+ dns2_long: aConfig.dns2,
+ };
+
+ this.controlMessage(params, function(aResult) {
+ aCallback.nativeCommandResult(!aResult.error);
+ });
+ },
+
+ dhcpRequest: function(aInterfaceName, aCallback) {
+ let params = {
+ cmd: "dhcpRequest",
+ ifname: aInterfaceName
+ };
+
+ this.controlMessage(params, function(aResult) {
+ aCallback.dhcpRequestResult(!aResult.error, aResult.error ? null : aResult);
+ });
+ },
+
+ stopDhcp: function(aInterfaceName, aCallback) {
+ let params = {
+ cmd: "stopDhcp",
+ ifname: aInterfaceName
+ };
+
+ this.controlMessage(params, function(aResult) {
+ aCallback.nativeCommandResult(!aResult.error);
+ });
+ },
+
+ enableInterface: function(aInterfaceName, aCallback) {
+ let params = {
+ cmd: "enableInterface",
+ ifname: aInterfaceName
+ };
+
+ this.controlMessage(params, function(aResult) {
+ aCallback.nativeCommandResult(!aResult.error);
+ });
+ },
+
+ disableInterface: function(aInterfaceName, aCallback) {
+ let params = {
+ cmd: "disableInterface",
+ ifname: aInterfaceName
+ };
+
+ this.controlMessage(params, function(aResult) {
+ aCallback.nativeCommandResult(!aResult.error);
+ });
+ },
+
+ resetConnections: function(aInterfaceName, aCallback) {
+ let params = {
+ cmd: "resetConnections",
+ ifname: aInterfaceName
+ };
+
+ this.controlMessage(params, function(aResult) {
+ aCallback.nativeCommandResult(!aResult.error);
+ });
+ },
+
+ createNetwork: function(aInterfaceName, aCallback) {
+ let params = {
+ cmd: "createNetwork",
+ ifname: aInterfaceName
+ };
+
+ this.controlMessage(params, function(aResult) {
+ aCallback.nativeCommandResult(!aResult.error);
+ });
+ },
+
+ destroyNetwork: function(aInterfaceName, aCallback) {
+ let params = {
+ cmd: "destroyNetwork",
+ ifname: aInterfaceName
+ };
+
+ this.controlMessage(params, function(aResult) {
+ aCallback.nativeCommandResult(!aResult.error);
+ });
+ },
+
+ getNetId: function(aInterfaceName) {
+ let params = {
+ cmd: "getNetId",
+ ifname: aInterfaceName
+ };
+
+ return new Promise((aResolve, aReject) => {
+ this.controlMessage(params, result => {
+ if (result.error) {
+ aReject(result.reason);
+ return;
+ }
+ aResolve(result.netId);
+ });
+ });
+ },
+
+ setMtu: function (aInterfaceName, aMtu, aCallback) {
+ debug("Set MTU on " + aInterfaceName + ": " + aMtu);
+
+ let params = {
+ cmd: "setMtu",
+ ifname: aInterfaceName,
+ mtu: aMtu
+ };
+
+ this.controlMessage(params, function(aResult) {
+ aCallback.nativeCommandResult(!aResult.error);
+ });
+ }
+};
+
+this.NSGetFactory = XPCOMUtils.generateNSGetFactory([NetworkService]);
diff --git a/dom/system/gonk/NetworkService.manifest b/dom/system/gonk/NetworkService.manifest
new file mode 100644
index 000000000..caf8f2554
--- /dev/null
+++ b/dom/system/gonk/NetworkService.manifest
@@ -0,0 +1,3 @@
+# NetworkService.js
+component {48c13741-aec9-4a86-8962-432011708261} NetworkService.js
+contract @mozilla.org/network/service;1 {48c13741-aec9-4a86-8962-432011708261}
diff --git a/dom/system/gonk/NetworkUtils.cpp b/dom/system/gonk/NetworkUtils.cpp
new file mode 100644
index 000000000..d661368b8
--- /dev/null
+++ b/dom/system/gonk/NetworkUtils.cpp
@@ -0,0 +1,2973 @@
+/* Copyright 2012 Mozilla Foundation and Mozilla contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "NetworkUtils.h"
+
+#include "mozilla/Sprintf.h"
+#include "SystemProperty.h"
+
+#include <android/log.h>
+#include <limits>
+#include "mozilla/dom/network/NetUtils.h"
+#include "mozilla/fallible.h"
+#include "base/task.h"
+
+#include <errno.h>
+#include <string.h>
+#include <sys/types.h> // struct addrinfo
+#include <sys/socket.h> // getaddrinfo(), freeaddrinfo()
+#include <netdb.h>
+#include <arpa/inet.h> // inet_ntop()
+
+#define _DEBUG 0
+
+#define WARN(args...) __android_log_print(ANDROID_LOG_WARN, "NetworkUtils", ## args)
+#define ERROR(args...) __android_log_print(ANDROID_LOG_ERROR, "NetworkUtils", ## args)
+
+#if _DEBUG
+#define NU_DBG(args...) __android_log_print(ANDROID_LOG_DEBUG, "NetworkUtils" , ## args)
+#else
+#define NU_DBG(args...)
+#endif
+
+using namespace mozilla::dom;
+using namespace mozilla::ipc;
+using mozilla::system::Property;
+
+static const char* PERSIST_SYS_USB_CONFIG_PROPERTY = "persist.sys.usb.config";
+static const char* SYS_USB_CONFIG_PROPERTY = "sys.usb.config";
+static const char* SYS_USB_STATE_PROPERTY = "sys.usb.state";
+
+static const char* USB_FUNCTION_RNDIS = "rndis";
+static const char* USB_FUNCTION_ADB = "adb";
+
+// Use this command to continue the function chain.
+static const char* DUMMY_COMMAND = "tether status";
+
+// IPV6 Tethering is not supported in AOSP, use the property to
+// identify vendor specific support in IPV6. We can remove this flag
+// once upstream Android support IPV6 in tethering.
+static const char* IPV6_TETHERING = "ro.tethering.ipv6";
+
+// Retry 20 times (2 seconds) for usb state transition.
+static const uint32_t USB_FUNCTION_RETRY_TIMES = 20;
+// Check "sys.usb.state" every 100ms.
+static const uint32_t USB_FUNCTION_RETRY_INTERVAL = 100;
+
+// 1xx - Requested action is proceeding
+static const uint32_t NETD_COMMAND_PROCEEDING = 100;
+// 2xx - Requested action has been successfully completed
+static const uint32_t NETD_COMMAND_OKAY = 200;
+// 4xx - The command is accepted but the requested action didn't
+// take place.
+static const uint32_t NETD_COMMAND_FAIL = 400;
+// 5xx - The command syntax or parameters error
+static const uint32_t NETD_COMMAND_ERROR = 500;
+// 6xx - Unsolicited broadcasts
+static const uint32_t NETD_COMMAND_UNSOLICITED = 600;
+
+// Broadcast messages
+static const uint32_t NETD_COMMAND_INTERFACE_CHANGE = 600;
+static const uint32_t NETD_COMMAND_BANDWIDTH_CONTROLLER = 601;
+
+static const char* INTERFACE_DELIMIT = ",";
+static const char* USB_CONFIG_DELIMIT = ",";
+static const char* NETD_MESSAGE_DELIMIT = " ";
+
+static const uint32_t BUF_SIZE = 1024;
+
+static const int32_t SUCCESS = 0;
+
+static uint32_t SDK_VERSION;
+static uint32_t SUPPORT_IPV6_TETHERING;
+
+struct IFProperties {
+ char gateway[Property::VALUE_MAX_LENGTH];
+ char dns1[Property::VALUE_MAX_LENGTH];
+ char dns2[Property::VALUE_MAX_LENGTH];
+};
+
+struct CurrentCommand {
+ CommandChain* chain;
+ CommandCallback callback;
+ char command[MAX_COMMAND_SIZE];
+};
+
+typedef Tuple3<NetdCommand*, CommandChain*, CommandCallback> QueueData;
+
+#define GET_CURRENT_NETD_COMMAND (gCommandQueue.IsEmpty() ? nullptr : gCommandQueue[0].a)
+#define GET_CURRENT_CHAIN (gCommandQueue.IsEmpty() ? nullptr : gCommandQueue[0].b)
+#define GET_CURRENT_CALLBACK (gCommandQueue.IsEmpty() ? nullptr : gCommandQueue[0].c)
+#define GET_CURRENT_COMMAND (gCommandQueue.IsEmpty() ? nullptr : gCommandQueue[0].a->mData)
+
+// A macro for native function call return value check.
+// For native function call, non-zero return value means failure.
+#define RETURN_IF_FAILED(rv) do { \
+ if (SUCCESS != rv) { \
+ return rv; \
+ } \
+} while (0);
+
+#define WARN_IF_FAILED(rv) do { \
+ if (SUCCESS != rv) { \
+ WARN("Error (%d) occurred in %s (%s:%d)", rv, __PRETTY_FUNCTION__, __FILE__, __LINE__); \
+ } \
+} while (0);
+
+static NetworkUtils* gNetworkUtils;
+static nsTArray<QueueData> gCommandQueue;
+static CurrentCommand gCurrentCommand;
+static bool gPending = false;
+static nsTArray<nsCString> gReason;
+static NetworkParams *gWifiTetheringParms = 0;
+
+static nsTArray<CommandChain*> gCommandChainQueue;
+
+const CommandFunc NetworkUtils::sWifiEnableChain[] = {
+ NetworkUtils::clearWifiTetherParms,
+ NetworkUtils::wifiFirmwareReload,
+ NetworkUtils::startAccessPointDriver,
+ NetworkUtils::setAccessPoint,
+ NetworkUtils::startSoftAP,
+ NetworkUtils::setConfig,
+ NetworkUtils::tetherInterface,
+ NetworkUtils::addInterfaceToLocalNetwork,
+ NetworkUtils::addRouteToLocalNetwork,
+ NetworkUtils::setIpForwardingEnabled,
+ NetworkUtils::tetheringStatus,
+ NetworkUtils::startTethering,
+ NetworkUtils::setDnsForwarders,
+ NetworkUtils::enableNat,
+ NetworkUtils::wifiTetheringSuccess
+};
+
+const CommandFunc NetworkUtils::sWifiDisableChain[] = {
+ NetworkUtils::clearWifiTetherParms,
+ NetworkUtils::stopSoftAP,
+ NetworkUtils::stopAccessPointDriver,
+ NetworkUtils::wifiFirmwareReload,
+ NetworkUtils::untetherInterface,
+ NetworkUtils::removeInterfaceFromLocalNetwork,
+ NetworkUtils::preTetherInterfaceList,
+ NetworkUtils::postTetherInterfaceList,
+ NetworkUtils::disableNat,
+ NetworkUtils::setIpForwardingEnabled,
+ NetworkUtils::stopTethering,
+ NetworkUtils::wifiTetheringSuccess
+};
+
+const CommandFunc NetworkUtils::sWifiFailChain[] = {
+ NetworkUtils::clearWifiTetherParms,
+ NetworkUtils::stopSoftAP,
+ NetworkUtils::setIpForwardingEnabled,
+ NetworkUtils::stopTethering
+};
+
+const CommandFunc NetworkUtils::sWifiRetryChain[] = {
+ NetworkUtils::clearWifiTetherParms,
+ NetworkUtils::stopSoftAP,
+ NetworkUtils::stopTethering,
+
+ // sWifiEnableChain:
+ NetworkUtils::wifiFirmwareReload,
+ NetworkUtils::startAccessPointDriver,
+ NetworkUtils::setAccessPoint,
+ NetworkUtils::startSoftAP,
+ NetworkUtils::setConfig,
+ NetworkUtils::tetherInterface,
+ NetworkUtils::addInterfaceToLocalNetwork,
+ NetworkUtils::addRouteToLocalNetwork,
+ NetworkUtils::setIpForwardingEnabled,
+ NetworkUtils::tetheringStatus,
+ NetworkUtils::startTethering,
+ NetworkUtils::setDnsForwarders,
+ NetworkUtils::enableNat,
+ NetworkUtils::wifiTetheringSuccess
+};
+
+const CommandFunc NetworkUtils::sWifiOperationModeChain[] = {
+ NetworkUtils::wifiFirmwareReload,
+ NetworkUtils::wifiOperationModeSuccess
+};
+
+const CommandFunc NetworkUtils::sUSBEnableChain[] = {
+ NetworkUtils::setConfig,
+ NetworkUtils::enableNat,
+ NetworkUtils::setIpForwardingEnabled,
+ NetworkUtils::tetherInterface,
+ NetworkUtils::addInterfaceToLocalNetwork,
+ NetworkUtils::addRouteToLocalNetwork,
+ NetworkUtils::tetheringStatus,
+ NetworkUtils::startTethering,
+ NetworkUtils::setDnsForwarders,
+ NetworkUtils::addUpstreamInterface,
+ NetworkUtils::usbTetheringSuccess
+};
+
+const CommandFunc NetworkUtils::sUSBDisableChain[] = {
+ NetworkUtils::untetherInterface,
+ NetworkUtils::removeInterfaceFromLocalNetwork,
+ NetworkUtils::preTetherInterfaceList,
+ NetworkUtils::postTetherInterfaceList,
+ NetworkUtils::removeUpstreamInterface,
+ NetworkUtils::disableNat,
+ NetworkUtils::setIpForwardingEnabled,
+ NetworkUtils::stopTethering,
+ NetworkUtils::usbTetheringSuccess
+};
+
+const CommandFunc NetworkUtils::sUSBFailChain[] = {
+ NetworkUtils::stopSoftAP,
+ NetworkUtils::setIpForwardingEnabled,
+ NetworkUtils::stopTethering
+};
+
+const CommandFunc NetworkUtils::sUpdateUpStreamChain[] = {
+ NetworkUtils::cleanUpStream,
+ NetworkUtils::removeUpstreamInterface,
+ NetworkUtils::createUpStream,
+ NetworkUtils::addUpstreamInterface,
+ NetworkUtils::updateUpStreamSuccess
+};
+
+const CommandFunc NetworkUtils::sStartDhcpServerChain[] = {
+ NetworkUtils::setConfig,
+ NetworkUtils::startTethering,
+ NetworkUtils::setDhcpServerSuccess
+};
+
+const CommandFunc NetworkUtils::sStopDhcpServerChain[] = {
+ NetworkUtils::stopTethering,
+ NetworkUtils::setDhcpServerSuccess
+};
+
+const CommandFunc NetworkUtils::sNetworkInterfaceEnableAlarmChain[] = {
+ NetworkUtils::enableAlarm,
+ NetworkUtils::setQuota,
+ NetworkUtils::setAlarm,
+ NetworkUtils::networkInterfaceAlarmSuccess
+};
+
+const CommandFunc NetworkUtils::sNetworkInterfaceDisableAlarmChain[] = {
+ NetworkUtils::removeQuota,
+ NetworkUtils::disableAlarm,
+ NetworkUtils::networkInterfaceAlarmSuccess
+};
+
+const CommandFunc NetworkUtils::sNetworkInterfaceSetAlarmChain[] = {
+ NetworkUtils::setAlarm,
+ NetworkUtils::networkInterfaceAlarmSuccess
+};
+
+const CommandFunc NetworkUtils::sGetInterfacesChain[] = {
+ NetworkUtils::getInterfaceList,
+ NetworkUtils::getInterfacesSuccess
+};
+
+const CommandFunc NetworkUtils::sGetInterfaceConfigChain[] = {
+ NetworkUtils::getConfig,
+ NetworkUtils::getInterfaceConfigSuccess
+};
+
+const CommandFunc NetworkUtils::sSetInterfaceConfigChain[] = {
+ NetworkUtils::setConfig,
+ NetworkUtils::setInterfaceConfigSuccess
+};
+
+const CommandFunc NetworkUtils::sTetheringInterfaceSetAlarmChain[] = {
+ NetworkUtils::setGlobalAlarm,
+ NetworkUtils::removeAlarm,
+ NetworkUtils::networkInterfaceAlarmSuccess
+};
+
+const CommandFunc NetworkUtils::sTetheringInterfaceRemoveAlarmChain[] = {
+ NetworkUtils::removeGlobalAlarm,
+ NetworkUtils::setAlarm,
+ NetworkUtils::networkInterfaceAlarmSuccess
+};
+
+const CommandFunc NetworkUtils::sTetheringGetStatusChain[] = {
+ NetworkUtils::tetheringStatus,
+ NetworkUtils::defaultAsyncSuccessHandler
+};
+
+/**
+ * Helper function to get the mask from given prefix length.
+ */
+static uint32_t makeMask(const uint32_t prefixLength)
+{
+ uint32_t mask = 0;
+ for (uint32_t i = 0; i < prefixLength; ++i) {
+ mask |= (0x80000000 >> i);
+ }
+ return ntohl(mask);
+}
+
+/**
+ * Helper function to get the network part of an ip from prefix.
+ * param ip must be in network byte order.
+ */
+static char* getNetworkAddr(const uint32_t ip, const uint32_t prefix)
+{
+ uint32_t mask = 0, subnet = 0;
+
+ mask = ~mask << (32 - prefix);
+ mask = htonl(mask);
+ subnet = ip & mask;
+
+ struct in_addr addr;
+ addr.s_addr = subnet;
+
+ return inet_ntoa(addr);
+}
+
+/**
+ * Helper function to split string by seperator, store split result as an nsTArray.
+ */
+static void split(char* str, const char* sep, nsTArray<nsCString>& result)
+{
+ char *s = strtok(str, sep);
+ while (s != nullptr) {
+ result.AppendElement(s);
+ s = strtok(nullptr, sep);
+ }
+}
+
+static void split(char* str, const char* sep, nsTArray<nsString>& result)
+{
+ char *s = strtok(str, sep);
+ while (s != nullptr) {
+ result.AppendElement(NS_ConvertUTF8toUTF16(s));
+ s = strtok(nullptr, sep);
+ }
+}
+
+/**
+ * Helper function that implement join function.
+ */
+static void join(nsTArray<nsCString>& array,
+ const char* sep,
+ const uint32_t maxlen,
+ char* result)
+{
+#define CHECK_LENGTH(len, add, max) len += add; \
+ if (len > max - 1) \
+ return; \
+
+ uint32_t len = 0;
+ uint32_t seplen = strlen(sep);
+
+ if (array.Length() > 0) {
+ CHECK_LENGTH(len, strlen(array[0].get()), maxlen)
+ strcpy(result, array[0].get());
+
+ for (uint32_t i = 1; i < array.Length(); i++) {
+ CHECK_LENGTH(len, seplen, maxlen)
+ strcat(result, sep);
+
+ CHECK_LENGTH(len, strlen(array[i].get()), maxlen)
+ strcat(result, array[i].get());
+ }
+ }
+
+#undef CHECK_LEN
+}
+
+static void convertUTF8toUTF16(nsTArray<nsCString>& narrow,
+ nsTArray<nsString>& wide,
+ uint32_t length)
+{
+ for (uint32_t i = 0; i < length; i++) {
+ wide.AppendElement(NS_ConvertUTF8toUTF16(narrow[i].get()));
+ }
+}
+
+/**
+ * Helper function to get network interface properties from the system property table.
+ */
+static void getIFProperties(const char* ifname, IFProperties& prop)
+{
+ char key[Property::KEY_MAX_LENGTH];
+ snprintf(key, Property::KEY_MAX_LENGTH - 1, "net.%s.gw", ifname);
+ Property::Get(key, prop.gateway, "");
+ snprintf(key, Property::KEY_MAX_LENGTH - 1, "net.%s.dns1", ifname);
+ Property::Get(key, prop.dns1, "");
+ snprintf(key, Property::KEY_MAX_LENGTH - 1, "net.%s.dns2", ifname);
+ Property::Get(key, prop.dns2, "");
+}
+
+static int getIpType(const char *aIp) {
+ struct addrinfo hint, *ip_info = NULL;
+
+ memset(&hint, 0, sizeof(hint));
+ hint.ai_family = AF_UNSPEC;
+ hint.ai_flags = AI_NUMERICHOST;
+
+ if (getaddrinfo(aIp, NULL, &hint, &ip_info)) {
+ return AF_UNSPEC;
+ }
+
+ int type = ip_info->ai_family;
+ freeaddrinfo(ip_info);
+
+ return type;
+}
+
+static void postMessage(NetworkResultOptions& aResult)
+{
+ MOZ_ASSERT(gNetworkUtils);
+ MOZ_ASSERT(gNetworkUtils->getMessageCallback());
+
+ if (*(gNetworkUtils->getMessageCallback()))
+ (*(gNetworkUtils->getMessageCallback()))(aResult);
+}
+
+static void postMessage(NetworkParams& aOptions, NetworkResultOptions& aResult)
+{
+ MOZ_ASSERT(gNetworkUtils);
+ MOZ_ASSERT(gNetworkUtils->getMessageCallback());
+
+ aResult.mId = aOptions.mId;
+
+ if (*(gNetworkUtils->getMessageCallback()))
+ (*(gNetworkUtils->getMessageCallback()))(aResult);
+}
+
+void NetworkUtils::runNextQueuedCommandChain()
+{
+ if (gCommandChainQueue.IsEmpty()) {
+ NU_DBG("No command chain left in the queue. Done!");
+ return;
+ }
+ NU_DBG("Process the queued command chain.");
+ CommandChain* nextChain = gCommandChainQueue[0];
+ NetworkResultOptions newResult;
+ next(nextChain, false, newResult);
+}
+
+void NetworkUtils::next(CommandChain* aChain, bool aError, NetworkResultOptions& aResult)
+{
+ if (aError) {
+ ErrorCallback onError = aChain->getErrorCallback();
+ if(onError) {
+ aResult.mError = true;
+ (*onError)(aChain->getParams(), aResult);
+ }
+ delete aChain;
+ gCommandChainQueue.RemoveElementAt(0);
+ runNextQueuedCommandChain();
+ return;
+ }
+ CommandFunc f = aChain->getNextCommand();
+ if (!f) {
+ delete aChain;
+ gCommandChainQueue.RemoveElementAt(0);
+ runNextQueuedCommandChain();
+ return;
+ }
+
+ (*f)(aChain, next, aResult);
+}
+
+CommandResult::CommandResult(int32_t aResultCode)
+ : mIsPending(false)
+{
+ // This is usually not a netd command. We treat the return code
+ // typical linux convention, which uses 0 to indicate success.
+ mResult.mError = (aResultCode == SUCCESS ? false : true);
+ mResult.mResultCode = aResultCode;
+ if (aResultCode != SUCCESS) {
+ // The returned value is sometimes negative, make sure we pass a positive
+ // error number to strerror.
+ enum { STRERROR_R_BUF_SIZE = 1024, };
+ char strerrorBuf[STRERROR_R_BUF_SIZE];
+ strerror_r(abs(aResultCode), strerrorBuf, STRERROR_R_BUF_SIZE);
+ mResult.mReason = NS_ConvertUTF8toUTF16(strerrorBuf);
+ }
+}
+
+CommandResult::CommandResult(const mozilla::dom::NetworkResultOptions& aResult)
+ : mResult(aResult)
+ , mIsPending(false)
+{
+}
+
+CommandResult::CommandResult(const Pending&)
+ : mIsPending(true)
+{
+}
+
+bool CommandResult::isPending() const
+{
+ return mIsPending;
+}
+
+/**
+ * Send command to netd.
+ */
+void NetworkUtils::nextNetdCommand()
+{
+ if (gCommandQueue.IsEmpty() || gPending) {
+ return;
+ }
+
+ gCurrentCommand.chain = GET_CURRENT_CHAIN;
+ gCurrentCommand.callback = GET_CURRENT_CALLBACK;
+ snprintf(gCurrentCommand.command, MAX_COMMAND_SIZE - 1, "%s", GET_CURRENT_COMMAND);
+
+ NU_DBG("Sending \'%s\' command to netd.", gCurrentCommand.command);
+ SendNetdCommand(GET_CURRENT_NETD_COMMAND);
+
+ gCommandQueue.RemoveElementAt(0);
+ gPending = true;
+}
+
+/**
+ * Composite NetdCommand sent to netd
+ *
+ * @param aCommand Command sent to netd to execute.
+ * @param aChain Store command chain data, ex. command parameter.
+ * @param aCallback Callback function to be executed when the result of
+ * this command is returned from netd.
+ */
+void NetworkUtils::doCommand(const char* aCommand, CommandChain* aChain, CommandCallback aCallback)
+{
+ NU_DBG("Preparing to send \'%s\' command...", aCommand);
+
+ NetdCommand* netdCommand = new NetdCommand();
+
+ // Android JB version adds sequence number to netd command.
+ if (SDK_VERSION >= 16) {
+ snprintf((char*)netdCommand->mData, MAX_COMMAND_SIZE - 1, "0 %s", aCommand);
+ } else {
+ snprintf((char*)netdCommand->mData, MAX_COMMAND_SIZE - 1, "%s", aCommand);
+ }
+ netdCommand->mSize = strlen((char*)netdCommand->mData) + 1;
+
+ gCommandQueue.AppendElement(QueueData(netdCommand, aChain, aCallback));
+
+ nextNetdCommand();
+}
+
+/*
+ * Netd command function
+ */
+#define GET_CHAR(prop) NS_ConvertUTF16toUTF8(aChain->getParams().prop).get()
+#define GET_FIELD(prop) aChain->getParams().prop
+
+void NetworkUtils::wifiFirmwareReload(CommandChain* aChain,
+ CommandCallback aCallback,
+ NetworkResultOptions& aResult)
+{
+ char command[MAX_COMMAND_SIZE];
+ snprintf(command, MAX_COMMAND_SIZE - 1, "softap fwreload %s %s", GET_CHAR(mIfname), GET_CHAR(mMode));
+
+ doCommand(command, aChain, aCallback);
+}
+
+void NetworkUtils::startAccessPointDriver(CommandChain* aChain,
+ CommandCallback aCallback,
+ NetworkResultOptions& aResult)
+{
+ // Skip the command for sdk version >= 16.
+ if (SDK_VERSION >= 16) {
+ aResult.mResultCode = 0;
+ aResult.mResultReason = NS_ConvertUTF8toUTF16("");
+ aCallback(aChain, false, aResult);
+ return;
+ }
+
+ char command[MAX_COMMAND_SIZE];
+ snprintf(command, MAX_COMMAND_SIZE - 1, "softap start %s", GET_CHAR(mIfname));
+
+ doCommand(command, aChain, aCallback);
+}
+
+void NetworkUtils::stopAccessPointDriver(CommandChain* aChain,
+ CommandCallback aCallback,
+ NetworkResultOptions& aResult)
+{
+ // Skip the command for sdk version >= 16.
+ if (SDK_VERSION >= 16) {
+ aResult.mResultCode = 0;
+ aResult.mResultReason = NS_ConvertUTF8toUTF16("");
+ aCallback(aChain, false, aResult);
+ return;
+ }
+
+ char command[MAX_COMMAND_SIZE];
+ snprintf(command, MAX_COMMAND_SIZE - 1, "softap stop %s", GET_CHAR(mIfname));
+
+ doCommand(command, aChain, aCallback);
+}
+
+/**
+ * Command format for sdk version < 16
+ * Arguments:
+ * argv[2] - wlan interface
+ * argv[3] - SSID
+ * argv[4] - Security
+ * argv[5] - Key
+ * argv[6] - Channel
+ * argv[7] - Preamble
+ * argv[8] - Max SCB
+ *
+ * Command format for sdk version >= 16
+ * Arguments:
+ * argv[2] - wlan interface
+ * argv[3] - SSID
+ * argv[4] - Security
+ * argv[5] - Key
+ *
+ * Command format for sdk version >= 18
+ * Arguments:
+ * argv[2] - wlan interface
+ * argv[3] - SSID
+ * argv[4] - Broadcast/Hidden
+ * argv[5] - Channel
+ * argv[6] - Security
+ * argv[7] - Key
+ */
+void NetworkUtils::setAccessPoint(CommandChain* aChain,
+ CommandCallback aCallback,
+ NetworkResultOptions& aResult)
+{
+ char command[MAX_COMMAND_SIZE];
+ nsCString ssid(GET_CHAR(mSsid));
+ nsCString key(GET_CHAR(mKey));
+
+ escapeQuote(ssid);
+ escapeQuote(key);
+
+ if (SDK_VERSION >= 19) {
+ snprintf(command, MAX_COMMAND_SIZE - 1, "softap set %s \"%s\" broadcast 6 %s \"%s\"",
+ GET_CHAR(mIfname),
+ ssid.get(),
+ GET_CHAR(mSecurity),
+ key.get());
+ } else if (SDK_VERSION >= 16) {
+ snprintf(command, MAX_COMMAND_SIZE - 1, "softap set %s \"%s\" %s \"%s\"",
+ GET_CHAR(mIfname),
+ ssid.get(),
+ GET_CHAR(mSecurity),
+ key.get());
+ } else {
+ snprintf(command, MAX_COMMAND_SIZE - 1, "softap set %s %s \"%s\" %s \"%s\" 6 0 8",
+ GET_CHAR(mIfname),
+ GET_CHAR(mWifictrlinterfacename),
+ ssid.get(),
+ GET_CHAR(mSecurity),
+ key.get());
+ }
+
+ doCommand(command, aChain, aCallback);
+}
+
+void NetworkUtils::cleanUpStream(CommandChain* aChain,
+ CommandCallback aCallback,
+ NetworkResultOptions& aResult)
+{
+ char command[MAX_COMMAND_SIZE];
+ snprintf(command, MAX_COMMAND_SIZE - 1, "nat disable %s %s 0", GET_CHAR(mPreInternalIfname), GET_CHAR(mPreExternalIfname));
+
+ doCommand(command, aChain, aCallback);
+}
+
+void NetworkUtils::createUpStream(CommandChain* aChain,
+ CommandCallback aCallback,
+ NetworkResultOptions& aResult)
+{
+ char command[MAX_COMMAND_SIZE];
+ snprintf(command, MAX_COMMAND_SIZE - 1, "nat enable %s %s 0", GET_CHAR(mCurInternalIfname), GET_CHAR(mCurExternalIfname));
+
+ doCommand(command, aChain, aCallback);
+}
+
+void NetworkUtils::startSoftAP(CommandChain* aChain,
+ CommandCallback aCallback,
+ NetworkResultOptions& aResult)
+{
+ const char* command= "softap startap";
+ doCommand(command, aChain, aCallback);
+}
+
+void NetworkUtils::stopSoftAP(CommandChain* aChain,
+ CommandCallback aCallback,
+ NetworkResultOptions& aResult)
+{
+ const char* command= "softap stopap";
+ doCommand(command, aChain, aCallback);
+}
+
+void NetworkUtils::clearWifiTetherParms(CommandChain* aChain,
+ CommandCallback aCallback,
+ NetworkResultOptions& aResult)
+{
+ delete gWifiTetheringParms;
+ gWifiTetheringParms = 0;
+ next(aChain, false, aResult);
+}
+
+void NetworkUtils::enableAlarm(CommandChain* aChain,
+ CommandCallback aCallback,
+ NetworkResultOptions& aResult)
+{
+ const char* command= "bandwidth enable";
+ doCommand(command, aChain, aCallback);
+}
+
+void NetworkUtils::disableAlarm(CommandChain* aChain,
+ CommandCallback aCallback,
+ NetworkResultOptions& aResult)
+{
+ const char* command= "bandwidth disable";
+ doCommand(command, aChain, aCallback);
+}
+
+void NetworkUtils::setQuota(CommandChain* aChain,
+ CommandCallback aCallback,
+ NetworkResultOptions& aResult)
+{
+ char command[MAX_COMMAND_SIZE];
+ snprintf(command, MAX_COMMAND_SIZE - 1, "bandwidth setiquota %s % " PRId64, GET_CHAR(mIfname), INT64_MAX);
+
+ doCommand(command, aChain, aCallback);
+}
+
+void NetworkUtils::removeQuota(CommandChain* aChain,
+ CommandCallback aCallback,
+ NetworkResultOptions& aResult)
+{
+ char command[MAX_COMMAND_SIZE];
+ snprintf(command, MAX_COMMAND_SIZE - 1, "bandwidth removeiquota %s", GET_CHAR(mIfname));
+
+ doCommand(command, aChain, aCallback);
+}
+
+void NetworkUtils::setAlarm(CommandChain* aChain,
+ CommandCallback aCallback,
+ NetworkResultOptions& aResult)
+{
+ char command[MAX_COMMAND_SIZE];
+ snprintf(command, MAX_COMMAND_SIZE - 1, "bandwidth setinterfacealert %s %lld",
+ GET_CHAR(mIfname), GET_FIELD(mThreshold));
+
+ doCommand(command, aChain, aCallback);
+}
+
+void NetworkUtils::removeAlarm(CommandChain* aChain,
+ CommandCallback aCallback,
+ NetworkResultOptions& aResult)
+{
+ char command[MAX_COMMAND_SIZE];
+ snprintf(command, MAX_COMMAND_SIZE - 1, "bandwidth removeinterfacealert %s", GET_CHAR(mIfname));
+
+ doCommand(command, aChain, aCallback);
+}
+
+void NetworkUtils::setGlobalAlarm(CommandChain* aChain,
+ CommandCallback aCallback,
+ NetworkResultOptions& aResult)
+{
+ char command[MAX_COMMAND_SIZE];
+
+ snprintf(command, MAX_COMMAND_SIZE - 1, "bandwidth setglobalalert %lld", GET_FIELD(mThreshold));
+ doCommand(command, aChain, aCallback);
+}
+
+void NetworkUtils::removeGlobalAlarm(CommandChain* aChain,
+ CommandCallback aCallback,
+ NetworkResultOptions& aResult)
+{
+ char command[MAX_COMMAND_SIZE];
+
+ snprintf(command, MAX_COMMAND_SIZE - 1, "bandwidth removeglobalalert");
+ doCommand(command, aChain, aCallback);
+}
+
+void NetworkUtils::tetherInterface(CommandChain* aChain,
+ CommandCallback aCallback,
+ NetworkResultOptions& aResult)
+{
+ char command[MAX_COMMAND_SIZE];
+ snprintf(command, MAX_COMMAND_SIZE - 1, "tether interface add %s", GET_CHAR(mIfname));
+
+ doCommand(command, aChain, aCallback);
+}
+
+void NetworkUtils::addInterfaceToLocalNetwork(CommandChain* aChain,
+ CommandCallback aCallback,
+ NetworkResultOptions& aResult)
+{
+ // Skip the command for sdk version < 20.
+ if (SDK_VERSION < 20) {
+ aResult.mResultCode = 0;
+ aResult.mResultReason = NS_ConvertUTF8toUTF16("");
+ aCallback(aChain, false, aResult);
+ return;
+ }
+
+ char command[MAX_COMMAND_SIZE];
+ snprintf(command, MAX_COMMAND_SIZE - 1, "network interface add local %s",
+ GET_CHAR(mInternalIfname));
+
+ doCommand(command, aChain, aCallback);
+}
+
+void NetworkUtils::addRouteToLocalNetwork(CommandChain* aChain,
+ CommandCallback aCallback,
+ NetworkResultOptions& aResult)
+{
+ // Skip the command for sdk version < 20.
+ if (SDK_VERSION < 20) {
+ aResult.mResultCode = 0;
+ aResult.mResultReason = NS_ConvertUTF8toUTF16("");
+ aCallback(aChain, false, aResult);
+ return;
+ }
+
+ char command[MAX_COMMAND_SIZE];
+ uint32_t prefix = atoi(GET_CHAR(mPrefix));
+ uint32_t ip = inet_addr(GET_CHAR(mIp));
+ char* networkAddr = getNetworkAddr(ip, prefix);
+
+ snprintf(command, MAX_COMMAND_SIZE - 1, "network route add local %s %s/%s",
+ GET_CHAR(mInternalIfname), networkAddr, GET_CHAR(mPrefix));
+
+ doCommand(command, aChain, aCallback);
+}
+
+void NetworkUtils::preTetherInterfaceList(CommandChain* aChain,
+ CommandCallback aCallback,
+ NetworkResultOptions& aResult)
+{
+ char command[MAX_COMMAND_SIZE];
+ if (SDK_VERSION >= 16) {
+ snprintf(command, MAX_COMMAND_SIZE - 1, "tether interface list");
+ } else {
+ snprintf(command, MAX_COMMAND_SIZE - 1, "tether interface list 0");
+ }
+
+ doCommand(command, aChain, aCallback);
+}
+
+void NetworkUtils::postTetherInterfaceList(CommandChain* aChain,
+ CommandCallback aCallback,
+ NetworkResultOptions& aResult)
+{
+ // Send the dummy command to continue the function chain.
+ char command[MAX_COMMAND_SIZE];
+ snprintf(command, MAX_COMMAND_SIZE - 1, "%s", DUMMY_COMMAND);
+
+ char buf[BUF_SIZE];
+ NS_ConvertUTF16toUTF8 reason(aResult.mResultReason);
+
+ size_t length = reason.Length() + 1 < BUF_SIZE ? reason.Length() + 1 : BUF_SIZE;
+ memcpy(buf, reason.get(), length);
+ split(buf, INTERFACE_DELIMIT, GET_FIELD(mInterfaceList));
+
+ doCommand(command, aChain, aCallback);
+}
+
+bool isCommandChainIPv6(CommandChain* aChain, const char *externalInterface) {
+ // Check by gateway address
+ if (getIpType(GET_CHAR(mGateway)) == AF_INET6) {
+ return true;
+ }
+
+ uint32_t length = GET_FIELD(mGateways).Length();
+ for (uint32_t i = 0; i < length; i++) {
+ NS_ConvertUTF16toUTF8 autoGateway(GET_FIELD(mGateways)[i]);
+ if(getIpType(autoGateway.get()) == AF_INET6) {
+ return true;
+ }
+ }
+
+ // Check by external inteface address
+ FILE *file = fopen("/proc/net/if_inet6", "r");
+ if (!file) {
+ return false;
+ }
+
+ bool isIPv6 = false;
+ char interface[32];
+ while(fscanf(file, "%*s %*s %*s %*s %*s %32s", interface)) {
+ if (strcmp(interface, externalInterface) == 0) {
+ isIPv6 = true;
+ break;
+ }
+ }
+
+ fclose(file);
+ return isIPv6;
+}
+
+void NetworkUtils::addUpstreamInterface(CommandChain* aChain,
+ CommandCallback aCallback,
+ NetworkResultOptions& aResult)
+{
+ nsCString interface(GET_CHAR(mExternalIfname));
+ if (!interface.get()[0]) {
+ interface = GET_CHAR(mCurExternalIfname);
+ }
+
+ if (SUPPORT_IPV6_TETHERING == 0 || !isCommandChainIPv6(aChain, interface.get())) {
+ aCallback(aChain, false, aResult);
+ return;
+ }
+
+ char command[MAX_COMMAND_SIZE];
+ snprintf(command, MAX_COMMAND_SIZE - 1, "tether interface add_upstream %s",
+ interface.get());
+ doCommand(command, aChain, aCallback);
+}
+
+void NetworkUtils::removeUpstreamInterface(CommandChain* aChain,
+ CommandCallback aCallback,
+ NetworkResultOptions& aResult)
+{
+ nsCString interface(GET_CHAR(mExternalIfname));
+ if (!interface.get()[0]) {
+ interface = GET_CHAR(mPreExternalIfname);
+ }
+
+ if (SUPPORT_IPV6_TETHERING == 0 || !isCommandChainIPv6(aChain, interface.get())) {
+ aCallback(aChain, false, aResult);
+ return;
+ }
+
+ char command[MAX_COMMAND_SIZE];
+ snprintf(command, MAX_COMMAND_SIZE - 1, "tether interface remove_upstream %s",
+ interface.get());
+ doCommand(command, aChain, aCallback);
+}
+
+void NetworkUtils::setIpForwardingEnabled(CommandChain* aChain,
+ CommandCallback aCallback,
+ NetworkResultOptions& aResult)
+{
+ char command[MAX_COMMAND_SIZE];
+
+ if (GET_FIELD(mEnable)) {
+ snprintf(command, MAX_COMMAND_SIZE - 1, "ipfwd enable");
+ } else {
+ // Don't disable ip forwarding because others interface still need it.
+ // Send the dummy command to continue the function chain.
+ if (GET_FIELD(mInterfaceList).Length() > 1) {
+ snprintf(command, MAX_COMMAND_SIZE - 1, "%s", DUMMY_COMMAND);
+ } else {
+ snprintf(command, MAX_COMMAND_SIZE - 1, "ipfwd disable");
+ }
+ }
+
+ doCommand(command, aChain, aCallback);
+}
+
+void NetworkUtils::tetheringStatus(CommandChain* aChain,
+ CommandCallback aCallback,
+ NetworkResultOptions& aResult)
+{
+ const char* command= "tether status";
+ doCommand(command, aChain, aCallback);
+}
+
+void NetworkUtils::stopTethering(CommandChain* aChain,
+ CommandCallback aCallback,
+ NetworkResultOptions& aResult)
+{
+ char command[MAX_COMMAND_SIZE];
+
+ // Don't stop tethering because others interface still need it.
+ // Send the dummy to continue the function chain.
+ if (GET_FIELD(mInterfaceList).Length() > 1) {
+ snprintf(command, MAX_COMMAND_SIZE - 1, "%s", DUMMY_COMMAND);
+ } else {
+ snprintf(command, MAX_COMMAND_SIZE - 1, "tether stop");
+ }
+
+ doCommand(command, aChain, aCallback);
+}
+
+void NetworkUtils::startTethering(CommandChain* aChain,
+ CommandCallback aCallback,
+ NetworkResultOptions& aResult)
+{
+ char command[MAX_COMMAND_SIZE];
+
+ // We don't need to start tethering again.
+ // Send the dummy command to continue the function chain.
+ if (aResult.mResultReason.Find("started") != kNotFound) {
+ snprintf(command, MAX_COMMAND_SIZE - 1, "%s", DUMMY_COMMAND);
+ } else {
+ // If usbStartIp/usbEndIp is not valid, don't append them since
+ // the trailing white spaces will be parsed to extra empty args
+ // See: http://androidxref.com/4.3_r2.1/xref/system/core/libsysutils/src/FrameworkListener.cpp#78
+ if (!GET_FIELD(mUsbStartIp).IsEmpty() && !GET_FIELD(mUsbEndIp).IsEmpty()) {
+ snprintf(command, MAX_COMMAND_SIZE - 1, "tether start %s %s %s %s",
+ GET_CHAR(mWifiStartIp), GET_CHAR(mWifiEndIp),
+ GET_CHAR(mUsbStartIp), GET_CHAR(mUsbEndIp));
+ } else {
+ snprintf(command, MAX_COMMAND_SIZE - 1, "tether start %s %s",
+ GET_CHAR(mWifiStartIp), GET_CHAR(mWifiEndIp));
+ }
+ }
+
+ doCommand(command, aChain, aCallback);
+}
+
+void NetworkUtils::untetherInterface(CommandChain* aChain,
+ CommandCallback aCallback,
+ NetworkResultOptions& aResult)
+{
+ char command[MAX_COMMAND_SIZE];
+ snprintf(command, MAX_COMMAND_SIZE - 1, "tether interface remove %s", GET_CHAR(mIfname));
+
+ doCommand(command, aChain, aCallback);
+}
+
+void NetworkUtils::removeInterfaceFromLocalNetwork(CommandChain* aChain,
+ CommandCallback aCallback,
+ NetworkResultOptions& aResult)
+{
+ // Skip the command for sdk version < 20.
+ if (SDK_VERSION < 20) {
+ aResult.mResultCode = 0;
+ aResult.mResultReason = NS_ConvertUTF8toUTF16("");
+ aCallback(aChain, false, aResult);
+ return;
+ }
+
+ char command[MAX_COMMAND_SIZE];
+ snprintf(command, MAX_COMMAND_SIZE - 1, "network interface remove local %s",
+ GET_CHAR(mIfname));
+
+ doCommand(command, aChain, aCallback);
+}
+
+void NetworkUtils::setDnsForwarders(CommandChain* aChain,
+ CommandCallback aCallback,
+ NetworkResultOptions& aResult)
+{
+ char command[MAX_COMMAND_SIZE];
+
+ if (SDK_VERSION >= 20) {
+ snprintf(command, MAX_COMMAND_SIZE - 1, "tether dns set %d %s %s",
+ GET_FIELD(mNetId), GET_CHAR(mDns1), GET_CHAR(mDns2));
+ } else {
+ snprintf(command, MAX_COMMAND_SIZE - 1, "tether dns set %s %s",
+ GET_CHAR(mDns1), GET_CHAR(mDns2));
+ }
+
+ doCommand(command, aChain, aCallback);
+}
+
+void NetworkUtils::enableNat(CommandChain* aChain,
+ CommandCallback aCallback,
+ NetworkResultOptions& aResult)
+{
+ char command[MAX_COMMAND_SIZE];
+
+ if (!GET_FIELD(mIp).IsEmpty() && !GET_FIELD(mPrefix).IsEmpty()) {
+ uint32_t prefix = atoi(GET_CHAR(mPrefix));
+ uint32_t ip = inet_addr(GET_CHAR(mIp));
+ char* networkAddr = getNetworkAddr(ip, prefix);
+
+ // address/prefix will only take effect when secondary routing table exists.
+ snprintf(command, MAX_COMMAND_SIZE - 1, "nat enable %s %s 1 %s/%s",
+ GET_CHAR(mInternalIfname), GET_CHAR(mExternalIfname), networkAddr,
+ GET_CHAR(mPrefix));
+ } else {
+ snprintf(command, MAX_COMMAND_SIZE - 1, "nat enable %s %s 0",
+ GET_CHAR(mInternalIfname), GET_CHAR(mExternalIfname));
+ }
+
+ doCommand(command, aChain, aCallback);
+}
+
+void NetworkUtils::disableNat(CommandChain* aChain,
+ CommandCallback aCallback,
+ NetworkResultOptions& aResult)
+{
+ char command[MAX_COMMAND_SIZE];
+
+ if (!GET_FIELD(mIp).IsEmpty() && !GET_FIELD(mPrefix).IsEmpty()) {
+ uint32_t prefix = atoi(GET_CHAR(mPrefix));
+ uint32_t ip = inet_addr(GET_CHAR(mIp));
+ char* networkAddr = getNetworkAddr(ip, prefix);
+
+ snprintf(command, MAX_COMMAND_SIZE - 1, "nat disable %s %s 1 %s/%s",
+ GET_CHAR(mInternalIfname), GET_CHAR(mExternalIfname), networkAddr,
+ GET_CHAR(mPrefix));
+ } else {
+ snprintf(command, MAX_COMMAND_SIZE - 1, "nat disable %s %s 0",
+ GET_CHAR(mInternalIfname), GET_CHAR(mExternalIfname));
+ }
+
+ doCommand(command, aChain, aCallback);
+}
+
+void NetworkUtils::setDefaultInterface(CommandChain* aChain,
+ CommandCallback aCallback,
+ NetworkResultOptions& aResult)
+{
+ char command[MAX_COMMAND_SIZE];
+ snprintf(command, MAX_COMMAND_SIZE - 1, "resolver setdefaultif %s", GET_CHAR(mIfname));
+
+ doCommand(command, aChain, aCallback);
+}
+
+void NetworkUtils::removeDefaultRoute(CommandChain* aChain,
+ CommandCallback aCallback,
+ NetworkResultOptions& aResult)
+{
+ if (GET_FIELD(mLoopIndex) >= GET_FIELD(mGateways).Length()) {
+ aCallback(aChain, false, aResult);
+ return;
+ }
+
+ char command[MAX_COMMAND_SIZE];
+ nsTArray<nsString>& gateways = GET_FIELD(mGateways);
+ NS_ConvertUTF16toUTF8 autoGateway(gateways[GET_FIELD(mLoopIndex)]);
+
+ int type = getIpType(autoGateway.get());
+ snprintf(command, MAX_COMMAND_SIZE - 1, "network route remove %d %s %s/0 %s",
+ GET_FIELD(mNetId), GET_CHAR(mIfname),
+ type == AF_INET6 ? "::" : "0.0.0.0", autoGateway.get());
+
+ struct MyCallback {
+ static void callback(CommandCallback::CallbackType aOriginalCallback,
+ CommandChain* aChain,
+ bool aError,
+ mozilla::dom::NetworkResultOptions& aResult)
+ {
+ NS_ConvertUTF16toUTF8 reason(aResult.mResultReason);
+ NU_DBG("removeDefaultRoute's reason: %s", reason.get());
+ if (aError && !reason.EqualsASCII("removeRoute() failed (No such process)")) {
+ return aOriginalCallback(aChain, aError, aResult);
+ }
+
+ GET_FIELD(mLoopIndex)++;
+ return removeDefaultRoute(aChain, aOriginalCallback, aResult);
+ }
+ };
+
+ CommandCallback wrappedCallback(MyCallback::callback, aCallback);
+ doCommand(command, aChain, wrappedCallback);
+}
+
+void NetworkUtils::setInterfaceDns(CommandChain* aChain,
+ CommandCallback aCallback,
+ NetworkResultOptions& aResult)
+{
+ char command[MAX_COMMAND_SIZE];
+ int written;
+
+ if (SDK_VERSION >= 20) {
+ written = SprintfLiteral(command, "resolver setnetdns %d %s",
+ GET_FIELD(mNetId), GET_CHAR(mDomain));
+ } else {
+ written = SprintfLiteral(command, "resolver setifdns %s %s",
+ GET_CHAR(mIfname), GET_CHAR(mDomain));
+ }
+
+ nsTArray<nsString>& dnses = GET_FIELD(mDnses);
+ uint32_t length = dnses.Length();
+
+ for (uint32_t i = 0; i < length; i++) {
+ NS_ConvertUTF16toUTF8 autoDns(dnses[i]);
+
+ int ret = snprintf(command + written, sizeof(command) - written, " %s", autoDns.get());
+ if (ret <= 1) {
+ command[written] = '\0';
+ continue;
+ }
+
+ if (((size_t)ret + written) >= sizeof(command)) {
+ command[written] = '\0';
+ break;
+ }
+
+ written += ret;
+ }
+
+ doCommand(command, aChain, aCallback);
+}
+
+void NetworkUtils::getInterfaceList(CommandChain* aChain,
+ CommandCallback aCallback,
+ NetworkResultOptions& aResult)
+{
+ char command[MAX_COMMAND_SIZE];
+ snprintf(command, MAX_COMMAND_SIZE - 1, "interface list");
+
+ doCommand(command, aChain, aCallback);
+}
+
+void NetworkUtils::getConfig(CommandChain* aChain,
+ CommandCallback aCallback,
+ NetworkResultOptions& aResult)
+{
+ char command[MAX_COMMAND_SIZE];
+ snprintf(command, MAX_COMMAND_SIZE - 1, "interface getcfg %s", GET_CHAR(mIfname));
+
+ doCommand(command, aChain, aCallback);
+}
+
+void NetworkUtils::setConfig(CommandChain* aChain,
+ CommandCallback aCallback,
+ NetworkResultOptions& aResult)
+{
+ char command[MAX_COMMAND_SIZE];
+ if (SDK_VERSION >= 16) {
+ snprintf(command, MAX_COMMAND_SIZE - 1, "interface setcfg %s %s %s %s",
+ GET_CHAR(mIfname),
+ GET_CHAR(mIp),
+ GET_CHAR(mPrefix),
+ GET_CHAR(mLink));
+ } else {
+ snprintf(command, MAX_COMMAND_SIZE - 1, "interface setcfg %s %s %s [%s]",
+ GET_CHAR(mIfname),
+ GET_CHAR(mIp),
+ GET_CHAR(mPrefix),
+ GET_CHAR(mLink));
+ }
+ doCommand(command, aChain, aCallback);
+}
+
+void NetworkUtils::clearAddrForInterface(CommandChain* aChain,
+ CommandCallback aCallback,
+ NetworkResultOptions& aResult)
+{
+ char command[MAX_COMMAND_SIZE];
+ snprintf(command, MAX_COMMAND_SIZE - 1, "interface clearaddrs %s", GET_CHAR(mIfname));
+
+ doCommand(command, aChain, aCallback);
+}
+
+void NetworkUtils::createNetwork(CommandChain* aChain,
+ CommandCallback aCallback,
+ NetworkResultOptions& aResult)
+{
+ char command[MAX_COMMAND_SIZE];
+ snprintf(command, MAX_COMMAND_SIZE - 1, "network create %d", GET_FIELD(mNetId));
+
+ doCommand(command, aChain, aCallback);
+}
+
+void NetworkUtils::destroyNetwork(CommandChain* aChain,
+ CommandCallback aCallback,
+ NetworkResultOptions& aResult)
+{
+ char command[MAX_COMMAND_SIZE];
+ snprintf(command, MAX_COMMAND_SIZE - 1, "network destroy %d", GET_FIELD(mNetId));
+
+ doCommand(command, aChain, aCallback);
+}
+
+void NetworkUtils::addInterfaceToNetwork(CommandChain* aChain,
+ CommandCallback aCallback,
+ NetworkResultOptions& aResult)
+{
+ char command[MAX_COMMAND_SIZE];
+ snprintf(command, MAX_COMMAND_SIZE - 1, "network interface add %d %s",
+ GET_FIELD(mNetId), GET_CHAR(mIfname));
+
+ doCommand(command, aChain, aCallback);
+}
+
+void NetworkUtils::addRouteToInterface(CommandChain* aChain,
+ CommandCallback aCallback,
+ NetworkResultOptions& aResult)
+{
+ struct MyCallback {
+ static void callback(CommandCallback::CallbackType aOriginalCallback,
+ CommandChain* aChain,
+ bool aError,
+ mozilla::dom::NetworkResultOptions& aResult)
+ {
+ NS_ConvertUTF16toUTF8 reason(aResult.mResultReason);
+ NU_DBG("addRouteToInterface's reason: %s", reason.get());
+ if (aError && reason.EqualsASCII("addRoute() failed (File exists)")) {
+ NU_DBG("Ignore \"File exists\" error when adding host route.");
+ return aOriginalCallback(aChain, false, aResult);
+ }
+ aOriginalCallback(aChain, aError, aResult);
+ }
+ };
+
+ CommandCallback wrappedCallback(MyCallback::callback, aCallback);
+ modifyRouteOnInterface(aChain, wrappedCallback, aResult, true);
+}
+
+void NetworkUtils::removeRouteFromInterface(CommandChain* aChain,
+ CommandCallback aCallback,
+ NetworkResultOptions& aResult)
+{
+ modifyRouteOnInterface(aChain, aCallback, aResult, false);
+}
+
+void NetworkUtils::modifyRouteOnInterface(CommandChain* aChain,
+ CommandCallback aCallback,
+ NetworkResultOptions& aResult,
+ bool aDoAdd)
+{
+ char command[MAX_COMMAND_SIZE];
+
+ // AOSP adds host route to its interface table but it doesn't work for
+ // B2G because we cannot set fwmark per application. So, we add
+ // all host routes to legacy_system table except scope link route.
+
+ nsCString ipOrSubnetIp = NS_ConvertUTF16toUTF8(GET_FIELD(mIp));
+ nsCString gatewayOrEmpty;
+ const char* legacyOrEmpty = "legacy 0 "; // Add to legacy by default.
+ if (GET_FIELD(mGateway).IsEmpty()) {
+ ipOrSubnetIp = getSubnetIp(ipOrSubnetIp, GET_FIELD(mPrefixLength));
+ legacyOrEmpty = ""; // Add to interface table for scope link route.
+ } else {
+ gatewayOrEmpty = nsCString(" ") + NS_ConvertUTF16toUTF8(GET_FIELD(mGateway));
+ }
+
+ const char* action = aDoAdd ? "add" : "remove";
+
+ snprintf(command, MAX_COMMAND_SIZE - 1, "network route %s%s %d %s %s/%d%s",
+ legacyOrEmpty, action,
+ GET_FIELD(mNetId), GET_CHAR(mIfname), ipOrSubnetIp.get(),
+ GET_FIELD(mPrefixLength), gatewayOrEmpty.get());
+
+ doCommand(command, aChain, aCallback);
+}
+
+void NetworkUtils::addDefaultRouteToNetwork(CommandChain* aChain,
+ CommandCallback aCallback,
+ NetworkResultOptions& aResult)
+{
+ if (GET_FIELD(mLoopIndex) >= GET_FIELD(mGateways).Length()) {
+ aCallback(aChain, false, aResult);
+ return;
+ }
+
+ char command[MAX_COMMAND_SIZE];
+ nsTArray<nsString>& gateways = GET_FIELD(mGateways);
+ NS_ConvertUTF16toUTF8 autoGateway(gateways[GET_FIELD(mLoopIndex)]);
+
+ int type = getIpType(autoGateway.get());
+ snprintf(command, MAX_COMMAND_SIZE - 1, "network route add %d %s %s/0 %s",
+ GET_FIELD(mNetId), GET_CHAR(mIfname),
+ type == AF_INET6 ? "::" : "0.0.0.0", autoGateway.get());
+
+ struct MyCallback {
+ static void callback(CommandCallback::CallbackType aOriginalCallback,
+ CommandChain* aChain,
+ bool aError,
+ mozilla::dom::NetworkResultOptions& aResult)
+ {
+ NS_ConvertUTF16toUTF8 reason(aResult.mResultReason);
+ NU_DBG("addDefaultRouteToNetwork's reason: %s", reason.get());
+ if (aError && !reason.EqualsASCII("addRoute() failed (File exists)")) {
+ return aOriginalCallback(aChain, aError, aResult);
+ }
+
+ GET_FIELD(mLoopIndex)++;
+ return addDefaultRouteToNetwork(aChain, aOriginalCallback, aResult);
+ }
+ };
+
+ CommandCallback wrappedCallback(MyCallback::callback, aCallback);
+ doCommand(command, aChain, wrappedCallback);
+}
+
+void NetworkUtils::setDefaultNetwork(CommandChain* aChain,
+ CommandCallback aCallback,
+ NetworkResultOptions& aResult)
+{
+ char command[MAX_COMMAND_SIZE];
+ snprintf(command, MAX_COMMAND_SIZE - 1, "network default set %d", GET_FIELD(mNetId));
+
+ doCommand(command, aChain, aCallback);
+}
+
+void NetworkUtils::addRouteToSecondaryTable(CommandChain* aChain,
+ CommandCallback aCallback,
+ NetworkResultOptions& aResult) {
+
+ char command[MAX_COMMAND_SIZE];
+
+ if (SDK_VERSION >= 20) {
+ snprintf(command, MAX_COMMAND_SIZE - 1,
+ "network route add %d %s %s/%s %s",
+ GET_FIELD(mNetId),
+ GET_CHAR(mIfname),
+ GET_CHAR(mIp),
+ GET_CHAR(mPrefix),
+ GET_CHAR(mGateway));
+ } else {
+ snprintf(command, MAX_COMMAND_SIZE - 1,
+ "interface route add %s secondary %s %s %s",
+ GET_CHAR(mIfname),
+ GET_CHAR(mIp),
+ GET_CHAR(mPrefix),
+ GET_CHAR(mGateway));
+ }
+
+ doCommand(command, aChain, aCallback);
+}
+
+void NetworkUtils::removeRouteFromSecondaryTable(CommandChain* aChain,
+ CommandCallback aCallback,
+ NetworkResultOptions& aResult) {
+ char command[MAX_COMMAND_SIZE];
+
+ if (SDK_VERSION >= 20) {
+ snprintf(command, MAX_COMMAND_SIZE - 1,
+ "network route remove %d %s %s/%s %s",
+ GET_FIELD(mNetId),
+ GET_CHAR(mIfname),
+ GET_CHAR(mIp),
+ GET_CHAR(mPrefix),
+ GET_CHAR(mGateway));
+ } else {
+ snprintf(command, MAX_COMMAND_SIZE - 1,
+ "interface route remove %s secondary %s %s %s",
+ GET_CHAR(mIfname),
+ GET_CHAR(mIp),
+ GET_CHAR(mPrefix),
+ GET_CHAR(mGateway));
+ }
+
+ doCommand(command, aChain, aCallback);
+}
+
+void NetworkUtils::setIpv6Enabled(CommandChain* aChain,
+ CommandCallback aCallback,
+ NetworkResultOptions& aResult,
+ bool aEnabled)
+{
+ char command[MAX_COMMAND_SIZE];
+ snprintf(command, MAX_COMMAND_SIZE - 1, "interface ipv6 %s %s",
+ GET_CHAR(mIfname), aEnabled ? "enable" : "disable");
+
+ struct MyCallback {
+ static void callback(CommandCallback::CallbackType aOriginalCallback,
+ CommandChain* aChain,
+ bool aError,
+ mozilla::dom::NetworkResultOptions& aResult)
+ {
+ aOriginalCallback(aChain, false, aResult);
+ }
+ };
+
+ CommandCallback wrappedCallback(MyCallback::callback, aCallback);
+ doCommand(command, aChain, wrappedCallback);
+}
+
+void NetworkUtils::enableIpv6(CommandChain* aChain,
+ CommandCallback aCallback,
+ NetworkResultOptions& aResult)
+{
+ setIpv6Enabled(aChain, aCallback, aResult, true);
+}
+
+void NetworkUtils::disableIpv6(CommandChain* aChain,
+ CommandCallback aCallback,
+ NetworkResultOptions& aResult)
+{
+ setIpv6Enabled(aChain, aCallback, aResult, false);
+}
+
+void NetworkUtils::setMtu(CommandChain* aChain,
+ CommandCallback aCallback,
+ NetworkResultOptions& aResult)
+{
+ char command[MAX_COMMAND_SIZE];
+ snprintf(command, MAX_COMMAND_SIZE - 1, "interface setmtu %s %ld",
+ GET_CHAR(mIfname), GET_FIELD(mMtu));
+
+ doCommand(command, aChain, aCallback);
+}
+
+#undef GET_CHAR
+#undef GET_FIELD
+
+/*
+ * Netd command success/fail function
+ */
+#define ASSIGN_FIELD(prop) aResult.prop = aChain->getParams().prop;
+#define ASSIGN_FIELD_VALUE(prop, value) aResult.prop = value;
+
+template<size_t N>
+void NetworkUtils::runChain(const NetworkParams& aParams,
+ const CommandFunc (&aCmds)[N],
+ ErrorCallback aError)
+{
+ CommandChain* chain = new CommandChain(aParams, aCmds, N, aError);
+ gCommandChainQueue.AppendElement(chain);
+
+ if (gCommandChainQueue.Length() > 1) {
+ NU_DBG("%d command chains are queued. Wait!", gCommandChainQueue.Length());
+ return;
+ }
+
+ NetworkResultOptions result;
+ NetworkUtils::next(gCommandChainQueue[0], false, result);
+}
+
+// Called to clean up the command chain and process the queued command chain if any.
+void NetworkUtils::finalizeSuccess(CommandChain* aChain,
+ NetworkResultOptions& aResult)
+{
+ next(aChain, false, aResult);
+}
+
+void NetworkUtils::wifiTetheringFail(NetworkParams& aOptions, NetworkResultOptions& aResult)
+{
+ // Notify the main thread.
+ postMessage(aOptions, aResult);
+
+ // If one of the stages fails, we try roll back to ensure
+ // we don't leave the network systems in limbo.
+ ASSIGN_FIELD_VALUE(mEnable, false)
+ runChain(aOptions, sWifiFailChain, nullptr);
+}
+
+void NetworkUtils::wifiTetheringSuccess(CommandChain* aChain,
+ CommandCallback aCallback,
+ NetworkResultOptions& aResult)
+{
+ ASSIGN_FIELD(mEnable)
+
+ if (aChain->getParams().mEnable) {
+ MOZ_ASSERT(!gWifiTetheringParms);
+ gWifiTetheringParms = new NetworkParams(aChain->getParams());
+ }
+ postMessage(aChain->getParams(), aResult);
+ finalizeSuccess(aChain, aResult);
+}
+
+void NetworkUtils::usbTetheringFail(NetworkParams& aOptions,
+ NetworkResultOptions& aResult)
+{
+ // Notify the main thread.
+ postMessage(aOptions, aResult);
+ // Try to roll back to ensure
+ // we don't leave the network systems in limbo.
+ // This parameter is used to disable ipforwarding.
+ {
+ aOptions.mEnable = false;
+ runChain(aOptions, sUSBFailChain, nullptr);
+ }
+
+ // Disable usb rndis function.
+ {
+ NetworkParams options;
+ options.mEnable = false;
+ options.mReport = false;
+ gNetworkUtils->enableUsbRndis(options);
+ }
+}
+
+void NetworkUtils::usbTetheringSuccess(CommandChain* aChain,
+ CommandCallback aCallback,
+ NetworkResultOptions& aResult)
+{
+ ASSIGN_FIELD(mEnable)
+ postMessage(aChain->getParams(), aResult);
+ finalizeSuccess(aChain, aResult);
+}
+
+void NetworkUtils::networkInterfaceAlarmFail(NetworkParams& aOptions, NetworkResultOptions& aResult)
+{
+ postMessage(aOptions, aResult);
+}
+
+void NetworkUtils::networkInterfaceAlarmSuccess(CommandChain* aChain,
+ CommandCallback aCallback,
+ NetworkResultOptions& aResult)
+{
+ // TODO : error is not used , and it is conflict with boolean type error.
+ // params.error = parseFloat(params.resultReason);
+ postMessage(aChain->getParams(), aResult);
+ finalizeSuccess(aChain, aResult);
+}
+
+void NetworkUtils::updateUpStreamFail(NetworkParams& aOptions, NetworkResultOptions& aResult)
+{
+ postMessage(aOptions, aResult);
+}
+
+void NetworkUtils::updateUpStreamSuccess(CommandChain* aChain,
+ CommandCallback aCallback,
+ NetworkResultOptions& aResult)
+{
+ ASSIGN_FIELD(mCurExternalIfname)
+ ASSIGN_FIELD(mCurInternalIfname)
+ postMessage(aChain->getParams(), aResult);
+ finalizeSuccess(aChain, aResult);
+}
+
+void NetworkUtils::setDhcpServerFail(NetworkParams& aOptions, NetworkResultOptions& aResult)
+{
+ aResult.mSuccess = false;
+ postMessage(aOptions, aResult);
+}
+
+void NetworkUtils::setDhcpServerSuccess(CommandChain* aChain, CommandCallback aCallback, NetworkResultOptions& aResult)
+{
+ aResult.mSuccess = true;
+ postMessage(aChain->getParams(), aResult);
+ finalizeSuccess(aChain, aResult);
+}
+
+void NetworkUtils::wifiOperationModeFail(NetworkParams& aOptions, NetworkResultOptions& aResult)
+{
+ postMessage(aOptions, aResult);
+}
+
+void NetworkUtils::wifiOperationModeSuccess(CommandChain* aChain,
+ CommandCallback aCallback,
+ NetworkResultOptions& aResult)
+{
+ postMessage(aChain->getParams(), aResult);
+ finalizeSuccess(aChain, aResult);
+}
+
+void NetworkUtils::setDnsFail(NetworkParams& aOptions, NetworkResultOptions& aResult)
+{
+ postMessage(aOptions, aResult);
+}
+
+void NetworkUtils::defaultAsyncSuccessHandler(CommandChain* aChain,
+ CommandCallback aCallback,
+ NetworkResultOptions& aResult)
+{
+ NU_DBG("defaultAsyncSuccessHandler");
+ aResult.mRet = true;
+ postMessage(aChain->getParams(), aResult);
+ finalizeSuccess(aChain, aResult);
+}
+
+void NetworkUtils::defaultAsyncFailureHandler(NetworkParams& aOptions, NetworkResultOptions& aResult)
+{
+ aResult.mRet = false;
+ postMessage(aOptions, aResult);
+}
+
+void NetworkUtils::getInterfacesFail(NetworkParams& aOptions, NetworkResultOptions& aResult)
+{
+ postMessage(aOptions, aResult);
+}
+
+void NetworkUtils::getInterfacesSuccess(CommandChain* aChain,
+ CommandCallback aCallback,
+ NetworkResultOptions& aResult)
+{
+ char buf[BUF_SIZE];
+ NS_ConvertUTF16toUTF8 reason(aResult.mResultReason);
+ memcpy(buf, reason.get(), strlen(reason.get()));
+
+ nsTArray<nsCString> result;
+ split(buf, INTERFACE_DELIMIT, result);
+
+ nsTArray<nsString> interfaceList;
+ uint32_t length = result.Length();
+ convertUTF8toUTF16(result, interfaceList, length);
+
+ aResult.mInterfaceList.Construct();
+ for (uint32_t i = 0; i < length; i++) {
+ aResult.mInterfaceList.Value().AppendElement(interfaceList[i], fallible_t());
+ }
+
+ postMessage(aChain->getParams(), aResult);
+ finalizeSuccess(aChain, aResult);
+}
+
+void NetworkUtils::getInterfaceConfigFail(NetworkParams& aOptions,
+ NetworkResultOptions& aResult)
+{
+ postMessage(aOptions, aResult);
+}
+
+void NetworkUtils::getInterfaceConfigSuccess(CommandChain* aChain,
+ CommandCallback aCallback,
+ NetworkResultOptions& aResult)
+{
+ char buf[BUF_SIZE];
+ NS_ConvertUTF16toUTF8 reason(aResult.mResultReason);
+ memcpy(buf, reason.get(), strlen(reason.get()));
+
+ nsTArray<nsCString> result;
+ split(buf, NETD_MESSAGE_DELIMIT, result);
+
+ ASSIGN_FIELD_VALUE(mMacAddr, NS_ConvertUTF8toUTF16(result[0]))
+ ASSIGN_FIELD_VALUE(mIpAddr, NS_ConvertUTF8toUTF16(result[1]))
+ ASSIGN_FIELD_VALUE(mPrefixLength, atol(result[2].get()))
+
+ if (result[3].Find("up")) {
+ ASSIGN_FIELD_VALUE(mFlag, NS_ConvertUTF8toUTF16("up"))
+ } else {
+ ASSIGN_FIELD_VALUE(mFlag, NS_ConvertUTF8toUTF16("down"))
+ }
+
+ postMessage(aChain->getParams(), aResult);
+ finalizeSuccess(aChain, aResult);
+}
+
+void NetworkUtils::setInterfaceConfigFail(NetworkParams& aOptions,
+ NetworkResultOptions& aResult)
+{
+ postMessage(aOptions, aResult);
+}
+
+void NetworkUtils::setInterfaceConfigSuccess(CommandChain* aChain,
+ CommandCallback aCallback,
+ NetworkResultOptions& aResult)
+{
+ postMessage(aChain->getParams(), aResult);
+ finalizeSuccess(aChain, aResult);
+}
+
+#undef ASSIGN_FIELD
+#undef ASSIGN_FIELD_VALUE
+
+NetworkUtils::NetworkUtils(MessageCallback aCallback)
+ : mMessageCallback(aCallback)
+{
+ mNetUtils = new NetUtils();
+
+ char value[Property::VALUE_MAX_LENGTH];
+ Property::Get("ro.build.version.sdk", value, nullptr);
+ SDK_VERSION = atoi(value);
+
+ Property::Get(IPV6_TETHERING, value, "0");
+ SUPPORT_IPV6_TETHERING = atoi(value);
+
+ gNetworkUtils = this;
+}
+
+NetworkUtils::~NetworkUtils()
+{
+}
+
+#define GET_CHAR(prop) NS_ConvertUTF16toUTF8(aOptions.prop).get()
+#define GET_FIELD(prop) aOptions.prop
+
+// Hoist this type definition to global to avoid template
+// instantiation error on gcc 4.4 used by ICS emulator.
+typedef CommandResult (NetworkUtils::*CommandHandler)(NetworkParams&);
+struct CommandHandlerEntry
+{
+ const char* mCommandName;
+ CommandHandler mCommandHandler;
+};
+
+void NetworkUtils::ExecuteCommand(NetworkParams aOptions)
+{
+ const static CommandHandlerEntry
+ COMMAND_HANDLER_TABLE[] = {
+
+ // For command 'testCommand', BUILD_ENTRY(testCommand) will generate
+ // {"testCommand", NetworkUtils::testCommand}
+ #define BUILD_ENTRY(c) {#c, &NetworkUtils::c}
+
+ BUILD_ENTRY(removeNetworkRoute),
+ BUILD_ENTRY(setDNS),
+ BUILD_ENTRY(setDefaultRoute),
+ BUILD_ENTRY(removeDefaultRoute),
+ BUILD_ENTRY(addHostRoute),
+ BUILD_ENTRY(removeHostRoute),
+ BUILD_ENTRY(addSecondaryRoute),
+ BUILD_ENTRY(removeSecondaryRoute),
+ BUILD_ENTRY(setNetworkInterfaceAlarm),
+ BUILD_ENTRY(enableNetworkInterfaceAlarm),
+ BUILD_ENTRY(disableNetworkInterfaceAlarm),
+ BUILD_ENTRY(setTetheringAlarm),
+ BUILD_ENTRY(removeTetheringAlarm),
+ BUILD_ENTRY(getTetheringStatus),
+ BUILD_ENTRY(setWifiOperationMode),
+ BUILD_ENTRY(setDhcpServer),
+ BUILD_ENTRY(setWifiTethering),
+ BUILD_ENTRY(setUSBTethering),
+ BUILD_ENTRY(enableUsbRndis),
+ BUILD_ENTRY(updateUpStream),
+ BUILD_ENTRY(configureInterface),
+ BUILD_ENTRY(dhcpRequest),
+ BUILD_ENTRY(stopDhcp),
+ BUILD_ENTRY(enableInterface),
+ BUILD_ENTRY(disableInterface),
+ BUILD_ENTRY(resetConnections),
+ BUILD_ENTRY(createNetwork),
+ BUILD_ENTRY(destroyNetwork),
+ BUILD_ENTRY(getNetId),
+ BUILD_ENTRY(getInterfaces),
+ BUILD_ENTRY(getInterfaceConfig),
+ BUILD_ENTRY(setInterfaceConfig),
+ BUILD_ENTRY(setMtu),
+
+ #undef BUILD_ENTRY
+ };
+
+ // Loop until we find the command name which matches aOptions.mCmd.
+ CommandHandler handler = nullptr;
+ for (size_t i = 0; i < mozilla::ArrayLength(COMMAND_HANDLER_TABLE); i++) {
+ if (aOptions.mCmd.EqualsASCII(COMMAND_HANDLER_TABLE[i].mCommandName)) {
+ handler = COMMAND_HANDLER_TABLE[i].mCommandHandler;
+ break;
+ }
+ }
+
+ if (!handler) {
+ // Command not found in COMMAND_HANDLER_TABLE.
+ WARN("unknown message: %s", NS_ConvertUTF16toUTF8(aOptions.mCmd).get());
+ return;
+ }
+
+ // The handler would return one of the following 3 values
+ // to be wrapped to CommandResult:
+ //
+ // 1) |int32_t| for mostly synchronous native function calls.
+ // 2) |NetworkResultOptions| to populate additional results. (e.g. dhcpRequest)
+ // 3) |CommandResult::Pending| to indicate the result is not
+ // obtained yet.
+ //
+ // If the handler returns "Pending", the handler should take the
+ // responsibility for posting result to main thread.
+ CommandResult commandResult = (this->*handler)(aOptions);
+ if (!commandResult.isPending()) {
+ postMessage(aOptions, commandResult.mResult);
+ }
+}
+
+/**
+ * Handle received data from netd.
+ */
+void NetworkUtils::onNetdMessage(NetdCommand* aCommand)
+{
+ char* data = (char*)aCommand->mData;
+
+ // get code & reason.
+ char* result = strtok(data, NETD_MESSAGE_DELIMIT);
+
+ if (!result) {
+ nextNetdCommand();
+ return;
+ }
+ uint32_t code = atoi(result);
+
+ if (!isBroadcastMessage(code) && SDK_VERSION >= 16) {
+ strtok(nullptr, NETD_MESSAGE_DELIMIT);
+ }
+
+ char* reason = strtok(nullptr, "\0");
+
+ if (isBroadcastMessage(code)) {
+ NU_DBG("Receiving broadcast message from netd.");
+ NU_DBG(" ==> Code: %d Reason: %s", code, reason);
+ sendBroadcastMessage(code, reason);
+
+ if (code == NETD_COMMAND_INTERFACE_CHANGE) {
+ if (gWifiTetheringParms) {
+ char linkdownReason[MAX_COMMAND_SIZE];
+ snprintf(linkdownReason, MAX_COMMAND_SIZE - 1,
+ "Iface linkstate %s down",
+ NS_ConvertUTF16toUTF8(gWifiTetheringParms->mIfname).get());
+
+ if (!strcmp(reason, linkdownReason)) {
+ NU_DBG("Wifi link down, restarting tethering.");
+ runChain(*gWifiTetheringParms, sWifiRetryChain, wifiTetheringFail);
+ }
+ }
+ }
+
+ nextNetdCommand();
+ return;
+ }
+
+ // Set pending to false before we handle next command.
+ NU_DBG("Receiving \"%s\" command response from netd.", gCurrentCommand.command);
+ NU_DBG(" ==> Code: %d Reason: %s", code, reason);
+
+ gReason.AppendElement(nsCString(reason));
+
+ // 1xx response code regards as command is proceeding, we need to wait for
+ // final response code such as 2xx, 4xx and 5xx before sending next command.
+ if (isProceeding(code)) {
+ return;
+ }
+
+ if (isComplete(code)) {
+ gPending = false;
+ }
+
+ {
+ char buf[BUF_SIZE];
+ join(gReason, INTERFACE_DELIMIT, BUF_SIZE, buf);
+
+ NetworkResultOptions result;
+ result.mResultCode = code;
+ result.mResultReason = NS_ConvertUTF8toUTF16(buf);
+ (gCurrentCommand.callback)(gCurrentCommand.chain, isError(code), result);
+ gReason.Clear();
+ }
+
+ // Handling pending commands if any.
+ if (isComplete(code)) {
+ nextNetdCommand();
+ }
+}
+
+/**
+ * Start/Stop DHCP server.
+ */
+CommandResult NetworkUtils::setDhcpServer(NetworkParams& aOptions)
+{
+ if (aOptions.mEnabled) {
+ aOptions.mWifiStartIp = aOptions.mStartIp;
+ aOptions.mWifiEndIp = aOptions.mEndIp;
+ aOptions.mIp = aOptions.mServerIp;
+ aOptions.mPrefix = aOptions.mMaskLength;
+ aOptions.mLink = NS_ConvertUTF8toUTF16("up");
+
+ runChain(aOptions, sStartDhcpServerChain, setDhcpServerFail);
+ } else {
+ runChain(aOptions, sStopDhcpServerChain, setDhcpServerFail);
+ }
+ return CommandResult::Pending();
+}
+
+/**
+ * Set DNS servers for given network interface.
+ */
+CommandResult NetworkUtils::setDNS(NetworkParams& aOptions)
+{
+ uint32_t length = aOptions.mDnses.Length();
+
+ if (length > 0) {
+ for (uint32_t i = 0; i < length; i++) {
+ NS_ConvertUTF16toUTF8 autoDns(aOptions.mDnses[i]);
+
+ char dns_prop_key[Property::VALUE_MAX_LENGTH];
+ SprintfLiteral(dns_prop_key, "net.dns%d", i+1);
+ Property::Set(dns_prop_key, autoDns.get());
+ }
+ } else {
+ // Set dnses from system properties.
+ IFProperties interfaceProperties;
+ getIFProperties(GET_CHAR(mIfname), interfaceProperties);
+
+ Property::Set("net.dns1", interfaceProperties.dns1);
+ Property::Set("net.dns2", interfaceProperties.dns2);
+ }
+
+ // Bump the DNS change property.
+ char dnschange[Property::VALUE_MAX_LENGTH];
+ Property::Get("net.dnschange", dnschange, "0");
+
+ char num[Property::VALUE_MAX_LENGTH];
+ snprintf(num, Property::VALUE_MAX_LENGTH - 1, "%d", atoi(dnschange) + 1);
+ Property::Set("net.dnschange", num);
+
+ // DNS needs to be set through netd since JellyBean (4.3).
+ if (SDK_VERSION >= 20) {
+ // Lollipop.
+ static CommandFunc COMMAND_CHAIN[] = {
+ setInterfaceDns,
+ addDefaultRouteToNetwork,
+ defaultAsyncSuccessHandler
+ };
+ NetIdManager::NetIdInfo netIdInfo;
+ if (!mNetIdManager.lookup(aOptions.mIfname, &netIdInfo)) {
+ return -1;
+ }
+ aOptions.mNetId = netIdInfo.mNetId;
+ runChain(aOptions, COMMAND_CHAIN, setDnsFail);
+ return CommandResult::Pending();
+ }
+ if (SDK_VERSION >= 18) {
+ // JB, KK.
+ static CommandFunc COMMAND_CHAIN[] = {
+ #if ANDROID_VERSION == 18
+ // Since we don't use per-interface DNS lookup feature on JB,
+ // we need to set the default DNS interface whenever setting the
+ // DNS name server.
+ setDefaultInterface,
+ #endif
+ setInterfaceDns,
+ defaultAsyncSuccessHandler
+ };
+ runChain(aOptions, COMMAND_CHAIN, setDnsFail);
+ return CommandResult::Pending();
+ }
+
+ return SUCCESS;
+}
+
+CommandResult NetworkUtils::configureInterface(NetworkParams& aOptions)
+{
+ NS_ConvertUTF16toUTF8 autoIfname(aOptions.mIfname);
+ return mNetUtils->do_ifc_configure(
+ autoIfname.get(),
+ aOptions.mIpaddr,
+ aOptions.mMask,
+ aOptions.mGateway_long,
+ aOptions.mDns1_long,
+ aOptions.mDns2_long
+ );
+}
+
+CommandResult NetworkUtils::stopDhcp(NetworkParams& aOptions)
+{
+ return mNetUtils->do_dhcp_stop(GET_CHAR(mIfname));
+}
+
+CommandResult NetworkUtils::dhcpRequest(NetworkParams& aOptions) {
+ mozilla::dom::NetworkResultOptions result;
+
+ NS_ConvertUTF16toUTF8 autoIfname(aOptions.mIfname);
+ char ipaddr[Property::VALUE_MAX_LENGTH];
+ char gateway[Property::VALUE_MAX_LENGTH];
+ uint32_t prefixLength;
+ char dns1[Property::VALUE_MAX_LENGTH];
+ char dns2[Property::VALUE_MAX_LENGTH];
+ char server[Property::VALUE_MAX_LENGTH];
+ uint32_t lease;
+ char vendorinfo[Property::VALUE_MAX_LENGTH];
+ int32_t ret = mNetUtils->do_dhcp_do_request(autoIfname.get(),
+ ipaddr,
+ gateway,
+ &prefixLength,
+ dns1,
+ dns2,
+ server,
+ &lease,
+ vendorinfo);
+
+ RETURN_IF_FAILED(ret);
+
+ result.mIpaddr_str = NS_ConvertUTF8toUTF16(ipaddr);
+ result.mGateway_str = NS_ConvertUTF8toUTF16(gateway);
+ result.mDns1_str = NS_ConvertUTF8toUTF16(dns1);
+ result.mDns2_str = NS_ConvertUTF8toUTF16(dns2);
+ result.mServer_str = NS_ConvertUTF8toUTF16(server);
+ result.mVendor_str = NS_ConvertUTF8toUTF16(vendorinfo);
+ result.mLease = lease;
+ result.mPrefixLength = prefixLength;
+ result.mMask = makeMask(prefixLength);
+
+ uint32_t inet4; // only support IPv4 for now.
+
+#define INET_PTON(var, field) \
+ PR_BEGIN_MACRO \
+ inet_pton(AF_INET, var, &inet4); \
+ result.field = inet4; \
+ PR_END_MACRO
+
+ INET_PTON(ipaddr, mIpaddr);
+ INET_PTON(gateway, mGateway);
+
+ if (dns1[0] != '\0') {
+ INET_PTON(dns1, mDns1);
+ }
+
+ if (dns2[0] != '\0') {
+ INET_PTON(dns2, mDns2);
+ }
+
+ INET_PTON(server, mServer);
+
+ char inet_str[64];
+ if (inet_ntop(AF_INET, &result.mMask, inet_str, sizeof(inet_str))) {
+ result.mMask_str = NS_ConvertUTF8toUTF16(inet_str);
+ }
+
+ return result;
+}
+
+CommandResult NetworkUtils::enableInterface(NetworkParams& aOptions) {
+ return mNetUtils->do_ifc_enable(
+ NS_ConvertUTF16toUTF8(aOptions.mIfname).get());
+}
+
+CommandResult NetworkUtils::disableInterface(NetworkParams& aOptions) {
+ return mNetUtils->do_ifc_disable(
+ NS_ConvertUTF16toUTF8(aOptions.mIfname).get());
+}
+
+CommandResult NetworkUtils::resetConnections(NetworkParams& aOptions) {
+ NS_ConvertUTF16toUTF8 autoIfname(aOptions.mIfname);
+ return mNetUtils->do_ifc_reset_connections(
+ NS_ConvertUTF16toUTF8(aOptions.mIfname).get(),
+ RESET_ALL_ADDRESSES);
+}
+
+/**
+ * Set default route and DNS servers for given network interface.
+ */
+CommandResult NetworkUtils::setDefaultRoute(NetworkParams& aOptions)
+{
+ if (SDK_VERSION < 20) {
+ return setDefaultRouteLegacy(aOptions);
+ }
+
+ static CommandFunc COMMAND_CHAIN[] = {
+ addDefaultRouteToNetwork,
+ setDefaultNetwork,
+ defaultAsyncSuccessHandler,
+ };
+
+ NetIdManager::NetIdInfo netIdInfo;
+ if (!mNetIdManager.lookup(GET_FIELD(mIfname), &netIdInfo)) {
+ ERROR("No such interface");
+ return -1;
+ }
+
+ aOptions.mNetId = netIdInfo.mNetId;
+ aOptions.mLoopIndex = 0;
+ runChain(aOptions, COMMAND_CHAIN, defaultAsyncFailureHandler);
+
+ return CommandResult::Pending();
+}
+
+/**
+ * Set default route and DNS servers for given network interface by obsoleted libnetutils.
+ */
+CommandResult NetworkUtils::setDefaultRouteLegacy(NetworkParams& aOptions)
+{
+ NS_ConvertUTF16toUTF8 autoIfname(aOptions.mIfname);
+
+ uint32_t length = aOptions.mGateways.Length();
+ if (length > 0) {
+ for (uint32_t i = 0; i < length; i++) {
+ NS_ConvertUTF16toUTF8 autoGateway(aOptions.mGateways[i]);
+
+ int type = getIpType(autoGateway.get());
+ if (type != AF_INET && type != AF_INET6) {
+ continue;
+ }
+
+ if (type == AF_INET6) {
+ RETURN_IF_FAILED(mNetUtils->do_ifc_add_route(autoIfname.get(), "::", 0, autoGateway.get()));
+ } else { /* type == AF_INET */
+ RETURN_IF_FAILED(mNetUtils->do_ifc_set_default_route(autoIfname.get(), inet_addr(autoGateway.get())));
+ }
+ }
+ } else {
+ // Set default froute from system properties.
+ char key[Property::KEY_MAX_LENGTH];
+ char gateway[Property::KEY_MAX_LENGTH];
+
+ snprintf(key, sizeof key - 1, "net.%s.gw", autoIfname.get());
+ Property::Get(key, gateway, "");
+
+ int type = getIpType(gateway);
+ if (type != AF_INET && type != AF_INET6) {
+ return EAFNOSUPPORT;
+ }
+
+ if (type == AF_INET6) {
+ RETURN_IF_FAILED(mNetUtils->do_ifc_add_route(autoIfname.get(), "::", 0, gateway));
+ } else { /* type == AF_INET */
+ RETURN_IF_FAILED(mNetUtils->do_ifc_set_default_route(autoIfname.get(), inet_addr(gateway)));
+ }
+ }
+
+ // Set the default DNS interface.
+ if (SDK_VERSION >= 18) {
+ // For JB, KK only.
+ static CommandFunc COMMAND_CHAIN[] = {
+ setDefaultInterface,
+ defaultAsyncSuccessHandler
+ };
+ runChain(aOptions, COMMAND_CHAIN, setDnsFail);
+ return CommandResult::Pending();
+ }
+
+ return SUCCESS;
+}
+
+/**
+ * Remove default route for given network interface.
+ */
+CommandResult NetworkUtils::removeDefaultRoute(NetworkParams& aOptions)
+{
+ NU_DBG("Calling NetworkUtils::removeDefaultRoute");
+
+ if (SDK_VERSION < 20) {
+ return removeDefaultRouteLegacy(aOptions);
+ }
+
+ static CommandFunc COMMAND_CHAIN[] = {
+ removeDefaultRoute,
+ defaultAsyncSuccessHandler,
+ };
+
+ NetIdManager::NetIdInfo netIdInfo;
+ if (!mNetIdManager.lookup(GET_FIELD(mIfname), &netIdInfo)) {
+ ERROR("No such interface: %s", GET_CHAR(mIfname));
+ return -1;
+ }
+
+ NU_DBG("Obtained netid %d for interface %s", netIdInfo.mNetId, GET_CHAR(mIfname));
+
+ aOptions.mNetId = netIdInfo.mNetId;
+ aOptions.mLoopIndex = 0;
+ runChain(aOptions, COMMAND_CHAIN, defaultAsyncFailureHandler);
+
+ return CommandResult::Pending();
+}
+
+/**
+ * Remove default route for given network interface by obsoleted libnetutils.
+ */
+CommandResult NetworkUtils::removeDefaultRouteLegacy(NetworkParams& aOptions)
+{
+ // Legacy libnetutils calls before Lollipop.
+ uint32_t length = aOptions.mGateways.Length();
+ for (uint32_t i = 0; i < length; i++) {
+ NS_ConvertUTF16toUTF8 autoGateway(aOptions.mGateways[i]);
+
+ int type = getIpType(autoGateway.get());
+ if (type != AF_INET && type != AF_INET6) {
+ return EAFNOSUPPORT;
+ }
+
+ WARN_IF_FAILED(mNetUtils->do_ifc_remove_route(GET_CHAR(mIfname),
+ type == AF_INET ? "0.0.0.0" : "::",
+ 0, autoGateway.get()));
+ }
+
+ return SUCCESS;
+}
+
+/**
+ * Add host route for given network interface.
+ */
+CommandResult NetworkUtils::addHostRoute(NetworkParams& aOptions)
+{
+ if (SDK_VERSION < 20) {
+ return addHostRouteLegacy(aOptions);
+ }
+
+ static CommandFunc COMMAND_CHAIN[] = {
+ addRouteToInterface,
+ defaultAsyncSuccessHandler,
+ };
+
+ NetIdManager::NetIdInfo netIdInfo;
+ if (!mNetIdManager.lookup(GET_FIELD(mIfname), &netIdInfo)) {
+ ERROR("No such interface: %s", GET_CHAR(mIfname));
+ return -1;
+ }
+
+ NU_DBG("Obtained netid %d for interface %s", netIdInfo.mNetId, GET_CHAR(mIfname));
+
+ aOptions.mNetId = netIdInfo.mNetId;
+ runChain(aOptions, COMMAND_CHAIN, defaultAsyncFailureHandler);
+
+ return CommandResult::Pending();
+}
+
+/**
+ * Add host route for given network interface.
+ */
+CommandResult NetworkUtils::addHostRouteLegacy(NetworkParams& aOptions)
+{
+ if (aOptions.mGateway.IsEmpty()) {
+ ERROR("addHostRouteLegacy does not support empty gateway.");
+ return EINVAL;
+ }
+
+ NS_ConvertUTF16toUTF8 autoIfname(aOptions.mIfname);
+ NS_ConvertUTF16toUTF8 autoHostname(aOptions.mIp);
+ NS_ConvertUTF16toUTF8 autoGateway(aOptions.mGateway);
+ int type, prefix;
+
+ type = getIpType(autoHostname.get());
+ if (type != AF_INET && type != AF_INET6) {
+ return EAFNOSUPPORT;
+ }
+
+ if (type != getIpType(autoGateway.get())) {
+ return EINVAL;
+ }
+
+ prefix = type == AF_INET ? 32 : 128;
+ return mNetUtils->do_ifc_add_route(autoIfname.get(), autoHostname.get(),
+ prefix, autoGateway.get());
+}
+
+/**
+ * Remove host route for given network interface.
+ */
+CommandResult NetworkUtils::removeHostRoute(NetworkParams& aOptions)
+{
+ if (SDK_VERSION < 20) {
+ return removeHostRouteLegacy(aOptions);
+ }
+
+ static CommandFunc COMMAND_CHAIN[] = {
+ removeRouteFromInterface,
+ defaultAsyncSuccessHandler,
+ };
+
+ NetIdManager::NetIdInfo netIdInfo;
+ if (!mNetIdManager.lookup(GET_FIELD(mIfname), &netIdInfo)) {
+ ERROR("No such interface: %s", GET_CHAR(mIfname));
+ return -1;
+ }
+
+ NU_DBG("Obtained netid %d for interface %s", netIdInfo.mNetId, GET_CHAR(mIfname));
+
+ aOptions.mNetId = netIdInfo.mNetId;
+ runChain(aOptions, COMMAND_CHAIN, defaultAsyncFailureHandler);
+
+ return CommandResult::Pending();
+}
+
+/**
+ * Remove host route for given network interface.
+ */
+CommandResult NetworkUtils::removeHostRouteLegacy(NetworkParams& aOptions)
+{
+ NS_ConvertUTF16toUTF8 autoIfname(aOptions.mIfname);
+ NS_ConvertUTF16toUTF8 autoHostname(aOptions.mIp);
+ NS_ConvertUTF16toUTF8 autoGateway(aOptions.mGateway);
+ int type, prefix;
+
+ type = getIpType(autoHostname.get());
+ if (type != AF_INET && type != AF_INET6) {
+ return EAFNOSUPPORT;
+ }
+
+ if (type != getIpType(autoGateway.get())) {
+ return EINVAL;
+ }
+
+ prefix = type == AF_INET ? 32 : 128;
+ return mNetUtils->do_ifc_remove_route(autoIfname.get(), autoHostname.get(),
+ prefix, autoGateway.get());
+}
+
+CommandResult NetworkUtils::removeNetworkRoute(NetworkParams& aOptions)
+{
+ if (SDK_VERSION < 20) {
+ return removeNetworkRouteLegacy(aOptions);
+ }
+
+ static CommandFunc COMMAND_CHAIN[] = {
+ clearAddrForInterface,
+ defaultAsyncSuccessHandler,
+ };
+
+ NetIdManager::NetIdInfo netIdInfo;
+ if (!mNetIdManager.lookup(GET_FIELD(mIfname), &netIdInfo)) {
+ ERROR("interface %s is not present in any network", GET_CHAR(mIfname));
+ return -1;
+ }
+
+ NU_DBG("Obtained netid %d for interface %s", netIdInfo.mNetId, GET_CHAR(mIfname));
+
+ aOptions.mNetId = netIdInfo.mNetId;
+ runChain(aOptions, COMMAND_CHAIN, defaultAsyncFailureHandler);
+
+ return CommandResult::Pending();
+}
+
+nsCString NetworkUtils::getSubnetIp(const nsCString& aIp, int aPrefixLength)
+{
+ int type = getIpType(aIp.get());
+
+ if (AF_INET6 == type) {
+ struct in6_addr in6;
+ if (inet_pton(AF_INET6, aIp.get(), &in6) != 1) {
+ return nsCString();
+ }
+
+ uint32_t p, i, p1, mask;
+ p = aPrefixLength;
+ i = 0;
+ while (i < 4) {
+ p1 = p > 32 ? 32 : p;
+ p -= p1;
+ mask = p1 ? ~0x0 << (32 - p1) : 0;
+ in6.s6_addr32[i++] &= htonl(mask);
+ }
+
+ char subnetStr[INET6_ADDRSTRLEN];
+ if (!inet_ntop(AF_INET6, &in6, subnetStr, sizeof subnetStr)) {
+ return nsCString();
+ }
+
+ return nsCString(subnetStr);
+ }
+
+ if (AF_INET == type) {
+ uint32_t ip = inet_addr(aIp.get());
+ uint32_t netmask = makeMask(aPrefixLength);
+ uint32_t subnet = ip & netmask;
+ struct in_addr addr;
+ addr.s_addr = subnet;
+ return nsCString(inet_ntoa(addr));
+ }
+
+ return nsCString();
+}
+
+CommandResult NetworkUtils::removeNetworkRouteLegacy(NetworkParams& aOptions)
+{
+ NS_ConvertUTF16toUTF8 autoIfname(aOptions.mIfname);
+ NS_ConvertUTF16toUTF8 autoIp(aOptions.mIp);
+
+ int type = getIpType(autoIp.get());
+ if (type != AF_INET && type != AF_INET6) {
+ return EAFNOSUPPORT;
+ }
+
+ uint32_t prefixLength = GET_FIELD(mPrefixLength);
+
+ if (type == AF_INET6) {
+ // Calculate subnet.
+ struct in6_addr in6;
+ if (inet_pton(AF_INET6, autoIp.get(), &in6) != 1) {
+ return EINVAL;
+ }
+
+ uint32_t p, i, p1, mask;
+ p = prefixLength;
+ i = 0;
+ while (i < 4) {
+ p1 = p > 32 ? 32 : p;
+ p -= p1;
+ mask = p1 ? ~0x0 << (32 - p1) : 0;
+ in6.s6_addr32[i++] &= htonl(mask);
+ }
+
+ char subnetStr[INET6_ADDRSTRLEN];
+ if (!inet_ntop(AF_INET6, &in6, subnetStr, sizeof subnetStr)) {
+ return EINVAL;
+ }
+
+ // Remove default route.
+ WARN_IF_FAILED(mNetUtils->do_ifc_remove_route(autoIfname.get(), "::", 0, NULL));
+
+ // Remove subnet route.
+ RETURN_IF_FAILED(mNetUtils->do_ifc_remove_route(autoIfname.get(), subnetStr, prefixLength, NULL));
+ return SUCCESS;
+ }
+
+ /* type == AF_INET */
+ uint32_t ip = inet_addr(autoIp.get());
+ uint32_t netmask = makeMask(prefixLength);
+ uint32_t subnet = ip & netmask;
+ const char* gateway = "0.0.0.0";
+ struct in_addr addr;
+ addr.s_addr = subnet;
+ const char* dst = inet_ntoa(addr);
+
+ RETURN_IF_FAILED(mNetUtils->do_ifc_remove_default_route(autoIfname.get()));
+ RETURN_IF_FAILED(mNetUtils->do_ifc_remove_route(autoIfname.get(), dst, prefixLength, gateway));
+ return SUCCESS;
+}
+
+CommandResult NetworkUtils::addSecondaryRoute(NetworkParams& aOptions)
+{
+ static CommandFunc COMMAND_CHAIN[] = {
+ addRouteToSecondaryTable,
+ defaultAsyncSuccessHandler
+ };
+
+ if (SDK_VERSION >= 20) {
+ NetIdManager::NetIdInfo netIdInfo;
+ if (!mNetIdManager.lookup(aOptions.mIfname, &netIdInfo)) {
+ return -1;
+ }
+ aOptions.mNetId = netIdInfo.mNetId;
+ }
+
+ runChain(aOptions, COMMAND_CHAIN, defaultAsyncFailureHandler);
+ return CommandResult::Pending();
+}
+
+CommandResult NetworkUtils::removeSecondaryRoute(NetworkParams& aOptions)
+{
+ static CommandFunc COMMAND_CHAIN[] = {
+ removeRouteFromSecondaryTable,
+ defaultAsyncSuccessHandler
+ };
+
+ if (SDK_VERSION >= 20) {
+ NetIdManager::NetIdInfo netIdInfo;
+ if (!mNetIdManager.lookup(aOptions.mIfname, &netIdInfo)) {
+ return -1;
+ }
+ aOptions.mNetId = netIdInfo.mNetId;
+ }
+
+ runChain(aOptions, COMMAND_CHAIN, defaultAsyncFailureHandler);
+ return CommandResult::Pending();
+}
+
+CommandResult NetworkUtils::setNetworkInterfaceAlarm(NetworkParams& aOptions)
+{
+ NU_DBG("setNetworkInterfaceAlarms: %s", GET_CHAR(mIfname));
+ runChain(aOptions, sNetworkInterfaceSetAlarmChain, networkInterfaceAlarmFail);
+ return CommandResult::Pending();
+}
+
+CommandResult NetworkUtils::enableNetworkInterfaceAlarm(NetworkParams& aOptions)
+{
+ NU_DBG("enableNetworkInterfaceAlarm: %s", GET_CHAR(mIfname));
+ runChain(aOptions, sNetworkInterfaceEnableAlarmChain, networkInterfaceAlarmFail);
+ return CommandResult::Pending();
+}
+
+CommandResult NetworkUtils::disableNetworkInterfaceAlarm(NetworkParams& aOptions)
+{
+ NU_DBG("disableNetworkInterfaceAlarms: %s", GET_CHAR(mIfname));
+ runChain(aOptions, sNetworkInterfaceDisableAlarmChain, networkInterfaceAlarmFail);
+ return CommandResult::Pending();
+}
+
+CommandResult NetworkUtils::setTetheringAlarm(NetworkParams& aOptions)
+{
+ NU_DBG("setTetheringAlarm");
+ runChain(aOptions, sTetheringInterfaceSetAlarmChain, networkInterfaceAlarmFail);
+ return CommandResult::Pending();
+}
+
+CommandResult NetworkUtils::removeTetheringAlarm(NetworkParams& aOptions)
+{
+ NU_DBG("removeTetheringAlarm");
+ runChain(aOptions, sTetheringInterfaceRemoveAlarmChain, networkInterfaceAlarmFail);
+ return CommandResult::Pending();
+}
+
+CommandResult NetworkUtils::getTetheringStatus(NetworkParams& aOptions)
+{
+ NU_DBG("getTetheringStatus");
+ runChain(aOptions, sTetheringGetStatusChain, networkInterfaceAlarmFail);
+ return CommandResult::Pending();
+}
+
+/**
+ * handling main thread's reload Wifi firmware request
+ */
+CommandResult NetworkUtils::setWifiOperationMode(NetworkParams& aOptions)
+{
+ NU_DBG("setWifiOperationMode: %s %s", GET_CHAR(mIfname), GET_CHAR(mMode));
+ runChain(aOptions, sWifiOperationModeChain, wifiOperationModeFail);
+ return CommandResult::Pending();
+}
+
+/**
+ * handling main thread's enable/disable WiFi Tethering request
+ */
+CommandResult NetworkUtils::setWifiTethering(NetworkParams& aOptions)
+{
+ bool enable = aOptions.mEnable;
+ IFProperties interfaceProperties;
+ getIFProperties(GET_CHAR(mExternalIfname), interfaceProperties);
+
+ if (strcmp(interfaceProperties.dns1, "")) {
+ int type = getIpType(interfaceProperties.dns1);
+ if (type != AF_INET6) {
+ aOptions.mDns1 = NS_ConvertUTF8toUTF16(interfaceProperties.dns1);
+ }
+ }
+ if (strcmp(interfaceProperties.dns2, "")) {
+ int type = getIpType(interfaceProperties.dns2);
+ if (type != AF_INET6) {
+ aOptions.mDns2 = NS_ConvertUTF8toUTF16(interfaceProperties.dns2);
+ }
+ }
+ dumpParams(aOptions, "WIFI");
+
+ if (SDK_VERSION >= 20) {
+ NetIdManager::NetIdInfo netIdInfo;
+ if (!mNetIdManager.lookup(aOptions.mExternalIfname, &netIdInfo)) {
+ ERROR("No such interface: %s", GET_CHAR(mExternalIfname));
+ return -1;
+ }
+ aOptions.mNetId = netIdInfo.mNetId;
+ }
+
+ if (enable) {
+ NU_DBG("Starting Wifi Tethering on %s <-> %s",
+ GET_CHAR(mInternalIfname), GET_CHAR(mExternalIfname));
+ runChain(aOptions, sWifiEnableChain, wifiTetheringFail);
+ } else {
+ NU_DBG("Stopping Wifi Tethering on %s <-> %s",
+ GET_CHAR(mInternalIfname), GET_CHAR(mExternalIfname));
+ runChain(aOptions, sWifiDisableChain, wifiTetheringFail);
+ }
+ return CommandResult::Pending();
+}
+
+CommandResult NetworkUtils::setUSBTethering(NetworkParams& aOptions)
+{
+ bool enable = aOptions.mEnable;
+ IFProperties interfaceProperties;
+ getIFProperties(GET_CHAR(mExternalIfname), interfaceProperties);
+
+ if (strcmp(interfaceProperties.dns1, "")) {
+ int type = getIpType(interfaceProperties.dns1);
+ if (type != AF_INET6) {
+ aOptions.mDns1 = NS_ConvertUTF8toUTF16(interfaceProperties.dns1);
+ }
+ }
+ if (strcmp(interfaceProperties.dns2, "")) {
+ int type = getIpType(interfaceProperties.dns2);
+ if (type != AF_INET6) {
+ aOptions.mDns2 = NS_ConvertUTF8toUTF16(interfaceProperties.dns2);
+ }
+ }
+ dumpParams(aOptions, "USB");
+
+ if (SDK_VERSION >= 20) {
+ NetIdManager::NetIdInfo netIdInfo;
+ if (!mNetIdManager.lookup(aOptions.mExternalIfname, &netIdInfo)) {
+ ERROR("No such interface: %s", GET_CHAR(mExternalIfname));
+ return -1;
+ }
+ aOptions.mNetId = netIdInfo.mNetId;
+ }
+
+ if (enable) {
+ NU_DBG("Starting USB Tethering on %s <-> %s",
+ GET_CHAR(mInternalIfname), GET_CHAR(mExternalIfname));
+ runChain(aOptions, sUSBEnableChain, usbTetheringFail);
+ } else {
+ NU_DBG("Stopping USB Tethering on %s <-> %s",
+ GET_CHAR(mInternalIfname), GET_CHAR(mExternalIfname));
+ runChain(aOptions, sUSBDisableChain, usbTetheringFail);
+ }
+ return CommandResult::Pending();
+}
+
+void NetworkUtils::escapeQuote(nsCString& aString)
+{
+ aString.ReplaceSubstring("\\", "\\\\");
+ aString.ReplaceSubstring("\"", "\\\"");
+}
+
+CommandResult NetworkUtils::checkUsbRndisState(NetworkParams& aOptions)
+{
+ static uint32_t retry = 0;
+
+ char currentState[Property::VALUE_MAX_LENGTH];
+ Property::Get(SYS_USB_STATE_PROPERTY, currentState, nullptr);
+
+ nsTArray<nsCString> stateFuncs;
+ split(currentState, USB_CONFIG_DELIMIT, stateFuncs);
+ bool rndisPresent = stateFuncs.Contains(nsCString(USB_FUNCTION_RNDIS));
+
+ if (aOptions.mEnable == rndisPresent) {
+ NetworkResultOptions result;
+ result.mEnable = aOptions.mEnable;
+ result.mResult = true;
+ retry = 0;
+ return result;
+ }
+ if (retry < USB_FUNCTION_RETRY_TIMES) {
+ retry++;
+ usleep(USB_FUNCTION_RETRY_INTERVAL * 1000);
+ return checkUsbRndisState(aOptions);
+ }
+
+ NetworkResultOptions result;
+ result.mResult = false;
+ retry = 0;
+ return result;
+}
+
+/**
+ * Modify usb function's property to turn on USB RNDIS function
+ */
+CommandResult NetworkUtils::enableUsbRndis(NetworkParams& aOptions)
+{
+ bool report = aOptions.mReport;
+
+ // For some reason, rndis doesn't play well with diag,modem,nmea.
+ // So when turning rndis on, we set sys.usb.config to either "rndis"
+ // or "rndis,adb". When turning rndis off, we go back to
+ // persist.sys.usb.config.
+ //
+ // On the otoro/unagi, persist.sys.usb.config should be one of:
+ //
+ // diag,modem,nmea,mass_storage
+ // diag,modem,nmea,mass_storage,adb
+ //
+ // When rndis is enabled, sys.usb.config should be one of:
+ //
+ // rdnis
+ // rndis,adb
+ //
+ // and when rndis is disabled, it should revert to persist.sys.usb.config
+
+ char currentConfig[Property::VALUE_MAX_LENGTH];
+ Property::Get(SYS_USB_CONFIG_PROPERTY, currentConfig, nullptr);
+
+ nsTArray<nsCString> configFuncs;
+ split(currentConfig, USB_CONFIG_DELIMIT, configFuncs);
+
+ char persistConfig[Property::VALUE_MAX_LENGTH];
+ Property::Get(PERSIST_SYS_USB_CONFIG_PROPERTY, persistConfig, nullptr);
+
+ nsTArray<nsCString> persistFuncs;
+ split(persistConfig, USB_CONFIG_DELIMIT, persistFuncs);
+
+ if (aOptions.mEnable) {
+ configFuncs.Clear();
+ configFuncs.AppendElement(nsCString(USB_FUNCTION_RNDIS));
+ if (persistFuncs.Contains(nsCString(USB_FUNCTION_ADB))) {
+ configFuncs.AppendElement(nsCString(USB_FUNCTION_ADB));
+ }
+ } else {
+ // We're turning rndis off, revert back to the persist setting.
+ // adb will already be correct there, so we don't need to do any
+ // further adjustments.
+ configFuncs = persistFuncs;
+ }
+
+ char newConfig[Property::VALUE_MAX_LENGTH] = "";
+ Property::Get(SYS_USB_CONFIG_PROPERTY, currentConfig, nullptr);
+ join(configFuncs, USB_CONFIG_DELIMIT, Property::VALUE_MAX_LENGTH, newConfig);
+ if (strcmp(currentConfig, newConfig)) {
+ Property::Set(SYS_USB_CONFIG_PROPERTY, newConfig);
+ }
+
+ // Trigger the timer to check usb state and report the result to NetworkManager.
+ if (report) {
+ usleep(USB_FUNCTION_RETRY_INTERVAL * 1000);
+ return checkUsbRndisState(aOptions);
+ }
+ return SUCCESS;
+}
+
+/**
+ * handling upstream interface change event.
+ */
+CommandResult NetworkUtils::updateUpStream(NetworkParams& aOptions)
+{
+ runChain(aOptions, sUpdateUpStreamChain, updateUpStreamFail);
+ return CommandResult::Pending();
+}
+
+/**
+ * handling upstream interface change event.
+ */
+CommandResult NetworkUtils::createNetwork(NetworkParams& aOptions)
+{
+ if (SDK_VERSION < 20) {
+ return SUCCESS;
+ }
+
+ static CommandFunc COMMAND_CHAIN[] = {
+ createNetwork,
+ enableIpv6,
+ addInterfaceToNetwork,
+ defaultAsyncSuccessHandler,
+ };
+
+ NetIdManager::NetIdInfo netIdInfo;
+ mNetIdManager.acquire(GET_FIELD(mIfname), &netIdInfo);
+ if (netIdInfo.mRefCnt > 1) {
+ // Already created. Just return.
+ NU_DBG("Interface %s (%d) has been created.", GET_CHAR(mIfname),
+ netIdInfo.mNetId);
+ return SUCCESS;
+ }
+
+ NU_DBG("Request netd to create a network with netid %d", netIdInfo.mNetId);
+ // Newly created netid. Ask netd to create network.
+ aOptions.mNetId = netIdInfo.mNetId;
+ runChain(aOptions, COMMAND_CHAIN, defaultAsyncFailureHandler);
+
+ return CommandResult::Pending();
+}
+
+/**
+ * handling upstream interface change event.
+ */
+CommandResult NetworkUtils::destroyNetwork(NetworkParams& aOptions)
+{
+ if (SDK_VERSION < 20) {
+ return SUCCESS;
+ }
+
+ static CommandFunc COMMAND_CHAIN[] = {
+ disableIpv6,
+ destroyNetwork,
+ defaultAsyncSuccessHandler,
+ };
+
+ NetIdManager::NetIdInfo netIdInfo;
+ if (!mNetIdManager.release(GET_FIELD(mIfname), &netIdInfo)) {
+ ERROR("No existing netid for %s", GET_CHAR(mIfname));
+ return -1;
+ }
+
+ if (netIdInfo.mRefCnt > 0) {
+ // Still be referenced. Just return.
+ NU_DBG("Someone is still using this interface.");
+ return SUCCESS;
+ }
+
+ NU_DBG("Interface %s (%d) is no longer used. Tell netd to destroy.",
+ GET_CHAR(mIfname), netIdInfo.mNetId);
+
+ aOptions.mNetId = netIdInfo.mNetId;
+ runChain(aOptions, COMMAND_CHAIN, defaultAsyncFailureHandler);
+ return CommandResult::Pending();
+}
+
+/**
+ * Query the netId associated with the given network interface name.
+ */
+CommandResult NetworkUtils::getNetId(NetworkParams& aOptions)
+{
+ NetworkResultOptions result;
+
+ if (SDK_VERSION < 20) {
+ // For pre-Lollipop, use the interface name as the fallback.
+ result.mNetId = GET_FIELD(mIfname);
+ return result;
+ }
+
+ NetIdManager::NetIdInfo netIdInfo;
+ if (-1 == mNetIdManager.lookup(GET_FIELD(mIfname), &netIdInfo)) {
+ return ESRCH;
+ }
+ result.mNetId.AppendInt(netIdInfo.mNetId, 10);
+ return result;
+}
+
+/**
+ * Get existing network interfaces.
+ */
+CommandResult NetworkUtils::getInterfaces(NetworkParams& aOptions)
+{
+ runChain(aOptions, sGetInterfacesChain, getInterfacesFail);
+ return CommandResult::Pending();
+}
+
+/**
+ * Get network config of a specified interface.
+ */
+CommandResult NetworkUtils::getInterfaceConfig(NetworkParams& aOptions)
+{
+ runChain(aOptions, sGetInterfaceConfigChain, getInterfaceConfigFail);
+ return CommandResult::Pending();
+}
+
+/**
+ * Set network config for a specified interface.
+ */
+CommandResult NetworkUtils::setInterfaceConfig(NetworkParams& aOptions)
+{
+ runChain(aOptions, sSetInterfaceConfigChain, setInterfaceConfigFail);
+ return CommandResult::Pending();
+}
+
+CommandResult NetworkUtils::setMtu(NetworkParams& aOptions)
+{
+ // Setting/getting mtu is supported since Kitkat.
+ if (SDK_VERSION < 19) {
+ ERROR("setMtu is not supported in current SDK_VERSION.");
+ return -1;
+ }
+
+ static CommandFunc COMMAND_CHAIN[] = {
+ setMtu,
+ defaultAsyncSuccessHandler,
+ };
+
+ runChain(aOptions, COMMAND_CHAIN, defaultAsyncFailureHandler);
+ return CommandResult::Pending();
+}
+
+void NetworkUtils::sendBroadcastMessage(uint32_t code, char* reason)
+{
+ NetworkResultOptions result;
+ switch(code) {
+ case NETD_COMMAND_INTERFACE_CHANGE:
+ result.mTopic = NS_ConvertUTF8toUTF16("netd-interface-change");
+ break;
+ case NETD_COMMAND_BANDWIDTH_CONTROLLER:
+ result.mTopic = NS_ConvertUTF8toUTF16("netd-bandwidth-control");
+ break;
+ default:
+ return;
+ }
+
+ result.mBroadcast = true;
+ result.mReason = NS_ConvertUTF8toUTF16(reason);
+ postMessage(result);
+}
+
+inline uint32_t NetworkUtils::netdResponseType(uint32_t code)
+{
+ return (code / 100) * 100;
+}
+
+inline bool NetworkUtils::isBroadcastMessage(uint32_t code)
+{
+ uint32_t type = netdResponseType(code);
+ return type == NETD_COMMAND_UNSOLICITED;
+}
+
+inline bool NetworkUtils::isError(uint32_t code)
+{
+ uint32_t type = netdResponseType(code);
+ return type != NETD_COMMAND_PROCEEDING && type != NETD_COMMAND_OKAY;
+}
+
+inline bool NetworkUtils::isComplete(uint32_t code)
+{
+ uint32_t type = netdResponseType(code);
+ return type != NETD_COMMAND_PROCEEDING;
+}
+
+inline bool NetworkUtils::isProceeding(uint32_t code)
+{
+ uint32_t type = netdResponseType(code);
+ return type == NETD_COMMAND_PROCEEDING;
+}
+
+void NetworkUtils::dumpParams(NetworkParams& aOptions, const char* aType)
+{
+#ifdef _DEBUG
+ NU_DBG("Dump params:");
+ NU_DBG(" ifname: %s", GET_CHAR(mIfname));
+ NU_DBG(" ip: %s", GET_CHAR(mIp));
+ NU_DBG(" link: %s", GET_CHAR(mLink));
+ NU_DBG(" prefix: %s", GET_CHAR(mPrefix));
+ NU_DBG(" wifiStartIp: %s", GET_CHAR(mWifiStartIp));
+ NU_DBG(" wifiEndIp: %s", GET_CHAR(mWifiEndIp));
+ NU_DBG(" usbStartIp: %s", GET_CHAR(mUsbStartIp));
+ NU_DBG(" usbEndIp: %s", GET_CHAR(mUsbEndIp));
+ NU_DBG(" dnsserver1: %s", GET_CHAR(mDns1));
+ NU_DBG(" dnsserver2: %s", GET_CHAR(mDns2));
+ NU_DBG(" internalIfname: %s", GET_CHAR(mInternalIfname));
+ NU_DBG(" externalIfname: %s", GET_CHAR(mExternalIfname));
+ if (!strcmp(aType, "WIFI")) {
+ NU_DBG(" wifictrlinterfacename: %s", GET_CHAR(mWifictrlinterfacename));
+ NU_DBG(" ssid: %s", GET_CHAR(mSsid));
+ NU_DBG(" security: %s", GET_CHAR(mSecurity));
+ NU_DBG(" key: %s", GET_CHAR(mKey));
+ }
+#endif
+}
+
+#undef GET_CHAR
+#undef GET_FIELD
diff --git a/dom/system/gonk/NetworkUtils.h b/dom/system/gonk/NetworkUtils.h
new file mode 100644
index 000000000..d1af35f09
--- /dev/null
+++ b/dom/system/gonk/NetworkUtils.h
@@ -0,0 +1,498 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef NetworkUtils_h
+#define NetworkUtils_h
+
+#include "nsAutoPtr.h"
+#include "nsString.h"
+#include "mozilla/dom/NetworkOptionsBinding.h"
+#include "mozilla/dom/network/NetUtils.h"
+#include "mozilla/ipc/Netd.h"
+#include "nsTArray.h"
+#include "NetIdManager.h"
+
+class NetworkParams;
+class CommandChain;
+
+class CommandCallback {
+public:
+ typedef void (*CallbackType)(CommandChain*, bool,
+ mozilla::dom::NetworkResultOptions& aResult);
+
+ typedef void (*CallbackWrapperType)(CallbackType aOriginalCallback,
+ CommandChain*, bool,
+ mozilla::dom::NetworkResultOptions& aResult);
+
+ CommandCallback()
+ : mCallback(nullptr)
+ , mCallbackWrapper(nullptr)
+ {
+ }
+
+ CommandCallback(CallbackType aCallback)
+ : mCallback(aCallback)
+ , mCallbackWrapper(nullptr)
+ {
+ }
+
+ CommandCallback(CallbackWrapperType aCallbackWrapper,
+ CommandCallback aOriginalCallback)
+ : mCallback(aOriginalCallback.mCallback)
+ , mCallbackWrapper(aCallbackWrapper)
+ {
+ }
+
+ void operator()(CommandChain* aChain, bool aError,
+ mozilla::dom::NetworkResultOptions& aResult)
+ {
+ if (mCallbackWrapper) {
+ return mCallbackWrapper(mCallback, aChain, aError, aResult);
+ }
+ if (mCallback) {
+ return mCallback(aChain, aError, aResult);
+ }
+ }
+
+private:
+ CallbackType mCallback;
+ CallbackWrapperType mCallbackWrapper;
+};
+
+typedef void (*CommandFunc)(CommandChain*, CommandCallback,
+ mozilla::dom::NetworkResultOptions& aResult);
+typedef void (*MessageCallback)(mozilla::dom::NetworkResultOptions& aResult);
+typedef void (*ErrorCallback)(NetworkParams& aOptions,
+ mozilla::dom::NetworkResultOptions& aResult);
+
+class NetworkParams
+{
+public:
+ NetworkParams() {
+ }
+
+ NetworkParams(const mozilla::dom::NetworkCommandOptions& aOther) {
+
+#define COPY_SEQUENCE_FIELD(prop, type) \
+ if (aOther.prop.WasPassed()) { \
+ mozilla::dom::Sequence<type > const & currentValue = aOther.prop.InternalValue(); \
+ uint32_t length = currentValue.Length(); \
+ for (uint32_t idx = 0; idx < length; idx++) { \
+ prop.AppendElement(currentValue[idx]); \
+ } \
+ }
+
+#define COPY_OPT_STRING_FIELD(prop, defaultValue) \
+ if (aOther.prop.WasPassed()) { \
+ if (aOther.prop.Value().EqualsLiteral("null")) { \
+ prop = defaultValue; \
+ } else { \
+ prop = aOther.prop.Value(); \
+ } \
+ } else { \
+ prop = defaultValue; \
+ }
+
+#define COPY_OPT_FIELD(prop, defaultValue) \
+ if (aOther.prop.WasPassed()) { \
+ prop = aOther.prop.Value(); \
+ } else { \
+ prop = defaultValue; \
+ }
+
+#define COPY_FIELD(prop) prop = aOther.prop;
+
+ COPY_FIELD(mId)
+ COPY_FIELD(mCmd)
+ COPY_OPT_STRING_FIELD(mDomain, EmptyString())
+ COPY_OPT_STRING_FIELD(mGateway, EmptyString())
+ COPY_SEQUENCE_FIELD(mGateways, nsString)
+ COPY_OPT_STRING_FIELD(mIfname, EmptyString())
+ COPY_OPT_STRING_FIELD(mIp, EmptyString())
+ COPY_OPT_FIELD(mPrefixLength, 0)
+ COPY_OPT_STRING_FIELD(mMode, EmptyString())
+ COPY_OPT_FIELD(mReport, false)
+ COPY_OPT_FIELD(mEnabled, false)
+ COPY_OPT_STRING_FIELD(mWifictrlinterfacename, EmptyString())
+ COPY_OPT_STRING_FIELD(mInternalIfname, EmptyString())
+ COPY_OPT_STRING_FIELD(mExternalIfname, EmptyString())
+ COPY_OPT_FIELD(mEnable, false)
+ COPY_OPT_STRING_FIELD(mSsid, EmptyString())
+ COPY_OPT_STRING_FIELD(mSecurity, EmptyString())
+ COPY_OPT_STRING_FIELD(mKey, EmptyString())
+ COPY_OPT_STRING_FIELD(mPrefix, EmptyString())
+ COPY_OPT_STRING_FIELD(mLink, EmptyString())
+ COPY_SEQUENCE_FIELD(mInterfaceList, nsString)
+ COPY_OPT_STRING_FIELD(mWifiStartIp, EmptyString())
+ COPY_OPT_STRING_FIELD(mWifiEndIp, EmptyString())
+ COPY_OPT_STRING_FIELD(mUsbStartIp, EmptyString())
+ COPY_OPT_STRING_FIELD(mUsbEndIp, EmptyString())
+ COPY_OPT_STRING_FIELD(mDns1, EmptyString())
+ COPY_OPT_STRING_FIELD(mDns2, EmptyString())
+ COPY_SEQUENCE_FIELD(mDnses, nsString)
+ COPY_OPT_STRING_FIELD(mStartIp, EmptyString())
+ COPY_OPT_STRING_FIELD(mEndIp, EmptyString())
+ COPY_OPT_STRING_FIELD(mServerIp, EmptyString())
+ COPY_OPT_STRING_FIELD(mMaskLength, EmptyString())
+ COPY_OPT_STRING_FIELD(mPreInternalIfname, EmptyString())
+ COPY_OPT_STRING_FIELD(mPreExternalIfname, EmptyString())
+ COPY_OPT_STRING_FIELD(mCurInternalIfname, EmptyString())
+ COPY_OPT_STRING_FIELD(mCurExternalIfname, EmptyString())
+ COPY_OPT_FIELD(mThreshold, -1)
+ COPY_OPT_FIELD(mIpaddr, 0)
+ COPY_OPT_FIELD(mMask, 0)
+ COPY_OPT_FIELD(mGateway_long, 0)
+ COPY_OPT_FIELD(mDns1_long, 0)
+ COPY_OPT_FIELD(mDns2_long, 0)
+ COPY_OPT_FIELD(mMtu, 0)
+
+ mLoopIndex = 0;
+
+#undef COPY_SEQUENCE_FIELD
+#undef COPY_OPT_STRING_FIELD
+#undef COPY_OPT_FIELD
+#undef COPY_FIELD
+ }
+
+ // Followings attributes are 1-to-1 mapping to NetworkCommandOptions.
+ int32_t mId;
+ nsString mCmd;
+ nsString mDomain;
+ nsString mGateway;
+ nsTArray<nsString> mGateways;
+ nsString mIfname;
+ nsString mIp;
+ uint32_t mPrefixLength;
+ nsString mMode;
+ bool mReport;
+ bool mEnabled;
+ nsString mWifictrlinterfacename;
+ nsString mInternalIfname;
+ nsString mExternalIfname;
+ bool mEnable;
+ nsString mSsid;
+ nsString mSecurity;
+ nsString mKey;
+ nsString mPrefix;
+ nsString mLink;
+ nsTArray<nsString> mInterfaceList;
+ nsString mWifiStartIp;
+ nsString mWifiEndIp;
+ nsString mUsbStartIp;
+ nsString mUsbEndIp;
+ nsString mDns1;
+ nsString mDns2;
+ nsTArray<nsString> mDnses;
+ nsString mStartIp;
+ nsString mEndIp;
+ nsString mServerIp;
+ nsString mMaskLength;
+ nsString mPreInternalIfname;
+ nsString mPreExternalIfname;
+ nsString mCurInternalIfname;
+ nsString mCurExternalIfname;
+ long long mThreshold;
+ long mIpaddr;
+ long mMask;
+ long mGateway_long;
+ long mDns1_long;
+ long mDns2_long;
+ long mMtu;
+
+ // Auxiliary information required to carry accros command chain.
+ int mNetId; // A locally defined id per interface.
+ uint32_t mLoopIndex; // Loop index for adding/removing multiple gateways.
+};
+
+// CommandChain store the necessary information to execute command one by one.
+// Including :
+// 1. Command parameters.
+// 2. Command list.
+// 3. Error callback function.
+// 4. Index of current execution command.
+class CommandChain final
+{
+public:
+ CommandChain(const NetworkParams& aParams,
+ const CommandFunc aCmds[],
+ uint32_t aLength,
+ ErrorCallback aError)
+ : mIndex(-1)
+ , mParams(aParams)
+ , mCommands(aCmds)
+ , mLength(aLength)
+ , mError(aError) {
+ }
+
+ NetworkParams&
+ getParams()
+ {
+ return mParams;
+ };
+
+ CommandFunc
+ getNextCommand()
+ {
+ mIndex++;
+ return mIndex < mLength ? mCommands[mIndex] : nullptr;
+ };
+
+ ErrorCallback
+ getErrorCallback() const
+ {
+ return mError;
+ };
+
+private:
+ uint32_t mIndex;
+ NetworkParams mParams;
+ const CommandFunc* mCommands;
+ uint32_t mLength;
+ ErrorCallback mError;
+};
+
+// A helper class to easily construct a resolved
+// or a pending result for command execution.
+class CommandResult
+{
+public:
+ struct Pending {};
+
+public:
+ CommandResult(int32_t aResultCode);
+ CommandResult(const mozilla::dom::NetworkResultOptions& aResult);
+ CommandResult(const Pending&);
+ bool isPending() const;
+
+ mozilla::dom::NetworkResultOptions mResult;
+
+private:
+ bool mIsPending;
+};
+
+class NetworkUtils final
+{
+public:
+ NetworkUtils(MessageCallback aCallback);
+ ~NetworkUtils();
+
+ void ExecuteCommand(NetworkParams aOptions);
+ void onNetdMessage(mozilla::ipc::NetdCommand* aCommand);
+
+ MessageCallback getMessageCallback() { return mMessageCallback; }
+
+private:
+ /**
+ * Commands supported by NetworkUtils.
+ */
+ CommandResult configureInterface(NetworkParams& aOptions);
+ CommandResult dhcpRequest(NetworkParams& aOptions);
+ CommandResult stopDhcp(NetworkParams& aOptions);
+ CommandResult enableInterface(NetworkParams& aOptions);
+ CommandResult disableInterface(NetworkParams& aOptions);
+ CommandResult resetConnections(NetworkParams& aOptions);
+ CommandResult setDefaultRoute(NetworkParams& aOptions);
+ CommandResult addHostRoute(NetworkParams& aOptions);
+ CommandResult removeDefaultRoute(NetworkParams& aOptions);
+ CommandResult removeHostRoute(NetworkParams& aOptions);
+ CommandResult removeNetworkRoute(NetworkParams& aOptions);
+ CommandResult setDNS(NetworkParams& aOptions);
+ CommandResult addSecondaryRoute(NetworkParams& aOptions);
+ CommandResult removeSecondaryRoute(NetworkParams& aOptions);
+ CommandResult setNetworkInterfaceAlarm(NetworkParams& aOptions);
+ CommandResult enableNetworkInterfaceAlarm(NetworkParams& aOptions);
+ CommandResult disableNetworkInterfaceAlarm(NetworkParams& aOptions);
+ CommandResult setTetheringAlarm(NetworkParams& aOptions);
+ CommandResult removeTetheringAlarm(NetworkParams& aOptions);
+ CommandResult getTetheringStatus(NetworkParams& aOptions);
+ CommandResult setWifiOperationMode(NetworkParams& aOptions);
+ CommandResult setDhcpServer(NetworkParams& aOptions);
+ CommandResult setWifiTethering(NetworkParams& aOptions);
+ CommandResult setUSBTethering(NetworkParams& aOptions);
+ CommandResult enableUsbRndis(NetworkParams& aOptions);
+ CommandResult updateUpStream(NetworkParams& aOptions);
+ CommandResult createNetwork(NetworkParams& aOptions);
+ CommandResult destroyNetwork(NetworkParams& aOptions);
+ CommandResult getNetId(NetworkParams& aOptions);
+ CommandResult setMtu(NetworkParams& aOptions);
+ CommandResult getInterfaces(NetworkParams& aOptions);
+ CommandResult getInterfaceConfig(NetworkParams& aOptions);
+ CommandResult setInterfaceConfig(NetworkParams& aOptions);
+
+ CommandResult addHostRouteLegacy(NetworkParams& aOptions);
+ CommandResult removeHostRouteLegacy(NetworkParams& aOptions);
+ CommandResult setDefaultRouteLegacy(NetworkParams& aOptions);
+ CommandResult removeDefaultRouteLegacy(NetworkParams& aOptions);
+ CommandResult removeNetworkRouteLegacy(NetworkParams& aOptions);
+
+
+ /**
+ * function pointer array holds all netd commands should be executed
+ * in sequence to accomplish a given command by other module.
+ */
+ static const CommandFunc sWifiEnableChain[];
+ static const CommandFunc sWifiDisableChain[];
+ static const CommandFunc sWifiFailChain[];
+ static const CommandFunc sWifiRetryChain[];
+ static const CommandFunc sWifiOperationModeChain[];
+ static const CommandFunc sUSBEnableChain[];
+ static const CommandFunc sUSBDisableChain[];
+ static const CommandFunc sUSBFailChain[];
+ static const CommandFunc sUpdateUpStreamChain[];
+ static const CommandFunc sStartDhcpServerChain[];
+ static const CommandFunc sStopDhcpServerChain[];
+ static const CommandFunc sNetworkInterfaceEnableAlarmChain[];
+ static const CommandFunc sNetworkInterfaceDisableAlarmChain[];
+ static const CommandFunc sNetworkInterfaceSetAlarmChain[];
+ static const CommandFunc sTetheringInterfaceSetAlarmChain[];
+ static const CommandFunc sTetheringInterfaceRemoveAlarmChain[];
+ static const CommandFunc sTetheringGetStatusChain[];
+ static const CommandFunc sGetInterfacesChain[];
+ static const CommandFunc sGetInterfaceConfigChain[];
+ static const CommandFunc sSetInterfaceConfigChain[];
+
+ /**
+ * Individual netd command stored in command chain.
+ */
+#define PARAMS CommandChain* aChain, CommandCallback aCallback, \
+ mozilla::dom::NetworkResultOptions& aResult
+ static void wifiFirmwareReload(PARAMS);
+ static void startAccessPointDriver(PARAMS);
+ static void stopAccessPointDriver(PARAMS);
+ static void setAccessPoint(PARAMS);
+ static void cleanUpStream(PARAMS);
+ static void createUpStream(PARAMS);
+ static void startSoftAP(PARAMS);
+ static void stopSoftAP(PARAMS);
+ static void clearWifiTetherParms(PARAMS);
+ static void enableAlarm(PARAMS);
+ static void disableAlarm(PARAMS);
+ static void setQuota(PARAMS);
+ static void removeQuota(PARAMS);
+ static void setAlarm(PARAMS);
+ static void removeAlarm(PARAMS);
+ static void setGlobalAlarm(PARAMS);
+ static void removeGlobalAlarm(PARAMS);
+ static void tetherInterface(PARAMS);
+ static void addInterfaceToLocalNetwork(PARAMS);
+ static void addRouteToLocalNetwork(PARAMS);
+ static void preTetherInterfaceList(PARAMS);
+ static void postTetherInterfaceList(PARAMS);
+ static void addUpstreamInterface(PARAMS);
+ static void removeUpstreamInterface(PARAMS);
+ static void setIpForwardingEnabled(PARAMS);
+ static void tetheringStatus(PARAMS);
+ static void stopTethering(PARAMS);
+ static void startTethering(PARAMS);
+ static void untetherInterface(PARAMS);
+ static void removeInterfaceFromLocalNetwork(PARAMS);
+ static void setDnsForwarders(PARAMS);
+ static void enableNat(PARAMS);
+ static void disableNat(PARAMS);
+ static void setDefaultInterface(PARAMS);
+ static void setInterfaceDns(PARAMS);
+ static void getInterfaceList(PARAMS);
+ static void getConfig(PARAMS);
+ static void setConfig(PARAMS);
+ static void wifiTetheringSuccess(PARAMS);
+ static void usbTetheringSuccess(PARAMS);
+ static void networkInterfaceAlarmSuccess(PARAMS);
+ static void updateUpStreamSuccess(PARAMS);
+ static void setDhcpServerSuccess(PARAMS);
+ static void wifiOperationModeSuccess(PARAMS);
+ static void clearAddrForInterface(PARAMS);
+ static void createNetwork(PARAMS);
+ static void destroyNetwork(PARAMS);
+ static void addInterfaceToNetwork(PARAMS);
+ static void addDefaultRouteToNetwork(PARAMS);
+ static void setDefaultNetwork(PARAMS);
+ static void removeDefaultRoute(PARAMS);
+ static void removeNetworkRouteSuccess(PARAMS);
+ static void removeNetworkRoute(PARAMS);
+ static void addRouteToInterface(PARAMS);
+ static void removeRouteFromInterface(PARAMS);
+ static void modifyRouteOnInterface(PARAMS, bool aDoAdd);
+ static void enableIpv6(PARAMS);
+ static void disableIpv6(PARAMS);
+ static void setMtu(PARAMS);
+ static void setIpv6Enabled(PARAMS, bool aEnabled);
+ static void addRouteToSecondaryTable(PARAMS);
+ static void removeRouteFromSecondaryTable(PARAMS);
+ static void defaultAsyncSuccessHandler(PARAMS);
+ static void getInterfacesSuccess(PARAMS);
+ static void getInterfaceConfigSuccess(PARAMS);
+ static void setInterfaceConfigSuccess(PARAMS);
+
+#undef PARAMS
+
+ /**
+ * Error callback function executed when a command is fail.
+ */
+#define PARAMS NetworkParams& aOptions, \
+ mozilla::dom::NetworkResultOptions& aResult
+ static void wifiTetheringFail(PARAMS);
+ static void wifiOperationModeFail(PARAMS);
+ static void usbTetheringFail(PARAMS);
+ static void updateUpStreamFail(PARAMS);
+ static void setDhcpServerFail(PARAMS);
+ static void networkInterfaceAlarmFail(PARAMS);
+ static void setDnsFail(PARAMS);
+ static void defaultAsyncFailureHandler(PARAMS);
+ static void getInterfacesFail(PARAMS);
+ static void getInterfaceConfigFail(PARAMS);
+ static void setInterfaceConfigFail(PARAMS);
+
+#undef PARAMS
+
+ /**
+ * Command chain processing functions.
+ */
+ static void next(CommandChain* aChain, bool aError,
+ mozilla::dom::NetworkResultOptions& aResult);
+ static void nextNetdCommand();
+ static void doCommand(const char* aCommand, CommandChain* aChain, CommandCallback aCallback);
+
+ /**
+ * Notify broadcast message to main thread.
+ */
+ void sendBroadcastMessage(uint32_t code, char* reason);
+
+ /**
+ * Utility functions.
+ */
+ CommandResult checkUsbRndisState(NetworkParams& aOptions);
+ void dumpParams(NetworkParams& aOptions, const char* aType);
+
+ static void escapeQuote(nsCString& aString);
+ inline uint32_t netdResponseType(uint32_t code);
+ inline bool isBroadcastMessage(uint32_t code);
+ inline bool isError(uint32_t code);
+ inline bool isComplete(uint32_t code);
+ inline bool isProceeding(uint32_t code);
+ void Shutdown();
+ static void runNextQueuedCommandChain();
+ static void finalizeSuccess(CommandChain* aChain,
+ mozilla::dom::NetworkResultOptions& aResult);
+
+ template<size_t N>
+ static void runChain(const NetworkParams& aParams,
+ const CommandFunc (&aCmds)[N],
+ ErrorCallback aError);
+
+ static nsCString getSubnetIp(const nsCString& aIp, int aPrefixLength);
+
+ /**
+ * Callback function to send netd result to main thread.
+ */
+ MessageCallback mMessageCallback;
+
+ /*
+ * Utility class to access libnetutils.
+ */
+ nsAutoPtr<NetUtils> mNetUtils;
+
+ NetIdManager mNetIdManager;
+};
+
+#endif
diff --git a/dom/system/gonk/NetworkWorker.cpp b/dom/system/gonk/NetworkWorker.cpp
new file mode 100644
index 000000000..caf07f375
--- /dev/null
+++ b/dom/system/gonk/NetworkWorker.cpp
@@ -0,0 +1,271 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "NetworkWorker.h"
+#include "NetworkUtils.h"
+#include <nsThreadUtils.h>
+#include "mozilla/ModuleUtils.h"
+#include "mozilla/ClearOnShutdown.h"
+#include "mozilla/dom/ScriptSettings.h"
+#include "mozilla/dom/ToJSValue.h"
+#include "nsAutoPtr.h"
+#include "nsXULAppAPI.h"
+
+#define NS_NETWORKWORKER_CID \
+ { 0x6df093e1, 0x8127, 0x4fa7, {0x90, 0x13, 0xa3, 0xaa, 0xa7, 0x79, 0xbb, 0xdd} }
+
+using namespace mozilla;
+using namespace mozilla::dom;
+using namespace mozilla::ipc;
+
+namespace mozilla {
+
+nsCOMPtr<nsIThread> gWorkerThread;
+
+// The singleton network worker, to be used on the main thread.
+StaticRefPtr<NetworkWorker> gNetworkWorker;
+
+// The singleton networkutils class, that can be used on any thread.
+static nsAutoPtr<NetworkUtils> gNetworkUtils;
+
+// Runnable used dispatch command result on the main thread.
+class NetworkResultDispatcher : public Runnable
+{
+public:
+ NetworkResultDispatcher(const NetworkResultOptions& aResult)
+ : mResult(aResult)
+ {
+ MOZ_ASSERT(!NS_IsMainThread());
+ }
+
+ NS_IMETHOD Run() override
+ {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (gNetworkWorker) {
+ gNetworkWorker->DispatchNetworkResult(mResult);
+ }
+ return NS_OK;
+ }
+private:
+ NetworkResultOptions mResult;
+};
+
+// Runnable used dispatch netd command on the worker thread.
+class NetworkCommandDispatcher : public Runnable
+{
+public:
+ NetworkCommandDispatcher(const NetworkParams& aParams)
+ : mParams(aParams)
+ {
+ MOZ_ASSERT(NS_IsMainThread());
+ }
+
+ NS_IMETHOD Run() override
+ {
+ MOZ_ASSERT(!NS_IsMainThread());
+
+ if (gNetworkUtils) {
+ gNetworkUtils->ExecuteCommand(mParams);
+ }
+ return NS_OK;
+ }
+private:
+ NetworkParams mParams;
+};
+
+// Runnable used dispatch netd result on the worker thread.
+class NetdEventRunnable : public Runnable
+{
+public:
+ NetdEventRunnable(NetdCommand* aCommand)
+ : mCommand(aCommand)
+ {
+ MOZ_ASSERT(!NS_IsMainThread());
+ }
+
+ NS_IMETHOD Run() override
+ {
+ MOZ_ASSERT(!NS_IsMainThread());
+
+ if (gNetworkUtils) {
+ gNetworkUtils->onNetdMessage(mCommand);
+ }
+ return NS_OK;
+ }
+
+private:
+ nsAutoPtr<NetdCommand> mCommand;
+};
+
+class NetdMessageConsumer : public NetdConsumer
+{
+public:
+ NetdMessageConsumer()
+ {
+ MOZ_ASSERT(NS_IsMainThread());
+ }
+
+ void MessageReceived(NetdCommand* aCommand)
+ {
+ MOZ_ASSERT(!NS_IsMainThread());
+
+ nsCOMPtr<nsIRunnable> runnable = new NetdEventRunnable(aCommand);
+ if (gWorkerThread) {
+ gWorkerThread->Dispatch(runnable, nsIEventTarget::DISPATCH_NORMAL);
+ }
+ }
+};
+
+NS_IMPL_ISUPPORTS(NetworkWorker, nsINetworkWorker)
+
+NetworkWorker::NetworkWorker()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(!gNetworkWorker);
+}
+
+NetworkWorker::~NetworkWorker()
+{
+ MOZ_ASSERT(!gNetworkWorker);
+ MOZ_ASSERT(!mListener);
+}
+
+already_AddRefed<NetworkWorker>
+NetworkWorker::FactoryCreate()
+{
+ if (!XRE_IsParentProcess()) {
+ return nullptr;
+ }
+
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (!gNetworkWorker) {
+ gNetworkWorker = new NetworkWorker();
+ ClearOnShutdown(&gNetworkWorker);
+
+ gNetworkUtils = new NetworkUtils(NetworkWorker::NotifyResult);
+ ClearOnShutdown(&gNetworkUtils);
+ }
+
+ RefPtr<NetworkWorker> worker = gNetworkWorker.get();
+ return worker.forget();
+}
+
+NS_IMETHODIMP
+NetworkWorker::Start(nsINetworkEventListener* aListener)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(aListener);
+
+ if (mListener) {
+ return NS_OK;
+ }
+
+ nsresult rv;
+
+ rv = NS_NewNamedThread("NetworkWorker", getter_AddRefs(gWorkerThread));
+ if (NS_FAILED(rv)) {
+ NS_WARNING("Can't create network control thread");
+ return NS_ERROR_FAILURE;
+ }
+
+ StartNetd(new NetdMessageConsumer());
+ mListener = aListener;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+NetworkWorker::Shutdown()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (!mListener) {
+ return NS_OK;
+ }
+
+ StopNetd();
+
+ gWorkerThread->Shutdown();
+ gWorkerThread = nullptr;
+
+ mListener = nullptr;
+ return NS_OK;
+}
+
+// Receive command from main thread (NetworkService.js).
+NS_IMETHODIMP
+NetworkWorker::PostMessage(JS::Handle<JS::Value> aOptions, JSContext* aCx)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ NetworkCommandOptions options;
+ if (!options.Init(aCx, aOptions)) {
+ NS_WARNING("Bad dictionary passed to NetworkWorker::SendCommand");
+ return NS_ERROR_FAILURE;
+ }
+
+ // Dispatch the command to the control thread.
+ NetworkParams NetworkParams(options);
+ nsCOMPtr<nsIRunnable> runnable = new NetworkCommandDispatcher(NetworkParams);
+ if (gWorkerThread) {
+ gWorkerThread->Dispatch(runnable, nsIEventTarget::DISPATCH_NORMAL);
+ }
+ return NS_OK;
+}
+
+void
+NetworkWorker::DispatchNetworkResult(const NetworkResultOptions& aOptions)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ mozilla::AutoSafeJSContext cx;
+ JS::RootedValue val(cx);
+
+ if (!ToJSValue(cx, aOptions, &val)) {
+ return;
+ }
+
+ // Call the listener with a JS value.
+ if (mListener) {
+ mListener->OnEvent(val);
+ }
+}
+
+// Callback function from network worker thread to update result on main thread.
+void
+NetworkWorker::NotifyResult(NetworkResultOptions& aResult)
+{
+ MOZ_ASSERT(!NS_IsMainThread());
+
+ nsCOMPtr<nsIRunnable> runnable = new NetworkResultDispatcher(aResult);
+ NS_DispatchToMainThread(runnable);
+}
+
+NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(NetworkWorker,
+ NetworkWorker::FactoryCreate)
+
+NS_DEFINE_NAMED_CID(NS_NETWORKWORKER_CID);
+
+static const mozilla::Module::CIDEntry kNetworkWorkerCIDs[] = {
+ { &kNS_NETWORKWORKER_CID, false, nullptr, NetworkWorkerConstructor },
+ { nullptr }
+};
+
+static const mozilla::Module::ContractIDEntry kNetworkWorkerContracts[] = {
+ { "@mozilla.org/network/worker;1", &kNS_NETWORKWORKER_CID },
+ { nullptr }
+};
+
+static const mozilla::Module kNetworkWorkerModule = {
+ mozilla::Module::kVersion,
+ kNetworkWorkerCIDs,
+ kNetworkWorkerContracts,
+ nullptr
+};
+
+} // namespace mozilla
+
+NSMODULE_DEFN(NetworkWorkerModule) = &kNetworkWorkerModule;
diff --git a/dom/system/gonk/NetworkWorker.h b/dom/system/gonk/NetworkWorker.h
new file mode 100644
index 000000000..f5c0a8fdd
--- /dev/null
+++ b/dom/system/gonk/NetworkWorker.h
@@ -0,0 +1,37 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef NetworkWorker_h
+#define NetworkWorker_h
+
+#include "mozilla/dom/NetworkOptionsBinding.h"
+#include "mozilla/ipc/Netd.h"
+#include "nsINetworkWorker.h"
+#include "nsCOMPtr.h"
+#include "nsThread.h"
+
+namespace mozilla {
+
+class NetworkWorker final : public nsINetworkWorker
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSINETWORKWORKER
+
+ static already_AddRefed<NetworkWorker> FactoryCreate();
+
+ void DispatchNetworkResult(const mozilla::dom::NetworkResultOptions& aOptions);
+
+private:
+ NetworkWorker();
+ ~NetworkWorker();
+
+ static void NotifyResult(mozilla::dom::NetworkResultOptions& aResult);
+
+ nsCOMPtr<nsINetworkEventListener> mListener;
+};
+
+} // namespace mozilla
+
+#endif // NetworkWorker_h
diff --git a/dom/system/gonk/OpenFileFinder.cpp b/dom/system/gonk/OpenFileFinder.cpp
new file mode 100644
index 000000000..388e813e1
--- /dev/null
+++ b/dom/system/gonk/OpenFileFinder.cpp
@@ -0,0 +1,251 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "OpenFileFinder.h"
+
+#include "mozilla/FileUtils.h"
+#include "nsPrintfCString.h"
+
+#include <sys/stat.h>
+#include <errno.h>
+
+#undef USE_DEBUG
+#define USE_DEBUG 0
+
+#undef LOG
+#undef LOGW
+#undef ERR
+#define LOG(args...) __android_log_print(ANDROID_LOG_INFO, "OpenFileFinder", ## args)
+#define LOGW(args...) __android_log_print(ANDROID_LOG_WARN, "OpenFileFinder", ## args)
+#define ERR(args...) __android_log_print(ANDROID_LOG_ERROR, "OpenFileFinder", ## args)
+
+#undef DBG
+#if USE_DEBUG
+#define DBG(args...) __android_log_print(ANDROID_LOG_DEBUG, "OpenFileFinder" , ## args)
+#else
+#define DBG(args...)
+#endif
+
+namespace mozilla {
+namespace system {
+
+OpenFileFinder::OpenFileFinder(const nsACString& aPath,
+ bool aCheckIsB2gOrDescendant /* = true */)
+ : mPath(aPath),
+ mProcDir(nullptr),
+ mFdDir(nullptr),
+ mPid(0),
+ mCheckIsB2gOrDescendant(aCheckIsB2gOrDescendant)
+{
+ // We assume that we're running in the parent process
+ mMyPid = getpid();
+}
+
+OpenFileFinder::~OpenFileFinder()
+{
+ Close();
+}
+
+bool
+OpenFileFinder::First(OpenFileFinder::Info* aInfo)
+{
+ Close();
+
+ mProcDir = opendir("/proc");
+ if (!mProcDir) {
+ return false;
+ }
+ mState = NEXT_PID;
+ return Next(aInfo);
+}
+
+bool
+OpenFileFinder::Next(OpenFileFinder::Info* aInfo)
+{
+ // NOTE: This function calls readdir and readlink, neither of which should
+ // block since we're using the proc filesystem, which is a purely
+ // kernel in-memory filesystem and doesn't depend on external driver
+ // behaviour.
+ while (mState != DONE) {
+ switch (mState) {
+ case NEXT_PID: {
+ struct dirent *pidEntry;
+ pidEntry = readdir(mProcDir);
+ if (!pidEntry) {
+ mState = DONE;
+ break;
+ }
+ char *endPtr;
+ mPid = strtol(pidEntry->d_name, &endPtr, 10);
+ if (mPid == 0 || *endPtr != '\0') {
+ // Not a +ve number - ignore
+ continue;
+ }
+ // We've found a /proc/PID directory. Scan open file descriptors.
+ if (mFdDir) {
+ closedir(mFdDir);
+ }
+ nsPrintfCString fdDirPath("/proc/%d/fd", mPid);
+ mFdDir = opendir(fdDirPath.get());
+ if (!mFdDir) {
+ continue;
+ }
+ mState = CHECK_FDS;
+ }
+ // Fall through
+ case CHECK_FDS: {
+ struct dirent *fdEntry;
+ while((fdEntry = readdir(mFdDir))) {
+ if (!strcmp(fdEntry->d_name, ".") ||
+ !strcmp(fdEntry->d_name, "..")) {
+ continue;
+ }
+ nsPrintfCString fdSymLink("/proc/%d/fd/%s", mPid, fdEntry->d_name);
+ nsCString resolvedPath;
+ if (ReadSymLink(fdSymLink, resolvedPath) && PathMatches(resolvedPath)) {
+ // We found an open file contained within the directory tree passed
+ // into the constructor.
+ FillInfo(aInfo, resolvedPath);
+ // If sCheckIsB2gOrDescendant is set false, the caller cares about
+ // all processes which have open files. If sCheckIsB2gOrDescendant
+ // is set false, we only care about the b2g proccess or its descendants.
+ if (!mCheckIsB2gOrDescendant || aInfo->mIsB2gOrDescendant) {
+ return true;
+ }
+ LOG("Ignore process(%d), not a b2g process or its descendant.",
+ aInfo->mPid);
+ }
+ }
+ // We've checked all of the files for this pid, move onto the next one.
+ mState = NEXT_PID;
+ continue;
+ }
+ case DONE:
+ default:
+ mState = DONE; // covers the default case
+ break;
+ }
+ }
+ return false;
+}
+
+void
+OpenFileFinder::Close()
+{
+ if (mFdDir) {
+ closedir(mFdDir);
+ }
+ if (mProcDir) {
+ closedir(mProcDir);
+ }
+}
+
+void
+OpenFileFinder::FillInfo(OpenFileFinder::Info* aInfo, const nsACString& aPath)
+{
+ aInfo->mFileName = aPath;
+ aInfo->mPid = mPid;
+ nsPrintfCString exePath("/proc/%d/exe", mPid);
+ ReadSymLink(exePath, aInfo->mExe);
+ aInfo->mComm.Truncate();
+ aInfo->mAppName.Truncate();
+ nsPrintfCString statPath("/proc/%d/stat", mPid);
+ nsCString statString;
+ statString.SetLength(200);
+ char *stat = statString.BeginWriting();
+ if (!stat) {
+ return;
+ }
+ ReadSysFile(statPath.get(), stat, statString.Length());
+ // The stat line includes the comm field, surrounded by parenthesis.
+ // However, the contents of the comm field itself is arbitrary and
+ // and can include ')', so we search for the rightmost ) as being
+ // the end of the comm field.
+ char *closeParen = strrchr(stat, ')');
+ if (!closeParen) {
+ return;
+ }
+ char *openParen = strchr(stat, '(');
+ if (!openParen) {
+ return;
+ }
+ if (openParen >= closeParen) {
+ return;
+ }
+ nsDependentCSubstring comm(&openParen[1], closeParen - openParen - 1);
+ aInfo->mComm = comm;
+ // There is a single character field after the comm and then
+ // the parent pid (the field we're interested in).
+ // ) X ppid
+ // 01234
+ int ppid = atoi(&closeParen[4]);
+
+ if (mPid == mMyPid) {
+ // This is chrome process
+ aInfo->mIsB2gOrDescendant = true;
+ DBG("Chrome process has open file(s)");
+ return;
+ }
+ // For the rest (non-chrome process), we recursively check the ppid to know
+ // it is a descendant of b2g or not. See bug 931456.
+ while (ppid != mMyPid && ppid != 1) {
+ DBG("Process(%d) is not forked from b2g(%d) or Init(1), keep looking",
+ ppid, mMyPid);
+ nsPrintfCString ppStatPath("/proc/%d/stat", ppid);
+ ReadSysFile(ppStatPath.get(), stat, statString.Length());
+ closeParen = strrchr(stat, ')');
+ if (!closeParen) {
+ return;
+ }
+ ppid = atoi(&closeParen[4]);
+ }
+ if (ppid == 1) {
+ // This is a not a b2g process.
+ DBG("Non-b2g process has open file(s)");
+ aInfo->mIsB2gOrDescendant = false;
+ return;
+ }
+ if (ppid == mMyPid) {
+ // This is a descendant of b2g.
+ DBG("Child process of chrome process has open file(s)");
+ aInfo->mIsB2gOrDescendant = true;
+ }
+
+ // This looks like a content process. The comm field will be the
+ // app name.
+ aInfo->mAppName = aInfo->mComm;
+}
+
+bool
+OpenFileFinder::ReadSymLink(const nsACString& aSymLink, nsACString& aOutPath)
+{
+ aOutPath.Truncate();
+ const char *symLink = aSymLink.BeginReading();
+
+ // Verify that we actually have a symlink.
+ struct stat st;
+ if (lstat(symLink, &st)) {
+ return false;
+ }
+ if ((st.st_mode & S_IFMT) != S_IFLNK) {
+ return false;
+ }
+
+ // Contrary to the documentation st.st_size doesn't seem to be a reliable
+ // indication of the length when reading from /proc, so we use a fixed
+ // size buffer instead.
+
+ char resolvedSymLink[PATH_MAX];
+ ssize_t pathLength = readlink(symLink, resolvedSymLink,
+ sizeof(resolvedSymLink) - 1);
+ if (pathLength <= 0) {
+ return false;
+ }
+ resolvedSymLink[pathLength] = '\0';
+ aOutPath.Assign(resolvedSymLink);
+ return true;
+}
+
+} // system
+} // mozilla
diff --git a/dom/system/gonk/OpenFileFinder.h b/dom/system/gonk/OpenFileFinder.h
new file mode 100644
index 000000000..24517965a
--- /dev/null
+++ b/dom/system/gonk/OpenFileFinder.h
@@ -0,0 +1,63 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_system_openfilefinder_h__
+#define mozilla_system_openfilefinder_h__
+
+#include "nsString.h"
+
+#include <dirent.h>
+
+namespace mozilla {
+namespace system {
+
+class OpenFileFinder
+{
+public:
+ enum State
+ {
+ NEXT_PID,
+ CHECK_FDS,
+ DONE
+ };
+ class Info
+ {
+ public:
+ nsCString mFileName; // name of the the open file
+ nsCString mAppName; // App which has the file open (if it's a b2g app)
+ pid_t mPid; // pid of the process which has the file open
+ nsCString mComm; // comm associated with pid
+ nsCString mExe; // executable name associated with pid
+ bool mIsB2gOrDescendant; // this is b2g/its descendant or not
+ };
+
+ OpenFileFinder(const nsACString& aPath, bool aCheckIsB2gOrDescendant = true);
+ ~OpenFileFinder();
+
+ bool First(Info* aInfo); // Return the first open file
+ bool Next(Info* aInfo); // Return the next open file
+ void Close();
+
+private:
+
+ void FillInfo(Info *aInfo, const nsACString& aPath);
+ bool ReadSymLink(const nsACString& aSymLink, nsACString& aOutPath);
+ bool PathMatches(const nsACString& aPath)
+ {
+ return Substring(aPath, 0, mPath.Length()).Equals(mPath);
+ }
+
+ State mState; // Keeps track of what we're doing.
+ nsCString mPath; // Only report files contained within this directory tree
+ DIR* mProcDir; // Used for scanning /proc
+ DIR* mFdDir; // Used for scanning /proc/PID/fd
+ int mPid; // PID currently being processed
+ pid_t mMyPid; // PID of parent process, we assume we're running on it.
+ bool mCheckIsB2gOrDescendant; // Do we care about non-b2g process?
+};
+
+} // system
+} // mozilla
+
+#endif // mozilla_system_nsvolume_h__
diff --git a/dom/system/gonk/RILSystemMessenger.jsm b/dom/system/gonk/RILSystemMessenger.jsm
new file mode 100644
index 000000000..81373458c
--- /dev/null
+++ b/dom/system/gonk/RILSystemMessenger.jsm
@@ -0,0 +1,338 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
+XPCOMUtils.defineLazyGetter(this, "RIL", function () {
+ let obj = {};
+ Cu.import("resource://gre/modules/ril_consts.js", obj);
+ return obj;
+});
+
+/**
+ * RILSystemMessenger
+ */
+this.RILSystemMessenger = function() {};
+RILSystemMessenger.prototype = {
+
+ /**
+ * Hook of Broadcast function
+ *
+ * @param aType
+ * The type of the message to be sent.
+ * @param aMessage
+ * The message object to be broadcasted.
+ */
+ broadcastMessage: function(aType, aMessage) {
+ // Function stub to be replaced by the owner of this messenger.
+ },
+
+ /**
+ * Hook of the function to create MozStkCommand message.
+ * @param aStkProactiveCmd
+ * nsIStkProactiveCmd instance.
+ *
+ * @return a JS object which complies the dictionary of MozStkCommand defined
+ * in MozStkCommandEvent.webidl
+ */
+ createCommandMessage: function(aStkProactiveCmd) {
+ // Function stub to be replaced by the owner of this messenger.
+ },
+
+ /**
+ * Wrapper to send "telephony-new-call" system message.
+ */
+ notifyNewCall: function() {
+ this.broadcastMessage("telephony-new-call", {});
+ },
+
+ /**
+ * Wrapper to send "telephony-call-ended" system message.
+ */
+ notifyCallEnded: function(aServiceId, aNumber, aCdmaWaitingNumber, aEmergency,
+ aDuration, aOutgoing, aHangUpLocal) {
+ let data = {
+ serviceId: aServiceId,
+ number: aNumber,
+ emergency: aEmergency,
+ duration: aDuration,
+ direction: aOutgoing ? "outgoing" : "incoming",
+ hangUpLocal: aHangUpLocal
+ };
+
+ if (aCdmaWaitingNumber != null) {
+ data.secondNumber = aCdmaWaitingNumber;
+ }
+
+ this.broadcastMessage("telephony-call-ended", data);
+ },
+
+ _convertSmsMessageClass: function(aMessageClass) {
+ return RIL.GECKO_SMS_MESSAGE_CLASSES[aMessageClass] || null;
+ },
+
+ _convertSmsDelivery: function(aDelivery) {
+ return ["received", "sending", "sent", "error"][aDelivery] || null;
+ },
+
+ _convertSmsDeliveryStatus: function(aDeliveryStatus) {
+ return [
+ RIL.GECKO_SMS_DELIVERY_STATUS_NOT_APPLICABLE,
+ RIL.GECKO_SMS_DELIVERY_STATUS_SUCCESS,
+ RIL.GECKO_SMS_DELIVERY_STATUS_PENDING,
+ RIL.GECKO_SMS_DELIVERY_STATUS_ERROR
+ ][aDeliveryStatus] || null;
+ },
+
+ /**
+ * Wrapper to send 'sms-received', 'sms-delivery-success', 'sms-sent',
+ * 'sms-failed', 'sms-delivery-error' system message.
+ */
+ notifySms: function(aNotificationType, aId, aThreadId, aIccId, aDelivery,
+ aDeliveryStatus, aSender, aReceiver, aBody, aMessageClass,
+ aTimestamp, aSentTimestamp, aDeliveryTimestamp, aRead) {
+ let msgType = [
+ "sms-received",
+ "sms-sent",
+ "sms-delivery-success",
+ "sms-failed",
+ "sms-delivery-error"
+ ][aNotificationType];
+
+ if (!msgType) {
+ throw new Error("Invalid Notification Type: " + aNotificationType);
+ }
+
+ this.broadcastMessage(msgType, {
+ iccId: aIccId,
+ type: "sms",
+ id: aId,
+ threadId: aThreadId,
+ delivery: this._convertSmsDelivery(aDelivery),
+ deliveryStatus: this._convertSmsDeliveryStatus(aDeliveryStatus),
+ sender: aSender,
+ receiver: aReceiver,
+ body: aBody,
+ messageClass: this._convertSmsMessageClass(aMessageClass),
+ timestamp: aTimestamp,
+ sentTimestamp: aSentTimestamp,
+ deliveryTimestamp: aDeliveryTimestamp,
+ read: aRead
+ });
+ },
+
+ _convertCbGsmGeographicalScope: function(aGeographicalScope) {
+ return RIL.CB_GSM_GEOGRAPHICAL_SCOPE_NAMES[aGeographicalScope] || null;
+ },
+
+ _convertCbMessageClass: function(aMessageClass) {
+ return RIL.GECKO_SMS_MESSAGE_CLASSES[aMessageClass] || null;
+ },
+
+ _convertCbEtwsWarningType: function(aWarningType) {
+ return RIL.CB_ETWS_WARNING_TYPE_NAMES[aWarningType] || null;
+ },
+
+ /**
+ * Wrapper to send 'cellbroadcast-received' system message.
+ */
+ notifyCbMessageReceived: function(aServiceId, aGsmGeographicalScope, aMessageCode,
+ aMessageId, aLanguage, aBody, aMessageClass,
+ aTimestamp, aCdmaServiceCategory, aHasEtwsInfo,
+ aEtwsWarningType, aEtwsEmergencyUserAlert, aEtwsPopup) {
+ // Align the same layout to MozCellBroadcastMessage
+ let data = {
+ serviceId: aServiceId,
+ gsmGeographicalScope: this._convertCbGsmGeographicalScope(aGsmGeographicalScope),
+ messageCode: aMessageCode,
+ messageId: aMessageId,
+ language: aLanguage,
+ body: aBody,
+ messageClass: this._convertCbMessageClass(aMessageClass),
+ timestamp: aTimestamp,
+ cdmaServiceCategory: null,
+ etws: null
+ };
+
+ if (aHasEtwsInfo) {
+ data.etws = {
+ warningType: this._convertCbEtwsWarningType(aEtwsWarningType),
+ emergencyUserAlert: aEtwsEmergencyUserAlert,
+ popup: aEtwsPopup
+ };
+ }
+
+ if (aCdmaServiceCategory !=
+ Ci.nsICellBroadcastService.CDMA_SERVICE_CATEGORY_INVALID) {
+ data.cdmaServiceCategory = aCdmaServiceCategory;
+ }
+
+ this.broadcastMessage("cellbroadcast-received", data);
+ },
+
+ /**
+ * Wrapper to send 'ussd-received' system message.
+ */
+ notifyUssdReceived: function(aServiceId, aMessage, aSessionEnded) {
+ this.broadcastMessage("ussd-received", {
+ serviceId: aServiceId,
+ message: aMessage,
+ sessionEnded: aSessionEnded
+ });
+ },
+
+ /**
+ * Wrapper to send 'cdma-info-rec-received' system message with Display Info.
+ */
+ notifyCdmaInfoRecDisplay: function(aServiceId, aDisplay) {
+ this.broadcastMessage("cdma-info-rec-received", {
+ clientId: aServiceId,
+ display: aDisplay
+ });
+ },
+
+ /**
+ * Wrapper to send 'cdma-info-rec-received' system message with Called Party
+ * Number Info.
+ */
+ notifyCdmaInfoRecCalledPartyNumber: function(aServiceId, aType, aPlan,
+ aNumber, aPi, aSi) {
+ this.broadcastMessage("cdma-info-rec-received", {
+ clientId: aServiceId,
+ calledNumber: {
+ type: aType,
+ plan: aPlan,
+ number: aNumber,
+ pi: aPi,
+ si: aSi
+ }
+ });
+ },
+
+ /**
+ * Wrapper to send 'cdma-info-rec-received' system message with Calling Party
+ * Number Info.
+ */
+ notifyCdmaInfoRecCallingPartyNumber: function(aServiceId, aType, aPlan,
+ aNumber, aPi, aSi) {
+ this.broadcastMessage("cdma-info-rec-received", {
+ clientId: aServiceId,
+ callingNumber: {
+ type: aType,
+ plan: aPlan,
+ number: aNumber,
+ pi: aPi,
+ si: aSi
+ }
+ });
+ },
+
+ /**
+ * Wrapper to send 'cdma-info-rec-received' system message with Connected Party
+ * Number Info.
+ */
+ notifyCdmaInfoRecConnectedPartyNumber: function(aServiceId, aType, aPlan,
+ aNumber, aPi, aSi) {
+ this.broadcastMessage("cdma-info-rec-received", {
+ clientId: aServiceId,
+ connectedNumber: {
+ type: aType,
+ plan: aPlan,
+ number: aNumber,
+ pi: aPi,
+ si: aSi
+ }
+ });
+ },
+
+ /**
+ * Wrapper to send 'cdma-info-rec-received' system message with Signal Info.
+ */
+ notifyCdmaInfoRecSignal: function(aServiceId, aType, aAlertPitch, aSignal) {
+ this.broadcastMessage("cdma-info-rec-received", {
+ clientId: aServiceId,
+ signal: {
+ type: aType,
+ alertPitch: aAlertPitch,
+ signal: aSignal
+ }
+ });
+ },
+
+ /**
+ * Wrapper to send 'cdma-info-rec-received' system message with Redirecting
+ * Number Info.
+ */
+ notifyCdmaInfoRecRedirectingNumber: function(aServiceId, aType, aPlan,
+ aNumber, aPi, aSi, aReason) {
+ this.broadcastMessage("cdma-info-rec-received", {
+ clientId: aServiceId,
+ redirect: {
+ type: aType,
+ plan: aPlan,
+ number: aNumber,
+ pi: aPi,
+ si: aSi,
+ reason: aReason
+ }
+ });
+ },
+
+ /**
+ * Wrapper to send 'cdma-info-rec-received' system message with Line Control Info.
+ */
+ notifyCdmaInfoRecLineControl: function(aServiceId, aPolarityIncluded,
+ aToggle, aReverse, aPowerDenial) {
+ this.broadcastMessage("cdma-info-rec-received", {
+ clientId: aServiceId,
+ lineControl: {
+ polarityIncluded: aPolarityIncluded,
+ toggle: aToggle,
+ reverse: aReverse,
+ powerDenial: aPowerDenial
+ }
+ });
+ },
+
+ /**
+ * Wrapper to send 'cdma-info-rec-received' system message with CLIR Info.
+ */
+ notifyCdmaInfoRecClir: function(aServiceId, aCause) {
+ this.broadcastMessage("cdma-info-rec-received", {
+ clientId: aServiceId,
+ clirCause: aCause
+ });
+ },
+
+ /**
+ * Wrapper to send 'cdma-info-rec-received' system message with Audio Control Info.
+ */
+ notifyCdmaInfoRecAudioControl: function(aServiceId, aUpLink, aDownLink) {
+ this.broadcastMessage("cdma-info-rec-received", {
+ clientId: aServiceId,
+ audioControl: {
+ upLink: aUpLink,
+ downLink: aDownLink
+ }
+ });
+ },
+
+ /**
+ * Wrapper to send 'icc-stkcommand' system message with Audio Control Info.
+ */
+ notifyStkProactiveCommand: function(aIccId, aCommand) {
+ this.broadcastMessage("icc-stkcommand", {
+ iccId: aIccId,
+ command: this.createCommandMessage(aCommand)
+ });
+ }
+};
+
+this.EXPORTED_SYMBOLS = [
+ 'RILSystemMessenger'
+];
diff --git a/dom/system/gonk/RILSystemMessengerHelper.js b/dom/system/gonk/RILSystemMessengerHelper.js
new file mode 100644
index 000000000..6ef263b66
--- /dev/null
+++ b/dom/system/gonk/RILSystemMessengerHelper.js
@@ -0,0 +1,169 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
+
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
+var RSM = {};
+Cu.import("resource://gre/modules/RILSystemMessenger.jsm", RSM);
+
+const RILSYSTEMMESSENGERHELPER_CONTRACTID =
+ "@mozilla.org/ril/system-messenger-helper;1";
+const RILSYSTEMMESSENGERHELPER_CID =
+ Components.ID("{19d9a4ea-580d-11e4-8f6c-37ababfaaea9}");
+
+XPCOMUtils.defineLazyServiceGetter(this, "gSystemMessenger",
+ "@mozilla.org/system-message-internal;1",
+ "nsISystemMessagesInternal");
+
+XPCOMUtils.defineLazyServiceGetter(this, "gStkCmdFactory",
+ "@mozilla.org/icc/stkcmdfactory;1",
+ "nsIStkCmdFactory");
+
+var DEBUG = false;
+function debug(s) {
+ dump("-@- RILSystemMessenger: " + s + "\n");
+};
+
+// Read debug setting from pref.
+try {
+ let debugPref = Services.prefs.getBoolPref("ril.debugging.enabled");
+ DEBUG = DEBUG || debugPref;
+} catch (e) {}
+
+/**
+ * RILSystemMessengerHelper
+ */
+function RILSystemMessengerHelper() {
+ this.messenger = new RSM.RILSystemMessenger();
+ this.messenger.broadcastMessage = (aType, aMessage) => {
+ if (DEBUG) {
+ debug("broadcastMessage: aType: " + aType +
+ ", aMessage: "+ JSON.stringify(aMessage));
+ }
+
+ gSystemMessenger.broadcastMessage(aType, aMessage);
+ };
+
+ this.messenger.createCommandMessage = (aStkProactiveCmd) => {
+ return gStkCmdFactory.createCommandMessage(aStkProactiveCmd);
+ };
+}
+RILSystemMessengerHelper.prototype = {
+
+ classID: RILSYSTEMMESSENGERHELPER_CID,
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsITelephonyMessenger,
+ Ci.nsISmsMessenger,
+ Ci.nsICellbroadcastMessenger,
+ Ci.nsIMobileConnectionMessenger,
+ Ci.nsIIccMessenger]),
+
+ /**
+ * RILSystemMessenger instance.
+ */
+ messenger: null,
+
+ /**
+ * nsITelephonyMessenger API
+ */
+ notifyNewCall: function() {
+ this.messenger.notifyNewCall();
+ },
+
+ notifyCallEnded: function(aServiceId, aNumber, aCdmaWaitingNumber, aEmergency,
+ aDuration, aOutgoing, aHangUpLocal) {
+ this.messenger.notifyCallEnded(aServiceId, aNumber, aCdmaWaitingNumber, aEmergency,
+ aDuration, aOutgoing, aHangUpLocal);
+ },
+
+ notifyUssdReceived: function(aServiceId, aMessage, aSessionEnded) {
+ this.messenger.notifyUssdReceived(aServiceId, aMessage, aSessionEnded);
+ },
+
+ /**
+ * nsISmsMessenger API
+ */
+ notifySms: function(aNotificationType, aId, aThreadId, aIccId, aDelivery,
+ aDeliveryStatus, aSender, aReceiver, aBody, aMessageClass,
+ aTimestamp, aSentTimestamp, aDeliveryTimestamp, aRead) {
+ this.messenger.notifySms(aNotificationType, aId, aThreadId, aIccId, aDelivery,
+ aDeliveryStatus, aSender, aReceiver, aBody, aMessageClass,
+ aTimestamp, aSentTimestamp, aDeliveryTimestamp, aRead);
+ },
+
+ /**
+ * nsICellbroadcastMessenger API
+ */
+ notifyCbMessageReceived: function(aServiceId, aGsmGeographicalScope, aMessageCode,
+ aMessageId, aLanguage, aBody, aMessageClass,
+ aTimestamp, aCdmaServiceCategory, aHasEtwsInfo,
+ aEtwsWarningType, aEtwsEmergencyUserAlert, aEtwsPopup) {
+ this.messenger.notifyCbMessageReceived(aServiceId, aGsmGeographicalScope, aMessageCode,
+ aMessageId, aLanguage, aBody, aMessageClass,
+ aTimestamp, aCdmaServiceCategory, aHasEtwsInfo,
+ aEtwsWarningType, aEtwsEmergencyUserAlert, aEtwsPopup);
+ },
+
+ /**
+ * nsIMobileConnectionMessenger API
+ */
+ notifyCdmaInfoRecDisplay: function(aServiceId, aDisplay) {
+ this.messenger.notifyCdmaInfoRecDisplay(aServiceId, aDisplay);
+ },
+
+ notifyCdmaInfoRecCalledPartyNumber: function(aServiceId, aType, aPlan,
+ aNumber, aPi, aSi) {
+ this.messenger.notifyCdmaInfoRecCalledPartyNumber(aServiceId, aType, aPlan,
+ aNumber, aPi, aSi);
+ },
+
+ notifyCdmaInfoRecCallingPartyNumber: function(aServiceId, aType, aPlan,
+ aNumber, aPi, aSi) {
+ this.messenger.notifyCdmaInfoRecCallingPartyNumber(aServiceId, aType, aPlan,
+ aNumber, aPi, aSi);
+ },
+
+ notifyCdmaInfoRecConnectedPartyNumber: function(aServiceId, aType, aPlan,
+ aNumber, aPi, aSi) {
+ this.messenger.notifyCdmaInfoRecConnectedPartyNumber(aServiceId, aType, aPlan,
+ aNumber, aPi, aSi);
+ },
+
+ notifyCdmaInfoRecSignal: function(aServiceId, aType, aAlertPitch, aSignal) {
+ this.messenger.notifyCdmaInfoRecSignal(aServiceId, aType, aAlertPitch, aSignal);
+ },
+
+ notifyCdmaInfoRecRedirectingNumber: function(aServiceId, aType, aPlan,
+ aNumber, aPi, aSi, aReason) {
+ this.messenger.notifyCdmaInfoRecRedirectingNumber(aServiceId, aType, aPlan,
+ aNumber, aPi, aSi, aReason);
+ },
+
+ notifyCdmaInfoRecLineControl: function(aServiceId, aPolarityIncluded,
+ aToggle, aReverse, aPowerDenial) {
+ this.messenger.notifyCdmaInfoRecLineControl(aServiceId, aPolarityIncluded,
+ aToggle, aReverse, aPowerDenial);
+ },
+
+ notifyCdmaInfoRecClir: function(aServiceId, aCause) {
+ this.messenger.notifyCdmaInfoRecClir(aServiceId, aCause);
+ },
+
+ notifyCdmaInfoRecAudioControl: function(aServiceId, aUpLink, aDownLink) {
+ this.messenger.notifyCdmaInfoRecAudioControl(aServiceId, aUpLink, aDownLink);
+ },
+
+ /**
+ * nsIIccMessenger API
+ */
+ notifyStkProactiveCommand: function(aIccId, aCommand) {
+ this.messenger.notifyStkProactiveCommand(aIccId, aCommand);
+ }
+};
+
+this.NSGetFactory = XPCOMUtils.generateNSGetFactory([RILSystemMessengerHelper]);
diff --git a/dom/system/gonk/RILSystemMessengerHelper.manifest b/dom/system/gonk/RILSystemMessengerHelper.manifest
new file mode 100644
index 000000000..7d1943702
--- /dev/null
+++ b/dom/system/gonk/RILSystemMessengerHelper.manifest
@@ -0,0 +1,6 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+component {19d9a4ea-580d-11e4-8f6c-37ababfaaea9} RILSystemMessengerHelper.js
+contract @mozilla.org/ril/system-messenger-helper;1 {19d9a4ea-580d-11e4-8f6c-37ababfaaea9} \ No newline at end of file
diff --git a/dom/system/gonk/RadioInterfaceLayer.js b/dom/system/gonk/RadioInterfaceLayer.js
new file mode 100644
index 000000000..f5885db5d
--- /dev/null
+++ b/dom/system/gonk/RadioInterfaceLayer.js
@@ -0,0 +1,1324 @@
+/* Copyright 2012 Mozilla Foundation and Mozilla contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+"use strict";
+
+const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/Sntp.jsm");
+Cu.import("resource://gre/modules/systemlibs.js");
+Cu.import("resource://gre/modules/Promise.jsm");
+Cu.import("resource://gre/modules/FileUtils.jsm");
+
+XPCOMUtils.defineLazyGetter(this, "RIL", function () {
+ let obj = {};
+ Cu.import("resource://gre/modules/ril_consts.js", obj);
+ return obj;
+});
+
+// Ril quirk to always turn the radio off for the client without SIM card
+// except hw default client.
+var RILQUIRKS_RADIO_OFF_WO_CARD =
+ libcutils.property_get("ro.moz.ril.radio_off_wo_card", "false") == "true";
+
+const RADIOINTERFACELAYER_CID =
+ Components.ID("{2d831c8d-6017-435b-a80c-e5d422810cea}");
+const RADIOINTERFACE_CID =
+ Components.ID("{6a7c91f0-a2b3-4193-8562-8969296c0b54}");
+
+const NS_XPCOM_SHUTDOWN_OBSERVER_ID = "xpcom-shutdown";
+const kNetworkConnStateChangedTopic = "network-connection-state-changed";
+const kMozSettingsChangedObserverTopic = "mozsettings-changed";
+const kSysMsgListenerReadyObserverTopic = "system-message-listener-ready";
+const kSysClockChangeObserverTopic = "system-clock-change";
+const kScreenStateChangedTopic = "screen-state-changed";
+
+const kSettingsClockAutoUpdateEnabled = "time.clock.automatic-update.enabled";
+const kSettingsClockAutoUpdateAvailable = "time.clock.automatic-update.available";
+const kSettingsTimezoneAutoUpdateEnabled = "time.timezone.automatic-update.enabled";
+const kSettingsTimezoneAutoUpdateAvailable = "time.timezone.automatic-update.available";
+
+const NS_PREFBRANCH_PREFCHANGE_TOPIC_ID = "nsPref:changed";
+
+const kPrefRilNumRadioInterfaces = "ril.numRadioInterfaces";
+const kPrefRilDebuggingEnabled = "ril.debugging.enabled";
+
+const RADIO_POWER_OFF_TIMEOUT = 30000;
+const HW_DEFAULT_CLIENT_ID = 0;
+
+const NETWORK_TYPE_WIFI = Ci.nsINetworkInfo.NETWORK_TYPE_WIFI;
+const NETWORK_TYPE_MOBILE = Ci.nsINetworkInfo.NETWORK_TYPE_MOBILE;
+
+// set to true in ril_consts.js to see debug messages
+var DEBUG = RIL.DEBUG_RIL;
+
+function updateDebugFlag() {
+ // Read debug setting from pref
+ let debugPref;
+ try {
+ debugPref = Services.prefs.getBoolPref(kPrefRilDebuggingEnabled);
+ } catch (e) {
+ debugPref = false;
+ }
+ DEBUG = RIL.DEBUG_RIL || debugPref;
+}
+updateDebugFlag();
+
+function debug(s) {
+ dump("-*- RadioInterfaceLayer: " + s + "\n");
+}
+
+XPCOMUtils.defineLazyServiceGetter(this, "gIccService",
+ "@mozilla.org/icc/gonkiccservice;1",
+ "nsIGonkIccService");
+
+XPCOMUtils.defineLazyServiceGetter(this, "gMobileMessageService",
+ "@mozilla.org/mobilemessage/mobilemessageservice;1",
+ "nsIMobileMessageService");
+
+XPCOMUtils.defineLazyServiceGetter(this, "gSmsService",
+ "@mozilla.org/sms/gonksmsservice;1",
+ "nsIGonkSmsService");
+
+XPCOMUtils.defineLazyServiceGetter(this, "ppmm",
+ "@mozilla.org/parentprocessmessagemanager;1",
+ "nsIMessageBroadcaster");
+
+XPCOMUtils.defineLazyServiceGetter(this, "gSettingsService",
+ "@mozilla.org/settingsService;1",
+ "nsISettingsService");
+
+XPCOMUtils.defineLazyServiceGetter(this, "gNetworkManager",
+ "@mozilla.org/network/manager;1",
+ "nsINetworkManager");
+
+XPCOMUtils.defineLazyServiceGetter(this, "gTimeService",
+ "@mozilla.org/time/timeservice;1",
+ "nsITimeService");
+
+XPCOMUtils.defineLazyServiceGetter(this, "gSystemWorkerManager",
+ "@mozilla.org/telephony/system-worker-manager;1",
+ "nsISystemWorkerManager");
+
+XPCOMUtils.defineLazyServiceGetter(this, "gTelephonyService",
+ "@mozilla.org/telephony/telephonyservice;1",
+ "nsIGonkTelephonyService");
+
+XPCOMUtils.defineLazyServiceGetter(this, "gMobileConnectionService",
+ "@mozilla.org/mobileconnection/mobileconnectionservice;1",
+ "nsIGonkMobileConnectionService");
+
+XPCOMUtils.defineLazyServiceGetter(this, "gCellBroadcastService",
+ "@mozilla.org/cellbroadcast/cellbroadcastservice;1",
+ "nsIGonkCellBroadcastService");
+
+XPCOMUtils.defineLazyServiceGetter(this, "gDataCallManager",
+ "@mozilla.org/datacall/manager;1",
+ "nsIDataCallManager");
+
+XPCOMUtils.defineLazyServiceGetter(this, "gDataCallInterfaceService",
+ "@mozilla.org/datacall/interfaceservice;1",
+ "nsIGonkDataCallInterfaceService");
+
+XPCOMUtils.defineLazyServiceGetter(this, "gStkCmdFactory",
+ "@mozilla.org/icc/stkcmdfactory;1",
+ "nsIStkCmdFactory");
+
+XPCOMUtils.defineLazyGetter(this, "gRadioEnabledController", function() {
+ let _ril = null;
+ let _pendingMessages = []; // For queueing "setRadioEnabled" message.
+ let _isProcessingPending = false;
+ let _timer = null;
+ let _request = null;
+ let _deactivatingDeferred = {};
+ let _initializedCardState = {};
+ let _allCardStateInitialized = !RILQUIRKS_RADIO_OFF_WO_CARD;
+
+ return {
+ init: function(ril) {
+ _ril = ril;
+ },
+
+ receiveCardState: function(clientId) {
+ if (_allCardStateInitialized) {
+ return;
+ }
+
+ if (DEBUG) debug("RadioControl: receive cardState from " + clientId);
+ _initializedCardState[clientId] = true;
+ if (Object.keys(_initializedCardState).length == _ril.numRadioInterfaces) {
+ _allCardStateInitialized = true;
+ this._startProcessingPending();
+ }
+ },
+
+ setRadioEnabled: function(clientId, data, callback) {
+ if (DEBUG) debug("setRadioEnabled: " + clientId + ": " + JSON.stringify(data));
+ let message = {
+ clientId: clientId,
+ data: data,
+ callback: callback
+ };
+ _pendingMessages.push(message);
+ this._startProcessingPending();
+ },
+
+ notifyRadioStateChanged: function(clientId, radioState) {
+ gMobileConnectionService.notifyRadioStateChanged(clientId, radioState);
+ },
+
+ _startProcessingPending: function() {
+ if (!_isProcessingPending) {
+ if (DEBUG) debug("RadioControl: start dequeue");
+ _isProcessingPending = true;
+ this._processNextMessage();
+ }
+ },
+
+ _processNextMessage: function() {
+ if (_pendingMessages.length === 0 || !_allCardStateInitialized) {
+ if (DEBUG) debug("RadioControl: stop dequeue");
+ _isProcessingPending = false;
+ return;
+ }
+
+ let msg = _pendingMessages.shift();
+ this._handleMessage(msg);
+ },
+
+ _getNumCards: function() {
+ let numCards = 0;
+ for (let i = 0, N = _ril.numRadioInterfaces; i < N; ++i) {
+ if (_ril.getRadioInterface(i).isCardPresent()) {
+ numCards++;
+ }
+ }
+ return numCards;
+ },
+
+ _isRadioAbleToEnableAtClient: function(clientId, numCards) {
+ if (!RILQUIRKS_RADIO_OFF_WO_CARD) {
+ return true;
+ }
+
+ // We could only turn on the radio for clientId if
+ // 1. a SIM card is presented or
+ // 2. it is the default clientId and there is no any SIM card at any client.
+
+ if (_ril.getRadioInterface(clientId).isCardPresent()) {
+ return true;
+ }
+
+ numCards = numCards == null ? this._getNumCards() : numCards;
+ if (clientId === HW_DEFAULT_CLIENT_ID && numCards === 0) {
+ return true;
+ }
+
+ return false;
+ },
+
+ _handleMessage: function(message) {
+ if (DEBUG) debug("RadioControl: handleMessage: " + JSON.stringify(message));
+ let clientId = message.clientId || 0;
+ let connection =
+ gMobileConnectionService.getItemByServiceId(clientId);
+ let radioState = connection && connection.radioState;
+
+ if (message.data.enabled) {
+ if (this._isRadioAbleToEnableAtClient(clientId)) {
+ this._setRadioEnabledInternal(message);
+ } else {
+ // Not really do it but respond success.
+ message.callback(message.data);
+ }
+
+ this._processNextMessage();
+ } else {
+ _request = this._setRadioEnabledInternal.bind(this, message);
+
+ // In 2G network, modem takes 35+ seconds to process deactivate data
+ // call request if device has active voice call (please see bug 964974
+ // for more details). Therefore we should hangup all active voice calls
+ // first. And considering some DSDS architecture, toggling one radio may
+ // toggle both, so we send hangUpAll to all clients.
+ let hangUpCallback = {
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsITelephonyCallback]),
+ notifySuccess: function() {},
+ notifyError: function() {}
+ };
+
+ gTelephonyService.enumerateCalls({
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsITelephonyListener]),
+ enumerateCallState: function(aInfo) {
+ gTelephonyService.hangUpCall(aInfo.clientId, aInfo.callIndex,
+ hangUpCallback);
+ },
+ enumerateCallStateComplete: function() {}
+ });
+
+ // In some DSDS architecture with only one modem, toggling one radio may
+ // toggle both. Therefore, for safely turning off, we should first
+ // explicitly deactivate all data calls from all clients.
+ this._deactivateDataCalls().then(() => {
+ if (DEBUG) debug("RadioControl: deactivation done");
+ this._executeRequest();
+ });
+
+ this._createTimer();
+ }
+ },
+
+ _setRadioEnabledInternal: function(message) {
+ let clientId = message.clientId || 0;
+ let enabled = message.data.enabled || false;
+ let radioInterface = _ril.getRadioInterface(clientId);
+
+ radioInterface.workerMessenger.send("setRadioEnabled", message.data,
+ (function(response) {
+ if (response.errorMsg) {
+ // If request fails, set current radio state to unknown, since we will
+ // handle it in |mobileConnectionService|.
+ this.notifyRadioStateChanged(clientId,
+ Ci.nsIMobileConnection.MOBILE_RADIO_STATE_UNKNOWN);
+ }
+ return message.callback(response);
+ }).bind(this));
+ },
+
+ _deactivateDataCalls: function() {
+ if (DEBUG) debug("RadioControl: deactivating data calls...");
+ _deactivatingDeferred = {};
+
+ let promise = Promise.resolve();
+ for (let i = 0, N = _ril.numRadioInterfaces; i < N; ++i) {
+ promise = promise.then(this._deactivateDataCallsForClient(i));
+ }
+
+ return promise;
+ },
+
+ _deactivateDataCallsForClient: function(clientId) {
+ return function() {
+ let deferred = _deactivatingDeferred[clientId] = Promise.defer();
+ let dataCallHandler = gDataCallManager.getDataCallHandler(clientId);
+
+ dataCallHandler.deactivateDataCalls(function() {
+ deferred.resolve();
+ });
+
+ return deferred.promise;
+ };
+ },
+
+ _createTimer: function() {
+ if (!_timer) {
+ _timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
+ }
+ _timer.initWithCallback(this._executeRequest.bind(this),
+ RADIO_POWER_OFF_TIMEOUT,
+ Ci.nsITimer.TYPE_ONE_SHOT);
+ },
+
+ _cancelTimer: function() {
+ if (_timer) {
+ _timer.cancel();
+ }
+ },
+
+ _executeRequest: function() {
+ if (typeof _request === "function") {
+ if (DEBUG) debug("RadioControl: executeRequest");
+ this._cancelTimer();
+ _request();
+ _request = null;
+ }
+ this._processNextMessage();
+ },
+ };
+});
+
+// Initialize shared preference "ril.numRadioInterfaces" according to system
+// property.
+try {
+ Services.prefs.setIntPref(kPrefRilNumRadioInterfaces, (function() {
+ // When Gonk property "ro.moz.ril.numclients" is not set, return 1; if
+ // explicitly set to any number larger-equal than 0, return num; else, return
+ // 1 for compatibility.
+ try {
+ let numString = libcutils.property_get("ro.moz.ril.numclients", "1");
+ let num = parseInt(numString, 10);
+ if (num >= 0) {
+ return num;
+ }
+ } catch (e) {}
+
+ return 1;
+ })());
+} catch (e) {}
+
+function DataCall(aAttributes) {
+ for (let key in aAttributes) {
+ if (key === "pdpType") {
+ // Convert pdp type into constant int value.
+ this[key] = RIL.RIL_DATACALL_PDP_TYPES.indexOf(aAttributes[key]);
+ continue;
+ }
+
+ this[key] = aAttributes[key];
+ }
+}
+DataCall.prototype = {
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIDataCall]),
+
+ failCause: Ci.nsIDataCallInterface.DATACALL_FAIL_NONE,
+ suggestedRetryTime: -1,
+ cid: -1,
+ active: -1,
+ pdpType: -1,
+ ifname: null,
+ addreses: null,
+ dnses: null,
+ gateways: null,
+ pcscf: null,
+ mtu: -1
+};
+
+function RadioInterfaceLayer() {
+ let workerMessenger = new WorkerMessenger();
+ workerMessenger.init();
+ this.setWorkerDebugFlag = workerMessenger.setDebugFlag.bind(workerMessenger);
+
+ let numIfaces = this.numRadioInterfaces;
+ if (DEBUG) debug(numIfaces + " interfaces");
+ this.radioInterfaces = [];
+ for (let clientId = 0; clientId < numIfaces; clientId++) {
+ this.radioInterfaces.push(new RadioInterface(clientId, workerMessenger));
+ }
+
+ Services.obs.addObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, false);
+ Services.prefs.addObserver(kPrefRilDebuggingEnabled, this, false);
+
+ gRadioEnabledController.init(this);
+}
+RadioInterfaceLayer.prototype = {
+
+ classID: RADIOINTERFACELAYER_CID,
+ classInfo: XPCOMUtils.generateCI({classID: RADIOINTERFACELAYER_CID,
+ classDescription: "RadioInterfaceLayer",
+ interfaces: [Ci.nsIRadioInterfaceLayer]}),
+
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIRadioInterfaceLayer,
+ Ci.nsIObserver]),
+
+ /**
+ * nsIObserver interface methods.
+ */
+
+ observe: function(subject, topic, data) {
+ switch (topic) {
+ case NS_XPCOM_SHUTDOWN_OBSERVER_ID:
+ for (let radioInterface of this.radioInterfaces) {
+ radioInterface.shutdown();
+ }
+ this.radioInterfaces = null;
+ Services.obs.removeObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID);
+ break;
+
+ case NS_PREFBRANCH_PREFCHANGE_TOPIC_ID:
+ if (data === kPrefRilDebuggingEnabled) {
+ updateDebugFlag();
+ this.setWorkerDebugFlag(DEBUG);
+ }
+ break;
+ }
+ },
+
+ /**
+ * nsIRadioInterfaceLayer interface methods.
+ */
+
+ getRadioInterface: function(clientId) {
+ return this.radioInterfaces[clientId];
+ },
+
+ setMicrophoneMuted: function(muted) {
+ for (let clientId = 0; clientId < this.numRadioInterfaces; clientId++) {
+ let radioInterface = this.radioInterfaces[clientId];
+ radioInterface.workerMessenger.send("setMute", { muted: muted });
+ }
+ }
+};
+
+XPCOMUtils.defineLazyGetter(RadioInterfaceLayer.prototype,
+ "numRadioInterfaces", function() {
+ try {
+ return Services.prefs.getIntPref(kPrefRilNumRadioInterfaces);
+ } catch(e) {}
+
+ return 1;
+});
+
+function WorkerMessenger() {
+ // Initial owning attributes.
+ this.radioInterfaces = [];
+ this.tokenCallbackMap = {};
+
+ this.worker = new ChromeWorker("resource://gre/modules/ril_worker.js");
+ this.worker.onerror = this.onerror.bind(this);
+ this.worker.onmessage = this.onmessage.bind(this);
+}
+WorkerMessenger.prototype = {
+ radioInterfaces: null,
+ worker: null,
+
+ // This gets incremented each time we send out a message.
+ token: 1,
+
+ // Maps tokens we send out with messages to the message callback.
+ tokenCallbackMap: null,
+
+ init: function() {
+ let options = {
+ debug: DEBUG,
+ quirks: {
+ callstateExtraUint32:
+ libcutils.property_get("ro.moz.ril.callstate_extra_int", "false") === "true",
+ requestUseDialEmergencyCall:
+ libcutils.property_get("ro.moz.ril.dial_emergency_call", "false") === "true",
+ simAppStateExtraFields:
+ libcutils.property_get("ro.moz.ril.simstate_extra_field", "false") === "true",
+ extraUint2ndCall:
+ libcutils.property_get("ro.moz.ril.extra_int_2nd_call", "false") === "true",
+ haveQueryIccLockRetryCount:
+ libcutils.property_get("ro.moz.ril.query_icc_count", "false") === "true",
+ sendStkProfileDownload:
+ libcutils.property_get("ro.moz.ril.send_stk_profile_dl", "false") === "true",
+ smscAddressFormat:
+ libcutils.property_get("ro.moz.ril.smsc_address_format", "text"),
+ dataRegistrationOnDemand:
+ libcutils.property_get("ro.moz.ril.data_reg_on_demand", "false") === "true",
+ subscriptionControl:
+ libcutils.property_get("ro.moz.ril.subscription_control", "false") === "true",
+ signalExtraInt:
+ libcutils.property_get("ro.moz.ril.signal_extra_int", "false") === "true",
+ availableNetworkExtraStr:
+ libcutils.property_get("ro.moz.ril.avlbl_nw_extra_str", "false") === "true",
+ }
+ };
+
+ this.send(null, "setInitialOptions", options);
+ },
+
+ setDebugFlag: function(aDebug) {
+ let options = { debug: aDebug };
+ this.send(null, "setDebugFlag", options);
+ },
+
+ debug: function(aClientId, aMessage) {
+ // We use the same debug subject with RadioInterface's here.
+ dump("-*- RadioInterface[" + aClientId + "]: " + aMessage + "\n");
+ },
+
+ onerror: function(event) {
+ if (DEBUG) {
+ this.debug("X", "Got an error: " + event.filename + ":" +
+ event.lineno + ": " + event.message + "\n");
+ }
+ event.preventDefault();
+ },
+
+ /**
+ * Process the incoming message from the RIL worker.
+ */
+ onmessage: function(event) {
+ let message = event.data;
+ let clientId = message.rilMessageClientId;
+ if (clientId === null) {
+ return;
+ }
+
+ if (DEBUG) {
+ this.debug(clientId, "Received message from worker: " + JSON.stringify(message));
+ }
+
+ let token = message.rilMessageToken;
+ if (token == null) {
+ // That's an unsolicited message. Pass to RadioInterface directly.
+ let radioInterface = this.radioInterfaces[clientId];
+ radioInterface.handleUnsolicitedWorkerMessage(message);
+ return;
+ }
+
+ let callback = this.tokenCallbackMap[message.rilMessageToken];
+ if (!callback) {
+ if (DEBUG) this.debug(clientId, "Ignore orphan token: " + message.rilMessageToken);
+ return;
+ }
+
+ let keep = false;
+ try {
+ keep = callback(message);
+ } catch(e) {
+ if (DEBUG) this.debug(clientId, "callback throws an exception: " + e);
+ }
+
+ if (!keep) {
+ delete this.tokenCallbackMap[message.rilMessageToken];
+ }
+ },
+
+ registerClient: function(aClientId, aRadioInterface) {
+ if (DEBUG) this.debug(aClientId, "Starting RIL Worker");
+
+ // Keep a reference so that we can dispatch unsolicited messages to it.
+ this.radioInterfaces[aClientId] = aRadioInterface;
+
+ this.send(null, "registerClient", { clientId: aClientId });
+ gSystemWorkerManager.registerRilWorker(aClientId, this.worker);
+ },
+
+ /**
+ * Send arbitrary message to worker.
+ *
+ * @param rilMessageType
+ * A text message type.
+ * @param message [optional]
+ * An optional message object to send.
+ * @param callback [optional]
+ * An optional callback function which is called when worker replies
+ * with an message containing a "rilMessageToken" attribute of the
+ * same value we passed. This callback function accepts only one
+ * parameter -- the reply from worker. It also returns a boolean
+ * value true to keep current token-callback mapping and wait for
+ * another worker reply, or false to remove the mapping.
+ */
+ send: function(clientId, rilMessageType, message, callback) {
+ message = message || {};
+
+ message.rilMessageClientId = clientId;
+ message.rilMessageToken = this.token;
+ this.token++;
+
+ if (callback) {
+ // Only create the map if callback is provided. For sending a request
+ // and intentionally leaving the callback undefined, that reply will
+ // be dropped in |this.onmessage| because of that orphan token.
+ //
+ // For sending a request that never replied at all, we're fine with this
+ // because no callback shall be passed and we leave nothing to be cleaned
+ // up later.
+ this.tokenCallbackMap[message.rilMessageToken] = callback;
+ }
+
+ message.rilMessageType = rilMessageType;
+ this.worker.postMessage(message);
+ }
+};
+
+function RadioInterface(aClientId, aWorkerMessenger) {
+ this.clientId = aClientId;
+ this.workerMessenger = {
+ send: aWorkerMessenger.send.bind(aWorkerMessenger, aClientId)
+ };
+ aWorkerMessenger.registerClient(aClientId, this);
+
+ this.operatorInfo = {};
+
+ let lock = gSettingsService.createLock();
+
+ // Read the "time.clock.automatic-update.enabled" setting to see if
+ // we need to adjust the system clock time by NITZ or SNTP.
+ lock.get(kSettingsClockAutoUpdateEnabled, this);
+
+ // Read the "time.timezone.automatic-update.enabled" setting to see if
+ // we need to adjust the system timezone by NITZ.
+ lock.get(kSettingsTimezoneAutoUpdateEnabled, this);
+
+ // Set "time.clock.automatic-update.available" to false when starting up.
+ this.setClockAutoUpdateAvailable(false);
+
+ // Set "time.timezone.automatic-update.available" to false when starting up.
+ this.setTimezoneAutoUpdateAvailable(false);
+
+ Services.obs.addObserver(this, kMozSettingsChangedObserverTopic, false);
+ Services.obs.addObserver(this, kSysClockChangeObserverTopic, false);
+ Services.obs.addObserver(this, kScreenStateChangedTopic, false);
+
+ Services.obs.addObserver(this, kNetworkConnStateChangedTopic, false);
+
+ this._sntp = new Sntp(this.setClockBySntp.bind(this),
+ Services.prefs.getIntPref("network.sntp.maxRetryCount"),
+ Services.prefs.getIntPref("network.sntp.refreshPeriod"),
+ Services.prefs.getIntPref("network.sntp.timeout"),
+ Services.prefs.getCharPref("network.sntp.pools").split(";"),
+ Services.prefs.getIntPref("network.sntp.port"));
+}
+
+RadioInterface.prototype = {
+
+ classID: RADIOINTERFACE_CID,
+ classInfo: XPCOMUtils.generateCI({classID: RADIOINTERFACE_CID,
+ classDescription: "RadioInterface",
+ interfaces: [Ci.nsIRadioInterface]}),
+
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIRadioInterface,
+ Ci.nsIObserver,
+ Ci.nsISettingsServiceCallback]),
+
+ // A private wrapped WorkerMessenger instance.
+ workerMessenger: null,
+
+ debug: function(s) {
+ dump("-*- RadioInterface[" + this.clientId + "]: " + s + "\n");
+ },
+
+ shutdown: function() {
+ Services.obs.removeObserver(this, kMozSettingsChangedObserverTopic);
+ Services.obs.removeObserver(this, kSysClockChangeObserverTopic);
+ Services.obs.removeObserver(this, kScreenStateChangedTopic);
+ Services.obs.removeObserver(this, kNetworkConnStateChangedTopic);
+ },
+
+ isCardPresent: function() {
+ let icc = gIccService.getIccByServiceId(this.clientId);
+ let cardState = icc ? icc.cardState : Ci.nsIIcc.CARD_STATE_UNKNOWN;
+ return cardState !== Ci.nsIIcc.CARD_STATE_UNDETECTED &&
+ cardState !== Ci.nsIIcc.CARD_STATE_UNKNOWN;
+ },
+
+ handleUnsolicitedWorkerMessage: function(message) {
+ switch (message.rilMessageType) {
+ case "callRing":
+ gTelephonyService.notifyCallRing();
+ break;
+ case "currentCalls":
+ gTelephonyService.notifyCurrentCalls(this.clientId, message.calls);
+ break;
+ case "cdmaCallWaiting":
+ gTelephonyService.notifyCdmaCallWaiting(this.clientId,
+ message.waitingCall);
+ break;
+ case "suppSvcNotification":
+ gTelephonyService.notifySupplementaryService(this.clientId,
+ message.number,
+ message.notification);
+ break;
+ case "ussdreceived":
+ gTelephonyService.notifyUssdReceived(this.clientId, message.message,
+ message.sessionEnded);
+ break;
+ case "datacalllistchanged":
+ let dataCalls = message.datacalls.map(dataCall => new DataCall(dataCall));
+ gDataCallInterfaceService.notifyDataCallListChanged(this.clientId,
+ dataCalls.length,
+ dataCalls);
+ break;
+ case "emergencyCbModeChange":
+ gMobileConnectionService.notifyEmergencyCallbackModeChanged(this.clientId,
+ message.active,
+ message.timeoutMs);
+ break;
+ case "networkinfochanged":
+ gMobileConnectionService.notifyNetworkInfoChanged(this.clientId,
+ message);
+ break;
+ case "networkselectionmodechange":
+ gMobileConnectionService.notifyNetworkSelectModeChanged(this.clientId,
+ message.mode);
+ break;
+ case "voiceregistrationstatechange":
+ gMobileConnectionService.notifyVoiceInfoChanged(this.clientId, message);
+ break;
+ case "dataregistrationstatechange":
+ gMobileConnectionService.notifyDataInfoChanged(this.clientId, message);
+ break;
+ case "signalstrengthchange":
+ gMobileConnectionService.notifySignalStrengthChanged(this.clientId,
+ message);
+ break;
+ case "operatorchange":
+ gMobileConnectionService.notifyOperatorChanged(this.clientId, message);
+ break;
+ case "otastatuschange":
+ gMobileConnectionService.notifyOtaStatusChanged(this.clientId, message.status);
+ break;
+ case "deviceidentitieschange":
+ gMobileConnectionService.notifyDeviceIdentitiesChanged(this.clientId,
+ message.deviceIdentities.imei,
+ message.deviceIdentities.imeisv,
+ message.deviceIdentities.esn,
+ message.deviceIdentities.meid);
+ break;
+ case "radiostatechange":
+ // gRadioEnabledController should know the radio state for each client,
+ // so notify gRadioEnabledController here.
+ gRadioEnabledController.notifyRadioStateChanged(this.clientId,
+ message.radioState);
+ break;
+ case "cardstatechange":
+ gIccService.notifyCardStateChanged(this.clientId,
+ message.cardState);
+ gRadioEnabledController.receiveCardState(this.clientId);
+ break;
+ case "sms-received":
+ this.handleSmsReceived(message);
+ break;
+ case "cellbroadcast-received":
+ this.handleCellbroadcastMessageReceived(message);
+ break;
+ case "nitzTime":
+ this.handleNitzTime(message);
+ break;
+ case "iccinfochange":
+ gIccService.notifyIccInfoChanged(this.clientId,
+ message.iccid ? message : null);
+ break;
+ case "iccimsi":
+ gIccService.notifyImsiChanged(this.clientId, message.imsi);
+ break;
+ case "iccmbdn":
+ this.handleIccMbdn(message);
+ break;
+ case "iccmwis":
+ this.handleIccMwis(message.mwi);
+ break;
+ case "stkcommand":
+ gIccService.notifyStkCommand(this.clientId,
+ gStkCmdFactory.createCommand(message));
+ break;
+ case "stksessionend":
+ gIccService.notifyStkSessionEnd(this.clientId);
+ break;
+ case "cdma-info-rec-received":
+ this.handleCdmaInformationRecords(message.records);
+ break;
+ default:
+ throw new Error("Don't know about this message type: " +
+ message.rilMessageType);
+ }
+ },
+
+ setDataRegistration: function(attach) {
+ let deferred = Promise.defer();
+ this.workerMessenger.send("setDataRegistration",
+ {attach: attach},
+ (function(response) {
+ // Always resolve to proceed with the following steps.
+ deferred.resolve(response.errorMsg ? response.errorMsg : null);
+ }).bind(this));
+
+ return deferred.promise;
+ },
+
+ /**
+ * TODO: Bug 911713 - B2G NetworkManager: Move policy control logic to
+ * NetworkManager
+ */
+ updateRILNetworkInterface: function() {
+ let connHandler = gDataCallManager.getDataCallHandler(this.clientId);
+ connHandler.updateRILNetworkInterface();
+ },
+
+ /**
+ * handle received SMS.
+ */
+ handleSmsReceived: function(aMessage) {
+ let header = aMessage.header;
+ // Concatenation Info:
+ // - segmentRef: a modulo 256 counter indicating the reference number for a
+ // particular concatenated short message. '0' is a valid number.
+ // - The concatenation info will not be available in |header| if
+ // segmentSeq or segmentMaxSeq is 0.
+ // See 3GPP TS 23.040, 9.2.3.24.1 Concatenated Short Messages.
+ let segmentRef = (header && header.segmentRef !== undefined)
+ ? header.segmentRef : 1;
+ let segmentSeq = header && header.segmentSeq || 1;
+ let segmentMaxSeq = header && header.segmentMaxSeq || 1;
+ // Application Ports:
+ // The port number ranges from 0 to 49151.
+ // see 3GPP TS 23.040, 9.2.3.24.3/4 Application Port Addressing.
+ let originatorPort = (header && header.originatorPort !== undefined)
+ ? header.originatorPort
+ : Ci.nsIGonkSmsService.SMS_APPLICATION_PORT_INVALID;
+ let destinationPort = (header && header.destinationPort !== undefined)
+ ? header.destinationPort
+ : Ci.nsIGonkSmsService.SMS_APPLICATION_PORT_INVALID;
+ // MWI info:
+ let mwiPresent = (aMessage.mwi)? true : false;
+ let mwiDiscard = (mwiPresent)? aMessage.mwi.discard: false;
+ let mwiMsgCount = (mwiPresent)? aMessage.mwi.msgCount: 0;
+ let mwiActive = (mwiPresent)? aMessage.mwi.active: false;
+ // CDMA related attributes:
+ let cdmaMessageType = aMessage.messageType || 0;
+ let cdmaTeleservice = aMessage.teleservice || 0;
+ let cdmaServiceCategory = aMessage.serviceCategory || 0;
+
+ gSmsService
+ .notifyMessageReceived(this.clientId,
+ aMessage.SMSC || null,
+ aMessage.sentTimestamp,
+ aMessage.sender,
+ aMessage.pid,
+ aMessage.encoding,
+ RIL.GECKO_SMS_MESSAGE_CLASSES
+ .indexOf(aMessage.messageClass),
+ aMessage.language || null,
+ segmentRef,
+ segmentSeq,
+ segmentMaxSeq,
+ originatorPort,
+ destinationPort,
+ mwiPresent,
+ mwiDiscard,
+ mwiMsgCount,
+ mwiActive,
+ cdmaMessageType,
+ cdmaTeleservice,
+ cdmaServiceCategory,
+ aMessage.body || null,
+ aMessage.data || [],
+ (aMessage.data) ? aMessage.data.length : 0);
+ },
+
+ /**
+ * Set the setting value of "time.clock.automatic-update.available".
+ */
+ setClockAutoUpdateAvailable: function(value) {
+ gSettingsService.createLock().set(kSettingsClockAutoUpdateAvailable, value, null);
+ },
+
+ /**
+ * Set the setting value of "time.timezone.automatic-update.available".
+ */
+ setTimezoneAutoUpdateAvailable: function(value) {
+ gSettingsService.createLock().set(kSettingsTimezoneAutoUpdateAvailable, value, null);
+ },
+
+ /**
+ * Set the system clock by NITZ.
+ */
+ setClockByNitz: function(message) {
+ // To set the system clock time. Note that there could be a time diff
+ // between when the NITZ was received and when the time is actually set.
+ gTimeService.set(
+ message.networkTimeInMS + (Date.now() - message.receiveTimeInMS));
+ },
+
+ /**
+ * Set the system time zone by NITZ.
+ */
+ setTimezoneByNitz: function(message) {
+ // To set the sytem timezone. Note that we need to convert the time zone
+ // value to a UTC repesentation string in the format of "UTC(+/-)hh:mm".
+ // Ex, time zone -480 is "UTC+08:00"; time zone 630 is "UTC-10:30".
+ //
+ // We can unapply the DST correction if we want the raw time zone offset:
+ // message.networkTimeZoneInMinutes -= message.networkDSTInMinutes;
+ if (message.networkTimeZoneInMinutes != (new Date()).getTimezoneOffset()) {
+ let absTimeZoneInMinutes = Math.abs(message.networkTimeZoneInMinutes);
+ let timeZoneStr = "UTC";
+ timeZoneStr += (message.networkTimeZoneInMinutes > 0 ? "-" : "+");
+ timeZoneStr += ("0" + Math.floor(absTimeZoneInMinutes / 60)).slice(-2);
+ timeZoneStr += ":";
+ timeZoneStr += ("0" + absTimeZoneInMinutes % 60).slice(-2);
+ gSettingsService.createLock().set("time.timezone", timeZoneStr, null);
+ }
+ },
+
+ /**
+ * Handle the NITZ message.
+ */
+ handleNitzTime: function(message) {
+ // Got the NITZ info received from the ril_worker.
+ this.setClockAutoUpdateAvailable(true);
+ this.setTimezoneAutoUpdateAvailable(true);
+
+ // Cache the latest NITZ message whenever receiving it.
+ this._lastNitzMessage = message;
+
+ // Set the received NITZ clock if the setting is enabled.
+ if (this._clockAutoUpdateEnabled) {
+ this.setClockByNitz(message);
+ }
+ // Set the received NITZ timezone if the setting is enabled.
+ if (this._timezoneAutoUpdateEnabled) {
+ this.setTimezoneByNitz(message);
+ }
+ },
+
+ /**
+ * Set the system clock by SNTP.
+ */
+ setClockBySntp: function(offset) {
+ // Got the SNTP info.
+ this.setClockAutoUpdateAvailable(true);
+ if (!this._clockAutoUpdateEnabled) {
+ return;
+ }
+ if (this._lastNitzMessage) {
+ if (DEBUG) debug("SNTP: NITZ available, discard SNTP");
+ return;
+ }
+ gTimeService.set(Date.now() + offset);
+ },
+
+ handleIccMbdn: function(message) {
+ let service = Cc["@mozilla.org/voicemail/voicemailservice;1"]
+ .getService(Ci.nsIGonkVoicemailService);
+ service.notifyInfoChanged(this.clientId, message.number, message.alphaId);
+ },
+
+ handleIccMwis: function(mwi) {
+ let service = Cc["@mozilla.org/voicemail/voicemailservice;1"]
+ .getService(Ci.nsIGonkVoicemailService);
+ // Note: returnNumber and returnMessage is not available from UICC.
+ service.notifyStatusChanged(this.clientId, mwi.active, mwi.msgCount,
+ null, null);
+ },
+
+ _convertCbGsmGeographicalScope: function(aGeographicalScope) {
+ return (aGeographicalScope != null)
+ ? aGeographicalScope
+ : Ci.nsICellBroadcastService.GSM_GEOGRAPHICAL_SCOPE_INVALID;
+ },
+
+ _convertCbMessageClass: function(aMessageClass) {
+ let index = RIL.GECKO_SMS_MESSAGE_CLASSES.indexOf(aMessageClass);
+ return (index != -1)
+ ? index
+ : Ci.nsICellBroadcastService.GSM_MESSAGE_CLASS_NORMAL;
+ },
+
+ _convertCbEtwsWarningType: function(aWarningType) {
+ return (aWarningType != null)
+ ? aWarningType
+ : Ci.nsICellBroadcastService.GSM_ETWS_WARNING_INVALID;
+ },
+
+ handleCellbroadcastMessageReceived: function(aMessage) {
+ let etwsInfo = aMessage.etws;
+ let hasEtwsInfo = etwsInfo != null;
+ let serviceCategory = (aMessage.serviceCategory)
+ ? aMessage.serviceCategory
+ : Ci.nsICellBroadcastService.CDMA_SERVICE_CATEGORY_INVALID;
+
+ gCellBroadcastService
+ .notifyMessageReceived(this.clientId,
+ this._convertCbGsmGeographicalScope(aMessage.geographicalScope),
+ aMessage.messageCode,
+ aMessage.messageId,
+ aMessage.language,
+ aMessage.fullBody,
+ this._convertCbMessageClass(aMessage.messageClass),
+ Date.now(),
+ serviceCategory,
+ hasEtwsInfo,
+ (hasEtwsInfo)
+ ? this._convertCbEtwsWarningType(etwsInfo.warningType)
+ : Ci.nsICellBroadcastService.GSM_ETWS_WARNING_INVALID,
+ hasEtwsInfo ? etwsInfo.emergencyUserAlert : false,
+ hasEtwsInfo ? etwsInfo.popup : false);
+ },
+
+ handleCdmaInformationRecords: function(aRecords) {
+ if (DEBUG) this.debug("cdma-info-rec-received: " + JSON.stringify(aRecords));
+
+ let clientId = this.clientId;
+
+ aRecords.forEach(function(aRecord) {
+ if (aRecord.display) {
+ gMobileConnectionService
+ .notifyCdmaInfoRecDisplay(clientId, aRecord.display);
+ return;
+ }
+
+ if (aRecord.calledNumber) {
+ gMobileConnectionService
+ .notifyCdmaInfoRecCalledPartyNumber(clientId,
+ aRecord.calledNumber.type,
+ aRecord.calledNumber.plan,
+ aRecord.calledNumber.number,
+ aRecord.calledNumber.pi,
+ aRecord.calledNumber.si);
+ return;
+ }
+
+ if (aRecord.callingNumber) {
+ gMobileConnectionService
+ .notifyCdmaInfoRecCallingPartyNumber(clientId,
+ aRecord.callingNumber.type,
+ aRecord.callingNumber.plan,
+ aRecord.callingNumber.number,
+ aRecord.callingNumber.pi,
+ aRecord.callingNumber.si);
+ return;
+ }
+
+ if (aRecord.connectedNumber) {
+ gMobileConnectionService
+ .notifyCdmaInfoRecConnectedPartyNumber(clientId,
+ aRecord.connectedNumber.type,
+ aRecord.connectedNumber.plan,
+ aRecord.connectedNumber.number,
+ aRecord.connectedNumber.pi,
+ aRecord.connectedNumber.si);
+ return;
+ }
+
+ if (aRecord.signal) {
+ gMobileConnectionService
+ .notifyCdmaInfoRecSignal(clientId,
+ aRecord.signal.type,
+ aRecord.signal.alertPitch,
+ aRecord.signal.signal);
+ return;
+ }
+
+ if (aRecord.redirect) {
+ gMobileConnectionService
+ .notifyCdmaInfoRecRedirectingNumber(clientId,
+ aRecord.redirect.type,
+ aRecord.redirect.plan,
+ aRecord.redirect.number,
+ aRecord.redirect.pi,
+ aRecord.redirect.si,
+ aRecord.redirect.reason);
+ return;
+ }
+
+ if (aRecord.lineControl) {
+ gMobileConnectionService
+ .notifyCdmaInfoRecLineControl(clientId,
+ aRecord.lineControl.polarityIncluded,
+ aRecord.lineControl.toggle,
+ aRecord.lineControl.reverse,
+ aRecord.lineControl.powerDenial);
+ return;
+ }
+
+ if (aRecord.clirCause) {
+ gMobileConnectionService
+ .notifyCdmaInfoRecClir(clientId,
+ aRecord.clirCause);
+ return;
+ }
+
+ if (aRecord.audioControl) {
+ gMobileConnectionService
+ .notifyCdmaInfoRecAudioControl(clientId,
+ aRecord.audioControl.upLink,
+ aRecord.audioControl.downLink);
+ return;
+ }
+ });
+ },
+
+ // nsIObserver
+
+ observe: function(subject, topic, data) {
+ switch (topic) {
+ case kMozSettingsChangedObserverTopic:
+ if ("wrappedJSObject" in subject) {
+ subject = subject.wrappedJSObject;
+ }
+ this.handleSettingsChange(subject.key, subject.value, subject.isInternalChange);
+ break;
+ case kSysClockChangeObserverTopic:
+ let offset = parseInt(data, 10);
+ if (this._lastNitzMessage) {
+ this._lastNitzMessage.receiveTimeInMS += offset;
+ }
+ this._sntp.updateOffset(offset);
+ break;
+ case kNetworkConnStateChangedTopic:
+ let networkInfo = subject.QueryInterface(Ci.nsINetworkInfo);
+ if (networkInfo.state != Ci.nsINetworkInfo.NETWORK_STATE_CONNECTED) {
+ return;
+ }
+
+ // SNTP can only update when we have mobile or Wifi connections.
+ if (networkInfo.type != NETWORK_TYPE_WIFI &&
+ networkInfo.type != NETWORK_TYPE_MOBILE) {
+ return;
+ }
+
+ // If the network comes from RIL, make sure the RIL service is matched.
+ if (subject instanceof Ci.nsIRilNetworkInfo) {
+ networkInfo = subject.QueryInterface(Ci.nsIRilNetworkInfo);
+ if (networkInfo.serviceId != this.clientId) {
+ return;
+ }
+ }
+
+ // SNTP won't update unless the SNTP is already expired.
+ if (this._sntp.isExpired()) {
+ this._sntp.request();
+ }
+ break;
+ case kScreenStateChangedTopic:
+ this.workerMessenger.send("setScreenState", { on: (data === "on") });
+ break;
+ }
+ },
+
+ // Flag to determine whether to update system clock automatically. It
+ // corresponds to the "time.clock.automatic-update.enabled" setting.
+ _clockAutoUpdateEnabled: null,
+
+ // Flag to determine whether to update system timezone automatically. It
+ // corresponds to the "time.clock.automatic-update.enabled" setting.
+ _timezoneAutoUpdateEnabled: null,
+
+ // Remember the last NITZ message so that we can set the time based on
+ // the network immediately when users enable network-based time.
+ _lastNitzMessage: null,
+
+ // Object that handles SNTP.
+ _sntp: null,
+
+ // Cell Broadcast settings values.
+ _cellBroadcastSearchList: null,
+
+ handleSettingsChange: function(aName, aResult, aIsInternalSetting) {
+ // Don't allow any content processes to modify the setting
+ // "time.clock.automatic-update.available" except for the chrome process.
+ if (aName === kSettingsClockAutoUpdateAvailable &&
+ !aIsInternalSetting) {
+ let isClockAutoUpdateAvailable = this._lastNitzMessage !== null ||
+ this._sntp.isAvailable();
+ if (aResult !== isClockAutoUpdateAvailable) {
+ if (DEBUG) {
+ debug("Content processes cannot modify 'time.clock.automatic-update.available'. Restore!");
+ }
+ // Restore the setting to the current value.
+ this.setClockAutoUpdateAvailable(isClockAutoUpdateAvailable);
+ }
+ }
+
+ // Don't allow any content processes to modify the setting
+ // "time.timezone.automatic-update.available" except for the chrome
+ // process.
+ if (aName === kSettingsTimezoneAutoUpdateAvailable &&
+ !aIsInternalSetting) {
+ let isTimezoneAutoUpdateAvailable = this._lastNitzMessage !== null;
+ if (aResult !== isTimezoneAutoUpdateAvailable) {
+ if (DEBUG) {
+ this.debug("Content processes cannot modify 'time.timezone.automatic-update.available'. Restore!");
+ }
+ // Restore the setting to the current value.
+ this.setTimezoneAutoUpdateAvailable(isTimezoneAutoUpdateAvailable);
+ }
+ }
+
+ this.handle(aName, aResult);
+ },
+
+ // nsISettingsServiceCallback
+ handle: function(aName, aResult) {
+ switch(aName) {
+ case kSettingsClockAutoUpdateEnabled:
+ this._clockAutoUpdateEnabled = aResult;
+ if (!this._clockAutoUpdateEnabled) {
+ break;
+ }
+
+ // Set the latest cached NITZ time if it's available.
+ if (this._lastNitzMessage) {
+ this.setClockByNitz(this._lastNitzMessage);
+ } else if (gNetworkManager.activeNetworkInfo &&
+ gNetworkManager.activeNetworkInfo.state ==
+ Ci.nsINetworkInfo.NETWORK_STATE_CONNECTED) {
+ // Set the latest cached SNTP time if it's available.
+ if (!this._sntp.isExpired()) {
+ this.setClockBySntp(this._sntp.getOffset());
+ } else {
+ // Or refresh the SNTP.
+ this._sntp.request();
+ }
+ } else {
+ // Set a sane minimum time.
+ let buildTime = libcutils.property_get("ro.build.date.utc", "0") * 1000;
+ let file = FileUtils.File("/system/b2g/b2g");
+ if (file.lastModifiedTime > buildTime) {
+ buildTime = file.lastModifiedTime;
+ }
+ if (buildTime > Date.now()) {
+ gTimeService.set(buildTime);
+ }
+ }
+ break;
+ case kSettingsTimezoneAutoUpdateEnabled:
+ this._timezoneAutoUpdateEnabled = aResult;
+
+ if (this._timezoneAutoUpdateEnabled) {
+ // Apply the latest cached NITZ for timezone if it's available.
+ if (this._timezoneAutoUpdateEnabled && this._lastNitzMessage) {
+ this.setTimezoneByNitz(this._lastNitzMessage);
+ }
+ }
+ break;
+ }
+ },
+
+ handleError: function(aErrorMessage) {
+ if (DEBUG) {
+ this.debug("There was an error while reading RIL settings.");
+ }
+ },
+
+ // nsIRadioInterface
+
+ // TODO: Bug 928861 - B2G NetworkManager: Provide a more generic function
+ // for connecting
+ setupDataCallByType: function(networkType) {
+ let connHandler = gDataCallManager.getDataCallHandler(this.clientId);
+ connHandler.setupDataCallByType(networkType);
+ },
+
+ // TODO: Bug 928861 - B2G NetworkManager: Provide a more generic function
+ // for connecting
+ deactivateDataCallByType: function(networkType) {
+ let connHandler = gDataCallManager.getDataCallHandler(this.clientId);
+ connHandler.deactivateDataCallByType(networkType);
+ },
+
+ // TODO: Bug 904514 - [meta] NetworkManager enhancement
+ getDataCallStateByType: function(networkType) {
+ let connHandler = gDataCallManager.getDataCallHandler(this.clientId);
+ return connHandler.getDataCallStateByType(networkType);
+ },
+
+ sendWorkerMessage: function(rilMessageType, message, callback) {
+ // Special handler for setRadioEnabled.
+ if (rilMessageType === "setRadioEnabled") {
+ // Forward it to gRadioEnabledController.
+ gRadioEnabledController.setRadioEnabled(this.clientId, message,
+ callback.handleResponse);
+ return;
+ }
+
+ if (callback) {
+ this.workerMessenger.send(rilMessageType, message, function(response) {
+ return callback.handleResponse(response);
+ });
+ } else {
+ this.workerMessenger.send(rilMessageType, message);
+ }
+ },
+};
+
+this.NSGetFactory = XPCOMUtils.generateNSGetFactory([RadioInterfaceLayer]);
diff --git a/dom/system/gonk/RadioInterfaceLayer.manifest b/dom/system/gonk/RadioInterfaceLayer.manifest
new file mode 100644
index 000000000..3c7c3b808
--- /dev/null
+++ b/dom/system/gonk/RadioInterfaceLayer.manifest
@@ -0,0 +1,18 @@
+# Copyright 2012 Mozilla Foundation and Mozilla contributors
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# RadioInterfaceLayer.js
+component {2d831c8d-6017-435b-a80c-e5d422810cea} RadioInterfaceLayer.js
+contract @mozilla.org/ril;1 {2d831c8d-6017-435b-a80c-e5d422810cea}
+category profile-after-change RadioInterfaceLayer @mozilla.org/ril;1
diff --git a/dom/system/gonk/SystemProperty.cpp b/dom/system/gonk/SystemProperty.cpp
new file mode 100644
index 000000000..1f874ce90
--- /dev/null
+++ b/dom/system/gonk/SystemProperty.cpp
@@ -0,0 +1,98 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "SystemProperty.h"
+
+#include <dlfcn.h>
+#include <string.h>
+
+#include "nsDebug.h"
+#include "prinit.h"
+
+namespace mozilla {
+namespace system {
+
+namespace {
+
+typedef int (*PropertyGet)(const char*, char*, const char*);
+typedef int (*PropertySet)(const char*, const char*);
+
+static void *sLibcUtils;
+static PRCallOnceType sInitLibcUtils;
+
+static int
+FakePropertyGet(const char* key, char* value, const char* default_value)
+{
+ if(!default_value) {
+ value[0] = '\0';
+ return 0;
+ }
+
+ int len = strlen(default_value);
+ if (len >= Property::VALUE_MAX_LENGTH) {
+ len = Property::VALUE_MAX_LENGTH - 1;
+ }
+ memcpy(value, default_value, len);
+ value[len] = '\0';
+
+ return len;
+}
+
+static int
+FakePropertySet(const char* key, const char* value)
+{
+ return 0;
+}
+
+static PRStatus
+InitLibcUtils()
+{
+ sLibcUtils = dlopen("/system/lib/libcutils.so", RTLD_LAZY);
+ // We will fallback to the fake getter/setter when sLibcUtils is not valid.
+ return PR_SUCCESS;
+}
+
+static void*
+GetLibcUtils()
+{
+ PR_CallOnce(&sInitLibcUtils, InitLibcUtils);
+ return sLibcUtils;
+}
+
+} // anonymous namespace
+
+/*static*/ int
+Property::Get(const char* key, char* value, const char* default_value)
+{
+ void *libcutils = GetLibcUtils();
+ if (libcutils) {
+ PropertyGet getter = (PropertyGet) dlsym(libcutils, "property_get");
+ if (getter) {
+ return getter(key, value, default_value);
+ }
+ NS_WARNING("Failed to get property_get() from libcutils!");
+ }
+ NS_WARNING("Fallback to the FakePropertyGet()");
+ return FakePropertyGet(key, value, default_value);
+}
+
+/*static*/ int
+Property::Set(const char* key, const char* value)
+{
+ void *libcutils = GetLibcUtils();
+ if (libcutils) {
+ PropertySet setter = (PropertySet) dlsym(libcutils, "property_set");
+ if (setter) {
+ return setter(key, value);
+ }
+ NS_WARNING("Failed to get property_set() from libcutils!");
+ }
+ NS_WARNING("Fallback to the FakePropertySet()");
+ return FakePropertySet(key, value);
+}
+
+} // namespace system
+} // namespace mozilla
diff --git a/dom/system/gonk/SystemProperty.h b/dom/system/gonk/SystemProperty.h
new file mode 100644
index 000000000..2b5ceae8b
--- /dev/null
+++ b/dom/system/gonk/SystemProperty.h
@@ -0,0 +1,39 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_system_Property_h
+#define mozilla_system_Property_h
+
+namespace mozilla {
+namespace system {
+
+/**
+* Abstraction of property_get/property_get in libcutils from AOSP.
+*/
+class Property
+{
+public:
+ // Constants defined in system_properties.h from AOSP.
+ enum {
+ KEY_MAX_LENGTH = 32,
+ VALUE_MAX_LENGTH = 92
+ };
+
+ static int
+ Get(const char* key, char* value, const char* default_value);
+
+ static int
+ Set(const char* key, const char* value);
+
+private:
+ Property() {}
+ virtual ~Property() {}
+};
+
+} // namespace system
+} // namespace mozilla
+
+ #endif // mozilla_system_Property_h
diff --git a/dom/system/gonk/SystemWorkerManager.cpp b/dom/system/gonk/SystemWorkerManager.cpp
new file mode 100644
index 000000000..ee3fc8de3
--- /dev/null
+++ b/dom/system/gonk/SystemWorkerManager.cpp
@@ -0,0 +1,214 @@
+/* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* Copyright 2012 Mozilla Foundation and Mozilla contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "SystemWorkerManager.h"
+
+#include "nsINetworkService.h"
+#include "nsIWifi.h"
+#include "nsIWorkerHolder.h"
+#include "nsIXPConnect.h"
+
+#include "jsfriendapi.h"
+#include "mozilla/dom/workers/Workers.h"
+#include "AutoMounter.h"
+#include "TimeZoneSettingObserver.h"
+#include "AudioManager.h"
+#include "mozilla/dom/ScriptSettings.h"
+#include "mozilla/ipc/KeyStore.h"
+#include "nsIObserverService.h"
+#include "nsServiceManagerUtils.h"
+#include "nsThreadUtils.h"
+#include "WifiWorker.h"
+#include "mozilla/Services.h"
+
+USING_WORKERS_NAMESPACE
+
+using namespace mozilla::dom::gonk;
+using namespace mozilla::ipc;
+using namespace mozilla::system;
+
+namespace {
+
+NS_DEFINE_CID(kWifiWorkerCID, NS_WIFIWORKER_CID);
+
+// Doesn't carry a reference, we're owned by services.
+SystemWorkerManager *gInstance = nullptr;
+
+} // namespace
+
+SystemWorkerManager::SystemWorkerManager()
+ : mShutdown(false)
+{
+ NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
+ NS_ASSERTION(!gInstance, "There should only be one instance!");
+}
+
+SystemWorkerManager::~SystemWorkerManager()
+{
+ NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
+ NS_ASSERTION(!gInstance || gInstance == this,
+ "There should only be one instance!");
+ gInstance = nullptr;
+}
+
+nsresult
+SystemWorkerManager::Init()
+{
+ if (!XRE_IsParentProcess()) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ NS_ASSERTION(NS_IsMainThread(), "We can only initialize on the main thread");
+ NS_ASSERTION(!mShutdown, "Already shutdown!");
+
+ nsresult rv = InitWifi();
+ if (NS_FAILED(rv)) {
+ NS_WARNING("Failed to initialize WiFi Networking!");
+ return rv;
+ }
+
+ InitKeyStore();
+
+ InitAutoMounter();
+ InitializeTimeZoneSettingObserver();
+ nsCOMPtr<nsIAudioManager> audioManager =
+ do_GetService(NS_AUDIOMANAGER_CONTRACTID);
+
+ nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
+ if (!obs) {
+ NS_WARNING("Failed to get observer service!");
+ return NS_ERROR_FAILURE;
+ }
+
+ rv = obs->AddObserver(this, WORKERS_SHUTDOWN_TOPIC, false);
+ if (NS_FAILED(rv)) {
+ NS_WARNING("Failed to initialize worker shutdown event!");
+ return rv;
+ }
+
+ return NS_OK;
+}
+
+void
+SystemWorkerManager::Shutdown()
+{
+ NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
+
+ mShutdown = true;
+
+ ShutdownAutoMounter();
+
+ nsCOMPtr<nsIWifi> wifi(do_QueryInterface(mWifiWorker));
+ if (wifi) {
+ wifi->Shutdown();
+ wifi = nullptr;
+ }
+ mWifiWorker = nullptr;
+
+ if (mKeyStore) {
+ mKeyStore->Shutdown();
+ mKeyStore = nullptr;
+ }
+
+ nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
+ if (obs) {
+ obs->RemoveObserver(this, WORKERS_SHUTDOWN_TOPIC);
+ }
+}
+
+// static
+already_AddRefed<SystemWorkerManager>
+SystemWorkerManager::FactoryCreate()
+{
+ NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
+
+ RefPtr<SystemWorkerManager> instance(gInstance);
+
+ if (!instance) {
+ instance = new SystemWorkerManager();
+ if (NS_FAILED(instance->Init())) {
+ instance->Shutdown();
+ return nullptr;
+ }
+
+ gInstance = instance;
+ }
+
+ return instance.forget();
+}
+
+// static
+nsIInterfaceRequestor*
+SystemWorkerManager::GetInterfaceRequestor()
+{
+ return gInstance;
+}
+
+NS_IMETHODIMP
+SystemWorkerManager::GetInterface(const nsIID &aIID, void **aResult)
+{
+ NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
+
+ if (aIID.Equals(NS_GET_IID(nsIWifi))) {
+ return CallQueryInterface(mWifiWorker,
+ reinterpret_cast<nsIWifi**>(aResult));
+ }
+
+ NS_WARNING("Got nothing for the requested IID!");
+ return NS_ERROR_NO_INTERFACE;
+}
+
+nsresult
+SystemWorkerManager::RegisterRilWorker(unsigned int aClientId,
+ JS::Handle<JS::Value> aWorker,
+ JSContext *aCx)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+nsresult
+SystemWorkerManager::InitWifi()
+{
+ nsCOMPtr<nsIWorkerHolder> worker = do_CreateInstance(kWifiWorkerCID);
+ NS_ENSURE_TRUE(worker, NS_ERROR_FAILURE);
+
+ mWifiWorker = worker;
+ return NS_OK;
+}
+
+nsresult
+SystemWorkerManager::InitKeyStore()
+{
+ mKeyStore = new KeyStore();
+ return NS_OK;
+}
+
+NS_IMPL_ISUPPORTS(SystemWorkerManager,
+ nsIObserver,
+ nsIInterfaceRequestor,
+ nsISystemWorkerManager)
+
+NS_IMETHODIMP
+SystemWorkerManager::Observe(nsISupports *aSubject, const char *aTopic,
+ const char16_t *aData)
+{
+ if (!strcmp(aTopic, WORKERS_SHUTDOWN_TOPIC)) {
+ Shutdown();
+ }
+
+ return NS_OK;
+}
diff --git a/dom/system/gonk/SystemWorkerManager.h b/dom/system/gonk/SystemWorkerManager.h
new file mode 100644
index 000000000..625cda261
--- /dev/null
+++ b/dom/system/gonk/SystemWorkerManager.h
@@ -0,0 +1,75 @@
+/* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */
+/* vim: set ts=2 et sw=2 tw=40: */
+/* Copyright 2012 Mozilla Foundation and Mozilla contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef mozilla_dom_system_b2g_systemworkermanager_h__
+#define mozilla_dom_system_b2g_systemworkermanager_h__
+
+#include "nsIInterfaceRequestor.h"
+#include "nsISystemWorkerManager.h"
+#include "nsIObserver.h"
+#include "nsCOMPtr.h"
+#include "nsXULAppAPI.h" // For XRE_GetProcessType
+
+class nsIWorkerHolder;
+
+namespace mozilla {
+
+namespace ipc {
+ class KeyStore;
+}
+
+namespace dom {
+namespace gonk {
+
+class SystemWorkerManager final : public nsIObserver,
+ public nsIInterfaceRequestor,
+ public nsISystemWorkerManager
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIOBSERVER
+ NS_DECL_NSIINTERFACEREQUESTOR
+ NS_DECL_NSISYSTEMWORKERMANAGER
+
+ nsresult Init();
+ void Shutdown();
+
+ static already_AddRefed<SystemWorkerManager>
+ FactoryCreate();
+
+ static nsIInterfaceRequestor*
+ GetInterfaceRequestor();
+
+private:
+ SystemWorkerManager();
+ ~SystemWorkerManager();
+
+ nsresult InitWifi();
+ nsresult InitKeyStore();
+
+ nsCOMPtr<nsIWorkerHolder> mWifiWorker;
+
+ RefPtr<mozilla::ipc::KeyStore> mKeyStore;
+
+ bool mShutdown;
+};
+
+}
+}
+}
+
+#endif // mozilla_dom_system_b2g_systemworkermanager_h__
diff --git a/dom/system/gonk/TetheringService.js b/dom/system/gonk/TetheringService.js
new file mode 100644
index 000000000..c5c478180
--- /dev/null
+++ b/dom/system/gonk/TetheringService.js
@@ -0,0 +1,891 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/FileUtils.jsm");
+Cu.import("resource://gre/modules/systemlibs.js");
+
+const TETHERINGSERVICE_CONTRACTID = "@mozilla.org/tethering/service;1";
+const TETHERINGSERVICE_CID =
+ Components.ID("{527a4121-ee5a-4651-be9c-f46f59cf7c01}");
+
+XPCOMUtils.defineLazyServiceGetter(this, "gNetworkManager",
+ "@mozilla.org/network/manager;1",
+ "nsINetworkManager");
+
+XPCOMUtils.defineLazyServiceGetter(this, "gNetworkService",
+ "@mozilla.org/network/service;1",
+ "nsINetworkService");
+
+XPCOMUtils.defineLazyServiceGetter(this, "gSettingsService",
+ "@mozilla.org/settingsService;1",
+ "nsISettingsService");
+
+XPCOMUtils.defineLazyServiceGetter(this, "gMobileConnectionService",
+ "@mozilla.org/mobileconnection/mobileconnectionservice;1",
+ "nsIMobileConnectionService");
+
+XPCOMUtils.defineLazyGetter(this, "gRil", function() {
+ try {
+ return Cc["@mozilla.org/ril;1"].getService(Ci.nsIRadioInterfaceLayer);
+ } catch (e) {}
+
+ return null;
+});
+
+const TOPIC_MOZSETTINGS_CHANGED = "mozsettings-changed";
+const TOPIC_CONNECTION_STATE_CHANGED = "network-connection-state-changed";
+const TOPIC_PREF_CHANGED = "nsPref:changed";
+const TOPIC_XPCOM_SHUTDOWN = "xpcom-shutdown";
+const PREF_MANAGE_OFFLINE_STATUS = "network.gonk.manage-offline-status";
+const PREF_NETWORK_DEBUG_ENABLED = "network.debugging.enabled";
+
+const POSSIBLE_USB_INTERFACE_NAME = "rndis0,usb0";
+const DEFAULT_USB_INTERFACE_NAME = "rndis0";
+const DEFAULT_3G_INTERFACE_NAME = "rmnet0";
+const DEFAULT_WIFI_INTERFACE_NAME = "wlan0";
+
+// The kernel's proc entry for network lists.
+const KERNEL_NETWORK_ENTRY = "/sys/class/net";
+
+const TETHERING_TYPE_WIFI = "WiFi";
+const TETHERING_TYPE_USB = "USB";
+
+const WIFI_FIRMWARE_AP = "AP";
+const WIFI_FIRMWARE_STATION = "STA";
+const WIFI_SECURITY_TYPE_NONE = "open";
+const WIFI_SECURITY_TYPE_WPA_PSK = "wpa-psk";
+const WIFI_SECURITY_TYPE_WPA2_PSK = "wpa2-psk";
+const WIFI_CTRL_INTERFACE = "wl0.1";
+
+const NETWORK_INTERFACE_UP = "up";
+const NETWORK_INTERFACE_DOWN = "down";
+
+const TETHERING_STATE_ONGOING = "ongoing";
+const TETHERING_STATE_IDLE = "idle";
+const TETHERING_STATE_ACTIVE = "active";
+
+// Settings DB path for USB tethering.
+const SETTINGS_USB_ENABLED = "tethering.usb.enabled";
+const SETTINGS_USB_IP = "tethering.usb.ip";
+const SETTINGS_USB_PREFIX = "tethering.usb.prefix";
+const SETTINGS_USB_DHCPSERVER_STARTIP = "tethering.usb.dhcpserver.startip";
+const SETTINGS_USB_DHCPSERVER_ENDIP = "tethering.usb.dhcpserver.endip";
+const SETTINGS_USB_DNS1 = "tethering.usb.dns1";
+const SETTINGS_USB_DNS2 = "tethering.usb.dns2";
+
+// Settings DB path for WIFI tethering.
+const SETTINGS_WIFI_DHCPSERVER_STARTIP = "tethering.wifi.dhcpserver.startip";
+const SETTINGS_WIFI_DHCPSERVER_ENDIP = "tethering.wifi.dhcpserver.endip";
+
+// Settings DB patch for dun required setting.
+const SETTINGS_DUN_REQUIRED = "tethering.dun.required";
+
+// Default value for USB tethering.
+const DEFAULT_USB_IP = "192.168.0.1";
+const DEFAULT_USB_PREFIX = "24";
+const DEFAULT_USB_DHCPSERVER_STARTIP = "192.168.0.10";
+const DEFAULT_USB_DHCPSERVER_ENDIP = "192.168.0.30";
+
+const DEFAULT_DNS1 = "8.8.8.8";
+const DEFAULT_DNS2 = "8.8.4.4";
+
+const DEFAULT_WIFI_DHCPSERVER_STARTIP = "192.168.1.10";
+const DEFAULT_WIFI_DHCPSERVER_ENDIP = "192.168.1.30";
+
+const SETTINGS_DATA_DEFAULT_SERVICE_ID = "ril.data.defaultServiceId";
+const MOBILE_DUN_CONNECT_TIMEOUT = 30000;
+const MOBILE_DUN_RETRY_INTERVAL = 5000;
+const MOBILE_DUN_MAX_RETRIES = 5;
+
+var debug;
+function updateDebug() {
+ let debugPref = false; // set default value here.
+ try {
+ debugPref = debugPref || Services.prefs.getBoolPref(PREF_NETWORK_DEBUG_ENABLED);
+ } catch (e) {}
+
+ if (debugPref) {
+ debug = function(s) {
+ dump("-*- TetheringService: " + s + "\n");
+ };
+ } else {
+ debug = function(s) {};
+ }
+}
+updateDebug();
+
+function TetheringService() {
+ Services.obs.addObserver(this, TOPIC_XPCOM_SHUTDOWN, false);
+ Services.obs.addObserver(this, TOPIC_MOZSETTINGS_CHANGED, false);
+ Services.obs.addObserver(this, TOPIC_CONNECTION_STATE_CHANGED, false);
+ Services.prefs.addObserver(PREF_NETWORK_DEBUG_ENABLED, this, false);
+ Services.prefs.addObserver(PREF_MANAGE_OFFLINE_STATUS, this, false);
+
+ try {
+ this._manageOfflineStatus =
+ Services.prefs.getBoolPref(PREF_MANAGE_OFFLINE_STATUS);
+ } catch(ex) {
+ // Ignore.
+ }
+
+ this._dataDefaultServiceId = 0;
+
+ // Possible usb tethering interfaces for different gonk platform.
+ this.possibleInterface = POSSIBLE_USB_INTERFACE_NAME.split(",");
+
+ // Default values for internal and external interfaces.
+ this._tetheringInterface = {};
+ this._tetheringInterface[TETHERING_TYPE_USB] = {
+ externalInterface: DEFAULT_3G_INTERFACE_NAME,
+ internalInterface: DEFAULT_USB_INTERFACE_NAME
+ };
+ this._tetheringInterface[TETHERING_TYPE_WIFI] = {
+ externalInterface: DEFAULT_3G_INTERFACE_NAME,
+ internalInterface: DEFAULT_WIFI_INTERFACE_NAME
+ };
+
+ this.tetheringSettings = {};
+ this.initTetheringSettings();
+
+ let settingsLock = gSettingsService.createLock();
+ // Read the default service id for data call.
+ settingsLock.get(SETTINGS_DATA_DEFAULT_SERVICE_ID, this);
+
+ // Read usb tethering data from settings DB.
+ settingsLock.get(SETTINGS_USB_IP, this);
+ settingsLock.get(SETTINGS_USB_PREFIX, this);
+ settingsLock.get(SETTINGS_USB_DHCPSERVER_STARTIP, this);
+ settingsLock.get(SETTINGS_USB_DHCPSERVER_ENDIP, this);
+ settingsLock.get(SETTINGS_USB_DNS1, this);
+ settingsLock.get(SETTINGS_USB_DNS2, this);
+ settingsLock.get(SETTINGS_USB_ENABLED, this);
+
+ // Read wifi tethering data from settings DB.
+ settingsLock.get(SETTINGS_WIFI_DHCPSERVER_STARTIP, this);
+ settingsLock.get(SETTINGS_WIFI_DHCPSERVER_ENDIP, this);
+
+ this._usbTetheringSettingsToRead = [SETTINGS_USB_IP,
+ SETTINGS_USB_PREFIX,
+ SETTINGS_USB_DHCPSERVER_STARTIP,
+ SETTINGS_USB_DHCPSERVER_ENDIP,
+ SETTINGS_USB_DNS1,
+ SETTINGS_USB_DNS2,
+ SETTINGS_USB_ENABLED,
+ SETTINGS_WIFI_DHCPSERVER_STARTIP,
+ SETTINGS_WIFI_DHCPSERVER_ENDIP];
+
+ this.wantConnectionEvent = null;
+
+ this.dunConnectTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
+
+ this.dunRetryTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
+
+ this._pendingTetheringRequests = [];
+}
+TetheringService.prototype = {
+ classID: TETHERINGSERVICE_CID,
+ classInfo: XPCOMUtils.generateCI({classID: TETHERINGSERVICE_CID,
+ contractID: TETHERINGSERVICE_CONTRACTID,
+ classDescription: "Tethering Service",
+ interfaces: [Ci.nsITetheringService]}),
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsITetheringService,
+ Ci.nsISupportsWeakReference,
+ Ci.nsIObserver,
+ Ci.nsISettingsServiceCallback]),
+
+ // Flag to record the default client id for data call.
+ _dataDefaultServiceId: null,
+
+ // Number of usb tehering requests to be processed.
+ _usbTetheringRequestCount: 0,
+
+ // Usb tethering state.
+ _usbTetheringAction: TETHERING_STATE_IDLE,
+
+ // Tethering settings.
+ tetheringSettings: null,
+
+ // Tethering settings need to be read from settings DB.
+ _usbTetheringSettingsToRead: null,
+
+ // Previous usb tethering enabled state.
+ _oldUsbTetheringEnabledState: null,
+
+ // External and internal interface name.
+ _tetheringInterface: null,
+
+ // Dun connection timer.
+ dunConnectTimer: null,
+
+ // Dun connection retry times.
+ dunRetryTimes: 0,
+
+ // Dun retry timer.
+ dunRetryTimer: null,
+
+ // Pending tethering request to handle after dun is connected.
+ _pendingTetheringRequests: null,
+
+ // Flag to indicate wether wifi tethering is being processed.
+ _wifiTetheringRequestOngoing: false,
+
+ // Arguments for pending wifi tethering request.
+ _pendingWifiTetheringRequestArgs: null,
+
+ // The state of tethering.
+ state: Ci.nsITetheringService.TETHERING_STATE_INACTIVE,
+
+ // Flag to check if we can modify the Services.io.offline.
+ _manageOfflineStatus: true,
+
+ // nsIObserver
+
+ observe: function(aSubject, aTopic, aData) {
+ let network;
+
+ switch(aTopic) {
+ case TOPIC_PREF_CHANGED:
+ if (aData === PREF_NETWORK_DEBUG_ENABLED) {
+ updateDebug();
+ }
+ break;
+ case TOPIC_MOZSETTINGS_CHANGED:
+ if ("wrappedJSObject" in aSubject) {
+ aSubject = aSubject.wrappedJSObject;
+ }
+ this.handle(aSubject.key, aSubject.value);
+ break;
+ case TOPIC_CONNECTION_STATE_CHANGED:
+ network = aSubject.QueryInterface(Ci.nsINetworkInfo);
+ debug("Network " + network.type + "/" + network.name +
+ " changed state to " + network.state);
+ this.onConnectionChanged(network);
+ break;
+ case TOPIC_XPCOM_SHUTDOWN:
+ Services.obs.removeObserver(this, TOPIC_XPCOM_SHUTDOWN);
+ Services.obs.removeObserver(this, TOPIC_MOZSETTINGS_CHANGED);
+ Services.obs.removeObserver(this, TOPIC_CONNECTION_STATE_CHANGED);
+ Services.prefs.removeObserver(PREF_NETWORK_DEBUG_ENABLED, this);
+ Services.prefs.removeObserver(PREF_MANAGE_OFFLINE_STATUS, this);
+
+ this.dunConnectTimer.cancel();
+ this.dunRetryTimer.cancel();
+ break;
+ case PREF_MANAGE_OFFLINE_STATUS:
+ try {
+ this._manageOfflineStatus =
+ Services.prefs.getBoolPref(PREF_MANAGE_OFFLINE_STATUS);
+ } catch(ex) {
+ // Ignore.
+ }
+ break;
+ }
+ },
+
+ // nsISettingsServiceCallback
+
+ handle: function(aName, aResult) {
+ switch(aName) {
+ case SETTINGS_DATA_DEFAULT_SERVICE_ID:
+ this._dataDefaultServiceId = aResult || 0;
+ debug("'_dataDefaultServiceId' is now " + this._dataDefaultServiceId);
+ break;
+ case SETTINGS_USB_ENABLED:
+ this._oldUsbTetheringEnabledState = this.tetheringSettings[SETTINGS_USB_ENABLED];
+ case SETTINGS_USB_IP:
+ case SETTINGS_USB_PREFIX:
+ case SETTINGS_USB_DHCPSERVER_STARTIP:
+ case SETTINGS_USB_DHCPSERVER_ENDIP:
+ case SETTINGS_USB_DNS1:
+ case SETTINGS_USB_DNS2:
+ case SETTINGS_WIFI_DHCPSERVER_STARTIP:
+ case SETTINGS_WIFI_DHCPSERVER_ENDIP:
+ if (aResult !== null) {
+ this.tetheringSettings[aName] = aResult;
+ }
+ debug("'" + aName + "'" + " is now " + this.tetheringSettings[aName]);
+ let index = this._usbTetheringSettingsToRead.indexOf(aName);
+
+ if (index != -1) {
+ this._usbTetheringSettingsToRead.splice(index, 1);
+ }
+
+ if (this._usbTetheringSettingsToRead.length) {
+ debug("We haven't read completely the usb Tethering data from settings db.");
+ break;
+ }
+
+ if (this._oldUsbTetheringEnabledState === this.tetheringSettings[SETTINGS_USB_ENABLED]) {
+ debug("No changes for SETTINGS_USB_ENABLED flag. Nothing to do.");
+ this.handlePendingWifiTetheringRequest();
+ break;
+ }
+
+ this._usbTetheringRequestCount++;
+ if (this._usbTetheringRequestCount === 1) {
+ if (this._wifiTetheringRequestOngoing) {
+ debug('USB tethering request is blocked by ongoing wifi tethering request.');
+ } else {
+ this.handleLastUsbTetheringRequest();
+ }
+ }
+ break;
+ };
+ },
+
+ handleError: function(aErrorMessage) {
+ debug("There was an error while reading Tethering settings.");
+ this.tetheringSettings = {};
+ this.tetheringSettings[SETTINGS_USB_ENABLED] = false;
+ },
+
+ initTetheringSettings: function() {
+ this.tetheringSettings[SETTINGS_USB_ENABLED] = false;
+ this.tetheringSettings[SETTINGS_USB_IP] = DEFAULT_USB_IP;
+ this.tetheringSettings[SETTINGS_USB_PREFIX] = DEFAULT_USB_PREFIX;
+ this.tetheringSettings[SETTINGS_USB_DHCPSERVER_STARTIP] = DEFAULT_USB_DHCPSERVER_STARTIP;
+ this.tetheringSettings[SETTINGS_USB_DHCPSERVER_ENDIP] = DEFAULT_USB_DHCPSERVER_ENDIP;
+ this.tetheringSettings[SETTINGS_USB_DNS1] = DEFAULT_DNS1;
+ this.tetheringSettings[SETTINGS_USB_DNS2] = DEFAULT_DNS2;
+
+ this.tetheringSettings[SETTINGS_WIFI_DHCPSERVER_STARTIP] = DEFAULT_WIFI_DHCPSERVER_STARTIP;
+ this.tetheringSettings[SETTINGS_WIFI_DHCPSERVER_ENDIP] = DEFAULT_WIFI_DHCPSERVER_ENDIP;
+
+ this.tetheringSettings[SETTINGS_DUN_REQUIRED] =
+ libcutils.property_get("ro.tethering.dun_required") === "1";
+ },
+
+ getNetworkInfo: function(aType, aServiceId) {
+ for (let networkId in gNetworkManager.allNetworkInfo) {
+ let networkInfo = gNetworkManager.allNetworkInfo[networkId];
+ if (networkInfo.type == aType) {
+ try {
+ if (networkInfo instanceof Ci.nsIRilNetworkInfo) {
+ let rilNetwork = networkInfo.QueryInterface(Ci.nsIRilNetworkInfo);
+ if (rilNetwork.serviceId != aServiceId) {
+ continue;
+ }
+ }
+ } catch (e) {}
+ return networkInfo;
+ }
+ }
+ return null;
+ },
+
+ handleLastUsbTetheringRequest: function() {
+ debug('handleLastUsbTetheringRequest... ' + this._usbTetheringRequestCount);
+
+ if (this._usbTetheringRequestCount === 0) {
+ if (this.wantConnectionEvent) {
+ if (this.tetheringSettings[SETTINGS_USB_ENABLED]) {
+ this.wantConnectionEvent.call(this);
+ }
+ this.wantConnectionEvent = null;
+ }
+ this.handlePendingWifiTetheringRequest();
+ return;
+ }
+
+ // Cancel the accumlated count to 1 since we only care about the
+ // last state.
+ this._usbTetheringRequestCount = 1;
+ this.handleUSBTetheringToggle(this.tetheringSettings[SETTINGS_USB_ENABLED]);
+ this.wantConnectionEvent = null;
+ },
+
+ handlePendingWifiTetheringRequest: function() {
+ if (this._pendingWifiTetheringRequestArgs) {
+ this.setWifiTethering.apply(this, this._pendingWifiTetheringRequestArgs);
+ this._pendingWifiTetheringRequestArgs = null;
+ }
+ },
+
+ /**
+ * Callback when dun connection fails to connect within timeout.
+ */
+ onDunConnectTimerTimeout: function() {
+ while (this._pendingTetheringRequests.length > 0) {
+ debug("onDunConnectTimerTimeout: callback without network info.");
+ let callback = this._pendingTetheringRequests.shift();
+ if (typeof callback === 'function') {
+ callback();
+ }
+ }
+ },
+
+ setupDunConnection: function() {
+ this.dunRetryTimer.cancel();
+ let connection =
+ gMobileConnectionService.getItemByServiceId(this._dataDefaultServiceId);
+ let data = connection && connection.data;
+ if (data && data.state === "registered") {
+ let ril = gRil.getRadioInterface(this._dataDefaultServiceId);
+
+ this.dunRetryTimes = 0;
+ ril.setupDataCallByType(Ci.nsINetworkInfo.NETWORK_TYPE_MOBILE_DUN);
+ this.dunConnectTimer.cancel();
+ this.dunConnectTimer.
+ initWithCallback(this.onDunConnectTimerTimeout.bind(this),
+ MOBILE_DUN_CONNECT_TIMEOUT, Ci.nsITimer.TYPE_ONE_SHOT);
+ return;
+ }
+
+ if (this.dunRetryTimes++ >= this.MOBILE_DUN_MAX_RETRIES) {
+ debug("setupDunConnection: max retries reached.");
+ this.dunRetryTimes = 0;
+ // same as dun connect timeout.
+ this.onDunConnectTimerTimeout();
+ return;
+ }
+
+ debug("Data not ready, retry dun after " + MOBILE_DUN_RETRY_INTERVAL + " ms.");
+ this.dunRetryTimer.
+ initWithCallback(this.setupDunConnection.bind(this),
+ MOBILE_DUN_RETRY_INTERVAL, Ci.nsITimer.TYPE_ONE_SHOT);
+ },
+
+ _dunActiveUsers: 0,
+ handleDunConnection: function(aEnable, aCallback) {
+ debug("handleDunConnection: " + aEnable);
+ let dun = this.getNetworkInfo(
+ Ci.nsINetworkInfo.NETWORK_TYPE_MOBILE_DUN, this._dataDefaultServiceId);
+
+ if (!aEnable) {
+ this._dunActiveUsers--;
+ if (this._dunActiveUsers > 0) {
+ debug("Dun still needed by others, do not disconnect.")
+ return;
+ }
+
+ this.dunRetryTimes = 0;
+ this.dunRetryTimer.cancel();
+ this.dunConnectTimer.cancel();
+ this._pendingTetheringRequests = [];
+
+ if (dun && (dun.state == Ci.nsINetworkInfo.NETWORK_STATE_CONNECTED)) {
+ gRil.getRadioInterface(this._dataDefaultServiceId)
+ .deactivateDataCallByType(Ci.nsINetworkInfo.NETWORK_TYPE_MOBILE_DUN);
+ }
+ return;
+ }
+
+ this._dunActiveUsers++;
+ if (!dun || (dun.state != Ci.nsINetworkInfo.NETWORK_STATE_CONNECTED)) {
+ debug("DUN data call inactive, setup dun data call!")
+ this._pendingTetheringRequests.push(aCallback);
+ this.dunRetryTimes = 0;
+ this.setupDunConnection();
+
+ return;
+ }
+
+ this._tetheringInterface[TETHERING_TYPE_USB].externalInterface = dun.name;
+ aCallback(dun);
+ },
+
+ handleUSBTetheringToggle: function(aEnable) {
+ debug("handleUSBTetheringToggle: " + aEnable);
+ if (aEnable &&
+ (this._usbTetheringAction === TETHERING_STATE_ONGOING ||
+ this._usbTetheringAction === TETHERING_STATE_ACTIVE)) {
+ debug("Usb tethering already connecting/connected.");
+ this._usbTetheringRequestCount = 0;
+ this.handlePendingWifiTetheringRequest();
+ return;
+ }
+
+ if (!aEnable &&
+ this._usbTetheringAction === TETHERING_STATE_IDLE) {
+ debug("Usb tethering already disconnected.");
+ this._usbTetheringRequestCount = 0;
+ this.handlePendingWifiTetheringRequest();
+ return;
+ }
+
+ if (!aEnable) {
+ this.tetheringSettings[SETTINGS_USB_ENABLED] = false;
+ gNetworkService.enableUsbRndis(false, this.enableUsbRndisResult.bind(this));
+ return;
+ }
+
+ this.tetheringSettings[SETTINGS_USB_ENABLED] = true;
+ this._usbTetheringAction = TETHERING_STATE_ONGOING;
+
+ if (this.tetheringSettings[SETTINGS_DUN_REQUIRED]) {
+ this.handleDunConnection(true, (aNetworkInfo) => {
+ if (!aNetworkInfo){
+ this.usbTetheringResultReport(aEnable, "Dun connection failed");
+ return;
+ }
+ this._tetheringInterface[TETHERING_TYPE_USB].externalInterface =
+ aNetworkInfo.name;
+ gNetworkService.enableUsbRndis(true, this.enableUsbRndisResult.bind(this));
+ });
+ return;
+ }
+
+ if (gNetworkManager.activeNetworkInfo) {
+ this._tetheringInterface[TETHERING_TYPE_USB].externalInterface =
+ gNetworkManager.activeNetworkInfo.name;
+ } else {
+ let mobile = this.getNetworkInfo(
+ Ci.nsINetworkInfo.NETWORK_TYPE_MOBILE, this._dataDefaultServiceId);
+ if (mobile && mobile.name) {
+ this._tetheringInterface[TETHERING_TYPE_USB].externalInterface = mobile.name;
+ }
+ }
+ gNetworkService.enableUsbRndis(true, this.enableUsbRndisResult.bind(this));
+ },
+
+ getUSBTetheringParameters: function(aEnable, aTetheringInterface) {
+ let interfaceIp = this.tetheringSettings[SETTINGS_USB_IP];
+ let prefix = this.tetheringSettings[SETTINGS_USB_PREFIX];
+ let wifiDhcpStartIp = this.tetheringSettings[SETTINGS_WIFI_DHCPSERVER_STARTIP];
+ let wifiDhcpEndIp = this.tetheringSettings[SETTINGS_WIFI_DHCPSERVER_ENDIP];
+ let usbDhcpStartIp = this.tetheringSettings[SETTINGS_USB_DHCPSERVER_STARTIP];
+ let usbDhcpEndIp = this.tetheringSettings[SETTINGS_USB_DHCPSERVER_ENDIP];
+ let dns1 = this.tetheringSettings[SETTINGS_USB_DNS1];
+ let dns2 = this.tetheringSettings[SETTINGS_USB_DNS2];
+ let internalInterface = aTetheringInterface.internalInterface;
+ let externalInterface = aTetheringInterface.externalInterface;
+
+ // Using the default values here until application support these settings.
+ if (interfaceIp == "" || prefix == "" ||
+ wifiDhcpStartIp == "" || wifiDhcpEndIp == "" ||
+ usbDhcpStartIp == "" || usbDhcpEndIp == "") {
+ debug("Invalid subnet information.");
+ return null;
+ }
+
+ return {
+ ifname: internalInterface,
+ ip: interfaceIp,
+ prefix: prefix,
+ wifiStartIp: wifiDhcpStartIp,
+ wifiEndIp: wifiDhcpEndIp,
+ usbStartIp: usbDhcpStartIp,
+ usbEndIp: usbDhcpEndIp,
+ dns1: dns1,
+ dns2: dns2,
+ internalIfname: internalInterface,
+ externalIfname: externalInterface,
+ enable: aEnable,
+ link: aEnable ? NETWORK_INTERFACE_UP : NETWORK_INTERFACE_DOWN
+ };
+ },
+
+ notifyError: function(aResetSettings, aCallback, aMsg) {
+ if (aResetSettings) {
+ let settingsLock = gSettingsService.createLock();
+ // Disable wifi tethering with a useful error message for the user.
+ settingsLock.set("tethering.wifi.enabled", false, null, aMsg);
+ }
+
+ debug("setWifiTethering: " + (aMsg ? aMsg : "success"));
+
+ if (aCallback) {
+ // Callback asynchronously to avoid netsted toggling.
+ Services.tm.currentThread.dispatch(() => {
+ aCallback.wifiTetheringEnabledChange(aMsg);
+ }, Ci.nsIThread.DISPATCH_NORMAL);
+ }
+ },
+
+ enableWifiTethering: function(aEnable, aConfig, aCallback) {
+ // Fill in config's required fields.
+ aConfig.ifname = this._tetheringInterface[TETHERING_TYPE_WIFI].internalInterface;
+ aConfig.internalIfname = this._tetheringInterface[TETHERING_TYPE_WIFI].internalInterface;
+ aConfig.externalIfname = this._tetheringInterface[TETHERING_TYPE_WIFI].externalInterface;
+
+ this._wifiTetheringRequestOngoing = true;
+ gNetworkService.setWifiTethering(aEnable, aConfig, (aError) => {
+ // Change the tethering state to WIFI if there is no error.
+ if (aEnable && !aError) {
+ this.state = Ci.nsITetheringService.TETHERING_STATE_WIFI;
+ } else {
+ // If wifi thethering is disable, or any error happens,
+ // then consider the following statements.
+
+ // Check whether the state is USB now or not. If no then just change
+ // it to INACTIVE, if yes then just keep it.
+ // It means that don't let the disable or error of WIFI affect
+ // the original active state.
+ if (this.state != Ci.nsITetheringService.TETHERING_STATE_USB) {
+ this.state = Ci.nsITetheringService.TETHERING_STATE_INACTIVE;
+ }
+
+ // Disconnect dun on error or when wifi tethering is disabled.
+ if (this.tetheringSettings[SETTINGS_DUN_REQUIRED]) {
+ this.handleDunConnection(false);
+ }
+ }
+
+ if (this._manageOfflineStatus) {
+ Services.io.offline = !this.isAnyConnected() &&
+ (this.state ===
+ Ci.nsITetheringService.TETHERING_STATE_INACTIVE);
+ }
+
+ let resetSettings = aError;
+ debug('gNetworkService.setWifiTethering finished');
+ this.notifyError(resetSettings, aCallback, aError);
+ this._wifiTetheringRequestOngoing = false;
+ if (this._usbTetheringRequestCount > 0) {
+ debug('Perform pending USB tethering requests.');
+ this.handleLastUsbTetheringRequest();
+ }
+ });
+ },
+
+ // Enable/disable WiFi tethering by sending commands to netd.
+ setWifiTethering: function(aEnable, aInterfaceName, aConfig, aCallback) {
+ debug("setWifiTethering: " + aEnable);
+ if (!aInterfaceName) {
+ this.notifyError(true, aCallback, "invalid network interface name");
+ return;
+ }
+
+ if (!aConfig) {
+ this.notifyError(true, aCallback, "invalid configuration");
+ return;
+ }
+
+ if (this._usbTetheringRequestCount > 0) {
+ // If there's still pending usb tethering request, save
+ // the request params and redo |setWifiTethering| on
+ // usb tethering task complete.
+ debug('USB tethering request is being processed. Queue this wifi tethering request.');
+ this._pendingWifiTetheringRequestArgs = Array.prototype.slice.call(arguments);
+ debug('Pending args: ' + JSON.stringify(this._pendingWifiTetheringRequestArgs));
+ return;
+ }
+
+ // Re-check again, test cases set this property later.
+ this.tetheringSettings[SETTINGS_DUN_REQUIRED] =
+ libcutils.property_get("ro.tethering.dun_required") === "1";
+
+ if (!aEnable) {
+ this.enableWifiTethering(false, aConfig, aCallback);
+ return;
+ }
+
+ this._tetheringInterface[TETHERING_TYPE_WIFI].internalInterface =
+ aInterfaceName;
+
+ if (this.tetheringSettings[SETTINGS_DUN_REQUIRED]) {
+ this.handleDunConnection(true, (aNetworkInfo) => {
+ if (!aNetworkInfo) {
+ this.notifyError(true, aCallback, "Dun connection failed");
+ return;
+ }
+ this._tetheringInterface[TETHERING_TYPE_WIFI].externalInterface =
+ aNetworkInfo.name;
+ this.enableWifiTethering(true, aConfig, aCallback);
+ });
+ return;
+ }
+
+ let mobile = this.getNetworkInfo(
+ Ci.nsINetworkInfo.NETWORK_TYPE_MOBILE, this._dataDefaultServiceId);
+ // Update the real interface name
+ if (mobile && mobile.name) {
+ this._tetheringInterface[TETHERING_TYPE_WIFI].externalInterface = mobile.name;
+ }
+
+ this.enableWifiTethering(true, aConfig, aCallback);
+ },
+
+ // Enable/disable USB tethering by sending commands to netd.
+ setUSBTethering: function(aEnable, aTetheringInterface, aCallback) {
+ let params = this.getUSBTetheringParameters(aEnable, aTetheringInterface);
+
+ if (params === null) {
+ gNetworkService.enableUsbRndis(false, function() {
+ this.usbTetheringResultReport(aEnable, "Invalid parameters");
+ });
+ return;
+ }
+
+ gNetworkService.setUSBTethering(aEnable, params, aCallback);
+ },
+
+ getUsbInterface: function() {
+ // Find the rndis interface.
+ for (let i = 0; i < this.possibleInterface.length; i++) {
+ try {
+ let file = new FileUtils.File(KERNEL_NETWORK_ENTRY + "/" +
+ this.possibleInterface[i]);
+ if (file.exists()) {
+ return this.possibleInterface[i];
+ }
+ } catch (e) {
+ debug("Not " + this.possibleInterface[i] + " interface.");
+ }
+ }
+ debug("Can't find rndis interface in possible lists.");
+ return DEFAULT_USB_INTERFACE_NAME;
+ },
+
+ enableUsbRndisResult: function(aSuccess, aEnable) {
+ if (aSuccess) {
+ // If enable is false, don't find usb interface cause it is already down,
+ // just use the internal interface in settings.
+ if (aEnable) {
+ this._tetheringInterface[TETHERING_TYPE_USB].internalInterface =
+ this.getUsbInterface();
+ }
+ this.setUSBTethering(aEnable,
+ this._tetheringInterface[TETHERING_TYPE_USB],
+ this.usbTetheringResultReport.bind(this, aEnable));
+ } else {
+ this.usbTetheringResultReport(aEnable, "enableUsbRndisResult failure");
+ throw new Error("failed to set USB Function to adb");
+ }
+ },
+
+ usbTetheringResultReport: function(aEnable, aError) {
+ this._usbTetheringRequestCount--;
+
+ let settingsLock = gSettingsService.createLock();
+
+ debug('usbTetheringResultReport callback. enable: ' + aEnable +
+ ', error: ' + aError);
+
+ // Disable tethering settings when fail to enable it.
+ if (aError) {
+ this.tetheringSettings[SETTINGS_USB_ENABLED] = false;
+ settingsLock.set("tethering.usb.enabled", false, null);
+ // Skip others request when we found an error.
+ this._usbTetheringRequestCount = 0;
+ this._usbTetheringAction = TETHERING_STATE_IDLE;
+ // If the thethering state is WIFI now, then just keep it,
+ // if not, just change the state to INACTIVE.
+ // It means that don't let the error of USB affect the original active state.
+ if (this.state != Ci.nsITetheringService.TETHERING_STATE_WIFI) {
+ this.state = Ci.nsITetheringService.TETHERING_STATE_INACTIVE;
+ }
+ if (this.tetheringSettings[SETTINGS_DUN_REQUIRED]) {
+ this.handleDunConnection(false);
+ }
+ } else {
+ if (aEnable) {
+ this._usbTetheringAction = TETHERING_STATE_ACTIVE;
+ this.state = Ci.nsITetheringService.TETHERING_STATE_USB;
+ } else {
+ this._usbTetheringAction = TETHERING_STATE_IDLE;
+ // If the state is now WIFI, don't let the disable of USB affect it.
+ if (this.state != Ci.nsITetheringService.TETHERING_STATE_WIFI) {
+ this.state = Ci.nsITetheringService.TETHERING_STATE_INACTIVE;
+ }
+ if (this.tetheringSettings[SETTINGS_DUN_REQUIRED]) {
+ this.handleDunConnection(false);
+ }
+ }
+
+ if (this._manageOfflineStatus) {
+ Services.io.offline = !this.isAnyConnected() &&
+ (this.state ===
+ Ci.nsITetheringService.TETHERING_STATE_INACTIVE);
+ }
+
+ this.handleLastUsbTetheringRequest();
+ }
+ },
+
+ onConnectionChangedReport: function(aSuccess, aExternalIfname) {
+ debug("onConnectionChangedReport result: success " + aSuccess);
+
+ if (aSuccess) {
+ // Update the external interface.
+ this._tetheringInterface[TETHERING_TYPE_USB].externalInterface =
+ aExternalIfname;
+ debug("Change the interface name to " + aExternalIfname);
+ }
+ },
+
+ onConnectionChanged: function(aNetworkInfo) {
+ if (aNetworkInfo.state != Ci.nsINetworkInfo.NETWORK_STATE_CONNECTED) {
+ debug("We are only interested in CONNECTED event");
+ return;
+ }
+
+ if (this.tetheringSettings[SETTINGS_DUN_REQUIRED] &&
+ aNetworkInfo.type === Ci.nsINetworkInfo.NETWORK_TYPE_MOBILE_DUN) {
+ this.dunConnectTimer.cancel();
+ debug("DUN data call connected, process callbacks.");
+ while (this._pendingTetheringRequests.length > 0) {
+ let callback = this._pendingTetheringRequests.shift();
+ if (typeof callback === 'function') {
+ callback(aNetworkInfo);
+ }
+ }
+ return;
+ }
+
+ if (!this.tetheringSettings[SETTINGS_USB_ENABLED]) {
+ debug("Usb tethering settings is not enabled");
+ return;
+ }
+
+ if (this.tetheringSettings[SETTINGS_DUN_REQUIRED] &&
+ aNetworkInfo.type === Ci.nsINetworkInfo.NETWORK_TYPE_MOBILE_DUN &&
+ this._tetheringInterface[TETHERING_TYPE_USB].externalInterface ===
+ aNetworkInfo.name) {
+ debug("Dun required and dun interface is the same");
+ return;
+ }
+
+ if (this._tetheringInterface[TETHERING_TYPE_USB].externalInterface ===
+ gNetworkManager.activeNetworkInfo.name) {
+ debug("The active interface is the same");
+ return;
+ }
+
+ let previous = {
+ internalIfname: this._tetheringInterface[TETHERING_TYPE_USB].internalInterface,
+ externalIfname: this._tetheringInterface[TETHERING_TYPE_USB].externalInterface
+ };
+
+ let current = {
+ internalIfname: this._tetheringInterface[TETHERING_TYPE_USB].internalInterface,
+ externalIfname: aNetworkInfo.name
+ };
+
+ let callback = (() => {
+ // Update external network interface.
+ debug("Update upstream interface to " + aNetworkInfo.name);
+ gNetworkService.updateUpStream(previous, current,
+ this.onConnectionChangedReport.bind(this));
+ });
+
+ if (this._usbTetheringAction === TETHERING_STATE_ONGOING) {
+ debug("Postpone the event and handle it when state is idle.");
+ this.wantConnectionEvent = callback;
+ return;
+ }
+ this.wantConnectionEvent = null;
+
+ callback.call(this);
+ },
+
+ isAnyConnected: function() {
+ let allNetworkInfo = gNetworkManager.allNetworkInfo;
+ for (let networkId in allNetworkInfo) {
+ if (allNetworkInfo.hasOwnProperty(networkId) &&
+ allNetworkInfo[networkId].state === Ci.nsINetworkInfo.NETWORK_STATE_CONNECTED) {
+ return true;
+ }
+ }
+ return false;
+ },
+};
+
+this.NSGetFactory = XPCOMUtils.generateNSGetFactory([TetheringService]);
diff --git a/dom/system/gonk/TetheringService.manifest b/dom/system/gonk/TetheringService.manifest
new file mode 100644
index 000000000..b8f18dec1
--- /dev/null
+++ b/dom/system/gonk/TetheringService.manifest
@@ -0,0 +1,4 @@
+# TetheringService.js
+component {527a4121-ee5a-4651-be9c-f46f59cf7c01} TetheringService.js
+contract @mozilla.org/tethering/service;1 {527a4121-ee5a-4651-be9c-f46f59cf7c01}
+category profile-after-change TetheringService @mozilla.org/tethering/service;1
diff --git a/dom/system/gonk/TimeZoneSettingObserver.cpp b/dom/system/gonk/TimeZoneSettingObserver.cpp
new file mode 100644
index 000000000..512f79908
--- /dev/null
+++ b/dom/system/gonk/TimeZoneSettingObserver.cpp
@@ -0,0 +1,239 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "base/message_loop.h"
+#include "jsapi.h"
+#include "mozilla/dom/ScriptSettings.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/ClearOnShutdown.h"
+#include "mozilla/Hal.h"
+#include "mozilla/Services.h"
+#include "mozilla/StaticPtr.h"
+#include "nsCOMPtr.h"
+#include "nsDebug.h"
+#include "nsIObserver.h"
+#include "nsIObserverService.h"
+#include "nsISettingsService.h"
+#include "nsJSUtils.h"
+#include "nsServiceManagerUtils.h"
+#include "nsString.h"
+#include "TimeZoneSettingObserver.h"
+#include "xpcpublic.h"
+#include "nsContentUtils.h"
+#include "nsPrintfCString.h"
+#include "mozilla/dom/BindingUtils.h"
+#include "mozilla/dom/SettingChangeNotificationBinding.h"
+
+#undef LOG
+#undef ERR
+#define LOG(args...) __android_log_print(ANDROID_LOG_INFO, "Time Zone Setting" , ## args)
+#define ERR(args...) __android_log_print(ANDROID_LOG_ERROR, "Time Zone Setting" , ## args)
+
+#define TIME_TIMEZONE "time.timezone"
+#define MOZSETTINGS_CHANGED "mozsettings-changed"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+
+namespace {
+
+class TimeZoneSettingObserver : public nsIObserver
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIOBSERVER
+
+ TimeZoneSettingObserver();
+ static nsresult SetTimeZone(const JS::Value &aValue, JSContext *aContext);
+
+protected:
+ virtual ~TimeZoneSettingObserver();
+};
+
+class TimeZoneSettingCb final : public nsISettingsServiceCallback
+{
+public:
+ NS_DECL_ISUPPORTS
+
+ TimeZoneSettingCb() {}
+
+ NS_IMETHOD Handle(const nsAString &aName, JS::Handle<JS::Value> aResult) {
+
+ JSContext *cx = nsContentUtils::GetCurrentJSContext();
+ NS_ENSURE_TRUE(cx, NS_OK);
+
+ // If we don't have time.timezone value in the settings, we need
+ // to initialize the settings based on the current system timezone
+ // to make settings consistent with system. This usually happens
+ // at the very first boot. After that, settings must have a value.
+ if (aResult.isNull()) {
+ // Get the current system time zone offset. Note that we need to
+ // convert the value to a UTC representation in the format of
+ // "UTC{+,-}hh:mm", so that the Gaia end can know how to interpret.
+ // E.g., -480 is "UTC+08:00"; 630 is "UTC-10:30".
+ int32_t timeZoneOffset = hal::GetTimezoneOffset();
+ nsPrintfCString curTimeZone("UTC%+03d:%02d",
+ -timeZoneOffset / 60,
+ abs(timeZoneOffset) % 60);
+
+ // Convert it to a JS string.
+ NS_ConvertUTF8toUTF16 utf16Str(curTimeZone);
+
+ JS::Rooted<JSString*> jsStr(cx, JS_NewUCStringCopyN(cx,
+ utf16Str.get(),
+ utf16Str.Length()));
+ if (!jsStr) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ // Set the settings based on the current system timezone.
+ nsCOMPtr<nsISettingsServiceLock> lock;
+ nsCOMPtr<nsISettingsService> settingsService =
+ do_GetService("@mozilla.org/settingsService;1");
+ if (!settingsService) {
+ ERR("Failed to get settingsLock service!");
+ return NS_OK;
+ }
+ settingsService->CreateLock(nullptr, getter_AddRefs(lock));
+ JS::Rooted<JS::Value> value(cx, JS::StringValue(jsStr));
+ lock->Set(TIME_TIMEZONE, value, nullptr, nullptr);
+ return NS_OK;
+ }
+
+ // Set the system timezone based on the current settings.
+ if (aResult.isString()) {
+ return TimeZoneSettingObserver::SetTimeZone(aResult, cx);
+ }
+
+ return NS_OK;
+ }
+
+ NS_IMETHOD HandleError(const nsAString &aName) {
+ ERR("TimeZoneSettingCb::HandleError: %s\n", NS_LossyConvertUTF16toASCII(aName).get());
+ return NS_OK;
+ }
+
+protected:
+ ~TimeZoneSettingCb() {}
+};
+
+NS_IMPL_ISUPPORTS(TimeZoneSettingCb, nsISettingsServiceCallback)
+
+TimeZoneSettingObserver::TimeZoneSettingObserver()
+{
+ // Setup an observer to watch changes to the setting.
+ nsCOMPtr<nsIObserverService> observerService = services::GetObserverService();
+ if (!observerService) {
+ ERR("GetObserverService failed");
+ return;
+ }
+ nsresult rv;
+ rv = observerService->AddObserver(this, MOZSETTINGS_CHANGED, false);
+ if (NS_FAILED(rv)) {
+ ERR("AddObserver failed");
+ return;
+ }
+
+ // Read the 'time.timezone' setting in order to start with a known
+ // value at boot time. The handle() will be called after reading.
+ nsCOMPtr<nsISettingsServiceLock> lock;
+ nsCOMPtr<nsISettingsService> settingsService =
+ do_GetService("@mozilla.org/settingsService;1");
+ if (!settingsService) {
+ ERR("Failed to get settingsLock service!");
+ return;
+ }
+ settingsService->CreateLock(nullptr, getter_AddRefs(lock));
+ nsCOMPtr<nsISettingsServiceCallback> callback = new TimeZoneSettingCb();
+ lock->Get(TIME_TIMEZONE, callback);
+}
+
+nsresult TimeZoneSettingObserver::SetTimeZone(const JS::Value &aValue, JSContext *aContext)
+{
+ // Convert the JS value to a nsCString type.
+ // The value should be a JS string like "America/Chicago" or "UTC-05:00".
+ nsAutoJSString valueStr;
+ if (!valueStr.init(aContext, aValue.toString())) {
+ ERR("Failed to convert JS value to nsCString");
+ return NS_ERROR_FAILURE;
+ }
+ NS_ConvertUTF16toUTF8 newTimezone(valueStr);
+
+ // Hal expects opposite sign from general notations,
+ // so we need to flip it.
+ if (newTimezone.Find(NS_LITERAL_CSTRING("UTC+")) == 0) {
+ if (!newTimezone.SetCharAt('-', 3)) {
+ return NS_ERROR_FAILURE;
+ }
+ } else if (newTimezone.Find(NS_LITERAL_CSTRING("UTC-")) == 0) {
+ if (!newTimezone.SetCharAt('+', 3)) {
+ return NS_ERROR_FAILURE;
+ }
+ }
+
+ // Set the timezone only when the system timezone is not identical.
+ nsCString curTimezone = hal::GetTimezone();
+ if (!curTimezone.Equals(newTimezone)) {
+ hal::SetTimezone(newTimezone);
+ }
+
+ return NS_OK;
+}
+
+TimeZoneSettingObserver::~TimeZoneSettingObserver()
+{
+ nsCOMPtr<nsIObserverService> observerService = services::GetObserverService();
+ if (observerService) {
+ observerService->RemoveObserver(this, MOZSETTINGS_CHANGED);
+ }
+}
+
+NS_IMPL_ISUPPORTS(TimeZoneSettingObserver, nsIObserver)
+
+NS_IMETHODIMP
+TimeZoneSettingObserver::Observe(nsISupports *aSubject,
+ const char *aTopic,
+ const char16_t *aData)
+{
+ if (strcmp(aTopic, MOZSETTINGS_CHANGED) != 0) {
+ return NS_OK;
+ }
+
+ // Note that this function gets called for any and all settings changes,
+ // so we need to carefully check if we have the one we're interested in.
+ //
+ // The string that we're interested in will be a JSON string that looks like:
+ // {"key":"time.timezone","value":"America/Chicago"} or
+ // {"key":"time.timezone","value":"UTC-05:00"}
+
+ AutoSafeJSContext cx;
+ RootedDictionary<SettingChangeNotification> setting(cx);
+ if (!WrappedJSToDictionary(cx, aSubject, setting)) {
+ return NS_OK;
+ }
+ if (!setting.mKey.EqualsASCII(TIME_TIMEZONE)) {
+ return NS_OK;
+ }
+ if (!setting.mValue.isString()) {
+ return NS_OK;
+ }
+
+ // Set the system timezone.
+ return SetTimeZone(setting.mValue, cx);
+}
+
+} // namespace
+
+static mozilla::StaticRefPtr<TimeZoneSettingObserver> sTimeZoneSettingObserver;
+namespace mozilla {
+namespace system {
+void
+InitializeTimeZoneSettingObserver()
+{
+ sTimeZoneSettingObserver = new TimeZoneSettingObserver();
+ ClearOnShutdown(&sTimeZoneSettingObserver);
+}
+
+} // namespace system
+} // namespace mozilla
diff --git a/dom/system/gonk/TimeZoneSettingObserver.h b/dom/system/gonk/TimeZoneSettingObserver.h
new file mode 100644
index 000000000..08b7cebb3
--- /dev/null
+++ b/dom/system/gonk/TimeZoneSettingObserver.h
@@ -0,0 +1,20 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_system_timesetting_h__
+#define mozilla_system_timesetting_h__
+
+namespace mozilla {
+namespace system {
+
+// Initialize TimeZoneSettingObserver which observes the time zone change
+// event from settings service. When receiving the event, it modifies the
+// system time zone.
+void InitializeTimeZoneSettingObserver();
+
+} // namespace system
+} // namespace mozilla
+
+#endif // mozilla_system_timesetting_h__
+
diff --git a/dom/system/gonk/Volume.cpp b/dom/system/gonk/Volume.cpp
new file mode 100644
index 000000000..f90c7b693
--- /dev/null
+++ b/dom/system/gonk/Volume.cpp
@@ -0,0 +1,596 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "Volume.h"
+#include "VolumeCommand.h"
+#include "VolumeManager.h"
+#include "VolumeManagerLog.h"
+#include "nsIVolume.h"
+#include "nsXULAppAPI.h"
+
+#include <vold/ResponseCode.h>
+
+namespace mozilla {
+namespace system {
+
+#if DEBUG_VOLUME_OBSERVER
+void
+VolumeObserverList::Broadcast(Volume* const& aVolume)
+{
+ uint32_t size = mObservers.Length();
+ for (uint32_t i = 0; i < size; ++i) {
+ LOG("VolumeObserverList::Broadcast to [%u] %p volume '%s'",
+ i, mObservers[i], aVolume->NameStr());
+ mObservers[i]->Notify(aVolume);
+ }
+}
+#endif
+
+VolumeObserverList Volume::sEventObserverList;
+
+// We have a feature where volumes can be locked when mounted. This
+// is used to prevent a volume from being shared with the PC while
+// it is actively being used (say for storing an update image)
+//
+// We use WakeLocks (a poor choice of name, but it does what we want)
+// from the PowerManagerService to determine when we're locked.
+// In particular we'll create a wakelock called volume-NAME-GENERATION
+// (where NAME is the volume name, and GENERATION is its generation
+// number), and if this wakelock is locked, then we'll prevent a volume
+// from being shared.
+//
+// Implementation Details:
+//
+// Since the AutoMounter can only control when something gets mounted
+// and not when it gets unmounted (for example: a user pulls the SDCard)
+// and because Volume and nsVolume data structures are maintained on
+// separate threads, we have the potential for some race conditions.
+// We eliminate the race conditions by introducing the concept of a
+// generation number. Every time a volume transitions to the Mounted
+// state, it gets assigned a new generation number. Whenever the state
+// of a Volume changes, we send the updated state and current generation
+// number to the main thread where it gets updated in the nsVolume.
+//
+// Since WakeLocks can only be queried from the main-thread, the
+// nsVolumeService looks for WakeLock status changes, and forwards
+// the results to the IOThread.
+//
+// If the Volume (IOThread) receives a volume update where the generation
+// number mismatches, then the update is simply ignored.
+//
+// When a Volume (IOThread) initially becomes mounted, we assume it to
+// be locked until we get our first update from nsVolume (MainThread).
+static int32_t sMountGeneration = 0;
+
+static uint32_t sNextId = 1;
+
+// We don't get media inserted/removed events at startup. So we
+// assume it's present, and we'll be told that it's missing.
+Volume::Volume(const nsCSubstring& aName)
+ : mMediaPresent(true),
+ mState(nsIVolume::STATE_INIT),
+ mName(aName),
+ mMountGeneration(-1),
+ mMountLocked(true), // Needs to agree with nsVolume::nsVolume
+ mSharingEnabled(false),
+ mFormatRequested(false),
+ mMountRequested(false),
+ mUnmountRequested(false),
+ mCanBeShared(true),
+ mIsSharing(false),
+ mIsFormatting(false),
+ mIsUnmounting(false),
+ mIsRemovable(false),
+ mIsHotSwappable(false),
+ mId(sNextId++)
+{
+ DBG("Volume %s: created", NameStr());
+}
+
+void
+Volume::Dump(const char* aLabel) const
+{
+ LOG("%s: Volume: %s (%d) is %s and %s @ %s gen %d locked %d",
+ aLabel,
+ NameStr(),
+ Id(),
+ StateStr(),
+ MediaPresent() ? "inserted" : "missing",
+ MountPoint().get(),
+ MountGeneration(),
+ (int)IsMountLocked());
+ LOG("%s: Sharing %s Mounting %s Formating %s Unmounting %s",
+ aLabel,
+ CanBeShared() ? (IsSharingEnabled() ? (IsSharing() ? "en-y" : "en-n")
+ : "dis")
+ : "x",
+ IsMountRequested() ? "req" : "n",
+ IsFormatRequested() ? (IsFormatting() ? "req-y" : "req-n")
+ : (IsFormatting() ? "y" : "n"),
+ IsUnmountRequested() ? (IsUnmounting() ? "req-y" : "req-n")
+ : (IsUnmounting() ? "y" : "n"));
+}
+
+void
+Volume::ResolveAndSetMountPoint(const nsCSubstring& aMountPoint)
+{
+ nsCString mountPoint(aMountPoint);
+ char realPathBuf[PATH_MAX];
+
+ // Call realpath so that we wind up with a path which is compatible with
+ // functions like nsVolumeService::GetVolumeByPath.
+
+ if (realpath(mountPoint.get(), realPathBuf) < 0) {
+ // The path we were handed doesn't exist. Warn about it, but use it
+ // anyways assuming that the user knows what they're doing.
+
+ ERR("ResolveAndSetMountPoint: realpath on '%s' failed: %d",
+ mountPoint.get(), errno);
+ mMountPoint = mountPoint;
+ } else {
+ mMountPoint = realPathBuf;
+ }
+ DBG("Volume %s: Setting mountpoint to '%s'", NameStr(), mMountPoint.get());
+}
+
+void Volume::SetFakeVolume(const nsACString& aMountPoint)
+{
+ this->mMountLocked = false;
+ this->mCanBeShared = false;
+ ResolveAndSetMountPoint(aMountPoint);
+ SetState(nsIVolume::STATE_MOUNTED);
+}
+
+void
+Volume::SetIsSharing(bool aIsSharing)
+{
+ if (aIsSharing == mIsSharing) {
+ return;
+ }
+ mIsSharing = aIsSharing;
+ LOG("Volume %s: IsSharing set to %d state %s",
+ NameStr(), (int)mIsSharing, StateStr(mState));
+ sEventObserverList.Broadcast(this);
+}
+
+void
+Volume::SetIsFormatting(bool aIsFormatting)
+{
+ if (aIsFormatting == mIsFormatting) {
+ return;
+ }
+ mIsFormatting = aIsFormatting;
+ LOG("Volume %s: IsFormatting set to %d state %s",
+ NameStr(), (int)mIsFormatting, StateStr(mState));
+ if (mIsFormatting) {
+ sEventObserverList.Broadcast(this);
+ }
+}
+
+void
+Volume::SetIsUnmounting(bool aIsUnmounting)
+{
+ if (aIsUnmounting == mIsUnmounting) {
+ return;
+ }
+ mIsUnmounting = aIsUnmounting;
+ LOG("Volume %s: IsUnmounting set to %d state %s",
+ NameStr(), (int)mIsUnmounting, StateStr(mState));
+ sEventObserverList.Broadcast(this);
+}
+
+void
+Volume::SetIsRemovable(bool aIsRemovable)
+{
+ if (aIsRemovable == mIsRemovable) {
+ return;
+ }
+ mIsRemovable = aIsRemovable;
+ if (!mIsRemovable) {
+ mIsHotSwappable = false;
+ }
+ LOG("Volume %s: IsRemovable set to %d state %s",
+ NameStr(), (int)mIsRemovable, StateStr(mState));
+ sEventObserverList.Broadcast(this);
+}
+
+void
+Volume::SetIsHotSwappable(bool aIsHotSwappable)
+{
+ if (aIsHotSwappable == mIsHotSwappable) {
+ return;
+ }
+ mIsHotSwappable = aIsHotSwappable;
+ if (mIsHotSwappable) {
+ mIsRemovable = true;
+ }
+ LOG("Volume %s: IsHotSwappable set to %d state %s",
+ NameStr(), (int)mIsHotSwappable, StateStr(mState));
+ sEventObserverList.Broadcast(this);
+}
+
+bool
+Volume::BoolConfigValue(const nsCString& aConfigValue, bool& aBoolValue)
+{
+ if (aConfigValue.EqualsLiteral("1") ||
+ aConfigValue.LowerCaseEqualsLiteral("true")) {
+ aBoolValue = true;
+ return true;
+ }
+ if (aConfigValue.EqualsLiteral("0") ||
+ aConfigValue.LowerCaseEqualsLiteral("false")) {
+ aBoolValue = false;
+ return true;
+ }
+ return false;
+}
+
+void
+Volume::SetConfig(const nsCString& aConfigName, const nsCString& aConfigValue)
+{
+ if (aConfigName.LowerCaseEqualsLiteral("removable")) {
+ bool value = false;
+ if (BoolConfigValue(aConfigValue, value)) {
+ SetIsRemovable(value);
+ } else {
+ ERR("Volume %s: invalid value '%s' for configuration '%s'",
+ NameStr(), aConfigValue.get(), aConfigName.get());
+ }
+ return;
+ }
+ if (aConfigName.LowerCaseEqualsLiteral("hotswappable")) {
+ bool value = false;
+ if (BoolConfigValue(aConfigValue, value)) {
+ SetIsHotSwappable(value);
+ } else {
+ ERR("Volume %s: invalid value '%s' for configuration '%s'",
+ NameStr(), aConfigValue.get(), aConfigName.get());
+ }
+ return;
+ }
+ ERR("Volume %s: invalid config '%s'", NameStr(), aConfigName.get());
+}
+
+void
+Volume::SetMediaPresent(bool aMediaPresent)
+{
+ MOZ_ASSERT(XRE_IsParentProcess());
+ MOZ_ASSERT(MessageLoop::current() == XRE_GetIOMessageLoop());
+
+ // mMediaPresent is slightly redunant to the state, however
+ // when media is removed (while Idle), we get the following:
+ // 631 Volume sdcard /mnt/sdcard disk removed (179:0)
+ // 605 Volume sdcard /mnt/sdcard state changed from 1 (Idle-Unmounted) to 0 (No-Media)
+ //
+ // And on media insertion, we get:
+ // 630 Volume sdcard /mnt/sdcard disk inserted (179:0)
+ // 605 Volume sdcard /mnt/sdcard state changed from 0 (No-Media) to 2 (Pending)
+ // 605 Volume sdcard /mnt/sdcard state changed from 2 (Pending) to 1 (Idle-Unmounted)
+ //
+ // On media removal while the media is mounted:
+ // 632 Volume sdcard /mnt/sdcard bad removal (179:1)
+ // 605 Volume sdcard /mnt/sdcard state changed from 4 (Mounted) to 5 (Unmounting)
+ // 605 Volume sdcard /mnt/sdcard state changed from 5 (Unmounting) to 1 (Idle-Unmounted)
+ // 631 Volume sdcard /mnt/sdcard disk removed (179:0)
+ // 605 Volume sdcard /mnt/sdcard state changed from 1 (Idle-Unmounted) to 0 (No-Media)
+ //
+ // When sharing with a PC, it goes Mounted -> Idle -> Shared
+ // When unsharing with a PC, it goes Shared -> Idle -> Mounted
+ //
+ // The AutoMounter needs to know whether the media is present or not when
+ // processing the Idle state.
+
+ if (mMediaPresent == aMediaPresent) {
+ return;
+ }
+
+ LOG("Volume: %s media %s", NameStr(), aMediaPresent ? "inserted" : "removed");
+ mMediaPresent = aMediaPresent;
+ sEventObserverList.Broadcast(this);
+}
+
+void
+Volume::SetSharingEnabled(bool aSharingEnabled)
+{
+ mSharingEnabled = aSharingEnabled;
+
+ LOG("SetSharingMode for volume %s to %d canBeShared = %d",
+ NameStr(), (int)mSharingEnabled, (int)mCanBeShared);
+ sEventObserverList.Broadcast(this);
+}
+
+void
+Volume::SetFormatRequested(bool aFormatRequested)
+{
+ mFormatRequested = aFormatRequested;
+
+ LOG("SetFormatRequested for volume %s to %d CanBeFormatted = %d",
+ NameStr(), (int)mFormatRequested, (int)CanBeFormatted());
+}
+
+void
+Volume::SetMountRequested(bool aMountRequested)
+{
+ mMountRequested = aMountRequested;
+
+ LOG("SetMountRequested for volume %s to %d CanBeMounted = %d",
+ NameStr(), (int)mMountRequested, (int)CanBeMounted());
+}
+
+void
+Volume::SetUnmountRequested(bool aUnmountRequested)
+{
+ mUnmountRequested = aUnmountRequested;
+
+ LOG("SetUnmountRequested for volume %s to %d CanBeMounted = %d",
+ NameStr(), (int)mUnmountRequested, (int)CanBeMounted());
+}
+
+void
+Volume::SetState(Volume::STATE aNewState)
+{
+ MOZ_ASSERT(XRE_IsParentProcess());
+ MOZ_ASSERT(MessageLoop::current() == XRE_GetIOMessageLoop());
+ if (aNewState == mState) {
+ return;
+ }
+ if (aNewState == nsIVolume::STATE_MOUNTED) {
+ mMountGeneration = ++sMountGeneration;
+ LOG("Volume %s (%u): changing state from %s to %s @ '%s' (%d observers) "
+ "mountGeneration = %d, locked = %d",
+ NameStr(), mId, StateStr(mState),
+ StateStr(aNewState), mMountPoint.get(), sEventObserverList.Length(),
+ mMountGeneration, (int)mMountLocked);
+ } else {
+ LOG("Volume %s (%u): changing state from %s to %s (%d observers)",
+ NameStr(), mId, StateStr(mState),
+ StateStr(aNewState), sEventObserverList.Length());
+ }
+
+ switch (aNewState) {
+ case nsIVolume::STATE_NOMEDIA:
+ // Cover the startup case where we don't get insertion/removal events
+ mMediaPresent = false;
+ mIsSharing = false;
+ mUnmountRequested = false;
+ mMountRequested = false;
+ mIsUnmounting = false;
+ break;
+
+ case nsIVolume::STATE_MOUNTED:
+ case nsIVolume::STATE_MOUNT_FAIL:
+ mMountRequested = false;
+ mIsFormatting = false;
+ mIsSharing = false;
+ mIsUnmounting = false;
+ break;
+
+ case nsIVolume::STATE_FORMATTING:
+ mFormatRequested = false;
+ mIsFormatting = true;
+ mIsSharing = false;
+ mIsUnmounting = false;
+ break;
+
+ case nsIVolume::STATE_SHARED:
+ case nsIVolume::STATE_SHAREDMNT:
+ // Covers startup cases. Normally, mIsSharing would be set to true
+ // when we issue the command to initiate the sharing process, but
+ // it's conceivable that a volume could already be in a shared state
+ // when b2g starts.
+ mIsSharing = true;
+ mIsUnmounting = false;
+ mIsFormatting = false;
+ break;
+
+ case nsIVolume::STATE_UNMOUNTING:
+ mIsUnmounting = true;
+ mIsFormatting = false;
+ mIsSharing = false;
+ break;
+
+ case nsIVolume::STATE_IDLE: // Fall through
+ case nsIVolume::STATE_CHECKMNT: // Fall through
+ default:
+ break;
+ }
+ mState = aNewState;
+ sEventObserverList.Broadcast(this);
+}
+
+void
+Volume::SetMountPoint(const nsCSubstring& aMountPoint)
+{
+ MOZ_ASSERT(XRE_IsParentProcess());
+ MOZ_ASSERT(MessageLoop::current() == XRE_GetIOMessageLoop());
+
+ if (mMountPoint.Equals(aMountPoint)) {
+ return;
+ }
+ ResolveAndSetMountPoint(aMountPoint);
+}
+
+void
+Volume::StartMount(VolumeResponseCallback* aCallback)
+{
+ MOZ_ASSERT(XRE_IsParentProcess());
+ MOZ_ASSERT(MessageLoop::current() == XRE_GetIOMessageLoop());
+
+ StartCommand(new VolumeActionCommand(this, "mount", "", aCallback));
+}
+
+void
+Volume::StartUnmount(VolumeResponseCallback* aCallback)
+{
+ MOZ_ASSERT(XRE_IsParentProcess());
+ MOZ_ASSERT(MessageLoop::current() == XRE_GetIOMessageLoop());
+
+ StartCommand(new VolumeActionCommand(this, "unmount", "force", aCallback));
+}
+
+void
+Volume::StartFormat(VolumeResponseCallback* aCallback)
+{
+ MOZ_ASSERT(XRE_IsParentProcess());
+ MOZ_ASSERT(MessageLoop::current() == XRE_GetIOMessageLoop());
+
+ StartCommand(new VolumeActionCommand(this, "format", "", aCallback));
+}
+
+void
+Volume::StartShare(VolumeResponseCallback* aCallback)
+{
+ MOZ_ASSERT(XRE_IsParentProcess());
+ MOZ_ASSERT(MessageLoop::current() == XRE_GetIOMessageLoop());
+
+ StartCommand(new VolumeActionCommand(this, "share", "ums", aCallback));
+}
+
+void
+Volume::StartUnshare(VolumeResponseCallback* aCallback)
+{
+ MOZ_ASSERT(XRE_IsParentProcess());
+ MOZ_ASSERT(MessageLoop::current() == XRE_GetIOMessageLoop());
+
+ StartCommand(new VolumeActionCommand(this, "unshare", "ums", aCallback));
+}
+
+void
+Volume::StartCommand(VolumeCommand* aCommand)
+{
+ MOZ_ASSERT(XRE_IsParentProcess());
+ MOZ_ASSERT(MessageLoop::current() == XRE_GetIOMessageLoop());
+
+ VolumeManager::PostCommand(aCommand);
+}
+
+//static
+void
+Volume::RegisterVolumeObserver(Volume::EventObserver* aObserver, const char* aName)
+{
+ MOZ_ASSERT(XRE_IsParentProcess());
+ MOZ_ASSERT(MessageLoop::current() == XRE_GetIOMessageLoop());
+
+ sEventObserverList.AddObserver(aObserver);
+
+ DBG("Added Volume Observer '%s' @%p, length = %u",
+ aName, aObserver, sEventObserverList.Length());
+
+ // Send an initial event to the observer (for each volume)
+ size_t numVolumes = VolumeManager::NumVolumes();
+ for (size_t volIndex = 0; volIndex < numVolumes; volIndex++) {
+ RefPtr<Volume> vol = VolumeManager::GetVolume(volIndex);
+ aObserver->Notify(vol);
+ }
+}
+
+//static
+void
+Volume::UnregisterVolumeObserver(Volume::EventObserver* aObserver, const char* aName)
+{
+ MOZ_ASSERT(XRE_IsParentProcess());
+ MOZ_ASSERT(MessageLoop::current() == XRE_GetIOMessageLoop());
+
+ sEventObserverList.RemoveObserver(aObserver);
+
+ DBG("Removed Volume Observer '%s' @%p, length = %u",
+ aName, aObserver, sEventObserverList.Length());
+}
+
+//static
+void
+Volume::UpdateMountLock(const nsACString& aVolumeName,
+ const int32_t& aMountGeneration,
+ const bool& aMountLocked)
+{
+ MOZ_ASSERT(XRE_IsParentProcess());
+ MOZ_ASSERT(MessageLoop::current() == XRE_GetIOMessageLoop());
+
+ RefPtr<Volume> vol = VolumeManager::FindVolumeByName(aVolumeName);
+ if (!vol || (vol->mMountGeneration != aMountGeneration)) {
+ return;
+ }
+ if (vol->mMountLocked != aMountLocked) {
+ vol->mMountLocked = aMountLocked;
+ DBG("Volume::UpdateMountLock for '%s' to %d\n", vol->NameStr(), (int)aMountLocked);
+ sEventObserverList.Broadcast(vol);
+ }
+}
+
+void
+Volume::HandleVoldResponse(int aResponseCode, nsCWhitespaceTokenizer& aTokenizer)
+{
+ MOZ_ASSERT(XRE_IsParentProcess());
+ MOZ_ASSERT(MessageLoop::current() == XRE_GetIOMessageLoop());
+
+ // The volume name will have already been parsed, and the tokenizer will point
+ // to the token after the volume name
+ switch (aResponseCode) {
+ case ::ResponseCode::VolumeListResult: {
+ // Each line will look something like:
+ //
+ // sdcard /mnt/sdcard 1
+ //
+ nsDependentCSubstring mntPoint(aTokenizer.nextToken());
+ SetMountPoint(mntPoint);
+ nsresult errCode;
+ nsCString state(aTokenizer.nextToken());
+ if (state.EqualsLiteral("X")) {
+ // Special state for creating fake volumes which can't be shared.
+ mCanBeShared = false;
+ SetState(nsIVolume::STATE_MOUNTED);
+ } else {
+ SetState((STATE)state.ToInteger(&errCode));
+ }
+ break;
+ }
+
+ case ::ResponseCode::VolumeStateChange: {
+ // Format of the line looks something like:
+ //
+ // Volume sdcard /mnt/sdcard state changed from 7 (Shared-Unmounted) to 1 (Idle-Unmounted)
+ //
+ // So we parse out the state after the string " to "
+ while (aTokenizer.hasMoreTokens()) {
+ nsAutoCString token(aTokenizer.nextToken());
+ if (token.EqualsLiteral("to")) {
+ nsresult errCode;
+ token = aTokenizer.nextToken();
+ STATE newState = (STATE)(token.ToInteger(&errCode));
+ if (newState == nsIVolume::STATE_MOUNTED) {
+ // We set the state to STATE_CHECKMNT here, and the once the
+ // AutoMounter detects that the volume is actually accessible
+ // then the AutoMounter will set the volume as STATE_MOUNTED.
+ SetState(nsIVolume::STATE_CHECKMNT);
+ } else {
+ if (State() == nsIVolume::STATE_CHECKING && newState == nsIVolume::STATE_IDLE) {
+ LOG("Mount of volume '%s' failed", NameStr());
+ SetState(nsIVolume::STATE_MOUNT_FAIL);
+ } else {
+ SetState(newState);
+ }
+ }
+ break;
+ }
+ }
+ break;
+ }
+
+ case ::ResponseCode::VolumeDiskInserted:
+ SetMediaPresent(true);
+ break;
+
+ case ::ResponseCode::VolumeDiskRemoved: // fall-thru
+ case ::ResponseCode::VolumeBadRemoval:
+ SetMediaPresent(false);
+ break;
+
+ default:
+ LOG("Volume: %s unrecognized reponse code (ignored)", NameStr());
+ break;
+ }
+}
+
+} // namespace system
+} // namespace mozilla
diff --git a/dom/system/gonk/Volume.h b/dom/system/gonk/Volume.h
new file mode 100644
index 000000000..821292a9a
--- /dev/null
+++ b/dom/system/gonk/Volume.h
@@ -0,0 +1,157 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_system_volume_h__
+#define mozilla_system_volume_h__
+
+#include "VolumeCommand.h"
+#include "nsIVolume.h"
+#include "nsString.h"
+#include "mozilla/Observer.h"
+#include "nsISupportsImpl.h"
+#include "nsWhitespaceTokenizer.h"
+
+namespace mozilla {
+namespace system {
+
+/***************************************************************************
+*
+* There is an instance of the Volume class for each volume reported
+* from vold.
+*
+* Each volume originates from the /system/etv/vold.fstab file.
+*
+***************************************************************************/
+
+class Volume;
+
+#define DEBUG_VOLUME_OBSERVER 0
+
+#if DEBUG_VOLUME_OBSERVER
+class VolumeObserverList : public mozilla::ObserverList<Volume*>
+{
+public:
+ void Broadcast(Volume* const& aVolume);
+};
+#else
+typedef mozilla::ObserverList<Volume*> VolumeObserverList;
+#endif
+
+class Volume final
+{
+public:
+ NS_INLINE_DECL_REFCOUNTING(Volume)
+
+ Volume(const nsCSubstring& aVolumeName);
+
+ typedef long STATE; // States are now defined in nsIVolume.idl
+
+ static const char* StateStr(STATE aState) { return NS_VolumeStateStr(aState); }
+ const char* StateStr() const { return StateStr(mState); }
+ STATE State() const { return mState; }
+
+ const nsCString& Name() const { return mName; }
+ const char* NameStr() const { return mName.get(); }
+
+ void Dump(const char* aLabel) const;
+
+ // The mount point is the name of the directory where the volume is mounted.
+ // (i.e. path that leads to the files stored on the volume).
+ const nsCString& MountPoint() const { return mMountPoint; }
+
+ uint32_t Id() const { return mId; }
+
+ int32_t MountGeneration() const { return mMountGeneration; }
+ bool IsMountLocked() const { return mMountLocked; }
+ bool MediaPresent() const { return mMediaPresent; }
+ bool CanBeShared() const { return mCanBeShared; }
+ bool CanBeFormatted() const { return CanBeShared(); }
+ bool CanBeMounted() const { return CanBeShared(); }
+ bool IsSharingEnabled() const { return mCanBeShared && mSharingEnabled; }
+ bool IsFormatRequested() const { return CanBeFormatted() && mFormatRequested; }
+ bool IsMountRequested() const { return CanBeMounted() && mMountRequested; }
+ bool IsUnmountRequested() const { return CanBeMounted() && mUnmountRequested; }
+ bool IsSharing() const { return mIsSharing; }
+ bool IsFormatting() const { return mIsFormatting; }
+ bool IsUnmounting() const { return mIsUnmounting; }
+ bool IsRemovable() const { return mIsRemovable; }
+ bool IsHotSwappable() const { return mIsHotSwappable; }
+
+ void SetFakeVolume(const nsACString& aMountPoint);
+
+ void SetSharingEnabled(bool aSharingEnabled);
+ void SetFormatRequested(bool aFormatRequested);
+ void SetMountRequested(bool aMountRequested);
+ void SetUnmountRequested(bool aUnmountRequested);
+
+ typedef mozilla::Observer<Volume *> EventObserver;
+
+ // NOTE: that observers must live in the IOThread.
+ static void RegisterVolumeObserver(EventObserver* aObserver, const char* aName);
+ static void UnregisterVolumeObserver(EventObserver* aObserver, const char* aName);
+
+protected:
+ ~Volume() {}
+
+private:
+ friend class AutoMounter; // Calls StartXxx
+ friend class nsVolume; // Calls UpdateMountLock
+ friend class VolumeManager; // Calls HandleVoldResponse
+ friend class VolumeListCallback; // Calls SetMountPoint, SetState
+
+ // The StartXxx functions will queue up a command to the VolumeManager.
+ // You can queue up as many commands as you like, and aCallback will
+ // be called as each one completes.
+ void StartMount(VolumeResponseCallback* aCallback);
+ void StartUnmount(VolumeResponseCallback* aCallback);
+ void StartFormat(VolumeResponseCallback* aCallback);
+ void StartShare(VolumeResponseCallback* aCallback);
+ void StartUnshare(VolumeResponseCallback* aCallback);
+
+ void SetIsSharing(bool aIsSharing);
+ void SetIsFormatting(bool aIsFormatting);
+ void SetIsUnmounting(bool aIsUnmounting);
+ void SetIsRemovable(bool aIsRemovable);
+ void SetIsHotSwappable(bool aIsHotSwappable);
+ void SetState(STATE aNewState);
+ void SetMediaPresent(bool aMediaPresent);
+ void SetMountPoint(const nsCSubstring& aMountPoint);
+ void StartCommand(VolumeCommand* aCommand);
+
+ void ResolveAndSetMountPoint(const nsCSubstring& aMountPoint);
+
+ bool BoolConfigValue(const nsCString& aConfigValue, bool& aBoolValue);
+ void SetConfig(const nsCString& aConfigName, const nsCString& aConfigValue);
+
+ void HandleVoldResponse(int aResponseCode, nsCWhitespaceTokenizer& aTokenizer);
+
+ static void UpdateMountLock(const nsACString& aVolumeName,
+ const int32_t& aMountGeneration,
+ const bool& aMountLocked);
+
+ bool mMediaPresent;
+ STATE mState;
+ const nsCString mName;
+ nsCString mMountPoint;
+ int32_t mMountGeneration;
+ bool mMountLocked;
+ bool mSharingEnabled;
+ bool mFormatRequested;
+ bool mMountRequested;
+ bool mUnmountRequested;
+ bool mCanBeShared;
+ bool mIsSharing;
+ bool mIsFormatting;
+ bool mIsUnmounting;
+ bool mIsRemovable;
+ bool mIsHotSwappable;
+ uint32_t mId; // Unique ID (used by MTP)
+
+ static VolumeObserverList sEventObserverList;
+};
+
+} // system
+} // mozilla
+
+#endif // mozilla_system_volumemanager_h__
diff --git a/dom/system/gonk/VolumeCommand.cpp b/dom/system/gonk/VolumeCommand.cpp
new file mode 100644
index 000000000..8095956a7
--- /dev/null
+++ b/dom/system/gonk/VolumeCommand.cpp
@@ -0,0 +1,85 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsString.h"
+#include "nsWhitespaceTokenizer.h"
+
+#include "Volume.h"
+#include "VolumeCommand.h"
+#include "VolumeManager.h"
+#include "VolumeManagerLog.h"
+
+namespace mozilla {
+namespace system {
+
+/***************************************************************************
+*
+* The VolumeActionCommand class is used to send commands which apply
+* to a particular volume.
+*
+* The following commands would fit into this category:
+*
+* volume mount <volname>
+* volume unmount <volname> [force]
+* volume format <volname>
+* volume share <volname> <method>
+* volume unshare <volname> <method>
+* volume shared <volname> <method>
+*
+* A typical response looks like:
+*
+* # vdc volume unshare sdcard ums
+* 605 Volume sdcard /mnt/sdcard state changed from 7 (Shared-Unmounted) to 1 (Idle-Unmounted)
+* 200 volume operation succeeded
+*
+* Note that the 600 series of responses are considered unsolicited and
+* are dealt with directly by the VolumeManager. This command will only
+* see the terminating response code (200 in the example above).
+*
+***************************************************************************/
+
+VolumeActionCommand::VolumeActionCommand(Volume* aVolume,
+ const char* aAction,
+ const char* aExtraArgs,
+ VolumeResponseCallback* aCallback)
+ : VolumeCommand(aCallback),
+ mVolume(aVolume)
+{
+ nsAutoCString cmd;
+
+ cmd = "volume ";
+ cmd += aAction;
+ cmd += " ";
+ cmd += aVolume->Name().get();
+
+ // vold doesn't like trailing white space, so only add it if we really need to.
+ if (aExtraArgs && (*aExtraArgs != '\0')) {
+ cmd += " ";
+ cmd += aExtraArgs;
+ }
+ SetCmd(cmd);
+}
+
+/***************************************************************************
+*
+* The VolumeListCommand class is used to send the "volume list" command to
+* vold.
+*
+* A typical response looks like:
+*
+* # vdc volume list
+* 110 sdcard /mnt/sdcard 4
+* 110 sdcard1 /mnt/sdcard/external_sd 4
+* 200 Volumes listed.
+*
+***************************************************************************/
+
+VolumeListCommand::VolumeListCommand(VolumeResponseCallback* aCallback)
+ : VolumeCommand(NS_LITERAL_CSTRING("volume list"), aCallback)
+{
+}
+
+} // system
+} // mozilla
+
diff --git a/dom/system/gonk/VolumeCommand.h b/dom/system/gonk/VolumeCommand.h
new file mode 100644
index 000000000..022965b5e
--- /dev/null
+++ b/dom/system/gonk/VolumeCommand.h
@@ -0,0 +1,204 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_system_volumecommand_h__
+#define mozilla_system_volumecommand_h__
+
+#include "nsString.h"
+#include "nsISupportsImpl.h"
+#include "mozilla/RefPtr.h"
+#include <algorithm>
+#include <vold/ResponseCode.h>
+
+namespace mozilla {
+namespace system {
+
+class Volume;
+class VolumeCommand;
+
+/***************************************************************************
+*
+* The VolumeResponseCallback class is an abstract base class. The ResponseReceived
+* method will be called for each response received.
+*
+* Depending on the command, there may be multiple responses for the
+* command. Done() will return true if this is the last response.
+*
+* The responses from vold are all of the form:
+*
+* <ResponseCode> <String>
+*
+* Valid Response codes can be found in the vold/ResponseCode.h header.
+*
+***************************************************************************/
+
+class VolumeResponseCallback
+{
+protected:
+ virtual ~VolumeResponseCallback() {}
+
+public:
+ NS_INLINE_DECL_REFCOUNTING(VolumeResponseCallback)
+ VolumeResponseCallback()
+ : mResponseCode(0), mPending(false) {}
+
+ bool Done() const
+ {
+ // Response codes from the 200, 400, and 500 series all indicated that
+ // the command has completed.
+
+ return (mResponseCode >= ::ResponseCode::CommandOkay)
+ && (mResponseCode < ::ResponseCode::UnsolicitedInformational);
+ }
+
+ bool WasSuccessful() const
+ {
+ return mResponseCode == ::ResponseCode::CommandOkay;
+ }
+
+ bool IsPending() const { return mPending; }
+ int ResponseCode() const { return mResponseCode; }
+ const nsCString &ResponseStr() const { return mResponseStr; }
+
+protected:
+ virtual void ResponseReceived(const VolumeCommand* aCommand) = 0;
+
+private:
+ friend class VolumeCommand; // Calls HandleResponse and SetPending
+
+ void HandleResponse(const VolumeCommand* aCommand,
+ int aResponseCode,
+ nsACString& aResponseStr)
+ {
+ mResponseCode = aResponseCode;
+#if ANDROID_VERSION >= 17
+ // There's a sequence number here that we don't care about
+ // We expect it to be 0. See VolumeCommand::SetCmd
+ mResponseStr = Substring(aResponseStr, 2);
+#else
+ mResponseStr = aResponseStr;
+#endif
+ if (mResponseCode >= ::ResponseCode::CommandOkay) {
+ // This is a final response.
+ mPending = false;
+ }
+ ResponseReceived(aCommand);
+ }
+
+ void SetPending(bool aPending) { mPending = aPending; }
+
+ int mResponseCode; // The response code parsed from vold
+ nsCString mResponseStr; // The rest of the line.
+ bool mPending; // Waiting for response?
+};
+
+/***************************************************************************
+*
+* The VolumeCommand class is an abstract base class used to encapsulate
+* volume commands send to vold.
+*
+* See VolumeManager.h for a list of the volume commands.
+*
+* Commands sent to vold need an explicit null character so we add one
+* to the command to ensure that it's included in the length.
+*
+* All of these commands are asynchronous in nature, and the
+* ResponseReceived callback will be called when a response is available.
+*
+***************************************************************************/
+
+class VolumeCommand
+{
+protected:
+ virtual ~VolumeCommand() {}
+
+public:
+ NS_INLINE_DECL_REFCOUNTING(VolumeCommand)
+
+ VolumeCommand(VolumeResponseCallback* aCallback)
+ : mBytesConsumed(0),
+ mCallback(aCallback)
+ {
+ SetCmd(NS_LITERAL_CSTRING(""));
+ }
+
+ VolumeCommand(const nsACString& aCommand, VolumeResponseCallback* aCallback)
+ : mBytesConsumed(0),
+ mCallback(aCallback)
+ {
+ SetCmd(aCommand);
+ }
+
+ void SetCmd(const nsACString& aCommand)
+ {
+ mCmd.Truncate();
+#if ANDROID_VERSION >= 17
+ // JB requires a sequence number at the beginning of messages.
+ // It doesn't matter what we use, so we use 0.
+ mCmd = "0 ";
+#endif
+ mCmd.Append(aCommand);
+ // Add a null character. We want this to be included in the length since
+ // vold uses it to determine the end of the command.
+ mCmd.Append('\0');
+ }
+
+ const char* CmdStr() const { return mCmd.get(); }
+ const char* Data() const { return mCmd.Data() + mBytesConsumed; }
+ size_t BytesConsumed() const { return mBytesConsumed; }
+
+ size_t BytesRemaining() const
+ {
+ return mCmd.Length() - std::min(mCmd.Length(), mBytesConsumed);
+ }
+
+ void ConsumeBytes(size_t aNumBytes)
+ {
+ mBytesConsumed += std::min(BytesRemaining(), aNumBytes);
+ }
+
+private:
+ friend class VolumeManager; // Calls SetPending & HandleResponse
+
+ void SetPending(bool aPending)
+ {
+ if (mCallback) {
+ mCallback->SetPending(aPending);
+ }
+ }
+
+ void HandleResponse(int aResponseCode, nsACString& aResponseStr)
+ {
+ if (mCallback) {
+ mCallback->HandleResponse(this, aResponseCode, aResponseStr);
+ }
+ }
+
+ nsCString mCmd; // Command being sent
+ size_t mBytesConsumed; // How many bytes have been sent
+
+ // Called when a response to the command is received.
+ RefPtr<VolumeResponseCallback> mCallback;
+};
+
+class VolumeActionCommand : public VolumeCommand
+{
+public:
+ VolumeActionCommand(Volume* aVolume, const char* aAction,
+ const char* aExtraArgs, VolumeResponseCallback* aCallback);
+
+private:
+ RefPtr<Volume> mVolume;
+};
+
+class VolumeListCommand : public VolumeCommand
+{
+public:
+ VolumeListCommand(VolumeResponseCallback* aCallback);
+};
+
+} // system
+} // mozilla
+
+#endif // mozilla_system_volumecommand_h__
diff --git a/dom/system/gonk/VolumeManager.cpp b/dom/system/gonk/VolumeManager.cpp
new file mode 100644
index 000000000..ddfa7af09
--- /dev/null
+++ b/dom/system/gonk/VolumeManager.cpp
@@ -0,0 +1,591 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "VolumeManager.h"
+
+#include "Volume.h"
+#include "VolumeCommand.h"
+#include "VolumeManagerLog.h"
+#include "VolumeServiceTest.h"
+
+#include "nsWhitespaceTokenizer.h"
+#include "nsXULAppAPI.h"
+
+#include "base/message_loop.h"
+#include "base/task.h"
+#include "mozilla/Scoped.h"
+#include "mozilla/StaticPtr.h"
+
+#include <android/log.h>
+#include <cutils/sockets.h>
+#include <fcntl.h>
+#include <sys/socket.h>
+
+namespace mozilla {
+namespace system {
+
+static StaticRefPtr<VolumeManager> sVolumeManager;
+
+VolumeManager::STATE VolumeManager::mState = VolumeManager::UNINITIALIZED;
+VolumeManager::StateObserverList VolumeManager::mStateObserverList;
+
+/***************************************************************************/
+
+VolumeManager::VolumeManager()
+ : LineWatcher('\0', kRcvBufSize),
+ mSocket(-1),
+ mCommandPending(false)
+{
+ DBG("VolumeManager constructor called");
+}
+
+VolumeManager::~VolumeManager()
+{
+}
+
+//static
+void
+VolumeManager::Dump(const char* aLabel)
+{
+ if (!sVolumeManager) {
+ LOG("%s: sVolumeManager == null", aLabel);
+ return;
+ }
+
+ VolumeArray::size_type numVolumes = NumVolumes();
+ VolumeArray::index_type volIndex;
+ for (volIndex = 0; volIndex < numVolumes; volIndex++) {
+ RefPtr<Volume> vol = GetVolume(volIndex);
+ vol->Dump(aLabel);
+ }
+}
+
+//static
+size_t
+VolumeManager::NumVolumes()
+{
+ if (!sVolumeManager) {
+ return 0;
+ }
+ return sVolumeManager->mVolumeArray.Length();
+}
+
+//static
+already_AddRefed<Volume>
+VolumeManager::GetVolume(size_t aIndex)
+{
+ MOZ_ASSERT(aIndex < NumVolumes());
+ RefPtr<Volume> vol = sVolumeManager->mVolumeArray[aIndex];
+ return vol.forget();
+}
+
+//static
+VolumeManager::STATE
+VolumeManager::State()
+{
+ return mState;
+}
+
+//static
+const char *
+VolumeManager::StateStr(VolumeManager::STATE aState)
+{
+ switch (aState) {
+ case UNINITIALIZED: return "Uninitialized";
+ case STARTING: return "Starting";
+ case VOLUMES_READY: return "Volumes Ready";
+ }
+ return "???";
+}
+
+
+//static
+void
+VolumeManager::SetState(STATE aNewState)
+{
+ if (mState != aNewState) {
+ LOG("changing state from '%s' to '%s'",
+ StateStr(mState), StateStr(aNewState));
+ mState = aNewState;
+ mStateObserverList.Broadcast(StateChangedEvent());
+ }
+}
+
+//static
+void
+VolumeManager::RegisterStateObserver(StateObserver* aObserver)
+{
+ mStateObserverList.AddObserver(aObserver);
+}
+
+//static
+void VolumeManager::UnregisterStateObserver(StateObserver* aObserver)
+{
+ mStateObserverList.RemoveObserver(aObserver);
+}
+
+//static
+already_AddRefed<Volume>
+VolumeManager::FindVolumeByName(const nsCSubstring& aName)
+{
+ if (!sVolumeManager) {
+ return nullptr;
+ }
+ VolumeArray::size_type numVolumes = NumVolumes();
+ VolumeArray::index_type volIndex;
+ for (volIndex = 0; volIndex < numVolumes; volIndex++) {
+ RefPtr<Volume> vol = GetVolume(volIndex);
+ if (vol->Name().Equals(aName)) {
+ return vol.forget();
+ }
+ }
+ return nullptr;
+}
+
+//static
+already_AddRefed<Volume>
+VolumeManager::FindAddVolumeByName(const nsCSubstring& aName)
+{
+ RefPtr<Volume> vol = FindVolumeByName(aName);
+ if (vol) {
+ return vol.forget();
+ }
+ // No volume found, create and add a new one.
+ vol = new Volume(aName);
+ sVolumeManager->mVolumeArray.AppendElement(vol);
+ return vol.forget();
+}
+
+//static
+bool
+VolumeManager::RemoveVolumeByName(const nsCSubstring& aName)
+{
+ if (!sVolumeManager) {
+ return false;
+ }
+ VolumeArray::size_type numVolumes = NumVolumes();
+ VolumeArray::index_type volIndex;
+ for (volIndex = 0; volIndex < numVolumes; volIndex++) {
+ RefPtr<Volume> vol = GetVolume(volIndex);
+ if (vol->Name().Equals(aName)) {
+ sVolumeManager->mVolumeArray.RemoveElementAt(volIndex);
+ return true;
+ }
+ }
+ // No volume found. Return false to indicate this.
+ return false;
+}
+
+
+//static
+void VolumeManager::InitConfig()
+{
+ MOZ_ASSERT(MessageLoop::current() == XRE_GetIOMessageLoop());
+
+ // This function uses /system/etc/volume.cfg to add additional volumes
+ // to the Volume Manager.
+ //
+ // This is useful on devices like the Nexus 4, which have no physical sd card
+ // or dedicated partition.
+ //
+ // The format of the volume.cfg file is as follows:
+ // create volume-name mount-point
+ // configure volume-name preference preference-value
+ // Blank lines and lines starting with the hash character "#" will be ignored.
+
+ ScopedCloseFile fp;
+ int n = 0;
+ char line[255];
+ const char *filename = "/system/etc/volume.cfg";
+ if (!(fp = fopen(filename, "r"))) {
+ LOG("Unable to open volume configuration file '%s' - ignoring", filename);
+ return;
+ }
+ while(fgets(line, sizeof(line), fp)) {
+ n++;
+
+ if (line[0] == '#')
+ continue;
+
+ nsCString commandline(line);
+ nsCWhitespaceTokenizer tokenizer(commandline);
+ if (!tokenizer.hasMoreTokens()) {
+ // Blank line - ignore
+ continue;
+ }
+
+ nsCString command(tokenizer.nextToken());
+ if (command.EqualsLiteral("create")) {
+ if (!tokenizer.hasMoreTokens()) {
+ ERR("No vol_name in %s line %d", filename, n);
+ continue;
+ }
+ nsCString volName(tokenizer.nextToken());
+ if (!tokenizer.hasMoreTokens()) {
+ ERR("No mount point for volume '%s'. %s line %d",
+ volName.get(), filename, n);
+ continue;
+ }
+ nsCString mountPoint(tokenizer.nextToken());
+ RefPtr<Volume> vol = FindAddVolumeByName(volName);
+ vol->SetFakeVolume(mountPoint);
+ continue;
+ }
+ if (command.EqualsLiteral("configure")) {
+ if (!tokenizer.hasMoreTokens()) {
+ ERR("No vol_name in %s line %d", filename, n);
+ continue;
+ }
+ nsCString volName(tokenizer.nextToken());
+ if (!tokenizer.hasMoreTokens()) {
+ ERR("No configuration name specified for volume '%s'. %s line %d",
+ volName.get(), filename, n);
+ continue;
+ }
+ nsCString configName(tokenizer.nextToken());
+ if (!tokenizer.hasMoreTokens()) {
+ ERR("No value for configuration name '%s'. %s line %d",
+ configName.get(), filename, n);
+ continue;
+ }
+ nsCString configValue(tokenizer.nextToken());
+ RefPtr<Volume> vol = FindVolumeByName(volName);
+ if (vol) {
+ vol->SetConfig(configName, configValue);
+ } else {
+ ERR("Invalid volume name '%s'.", volName.get());
+ }
+ continue;
+ }
+ if (command.EqualsLiteral("ignore")) {
+ // This command is useful to remove volumes which are being tracked by
+ // vold, but for which we have no interest.
+ if (!tokenizer.hasMoreTokens()) {
+ ERR("No vol_name in %s line %d", filename, n);
+ continue;
+ }
+ nsCString volName(tokenizer.nextToken());
+ RemoveVolumeByName(volName);
+ continue;
+ }
+ ERR("Unrecognized command: '%s'", command.get());
+ }
+}
+
+void
+VolumeManager::DefaultConfig()
+{
+
+ VolumeManager::VolumeArray::size_type numVolumes = VolumeManager::NumVolumes();
+ if (numVolumes == 0) {
+ return;
+ }
+ if (numVolumes == 1) {
+ // This is to cover early shipping phones like the Buri,
+ // which had no internal storage, and only external sdcard.
+ //
+ // Phones line the nexus-4 which only have an internal
+ // storage area will need to have a volume.cfg file with
+ // removable set to false.
+ RefPtr<Volume> vol = VolumeManager::GetVolume(0);
+ vol->SetIsRemovable(true);
+ vol->SetIsHotSwappable(true);
+ return;
+ }
+ VolumeManager::VolumeArray::index_type volIndex;
+ for (volIndex = 0; volIndex < numVolumes; volIndex++) {
+ RefPtr<Volume> vol = VolumeManager::GetVolume(volIndex);
+ if (!vol->Name().EqualsLiteral("sdcard")) {
+ vol->SetIsRemovable(true);
+ vol->SetIsHotSwappable(true);
+ }
+ }
+}
+
+class VolumeListCallback : public VolumeResponseCallback
+{
+ virtual void ResponseReceived(const VolumeCommand* aCommand)
+ {
+ switch (ResponseCode()) {
+ case ::ResponseCode::VolumeListResult: {
+ // Each line will look something like:
+ //
+ // sdcard /mnt/sdcard 1
+ //
+ // So for each volume that we get back, we update any volumes that
+ // we have of the same name, or add new ones if they don't exist.
+ nsCWhitespaceTokenizer tokenizer(ResponseStr());
+ nsDependentCSubstring volName(tokenizer.nextToken());
+ RefPtr<Volume> vol = VolumeManager::FindAddVolumeByName(volName);
+ vol->HandleVoldResponse(ResponseCode(), tokenizer);
+ break;
+ }
+
+ case ::ResponseCode::CommandOkay: {
+ // We've received the list of volumes. Now read the Volume.cfg
+ // file to perform customizations, and then tell everybody
+ // that we're ready for business.
+ VolumeManager::DefaultConfig();
+ VolumeManager::InitConfig();
+ VolumeManager::Dump("READY");
+ VolumeManager::SetState(VolumeManager::VOLUMES_READY);
+ break;
+ }
+ }
+ }
+};
+
+bool
+VolumeManager::OpenSocket()
+{
+ SetState(STARTING);
+ if ((mSocket.rwget() = socket_local_client("vold",
+ ANDROID_SOCKET_NAMESPACE_RESERVED,
+ SOCK_STREAM)) < 0) {
+ ERR("Error connecting to vold: (%s) - will retry", strerror(errno));
+ return false;
+ }
+ // add FD_CLOEXEC flag
+ int flags = fcntl(mSocket.get(), F_GETFD);
+ if (flags == -1) {
+ return false;
+ }
+ flags |= FD_CLOEXEC;
+ if (fcntl(mSocket.get(), F_SETFD, flags) == -1) {
+ return false;
+ }
+ // set non-blocking
+ if (fcntl(mSocket.get(), F_SETFL, O_NONBLOCK) == -1) {
+ return false;
+ }
+ if (!MessageLoopForIO::current()->
+ WatchFileDescriptor(mSocket.get(),
+ true,
+ MessageLoopForIO::WATCH_READ,
+ &mReadWatcher,
+ this)) {
+ return false;
+ }
+
+ LOG("Connected to vold");
+ PostCommand(new VolumeListCommand(new VolumeListCallback));
+ return true;
+}
+
+//static
+void
+VolumeManager::PostCommand(VolumeCommand* aCommand)
+{
+ if (!sVolumeManager) {
+ ERR("VolumeManager not initialized. Dropping command '%s'", aCommand->Data());
+ return;
+ }
+ aCommand->SetPending(true);
+
+ DBG("Sending command '%s'", aCommand->Data());
+ // vold can only process one command at a time, so add our command
+ // to the end of the command queue.
+ sVolumeManager->mCommands.push(aCommand);
+ if (!sVolumeManager->mCommandPending) {
+ // There aren't any commands currently being processed, so go
+ // ahead and kick this one off.
+ sVolumeManager->mCommandPending = true;
+ sVolumeManager->WriteCommandData();
+ }
+}
+
+/***************************************************************************
+* The WriteCommandData initiates sending of a command to vold. Since
+* we're running on the IOThread and not allowed to block, WriteCommandData
+* will write as much data as it can, and if not all of the data can be
+* written then it will setup a file descriptor watcher and
+* OnFileCanWriteWithoutBlocking will call WriteCommandData to write out
+* more of the command data.
+*/
+void
+VolumeManager::WriteCommandData()
+{
+ if (mCommands.size() == 0) {
+ return;
+ }
+
+ VolumeCommand* cmd = mCommands.front();
+ if (cmd->BytesRemaining() == 0) {
+ // All bytes have been written. We're waiting for a response.
+ return;
+ }
+ // There are more bytes left to write. Try to write them all.
+ ssize_t bytesWritten = write(mSocket.get(), cmd->Data(), cmd->BytesRemaining());
+ if (bytesWritten < 0) {
+ ERR("Failed to write %d bytes to vold socket", cmd->BytesRemaining());
+ Restart();
+ return;
+ }
+ DBG("Wrote %d bytes (of %d)", bytesWritten, cmd->BytesRemaining());
+ cmd->ConsumeBytes(bytesWritten);
+ if (cmd->BytesRemaining() == 0) {
+ return;
+ }
+ // We were unable to write all of the command bytes. Setup a watcher
+ // so we'll get called again when we can write without blocking.
+ if (!MessageLoopForIO::current()->
+ WatchFileDescriptor(mSocket.get(),
+ false, // one-shot
+ MessageLoopForIO::WATCH_WRITE,
+ &mWriteWatcher,
+ this)) {
+ ERR("Failed to setup write watcher for vold socket");
+ Restart();
+ }
+}
+
+void
+VolumeManager::OnLineRead(int aFd, nsDependentCSubstring& aMessage)
+{
+ MOZ_ASSERT(aFd == mSocket.get());
+ char* endPtr;
+ int responseCode = strtol(aMessage.Data(), &endPtr, 10);
+ if (*endPtr == ' ') {
+ endPtr++;
+ }
+
+ // Now fish out the rest of the line after the response code
+ nsDependentCString responseLine(endPtr, aMessage.Length() - (endPtr - aMessage.Data()));
+ DBG("Rcvd: %d '%s'", responseCode, responseLine.Data());
+
+ if (responseCode >= ::ResponseCode::UnsolicitedInformational) {
+ // These are unsolicited broadcasts. We intercept these and process
+ // them ourselves
+ HandleBroadcast(responseCode, responseLine);
+ } else {
+ // Everything else is considered to be part of the command response.
+ if (mCommands.size() > 0) {
+ VolumeCommand* cmd = mCommands.front();
+ cmd->HandleResponse(responseCode, responseLine);
+ if (responseCode >= ::ResponseCode::CommandOkay) {
+ // That's a terminating response. We can remove the command.
+ mCommands.pop();
+ mCommandPending = false;
+ // Start the next command, if there is one.
+ WriteCommandData();
+ }
+ } else {
+ ERR("Response with no command");
+ }
+ }
+}
+
+void
+VolumeManager::OnFileCanWriteWithoutBlocking(int aFd)
+{
+ MOZ_ASSERT(aFd == mSocket.get());
+ WriteCommandData();
+}
+
+void
+VolumeManager::HandleBroadcast(int aResponseCode, nsCString& aResponseLine)
+{
+ // Format of the line is something like:
+ //
+ // Volume sdcard /mnt/sdcard state changed from 7 (Shared-Unmounted) to 1 (Idle-Unmounted)
+ //
+ // So we parse out the volume name and the state after the string " to "
+ nsCWhitespaceTokenizer tokenizer(aResponseLine);
+ tokenizer.nextToken(); // The word "Volume"
+ nsDependentCSubstring volName(tokenizer.nextToken());
+
+ RefPtr<Volume> vol = FindVolumeByName(volName);
+ if (!vol) {
+ return;
+ }
+ vol->HandleVoldResponse(aResponseCode, tokenizer);
+}
+
+void
+VolumeManager::Restart()
+{
+ mReadWatcher.StopWatchingFileDescriptor();
+ mWriteWatcher.StopWatchingFileDescriptor();
+
+ while (!mCommands.empty()) {
+ mCommands.pop();
+ }
+ mCommandPending = false;
+ mSocket.dispose();
+ Start();
+}
+
+//static
+void
+VolumeManager::Start()
+{
+ MOZ_ASSERT(MessageLoop::current() == XRE_GetIOMessageLoop());
+
+ if (!sVolumeManager) {
+ return;
+ }
+ SetState(STARTING);
+ if (!sVolumeManager->OpenSocket()) {
+ // Socket open failed, try again in a second.
+ MessageLoopForIO::current()->
+ PostDelayedTask(NewRunnableFunction(VolumeManager::Start),
+ 1000);
+ }
+}
+
+void
+VolumeManager::OnError()
+{
+ Restart();
+}
+
+/***************************************************************************/
+
+static void
+InitVolumeManagerIOThread()
+{
+ MOZ_ASSERT(MessageLoop::current() == XRE_GetIOMessageLoop());
+ MOZ_ASSERT(!sVolumeManager);
+
+ sVolumeManager = new VolumeManager();
+ VolumeManager::Start();
+
+ InitVolumeServiceTestIOThread();
+}
+
+static void
+ShutdownVolumeManagerIOThread()
+{
+ MOZ_ASSERT(MessageLoop::current() == XRE_GetIOMessageLoop());
+
+ sVolumeManager = nullptr;
+}
+
+/**************************************************************************
+*
+* Public API
+*
+* Since the VolumeManager runs in IO Thread context, we need to switch
+* to IOThread context before we can do anything.
+*
+**************************************************************************/
+
+void
+InitVolumeManager()
+{
+ XRE_GetIOMessageLoop()->PostTask(
+ NewRunnableFunction(InitVolumeManagerIOThread));
+}
+
+void
+ShutdownVolumeManager()
+{
+ ShutdownVolumeServiceTest();
+
+ XRE_GetIOMessageLoop()->PostTask(
+ NewRunnableFunction(ShutdownVolumeManagerIOThread));
+}
+
+} // system
+} // mozilla
diff --git a/dom/system/gonk/VolumeManager.h b/dom/system/gonk/VolumeManager.h
new file mode 100644
index 000000000..7c0503389
--- /dev/null
+++ b/dom/system/gonk/VolumeManager.h
@@ -0,0 +1,192 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_system_volumemanager_h__
+#define mozilla_system_volumemanager_h__
+
+#include <vector>
+#include <queue>
+
+#include "base/message_loop.h"
+#include "mozilla/FileUtils.h"
+#include "mozilla/Observer.h"
+#include "nsISupportsImpl.h"
+#include "nsString.h"
+#include "nsTArray.h"
+
+#include "Volume.h"
+#include "VolumeCommand.h"
+
+namespace mozilla {
+namespace system {
+
+/***************************************************************************
+*
+* All of the public API mentioned in this file (unless otherwise
+* mentioned) must run from the IOThread.
+*
+***************************************************************************/
+
+/***************************************************************************
+*
+* The VolumeManager class is a front-end for android's vold service.
+*
+* Vold uses a unix socket interface and accepts null-terminated string
+* commands. The following commands were determined by examining the vold
+* source code:
+*
+* volume list
+* volume mount <volname>
+* volume unmount <volname> [force]
+* volume debug [on|off]
+* volume format <volname>
+* volume share <volname> <method>
+* volume unshare <volname> <method>
+* volume shared <volname> <method>
+*
+* <volname> is the name of the volume as used in /system/etc/vold.fstab
+* <method> is ums
+*
+* dump
+*
+* share status <method> (Determines if a particular sharing method is available)
+* (GB only - not available in ICS)
+*
+* storage users (??? always crashes vold ???)
+*
+* asec list
+* asec ...lots more...
+*
+* obb list
+* obb ...lots more...
+*
+* xwarp enable
+* xwarp disable
+* xwarp status
+*
+* There is also a command line tool called vdc, which can be used to send
+* the above commands to vold.
+*
+* Currently, only the volume list, share/unshare, and mount/unmount
+* commands are being used.
+*
+***************************************************************************/
+
+class VolumeManager final : public MessageLoopForIO::LineWatcher
+{
+ virtual ~VolumeManager();
+
+public:
+ NS_INLINE_DECL_REFCOUNTING(VolumeManager)
+
+ typedef nsTArray<RefPtr<Volume>> VolumeArray;
+
+ VolumeManager();
+
+ //-----------------------------------------------------------------------
+ //
+ // State related methods.
+ //
+ // The VolumeManager starts off in the STARTING state. Once a connection
+ // is established with vold, it asks for a list of volumes, and once the
+ // volume list has been received, then the VolumeManager enters the
+ // VOLUMES_READY state.
+ //
+ // If vold crashes, then the VolumeManager will once again enter the
+ // STARTING state and try to reestablish a connection with vold.
+
+ enum STATE
+ {
+ UNINITIALIZED,
+ STARTING,
+ VOLUMES_READY
+ };
+
+ static STATE State();
+ static const char* StateStr(STATE aState);
+ static const char* StateStr() { return StateStr(State()); }
+
+ class StateChangedEvent
+ {
+ public:
+ StateChangedEvent() {}
+ };
+
+ typedef mozilla::Observer<StateChangedEvent> StateObserver;
+ typedef mozilla::ObserverList<StateChangedEvent> StateObserverList;
+
+ static void RegisterStateObserver(StateObserver* aObserver);
+ static void UnregisterStateObserver(StateObserver* aObserver);
+
+ //-----------------------------------------------------------------------
+
+ static void Start();
+ static void Dump(const char* aLabel);
+
+ static VolumeArray::size_type NumVolumes();
+ static already_AddRefed<Volume> GetVolume(VolumeArray::index_type aIndex);
+ static already_AddRefed<Volume> FindVolumeByName(const nsCSubstring& aName);
+ static already_AddRefed<Volume> FindAddVolumeByName(const nsCSubstring& aName);
+ static bool RemoveVolumeByName(const nsCSubstring& aName);
+ static void InitConfig();
+
+ static void PostCommand(VolumeCommand* aCommand);
+
+protected:
+
+ virtual void OnLineRead(int aFd, nsDependentCSubstring& aMessage);
+ virtual void OnFileCanWriteWithoutBlocking(int aFd);
+ virtual void OnError();
+
+ static void DefaultConfig();
+
+private:
+ bool OpenSocket();
+
+ friend class VolumeListCallback; // Calls SetState
+
+ static void SetState(STATE aNewState);
+
+ void Restart();
+ void WriteCommandData();
+ void HandleBroadcast(int aResponseCode, nsCString& aResponseLine);
+
+ typedef std::queue<RefPtr<VolumeCommand> > CommandQueue;
+
+ static STATE mState;
+ static StateObserverList mStateObserverList;
+
+ static const int kRcvBufSize = 1024;
+ ScopedClose mSocket;
+ VolumeArray mVolumeArray;
+ CommandQueue mCommands;
+ bool mCommandPending;
+ MessageLoopForIO::FileDescriptorWatcher mReadWatcher;
+ MessageLoopForIO::FileDescriptorWatcher mWriteWatcher;
+ RefPtr<VolumeResponseCallback> mBroadcastCallback;
+};
+
+/***************************************************************************
+*
+* The initialization/shutdown functions do not need to be called from
+* the IOThread context.
+*
+***************************************************************************/
+
+/**
+ * Initialize the Volume Manager. On initialization, the VolumeManager will
+ * attempt to connect with vold and collect the list of volumes that vold
+ * knows about.
+ */
+void InitVolumeManager();
+
+/**
+ * Shuts down the Volume Manager.
+ */
+void ShutdownVolumeManager();
+
+} // system
+} // mozilla
+
+#endif // mozilla_system_volumemanager_h__
diff --git a/dom/system/gonk/VolumeManagerLog.h b/dom/system/gonk/VolumeManagerLog.h
new file mode 100644
index 000000000..793f4889c
--- /dev/null
+++ b/dom/system/gonk/VolumeManagerLog.h
@@ -0,0 +1,27 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_system_volumemanagerlog_h__
+#define mozilla_system_volumemanagerlog_h__
+
+#undef USE_DEBUG
+#define USE_DEBUG 0
+
+#if !defined(VOLUME_MANAGER_LOG_TAG)
+#define VOLUME_MANAGER_LOG_TAG "VolumeManager"
+#endif
+
+#undef LOG
+#undef ERR
+#define LOG(args...) __android_log_print(ANDROID_LOG_INFO, VOLUME_MANAGER_LOG_TAG, ## args)
+#define ERR(args...) __android_log_print(ANDROID_LOG_ERROR, VOLUME_MANAGER_LOG_TAG, ## args)
+
+#undef DBG
+#if USE_DEBUG
+#define DBG(args...) __android_log_print(ANDROID_LOG_DEBUG, VOLUME_MANAGER_LOG_TAG, ## args)
+#else
+#define DBG(args...)
+#endif
+
+#endif // mozilla_system_volumemanagerlog_h__
diff --git a/dom/system/gonk/VolumeServiceIOThread.cpp b/dom/system/gonk/VolumeServiceIOThread.cpp
new file mode 100644
index 000000000..7eda843c0
--- /dev/null
+++ b/dom/system/gonk/VolumeServiceIOThread.cpp
@@ -0,0 +1,82 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "VolumeServiceIOThread.h"
+#include "base/message_loop.h"
+#include "nsVolumeService.h"
+#include "nsXULAppAPI.h"
+#include "Volume.h"
+#include "VolumeManager.h"
+
+namespace mozilla {
+namespace system {
+
+VolumeServiceIOThread::VolumeServiceIOThread(nsVolumeService* aVolumeService)
+ : mVolumeService(aVolumeService)
+{
+ MOZ_ASSERT(MessageLoop::current() == XRE_GetIOMessageLoop());
+
+ VolumeManager::RegisterStateObserver(this);
+ Volume::RegisterVolumeObserver(this, "VolumeServiceIOThread");
+ UpdateAllVolumes();
+}
+
+VolumeServiceIOThread::~VolumeServiceIOThread()
+{
+ MOZ_ASSERT(MessageLoop::current() == XRE_GetIOMessageLoop());
+ Volume::UnregisterVolumeObserver(this, "VolumeServiceIOThread");
+ VolumeManager::UnregisterStateObserver(this);
+}
+
+void
+VolumeServiceIOThread::Notify(Volume* const & aVolume)
+{
+ MOZ_ASSERT(MessageLoop::current() == XRE_GetIOMessageLoop());
+ if (VolumeManager::State() != VolumeManager::VOLUMES_READY) {
+ return;
+ }
+ mVolumeService->UpdateVolumeIOThread(aVolume);
+}
+
+void
+VolumeServiceIOThread::Notify(const VolumeManager::StateChangedEvent& aEvent)
+{
+ MOZ_ASSERT(MessageLoop::current() == XRE_GetIOMessageLoop());
+ UpdateAllVolumes();
+}
+
+void
+VolumeServiceIOThread::UpdateAllVolumes()
+{
+ MOZ_ASSERT(MessageLoop::current() == XRE_GetIOMessageLoop());
+ if (VolumeManager::State() != VolumeManager::VOLUMES_READY) {
+ return;
+ }
+ VolumeManager::VolumeArray::size_type numVolumes = VolumeManager::NumVolumes();
+ VolumeManager::VolumeArray::index_type volIndex;
+
+ for (volIndex = 0; volIndex < numVolumes; volIndex++) {
+ RefPtr<Volume> vol = VolumeManager::GetVolume(volIndex);
+ mVolumeService->UpdateVolumeIOThread(vol);
+ }
+}
+
+static StaticRefPtr<VolumeServiceIOThread> sVolumeServiceIOThread;
+
+void
+InitVolumeServiceIOThread(nsVolumeService* const & aVolumeService)
+{
+ MOZ_ASSERT(MessageLoop::current() == XRE_GetIOMessageLoop());
+ sVolumeServiceIOThread = new VolumeServiceIOThread(aVolumeService);
+}
+
+void
+ShutdownVolumeServiceIOThread()
+{
+ MOZ_ASSERT(MessageLoop::current() == XRE_GetIOMessageLoop());
+ sVolumeServiceIOThread = nullptr;
+}
+
+} // system
+} // mozilla
diff --git a/dom/system/gonk/VolumeServiceIOThread.h b/dom/system/gonk/VolumeServiceIOThread.h
new file mode 100644
index 000000000..0c2a6a62f
--- /dev/null
+++ b/dom/system/gonk/VolumeServiceIOThread.h
@@ -0,0 +1,49 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_system_volumeserviceiothread_h__
+#define mozilla_system_volumeserviceiothread_h__
+
+#include "Volume.h"
+#include "VolumeManager.h"
+#include "mozilla/RefPtr.h"
+
+namespace mozilla {
+namespace system {
+
+class nsVolumeService;
+
+/***************************************************************************
+* The nsVolumeServiceIOThread is a companion class to the nsVolumeService
+* class, but whose methods are called from IOThread.
+*/
+class VolumeServiceIOThread : public VolumeManager::StateObserver,
+ public Volume::EventObserver
+{
+ ~VolumeServiceIOThread();
+
+public:
+ NS_INLINE_DECL_REFCOUNTING(VolumeServiceIOThread)
+
+ VolumeServiceIOThread(nsVolumeService* aVolumeService);
+
+private:
+ void UpdateAllVolumes();
+
+ virtual void Notify(const VolumeManager::StateChangedEvent& aEvent);
+ virtual void Notify(Volume* const & aVolume);
+
+ RefPtr<nsVolumeService> mVolumeService;
+};
+
+void InitVolumeServiceIOThread(nsVolumeService* const & aVolumeService);
+void ShutdownVolumeServiceIOThread();
+void FormatVolume(const nsCString& aVolume);
+void MountVolume(const nsCString& aVolume);
+void UnmountVolume(const nsCString& aVolume);
+
+} // system
+} // mozilla
+
+#endif // mozilla_system_volumeserviceiothread_h__
diff --git a/dom/system/gonk/VolumeServiceTest.cpp b/dom/system/gonk/VolumeServiceTest.cpp
new file mode 100644
index 000000000..4082e3889
--- /dev/null
+++ b/dom/system/gonk/VolumeServiceTest.cpp
@@ -0,0 +1,202 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "VolumeServiceTest.h"
+
+#include "base/message_loop.h"
+#include "nsCOMPtr.h"
+#include "nsIObserver.h"
+#include "nsIObserverService.h"
+#include "nsServiceManagerUtils.h"
+#include "nsThreadUtils.h"
+#include "nsIVolume.h"
+#include "nsIVolumeService.h"
+#include "nsIVolumeStat.h"
+#include "nsXULAppAPI.h"
+
+#include "mozilla/Services.h"
+
+#undef VOLUME_MANAGER_LOG_TAG
+#define VOLUME_MANAGER_LOG_TAG "VolumeServiceTest"
+#include "VolumeManagerLog.h"
+
+using namespace mozilla::services;
+
+namespace mozilla {
+namespace system {
+
+#define TEST_NSVOLUME_OBSERVER 0
+
+#if TEST_NSVOLUME_OBSERVER
+
+/***************************************************************************
+* A test class to verify that the Observer stuff is working properly.
+*/
+class VolumeTestObserver : public nsIObserver
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIOBSERVER
+
+ VolumeTestObserver()
+ {
+ nsCOMPtr<nsIObserverService> obs = GetObserverService();
+ if (!obs) {
+ return;
+ }
+ obs->AddObserver(this, NS_VOLUME_STATE_CHANGED, false);
+ }
+ ~VolumeTestObserver()
+ {
+ nsCOMPtr<nsIObserverService> obs = GetObserverService();
+ if (!obs) {
+ return;
+ }
+ obs->RemoveObserver(this, NS_VOLUME_STATE_CHANGED);
+ }
+
+ void LogVolume(nsIVolume* vol)
+ {
+ nsString volName;
+ nsString mountPoint;
+ int32_t volState;
+
+ vol->GetName(volName);
+ vol->GetMountPoint(mountPoint);
+ vol->GetState(&volState);
+
+ LOG(" Volume: %s MountPoint: %s State: %s",
+ NS_LossyConvertUTF16toASCII(volName).get(),
+ NS_LossyConvertUTF16toASCII(mountPoint).get(),
+ NS_VolumeStateStr(volState));
+
+ nsCOMPtr<nsIVolumeStat> stat;
+ nsresult rv = vol->GetStats(getter_AddRefs(stat));
+ if (NS_SUCCEEDED(rv)) {
+ int64_t totalBytes;
+ int64_t freeBytes;
+
+ stat->GetTotalBytes(&totalBytes);
+ stat->GetFreeBytes(&freeBytes);
+
+ LOG(" Total Space: %llu Mb Free Bytes: %llu Mb",
+ totalBytes / (1024LL * 1024LL), freeBytes / (1024LL * 1024LL));
+ }
+ else {
+ LOG(" Unable to retrieve stats");
+ }
+ }
+};
+static nsCOMPtr<VolumeTestObserver> sTestObserver;
+
+NS_IMPL_ISUPPORTS(VolumeTestObserver, nsIObserver)
+
+NS_IMETHODIMP
+VolumeTestObserver::Observe(nsISupports* aSubject,
+ const char* aTopic,
+ const char16_t* aData)
+{
+ LOG("TestObserver: topic: %s", aTopic);
+
+ if (strcmp(aTopic, NS_VOLUME_STATE_CHANGED) != 0) {
+ return NS_OK;
+ }
+ nsCOMPtr<nsIVolume> vol = do_QueryInterface(aSubject);
+ if (vol) {
+ LogVolume(vol);
+ }
+
+ // Since this observe method was called then we know that the service
+ // has been initialized so we can do the VolumeService tests.
+
+ nsCOMPtr<nsIVolumeService> vs = do_GetService(NS_VOLUMESERVICE_CONTRACTID);
+ if (!vs) {
+ ERR("do_GetService('%s') failed", NS_VOLUMESERVICE_CONTRACTID);
+ return NS_ERROR_FAILURE;
+ }
+
+ nsresult rv = vs->GetVolumeByName(NS_LITERAL_STRING("sdcard"), getter_AddRefs(vol));
+ if (NS_SUCCEEDED(rv)) {
+ LOG("GetVolumeByName( 'sdcard' ) succeeded (expected)");
+ LogVolume(vol);
+ } else {
+ ERR("GetVolumeByName( 'sdcard' ) failed (unexpected)");
+ }
+
+ rv = vs->GetVolumeByName(NS_LITERAL_STRING("foo"), getter_AddRefs(vol));
+ if (NS_SUCCEEDED(rv)) {
+ ERR("GetVolumeByName( 'foo' ) succeeded (unexpected)");
+ } else {
+ LOG("GetVolumeByName( 'foo' ) failed (expected)");
+ }
+
+ rv = vs->GetVolumeByPath(NS_LITERAL_STRING("/mnt/sdcard"), getter_AddRefs(vol));
+ if (NS_SUCCEEDED(rv)) {
+ LOG("GetVolumeByPath( '/mnt/sdcard' ) succeeded (expected)");
+ LogVolume(vol);
+ } else {
+ ERR("GetVolumeByPath( '/mnt/sdcard' ) failed (unexpected");
+ }
+
+ rv = vs->GetVolumeByPath(NS_LITERAL_STRING("/mnt/sdcard/foo"), getter_AddRefs(vol));
+ if (NS_SUCCEEDED(rv)) {
+ LOG("GetVolumeByPath( '/mnt/sdcard/foo' ) succeeded (expected)");
+ LogVolume(vol);
+ } else {
+ LOG("GetVolumeByPath( '/mnt/sdcard/foo' ) failed (unexpected)");
+ }
+
+ rv = vs->GetVolumeByPath(NS_LITERAL_STRING("/mnt/sdcardfoo"), getter_AddRefs(vol));
+ if (NS_SUCCEEDED(rv)) {
+ ERR("GetVolumeByPath( '/mnt/sdcardfoo' ) succeeded (unexpected)");
+ } else {
+ LOG("GetVolumeByPath( '/mnt/sdcardfoo' ) failed (expected)");
+ }
+
+ return NS_OK;
+}
+
+class InitVolumeServiceTestIO : public Runnable
+{
+public:
+ NS_IMETHOD Run() override
+ {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ DBG("InitVolumeServiceTest called");
+ nsCOMPtr<nsIVolumeService> vs = do_GetService(NS_VOLUMESERVICE_CONTRACTID);
+ if (!vs) {
+ ERR("do_GetService('%s') failed", NS_VOLUMESERVICE_CONTRACTID);
+ return NS_ERROR_FAILURE;
+ }
+ sTestObserver = new VolumeTestObserver();
+
+ return NS_OK;
+ }
+};
+#endif // TEST_NSVOLUME_OBSERVER
+
+void
+InitVolumeServiceTestIOThread()
+{
+ MOZ_ASSERT(MessageLoop::current() == XRE_GetIOMessageLoop());
+
+#if TEST_NSVOLUME_OBSERVER
+ // Now that the volume manager is initialized we can go
+ // ahead and do our test (on main thread).
+ NS_DispatchToMainThread(new InitVolumeServiceTestIO());
+#endif
+}
+
+void
+ShutdownVolumeServiceTest()
+{
+#if TEST_NSVOLUME_OBSERVER
+ DBG("ShutdownVolumeServiceTestIOThread called");
+ sTestObserver = nullptr;
+#endif
+}
+
+} // system
+} // mozilla
diff --git a/dom/system/gonk/VolumeServiceTest.h b/dom/system/gonk/VolumeServiceTest.h
new file mode 100644
index 000000000..71a92bf6c
--- /dev/null
+++ b/dom/system/gonk/VolumeServiceTest.h
@@ -0,0 +1,19 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_system_volumeservicetest_h__
+#define mozilla_system_volumeservicetest_h__
+
+
+namespace mozilla {
+namespace system {
+
+void InitVolumeServiceTestIOThread();
+void ShutdownVolumeServiceTest();
+
+} // system
+} // mozilla
+
+#endif // mozilla_system_volumeservicetest_h__
+
diff --git a/dom/system/gonk/android_audio/AudioSystem.h b/dom/system/gonk/android_audio/AudioSystem.h
new file mode 100644
index 000000000..d5841eaaa
--- /dev/null
+++ b/dom/system/gonk/android_audio/AudioSystem.h
@@ -0,0 +1,1134 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_AUDIOSYSTEM_H_
+#define ANDROID_AUDIOSYSTEM_H_
+
+#pragma GCC visibility push(default)
+
+#include <utils/RefBase.h>
+#include <utils/threads.h>
+#include "IAudioFlinger.h"
+
+#ifndef VANILLA_ANDROID
+/* request to open a direct output with get_output() (by opposition to
+ * sharing an output with other AudioTracks)
+ */
+typedef enum {
+ AUDIO_POLICY_OUTPUT_FLAG_INDIRECT = 0x0,
+ AUDIO_POLICY_OUTPUT_FLAG_DIRECT = 0x1
+} audio_policy_output_flags_t;
+
+/* device categories used for audio_policy->set_force_use() */
+typedef enum {
+ AUDIO_POLICY_FORCE_NONE,
+ AUDIO_POLICY_FORCE_SPEAKER,
+ AUDIO_POLICY_FORCE_HEADPHONES,
+ AUDIO_POLICY_FORCE_BT_SCO,
+ AUDIO_POLICY_FORCE_BT_A2DP,
+ AUDIO_POLICY_FORCE_WIRED_ACCESSORY,
+ AUDIO_POLICY_FORCE_BT_CAR_DOCK,
+ AUDIO_POLICY_FORCE_BT_DESK_DOCK,
+ AUDIO_POLICY_FORCE_ANALOG_DOCK,
+ AUDIO_POLICY_FORCE_DIGITAL_DOCK,
+ AUDIO_POLICY_FORCE_NO_BT_A2DP,
+ AUDIO_POLICY_FORCE_CFG_CNT,
+ AUDIO_POLICY_FORCE_CFG_MAX = AUDIO_POLICY_FORCE_CFG_CNT - 1,
+
+ AUDIO_POLICY_FORCE_DEFAULT = AUDIO_POLICY_FORCE_NONE,
+} audio_policy_forced_cfg_t;
+
+/* usages used for audio_policy->set_force_use() */
+typedef enum {
+ AUDIO_POLICY_FORCE_FOR_COMMUNICATION,
+ AUDIO_POLICY_FORCE_FOR_MEDIA,
+ AUDIO_POLICY_FORCE_FOR_RECORD,
+ AUDIO_POLICY_FORCE_FOR_DOCK,
+
+ AUDIO_POLICY_FORCE_USE_CNT,
+ AUDIO_POLICY_FORCE_USE_MAX = AUDIO_POLICY_FORCE_USE_CNT - 1,
+} audio_policy_force_use_t;
+
+typedef enum {
+ AUDIO_STREAM_DEFAULT = -1,
+ AUDIO_STREAM_VOICE_CALL = 0,
+ AUDIO_STREAM_SYSTEM = 1,
+ AUDIO_STREAM_RING = 2,
+ AUDIO_STREAM_MUSIC = 3,
+ AUDIO_STREAM_ALARM = 4,
+ AUDIO_STREAM_NOTIFICATION = 5,
+ AUDIO_STREAM_BLUETOOTH_SCO = 6,
+ AUDIO_STREAM_ENFORCED_AUDIBLE = 7, /* Sounds that cannot be muted by user and must be routed to speaker */
+ AUDIO_STREAM_DTMF = 8,
+ AUDIO_STREAM_TTS = 9,
+#if ANDROID_VERSION < 19
+ AUDIO_STREAM_FM = 10,
+#endif
+
+ AUDIO_STREAM_CNT,
+ AUDIO_STREAM_MAX = AUDIO_STREAM_CNT - 1,
+} audio_stream_type_t;
+
+/* PCM sub formats */
+typedef enum {
+ AUDIO_FORMAT_PCM_SUB_16_BIT = 0x1, /* DO NOT CHANGE - PCM signed 16 bits */
+ AUDIO_FORMAT_PCM_SUB_8_BIT = 0x2, /* DO NOT CHANGE - PCM unsigned 8 bits */
+ AUDIO_FORMAT_PCM_SUB_32_BIT = 0x3, /* PCM signed .31 fixed point */
+ AUDIO_FORMAT_PCM_SUB_8_24_BIT = 0x4, /* PCM signed 7.24 fixed point */
+} audio_format_pcm_sub_fmt_t;
+
+/* Audio format consists in a main format field (upper 8 bits) and a sub format
+ * field (lower 24 bits).
+ *
+ * The main format indicates the main codec type. The sub format field
+ * indicates options and parameters for each format. The sub format is mainly
+ * used for record to indicate for instance the requested bitrate or profile.
+ * It can also be used for certain formats to give informations not present in
+ * the encoded audio stream (e.g. octet alignement for AMR).
+ */
+typedef enum {
+ AUDIO_FORMAT_INVALID = 0xFFFFFFFFUL,
+ AUDIO_FORMAT_DEFAULT = 0,
+ AUDIO_FORMAT_PCM = 0x00000000UL, /* DO NOT CHANGE */
+ AUDIO_FORMAT_MP3 = 0x01000000UL,
+ AUDIO_FORMAT_AMR_NB = 0x02000000UL,
+ AUDIO_FORMAT_AMR_WB = 0x03000000UL,
+ AUDIO_FORMAT_AAC = 0x04000000UL,
+ AUDIO_FORMAT_HE_AAC_V1 = 0x05000000UL,
+ AUDIO_FORMAT_HE_AAC_V2 = 0x06000000UL,
+ AUDIO_FORMAT_VORBIS = 0x07000000UL,
+ AUDIO_FORMAT_MAIN_MASK = 0xFF000000UL,
+ AUDIO_FORMAT_SUB_MASK = 0x00FFFFFFUL,
+
+ /* Aliases */
+ AUDIO_FORMAT_PCM_16_BIT = (AUDIO_FORMAT_PCM |
+ AUDIO_FORMAT_PCM_SUB_16_BIT),
+ AUDIO_FORMAT_PCM_8_BIT = (AUDIO_FORMAT_PCM |
+ AUDIO_FORMAT_PCM_SUB_8_BIT),
+ AUDIO_FORMAT_PCM_32_BIT = (AUDIO_FORMAT_PCM |
+ AUDIO_FORMAT_PCM_SUB_32_BIT),
+ AUDIO_FORMAT_PCM_8_24_BIT = (AUDIO_FORMAT_PCM |
+ AUDIO_FORMAT_PCM_SUB_8_24_BIT),
+} audio_format_t;
+
+typedef enum {
+ /* output channels */
+ AUDIO_CHANNEL_OUT_FRONT_LEFT = 0x1,
+ AUDIO_CHANNEL_OUT_FRONT_RIGHT = 0x2,
+ AUDIO_CHANNEL_OUT_FRONT_CENTER = 0x4,
+ AUDIO_CHANNEL_OUT_LOW_FREQUENCY = 0x8,
+ AUDIO_CHANNEL_OUT_BACK_LEFT = 0x10,
+ AUDIO_CHANNEL_OUT_BACK_RIGHT = 0x20,
+ AUDIO_CHANNEL_OUT_FRONT_LEFT_OF_CENTER = 0x40,
+ AUDIO_CHANNEL_OUT_FRONT_RIGHT_OF_CENTER = 0x80,
+ AUDIO_CHANNEL_OUT_BACK_CENTER = 0x100,
+ AUDIO_CHANNEL_OUT_SIDE_LEFT = 0x200,
+ AUDIO_CHANNEL_OUT_SIDE_RIGHT = 0x400,
+ AUDIO_CHANNEL_OUT_TOP_CENTER = 0x800,
+ AUDIO_CHANNEL_OUT_TOP_FRONT_LEFT = 0x1000,
+ AUDIO_CHANNEL_OUT_TOP_FRONT_CENTER = 0x2000,
+ AUDIO_CHANNEL_OUT_TOP_FRONT_RIGHT = 0x4000,
+ AUDIO_CHANNEL_OUT_TOP_BACK_LEFT = 0x8000,
+ AUDIO_CHANNEL_OUT_TOP_BACK_CENTER = 0x10000,
+ AUDIO_CHANNEL_OUT_TOP_BACK_RIGHT = 0x20000,
+
+ AUDIO_CHANNEL_OUT_MONO = AUDIO_CHANNEL_OUT_FRONT_LEFT,
+ AUDIO_CHANNEL_OUT_STEREO = (AUDIO_CHANNEL_OUT_FRONT_LEFT |
+ AUDIO_CHANNEL_OUT_FRONT_RIGHT),
+ AUDIO_CHANNEL_OUT_QUAD = (AUDIO_CHANNEL_OUT_FRONT_LEFT |
+ AUDIO_CHANNEL_OUT_FRONT_RIGHT |
+ AUDIO_CHANNEL_OUT_BACK_LEFT |
+ AUDIO_CHANNEL_OUT_BACK_RIGHT),
+ AUDIO_CHANNEL_OUT_SURROUND = (AUDIO_CHANNEL_OUT_FRONT_LEFT |
+ AUDIO_CHANNEL_OUT_FRONT_RIGHT |
+ AUDIO_CHANNEL_OUT_FRONT_CENTER |
+ AUDIO_CHANNEL_OUT_BACK_CENTER),
+ AUDIO_CHANNEL_OUT_5POINT1 = (AUDIO_CHANNEL_OUT_FRONT_LEFT |
+ AUDIO_CHANNEL_OUT_FRONT_RIGHT |
+ AUDIO_CHANNEL_OUT_FRONT_CENTER |
+ AUDIO_CHANNEL_OUT_LOW_FREQUENCY |
+ AUDIO_CHANNEL_OUT_BACK_LEFT |
+ AUDIO_CHANNEL_OUT_BACK_RIGHT),
+ // matches the correct AudioFormat.CHANNEL_OUT_7POINT1_SURROUND definition for 7.1
+ AUDIO_CHANNEL_OUT_7POINT1 = (AUDIO_CHANNEL_OUT_FRONT_LEFT |
+ AUDIO_CHANNEL_OUT_FRONT_RIGHT |
+ AUDIO_CHANNEL_OUT_FRONT_CENTER |
+ AUDIO_CHANNEL_OUT_LOW_FREQUENCY |
+ AUDIO_CHANNEL_OUT_BACK_LEFT |
+ AUDIO_CHANNEL_OUT_BACK_RIGHT |
+ AUDIO_CHANNEL_OUT_SIDE_LEFT |
+ AUDIO_CHANNEL_OUT_SIDE_RIGHT),
+ AUDIO_CHANNEL_OUT_ALL = (AUDIO_CHANNEL_OUT_FRONT_LEFT |
+ AUDIO_CHANNEL_OUT_FRONT_RIGHT |
+ AUDIO_CHANNEL_OUT_FRONT_CENTER |
+ AUDIO_CHANNEL_OUT_LOW_FREQUENCY |
+ AUDIO_CHANNEL_OUT_BACK_LEFT |
+ AUDIO_CHANNEL_OUT_BACK_RIGHT |
+ AUDIO_CHANNEL_OUT_FRONT_LEFT_OF_CENTER |
+ AUDIO_CHANNEL_OUT_FRONT_RIGHT_OF_CENTER |
+ AUDIO_CHANNEL_OUT_BACK_CENTER|
+ AUDIO_CHANNEL_OUT_SIDE_LEFT|
+ AUDIO_CHANNEL_OUT_SIDE_RIGHT|
+ AUDIO_CHANNEL_OUT_TOP_CENTER|
+ AUDIO_CHANNEL_OUT_TOP_FRONT_LEFT|
+ AUDIO_CHANNEL_OUT_TOP_FRONT_CENTER|
+ AUDIO_CHANNEL_OUT_TOP_FRONT_RIGHT|
+ AUDIO_CHANNEL_OUT_TOP_BACK_LEFT|
+ AUDIO_CHANNEL_OUT_TOP_BACK_CENTER|
+ AUDIO_CHANNEL_OUT_TOP_BACK_RIGHT),
+
+ /* input channels */
+ AUDIO_CHANNEL_IN_LEFT = 0x4,
+ AUDIO_CHANNEL_IN_RIGHT = 0x8,
+ AUDIO_CHANNEL_IN_FRONT = 0x10,
+ AUDIO_CHANNEL_IN_BACK = 0x20,
+ AUDIO_CHANNEL_IN_LEFT_PROCESSED = 0x40,
+ AUDIO_CHANNEL_IN_RIGHT_PROCESSED = 0x80,
+ AUDIO_CHANNEL_IN_FRONT_PROCESSED = 0x100,
+ AUDIO_CHANNEL_IN_BACK_PROCESSED = 0x200,
+ AUDIO_CHANNEL_IN_PRESSURE = 0x400,
+ AUDIO_CHANNEL_IN_X_AXIS = 0x800,
+ AUDIO_CHANNEL_IN_Y_AXIS = 0x1000,
+ AUDIO_CHANNEL_IN_Z_AXIS = 0x2000,
+ AUDIO_CHANNEL_IN_VOICE_UPLINK = 0x4000,
+ AUDIO_CHANNEL_IN_VOICE_DNLINK = 0x8000,
+
+ AUDIO_CHANNEL_IN_MONO = AUDIO_CHANNEL_IN_FRONT,
+ AUDIO_CHANNEL_IN_STEREO = (AUDIO_CHANNEL_IN_LEFT | AUDIO_CHANNEL_IN_RIGHT),
+ AUDIO_CHANNEL_IN_ALL = (AUDIO_CHANNEL_IN_LEFT |
+ AUDIO_CHANNEL_IN_RIGHT |
+ AUDIO_CHANNEL_IN_FRONT |
+ AUDIO_CHANNEL_IN_BACK|
+ AUDIO_CHANNEL_IN_LEFT_PROCESSED |
+ AUDIO_CHANNEL_IN_RIGHT_PROCESSED |
+ AUDIO_CHANNEL_IN_FRONT_PROCESSED |
+ AUDIO_CHANNEL_IN_BACK_PROCESSED|
+ AUDIO_CHANNEL_IN_PRESSURE |
+ AUDIO_CHANNEL_IN_X_AXIS |
+ AUDIO_CHANNEL_IN_Y_AXIS |
+ AUDIO_CHANNEL_IN_Z_AXIS |
+ AUDIO_CHANNEL_IN_VOICE_UPLINK |
+ AUDIO_CHANNEL_IN_VOICE_DNLINK),
+} audio_channels_t;
+
+#if ANDROID_VERSION >= 17
+typedef enum {
+ AUDIO_MODE_INVALID = -2,
+ AUDIO_MODE_CURRENT = -1,
+ AUDIO_MODE_NORMAL = 0,
+ AUDIO_MODE_RINGTONE = 1,
+ AUDIO_MODE_IN_CALL = 2,
+ AUDIO_MODE_IN_COMMUNICATION = 3,
+
+ AUDIO_MODE_CNT,
+ AUDIO_MODE_MAX = AUDIO_MODE_CNT - 1,
+} audio_mode_t;
+#endif
+#endif
+
+#if ANDROID_VERSION < 17
+typedef enum {
+ AUDIO_DEVICE_NONE = 0x0,
+ /* output devices */
+ AUDIO_DEVICE_OUT_EARPIECE = 0x1,
+ AUDIO_DEVICE_OUT_SPEAKER = 0x2,
+ AUDIO_DEVICE_OUT_WIRED_HEADSET = 0x4,
+ AUDIO_DEVICE_OUT_WIRED_HEADPHONE = 0x8,
+ AUDIO_DEVICE_OUT_BLUETOOTH_SCO = 0x10,
+ AUDIO_DEVICE_OUT_BLUETOOTH_SCO_HEADSET = 0x20,
+ AUDIO_DEVICE_OUT_BLUETOOTH_SCO_CARKIT = 0x40,
+ AUDIO_DEVICE_OUT_BLUETOOTH_A2DP = 0x80,
+ AUDIO_DEVICE_OUT_BLUETOOTH_A2DP_HEADPHONES = 0x100,
+ AUDIO_DEVICE_OUT_BLUETOOTH_A2DP_SPEAKER = 0x200,
+ AUDIO_DEVICE_OUT_AUX_DIGITAL = 0x400,
+ AUDIO_DEVICE_OUT_ANLG_DOCK_HEADSET = 0x800,
+ AUDIO_DEVICE_OUT_DGTL_DOCK_HEADSET = 0x1000,
+ AUDIO_DEVICE_OUT_FM = 0x2000,
+ AUDIO_DEVICE_OUT_ANC_HEADSET = 0x4000,
+ AUDIO_DEVICE_OUT_ANC_HEADPHONE = 0x8000,
+ AUDIO_DEVICE_OUT_FM_TX = 0x10000,
+ AUDIO_DEVICE_OUT_DIRECTOUTPUT = 0x20000,
+ AUDIO_DEVICE_OUT_PROXY = 0x40000,
+ AUDIO_DEVICE_OUT_DEFAULT = 0x80000,
+ AUDIO_DEVICE_OUT_ALL = (AUDIO_DEVICE_OUT_EARPIECE |
+ AUDIO_DEVICE_OUT_SPEAKER |
+ AUDIO_DEVICE_OUT_WIRED_HEADSET |
+ AUDIO_DEVICE_OUT_WIRED_HEADPHONE |
+ AUDIO_DEVICE_OUT_BLUETOOTH_SCO |
+ AUDIO_DEVICE_OUT_BLUETOOTH_SCO_HEADSET |
+ AUDIO_DEVICE_OUT_BLUETOOTH_SCO_CARKIT |
+ AUDIO_DEVICE_OUT_BLUETOOTH_A2DP |
+ AUDIO_DEVICE_OUT_BLUETOOTH_A2DP_HEADPHONES |
+ AUDIO_DEVICE_OUT_BLUETOOTH_A2DP_SPEAKER |
+ AUDIO_DEVICE_OUT_AUX_DIGITAL |
+ AUDIO_DEVICE_OUT_ANLG_DOCK_HEADSET |
+ AUDIO_DEVICE_OUT_DGTL_DOCK_HEADSET |
+ AUDIO_DEVICE_OUT_FM |
+ AUDIO_DEVICE_OUT_ANC_HEADSET |
+ AUDIO_DEVICE_OUT_ANC_HEADPHONE |
+ AUDIO_DEVICE_OUT_FM_TX |
+ AUDIO_DEVICE_OUT_DIRECTOUTPUT |
+ AUDIO_DEVICE_OUT_PROXY |
+ AUDIO_DEVICE_OUT_DEFAULT),
+ AUDIO_DEVICE_OUT_ALL_A2DP = (AUDIO_DEVICE_OUT_BLUETOOTH_A2DP |
+ AUDIO_DEVICE_OUT_BLUETOOTH_A2DP_HEADPHONES |
+ AUDIO_DEVICE_OUT_BLUETOOTH_A2DP_SPEAKER),
+ AUDIO_DEVICE_OUT_ALL_SCO = (AUDIO_DEVICE_OUT_BLUETOOTH_SCO |
+ AUDIO_DEVICE_OUT_BLUETOOTH_SCO_HEADSET |
+ AUDIO_DEVICE_OUT_BLUETOOTH_SCO_CARKIT),
+ /* input devices */
+ AUDIO_DEVICE_IN_COMMUNICATION = 0x100000,
+ AUDIO_DEVICE_IN_AMBIENT = 0x200000,
+ AUDIO_DEVICE_IN_BUILTIN_MIC = 0x400000,
+ AUDIO_DEVICE_IN_BLUETOOTH_SCO_HEADSET = 0x800000,
+ AUDIO_DEVICE_IN_WIRED_HEADSET = 0x1000000,
+ AUDIO_DEVICE_IN_AUX_DIGITAL = 0x2000000,
+ AUDIO_DEVICE_IN_VOICE_CALL = 0x4000000,
+ AUDIO_DEVICE_IN_BACK_MIC = 0x8000000,
+ AUDIO_DEVICE_IN_ANC_HEADSET = 0x10000000,
+ AUDIO_DEVICE_IN_FM_RX = 0x20000000,
+ AUDIO_DEVICE_IN_FM_RX_A2DP = 0x40000000,
+ AUDIO_DEVICE_IN_DEFAULT = 0x80000000,
+
+ AUDIO_DEVICE_IN_ALL = (AUDIO_DEVICE_IN_COMMUNICATION |
+ AUDIO_DEVICE_IN_AMBIENT |
+ AUDIO_DEVICE_IN_BUILTIN_MIC |
+ AUDIO_DEVICE_IN_BLUETOOTH_SCO_HEADSET |
+ AUDIO_DEVICE_IN_WIRED_HEADSET |
+ AUDIO_DEVICE_IN_AUX_DIGITAL |
+ AUDIO_DEVICE_IN_VOICE_CALL |
+ AUDIO_DEVICE_IN_BACK_MIC |
+ AUDIO_DEVICE_IN_ANC_HEADSET |
+ AUDIO_DEVICE_IN_FM_RX |
+ AUDIO_DEVICE_IN_FM_RX_A2DP |
+ AUDIO_DEVICE_IN_DEFAULT),
+ AUDIO_DEVICE_IN_ALL_SCO = AUDIO_DEVICE_IN_BLUETOOTH_SCO_HEADSET,
+} audio_devices_t;
+#elif ANDROID_VERSION < 21
+enum {
+ AUDIO_DEVICE_NONE = 0x0,
+ /* reserved bits */
+ AUDIO_DEVICE_BIT_IN = 0x80000000,
+ AUDIO_DEVICE_BIT_DEFAULT = 0x40000000,
+ /* output devices */
+ AUDIO_DEVICE_OUT_EARPIECE = 0x1,
+ AUDIO_DEVICE_OUT_SPEAKER = 0x2,
+ AUDIO_DEVICE_OUT_WIRED_HEADSET = 0x4,
+ AUDIO_DEVICE_OUT_WIRED_HEADPHONE = 0x8,
+ AUDIO_DEVICE_OUT_BLUETOOTH_SCO = 0x10,
+ AUDIO_DEVICE_OUT_BLUETOOTH_SCO_HEADSET = 0x20,
+ AUDIO_DEVICE_OUT_BLUETOOTH_SCO_CARKIT = 0x40,
+ AUDIO_DEVICE_OUT_BLUETOOTH_A2DP = 0x80,
+ AUDIO_DEVICE_OUT_BLUETOOTH_A2DP_HEADPHONES = 0x100,
+ AUDIO_DEVICE_OUT_BLUETOOTH_A2DP_SPEAKER = 0x200,
+ AUDIO_DEVICE_OUT_AUX_DIGITAL = 0x400,
+ AUDIO_DEVICE_OUT_ANLG_DOCK_HEADSET = 0x800,
+ AUDIO_DEVICE_OUT_DGTL_DOCK_HEADSET = 0x1000,
+ AUDIO_DEVICE_OUT_USB_ACCESSORY = 0x2000,
+ AUDIO_DEVICE_OUT_USB_DEVICE = 0x4000,
+ AUDIO_DEVICE_OUT_REMOTE_SUBMIX = 0x8000,
+ AUDIO_DEVICE_OUT_ANC_HEADSET = 0x10000,
+ AUDIO_DEVICE_OUT_ANC_HEADPHONE = 0x20000,
+ AUDIO_DEVICE_OUT_PROXY = 0x40000,
+ AUDIO_DEVICE_OUT_FM = 0x80000,
+ AUDIO_DEVICE_OUT_FM_TX = 0x100000,
+ AUDIO_DEVICE_OUT_DEFAULT = AUDIO_DEVICE_BIT_DEFAULT,
+ AUDIO_DEVICE_OUT_ALL = (AUDIO_DEVICE_OUT_EARPIECE |
+ AUDIO_DEVICE_OUT_SPEAKER |
+ AUDIO_DEVICE_OUT_WIRED_HEADSET |
+ AUDIO_DEVICE_OUT_WIRED_HEADPHONE |
+ AUDIO_DEVICE_OUT_BLUETOOTH_SCO |
+ AUDIO_DEVICE_OUT_BLUETOOTH_SCO_HEADSET |
+ AUDIO_DEVICE_OUT_BLUETOOTH_SCO_CARKIT |
+ AUDIO_DEVICE_OUT_BLUETOOTH_A2DP |
+ AUDIO_DEVICE_OUT_BLUETOOTH_A2DP_HEADPHONES |
+ AUDIO_DEVICE_OUT_BLUETOOTH_A2DP_SPEAKER |
+ AUDIO_DEVICE_OUT_AUX_DIGITAL |
+ AUDIO_DEVICE_OUT_ANLG_DOCK_HEADSET |
+ AUDIO_DEVICE_OUT_DGTL_DOCK_HEADSET |
+ AUDIO_DEVICE_OUT_USB_ACCESSORY |
+ AUDIO_DEVICE_OUT_USB_DEVICE |
+ AUDIO_DEVICE_OUT_REMOTE_SUBMIX |
+ AUDIO_DEVICE_OUT_ANC_HEADSET |
+ AUDIO_DEVICE_OUT_ANC_HEADPHONE |
+ AUDIO_DEVICE_OUT_PROXY |
+ AUDIO_DEVICE_OUT_FM |
+ AUDIO_DEVICE_OUT_FM_TX |
+ AUDIO_DEVICE_OUT_DEFAULT),
+ AUDIO_DEVICE_OUT_ALL_A2DP = (AUDIO_DEVICE_OUT_BLUETOOTH_A2DP |
+ AUDIO_DEVICE_OUT_BLUETOOTH_A2DP_HEADPHONES |
+ AUDIO_DEVICE_OUT_BLUETOOTH_A2DP_SPEAKER),
+ AUDIO_DEVICE_OUT_ALL_SCO = (AUDIO_DEVICE_OUT_BLUETOOTH_SCO |
+ AUDIO_DEVICE_OUT_BLUETOOTH_SCO_HEADSET |
+ AUDIO_DEVICE_OUT_BLUETOOTH_SCO_CARKIT),
+ AUDIO_DEVICE_OUT_ALL_USB = (AUDIO_DEVICE_OUT_USB_ACCESSORY |
+ AUDIO_DEVICE_OUT_USB_DEVICE),
+
+ /* input devices */
+ AUDIO_DEVICE_IN_COMMUNICATION = AUDIO_DEVICE_BIT_IN | 0x1,
+ AUDIO_DEVICE_IN_AMBIENT = AUDIO_DEVICE_BIT_IN | 0x2,
+ AUDIO_DEVICE_IN_BUILTIN_MIC = AUDIO_DEVICE_BIT_IN | 0x4,
+ AUDIO_DEVICE_IN_BLUETOOTH_SCO_HEADSET = AUDIO_DEVICE_BIT_IN | 0x8,
+ AUDIO_DEVICE_IN_WIRED_HEADSET = AUDIO_DEVICE_BIT_IN | 0x10,
+ AUDIO_DEVICE_IN_AUX_DIGITAL = AUDIO_DEVICE_BIT_IN | 0x20,
+ AUDIO_DEVICE_IN_VOICE_CALL = AUDIO_DEVICE_BIT_IN | 0x40,
+ AUDIO_DEVICE_IN_BACK_MIC = AUDIO_DEVICE_BIT_IN | 0x80,
+ AUDIO_DEVICE_IN_REMOTE_SUBMIX = AUDIO_DEVICE_BIT_IN | 0x100,
+ AUDIO_DEVICE_IN_ANLG_DOCK_HEADSET = AUDIO_DEVICE_BIT_IN | 0x200,
+ AUDIO_DEVICE_IN_DGTL_DOCK_HEADSET = AUDIO_DEVICE_BIT_IN | 0x400,
+ AUDIO_DEVICE_IN_USB_ACCESSORY = AUDIO_DEVICE_BIT_IN | 0x800,
+ AUDIO_DEVICE_IN_USB_DEVICE = AUDIO_DEVICE_BIT_IN | 0x1000,
+ AUDIO_DEVICE_IN_ANC_HEADSET = AUDIO_DEVICE_BIT_IN | 0x2000,
+ AUDIO_DEVICE_IN_PROXY = AUDIO_DEVICE_BIT_IN | 0x4000,
+ AUDIO_DEVICE_IN_FM_RX = AUDIO_DEVICE_BIT_IN | 0x8000,
+ AUDIO_DEVICE_IN_FM_RX_A2DP = AUDIO_DEVICE_BIT_IN | 0x10000,
+ AUDIO_DEVICE_IN_DEFAULT = AUDIO_DEVICE_BIT_IN | AUDIO_DEVICE_BIT_DEFAULT,
+
+ AUDIO_DEVICE_IN_ALL = (AUDIO_DEVICE_IN_COMMUNICATION |
+ AUDIO_DEVICE_IN_AMBIENT |
+ AUDIO_DEVICE_IN_BUILTIN_MIC |
+ AUDIO_DEVICE_IN_BLUETOOTH_SCO_HEADSET |
+ AUDIO_DEVICE_IN_WIRED_HEADSET |
+ AUDIO_DEVICE_IN_AUX_DIGITAL |
+ AUDIO_DEVICE_IN_VOICE_CALL |
+ AUDIO_DEVICE_IN_BACK_MIC |
+ AUDIO_DEVICE_IN_REMOTE_SUBMIX |
+ AUDIO_DEVICE_IN_ANLG_DOCK_HEADSET |
+ AUDIO_DEVICE_IN_DGTL_DOCK_HEADSET |
+ AUDIO_DEVICE_IN_USB_ACCESSORY |
+ AUDIO_DEVICE_IN_USB_DEVICE |
+ AUDIO_DEVICE_IN_ANC_HEADSET |
+ AUDIO_DEVICE_IN_FM_RX |
+ AUDIO_DEVICE_IN_FM_RX_A2DP |
+ AUDIO_DEVICE_IN_PROXY |
+ AUDIO_DEVICE_IN_DEFAULT),
+ AUDIO_DEVICE_IN_ALL_SCO = AUDIO_DEVICE_IN_BLUETOOTH_SCO_HEADSET,
+};
+
+typedef uint32_t audio_devices_t;
+#else
+enum {
+ AUDIO_DEVICE_NONE = 0x0,
+ /* reserved bits */
+ AUDIO_DEVICE_BIT_IN = 0x80000000,
+ AUDIO_DEVICE_BIT_DEFAULT = 0x40000000,
+ /* output devices */
+ AUDIO_DEVICE_OUT_EARPIECE = 0x1,
+ AUDIO_DEVICE_OUT_SPEAKER = 0x2,
+ AUDIO_DEVICE_OUT_WIRED_HEADSET = 0x4,
+ AUDIO_DEVICE_OUT_WIRED_HEADPHONE = 0x8,
+ AUDIO_DEVICE_OUT_BLUETOOTH_SCO = 0x10,
+ AUDIO_DEVICE_OUT_BLUETOOTH_SCO_HEADSET = 0x20,
+ AUDIO_DEVICE_OUT_BLUETOOTH_SCO_CARKIT = 0x40,
+ AUDIO_DEVICE_OUT_BLUETOOTH_A2DP = 0x80,
+ AUDIO_DEVICE_OUT_BLUETOOTH_A2DP_HEADPHONES = 0x100,
+ AUDIO_DEVICE_OUT_BLUETOOTH_A2DP_SPEAKER = 0x200,
+ AUDIO_DEVICE_OUT_AUX_DIGITAL = 0x400,
+ AUDIO_DEVICE_OUT_HDMI = AUDIO_DEVICE_OUT_AUX_DIGITAL,
+ /* uses an analog connection (multiplexed over the USB connector pins for instance) */
+ AUDIO_DEVICE_OUT_ANLG_DOCK_HEADSET = 0x800,
+ AUDIO_DEVICE_OUT_DGTL_DOCK_HEADSET = 0x1000,
+ /* USB accessory mode: your Android device is a USB device and the dock is a USB host */
+ AUDIO_DEVICE_OUT_USB_ACCESSORY = 0x2000,
+ /* USB host mode: your Android device is a USB host and the dock is a USB device */
+ AUDIO_DEVICE_OUT_USB_DEVICE = 0x4000,
+ AUDIO_DEVICE_OUT_REMOTE_SUBMIX = 0x8000,
+ /* Telephony voice TX path */
+ AUDIO_DEVICE_OUT_TELEPHONY_TX = 0x10000,
+ /* Analog jack with line impedance detected */
+ AUDIO_DEVICE_OUT_LINE = 0x20000,
+ /* HDMI Audio Return Channel */
+ AUDIO_DEVICE_OUT_HDMI_ARC = 0x40000,
+ /* S/PDIF out */
+ AUDIO_DEVICE_OUT_SPDIF = 0x80000,
+ /* FM transmitter out */
+ AUDIO_DEVICE_OUT_FM = 0x100000,
+ /* Line out for av devices */
+ AUDIO_DEVICE_OUT_AUX_LINE = 0x200000,
+ /* limited-output speaker device for acoustic safety */
+ AUDIO_DEVICE_OUT_SPEAKER_SAFE = 0x400000,
+ AUDIO_DEVICE_OUT_DEFAULT = AUDIO_DEVICE_BIT_DEFAULT,
+ AUDIO_DEVICE_OUT_ALL = (AUDIO_DEVICE_OUT_EARPIECE |
+ AUDIO_DEVICE_OUT_SPEAKER |
+ AUDIO_DEVICE_OUT_WIRED_HEADSET |
+ AUDIO_DEVICE_OUT_WIRED_HEADPHONE |
+ AUDIO_DEVICE_OUT_BLUETOOTH_SCO |
+ AUDIO_DEVICE_OUT_BLUETOOTH_SCO_HEADSET |
+ AUDIO_DEVICE_OUT_BLUETOOTH_SCO_CARKIT |
+ AUDIO_DEVICE_OUT_BLUETOOTH_A2DP |
+ AUDIO_DEVICE_OUT_BLUETOOTH_A2DP_HEADPHONES |
+ AUDIO_DEVICE_OUT_BLUETOOTH_A2DP_SPEAKER |
+ AUDIO_DEVICE_OUT_AUX_DIGITAL |
+ AUDIO_DEVICE_OUT_ANLG_DOCK_HEADSET |
+ AUDIO_DEVICE_OUT_DGTL_DOCK_HEADSET |
+ AUDIO_DEVICE_OUT_USB_ACCESSORY |
+ AUDIO_DEVICE_OUT_USB_DEVICE |
+ AUDIO_DEVICE_OUT_REMOTE_SUBMIX |
+ AUDIO_DEVICE_OUT_TELEPHONY_TX |
+ AUDIO_DEVICE_OUT_LINE |
+ AUDIO_DEVICE_OUT_HDMI_ARC |
+ AUDIO_DEVICE_OUT_SPDIF |
+ AUDIO_DEVICE_OUT_FM |
+ AUDIO_DEVICE_OUT_AUX_LINE |
+ AUDIO_DEVICE_OUT_SPEAKER_SAFE |
+ AUDIO_DEVICE_OUT_DEFAULT),
+ AUDIO_DEVICE_OUT_ALL_A2DP = (AUDIO_DEVICE_OUT_BLUETOOTH_A2DP |
+ AUDIO_DEVICE_OUT_BLUETOOTH_A2DP_HEADPHONES |
+ AUDIO_DEVICE_OUT_BLUETOOTH_A2DP_SPEAKER),
+ AUDIO_DEVICE_OUT_ALL_SCO = (AUDIO_DEVICE_OUT_BLUETOOTH_SCO |
+ AUDIO_DEVICE_OUT_BLUETOOTH_SCO_HEADSET |
+ AUDIO_DEVICE_OUT_BLUETOOTH_SCO_CARKIT),
+ AUDIO_DEVICE_OUT_ALL_USB = (AUDIO_DEVICE_OUT_USB_ACCESSORY |
+ AUDIO_DEVICE_OUT_USB_DEVICE),
+ /* input devices */
+ AUDIO_DEVICE_IN_COMMUNICATION = AUDIO_DEVICE_BIT_IN | 0x1,
+ AUDIO_DEVICE_IN_AMBIENT = AUDIO_DEVICE_BIT_IN | 0x2,
+ AUDIO_DEVICE_IN_BUILTIN_MIC = AUDIO_DEVICE_BIT_IN | 0x4,
+ AUDIO_DEVICE_IN_BLUETOOTH_SCO_HEADSET = AUDIO_DEVICE_BIT_IN | 0x8,
+ AUDIO_DEVICE_IN_WIRED_HEADSET = AUDIO_DEVICE_BIT_IN | 0x10,
+ AUDIO_DEVICE_IN_AUX_DIGITAL = AUDIO_DEVICE_BIT_IN | 0x20,
+ AUDIO_DEVICE_IN_HDMI = AUDIO_DEVICE_IN_AUX_DIGITAL,
+ /* Telephony voice RX path */
+ AUDIO_DEVICE_IN_VOICE_CALL = AUDIO_DEVICE_BIT_IN | 0x40,
+ AUDIO_DEVICE_IN_BACK_MIC = AUDIO_DEVICE_BIT_IN | 0x80,
+ AUDIO_DEVICE_IN_REMOTE_SUBMIX = AUDIO_DEVICE_BIT_IN | 0x100,
+ AUDIO_DEVICE_IN_ANLG_DOCK_HEADSET = AUDIO_DEVICE_BIT_IN | 0x200,
+ AUDIO_DEVICE_IN_DGTL_DOCK_HEADSET = AUDIO_DEVICE_BIT_IN | 0x400,
+ AUDIO_DEVICE_IN_USB_ACCESSORY = AUDIO_DEVICE_BIT_IN | 0x800,
+ AUDIO_DEVICE_IN_USB_DEVICE = AUDIO_DEVICE_BIT_IN | 0x1000,
+ /* FM tuner input */
+ AUDIO_DEVICE_IN_FM_TUNER = AUDIO_DEVICE_BIT_IN | 0x2000,
+ /* TV tuner input */
+ AUDIO_DEVICE_IN_TV_TUNER = AUDIO_DEVICE_BIT_IN | 0x4000,
+ /* Analog jack with line impedance detected */
+ AUDIO_DEVICE_IN_LINE = AUDIO_DEVICE_BIT_IN | 0x8000,
+ /* S/PDIF in */
+ AUDIO_DEVICE_IN_SPDIF = AUDIO_DEVICE_BIT_IN | 0x10000,
+ AUDIO_DEVICE_IN_BLUETOOTH_A2DP = AUDIO_DEVICE_BIT_IN | 0x20000,
+ AUDIO_DEVICE_IN_LOOPBACK = AUDIO_DEVICE_BIT_IN | 0x40000,
+ AUDIO_DEVICE_IN_DEFAULT = AUDIO_DEVICE_BIT_IN | AUDIO_DEVICE_BIT_DEFAULT,
+ AUDIO_DEVICE_IN_ALL = (AUDIO_DEVICE_IN_COMMUNICATION |
+ AUDIO_DEVICE_IN_AMBIENT |
+ AUDIO_DEVICE_IN_BUILTIN_MIC |
+ AUDIO_DEVICE_IN_BLUETOOTH_SCO_HEADSET |
+ AUDIO_DEVICE_IN_WIRED_HEADSET |
+ AUDIO_DEVICE_IN_AUX_DIGITAL |
+ AUDIO_DEVICE_IN_VOICE_CALL |
+ AUDIO_DEVICE_IN_BACK_MIC |
+ AUDIO_DEVICE_IN_REMOTE_SUBMIX |
+ AUDIO_DEVICE_IN_ANLG_DOCK_HEADSET |
+ AUDIO_DEVICE_IN_DGTL_DOCK_HEADSET |
+ AUDIO_DEVICE_IN_USB_ACCESSORY |
+ AUDIO_DEVICE_IN_USB_DEVICE |
+ AUDIO_DEVICE_IN_FM_TUNER |
+ AUDIO_DEVICE_IN_TV_TUNER |
+ AUDIO_DEVICE_IN_LINE |
+ AUDIO_DEVICE_IN_SPDIF |
+ AUDIO_DEVICE_IN_BLUETOOTH_A2DP |
+ AUDIO_DEVICE_IN_LOOPBACK |
+ AUDIO_DEVICE_IN_DEFAULT),
+ AUDIO_DEVICE_IN_ALL_SCO = AUDIO_DEVICE_IN_BLUETOOTH_SCO_HEADSET,
+ AUDIO_DEVICE_IN_ALL_USB = (AUDIO_DEVICE_IN_USB_ACCESSORY |
+ AUDIO_DEVICE_IN_USB_DEVICE),
+};
+
+typedef uint32_t audio_devices_t;
+#endif
+
+static inline bool audio_is_output_device(uint32_t device)
+{
+#if ANDROID_VERSION < 17
+ if ((__builtin_popcount(device) == 1) && ((device & ~AUDIO_DEVICE_OUT_ALL) == 0))
+ return true;
+ else
+ return false;
+#else
+ if (((device & AUDIO_DEVICE_BIT_IN) == 0) &&
+ (__builtin_popcount(device) == 1) && ((device & ~AUDIO_DEVICE_OUT_ALL) == 0))
+ return true;
+ else
+ return false;
+#endif
+}
+
+/* device connection states used for audio_policy->set_device_connection_state()
+ * */
+typedef enum {
+ AUDIO_POLICY_DEVICE_STATE_UNAVAILABLE,
+ AUDIO_POLICY_DEVICE_STATE_AVAILABLE,
+
+ AUDIO_POLICY_DEVICE_STATE_CNT,
+ AUDIO_POLICY_DEVICE_STATE_MAX = AUDIO_POLICY_DEVICE_STATE_CNT - 1,
+} audio_policy_dev_state_t;
+
+namespace android {
+
+typedef void (*audio_error_callback)(status_t err);
+typedef int audio_io_handle_t;
+
+class IAudioPolicyService;
+class String8;
+
+class AudioSystem
+{
+public:
+
+ enum stream_type {
+ DEFAULT =-1,
+ VOICE_CALL = 0,
+ SYSTEM = 1,
+ RING = 2,
+ MUSIC = 3,
+ ALARM = 4,
+ NOTIFICATION = 5,
+ BLUETOOTH_SCO = 6,
+ ENFORCED_AUDIBLE = 7, // Sounds that cannot be muted by user and must be routed to speaker
+ DTMF = 8,
+ TTS = 9,
+ FM = 10,
+ NUM_STREAM_TYPES
+ };
+
+ // Audio sub formats (see AudioSystem::audio_format).
+ enum pcm_sub_format {
+ PCM_SUB_16_BIT = 0x1, // must be 1 for backward compatibility
+ PCM_SUB_8_BIT = 0x2, // must be 2 for backward compatibility
+ };
+
+ // MP3 sub format field definition : can use 11 LSBs in the same way as MP3 frame header to specify
+ // bit rate, stereo mode, version...
+ enum mp3_sub_format {
+ //TODO
+ };
+
+ // AMR NB/WB sub format field definition: specify frame block interleaving, bandwidth efficient or octet aligned,
+ // encoding mode for recording...
+ enum amr_sub_format {
+ //TODO
+ };
+
+ // AAC sub format field definition: specify profile or bitrate for recording...
+ enum aac_sub_format {
+ //TODO
+ };
+
+ // VORBIS sub format field definition: specify quality for recording...
+ enum vorbis_sub_format {
+ //TODO
+ };
+
+ // Audio format consists in a main format field (upper 8 bits) and a sub format field (lower 24 bits).
+ // The main format indicates the main codec type. The sub format field indicates options and parameters
+ // for each format. The sub format is mainly used for record to indicate for instance the requested bitrate
+ // or profile. It can also be used for certain formats to give informations not present in the encoded
+ // audio stream (e.g. octet alignement for AMR).
+ enum audio_format {
+ INVALID_FORMAT = -1,
+ FORMAT_DEFAULT = 0,
+ PCM = 0x00000000, // must be 0 for backward compatibility
+ MP3 = 0x01000000,
+ AMR_NB = 0x02000000,
+ AMR_WB = 0x03000000,
+ AAC = 0x04000000,
+ HE_AAC_V1 = 0x05000000,
+ HE_AAC_V2 = 0x06000000,
+ VORBIS = 0x07000000,
+ EVRC = 0x08000000,
+ QCELP = 0x09000000,
+ VOIP_PCM_INPUT = 0x0A000000,
+ MAIN_FORMAT_MASK = 0xFF000000,
+ SUB_FORMAT_MASK = 0x00FFFFFF,
+ // Aliases
+ PCM_16_BIT = (PCM|PCM_SUB_16_BIT),
+ PCM_8_BIT = (PCM|PCM_SUB_8_BIT)
+ };
+
+
+ // Channel mask definitions must be kept in sync with JAVA values in /media/java/android/media/AudioFormat.java
+ enum audio_channels {
+ // output channels
+ CHANNEL_OUT_FRONT_LEFT = 0x4,
+ CHANNEL_OUT_FRONT_RIGHT = 0x8,
+ CHANNEL_OUT_FRONT_CENTER = 0x10,
+ CHANNEL_OUT_LOW_FREQUENCY = 0x20,
+ CHANNEL_OUT_BACK_LEFT = 0x40,
+ CHANNEL_OUT_BACK_RIGHT = 0x80,
+ CHANNEL_OUT_FRONT_LEFT_OF_CENTER = 0x100,
+ CHANNEL_OUT_FRONT_RIGHT_OF_CENTER = 0x200,
+ CHANNEL_OUT_BACK_CENTER = 0x400,
+ CHANNEL_OUT_MONO = CHANNEL_OUT_FRONT_LEFT,
+ CHANNEL_OUT_STEREO = (CHANNEL_OUT_FRONT_LEFT | CHANNEL_OUT_FRONT_RIGHT),
+ CHANNEL_OUT_QUAD = (CHANNEL_OUT_FRONT_LEFT | CHANNEL_OUT_FRONT_RIGHT |
+ CHANNEL_OUT_BACK_LEFT | CHANNEL_OUT_BACK_RIGHT),
+ CHANNEL_OUT_SURROUND = (CHANNEL_OUT_FRONT_LEFT | CHANNEL_OUT_FRONT_RIGHT |
+ CHANNEL_OUT_FRONT_CENTER | CHANNEL_OUT_BACK_CENTER),
+ CHANNEL_OUT_5POINT1 = (CHANNEL_OUT_FRONT_LEFT | CHANNEL_OUT_FRONT_RIGHT |
+ CHANNEL_OUT_FRONT_CENTER | CHANNEL_OUT_LOW_FREQUENCY | CHANNEL_OUT_BACK_LEFT | CHANNEL_OUT_BACK_RIGHT),
+ CHANNEL_OUT_7POINT1 = (CHANNEL_OUT_FRONT_LEFT | CHANNEL_OUT_FRONT_RIGHT |
+ CHANNEL_OUT_FRONT_CENTER | CHANNEL_OUT_LOW_FREQUENCY | CHANNEL_OUT_BACK_LEFT | CHANNEL_OUT_BACK_RIGHT |
+ CHANNEL_OUT_FRONT_LEFT_OF_CENTER | CHANNEL_OUT_FRONT_RIGHT_OF_CENTER),
+ CHANNEL_OUT_ALL = (CHANNEL_OUT_FRONT_LEFT | CHANNEL_OUT_FRONT_RIGHT |
+ CHANNEL_OUT_FRONT_CENTER | CHANNEL_OUT_LOW_FREQUENCY | CHANNEL_OUT_BACK_LEFT | CHANNEL_OUT_BACK_RIGHT |
+ CHANNEL_OUT_FRONT_LEFT_OF_CENTER | CHANNEL_OUT_FRONT_RIGHT_OF_CENTER | CHANNEL_OUT_BACK_CENTER),
+
+ // input channels
+ CHANNEL_IN_LEFT = 0x4,
+ CHANNEL_IN_RIGHT = 0x8,
+ CHANNEL_IN_FRONT = 0x10,
+ CHANNEL_IN_BACK = 0x20,
+ CHANNEL_IN_LEFT_PROCESSED = 0x40,
+ CHANNEL_IN_RIGHT_PROCESSED = 0x80,
+ CHANNEL_IN_FRONT_PROCESSED = 0x100,
+ CHANNEL_IN_BACK_PROCESSED = 0x200,
+ CHANNEL_IN_PRESSURE = 0x400,
+ CHANNEL_IN_X_AXIS = 0x800,
+ CHANNEL_IN_Y_AXIS = 0x1000,
+ CHANNEL_IN_Z_AXIS = 0x2000,
+ CHANNEL_IN_VOICE_UPLINK = 0x4000,
+ CHANNEL_IN_VOICE_DNLINK = 0x8000,
+ CHANNEL_IN_MONO = CHANNEL_IN_FRONT,
+ CHANNEL_IN_STEREO = (CHANNEL_IN_LEFT | CHANNEL_IN_RIGHT),
+ CHANNEL_IN_ALL = (CHANNEL_IN_LEFT | CHANNEL_IN_RIGHT | CHANNEL_IN_FRONT | CHANNEL_IN_BACK|
+ CHANNEL_IN_LEFT_PROCESSED | CHANNEL_IN_RIGHT_PROCESSED | CHANNEL_IN_FRONT_PROCESSED | CHANNEL_IN_BACK_PROCESSED|
+ CHANNEL_IN_PRESSURE | CHANNEL_IN_X_AXIS | CHANNEL_IN_Y_AXIS | CHANNEL_IN_Z_AXIS |
+ CHANNEL_IN_VOICE_UPLINK | CHANNEL_IN_VOICE_DNLINK)
+ };
+
+ enum audio_mode {
+ MODE_INVALID = -2,
+ MODE_CURRENT = -1,
+ MODE_NORMAL = 0,
+ MODE_RINGTONE,
+ MODE_IN_CALL,
+ MODE_IN_COMMUNICATION,
+ NUM_MODES // not a valid entry, denotes end-of-list
+ };
+
+ enum audio_in_acoustics {
+ AGC_ENABLE = 0x0001,
+ AGC_DISABLE = 0,
+ NS_ENABLE = 0x0002,
+ NS_DISABLE = 0,
+ TX_IIR_ENABLE = 0x0004,
+ TX_DISABLE = 0
+ };
+
+ // special audio session values
+ enum audio_sessions {
+ SESSION_OUTPUT_STAGE = -1, // session for effects attached to a particular output stream
+ // (value must be less than 0)
+ SESSION_OUTPUT_MIX = 0, // session for effects applied to output mix. These effects can
+ // be moved by audio policy manager to another output stream
+ // (value must be 0)
+ };
+
+ /* These are static methods to control the system-wide AudioFlinger
+ * only privileged processes can have access to them
+ */
+
+ // mute/unmute microphone
+ static status_t muteMicrophone(bool state);
+ static status_t isMicrophoneMuted(bool *state);
+
+ // set/get master volume
+ static status_t setMasterVolume(float value);
+ static status_t getMasterVolume(float* volume);
+ // mute/unmute audio outputs
+ static status_t setMasterMute(bool mute);
+ static status_t getMasterMute(bool* mute);
+
+ // set/get stream volume on specified output
+ static status_t setStreamVolume(int stream, float value, int output);
+ static status_t getStreamVolume(int stream, float* volume, int output);
+
+ // mute/unmute stream
+ static status_t setStreamMute(int stream, bool mute);
+ static status_t getStreamMute(int stream, bool* mute);
+
+ // set audio mode in audio hardware (see AudioSystem::audio_mode)
+ static status_t setMode(int mode);
+
+ // returns true in *state if tracks are active on the specified stream
+ static status_t isStreamActive(int stream, bool *state);
+
+ // set/get audio hardware parameters. The function accepts a list of parameters
+ // key value pairs in the form: key1=value1;key2=value2;...
+ // Some keys are reserved for standard parameters (See AudioParameter class).
+ static status_t setParameters(audio_io_handle_t ioHandle, const String8& keyValuePairs);
+ static String8 getParameters(audio_io_handle_t ioHandle, const String8& keys);
+
+ static void setErrorCallback(audio_error_callback cb);
+
+ // helper function to obtain AudioFlinger service handle
+ static const sp<IAudioFlinger>& get_audio_flinger();
+
+ static float linearToLog(int volume);
+ static int logToLinear(float volume);
+
+ static status_t getOutputSamplingRate(int* samplingRate, int stream = DEFAULT);
+ static status_t getOutputFrameCount(int* frameCount, int stream = DEFAULT);
+ static status_t getOutputLatency(uint32_t* latency, int stream = DEFAULT);
+
+ static bool routedToA2dpOutput(int streamType);
+
+ static status_t getInputBufferSize(uint32_t sampleRate, int format, int channelCount,
+ size_t* buffSize);
+
+ static status_t setVoiceVolume(float volume);
+
+ // return the number of audio frames written by AudioFlinger to audio HAL and
+ // audio dsp to DAC since the output on which the specificed stream is playing
+ // has exited standby.
+ // returned status (from utils/Errors.h) can be:
+ // - NO_ERROR: successful operation, halFrames and dspFrames point to valid data
+ // - INVALID_OPERATION: Not supported on current hardware platform
+ // - BAD_VALUE: invalid parameter
+ // NOTE: this feature is not supported on all hardware platforms and it is
+ // necessary to check returned status before using the returned values.
+ static status_t getRenderPosition(uint32_t *halFrames, uint32_t *dspFrames, int stream = DEFAULT);
+
+ static unsigned int getInputFramesLost(audio_io_handle_t ioHandle);
+
+ static int newAudioSessionId();
+ //
+ // AudioPolicyService interface
+ //
+
+ enum audio_devices {
+ // output devices
+ DEVICE_OUT_EARPIECE = 0x1,
+ DEVICE_OUT_SPEAKER = 0x2,
+ DEVICE_OUT_WIRED_HEADSET = 0x4,
+ DEVICE_OUT_WIRED_HEADPHONE = 0x8,
+ DEVICE_OUT_BLUETOOTH_SCO = 0x10,
+ DEVICE_OUT_BLUETOOTH_SCO_HEADSET = 0x20,
+ DEVICE_OUT_BLUETOOTH_SCO_CARKIT = 0x40,
+ DEVICE_OUT_BLUETOOTH_A2DP = 0x80,
+ DEVICE_OUT_BLUETOOTH_A2DP_HEADPHONES = 0x100,
+ DEVICE_OUT_BLUETOOTH_A2DP_SPEAKER = 0x200,
+ DEVICE_OUT_AUX_DIGITAL = 0x400,
+ DEVICE_OUT_DEFAULT = 0x8000,
+ DEVICE_OUT_ALL = (DEVICE_OUT_EARPIECE | DEVICE_OUT_SPEAKER | DEVICE_OUT_WIRED_HEADSET |
+ DEVICE_OUT_WIRED_HEADPHONE | DEVICE_OUT_BLUETOOTH_SCO | DEVICE_OUT_BLUETOOTH_SCO_HEADSET |
+ DEVICE_OUT_BLUETOOTH_SCO_CARKIT | DEVICE_OUT_BLUETOOTH_A2DP | DEVICE_OUT_BLUETOOTH_A2DP_HEADPHONES |
+ DEVICE_OUT_BLUETOOTH_A2DP_SPEAKER | DEVICE_OUT_AUX_DIGITAL | DEVICE_OUT_DEFAULT),
+ DEVICE_OUT_ALL_A2DP = (DEVICE_OUT_BLUETOOTH_A2DP | DEVICE_OUT_BLUETOOTH_A2DP_HEADPHONES |
+ DEVICE_OUT_BLUETOOTH_A2DP_SPEAKER),
+
+ // input devices
+ DEVICE_IN_COMMUNICATION = 0x10000,
+ DEVICE_IN_AMBIENT = 0x20000,
+ DEVICE_IN_BUILTIN_MIC = 0x40000,
+ DEVICE_IN_BLUETOOTH_SCO_HEADSET = 0x80000,
+ DEVICE_IN_WIRED_HEADSET = 0x100000,
+ DEVICE_IN_AUX_DIGITAL = 0x200000,
+ DEVICE_IN_VOICE_CALL = 0x400000,
+ DEVICE_IN_BACK_MIC = 0x800000,
+ DEVICE_IN_DEFAULT = 0x80000000,
+
+ DEVICE_IN_ALL = (DEVICE_IN_COMMUNICATION | DEVICE_IN_AMBIENT | DEVICE_IN_BUILTIN_MIC |
+ DEVICE_IN_BLUETOOTH_SCO_HEADSET | DEVICE_IN_WIRED_HEADSET | DEVICE_IN_AUX_DIGITAL |
+ DEVICE_IN_VOICE_CALL | DEVICE_IN_BACK_MIC | DEVICE_IN_DEFAULT)
+ };
+
+ // device connection states used for setDeviceConnectionState()
+ enum device_connection_state {
+ DEVICE_STATE_UNAVAILABLE,
+ DEVICE_STATE_AVAILABLE,
+ NUM_DEVICE_STATES
+ };
+
+ // request to open a direct output with getOutput() (by opposition to sharing an output with other AudioTracks)
+ enum output_flags {
+ OUTPUT_FLAG_INDIRECT = 0x0,
+ OUTPUT_FLAG_DIRECT = 0x1
+ };
+
+ // device categories used for setForceUse()
+ enum forced_config {
+ FORCE_NONE,
+ FORCE_SPEAKER,
+ FORCE_HEADPHONES,
+ FORCE_BT_SCO,
+ FORCE_BT_A2DP,
+ FORCE_WIRED_ACCESSORY,
+ FORCE_BT_CAR_DOCK,
+ FORCE_BT_DESK_DOCK,
+ FORCE_ANALOG_DOCK,
+ FORCE_DIGITAL_DOCK,
+ FORCE_NO_BT_A2DP,
+ NUM_FORCE_CONFIG,
+ FORCE_DEFAULT = FORCE_NONE
+ };
+
+ // usages used for setForceUse()
+ enum force_use {
+ FOR_COMMUNICATION,
+ FOR_MEDIA,
+ FOR_RECORD,
+ FOR_DOCK,
+ NUM_FORCE_USE
+ };
+
+ // types of io configuration change events received with ioConfigChanged()
+ enum io_config_event {
+ OUTPUT_OPENED,
+ OUTPUT_CLOSED,
+ OUTPUT_CONFIG_CHANGED,
+ INPUT_OPENED,
+ INPUT_CLOSED,
+ INPUT_CONFIG_CHANGED,
+ STREAM_CONFIG_CHANGED,
+ NUM_CONFIG_EVENTS
+ };
+
+ // audio output descritor used to cache output configurations in client process to avoid frequent calls
+ // through IAudioFlinger
+ class OutputDescriptor {
+ public:
+ OutputDescriptor()
+ : samplingRate(0), format(0), channels(0), frameCount(0), latency(0) {}
+
+ uint32_t samplingRate;
+ int32_t format;
+ int32_t channels;
+ size_t frameCount;
+ uint32_t latency;
+ };
+
+ //
+ // IAudioPolicyService interface (see AudioPolicyInterface for method descriptions)
+ //
+ static status_t setDeviceConnectionState(audio_devices device, device_connection_state state, const char *device_address);
+ static device_connection_state getDeviceConnectionState(audio_devices device, const char *device_address);
+ static status_t setPhoneState(int state);
+#if ANDROID_VERSION >= 17
+ static status_t setPhoneState(audio_mode_t state);
+#endif
+ static status_t setRingerMode(uint32_t mode, uint32_t mask);
+#ifdef VANILLA_ANDROID
+ static status_t setForceUse(force_use usage, forced_config config);
+ static forced_config getForceUse(force_use usage);
+ static audio_io_handle_t getOutput(stream_type stream,
+ uint32_t samplingRate = 0,
+ uint32_t format = FORMAT_DEFAULT,
+ uint32_t channels = CHANNEL_OUT_STEREO,
+ output_flags flags = OUTPUT_FLAG_INDIRECT);
+ static status_t setDeviceConnectionState(audio_devices_t device, audio_policy_dev_state_t state, const char *device_address);
+ static status_t setFmVolume(float volume);
+ static audio_policy_dev_state_t getDeviceConnectionState(audio_devices_t device, const char *device_address);
+#else
+ static status_t setForceUse(force_use usage, forced_config config) __attribute__((weak));
+ static forced_config getForceUse(force_use usage) __attribute__((weak));
+ static audio_io_handle_t getOutput(stream_type stream,
+ uint32_t samplingRate = 0,
+ uint32_t format = FORMAT_DEFAULT,
+ uint32_t channels = CHANNEL_OUT_STEREO,
+ output_flags flags = OUTPUT_FLAG_INDIRECT) __attribute__((weak));
+
+ static status_t setForceUse(audio_policy_force_use_t usage, audio_policy_forced_cfg_t config) __attribute__((weak));
+ static audio_policy_forced_cfg_t getForceUse(audio_policy_force_use_t usage) __attribute__((weak));
+ static audio_io_handle_t getOutput(audio_stream_type_t stream,
+ uint32_t samplingRate = 0,
+ uint32_t format = AUDIO_FORMAT_DEFAULT,
+ uint32_t channels = AUDIO_CHANNEL_OUT_STEREO,
+ audio_policy_output_flags_t flags = AUDIO_POLICY_OUTPUT_FLAG_INDIRECT) __attribute__((weak));
+ static status_t setDeviceConnectionState(audio_devices_t device, audio_policy_dev_state_t state, const char *device_address) __attribute__((weak));
+ static status_t setFmVolume(float volume) __attribute__((weak));
+ static audio_policy_dev_state_t getDeviceConnectionState(audio_devices_t device, const char *device_address) __attribute__((weak));
+
+#endif
+ static status_t startOutput(audio_io_handle_t output,
+ AudioSystem::stream_type stream,
+ int session = 0);
+ static status_t stopOutput(audio_io_handle_t output,
+ AudioSystem::stream_type stream,
+ int session = 0);
+ static void releaseOutput(audio_io_handle_t output);
+ static audio_io_handle_t getInput(int inputSource,
+ uint32_t samplingRate = 0,
+ uint32_t format = FORMAT_DEFAULT,
+ uint32_t channels = CHANNEL_IN_MONO,
+ audio_in_acoustics acoustics = (audio_in_acoustics)0);
+ static status_t startInput(audio_io_handle_t input);
+ static status_t stopInput(audio_io_handle_t input);
+ static void releaseInput(audio_io_handle_t input);
+ static status_t initStreamVolume(stream_type stream,
+ int indexMin,
+ int indexMax);
+ static status_t initStreamVolume(audio_stream_type_t stream,
+ int indexMin,
+ int indexMax);
+ static status_t setStreamVolumeIndex(stream_type stream, int index);
+ static status_t setStreamVolumeIndex(audio_stream_type_t stream, int index);
+#if ANDROID_VERSION >= 17
+ static status_t setStreamVolumeIndex(audio_stream_type_t stream,
+ int index,
+ audio_devices_t device);
+ static status_t getStreamVolumeIndex(audio_stream_type_t stream,
+ int *index,
+ audio_devices_t device);
+#endif
+ static status_t getStreamVolumeIndex(stream_type stream, int *index);
+ static status_t getStreamVolumeIndex(audio_stream_type_t stream, int *index);
+
+ static uint32_t getStrategyForStream(stream_type stream);
+#if ANDROID_VERSION >= 17
+ static audio_devices_t getDevicesForStream(audio_stream_type_t stream);
+#endif
+
+ static audio_io_handle_t getOutputForEffect(effect_descriptor_t *desc);
+ static status_t registerEffect(effect_descriptor_t *desc,
+ audio_io_handle_t output,
+ uint32_t strategy,
+ int session,
+ int id);
+ static status_t unregisterEffect(int id);
+
+ static const sp<IAudioPolicyService>& get_audio_policy_service();
+
+ // ----------------------------------------------------------------------------
+
+ static uint32_t popCount(uint32_t u);
+ static bool isOutputDevice(audio_devices device);
+ static bool isInputDevice(audio_devices device);
+ static bool isA2dpDevice(audio_devices device);
+ static bool isBluetoothScoDevice(audio_devices device);
+ static bool isSeperatedStream(stream_type stream);
+ static bool isLowVisibility(stream_type stream);
+ static bool isOutputChannel(uint32_t channel);
+ static bool isInputChannel(uint32_t channel);
+ static bool isValidFormat(uint32_t format);
+ static bool isLinearPCM(uint32_t format);
+ static bool isModeInCall();
+
+#if ANDROID_VERSION >= 21
+ class AudioPortCallback : public RefBase
+ {
+ public:
+
+ AudioPortCallback() {}
+ virtual ~AudioPortCallback() {}
+
+ virtual void onAudioPortListUpdate() = 0;
+ virtual void onAudioPatchListUpdate() = 0;
+ virtual void onServiceDied() = 0;
+
+ };
+
+ static void setAudioPortCallback(sp<AudioPortCallback> callBack);
+#endif
+
+private:
+
+ class AudioFlingerClient: public IBinder::DeathRecipient, public BnAudioFlingerClient
+ {
+ public:
+ AudioFlingerClient() {
+ }
+
+ // DeathRecipient
+ virtual void binderDied(const wp<IBinder>& who);
+
+ // IAudioFlingerClient
+
+ // indicate a change in the configuration of an output or input: keeps the cached
+ // values for output/input parameters upto date in client process
+ virtual void ioConfigChanged(int event, int ioHandle, void *param2);
+ };
+
+ class AudioPolicyServiceClient: public IBinder::DeathRecipient
+ {
+ public:
+ AudioPolicyServiceClient() {
+ }
+
+ // DeathRecipient
+ virtual void binderDied(const wp<IBinder>& who);
+ };
+
+ static sp<AudioFlingerClient> gAudioFlingerClient;
+ static sp<AudioPolicyServiceClient> gAudioPolicyServiceClient;
+ friend class AudioFlingerClient;
+ friend class AudioPolicyServiceClient;
+
+ static Mutex gLock;
+ static sp<IAudioFlinger> gAudioFlinger;
+ static audio_error_callback gAudioErrorCallback;
+
+ static size_t gInBuffSize;
+ // previous parameters for recording buffer size queries
+ static uint32_t gPrevInSamplingRate;
+ static int gPrevInFormat;
+ static int gPrevInChannelCount;
+
+ static sp<IAudioPolicyService> gAudioPolicyService;
+
+ // mapping between stream types and outputs
+ static DefaultKeyedVector<int, audio_io_handle_t> gStreamOutputMap;
+ // list of output descritor containing cached parameters (sampling rate, framecount, channel count...)
+ static DefaultKeyedVector<audio_io_handle_t, OutputDescriptor *> gOutputs;
+};
+
+class AudioParameter {
+
+public:
+ AudioParameter() {}
+ AudioParameter(const String8& keyValuePairs);
+ virtual ~AudioParameter();
+
+ // reserved parameter keys for changing standard parameters with setParameters() function.
+ // Using these keys is mandatory for AudioFlinger to properly monitor audio output/input
+ // configuration changes and act accordingly.
+ // keyRouting: to change audio routing, value is an int in AudioSystem::audio_devices
+ // keySamplingRate: to change sampling rate routing, value is an int
+ // keyFormat: to change audio format, value is an int in AudioSystem::audio_format
+ // keyChannels: to change audio channel configuration, value is an int in AudioSystem::audio_channels
+ // keyFrameCount: to change audio output frame count, value is an int
+ // keyInputSource: to change audio input source, value is an int in audio_source
+ // (defined in media/mediarecorder.h)
+ static const char *keyRouting;
+ static const char *keySamplingRate;
+ static const char *keyFormat;
+ static const char *keyChannels;
+ static const char *keyFrameCount;
+ static const char *keyInputSource;
+
+ String8 toString();
+
+ status_t add(const String8& key, const String8& value);
+ status_t addInt(const String8& key, const int value);
+ status_t addFloat(const String8& key, const float value);
+
+ status_t remove(const String8& key);
+
+ status_t get(const String8& key, String8& value);
+ status_t getInt(const String8& key, int& value);
+ status_t getFloat(const String8& key, float& value);
+ status_t getAt(size_t index, String8& key, String8& value);
+
+ size_t size() { return mParameters.size(); }
+
+private:
+ String8 mKeyValuePairs;
+ KeyedVector <String8, String8> mParameters;
+};
+
+}; // namespace android
+
+#pragma GCC visibility pop
+
+#endif /*ANDROID_AUDIOSYSTEM_H_*/
diff --git a/dom/system/gonk/android_audio/AudioTrack.h b/dom/system/gonk/android_audio/AudioTrack.h
new file mode 100644
index 000000000..6f8c6bb28
--- /dev/null
+++ b/dom/system/gonk/android_audio/AudioTrack.h
@@ -0,0 +1,489 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_AUDIOTRACK_H
+#define ANDROID_AUDIOTRACK_H
+
+#include <stdint.h>
+#include <sys/types.h>
+
+#include "IAudioFlinger.h"
+#include "IAudioTrack.h"
+#include "AudioSystem.h"
+
+#include <utils/RefBase.h>
+#include <utils/Errors.h>
+#include <binder/IInterface.h>
+#include <binder/IMemory.h>
+#include <utils/threads.h>
+
+
+namespace android {
+
+// ----------------------------------------------------------------------------
+
+class audio_track_cblk_t;
+
+// ----------------------------------------------------------------------------
+
+class AudioTrack
+{
+public:
+ enum channel_index {
+ MONO = 0,
+ LEFT = 0,
+ RIGHT = 1
+ };
+
+ /* Events used by AudioTrack callback function (audio_track_cblk_t).
+ */
+ enum event_type {
+ EVENT_MORE_DATA = 0, // Request to write more data to PCM buffer.
+ EVENT_UNDERRUN = 1, // PCM buffer underrun occured.
+ EVENT_LOOP_END = 2, // Sample loop end was reached; playback restarted from loop start if loop count was not 0.
+ EVENT_MARKER = 3, // Playback head is at the specified marker position (See setMarkerPosition()).
+ EVENT_NEW_POS = 4, // Playback head is at a new position (See setPositionUpdatePeriod()).
+ EVENT_BUFFER_END = 5 // Playback head is at the end of the buffer.
+ };
+
+ /* Create Buffer on the stack and pass it to obtainBuffer()
+ * and releaseBuffer().
+ */
+
+ class Buffer
+ {
+ public:
+ enum {
+ MUTE = 0x00000001
+ };
+ uint32_t flags;
+ int channelCount;
+ int format;
+ size_t frameCount;
+ size_t size;
+ union {
+ void* raw;
+ short* i16;
+ int8_t* i8;
+ };
+ };
+
+
+ /* As a convenience, if a callback is supplied, a handler thread
+ * is automatically created with the appropriate priority. This thread
+ * invokes the callback when a new buffer becomes availlable or an underrun condition occurs.
+ * Parameters:
+ *
+ * event: type of event notified (see enum AudioTrack::event_type).
+ * user: Pointer to context for use by the callback receiver.
+ * info: Pointer to optional parameter according to event type:
+ * - EVENT_MORE_DATA: pointer to AudioTrack::Buffer struct. The callback must not write
+ * more bytes than indicated by 'size' field and update 'size' if less bytes are
+ * written.
+ * - EVENT_UNDERRUN: unused.
+ * - EVENT_LOOP_END: pointer to an int indicating the number of loops remaining.
+ * - EVENT_MARKER: pointer to an uin32_t containing the marker position in frames.
+ * - EVENT_NEW_POS: pointer to an uin32_t containing the new position in frames.
+ * - EVENT_BUFFER_END: unused.
+ */
+
+ typedef void (*callback_t)(int event, void* user, void *info);
+
+ /* Returns the minimum frame count required for the successful creation of
+ * an AudioTrack object.
+ * Returned status (from utils/Errors.h) can be:
+ * - NO_ERROR: successful operation
+ * - NO_INIT: audio server or audio hardware not initialized
+ */
+
+ static status_t getMinFrameCount(int* frameCount,
+ int streamType =-1,
+ uint32_t sampleRate = 0);
+
+ /* Constructs an uninitialized AudioTrack. No connection with
+ * AudioFlinger takes place.
+ */
+ AudioTrack();
+
+ /* Creates an audio track and registers it with AudioFlinger.
+ * Once created, the track needs to be started before it can be used.
+ * Unspecified values are set to the audio hardware's current
+ * values.
+ *
+ * Parameters:
+ *
+ * streamType: Select the type of audio stream this track is attached to
+ * (e.g. AudioSystem::MUSIC).
+ * sampleRate: Track sampling rate in Hz.
+ * format: Audio format (e.g AudioSystem::PCM_16_BIT for signed
+ * 16 bits per sample).
+ * channels: Channel mask: see AudioSystem::audio_channels.
+ * frameCount: Total size of track PCM buffer in frames. This defines the
+ * latency of the track.
+ * flags: Reserved for future use.
+ * cbf: Callback function. If not null, this function is called periodically
+ * to request new PCM data.
+ * notificationFrames: The callback function is called each time notificationFrames PCM
+ * frames have been comsumed from track input buffer.
+ * user Context for use by the callback receiver.
+ */
+
+ AudioTrack( int streamType,
+ uint32_t sampleRate = 0,
+ int format = 0,
+ int channels = 0,
+ int frameCount = 0,
+ uint32_t flags = 0,
+ callback_t cbf = 0,
+ void* user = 0,
+ int notificationFrames = 0,
+ int sessionId = 0);
+
+ /* Creates an audio track and registers it with AudioFlinger. With this constructor,
+ * The PCM data to be rendered by AudioTrack is passed in a shared memory buffer
+ * identified by the argument sharedBuffer. This prototype is for static buffer playback.
+ * PCM data must be present into memory before the AudioTrack is started.
+ * The Write() and Flush() methods are not supported in this case.
+ * It is recommented to pass a callback function to be notified of playback end by an
+ * EVENT_UNDERRUN event.
+ */
+
+ AudioTrack( int streamType,
+ uint32_t sampleRate = 0,
+ int format = 0,
+ int channels = 0,
+ const sp<IMemory>& sharedBuffer = 0,
+ uint32_t flags = 0,
+ callback_t cbf = 0,
+ void* user = 0,
+ int notificationFrames = 0,
+ int sessionId = 0);
+
+ /* Terminates the AudioTrack and unregisters it from AudioFlinger.
+ * Also destroys all resources assotiated with the AudioTrack.
+ */
+ ~AudioTrack();
+
+
+ /* Initialize an uninitialized AudioTrack.
+ * Returned status (from utils/Errors.h) can be:
+ * - NO_ERROR: successful intialization
+ * - INVALID_OPERATION: AudioTrack is already intitialized
+ * - BAD_VALUE: invalid parameter (channels, format, sampleRate...)
+ * - NO_INIT: audio server or audio hardware not initialized
+ * */
+ status_t set(int streamType =-1,
+ uint32_t sampleRate = 0,
+ int format = 0,
+ int channels = 0,
+ int frameCount = 0,
+ uint32_t flags = 0,
+ callback_t cbf = 0,
+ void* user = 0,
+ int notificationFrames = 0,
+ const sp<IMemory>& sharedBuffer = 0,
+ bool threadCanCallJava = false,
+ int sessionId = 0);
+
+
+ /* Result of constructing the AudioTrack. This must be checked
+ * before using any AudioTrack API (except for set()), using
+ * an uninitialized AudioTrack produces undefined results.
+ * See set() method above for possible return codes.
+ */
+ status_t initCheck() const;
+
+ /* Returns this track's latency in milliseconds.
+ * This includes the latency due to AudioTrack buffer size, AudioMixer (if any)
+ * and audio hardware driver.
+ */
+ uint32_t latency() const;
+
+ /* getters, see constructor */
+
+ int streamType() const;
+ int format() const;
+ int channelCount() const;
+ uint32_t frameCount() const;
+ int frameSize() const;
+ sp<IMemory>& sharedBuffer();
+
+
+ /* After it's created the track is not active. Call start() to
+ * make it active. If set, the callback will start being called.
+ */
+ void start();
+
+ /* Stop a track. If set, the callback will cease being called and
+ * obtainBuffer returns STOPPED. Note that obtainBuffer() still works
+ * and will fill up buffers until the pool is exhausted.
+ */
+ void stop();
+ bool stopped() const;
+
+ /* flush a stopped track. All pending buffers are discarded.
+ * This function has no effect if the track is not stoped.
+ */
+ void flush();
+
+ /* Pause a track. If set, the callback will cease being called and
+ * obtainBuffer returns STOPPED. Note that obtainBuffer() still works
+ * and will fill up buffers until the pool is exhausted.
+ */
+ void pause();
+
+ /* mute or unmutes this track.
+ * While mutted, the callback, if set, is still called.
+ */
+ void mute(bool);
+ bool muted() const;
+
+
+ /* set volume for this track, mostly used for games' sound effects
+ * left and right volumes. Levels must be <= 1.0.
+ */
+ status_t setVolume(float left, float right);
+ void getVolume(float* left, float* right);
+
+ /* set the send level for this track. An auxiliary effect should be attached
+ * to the track with attachEffect(). Level must be <= 1.0.
+ */
+ status_t setAuxEffectSendLevel(float level);
+ void getAuxEffectSendLevel(float* level);
+
+ /* set sample rate for this track, mostly used for games' sound effects
+ */
+ status_t setSampleRate(int sampleRate);
+ uint32_t getSampleRate();
+
+ /* Enables looping and sets the start and end points of looping.
+ *
+ * Parameters:
+ *
+ * loopStart: loop start expressed as the number of PCM frames played since AudioTrack start.
+ * loopEnd: loop end expressed as the number of PCM frames played since AudioTrack start.
+ * loopCount: number of loops to execute. Calling setLoop() with loopCount == 0 cancels any pending or
+ * active loop. loopCount = -1 means infinite looping.
+ *
+ * For proper operation the following condition must be respected:
+ * (loopEnd-loopStart) <= framecount()
+ */
+ status_t setLoop(uint32_t loopStart, uint32_t loopEnd, int loopCount);
+ status_t getLoop(uint32_t *loopStart, uint32_t *loopEnd, int *loopCount);
+
+
+ /* Sets marker position. When playback reaches the number of frames specified, a callback with event
+ * type EVENT_MARKER is called. Calling setMarkerPosition with marker == 0 cancels marker notification
+ * callback.
+ * If the AudioTrack has been opened with no callback function associated, the operation will fail.
+ *
+ * Parameters:
+ *
+ * marker: marker position expressed in frames.
+ *
+ * Returned status (from utils/Errors.h) can be:
+ * - NO_ERROR: successful operation
+ * - INVALID_OPERATION: the AudioTrack has no callback installed.
+ */
+ status_t setMarkerPosition(uint32_t marker);
+ status_t getMarkerPosition(uint32_t *marker);
+
+
+ /* Sets position update period. Every time the number of frames specified has been played,
+ * a callback with event type EVENT_NEW_POS is called.
+ * Calling setPositionUpdatePeriod with updatePeriod == 0 cancels new position notification
+ * callback.
+ * If the AudioTrack has been opened with no callback function associated, the operation will fail.
+ *
+ * Parameters:
+ *
+ * updatePeriod: position update notification period expressed in frames.
+ *
+ * Returned status (from utils/Errors.h) can be:
+ * - NO_ERROR: successful operation
+ * - INVALID_OPERATION: the AudioTrack has no callback installed.
+ */
+ status_t setPositionUpdatePeriod(uint32_t updatePeriod);
+ status_t getPositionUpdatePeriod(uint32_t *updatePeriod);
+
+
+ /* Sets playback head position within AudioTrack buffer. The new position is specified
+ * in number of frames.
+ * This method must be called with the AudioTrack in paused or stopped state.
+ * Note that the actual position set is <position> modulo the AudioTrack buffer size in frames.
+ * Therefore using this method makes sense only when playing a "static" audio buffer
+ * as opposed to streaming.
+ * The getPosition() method on the other hand returns the total number of frames played since
+ * playback start.
+ *
+ * Parameters:
+ *
+ * position: New playback head position within AudioTrack buffer.
+ *
+ * Returned status (from utils/Errors.h) can be:
+ * - NO_ERROR: successful operation
+ * - INVALID_OPERATION: the AudioTrack is not stopped.
+ * - BAD_VALUE: The specified position is beyond the number of frames present in AudioTrack buffer
+ */
+ status_t setPosition(uint32_t position);
+ status_t getPosition(uint32_t *position);
+
+ /* Forces AudioTrack buffer full condition. When playing a static buffer, this method avoids
+ * rewriting the buffer before restarting playback after a stop.
+ * This method must be called with the AudioTrack in paused or stopped state.
+ *
+ * Returned status (from utils/Errors.h) can be:
+ * - NO_ERROR: successful operation
+ * - INVALID_OPERATION: the AudioTrack is not stopped.
+ */
+ status_t reload();
+
+ /* returns a handle on the audio output used by this AudioTrack.
+ *
+ * Parameters:
+ * none.
+ *
+ * Returned value:
+ * handle on audio hardware output
+ */
+ audio_io_handle_t getOutput();
+
+ /* returns the unique ID associated to this track.
+ *
+ * Parameters:
+ * none.
+ *
+ * Returned value:
+ * AudioTrack ID.
+ */
+ int getSessionId();
+
+
+ /* Attach track auxiliary output to specified effect. Used effectId = 0
+ * to detach track from effect.
+ *
+ * Parameters:
+ *
+ * effectId: effectId obtained from AudioEffect::id().
+ *
+ * Returned status (from utils/Errors.h) can be:
+ * - NO_ERROR: successful operation
+ * - INVALID_OPERATION: the effect is not an auxiliary effect.
+ * - BAD_VALUE: The specified effect ID is invalid
+ */
+ status_t attachAuxEffect(int effectId);
+
+ /* obtains a buffer of "frameCount" frames. The buffer must be
+ * filled entirely. If the track is stopped, obtainBuffer() returns
+ * STOPPED instead of NO_ERROR as long as there are buffers availlable,
+ * at which point NO_MORE_BUFFERS is returned.
+ * Buffers will be returned until the pool (buffercount())
+ * is exhausted, at which point obtainBuffer() will either block
+ * or return WOULD_BLOCK depending on the value of the "blocking"
+ * parameter.
+ */
+
+ enum {
+ NO_MORE_BUFFERS = 0x80000001,
+ STOPPED = 1
+ };
+
+ status_t obtainBuffer(Buffer* audioBuffer, int32_t waitCount);
+ void releaseBuffer(Buffer* audioBuffer);
+
+
+ /* As a convenience we provide a write() interface to the audio buffer.
+ * This is implemented on top of lockBuffer/unlockBuffer. For best
+ * performance
+ *
+ */
+ ssize_t write(const void* buffer, size_t size);
+
+ /*
+ * Dumps the state of an audio track.
+ */
+ status_t dump(int fd, const Vector<String16>& args) const;
+
+private:
+ /* copying audio tracks is not allowed */
+ AudioTrack(const AudioTrack& other);
+ AudioTrack& operator = (const AudioTrack& other);
+
+ /* a small internal class to handle the callback */
+ class AudioTrackThread : public Thread
+ {
+ public:
+ AudioTrackThread(AudioTrack& receiver, bool bCanCallJava = false);
+ private:
+ friend class AudioTrack;
+ virtual bool threadLoop();
+ virtual status_t readyToRun();
+ virtual void onFirstRef();
+ AudioTrack& mReceiver;
+ Mutex mLock;
+ };
+
+ bool processAudioBuffer(const sp<AudioTrackThread>& thread);
+ status_t createTrack(int streamType,
+ uint32_t sampleRate,
+ int format,
+ int channelCount,
+ int frameCount,
+ uint32_t flags,
+ const sp<IMemory>& sharedBuffer,
+ audio_io_handle_t output,
+ bool enforceFrameCount);
+
+ sp<IAudioTrack> mAudioTrack;
+ sp<IMemory> mCblkMemory;
+ sp<AudioTrackThread> mAudioTrackThread;
+
+ float mVolume[2];
+ float mSendLevel;
+ uint32_t mFrameCount;
+
+ audio_track_cblk_t* mCblk;
+ uint8_t mStreamType;
+ uint8_t mFormat;
+ uint8_t mChannelCount;
+ uint8_t mMuted;
+ uint32_t mChannels;
+ status_t mStatus;
+ uint32_t mLatency;
+
+ volatile int32_t mActive;
+
+ callback_t mCbf;
+ void* mUserData;
+ uint32_t mNotificationFramesReq; // requested number of frames between each notification callback
+ uint32_t mNotificationFramesAct; // actual number of frames between each notification callback
+ sp<IMemory> mSharedBuffer;
+ int mLoopCount;
+ uint32_t mRemainingFrames;
+ uint32_t mMarkerPosition;
+ bool mMarkerReached;
+ uint32_t mNewPosition;
+ uint32_t mUpdatePeriod;
+ uint32_t mFlags;
+ int mSessionId;
+ int mAuxEffectId;
+ uint32_t mPadding[8];
+};
+
+
+}; // namespace android
+
+#endif // ANDROID_AUDIOTRACK_H
diff --git a/dom/system/gonk/android_audio/EffectApi.h b/dom/system/gonk/android_audio/EffectApi.h
new file mode 100644
index 000000000..729545d0c
--- /dev/null
+++ b/dom/system/gonk/android_audio/EffectApi.h
@@ -0,0 +1,798 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_EFFECTAPI_H_
+#define ANDROID_EFFECTAPI_H_
+
+#include <errno.h>
+#include <stdint.h>
+#include <sys/types.h>
+
+#if __cplusplus
+extern "C" {
+#endif
+
+/////////////////////////////////////////////////
+// Effect control interface
+/////////////////////////////////////////////////
+
+// The effect control interface is exposed by each effect engine implementation. It consists of
+// a set of functions controlling the configuration, activation and process of the engine.
+// The functions are grouped in a structure of type effect_interface_s:
+// struct effect_interface_s {
+// effect_process_t process;
+// effect_command_t command;
+// };
+
+
+// effect_interface_t: Effect control interface handle.
+// The effect_interface_t serves two purposes regarding the implementation of the effect engine:
+// - 1 it is the address of a pointer to an effect_interface_s structure where the functions
+// of the effect control API for a particular effect are located.
+// - 2 it is the address of the context of a particular effect instance.
+// A typical implementation in the effect library would define a structure as follows:
+// struct effect_module_s {
+// const struct effect_interface_s *itfe;
+// effect_config_t config;
+// effect_context_t context;
+// }
+// The implementation of EffectCreate() function would then allocate a structure of this
+// type and return its address as effect_interface_t
+typedef struct effect_interface_s **effect_interface_t;
+
+
+// Effect API version 1.0
+#define EFFECT_API_VERSION 0x0100 // Format 0xMMmm MM: Major version, mm: minor version
+
+// Maximum length of character strings in structures defines by this API.
+#define EFFECT_STRING_LEN_MAX 64
+
+//
+//--- Effect descriptor structure effect_descriptor_t
+//
+
+// Unique effect ID (can be generated from the following site:
+// http://www.itu.int/ITU-T/asn1/uuid.html)
+// This format is used for both "type" and "uuid" fields of the effect descriptor structure.
+// - When used for effect type and the engine is implementing and effect corresponding to a standard
+// OpenSL ES interface, this ID must be the one defined in OpenSLES_IID.h for that interface.
+// - When used as uuid, it should be a unique UUID for this particular implementation.
+typedef struct effect_uuid_s {
+ uint32_t timeLow;
+ uint16_t timeMid;
+ uint16_t timeHiAndVersion;
+ uint16_t clockSeq;
+ uint8_t node[6];
+} effect_uuid_t;
+
+// NULL UUID definition (matches SL_IID_NULL_)
+#define EFFECT_UUID_INITIALIZER { 0xec7178ec, 0xe5e1, 0x4432, 0xa3f4, \
+ { 0x46, 0x57, 0xe6, 0x79, 0x52, 0x10 } }
+static const effect_uuid_t EFFECT_UUID_NULL_ = EFFECT_UUID_INITIALIZER;
+const effect_uuid_t * const EFFECT_UUID_NULL = &EFFECT_UUID_NULL_;
+const char * const EFFECT_UUID_NULL_STR = "ec7178ec-e5e1-4432-a3f4-4657e6795210";
+
+// The effect descriptor contains necessary information to facilitate the enumeration of the effect
+// engines present in a library.
+typedef struct effect_descriptor_s {
+ effect_uuid_t type; // UUID of to the OpenSL ES interface implemented by this effect
+ effect_uuid_t uuid; // UUID for this particular implementation
+ uint16_t apiVersion; // Version of the effect API implemented: matches EFFECT_API_VERSION
+ uint32_t flags; // effect engine capabilities/requirements flags (see below)
+ uint16_t cpuLoad; // CPU load indication (see below)
+ uint16_t memoryUsage; // Data Memory usage (see below)
+ char name[EFFECT_STRING_LEN_MAX]; // human readable effect name
+ char implementor[EFFECT_STRING_LEN_MAX]; // human readable effect implementor name
+} effect_descriptor_t;
+
+// CPU load and memory usage indication: each effect implementation must provide an indication of
+// its CPU and memory usage for the audio effect framework to limit the number of effects
+// instantiated at a given time on a given platform.
+// The CPU load is expressed in 0.1 MIPS units as estimated on an ARM9E core (ARMv5TE) with 0 WS.
+// The memory usage is expressed in KB and includes only dynamically allocated memory
+
+// Definitions for flags field of effect descriptor.
+// +---------------------------+-----------+-----------------------------------
+// | description | bits | values
+// +---------------------------+-----------+-----------------------------------
+// | connection mode | 0..1 | 0 insert: after track process
+// | | | 1 auxiliary: connect to track auxiliary
+// | | | output and use send level
+// | | | 2 replace: replaces track process function;
+// | | | must implement SRC, volume and mono to stereo.
+// | | | 3 reserved
+// +---------------------------+-----------+-----------------------------------
+// | insertion preference | 2..4 | 0 none
+// | | | 1 first of the chain
+// | | | 2 last of the chain
+// | | | 3 exclusive (only effect in the insert chain)
+// | | | 4..7 reserved
+// +---------------------------+-----------+-----------------------------------
+// | Volume management | 5..6 | 0 none
+// | | | 1 implements volume control
+// | | | 2 requires volume indication
+// | | | 3 reserved
+// +---------------------------+-----------+-----------------------------------
+// | Device indication | 7..8 | 0 none
+// | | | 1 requires device updates
+// | | | 2..3 reserved
+// +---------------------------+-----------+-----------------------------------
+// | Sample input mode | 9..10 | 0 direct: process() function or EFFECT_CMD_CONFIGURE
+// | | | command must specify a buffer descriptor
+// | | | 1 provider: process() function uses the
+// | | | bufferProvider indicated by the
+// | | | EFFECT_CMD_CONFIGURE command to request input.
+// | | | buffers.
+// | | | 2 both: both input modes are supported
+// | | | 3 reserved
+// +---------------------------+-----------+-----------------------------------
+// | Sample output mode | 11..12 | 0 direct: process() function or EFFECT_CMD_CONFIGURE
+// | | | command must specify a buffer descriptor
+// | | | 1 provider: process() function uses the
+// | | | bufferProvider indicated by the
+// | | | EFFECT_CMD_CONFIGURE command to request output
+// | | | buffers.
+// | | | 2 both: both output modes are supported
+// | | | 3 reserved
+// +---------------------------+-----------+-----------------------------------
+// | Hardware acceleration | 13..15 | 0 No hardware acceleration
+// | | | 1 non tunneled hw acceleration: the process() function
+// | | | reads the samples, send them to HW accelerated
+// | | | effect processor, reads back the processed samples
+// | | | and returns them to the output buffer.
+// | | | 2 tunneled hw acceleration: the process() function is
+// | | | transparent. The effect interface is only used to
+// | | | control the effect engine. This mode is relevant for
+// | | | global effects actually applied by the audio
+// | | | hardware on the output stream.
+// +---------------------------+-----------+-----------------------------------
+// | Audio Mode indication | 16..17 | 0 none
+// | | | 1 requires audio mode updates
+// | | | 2..3 reserved
+// +---------------------------+-----------+-----------------------------------
+
+// Insert mode
+#define EFFECT_FLAG_TYPE_MASK 0x00000003
+#define EFFECT_FLAG_TYPE_INSERT 0x00000000
+#define EFFECT_FLAG_TYPE_AUXILIARY 0x00000001
+#define EFFECT_FLAG_TYPE_REPLACE 0x00000002
+
+// Insert preference
+#define EFFECT_FLAG_INSERT_MASK 0x0000001C
+#define EFFECT_FLAG_INSERT_ANY 0x00000000
+#define EFFECT_FLAG_INSERT_FIRST 0x00000004
+#define EFFECT_FLAG_INSERT_LAST 0x00000008
+#define EFFECT_FLAG_INSERT_EXCLUSIVE 0x0000000C
+
+
+// Volume control
+#define EFFECT_FLAG_VOLUME_MASK 0x00000060
+#define EFFECT_FLAG_VOLUME_CTRL 0x00000020
+#define EFFECT_FLAG_VOLUME_IND 0x00000040
+#define EFFECT_FLAG_VOLUME_NONE 0x00000000
+
+// Device indication
+#define EFFECT_FLAG_DEVICE_MASK 0x00000180
+#define EFFECT_FLAG_DEVICE_IND 0x00000080
+#define EFFECT_FLAG_DEVICE_NONE 0x00000000
+
+// Sample input modes
+#define EFFECT_FLAG_INPUT_MASK 0x00000600
+#define EFFECT_FLAG_INPUT_DIRECT 0x00000000
+#define EFFECT_FLAG_INPUT_PROVIDER 0x00000200
+#define EFFECT_FLAG_INPUT_BOTH 0x00000400
+
+// Sample output modes
+#define EFFECT_FLAG_OUTPUT_MASK 0x00001800
+#define EFFECT_FLAG_OUTPUT_DIRECT 0x00000000
+#define EFFECT_FLAG_OUTPUT_PROVIDER 0x00000800
+#define EFFECT_FLAG_OUTPUT_BOTH 0x00001000
+
+// Hardware acceleration mode
+#define EFFECT_FLAG_HW_ACC_MASK 0x00006000
+#define EFFECT_FLAG_HW_ACC_SIMPLE 0x00002000
+#define EFFECT_FLAG_HW_ACC_TUNNEL 0x00004000
+
+// Audio mode indication
+#define EFFECT_FLAG_AUDIO_MODE_MASK 0x00018000
+#define EFFECT_FLAG_AUDIO_MODE_IND 0x00008000
+#define EFFECT_FLAG_AUDIO_MODE_NONE 0x00000000
+
+// Forward definition of type audio_buffer_t
+typedef struct audio_buffer_s audio_buffer_t;
+
+////////////////////////////////////////////////////////////////////////////////
+//
+// Function: process
+//
+// Description: Effect process function. Takes input samples as specified
+// (count and location) in input buffer descriptor and output processed
+// samples as specified in output buffer descriptor. If the buffer descriptor
+// is not specified the function must use either the buffer or the
+// buffer provider function installed by the EFFECT_CMD_CONFIGURE command.
+// The effect framework will call the process() function after the EFFECT_CMD_ENABLE
+// command is received and until the EFFECT_CMD_DISABLE is received. When the engine
+// receives the EFFECT_CMD_DISABLE command it should turn off the effect gracefully
+// and when done indicate that it is OK to stop calling the process() function by
+// returning the -ENODATA status.
+//
+// NOTE: the process() function implementation should be "real-time safe" that is
+// it should not perform blocking calls: malloc/free, sleep, read/write/open/close,
+// pthread_cond_wait/pthread_mutex_lock...
+//
+// Input:
+// effect_interface_t: handle to the effect interface this function
+// is called on.
+// inBuffer: buffer descriptor indicating where to read samples to process.
+// If NULL, use the configuration passed by EFFECT_CMD_CONFIGURE command.
+//
+// inBuffer: buffer descriptor indicating where to write processed samples.
+// If NULL, use the configuration passed by EFFECT_CMD_CONFIGURE command.
+//
+// Output:
+// returned value: 0 successful operation
+// -ENODATA the engine has finished the disable phase and the framework
+// can stop calling process()
+// -EINVAL invalid interface handle or
+// invalid input/output buffer description
+////////////////////////////////////////////////////////////////////////////////
+typedef int32_t (*effect_process_t)(effect_interface_t self,
+ audio_buffer_t *inBuffer,
+ audio_buffer_t *outBuffer);
+
+////////////////////////////////////////////////////////////////////////////////
+//
+// Function: command
+//
+// Description: Send a command and receive a response to/from effect engine.
+//
+// Input:
+// effect_interface_t: handle to the effect interface this function
+// is called on.
+// cmdCode: command code: the command can be a standardized command defined in
+// effect_command_e (see below) or a proprietary command.
+// cmdSize: size of command in bytes
+// pCmdData: pointer to command data
+// pReplyData: pointer to reply data
+//
+// Input/Output:
+// replySize: maximum size of reply data as input
+// actual size of reply data as output
+//
+// Output:
+// returned value: 0 successful operation
+// -EINVAL invalid interface handle or
+// invalid command/reply size or format according to command code
+// The return code should be restricted to indicate problems related to the this
+// API specification. Status related to the execution of a particular command should be
+// indicated as part of the reply field.
+//
+// *pReplyData updated with command response
+//
+////////////////////////////////////////////////////////////////////////////////
+typedef int32_t (*effect_command_t)(effect_interface_t self,
+ uint32_t cmdCode,
+ uint32_t cmdSize,
+ void *pCmdData,
+ uint32_t *replySize,
+ void *pReplyData);
+
+
+// Effect control interface definition
+struct effect_interface_s {
+ effect_process_t process;
+ effect_command_t command;
+};
+
+
+//
+//--- Standardized command codes for command() function
+//
+enum effect_command_e {
+ EFFECT_CMD_INIT, // initialize effect engine
+ EFFECT_CMD_CONFIGURE, // configure effect engine (see effect_config_t)
+ EFFECT_CMD_RESET, // reset effect engine
+ EFFECT_CMD_ENABLE, // enable effect process
+ EFFECT_CMD_DISABLE, // disable effect process
+ EFFECT_CMD_SET_PARAM, // set parameter immediately (see effect_param_t)
+ EFFECT_CMD_SET_PARAM_DEFERRED, // set parameter deferred
+ EFFECT_CMD_SET_PARAM_COMMIT, // commit previous set parameter deferred
+ EFFECT_CMD_GET_PARAM, // get parameter
+ EFFECT_CMD_SET_DEVICE, // set audio device (see audio_device_e)
+ EFFECT_CMD_SET_VOLUME, // set volume
+ EFFECT_CMD_SET_AUDIO_MODE, // set the audio mode (normal, ring, ...)
+ EFFECT_CMD_FIRST_PROPRIETARY = 0x10000 // first proprietary command code
+};
+
+//==================================================================================================
+// command: EFFECT_CMD_INIT
+//--------------------------------------------------------------------------------------------------
+// description:
+// Initialize effect engine: All configurations return to default
+//--------------------------------------------------------------------------------------------------
+// command format:
+// size: 0
+// data: N/A
+//--------------------------------------------------------------------------------------------------
+// reply format:
+// size: sizeof(int)
+// data: status
+//==================================================================================================
+// command: EFFECT_CMD_CONFIGURE
+//--------------------------------------------------------------------------------------------------
+// description:
+// Apply new audio parameters configurations for input and output buffers
+//--------------------------------------------------------------------------------------------------
+// command format:
+// size: sizeof(effect_config_t)
+// data: effect_config_t
+//--------------------------------------------------------------------------------------------------
+// reply format:
+// size: sizeof(int)
+// data: status
+//==================================================================================================
+// command: EFFECT_CMD_RESET
+//--------------------------------------------------------------------------------------------------
+// description:
+// Reset the effect engine. Keep configuration but resets state and buffer content
+//--------------------------------------------------------------------------------------------------
+// command format:
+// size: 0
+// data: N/A
+//--------------------------------------------------------------------------------------------------
+// reply format:
+// size: 0
+// data: N/A
+//==================================================================================================
+// command: EFFECT_CMD_ENABLE
+//--------------------------------------------------------------------------------------------------
+// description:
+// Enable the process. Called by the framework before the first call to process()
+//--------------------------------------------------------------------------------------------------
+// command format:
+// size: 0
+// data: N/A
+//--------------------------------------------------------------------------------------------------
+// reply format:
+// size: sizeof(int)
+// data: status
+//==================================================================================================
+// command: EFFECT_CMD_DISABLE
+//--------------------------------------------------------------------------------------------------
+// description:
+// Disable the process. Called by the framework after the last call to process()
+//--------------------------------------------------------------------------------------------------
+// command format:
+// size: 0
+// data: N/A
+//--------------------------------------------------------------------------------------------------
+// reply format:
+// size: sizeof(int)
+// data: status
+//==================================================================================================
+// command: EFFECT_CMD_SET_PARAM
+//--------------------------------------------------------------------------------------------------
+// description:
+// Set a parameter and apply it immediately
+//--------------------------------------------------------------------------------------------------
+// command format:
+// size: sizeof(effect_param_t) + size of param and value
+// data: effect_param_t + param + value. See effect_param_t definition below for value offset
+//--------------------------------------------------------------------------------------------------
+// reply format:
+// size: sizeof(int)
+// data: status
+//==================================================================================================
+// command: EFFECT_CMD_SET_PARAM_DEFERRED
+//--------------------------------------------------------------------------------------------------
+// description:
+// Set a parameter but apply it only when receiving EFFECT_CMD_SET_PARAM_COMMIT command
+//--------------------------------------------------------------------------------------------------
+// command format:
+// size: sizeof(effect_param_t) + size of param and value
+// data: effect_param_t + param + value. See effect_param_t definition below for value offset
+//--------------------------------------------------------------------------------------------------
+// reply format:
+// size: 0
+// data: N/A
+//==================================================================================================
+// command: EFFECT_CMD_SET_PARAM_COMMIT
+//--------------------------------------------------------------------------------------------------
+// description:
+// Apply all previously received EFFECT_CMD_SET_PARAM_DEFERRED commands
+//--------------------------------------------------------------------------------------------------
+// command format:
+// size: 0
+// data: N/A
+//--------------------------------------------------------------------------------------------------
+// reply format:
+// size: sizeof(int)
+// data: status
+//==================================================================================================
+// command: EFFECT_CMD_GET_PARAM
+//--------------------------------------------------------------------------------------------------
+// description:
+// Get a parameter value
+//--------------------------------------------------------------------------------------------------
+// command format:
+// size: sizeof(effect_param_t) + size of param
+// data: effect_param_t + param
+//--------------------------------------------------------------------------------------------------
+// reply format:
+// size: sizeof(effect_param_t) + size of param and value
+// data: effect_param_t + param + value. See effect_param_t definition below for value offset
+//==================================================================================================
+// command: EFFECT_CMD_SET_DEVICE
+//--------------------------------------------------------------------------------------------------
+// description:
+// Set the rendering device the audio output path is connected to. See audio_device_e for device
+// values.
+// The effect implementation must set EFFECT_FLAG_DEVICE_IND flag in its descriptor to receive this
+// command when the device changes
+//--------------------------------------------------------------------------------------------------
+// command format:
+// size: sizeof(uint32_t)
+// data: audio_device_e
+//--------------------------------------------------------------------------------------------------
+// reply format:
+// size: 0
+// data: N/A
+//==================================================================================================
+// command: EFFECT_CMD_SET_VOLUME
+//--------------------------------------------------------------------------------------------------
+// description:
+// Set and get volume. Used by audio framework to delegate volume control to effect engine.
+// The effect implementation must set EFFECT_FLAG_VOLUME_IND or EFFECT_FLAG_VOLUME_CTRL flag in
+// its descriptor to receive this command before every call to process() function
+// If EFFECT_FLAG_VOLUME_CTRL flag is set in the effect descriptor, the effect engine must return
+// the volume that should be applied before the effect is processed. The overall volume (the volume
+// actually applied by the effect engine multiplied by the returned value) should match the value
+// indicated in the command.
+//--------------------------------------------------------------------------------------------------
+// command format:
+// size: n * sizeof(uint32_t)
+// data: volume for each channel defined in effect_config_t for output buffer expressed in
+// 8.24 fixed point format
+//--------------------------------------------------------------------------------------------------
+// reply format:
+// size: n * sizeof(uint32_t) / 0
+// data: - if EFFECT_FLAG_VOLUME_CTRL is set in effect descriptor:
+// volume for each channel defined in effect_config_t for output buffer expressed in
+// 8.24 fixed point format
+// - if EFFECT_FLAG_VOLUME_CTRL is not set in effect descriptor:
+// N/A
+// It is legal to receive a null pointer as pReplyData in which case the effect framework has
+// delegated volume control to another effect
+//==================================================================================================
+// command: EFFECT_CMD_SET_AUDIO_MODE
+//--------------------------------------------------------------------------------------------------
+// description:
+// Set the audio mode. The effect implementation must set EFFECT_FLAG_AUDIO_MODE_IND flag in its
+// descriptor to receive this command when the audio mode changes.
+//--------------------------------------------------------------------------------------------------
+// command format:
+// size: sizeof(uint32_t)
+// data: audio_mode_e
+//--------------------------------------------------------------------------------------------------
+// reply format:
+// size: 0
+// data: N/A
+//==================================================================================================
+// command: EFFECT_CMD_FIRST_PROPRIETARY
+//--------------------------------------------------------------------------------------------------
+// description:
+// All proprietary effect commands must use command codes above this value. The size and format of
+// command and response fields is free in this case
+//==================================================================================================
+
+
+// Audio buffer descriptor used by process(), bufferProvider() functions and buffer_config_t
+// structure. Multi-channel audio is always interleaved. The channel order is from LSB to MSB with
+// regard to the channel mask definition in audio_channels_e e.g :
+// Stereo: left, right
+// 5 point 1: front left, front right, front center, low frequency, back left, back right
+// The buffer size is expressed in frame count, a frame being composed of samples for all
+// channels at a given time. Frame size for unspecified format (AUDIO_FORMAT_OTHER) is 8 bit by
+// definition
+struct audio_buffer_s {
+ size_t frameCount; // number of frames in buffer
+ union {
+ void* raw; // raw pointer to start of buffer
+ int32_t* s32; // pointer to signed 32 bit data at start of buffer
+ int16_t* s16; // pointer to signed 16 bit data at start of buffer
+ uint8_t* u8; // pointer to unsigned 8 bit data at start of buffer
+ };
+};
+
+// The buffer_provider_s structure contains functions that can be used
+// by the effect engine process() function to query and release input
+// or output audio buffer.
+// The getBuffer() function is called to retrieve a buffer where data
+// should read from or written to by process() function.
+// The releaseBuffer() function MUST be called when the buffer retrieved
+// with getBuffer() is not needed anymore.
+// The process function should use the buffer provider mechanism to retrieve
+// input or output buffer if the inBuffer or outBuffer passed as argument is NULL
+// and the buffer configuration (buffer_config_t) given by the EFFECT_CMD_CONFIGURE
+// command did not specify an audio buffer.
+
+typedef int32_t (* buffer_function_t)(void *cookie, audio_buffer_t *buffer);
+
+typedef struct buffer_provider_s {
+ buffer_function_t getBuffer; // retrieve next buffer
+ buffer_function_t releaseBuffer; // release used buffer
+ void *cookie; // for use by client of buffer provider functions
+} buffer_provider_t;
+
+
+// The buffer_config_s structure specifies the input or output audio format
+// to be used by the effect engine. It is part of the effect_config_t
+// structure that defines both input and output buffer configurations and is
+// passed by the EFFECT_CMD_CONFIGURE command.
+typedef struct buffer_config_s {
+ audio_buffer_t buffer; // buffer for use by process() function if not passed explicitly
+ uint32_t samplingRate; // sampling rate
+ uint32_t channels; // channel mask (see audio_channels_e)
+ buffer_provider_t bufferProvider; // buffer provider
+ uint8_t format; // Audio format (see audio_format_e)
+ uint8_t accessMode; // read/write or accumulate in buffer (effect_buffer_access_e)
+ uint16_t mask; // indicates which of the above fields is valid
+} buffer_config_t;
+
+// Sample format
+enum audio_format_e {
+ SAMPLE_FORMAT_PCM_S15, // PCM signed 16 bits
+ SAMPLE_FORMAT_PCM_U8, // PCM unsigned 8 bits
+ SAMPLE_FORMAT_PCM_S7_24, // PCM signed 7.24 fixed point representation
+ SAMPLE_FORMAT_OTHER // other format (e.g. compressed)
+};
+
+// Channel mask
+enum audio_channels_e {
+ CHANNEL_FRONT_LEFT = 0x1, // front left channel
+ CHANNEL_FRONT_RIGHT = 0x2, // front right channel
+ CHANNEL_FRONT_CENTER = 0x4, // front center channel
+ CHANNEL_LOW_FREQUENCY = 0x8, // low frequency channel
+ CHANNEL_BACK_LEFT = 0x10, // back left channel
+ CHANNEL_BACK_RIGHT = 0x20, // back right channel
+ CHANNEL_FRONT_LEFT_OF_CENTER = 0x40, // front left of center channel
+ CHANNEL_FRONT_RIGHT_OF_CENTER = 0x80, // front right of center channel
+ CHANNEL_BACK_CENTER = 0x100, // back center channel
+ CHANNEL_MONO = CHANNEL_FRONT_LEFT,
+ CHANNEL_STEREO = (CHANNEL_FRONT_LEFT | CHANNEL_FRONT_RIGHT),
+ CHANNEL_QUAD = (CHANNEL_FRONT_LEFT | CHANNEL_FRONT_RIGHT |
+ CHANNEL_BACK_LEFT | CHANNEL_BACK_RIGHT),
+ CHANNEL_SURROUND = (CHANNEL_FRONT_LEFT | CHANNEL_FRONT_RIGHT |
+ CHANNEL_FRONT_CENTER | CHANNEL_BACK_CENTER),
+ CHANNEL_5POINT1 = (CHANNEL_FRONT_LEFT | CHANNEL_FRONT_RIGHT |
+ CHANNEL_FRONT_CENTER | CHANNEL_LOW_FREQUENCY | CHANNEL_BACK_LEFT | CHANNEL_BACK_RIGHT),
+ CHANNEL_7POINT1 = (CHANNEL_FRONT_LEFT | CHANNEL_FRONT_RIGHT |
+ CHANNEL_FRONT_CENTER | CHANNEL_LOW_FREQUENCY | CHANNEL_BACK_LEFT | CHANNEL_BACK_RIGHT |
+ CHANNEL_FRONT_LEFT_OF_CENTER | CHANNEL_FRONT_RIGHT_OF_CENTER),
+};
+
+// Render device
+enum audio_device_e {
+ DEVICE_EARPIECE = 0x1, // earpiece
+ DEVICE_SPEAKER = 0x2, // speaker
+ DEVICE_WIRED_HEADSET = 0x4, // wired headset, with microphone
+ DEVICE_WIRED_HEADPHONE = 0x8, // wired headphone, without microphone
+ DEVICE_BLUETOOTH_SCO = 0x10, // generic bluetooth SCO
+ DEVICE_BLUETOOTH_SCO_HEADSET = 0x20, // bluetooth SCO headset
+ DEVICE_BLUETOOTH_SCO_CARKIT = 0x40, // bluetooth SCO car kit
+ DEVICE_BLUETOOTH_A2DP = 0x80, // generic bluetooth A2DP
+ DEVICE_BLUETOOTH_A2DP_HEADPHONES = 0x100, // bluetooth A2DP headphones
+ DEVICE_BLUETOOTH_A2DP_SPEAKER = 0x200, // bluetooth A2DP speakers
+ DEVICE_AUX_DIGITAL = 0x400, // digital output
+ DEVICE_EXTERNAL_SPEAKER = 0x800 // external speaker (stereo and High quality)
+};
+
+#if ANDROID_VERSION < 17
+// Audio mode
+enum audio_mode_e {
+ AUDIO_MODE_NORMAL, // device idle
+ AUDIO_MODE_RINGTONE, // device ringing
+ AUDIO_MODE_IN_CALL // audio call connected (VoIP or telephony)
+};
+#endif
+
+// Values for "accessMode" field of buffer_config_t:
+// overwrite, read only, accumulate (read/modify/write)
+enum effect_buffer_access_e {
+ EFFECT_BUFFER_ACCESS_WRITE,
+ EFFECT_BUFFER_ACCESS_READ,
+ EFFECT_BUFFER_ACCESS_ACCUMULATE
+
+};
+
+// Values for bit field "mask" in buffer_config_t. If a bit is set, the corresponding field
+// in buffer_config_t must be taken into account when executing the EFFECT_CMD_CONFIGURE command
+#define EFFECT_CONFIG_BUFFER 0x0001 // buffer field must be taken into account
+#define EFFECT_CONFIG_SMP_RATE 0x0002 // samplingRate field must be taken into account
+#define EFFECT_CONFIG_CHANNELS 0x0004 // channels field must be taken into account
+#define EFFECT_CONFIG_FORMAT 0x0008 // format field must be taken into account
+#define EFFECT_CONFIG_ACC_MODE 0x0010 // accessMode field must be taken into account
+#define EFFECT_CONFIG_PROVIDER 0x0020 // bufferProvider field must be taken into account
+#define EFFECT_CONFIG_ALL (EFFECT_CONFIG_BUFFER | EFFECT_CONFIG_SMP_RATE | \
+ EFFECT_CONFIG_CHANNELS | EFFECT_CONFIG_FORMAT | \
+ EFFECT_CONFIG_ACC_MODE | EFFECT_CONFIG_PROVIDER)
+
+
+// effect_config_s structure describes the format of the pCmdData argument of EFFECT_CMD_CONFIGURE
+// command to configure audio parameters and buffers for effect engine input and output.
+typedef struct effect_config_s {
+ buffer_config_t inputCfg;
+ buffer_config_t outputCfg;;
+} effect_config_t;
+
+
+// effect_param_s structure describes the format of the pCmdData argument of EFFECT_CMD_SET_PARAM
+// command and pCmdData and pReplyData of EFFECT_CMD_GET_PARAM command.
+// psize and vsize represent the actual size of parameter and value.
+//
+// NOTE: the start of value field inside the data field is always on a 32 bit boundary:
+//
+// +-----------+
+// | status | sizeof(int)
+// +-----------+
+// | psize | sizeof(int)
+// +-----------+
+// | vsize | sizeof(int)
+// +-----------+
+// | | | |
+// ~ parameter ~ > psize |
+// | | | > ((psize - 1)/sizeof(int) + 1) * sizeof(int)
+// +-----------+ |
+// | padding | |
+// +-----------+
+// | | |
+// ~ value ~ > vsize
+// | | |
+// +-----------+
+
+typedef struct effect_param_s {
+ int32_t status; // Transaction status (unused for command, used for reply)
+ uint32_t psize; // Parameter size
+ uint32_t vsize; // Value size
+ char data[]; // Start of Parameter + Value data
+} effect_param_t;
+
+
+/////////////////////////////////////////////////
+// Effect library interface
+/////////////////////////////////////////////////
+
+// An effect library is required to implement and expose the following functions
+// to enable effect enumeration and instantiation. The name of these functions must be as
+// specified here as the effect framework will get the function address with dlsym():
+//
+// - effect_QueryNumberEffects_t EffectQueryNumberEffects;
+// - effect_QueryEffect_t EffectQueryEffect;
+// - effect_CreateEffect_t EffectCreate;
+// - effect_ReleaseEffect_t EffectRelease;
+
+
+////////////////////////////////////////////////////////////////////////////////
+//
+// Function: EffectQueryNumberEffects
+//
+// Description: Returns the number of different effects exposed by the
+// library. Each effect must have a unique effect uuid (see
+// effect_descriptor_t). This function together with EffectQueryEffect()
+// is used to enumerate all effects present in the library.
+//
+// Input/Output:
+// pNumEffects: address where the number of effects should be returned.
+//
+// Output:
+// returned value: 0 successful operation.
+// -ENODEV library failed to initialize
+// -EINVAL invalid pNumEffects
+// *pNumEffects: updated with number of effects in library
+//
+////////////////////////////////////////////////////////////////////////////////
+typedef int32_t (*effect_QueryNumberEffects_t)(uint32_t *pNumEffects);
+
+////////////////////////////////////////////////////////////////////////////////
+//
+// Function: EffectQueryEffect
+//
+// Description: Returns the descriptor of the effect engine which index is
+// given as first argument.
+// See effect_descriptor_t for details on effect descriptors.
+// This function together with EffectQueryNumberEffects() is used to enumerate all
+// effects present in the library. The enumeration sequence is:
+// EffectQueryNumberEffects(&num_effects);
+// for (i = 0; i < num_effects; i++)
+// EffectQueryEffect(i,...);
+//
+// Input/Output:
+// index: index of the effect
+// pDescriptor: address where to return the effect descriptor.
+//
+// Output:
+// returned value: 0 successful operation.
+// -ENODEV library failed to initialize
+// -EINVAL invalid pDescriptor or index
+// -ENOSYS effect list has changed since last execution of
+// EffectQueryNumberEffects()
+// -ENOENT no more effect available
+// *pDescriptor: updated with the effect descriptor.
+//
+////////////////////////////////////////////////////////////////////////////////
+typedef int32_t (*effect_QueryEffect_t)(uint32_t index,
+ effect_descriptor_t *pDescriptor);
+
+////////////////////////////////////////////////////////////////////////////////
+//
+// Function: EffectCreate
+//
+// Description: Creates an effect engine of the specified type and returns an
+// effect control interface on this engine. The function will allocate the
+// resources for an instance of the requested effect engine and return
+// a handle on the effect control interface.
+//
+// Input:
+// uuid: pointer to the effect uuid.
+// sessionId: audio session to which this effect instance will be attached. All effects
+// created with the same session ID are connected in series and process the same signal
+// stream. Knowing that two effects are part of the same effect chain can help the
+// library implement some kind of optimizations.
+// ioId: identifies the output or input stream this effect is directed to at audio HAL.
+// For future use especially with tunneled HW accelerated effects
+//
+// Input/Output:
+// pInterface: address where to return the effect interface.
+//
+// Output:
+// returned value: 0 successful operation.
+// -ENODEV library failed to initialize
+// -EINVAL invalid pEffectUuid or pInterface
+// -ENOENT no effect with this uuid found
+// *pInterface: updated with the effect interface handle.
+//
+////////////////////////////////////////////////////////////////////////////////
+typedef int32_t (*effect_CreateEffect_t)(effect_uuid_t *uuid,
+ int32_t sessionId,
+ int32_t ioId,
+ effect_interface_t *pInterface);
+
+////////////////////////////////////////////////////////////////////////////////
+//
+// Function: EffectRelease
+//
+// Description: Releases the effect engine whose handle is given as argument.
+// All resources allocated to this particular instance of the effect are
+// released.
+//
+// Input:
+// interface: handle on the effect interface to be released.
+//
+// Output:
+// returned value: 0 successful operation.
+// -ENODEV library failed to initialize
+// -EINVAL invalid interface handle
+//
+////////////////////////////////////////////////////////////////////////////////
+typedef int32_t (*effect_ReleaseEffect_t)(effect_interface_t interface);
+
+
+#if __cplusplus
+} // extern "C"
+#endif
+
+
+#endif /*ANDROID_EFFECTAPI_H_*/
diff --git a/dom/system/gonk/android_audio/IAudioFlinger.h b/dom/system/gonk/android_audio/IAudioFlinger.h
new file mode 100644
index 000000000..b10d3ab93
--- /dev/null
+++ b/dom/system/gonk/android_audio/IAudioFlinger.h
@@ -0,0 +1,184 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_IAUDIOFLINGER_H
+#define ANDROID_IAUDIOFLINGER_H
+
+#include <stdint.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <utils/RefBase.h>
+#include <utils/Errors.h>
+#include <binder/IInterface.h>
+#include "IAudioTrack.h"
+#include "IAudioRecord.h"
+#include "IAudioFlingerClient.h"
+#include "EffectApi.h"
+#include "IEffect.h"
+#include "IEffectClient.h"
+#include <utils/String8.h>
+
+namespace android {
+
+// ----------------------------------------------------------------------------
+
+class IAudioFlinger : public IInterface
+{
+public:
+ DECLARE_META_INTERFACE(AudioFlinger);
+
+ /* create an audio track and registers it with AudioFlinger.
+ * return null if the track cannot be created.
+ */
+ virtual sp<IAudioTrack> createTrack(
+ pid_t pid,
+ int streamType,
+ uint32_t sampleRate,
+ int format,
+ int channelCount,
+ int frameCount,
+ uint32_t flags,
+ const sp<IMemory>& sharedBuffer,
+ int output,
+ int *sessionId,
+ status_t *status) = 0;
+
+ virtual sp<IAudioRecord> openRecord(
+ pid_t pid,
+ int input,
+ uint32_t sampleRate,
+ int format,
+ int channelCount,
+ int frameCount,
+ uint32_t flags,
+ int *sessionId,
+ status_t *status) = 0;
+
+ /* query the audio hardware state. This state never changes,
+ * and therefore can be cached.
+ */
+ virtual uint32_t sampleRate(int output) const = 0;
+ virtual int channelCount(int output) const = 0;
+ virtual int format(int output) const = 0;
+ virtual size_t frameCount(int output) const = 0;
+ virtual uint32_t latency(int output) const = 0;
+
+ /* set/get the audio hardware state. This will probably be used by
+ * the preference panel, mostly.
+ */
+ virtual status_t setMasterVolume(float value) = 0;
+ virtual status_t setMasterMute(bool muted) = 0;
+
+ virtual float masterVolume() const = 0;
+ virtual bool masterMute() const = 0;
+
+ /* set/get stream type state. This will probably be used by
+ * the preference panel, mostly.
+ */
+ virtual status_t setStreamVolume(int stream, float value, int output) = 0;
+ virtual status_t setStreamMute(int stream, bool muted) = 0;
+
+ virtual float streamVolume(int stream, int output) const = 0;
+ virtual bool streamMute(int stream) const = 0;
+
+ // set audio mode
+ virtual status_t setMode(int mode) = 0;
+
+ // mic mute/state
+ virtual status_t setMicMute(bool state) = 0;
+ virtual bool getMicMute() const = 0;
+
+ // is any track active on this stream?
+ virtual bool isStreamActive(int stream) const = 0;
+
+ virtual status_t setParameters(int ioHandle, const String8& keyValuePairs) = 0;
+ virtual String8 getParameters(int ioHandle, const String8& keys) = 0;
+
+ // register a current process for audio output change notifications
+ virtual void registerClient(const sp<IAudioFlingerClient>& client) = 0;
+
+ // retrieve the audio recording buffer size
+ virtual size_t getInputBufferSize(uint32_t sampleRate, int format, int channelCount) = 0;
+
+ virtual int openOutput(uint32_t *pDevices,
+ uint32_t *pSamplingRate,
+ uint32_t *pFormat,
+ uint32_t *pChannels,
+ uint32_t *pLatencyMs,
+ uint32_t flags) = 0;
+ virtual int openDuplicateOutput(int output1, int output2) = 0;
+ virtual status_t closeOutput(int output) = 0;
+ virtual status_t suspendOutput(int output) = 0;
+ virtual status_t restoreOutput(int output) = 0;
+
+ virtual int openInput(uint32_t *pDevices,
+ uint32_t *pSamplingRate,
+ uint32_t *pFormat,
+ uint32_t *pChannels,
+ uint32_t acoustics) = 0;
+ virtual status_t closeInput(int input) = 0;
+
+ virtual status_t setStreamOutput(uint32_t stream, int output) = 0;
+
+ virtual status_t setVoiceVolume(float volume) = 0;
+
+ virtual status_t getRenderPosition(uint32_t *halFrames, uint32_t *dspFrames, int output) = 0;
+
+ virtual unsigned int getInputFramesLost(int ioHandle) = 0;
+
+ virtual int newAudioSessionId() = 0;
+
+ virtual status_t loadEffectLibrary(const char *libPath, int *handle) = 0;
+
+ virtual status_t unloadEffectLibrary(int handle) = 0;
+
+ virtual status_t queryNumberEffects(uint32_t *numEffects) = 0;
+
+ virtual status_t queryEffect(uint32_t index, effect_descriptor_t *pDescriptor) = 0;
+
+ virtual status_t getEffectDescriptor(effect_uuid_t *pEffectUUID, effect_descriptor_t *pDescriptor) = 0;
+
+ virtual sp<IEffect> createEffect(pid_t pid,
+ effect_descriptor_t *pDesc,
+ const sp<IEffectClient>& client,
+ int32_t priority,
+ int output,
+ int sessionId,
+ status_t *status,
+ int *id,
+ int *enabled) = 0;
+
+ virtual status_t moveEffects(int session, int srcOutput, int dstOutput) = 0;
+};
+
+
+// ----------------------------------------------------------------------------
+
+class BnAudioFlinger : public BnInterface<IAudioFlinger>
+{
+public:
+ virtual status_t onTransact( uint32_t code,
+ const Parcel& data,
+ Parcel* reply,
+ uint32_t flags = 0);
+};
+
+// ----------------------------------------------------------------------------
+
+}; // namespace android
+
+#endif // ANDROID_IAUDIOFLINGER_H
diff --git a/dom/system/gonk/android_audio/IAudioFlingerClient.h b/dom/system/gonk/android_audio/IAudioFlingerClient.h
new file mode 100644
index 000000000..aa0cdcff1
--- /dev/null
+++ b/dom/system/gonk/android_audio/IAudioFlingerClient.h
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_IAUDIOFLINGERCLIENT_H
+#define ANDROID_IAUDIOFLINGERCLIENT_H
+
+
+#include <utils/RefBase.h>
+#include <binder/IInterface.h>
+#include <utils/KeyedVector.h>
+
+namespace android {
+
+// ----------------------------------------------------------------------------
+
+class IAudioFlingerClient : public IInterface
+{
+public:
+ DECLARE_META_INTERFACE(AudioFlingerClient);
+
+ // Notifies a change of audio input/output configuration.
+ virtual void ioConfigChanged(int event, int ioHandle, void *param2) = 0;
+
+};
+
+
+// ----------------------------------------------------------------------------
+
+class BnAudioFlingerClient : public BnInterface<IAudioFlingerClient>
+{
+public:
+ virtual status_t onTransact( uint32_t code,
+ const Parcel& data,
+ Parcel* reply,
+ uint32_t flags = 0);
+};
+
+// ----------------------------------------------------------------------------
+
+}; // namespace android
+
+#endif // ANDROID_IAUDIOFLINGERCLIENT_H
diff --git a/dom/system/gonk/android_audio/IAudioRecord.h b/dom/system/gonk/android_audio/IAudioRecord.h
new file mode 100644
index 000000000..46735def2
--- /dev/null
+++ b/dom/system/gonk/android_audio/IAudioRecord.h
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef IAUDIORECORD_H_
+#define IAUDIORECORD_H_
+
+#include <stdint.h>
+#include <sys/types.h>
+
+#include <utils/RefBase.h>
+#include <utils/Errors.h>
+#include <binder/IInterface.h>
+#include <binder/IMemory.h>
+
+
+namespace android {
+
+// ----------------------------------------------------------------------------
+
+class IAudioRecord : public IInterface
+{
+public:
+ DECLARE_META_INTERFACE(AudioRecord);
+
+ /* After it's created the track is not active. Call start() to
+ * make it active. If set, the callback will start being called.
+ */
+ virtual status_t start() = 0;
+
+ /* Stop a track. If set, the callback will cease being called and
+ * obtainBuffer will return an error. Buffers that are already released
+ * will be processed, unless flush() is called.
+ */
+ virtual void stop() = 0;
+
+ /* get this tracks control block */
+ virtual sp<IMemory> getCblk() const = 0;
+};
+
+// ----------------------------------------------------------------------------
+
+class BnAudioRecord : public BnInterface<IAudioRecord>
+{
+public:
+ virtual status_t onTransact( uint32_t code,
+ const Parcel& data,
+ Parcel* reply,
+ uint32_t flags = 0);
+};
+
+// ----------------------------------------------------------------------------
+
+}; // namespace android
+
+#endif /*IAUDIORECORD_H_*/
diff --git a/dom/system/gonk/android_audio/IAudioTrack.h b/dom/system/gonk/android_audio/IAudioTrack.h
new file mode 100644
index 000000000..47d530be5
--- /dev/null
+++ b/dom/system/gonk/android_audio/IAudioTrack.h
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_IAUDIOTRACK_H
+#define ANDROID_IAUDIOTRACK_H
+
+#include <stdint.h>
+#include <sys/types.h>
+
+#include <utils/RefBase.h>
+#include <utils/Errors.h>
+#include <binder/IInterface.h>
+#include <binder/IMemory.h>
+
+
+namespace android {
+
+// ----------------------------------------------------------------------------
+
+class IAudioTrack : public IInterface
+{
+public:
+ DECLARE_META_INTERFACE(AudioTrack);
+
+ /* After it's created the track is not active. Call start() to
+ * make it active. If set, the callback will start being called.
+ */
+ virtual status_t start() = 0;
+
+ /* Stop a track. If set, the callback will cease being called and
+ * obtainBuffer will return an error. Buffers that are already released
+ * will be processed, unless flush() is called.
+ */
+ virtual void stop() = 0;
+
+ /* flush a stopped track. All pending buffers are discarded.
+ * This function has no effect if the track is not stoped.
+ */
+ virtual void flush() = 0;
+
+ /* mute or unmutes this track.
+ * While mutted, the callback, if set, is still called.
+ */
+ virtual void mute(bool) = 0;
+
+ /* Pause a track. If set, the callback will cease being called and
+ * obtainBuffer will return an error. Buffers that are already released
+ * will be processed, unless flush() is called.
+ */
+ virtual void pause() = 0;
+
+ /* Attach track auxiliary output to specified effect. Use effectId = 0
+ * to detach track from effect.
+ */
+ virtual status_t attachAuxEffect(int effectId) = 0;
+
+ /* get this tracks control block */
+ virtual sp<IMemory> getCblk() const = 0;
+};
+
+// ----------------------------------------------------------------------------
+
+class BnAudioTrack : public BnInterface<IAudioTrack>
+{
+public:
+ virtual status_t onTransact( uint32_t code,
+ const Parcel& data,
+ Parcel* reply,
+ uint32_t flags = 0);
+};
+
+// ----------------------------------------------------------------------------
+
+}; // namespace android
+
+#endif // ANDROID_IAUDIOTRACK_H
diff --git a/dom/system/gonk/android_audio/IEffect.h b/dom/system/gonk/android_audio/IEffect.h
new file mode 100644
index 000000000..ff04869e0
--- /dev/null
+++ b/dom/system/gonk/android_audio/IEffect.h
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_IEFFECT_H
+#define ANDROID_IEFFECT_H
+
+#include <utils/RefBase.h>
+#include <binder/IInterface.h>
+#include <binder/Parcel.h>
+#include <binder/IMemory.h>
+
+namespace android {
+
+class IEffect: public IInterface
+{
+public:
+ DECLARE_META_INTERFACE(Effect);
+
+ virtual status_t enable() = 0;
+
+ virtual status_t disable() = 0;
+
+ virtual status_t command(uint32_t cmdCode,
+ uint32_t cmdSize,
+ void *pCmdData,
+ uint32_t *pReplySize,
+ void *pReplyData) = 0;
+
+ virtual void disconnect() = 0;
+
+ virtual sp<IMemory> getCblk() const = 0;
+};
+
+// ----------------------------------------------------------------------------
+
+class BnEffect: public BnInterface<IEffect>
+{
+public:
+ virtual status_t onTransact( uint32_t code,
+ const Parcel& data,
+ Parcel* reply,
+ uint32_t flags = 0);
+};
+
+}; // namespace android
+
+#endif // ANDROID_IEFFECT_H
diff --git a/dom/system/gonk/android_audio/IEffectClient.h b/dom/system/gonk/android_audio/IEffectClient.h
new file mode 100644
index 000000000..2f78c98f1
--- /dev/null
+++ b/dom/system/gonk/android_audio/IEffectClient.h
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_IEFFECTCLIENT_H
+#define ANDROID_IEFFECTCLIENT_H
+
+#include <utils/RefBase.h>
+#include <binder/IInterface.h>
+#include <binder/Parcel.h>
+#include <binder/IMemory.h>
+
+namespace android {
+
+class IEffectClient: public IInterface
+{
+public:
+ DECLARE_META_INTERFACE(EffectClient);
+
+ virtual void controlStatusChanged(bool controlGranted) = 0;
+ virtual void enableStatusChanged(bool enabled) = 0;
+ virtual void commandExecuted(uint32_t cmdCode,
+ uint32_t cmdSize,
+ void *pCmdData,
+ uint32_t replySize,
+ void *pReplyData) = 0;
+};
+
+// ----------------------------------------------------------------------------
+
+class BnEffectClient: public BnInterface<IEffectClient>
+{
+public:
+ virtual status_t onTransact( uint32_t code,
+ const Parcel& data,
+ Parcel* reply,
+ uint32_t flags = 0);
+};
+
+}; // namespace android
+
+#endif // ANDROID_IEFFECTCLIENT_H
diff --git a/dom/system/gonk/moz.build b/dom/system/gonk/moz.build
new file mode 100644
index 000000000..229baaab4
--- /dev/null
+++ b/dom/system/gonk/moz.build
@@ -0,0 +1,107 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# Copyright 2013 Mozilla Foundation and Mozilla contributors
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+XPIDL_SOURCES += [
+ 'nsIAudioManager.idl',
+ 'nsINetworkInterface.idl',
+ 'nsINetworkInterfaceListService.idl',
+ 'nsINetworkManager.idl',
+ 'nsINetworkService.idl',
+ 'nsINetworkWorker.idl',
+ 'nsISystemWorkerManager.idl',
+ 'nsITetheringService.idl',
+ 'nsIVolume.idl',
+ 'nsIVolumeMountLock.idl',
+ 'nsIVolumeService.idl',
+ 'nsIVolumeStat.idl',
+ 'nsIWorkerHolder.idl',
+]
+
+XPIDL_MODULE = 'dom_system_gonk'
+
+EXPORTS += [
+ 'GeolocationUtil.h',
+ 'GonkGPSGeolocationProvider.h',
+ 'nsVolume.h',
+ 'nsVolumeService.h',
+ 'SystemProperty.h',
+]
+UNIFIED_SOURCES += [
+ 'AudioChannelManager.cpp',
+ 'AudioManager.cpp',
+ 'AutoMounter.cpp',
+ 'AutoMounterSetting.cpp',
+ 'GeolocationUtil.cpp',
+ 'GonkGPSGeolocationProvider.cpp',
+ 'MozMtpDatabase.cpp',
+ 'MozMtpServer.cpp',
+ 'MozMtpStorage.cpp',
+ 'NetIdManager.cpp',
+ 'NetworkUtils.cpp',
+ 'NetworkWorker.cpp',
+ 'nsVolume.cpp',
+ 'nsVolumeMountLock.cpp',
+ 'nsVolumeService.cpp',
+ 'nsVolumeStat.cpp',
+ 'OpenFileFinder.cpp',
+ 'SystemProperty.cpp',
+ 'SystemWorkerManager.cpp',
+ 'TimeZoneSettingObserver.cpp',
+ 'Volume.cpp',
+ 'VolumeCommand.cpp',
+ 'VolumeManager.cpp',
+ 'VolumeServiceIOThread.cpp',
+ 'VolumeServiceTest.cpp',
+]
+
+if CONFIG['ANDROID_VERSION'] >= '17':
+ LOCAL_INCLUDES += ['%' + '%s/frameworks/av/media/mtp' % CONFIG['ANDROID_SOURCE']]
+else:
+ LOCAL_INCLUDES += ['%' + '%s/frameworks/base/media/mtp' % CONFIG['ANDROID_SOURCE']]
+
+if CONFIG['ENABLE_TESTS']:
+ XPCSHELL_TESTS_MANIFESTS += ['tests/xpcshell.ini']
+
+EXTRA_COMPONENTS += [
+ 'NetworkInterfaceListService.js',
+ 'NetworkInterfaceListService.manifest',
+ 'NetworkManager.js',
+ 'NetworkManager.manifest',
+ 'NetworkService.js',
+ 'NetworkService.manifest',
+ 'TetheringService.js',
+ 'TetheringService.manifest',
+]
+EXTRA_JS_MODULES += [
+ 'systemlibs.js',
+]
+
+include('/ipc/chromium/chromium-config.mozbuild')
+
+DEFINES['HAVE_ANDROID_OS'] = True
+
+LOCAL_INCLUDES += [
+ '/dom/base',
+ '/dom/bluetooth/common',
+ '/dom/geolocation',
+ '/dom/wifi',
+]
+
+FINAL_LIBRARY = 'xul'
+
+FINAL_TARGET_FILES.modules.workers += [
+ 'worker_buf.js',
+]
diff --git a/dom/system/gonk/mozstumbler/MozStumbler.cpp b/dom/system/gonk/mozstumbler/MozStumbler.cpp
new file mode 100644
index 000000000..61e09e705
--- /dev/null
+++ b/dom/system/gonk/mozstumbler/MozStumbler.cpp
@@ -0,0 +1,426 @@
+/* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "MozStumbler.h"
+#include "nsDataHashtable.h"
+#include "nsGeoPosition.h"
+#include "nsNetCID.h"
+#include "nsPrintfCString.h"
+#include "StumblerLogging.h"
+#include "WriteStumbleOnThread.h"
+#include "../GeolocationUtil.h"
+
+#include "nsIInterfaceRequestor.h"
+#include "nsIInterfaceRequestorUtils.h"
+#include "nsIMobileConnectionInfo.h"
+#include "nsIMobileConnectionService.h"
+#include "nsIMobileCellInfo.h"
+#include "nsIMobileNetworkInfo.h"
+#include "nsINetworkInterface.h"
+#include "nsIRadioInterfaceLayer.h"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+
+
+NS_IMPL_ISUPPORTS(StumblerInfo, nsICellInfoListCallback, nsIWifiScanResultsReady)
+
+class RequestCellInfoEvent : public Runnable {
+public:
+ RequestCellInfoEvent(StumblerInfo *callback)
+ : mRequestCallback(callback)
+ {}
+
+ NS_IMETHOD Run() override {
+ MOZ_ASSERT(NS_IsMainThread());
+ // Get Cell Info
+ nsCOMPtr<nsIMobileConnectionService> service =
+ do_GetService(NS_MOBILE_CONNECTION_SERVICE_CONTRACTID);
+
+ if (!service) {
+ STUMBLER_ERR("Stumbler-can not get nsIMobileConnectionService \n");
+ return NS_OK;
+ }
+ nsCOMPtr<nsIMobileConnection> connection;
+ uint32_t numberOfRilServices = 1, cellInfoNum = 0;
+
+ service->GetNumItems(&numberOfRilServices);
+ for (uint32_t rilNum = 0; rilNum < numberOfRilServices; rilNum++) {
+ service->GetItemByServiceId(rilNum /* Client Id */, getter_AddRefs(connection));
+ if (!connection) {
+ STUMBLER_ERR("Stumbler-can not get nsIMobileConnection by ServiceId %d \n", rilNum);
+ } else {
+ cellInfoNum++;
+ connection->GetCellInfoList(mRequestCallback);
+ }
+ }
+ mRequestCallback->SetCellInfoResponsesExpected(cellInfoNum);
+
+ // Get Wifi AP Info
+ nsCOMPtr<nsIInterfaceRequestor> ir = do_GetService("@mozilla.org/telephony/system-worker-manager;1");
+ nsCOMPtr<nsIWifi> wifi = do_GetInterface(ir);
+ if (!wifi) {
+ mRequestCallback->SetWifiInfoResponseReceived();
+ STUMBLER_ERR("Stumbler-can not get nsIWifi interface\n");
+ return NS_OK;
+ }
+ wifi->GetWifiScanResults(mRequestCallback);
+ return NS_OK;
+ }
+private:
+ RefPtr<StumblerInfo> mRequestCallback;
+};
+
+void
+MozStumble(nsGeoPosition* position)
+{
+ if (WriteStumbleOnThread::IsFileWaitingForUpload()) {
+ nsCOMPtr<nsIEventTarget> target = do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID);
+ MOZ_ASSERT(target);
+ // Knowing that file is waiting to upload, and no collection will take place,
+ // just trigger the thread with an empty string.
+ nsCOMPtr<nsIRunnable> event = new WriteStumbleOnThread(EmptyCString());
+ target->Dispatch(event, NS_DISPATCH_NORMAL);
+ return;
+ }
+
+ nsCOMPtr<nsIDOMGeoPositionCoords> coords;
+ position->GetCoords(getter_AddRefs(coords));
+ if (!coords) {
+ return;
+ }
+
+ double latitude, longitude;
+ coords->GetLatitude(&latitude);
+ coords->GetLongitude(&longitude);
+
+ const double kMinChangeInMeters = 30;
+ static int64_t lastTime_ms = 0;
+ static double sLastLat = 0;
+ static double sLastLon = 0;
+ double delta = -1.0;
+ int64_t timediff = (PR_Now() / PR_USEC_PER_MSEC) - lastTime_ms;
+
+ if (0 != sLastLon || 0 != sLastLat) {
+ delta = CalculateDeltaInMeter(latitude, longitude, sLastLat, sLastLon);
+ }
+ STUMBLER_DBG("Stumbler-Location. [%f , %f] time_diff:%lld, delta : %f\n",
+ longitude, latitude, timediff, delta);
+
+ // Consecutive GPS locations must be 30 meters and 3 seconds apart
+ if (lastTime_ms == 0 || ((timediff >= STUMBLE_INTERVAL_MS) && (delta > kMinChangeInMeters))){
+ lastTime_ms = (PR_Now() / PR_USEC_PER_MSEC);
+ sLastLat = latitude;
+ sLastLon = longitude;
+ RefPtr<StumblerInfo> requestCallback = new StumblerInfo(position);
+ RefPtr<RequestCellInfoEvent> runnable = new RequestCellInfoEvent(requestCallback);
+ NS_DispatchToMainThread(runnable);
+ } else {
+ STUMBLER_DBG("Stumbler-GPS locations less than 30 meters and 3 seconds. Ignore!\n");
+ }
+}
+
+void
+StumblerInfo::SetWifiInfoResponseReceived()
+{
+ mIsWifiInfoResponseReceived = true;
+
+ if (mIsWifiInfoResponseReceived && mCellInfoResponsesReceived == mCellInfoResponsesExpected) {
+ STUMBLER_DBG("Call DumpStumblerInfo from SetWifiInfoResponseReceived\n");
+ DumpStumblerInfo();
+ }
+}
+
+void
+StumblerInfo::SetCellInfoResponsesExpected(uint8_t count)
+{
+ mCellInfoResponsesExpected = count;
+ STUMBLER_DBG("SetCellInfoNum (%d)\n", count);
+
+ if (mIsWifiInfoResponseReceived && mCellInfoResponsesReceived == mCellInfoResponsesExpected) {
+ STUMBLER_DBG("Call DumpStumblerInfo from SetCellInfoResponsesExpected\n");
+ DumpStumblerInfo();
+ }
+}
+
+
+#define TEXT_LAT NS_LITERAL_CSTRING("latitude")
+#define TEXT_LON NS_LITERAL_CSTRING("longitude")
+#define TEXT_ACC NS_LITERAL_CSTRING("accuracy")
+#define TEXT_ALT NS_LITERAL_CSTRING("altitude")
+#define TEXT_ALTACC NS_LITERAL_CSTRING("altitudeAccuracy")
+#define TEXT_HEAD NS_LITERAL_CSTRING("heading")
+#define TEXT_SPD NS_LITERAL_CSTRING("speed")
+
+nsresult
+StumblerInfo::LocationInfoToString(nsACString& aLocDesc)
+{
+ nsCOMPtr<nsIDOMGeoPositionCoords> coords;
+ mPosition->GetCoords(getter_AddRefs(coords));
+ if (!coords) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsDataHashtable<nsCStringHashKey, double> info;
+
+ double val;
+ coords->GetLatitude(&val);
+ info.Put(TEXT_LAT, val);
+ coords->GetLongitude(&val);
+ info.Put(TEXT_LON, val);
+ coords->GetAccuracy(&val);
+ info.Put(TEXT_ACC, val);
+ coords->GetAltitude(&val);
+ info.Put(TEXT_ALT, val);
+ coords->GetAltitudeAccuracy(&val);
+ info.Put(TEXT_ALTACC, val);
+ coords->GetHeading(&val);
+ info.Put(TEXT_HEAD, val);
+ coords->GetSpeed(&val);
+ info.Put(TEXT_SPD, val);
+
+ for (auto it = info.Iter(); !it.Done(); it.Next()) {
+ const nsACString& key = it.Key();
+ val = it.UserData();
+ if (!IsNaN(val)) {
+ aLocDesc += nsPrintfCString("\"%s\":%f,", key.BeginReading(), val);
+ }
+ }
+
+ aLocDesc += nsPrintfCString("\"timestamp\":%lld,", PR_Now() / PR_USEC_PER_MSEC).get();
+ return NS_OK;
+}
+
+#define TEXT_RADIOTYPE NS_LITERAL_CSTRING("radioType")
+#define TEXT_MCC NS_LITERAL_CSTRING("mobileCountryCode")
+#define TEXT_MNC NS_LITERAL_CSTRING("mobileNetworkCode")
+#define TEXT_LAC NS_LITERAL_CSTRING("locationAreaCode")
+#define TEXT_CID NS_LITERAL_CSTRING("cellId")
+#define TEXT_PSC NS_LITERAL_CSTRING("psc")
+#define TEXT_STRENGTH_ASU NS_LITERAL_CSTRING("asu")
+#define TEXT_STRENGTH_DBM NS_LITERAL_CSTRING("signalStrength")
+#define TEXT_REGISTERED NS_LITERAL_CSTRING("serving")
+#define TEXT_TIMEING_ADVANCE NS_LITERAL_CSTRING("timingAdvance")
+
+template <class T> void
+ExtractCommonNonCDMACellInfoItems(nsCOMPtr<T>& cell, nsDataHashtable<nsCStringHashKey, int32_t>& info)
+{
+ int32_t mcc, mnc, cid, sig;
+
+ cell->GetMcc(&mcc);
+ cell->GetMnc(&mnc);
+ cell->GetCid(&cid);
+ cell->GetSignalStrength(&sig);
+
+ info.Put(TEXT_MCC, mcc);
+ info.Put(TEXT_MNC, mnc);
+ info.Put(TEXT_CID, cid);
+ info.Put(TEXT_STRENGTH_ASU, sig);
+}
+
+void
+StumblerInfo::CellNetworkInfoToString(nsACString& aCellDesc)
+{
+ aCellDesc += "\"cellTowers\": [";
+
+ for (uint32_t idx = 0; idx < mCellInfo.Length() ; idx++) {
+ const char* radioType = 0;
+ int32_t type;
+ mCellInfo[idx]->GetType(&type);
+ bool registered;
+ mCellInfo[idx]->GetRegistered(&registered);
+ if (idx) {
+ aCellDesc += ",{";
+ } else {
+ aCellDesc += "{";
+ }
+
+ STUMBLER_DBG("type=%d\n", type);
+
+ nsDataHashtable<nsCStringHashKey, int32_t> info;
+ info.Put(TEXT_REGISTERED, registered);
+
+ if(type == nsICellInfo::CELL_INFO_TYPE_GSM) {
+ radioType = "gsm";
+ nsCOMPtr<nsIGsmCellInfo> gsmCellInfo = do_QueryInterface(mCellInfo[idx]);
+ ExtractCommonNonCDMACellInfoItems(gsmCellInfo, info);
+ int32_t lac;
+ gsmCellInfo->GetLac(&lac);
+ info.Put(TEXT_LAC, lac);
+ } else if (type == nsICellInfo::CELL_INFO_TYPE_WCDMA) {
+ radioType = "wcdma";
+ nsCOMPtr<nsIWcdmaCellInfo> wcdmaCellInfo = do_QueryInterface(mCellInfo[idx]);
+ ExtractCommonNonCDMACellInfoItems(wcdmaCellInfo, info);
+ int32_t lac, psc;
+ wcdmaCellInfo->GetLac(&lac);
+ wcdmaCellInfo->GetPsc(&psc);
+ info.Put(TEXT_LAC, lac);
+ info.Put(TEXT_PSC, psc);
+ } else if (type == nsICellInfo::CELL_INFO_TYPE_CDMA) {
+ radioType = "cdma";
+ nsCOMPtr<nsICdmaCellInfo> cdmaCellInfo = do_QueryInterface(mCellInfo[idx]);
+ int32_t mnc, lac, cid, sig;
+ cdmaCellInfo->GetSystemId(&mnc);
+ cdmaCellInfo->GetNetworkId(&lac);
+ cdmaCellInfo->GetBaseStationId(&cid);
+ info.Put(TEXT_MNC, mnc);
+ info.Put(TEXT_LAC, lac);
+ info.Put(TEXT_CID, cid);
+
+ cdmaCellInfo->GetEvdoDbm(&sig);
+ if (sig < 0 || sig == nsICellInfo::UNKNOWN_VALUE) {
+ cdmaCellInfo->GetCdmaDbm(&sig);
+ }
+ if (sig > -1 && sig != nsICellInfo::UNKNOWN_VALUE) {
+ sig *= -1;
+ info.Put(TEXT_STRENGTH_DBM, sig);
+ }
+ } else if (type == nsICellInfo::CELL_INFO_TYPE_LTE) {
+ radioType = "lte";
+ nsCOMPtr<nsILteCellInfo> lteCellInfo = do_QueryInterface(mCellInfo[idx]);
+ ExtractCommonNonCDMACellInfoItems(lteCellInfo, info);
+ int32_t lac, timingAdvance, pcid, rsrp;
+ lteCellInfo->GetTac(&lac);
+ lteCellInfo->GetTimingAdvance(&timingAdvance);
+ lteCellInfo->GetPcid(&pcid);
+ lteCellInfo->GetRsrp(&rsrp);
+ info.Put(TEXT_LAC, lac);
+ info.Put(TEXT_TIMEING_ADVANCE, timingAdvance);
+ info.Put(TEXT_PSC, pcid);
+ if (rsrp != nsICellInfo::UNKNOWN_VALUE) {
+ info.Put(TEXT_STRENGTH_DBM, rsrp * -1);
+ }
+ }
+
+ aCellDesc += nsPrintfCString("\"%s\":\"%s\"", TEXT_RADIOTYPE.get(), radioType);
+ for (auto it = info.Iter(); !it.Done(); it.Next()) {
+ const nsACString& key = it.Key();
+ int32_t value = it.UserData();
+ if (value != nsICellInfo::UNKNOWN_VALUE) {
+ aCellDesc += nsPrintfCString(",\"%s\":%d", key.BeginReading(), value);
+ }
+ }
+
+ aCellDesc += "}";
+ }
+ aCellDesc += "]";
+}
+
+void
+StumblerInfo::DumpStumblerInfo()
+{
+ if (!mIsWifiInfoResponseReceived || mCellInfoResponsesReceived != mCellInfoResponsesExpected) {
+ STUMBLER_DBG("CellInfoReceived=%d (Expected=%d), WifiInfoResponseReceived=%d\n",
+ mCellInfoResponsesReceived, mCellInfoResponsesExpected, mIsWifiInfoResponseReceived);
+ return;
+ }
+ mIsWifiInfoResponseReceived = false;
+ mCellInfoResponsesReceived = 0;
+
+ nsAutoCString desc;
+ nsresult rv = LocationInfoToString(desc);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ STUMBLER_ERR("LocationInfoToString failed, skip this dump");
+ return;
+ }
+
+ CellNetworkInfoToString(desc);
+ desc += mWifiDesc;
+
+ STUMBLER_DBG("dispatch write event to thread\n");
+ nsCOMPtr<nsIEventTarget> target = do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID);
+ MOZ_ASSERT(target);
+
+ nsCOMPtr<nsIRunnable> event = new WriteStumbleOnThread(desc);
+ target->Dispatch(event, NS_DISPATCH_NORMAL);
+}
+
+NS_IMETHODIMP
+StumblerInfo::NotifyGetCellInfoList(uint32_t count, nsICellInfo** aCellInfos)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ STUMBLER_DBG("There are %d cellinfo in the result\n", count);
+
+ for (uint32_t i = 0; i < count; i++) {
+ mCellInfo.AppendElement(aCellInfos[i]);
+ }
+ mCellInfoResponsesReceived++;
+ DumpStumblerInfo();
+ return NS_OK;
+}
+
+NS_IMETHODIMP StumblerInfo::NotifyGetCellInfoListFailed(const nsAString& error)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ mCellInfoResponsesReceived++;
+ STUMBLER_ERR("NotifyGetCellInfoListFailedm CellInfoReadyNum=%d, mCellInfoResponsesExpected=%d, mIsWifiInfoResponseReceived=%d",
+ mCellInfoResponsesReceived, mCellInfoResponsesExpected, mIsWifiInfoResponseReceived);
+ DumpStumblerInfo();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+StumblerInfo::Onready(uint32_t count, nsIWifiScanResult** results)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ STUMBLER_DBG("There are %d wifiAPinfo in the result\n",count);
+
+ mWifiDesc += ",\"wifiAccessPoints\": [";
+ bool firstItem = true;
+ for (uint32_t i = 0 ; i < count ; i++) {
+ nsString ssid;
+ results[i]->GetSsid(ssid);
+ if (ssid.IsEmpty()) {
+ STUMBLER_DBG("no ssid, skip this AP\n");
+ continue;
+ }
+
+ if (ssid.Length() >= 6) {
+ if (StringEndsWith(ssid, NS_LITERAL_STRING("_nomap"))) {
+ STUMBLER_DBG("end with _nomap. skip this AP(ssid :%s)\n", ssid.get());
+ continue;
+ }
+ }
+
+ if (firstItem) {
+ mWifiDesc += "{";
+ firstItem = false;
+ } else {
+ mWifiDesc += ",{";
+ }
+
+ // mac address
+ nsString bssid;
+ results[i]->GetBssid(bssid);
+ // 00:00:00:00:00:00 --> 000000000000
+ bssid.StripChars(":");
+ mWifiDesc += "\"macAddress\":\"";
+ mWifiDesc += NS_ConvertUTF16toUTF8(bssid);
+
+ uint32_t signal;
+ results[i]->GetSignalStrength(&signal);
+ mWifiDesc += "\",\"signalStrength\":";
+ mWifiDesc.AppendInt(signal);
+
+ mWifiDesc += "}";
+ }
+ mWifiDesc += "]";
+
+ mIsWifiInfoResponseReceived = true;
+ DumpStumblerInfo();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+StumblerInfo::Onfailure()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ STUMBLER_ERR("GetWifiScanResults Onfailure\n");
+ mIsWifiInfoResponseReceived = true;
+ DumpStumblerInfo();
+ return NS_OK;
+}
+
diff --git a/dom/system/gonk/mozstumbler/MozStumbler.h b/dom/system/gonk/mozstumbler/MozStumbler.h
new file mode 100644
index 000000000..41ee4e5e1
--- /dev/null
+++ b/dom/system/gonk/mozstumbler/MozStumbler.h
@@ -0,0 +1,47 @@
+/* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_system_mozstumbler_h__
+#define mozilla_system_mozstumbler_h__
+
+#include "nsIDOMEventTarget.h"
+#include "nsICellInfo.h"
+#include "nsIWifi.h"
+
+#define STUMBLE_INTERVAL_MS 3000
+
+class nsGeoPosition;
+
+void MozStumble(nsGeoPosition* position);
+
+class StumblerInfo final : public nsICellInfoListCallback,
+ public nsIWifiScanResultsReady
+{
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSICELLINFOLISTCALLBACK
+ NS_DECL_NSIWIFISCANRESULTSREADY
+
+ explicit StumblerInfo(nsGeoPosition* position)
+ : mPosition(position), mCellInfoResponsesExpected(0), mCellInfoResponsesReceived(0), mIsWifiInfoResponseReceived(0)
+ {}
+ void SetWifiInfoResponseReceived();
+ void SetCellInfoResponsesExpected(uint8_t count);
+
+private:
+ ~StumblerInfo() {}
+ void DumpStumblerInfo();
+ nsresult LocationInfoToString(nsACString& aLocDesc);
+ void CellNetworkInfoToString(nsACString& aCellDesc);
+ nsTArray<RefPtr<nsICellInfo>> mCellInfo;
+ nsCString mWifiDesc;
+ RefPtr<nsGeoPosition> mPosition;
+ int mCellInfoResponsesExpected;
+ int mCellInfoResponsesReceived;
+ bool mIsWifiInfoResponseReceived;
+};
+#endif // mozilla_system_mozstumbler_h__
+
diff --git a/dom/system/gonk/mozstumbler/StumblerLogging.cpp b/dom/system/gonk/mozstumbler/StumblerLogging.cpp
new file mode 100644
index 000000000..acf23b3b1
--- /dev/null
+++ b/dom/system/gonk/mozstumbler/StumblerLogging.cpp
@@ -0,0 +1,13 @@
+/* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "StumblerLogging.h"
+
+mozilla::LogModule* GetLog()
+{
+ static mozilla::LazyLogModule log("mozstumbler");
+ return log;
+}
diff --git a/dom/system/gonk/mozstumbler/StumblerLogging.h b/dom/system/gonk/mozstumbler/StumblerLogging.h
new file mode 100644
index 000000000..038f44f8f
--- /dev/null
+++ b/dom/system/gonk/mozstumbler/StumblerLogging.h
@@ -0,0 +1,18 @@
+/* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef STUMBLERLOGGING_H
+#define STUMBLERLOGGING_H
+
+#include "mozilla/Logging.h"
+
+mozilla::LogModule* GetLog();
+
+#define STUMBLER_DBG(arg, ...) MOZ_LOG(GetLog(), mozilla::LogLevel::Debug, ("STUMBLER - %s: " arg, __func__, ##__VA_ARGS__))
+#define STUMBLER_LOG(arg, ...) MOZ_LOG(GetLog(), mozilla::LogLevel::Info, ("STUMBLER - %s: " arg, __func__, ##__VA_ARGS__))
+#define STUMBLER_ERR(arg, ...) MOZ_LOG(GetLog(), mozilla::LogLevel::Error, ("STUMBLER -%s: " arg, __func__, ##__VA_ARGS__))
+
+#endif
diff --git a/dom/system/gonk/mozstumbler/UploadStumbleRunnable.cpp b/dom/system/gonk/mozstumbler/UploadStumbleRunnable.cpp
new file mode 100644
index 000000000..d97aa9712
--- /dev/null
+++ b/dom/system/gonk/mozstumbler/UploadStumbleRunnable.cpp
@@ -0,0 +1,151 @@
+/* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "UploadStumbleRunnable.h"
+#include "StumblerLogging.h"
+#include "mozilla/dom/Event.h"
+#include "nsIInputStream.h"
+#include "nsIScriptSecurityManager.h"
+#include "nsIURLFormatter.h"
+#include "nsIXMLHttpRequest.h"
+#include "nsNetUtil.h"
+#include "nsVariant.h"
+
+UploadStumbleRunnable::UploadStumbleRunnable(nsIInputStream* aUploadData)
+: mUploadInputStream(aUploadData)
+{
+}
+
+NS_IMETHODIMP
+UploadStumbleRunnable::Run()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ nsresult rv = Upload();
+ if (NS_FAILED(rv)) {
+ WriteStumbleOnThread::UploadEnded(false);
+ }
+ return NS_OK;
+}
+
+nsresult
+UploadStumbleRunnable::Upload()
+{
+ nsresult rv;
+ RefPtr<nsVariant> variant = new nsVariant();
+
+ rv = variant->SetAsISupports(mUploadInputStream);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIXMLHttpRequest> xhr = do_CreateInstance(NS_XMLHTTPREQUEST_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIScriptSecurityManager> secman =
+ do_GetService(NS_SCRIPTSECURITYMANAGER_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIPrincipal> systemPrincipal;
+ rv = secman->GetSystemPrincipal(getter_AddRefs(systemPrincipal));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = xhr->Init(systemPrincipal, nullptr, nullptr, nullptr);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIURLFormatter> formatter =
+ do_CreateInstance("@mozilla.org/toolkit/URLFormatterService;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsString url;
+ rv = formatter->FormatURLPref(NS_LITERAL_STRING("geo.stumbler.url"), url);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = xhr->Open(NS_LITERAL_CSTRING("POST"), NS_ConvertUTF16toUTF8(url), false, EmptyString(), EmptyString());
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ xhr->SetRequestHeader(NS_LITERAL_CSTRING("Content-Type"), NS_LITERAL_CSTRING("gzip"));
+ xhr->SetMozBackgroundRequest(true);
+ // 60s timeout
+ xhr->SetTimeout(60 * 1000);
+
+ nsCOMPtr<EventTarget> target(do_QueryInterface(xhr));
+ RefPtr<nsIDOMEventListener> listener = new UploadEventListener(xhr);
+
+ const char* const sEventStrings[] = {
+ // nsIXMLHttpRequestEventTarget event types
+ "abort",
+ "error",
+ "load",
+ "timeout"
+ };
+
+ for (uint32_t index = 0; index < MOZ_ARRAY_LENGTH(sEventStrings); index++) {
+ nsAutoString eventType = NS_ConvertASCIItoUTF16(sEventStrings[index]);
+ rv = target->AddEventListener(eventType, listener, false);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ rv = xhr->Send(variant);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+NS_IMPL_ISUPPORTS(UploadEventListener, nsIDOMEventListener)
+
+UploadEventListener::UploadEventListener(nsIXMLHttpRequest* aXHR)
+: mXHR(aXHR)
+{
+}
+
+NS_IMETHODIMP
+UploadEventListener::HandleEvent(nsIDOMEvent* aEvent)
+{
+ nsString type;
+ if (NS_FAILED(aEvent->GetType(type))) {
+ STUMBLER_ERR("Failed to get event type");
+ WriteStumbleOnThread::UploadEnded(false);
+ return NS_ERROR_FAILURE;
+ }
+
+ if (type.EqualsLiteral("load")) {
+ STUMBLER_DBG("Got load Event\n");
+ } else if (type.EqualsLiteral("error") && mXHR) {
+ STUMBLER_ERR("Upload Error");
+ } else {
+ STUMBLER_DBG("Receive %s Event", NS_ConvertUTF16toUTF8(type).get());
+ }
+
+ uint32_t statusCode = 0;
+ bool doDelete = false;
+ if (!mXHR) {
+ return NS_OK;
+ }
+ nsresult rv = mXHR->GetStatus(&statusCode);
+ if (NS_SUCCEEDED(rv)) {
+ STUMBLER_DBG("statuscode %d \n", statusCode);
+ }
+
+ if (200 == statusCode || 400 == statusCode) {
+ doDelete = true;
+ }
+
+ WriteStumbleOnThread::UploadEnded(doDelete);
+ nsCOMPtr<EventTarget> target(do_QueryInterface(mXHR));
+
+ const char* const sEventStrings[] = {
+ // nsIXMLHttpRequestEventTarget event types
+ "abort",
+ "error",
+ "load",
+ "timeout"
+ };
+
+ for (uint32_t index = 0; index < MOZ_ARRAY_LENGTH(sEventStrings); index++) {
+ nsAutoString eventType = NS_ConvertASCIItoUTF16(sEventStrings[index]);
+ rv = target->RemoveEventListener(eventType, this, false);
+ }
+
+ mXHR = nullptr;
+ return NS_OK;
+}
diff --git a/dom/system/gonk/mozstumbler/UploadStumbleRunnable.h b/dom/system/gonk/mozstumbler/UploadStumbleRunnable.h
new file mode 100644
index 000000000..462665a86
--- /dev/null
+++ b/dom/system/gonk/mozstumbler/UploadStumbleRunnable.h
@@ -0,0 +1,46 @@
+/* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+
+#ifndef UPLOADSTUMBLERUNNABLE_H
+#define UPLOADSTUMBLERUNNABLE_H
+
+#include "nsIDOMEventListener.h"
+
+class nsIXMLHttpRequest;
+class nsIInputStream;
+
+/*
+ This runnable is managed by WriteStumbleOnThread only, see that class
+ for how this is scheduled.
+ */
+class UploadStumbleRunnable final : public Runnable
+{
+public:
+ explicit UploadStumbleRunnable(nsIInputStream* aUploadInputStream);
+
+ NS_IMETHOD Run() override;
+private:
+ virtual ~UploadStumbleRunnable() {}
+ nsCOMPtr<nsIInputStream> mUploadInputStream;
+ nsresult Upload();
+};
+
+
+class UploadEventListener : public nsIDOMEventListener
+{
+public:
+ UploadEventListener(nsIXMLHttpRequest* aXHR);
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIDOMEVENTLISTENER
+
+protected:
+ virtual ~UploadEventListener() {}
+ nsCOMPtr<nsIXMLHttpRequest> mXHR;
+};
+
+#endif
diff --git a/dom/system/gonk/mozstumbler/WriteStumbleOnThread.cpp b/dom/system/gonk/mozstumbler/WriteStumbleOnThread.cpp
new file mode 100644
index 000000000..e58e771c4
--- /dev/null
+++ b/dom/system/gonk/mozstumbler/WriteStumbleOnThread.cpp
@@ -0,0 +1,321 @@
+/* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "WriteStumbleOnThread.h"
+#include "StumblerLogging.h"
+#include "UploadStumbleRunnable.h"
+#include "nsDumpUtils.h"
+#include "nsGZFileWriter.h"
+#include "nsIFileStreams.h"
+#include "nsIInputStream.h"
+#include "nsPrintfCString.h"
+
+#define MAXFILESIZE_KB (15 * 1024)
+#define ONEDAY_IN_MSEC (24 * 60 * 60 * 1000)
+#define MAX_UPLOAD_ATTEMPTS 20
+
+mozilla::Atomic<bool> WriteStumbleOnThread::sIsFileWaitingForUpload(false);
+mozilla::Atomic<bool> WriteStumbleOnThread::sIsAlreadyRunning(false);
+WriteStumbleOnThread::UploadFreqGuard WriteStumbleOnThread::sUploadFreqGuard = {0};
+
+#define FILENAME_INPROGRESS NS_LITERAL_CSTRING("stumbles.json.gz")
+#define FILENAME_COMPLETED NS_LITERAL_CSTRING("stumbles.done.json.gz")
+#define OUTPUT_DIR NS_LITERAL_CSTRING("mozstumbler")
+
+class DeleteRunnable : public Runnable
+{
+ public:
+ DeleteRunnable() {}
+
+ NS_IMETHOD
+ Run() override
+ {
+ nsCOMPtr<nsIFile> tmpFile;
+ nsresult rv = nsDumpUtils::OpenTempFile(FILENAME_COMPLETED,
+ getter_AddRefs(tmpFile),
+ OUTPUT_DIR,
+ nsDumpUtils::CREATE);
+ if (NS_SUCCEEDED(rv)) {
+ tmpFile->Remove(true);
+ }
+ // critically, this sets this flag to false so writing can happen again
+ WriteStumbleOnThread::sIsAlreadyRunning = false;
+ WriteStumbleOnThread::sIsFileWaitingForUpload = false;
+ return NS_OK;
+ }
+
+ private:
+ ~DeleteRunnable() {}
+};
+
+bool
+WriteStumbleOnThread::IsFileWaitingForUpload()
+{
+ return sIsFileWaitingForUpload;
+}
+
+void
+WriteStumbleOnThread::UploadEnded(bool deleteUploadFile)
+{
+ if (!deleteUploadFile) {
+ sIsAlreadyRunning = false;
+ return;
+ }
+
+ nsCOMPtr<nsIEventTarget> target = do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID);
+ MOZ_ASSERT(target);
+ nsCOMPtr<nsIRunnable> event = new DeleteRunnable();
+ target->Dispatch(event, NS_DISPATCH_NORMAL);
+}
+
+void
+WriteStumbleOnThread::WriteJSON(Partition aPart)
+{
+ MOZ_ASSERT(!NS_IsMainThread());
+
+ nsCOMPtr<nsIFile> tmpFile;
+ nsresult rv;
+ rv = nsDumpUtils::OpenTempFile(FILENAME_INPROGRESS, getter_AddRefs(tmpFile),
+ OUTPUT_DIR, nsDumpUtils::CREATE);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ STUMBLER_ERR("Open a file for stumble failed");
+ return;
+ }
+
+ RefPtr<nsGZFileWriter> gzWriter = new nsGZFileWriter(nsGZFileWriter::Append);
+ rv = gzWriter->Init(tmpFile);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ STUMBLER_ERR("gzWriter init failed");
+ return;
+ }
+
+ /*
+ The json format is like below.
+ {items:[
+ {item},
+ {item},
+ {item}
+ ]}
+ */
+
+ // Need to add "]}" after the last item
+ if (aPart == Partition::End) {
+ rv = gzWriter->Write("]}");
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ STUMBLER_ERR("gzWriter Write failed");
+ }
+
+ rv = gzWriter->Finish();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ STUMBLER_ERR("ostream finish failed");
+ }
+
+ nsCOMPtr<nsIFile> targetFile;
+ nsresult rv = nsDumpUtils::OpenTempFile(FILENAME_COMPLETED, getter_AddRefs(targetFile),
+ OUTPUT_DIR, nsDumpUtils::CREATE);
+ nsAutoString targetFilename;
+ rv = targetFile->GetLeafName(targetFilename);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ STUMBLER_ERR("Get Filename failed");
+ return;
+ }
+ rv = targetFile->Remove(true);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ STUMBLER_ERR("Remove File failed");
+ return;
+ }
+ // Rename tmpfile
+ rv = tmpFile->MoveTo(/* directory */ nullptr, targetFilename);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ STUMBLER_ERR("Rename File failed");
+ return;
+ }
+ return;
+ }
+
+ // Need to add "{items:[" before the first item
+ if (aPart == Partition::Begining) {
+ rv = gzWriter->Write("{\"items\":[{");
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ STUMBLER_ERR("ostream write begining failed");
+ }
+ } else if (aPart == Partition::Middle) {
+ rv = gzWriter->Write(",{");
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ STUMBLER_ERR("ostream write middle failed");
+ }
+ }
+ rv = gzWriter->Write(mDesc.get());
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ STUMBLER_ERR("ostream write mDesc failed");
+ }
+ // one item is ended with '}' (e.g. {item})
+ rv = gzWriter->Write("}");
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ STUMBLER_ERR("ostream write end failed");
+ }
+
+ rv = gzWriter->Finish();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ STUMBLER_ERR("ostream finish failed");
+ }
+
+ // check if it is the end of this file
+ int64_t fileSize = 0;
+ rv = tmpFile->GetFileSize(&fileSize);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ STUMBLER_ERR("GetFileSize failed");
+ return;
+ }
+ if (fileSize >= MAXFILESIZE_KB) {
+ WriteJSON(Partition::End);
+ return;
+ }
+}
+
+WriteStumbleOnThread::Partition
+WriteStumbleOnThread::GetWritePosition()
+{
+ MOZ_ASSERT(!NS_IsMainThread());
+
+ nsCOMPtr<nsIFile> tmpFile;
+ nsresult rv = nsDumpUtils::OpenTempFile(FILENAME_INPROGRESS, getter_AddRefs(tmpFile),
+ OUTPUT_DIR, nsDumpUtils::CREATE);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ STUMBLER_ERR("Open a file for stumble failed");
+ return Partition::Unknown;
+ }
+
+ int64_t fileSize = 0;
+ rv = tmpFile->GetFileSize(&fileSize);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ STUMBLER_ERR("GetFileSize failed");
+ return Partition::Unknown;
+ }
+
+ if (fileSize == 0) {
+ return Partition::Begining;
+ } else if (fileSize >= MAXFILESIZE_KB) {
+ return Partition::End;
+ } else {
+ return Partition::Middle;
+ }
+}
+
+NS_IMETHODIMP
+WriteStumbleOnThread::Run()
+{
+ MOZ_ASSERT(!NS_IsMainThread());
+
+ bool b = sIsAlreadyRunning.exchange(true);
+ if (b) {
+ return NS_OK;
+ }
+
+ UploadFileStatus status = GetUploadFileStatus();
+
+ if (UploadFileStatus::NoFile != status) {
+ if (UploadFileStatus::ExistsAndReadyToUpload == status) {
+ sIsFileWaitingForUpload = true;
+ Upload();
+ return NS_OK;
+ }
+ } else {
+ Partition partition = GetWritePosition();
+ if (partition == Partition::Unknown) {
+ STUMBLER_ERR("GetWritePosition failed, skip once");
+ } else {
+ WriteJSON(partition);
+ }
+ }
+
+ sIsFileWaitingForUpload = false;
+ sIsAlreadyRunning = false;
+ return NS_OK;
+}
+
+
+/*
+ If the upload file exists, then check if it is one day old.
+ • if it is a day old -> ExistsAndReadyToUpload
+ • if it is less than the current day old -> Exists
+ • otherwise -> NoFile
+
+ The Exists case means that the upload and the stumbling is rate limited
+ per-day to the size of the one file.
+ */
+WriteStumbleOnThread::UploadFileStatus
+WriteStumbleOnThread::GetUploadFileStatus()
+{
+ nsCOMPtr<nsIFile> tmpFile;
+ nsresult rv = nsDumpUtils::OpenTempFile(FILENAME_COMPLETED, getter_AddRefs(tmpFile),
+ OUTPUT_DIR, nsDumpUtils::CREATE);
+ int64_t fileSize;
+ rv = tmpFile->GetFileSize(&fileSize);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ STUMBLER_ERR("GetFileSize failed");
+ return UploadFileStatus::NoFile;
+ }
+ if (fileSize <= 0) {
+ tmpFile->Remove(true);
+ return UploadFileStatus::NoFile;
+ }
+
+ PRTime lastModifiedTime;
+ tmpFile->GetLastModifiedTime(&lastModifiedTime);
+ if ((PR_Now() / PR_USEC_PER_MSEC) - lastModifiedTime >= ONEDAY_IN_MSEC) {
+ return UploadFileStatus::ExistsAndReadyToUpload;
+ }
+ return UploadFileStatus::Exists;
+}
+
+void
+WriteStumbleOnThread::Upload()
+{
+ MOZ_ASSERT(!NS_IsMainThread());
+
+ time_t seconds = time(0);
+ int day = seconds / (60 * 60 * 24);
+
+ if (sUploadFreqGuard.daySinceEpoch < day) {
+ sUploadFreqGuard.daySinceEpoch = day;
+ sUploadFreqGuard.attempts = 0;
+ }
+
+ sUploadFreqGuard.attempts++;
+ if (sUploadFreqGuard.attempts > MAX_UPLOAD_ATTEMPTS) {
+ STUMBLER_ERR("Too many upload attempts today");
+ sIsAlreadyRunning = false;
+ return;
+ }
+
+ nsCOMPtr<nsIFile> tmpFile;
+ nsresult rv = nsDumpUtils::OpenTempFile(FILENAME_COMPLETED, getter_AddRefs(tmpFile),
+ OUTPUT_DIR, nsDumpUtils::CREATE);
+ int64_t fileSize;
+ rv = tmpFile->GetFileSize(&fileSize);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ STUMBLER_ERR("GetFileSize failed");
+ sIsAlreadyRunning = false;
+ return;
+ }
+
+ if (fileSize <= 0) {
+ sIsAlreadyRunning = false;
+ return;
+ }
+
+ // prepare json into nsIInputStream
+ nsCOMPtr<nsIInputStream> inStream;
+ rv = NS_NewLocalFileInputStream(getter_AddRefs(inStream), tmpFile);
+ if (NS_FAILED(rv)) {
+ sIsAlreadyRunning = false;
+ return;
+ }
+
+ RefPtr<nsIRunnable> uploader = new UploadStumbleRunnable(inStream);
+ NS_DispatchToMainThread(uploader);
+}
diff --git a/dom/system/gonk/mozstumbler/WriteStumbleOnThread.h b/dom/system/gonk/mozstumbler/WriteStumbleOnThread.h
new file mode 100644
index 000000000..104cf9bdd
--- /dev/null
+++ b/dom/system/gonk/mozstumbler/WriteStumbleOnThread.h
@@ -0,0 +1,91 @@
+/* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef WriteStumbleOnThread_H
+#define WriteStumbleOnThread_H
+
+#include "mozilla/Atomics.h"
+
+class DeleteRunnable;
+
+/*
+ This class is the entry point to stumbling, in that it
+ receives the location+cell+wifi string and writes it
+ to disk, or instead, it calls UploadStumbleRunnable
+ to upload the data.
+
+ Writes will happen until the file is a max size, then stop.
+ Uploads will happen only when the file is one day old.
+ The purpose of these decisions is to have very simple rate-limiting
+ on the writes, as well as the uploads.
+
+ There is only one file active; it is either being used for writing,
+ or for uploading. If the file is ready for uploading, no further
+ writes will take place until this file has been uploaded.
+ This can mean writing might not take place for days until the uploaded
+ file is processed. This is correct by-design.
+
+ A notable limitation is that the upload is triggered by a location event,
+ this is used as an arbitrary and simple trigger. In future, there are
+ better events that can be used, such as detecting network activity.
+
+ This thread is guarded so that only one instance is active (see the
+ mozilla::Atomics used for this).
+ */
+class WriteStumbleOnThread : public mozilla::Runnable
+{
+public:
+ explicit WriteStumbleOnThread(const nsCString& aDesc)
+ : mDesc(aDesc)
+ {}
+
+ NS_IMETHOD Run() override;
+
+ static void UploadEnded(bool deleteUploadFile);
+
+ // Used externally to determine if cell+wifi scans should happen
+ // (returns false for that case).
+ static bool IsFileWaitingForUpload();
+
+private:
+ friend class DeleteRunnable;
+
+ enum class Partition {
+ Begining,
+ Middle,
+ End,
+ Unknown
+ };
+
+ enum class UploadFileStatus {
+ NoFile, Exists, ExistsAndReadyToUpload
+ };
+
+ ~WriteStumbleOnThread() {}
+
+ Partition GetWritePosition();
+ UploadFileStatus GetUploadFileStatus();
+ void WriteJSON(Partition aPart);
+ void Upload();
+
+ nsCString mDesc;
+
+ // Only run one instance of this
+ static mozilla::Atomic<bool> sIsAlreadyRunning;
+
+ static mozilla::Atomic<bool> sIsFileWaitingForUpload;
+
+ // Limit the upload attempts per day. If the device is rebooted
+ // this resets the allowed attempts, which is acceptable.
+ struct UploadFreqGuard {
+ int attempts;
+ int daySinceEpoch;
+ };
+ static UploadFreqGuard sUploadFreqGuard;
+
+};
+
+#endif
diff --git a/dom/system/gonk/nsIAudioManager.idl b/dom/system/gonk/nsIAudioManager.idl
new file mode 100644
index 000000000..c2eb62b21
--- /dev/null
+++ b/dom/system/gonk/nsIAudioManager.idl
@@ -0,0 +1,58 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+[scriptable, builtinclass, uuid(df31c280-1ef1-11e5-867f-0800200c9a66)]
+interface nsIAudioManager : nsISupports
+{
+ /**
+ * Microphone muted?
+ */
+ attribute boolean microphoneMuted;
+
+ /**
+ * Set the phone's audio mode.
+ */
+ const long PHONE_STATE_INVALID = -2;
+ const long PHONE_STATE_CURRENT = -1;
+ const long PHONE_STATE_NORMAL = 0;
+ const long PHONE_STATE_RINGTONE = 1;
+ const long PHONE_STATE_IN_CALL = 2;
+ const long PHONE_STATE_IN_COMMUNICATION = 3;
+
+ attribute long phoneState;
+
+ /**
+ * Configure a particular device ("force") to be used for one of the uses
+ * (communication, media playback, etc.)
+ */
+ const long FORCE_NONE = 0; // the default
+ const long FORCE_SPEAKER = 1;
+ const long FORCE_HEADPHONES = 2;
+ const long FORCE_BT_SCO = 3;
+ const long FORCE_BT_A2DP = 4;
+ const long FORCE_WIRED_ACCESSORY = 5;
+ const long FORCE_BT_CAR_DOCK = 6;
+ const long FORCE_BT_DESK_DOCK = 7;
+ const long FORCE_ANALOG_DOCK = 8;
+ const long FORCE_DIGITAL_DOCK = 9;
+ const long FORCE_NO_BT_A2DP = 10;
+ const long USE_COMMUNICATION = 0;
+ const long USE_MEDIA = 1;
+ const long USE_RECORD = 2;
+ const long USE_DOCK = 3;
+
+ void setForceForUse(in long usage, in long force);
+ long getForceForUse(in long usage);
+
+ /**
+ * These functions would be used when we enable the new volume control API
+ * (mozAudioChannelManager). The range of volume index is from 0 to N.
+ * More details on : https://gist.github.com/evanxd/41d8e2d91c5201a42bfa
+ */
+ void setAudioChannelVolume(in unsigned long channel, in unsigned long index);
+ unsigned long getAudioChannelVolume(in unsigned long channel);
+ unsigned long getMaxAudioChannelVolume(in unsigned long channel);
+};
diff --git a/dom/system/gonk/nsIDataCallInterfaceService.idl b/dom/system/gonk/nsIDataCallInterfaceService.idl
new file mode 100644
index 000000000..c387879fa
--- /dev/null
+++ b/dom/system/gonk/nsIDataCallInterfaceService.idl
@@ -0,0 +1,268 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+[scriptable, uuid(6b66446a-7000-438f-8e1b-b56b4cbf4fa9)]
+interface nsIDataCall : nsISupports
+{
+ /**
+ * Data call fail cause. One of the nsIDataCallInterface.DATACALL_FAIL_*
+ * values.
+ */
+ readonly attribute long failCause;
+
+ /**
+ * If failCause != nsIDataCallInterface.DATACALL_FAIL_NONE, this field
+ * indicates the suggested retry back-off timer. The unit is milliseconds.
+ */
+ readonly attribute long suggestedRetryTime;
+
+ /**
+ * Context ID, uniquely identifies this call.
+ */
+ readonly attribute long cid;
+
+ /**
+ * Data call network state. One of the nsIDataCallInterface.DATACALL_STATE_*
+ * values.
+ */
+ readonly attribute long active;
+
+ /**
+ * Data call connection type. One of the
+ * nsIDataCallInterface.DATACALL_PDP_TYPE_* values.
+ */
+ readonly attribute long pdpType;
+
+ /**
+ * The network interface name.
+ */
+ readonly attribute DOMString ifname;
+
+ /**
+ * A space-delimited list of addresses with optional "/" prefix length.
+ */
+ readonly attribute DOMString addresses;
+
+ /**
+ * A space-delimited list of DNS server addresses.
+ */
+ readonly attribute DOMString dnses;
+
+ /**
+ * A space-delimited list of default gateway addresses.
+ */
+ readonly attribute DOMString gateways;
+
+ /**
+ * A space-delimited list of Proxy Call State Control Function addresses for
+ * IMS client.
+ */
+ readonly attribute DOMString pcscf;
+
+ /**
+ * MTU received from network, -1 if not set or invalid.
+ */
+ readonly attribute long mtu;
+};
+
+[scriptable, uuid(e119c54b-9354-4ad6-a1ee-18608bde9320)]
+interface nsIDataCallInterfaceListener : nsISupports
+{
+ /**
+ * Notify data call interface listeners about unsolicited data call state
+ * changes.
+ */
+ void notifyDataCallListChanged(in uint32_t count,
+ [array, size_is(count)] in nsIDataCall
+ dataCalls);
+};
+
+[scriptable, uuid(db0b640a-3b3a-4f48-84dc-256e176876c2)]
+interface nsIDataCallCallback : nsISupports
+{
+ /**
+ * Called when setupDataCall() returns succesfully.
+ */
+ void notifySetupDataCallSuccess(in nsIDataCall dataCall);
+
+ /**
+ * Called when getDataCallList() returns succesfully.
+ */
+ void notifyGetDataCallListSuccess(in uint32_t count,
+ [array, size_is(count)] in nsIDataCall
+ dataCalls);
+ /**
+ * Called when request returns succesfully.
+ */
+ void notifySuccess();
+
+ /**
+ * Called when request returns error.
+ */
+ void notifyError(in AString errorMsg);
+};
+
+[scriptable, uuid(ec219021-8623-4b9f-aba5-4db58c60684f)]
+interface nsIDataCallInterface : nsISupports
+{
+ /**
+ * Data fail causes, defined in TS 24.008.
+ */
+ const long DATACALL_FAIL_NONE = 0;
+ const long DATACALL_FAIL_OPERATOR_BARRED = 0x08;
+ const long DATACALL_FAIL_INSUFFICIENT_RESOURCES = 0x1A;
+ const long DATACALL_FAIL_MISSING_UKNOWN_APN = 0x1B;
+ const long DATACALL_FAIL_UNKNOWN_PDP_ADDRESS_TYPE = 0x1C;
+ const long DATACALL_FAIL_USER_AUTHENTICATION = 0x1D;
+ const long DATACALL_FAIL_ACTIVATION_REJECT_GGSN = 0x1E;
+ const long DATACALL_FAIL_ACTIVATION_REJECT_UNSPECIFIED = 0x1F;
+ const long DATACALL_FAIL_SERVICE_OPTION_NOT_SUPPORTED = 0x20;
+ const long DATACALL_FAIL_SERVICE_OPTION_NOT_SUBSCRIBED = 0x21;
+ const long DATACALL_FAIL_SERVICE_OPTION_OUT_OF_ORDER = 0x22;
+ const long DATACALL_FAIL_NSAPI_IN_USE = 0x23;
+ const long DATACALL_FAIL_ONLY_IPV4_ALLOWED = 0x32;
+ const long DATACALL_FAIL_ONLY_IPV6_ALLOWED = 0x33;
+ const long DATACALL_FAIL_ONLY_SINGLE_BEARER_ALLOWED = 0x34;
+ const long DATACALL_FAIL_PROTOCOL_ERRORS = 0x6F;
+ /* Not mentioned in the specification */
+ const long DATACALL_FAIL_VOICE_REGISTRATION_FAIL = -1;
+ const long DATACALL_FAIL_DATA_REGISTRATION_FAIL = -2;
+ const long DATACALL_FAIL_SIGNAL_LOST = -3;
+ const long DATACALL_FAIL_PREF_RADIO_TECH_CHANGED = -4;
+ const long DATACALL_FAIL_RADIO_POWER_OFF = -5;
+ const long DATACALL_FAIL_TETHERED_CALL_ACTIVE = -6;
+ const long DATACALL_FAIL_ERROR_UNSPECIFIED = 0xFFFF;
+
+ /**
+ * Data call network state.
+ */
+ const long DATACALL_STATE_INACTIVE = 0;
+ const long DATACALL_STATE_ACTIVE_DOWN = 1;
+ const long DATACALL_STATE_ACTIVE_UP = 2;
+
+ /**
+ * Data call authentication type. Must match the values in ril_consts
+ * RIL_DATACALL_AUTH_TO_GECKO array.
+ */
+ const long DATACALL_AUTH_NONE = 0;
+ const long DATACALL_AUTH_PAP = 1;
+ const long DATACALL_AUTH_CHAP = 2;
+ const long DATACALL_AUTH_PAP_OR_CHAP = 3;
+
+ /**
+ * Data call protocol type. Must match the values in ril_consts
+ * RIL_DATACALL_PDP_TYPES array.
+ */
+ const long DATACALL_PDP_TYPE_IPV4 = 0;
+ const long DATACALL_PDP_TYPE_IPV4V6 = 1;
+ const long DATACALL_PDP_TYPE_IPV6 = 2;
+
+ /**
+ * Reason for deactivating data call.
+ */
+ const long DATACALL_DEACTIVATE_NO_REASON = 0;
+ const long DATACALL_DEACTIVATE_RADIO_SHUTDOWN = 1;
+
+ /**
+ * Setup data call.
+ *
+ * @param apn
+ * Apn to connect to.
+ * @param username
+ * Username for apn.
+ * @param password
+ * Password for apn.
+ * @param authType
+ * Authentication type. One of the DATACALL_AUTH_* values.
+ * @param pdpType
+ * Connection type. One of the DATACALL_PDP_TYPE_* values.
+ * @param nsIDataCallCallback
+ * Called when request is finished.
+ *
+ * If successful, the notifySetupDataCallSuccess() will be called with the
+ * new nsIDataCall.
+ *
+ * Otherwise, the notifyError() will be called, and the error will be either
+ * 'RadioNotAvailable', 'OpNotAllowedBeforeRegToNw',
+ * 'OpNotAllowedDuringVoiceCall', 'RequestNotSupported' or 'GenericFailure'.
+ */
+ void setupDataCall(in AString apn, in AString username,
+ in AString password, in long authType,
+ in long pdpType,
+ in nsIDataCallCallback callback);
+
+ /**
+ * Deactivate data call.
+ *
+ * @param cid
+ * Context id.
+ * @param reason
+ * Disconnect Reason. One of the DATACALL_DEACTIVATE_* values.
+ * @param nsIDataCallCallback
+ * Called when request is finished.
+ *
+ * If successful, the notifySuccess() will be called.
+ *
+ * Otherwise, the notifyError() will be called, and the error will be either
+ * 'RadioNotAvailable' or 'GenericFailure'.
+ */
+ void deactivateDataCall(in long cid,
+ in long reason,
+ in nsIDataCallCallback callback);
+
+ /**
+ * Get current data call list.
+ *
+ * @param nsIDataCallCallback
+ * Called when request is finished.
+ *
+ * If successful, the notifyGetDataCallListSuccess() will be called with the
+ * list of nsIDataCall(s).
+ *
+ * Otherwise, the notifyError() will be called, and the error will be either
+ * 'RadioNotAvailable' or 'GenericFailure'.
+ */
+ void getDataCallList(in nsIDataCallCallback callback);
+
+ /**
+ * Set data registration state.
+ *
+ * @param attach
+ * whether to attach data registration or not.
+ * @param nsIDataCallCallback
+ * Called when request is finished.
+ *
+ * If successful, the notifySuccess() will be called.
+ *
+ * Otherwise, the notifyError() will be called, and the error will be either
+ * 'RadioNotAvailable', 'SubscriptionNotAvailable' or 'GenericFailure'.
+ */
+ void setDataRegistration(in boolean attach,
+ in nsIDataCallCallback callback);
+
+ /**
+ * Register to receive unsolicited events from this nsIDataCallInterface.
+ */
+ void registerListener(in nsIDataCallInterfaceListener listener);
+
+ /**
+ * Unregister to stop receiving unsolicited events from this
+ * nsIDataCallInterface.
+ */
+ void unregisterListener(in nsIDataCallInterfaceListener listener);
+};
+
+[scriptable, uuid(64700406-7429-4743-a6ae-f82e9877fd0d)]
+interface nsIDataCallInterfaceService : nsISupports
+{
+ /**
+ * Get the corresponding data call interface.
+ *
+ * @param clientId
+ * clientId of the data call interface to get.
+ */
+ nsIDataCallInterface getDataCallInterface(in long clientId);
+}; \ No newline at end of file
diff --git a/dom/system/gonk/nsIDataCallManager.idl b/dom/system/gonk/nsIDataCallManager.idl
new file mode 100644
index 000000000..de8477801
--- /dev/null
+++ b/dom/system/gonk/nsIDataCallManager.idl
@@ -0,0 +1,81 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+#include "nsINetworkInterface.idl"
+
+[scriptable, uuid(b8bcd6aa-5b06-4362-a68c-317878429e51)]
+interface nsIRilNetworkInfo : nsINetworkInfo
+{
+ readonly attribute unsigned long serviceId;
+ readonly attribute DOMString iccId;
+
+ /* The following attributes are for MMS proxy settings. */
+ readonly attribute DOMString mmsc; // Empty string if not set.
+ readonly attribute DOMString mmsProxy; // Empty string if not set.
+ readonly attribute long mmsPort; // -1 if not set.
+
+ /**
+ * Get the list of pcscf addresses, could be IPv4 or IPv6.
+ *
+ * @param count
+ * The length of the list of pcscf addresses.
+ *
+ * @returns the list of pcscf addresses.
+ */
+ void getPcscf([optional] out unsigned long count,
+ [array, size_is(count), retval] out wstring pcscf);
+};
+
+[scriptable, function, uuid(cb2f0f5b-67f4-4c14-93e8-01e66b630464)]
+interface nsIDeactivateDataCallsCallback : nsISupports
+{
+ /**
+ * Callback function used to notify when all data calls are disconnected.
+ */
+ void notifyDataCallsDisconnected();
+};
+
+[scriptable, uuid(e3feec20-36b4-47de-a7a5-e32a65f20186)]
+interface nsIDataCallHandler : nsISupports
+{
+ /**
+ * PDP APIs
+ *
+ * @param networkType
+ * Mobile network type, that is,
+ * nsINetworkInterface.NETWORK_TYPE_MOBILE or one of the
+ * nsINetworkInterface.NETWORK_TYPE_MOBILE_* values.
+ */
+ void setupDataCallByType(in long networkType);
+ void deactivateDataCallByType(in long networkType);
+ long getDataCallStateByType(in long networkType);
+
+ /**
+ * Deactivate all data calls.
+ *
+ * @param callback
+ * Callback to notify when all data calls are disconnected.
+ */
+ void deactivateDataCalls(in nsIDeactivateDataCallsCallback callback);
+
+ /**
+ * Called to reconsider data call state.
+ */
+ void updateRILNetworkInterface();
+};
+
+[scriptable, uuid(2c46e37d-88dc-4d25-bb37-e1c0d3e9cb5f)]
+interface nsIDataCallManager : nsISupports
+{
+ readonly attribute long dataDefaultServiceId;
+
+ /**
+ * Get the corresponding data call handler.
+ *
+ * @param clientId
+ * clientId of the data call handler to get.
+ */
+ nsIDataCallHandler getDataCallHandler(in unsigned long clientId);
+}; \ No newline at end of file
diff --git a/dom/system/gonk/nsIGonkDataCallInterfaceService.idl b/dom/system/gonk/nsIGonkDataCallInterfaceService.idl
new file mode 100644
index 000000000..240ca6bab
--- /dev/null
+++ b/dom/system/gonk/nsIGonkDataCallInterfaceService.idl
@@ -0,0 +1,18 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIDataCallInterfaceService.idl"
+
+[scriptable, uuid(f008d00c-e2b8-49b2-8f88-19111577938e)]
+interface nsIGonkDataCallInterfaceService : nsIDataCallInterfaceService
+{
+ /**
+ * Called by RadioInterface or lower layer to notify about data call list
+ * changes.
+ */
+ void notifyDataCallListChanged(in unsigned long clientId,
+ in uint32_t count,
+ [array, size_is(count)] in nsIDataCall
+ dataCalls);
+}; \ No newline at end of file
diff --git a/dom/system/gonk/nsINetworkInterface.idl b/dom/system/gonk/nsINetworkInterface.idl
new file mode 100644
index 000000000..bd40e751a
--- /dev/null
+++ b/dom/system/gonk/nsINetworkInterface.idl
@@ -0,0 +1,108 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+[scriptable, uuid(4816a559-5620-4cb5-8433-ff0b25e6622f)]
+interface nsINetworkInfo : nsISupports
+{
+ const long NETWORK_STATE_UNKNOWN = -1;
+ const long NETWORK_STATE_CONNECTING = 0;
+ const long NETWORK_STATE_CONNECTED = 1;
+ const long NETWORK_STATE_DISCONNECTING = 2;
+ const long NETWORK_STATE_DISCONNECTED = 3;
+ const long NETWORK_STATE_ENABLED = 4;
+ const long NETWORK_STATE_DISABLED = 5;
+
+ /**
+ * Current network state, one of the NETWORK_STATE_* constants.
+ *
+ * When this changes, network interface implementations notify with
+ * updateNetworkInterface() API.
+ */
+ readonly attribute long state;
+
+ const long NETWORK_TYPE_UNKNOWN = -1;
+ const long NETWORK_TYPE_WIFI = 0;
+ const long NETWORK_TYPE_MOBILE = 1;
+ const long NETWORK_TYPE_MOBILE_MMS = 2;
+ const long NETWORK_TYPE_MOBILE_SUPL = 3;
+ const long NETWORK_TYPE_WIFI_P2P = 4;
+ const long NETWORK_TYPE_MOBILE_IMS = 5;
+ const long NETWORK_TYPE_MOBILE_DUN = 6;
+ const long NETWORK_TYPE_MOBILE_FOTA = 7;
+ const long NETWORK_TYPE_ETHERNET = 8;
+
+ /**
+ * Network type. One of the NETWORK_TYPE_* constants.
+ */
+ readonly attribute long type;
+
+ /**
+ * Interface name of the network interface this network info belongs to.
+ */
+ readonly attribute DOMString name;
+
+ /**
+ * Get the list of ip addresses and prefix lengths, ip address could be IPv4
+ * or IPv6, typically 1 IPv4 or 1 IPv6 or one of each.
+ *
+ * @param ips
+ * The list of ip addresses retrieved.
+ * @param prefixLengths
+ * The list of prefix lengths retrieved.
+ *
+ * @returns the length of the lists.
+ */
+ void getAddresses([array, size_is(count)] out wstring ips,
+ [array, size_is(count)] out unsigned long prefixLengths,
+ [retval] out unsigned long count);
+
+ /**
+ * Get the list of gateways, could be IPv4 or IPv6, typically 1 IPv4 or 1
+ * IPv6 or one of each.
+ *
+ * @param count
+ * The length of the list of gateways
+ *
+ * @returns the list of gateways.
+ */
+ void getGateways([optional] out unsigned long count,
+ [array, size_is(count), retval] out wstring gateways);
+
+ /**
+ * Get the list of dnses, could be IPv4 or IPv6.
+ *
+ * @param count
+ * The length of the list of dnses.
+ *
+ * @returns the list of dnses.
+ */
+ void getDnses([optional] out unsigned long count,
+ [array, size_is(count), retval] out wstring dnses);
+};
+
+[scriptable, uuid(8b1345fa-b34c-41b3-8d21-09f961bf8887)]
+interface nsINetworkInterface : nsISupports
+{
+ /**
+ * The network information about this network interface.
+ */
+ readonly attribute nsINetworkInfo info;
+
+ /**
+ * The host name of the http proxy server.
+ */
+ readonly attribute DOMString httpProxyHost;
+
+ /*
+ * The port number of the http proxy server.
+ */
+ readonly attribute long httpProxyPort;
+
+ /*
+ * The Maximun Transmit Unit for this network interface, -1 if not set.
+ */
+ readonly attribute long mtu;
+};
diff --git a/dom/system/gonk/nsINetworkInterfaceListService.idl b/dom/system/gonk/nsINetworkInterfaceListService.idl
new file mode 100644
index 000000000..0c224842e
--- /dev/null
+++ b/dom/system/gonk/nsINetworkInterfaceListService.idl
@@ -0,0 +1,40 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsINetworkInfo;
+
+[scriptable, uuid(55779d32-1e28-4f43-af87-09d04bc3cce9)]
+interface nsINetworkInterfaceList : nsISupports
+{
+ /**
+ * Number of the network interfaces that is available.
+ */
+ long getNumberOfInterface();
+
+ /**
+ * Get the i-th interface info info from the list.
+ * @param interfaceIndex index of interface, from 0 to number of interface - 1.
+ */
+ nsINetworkInfo getInterfaceInfo(in long interfaceIndex);
+};
+
+[scriptable, uuid(21d7fc8b-28c4-4a4f-a15e-1f9defbc2cec)]
+interface nsINetworkInterfaceListService : nsISupports
+{
+ const long LIST_NOT_INCLUDE_MMS_INTERFACES = (1 << 0);
+ const long LIST_NOT_INCLUDE_SUPL_INTERFACES = (1 << 1);
+ const long LIST_NOT_INCLUDE_IMS_INTERFACES = (1 << 2);
+ const long LIST_NOT_INCLUDE_DUN_INTERFACES = (1 << 3);
+ const long LIST_NOT_INCLUDE_FOTA_INTERFACES = (1 << 4);
+
+ /**
+ * Obtain a list of network interfaces that satisfy the specified condition.
+ * @param condition flags that specify the interfaces to be returned. This
+ * can be OR combination of LIST_* flags, or zero to make all available
+ * interfaces returned.
+ */
+ nsINetworkInterfaceList getDataInterfaceList(in long condition);
+};
diff --git a/dom/system/gonk/nsINetworkManager.idl b/dom/system/gonk/nsINetworkManager.idl
new file mode 100644
index 000000000..0da123796
--- /dev/null
+++ b/dom/system/gonk/nsINetworkManager.idl
@@ -0,0 +1,135 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsINetworkInfo;
+interface nsINetworkInterface;
+
+/**
+ * Manage network interfaces.
+ */
+[scriptable, uuid(1ba9346b-53b5-4660-9dc6-58f0b258d0a6)]
+interface nsINetworkManager : nsISupports
+{
+ /**
+ * Register the given network interface with the network manager.
+ *
+ * Consumers will be notified with the 'network-interface-registered'
+ * observer notification.
+ *
+ * Throws if there's already an interface registered with the same network id.
+ *
+ * @param network
+ * Network interface to register.
+ */
+ void registerNetworkInterface(in nsINetworkInterface network);
+
+ /**
+ * Update the routes and DNSes according the state of the given network.
+ *
+ * Consumers will be notified with the 'network-connection-state-changed'
+ * observer notification.
+ *
+ * Throws an exception if the specified network interface object isn't
+ * registered.
+ *
+ * @param network
+ * Network interface to update.
+ */
+ void updateNetworkInterface(in nsINetworkInterface network);
+
+ /**
+ * Unregister the given network interface from the network manager.
+ *
+ * Consumers will be notified with the 'network-interface-unregistered'
+ * observer notification.
+ *
+ * Throws an exception if the specified network interface object isn't
+ * registered.
+ *
+ * @param network
+ * Network interface to unregister.
+ */
+ void unregisterNetworkInterface(in nsINetworkInterface network);
+
+ /**
+ * Object containing all known network information, keyed by their
+ * network id. Network id is composed of a sub-id + '-' + network
+ * type. For mobile network types, sub-id is 'ril' + service id; for
+ * non-mobile network types, sub-id is always 'device'.
+ */
+ readonly attribute jsval allNetworkInfo;
+
+ /**
+ * Priority list of network types. An array of
+ * nsINetworkInterface::NETWORK_TYPE_* constants.
+ *
+ * The piror position of the type indicates the higher priority. The priority
+ * is used to determine route when there are multiple connected networks.
+ */
+ attribute jsval networkTypePriorityList;
+
+ /**
+ * The preferred network type. One of the
+ * nsINetworkInterface::NETWORK_TYPE_* constants.
+ *
+ * This attribute is used for setting default route to favor
+ * interfaces with given type. This can be overriden by calling
+ * overrideDefaultRoute().
+ */
+ attribute long preferredNetworkType;
+
+ /**
+ * The network information of the network interface handling all network
+ * traffic.
+ *
+ * When this changes, the 'network-active-changed' observer
+ * notification is dispatched.
+ */
+ readonly attribute nsINetworkInfo activeNetworkInfo;
+
+ /**
+ * Override the default behaviour for preferredNetworkType and route
+ * all network traffic through the the specified interface.
+ *
+ * Consumers can observe changes to the active network by subscribing to
+ * the 'network-active-changed' observer notification.
+ *
+ * @param network
+ * Network to route all network traffic to. If this is null,
+ * a previous override is canceled.
+ */
+ long overrideActive(in nsINetworkInterface network);
+
+ /**
+ * Add host route to the specified network into routing table.
+ *
+ * @param network
+ * The network information for the host to be routed to.
+ * @param host
+ * The host to be added.
+ * The host will be resolved in advance if it's not an ip-address.
+ *
+ * @return a Promise
+ * resolved if added; rejected, otherwise.
+ */
+ jsval addHostRoute(in nsINetworkInfo network,
+ in DOMString host);
+
+ /**
+ * Remove host route to the specified network from routing table.
+ *
+ * @param network
+ * The network information for the routing to be removed from.
+ * @param host
+ * The host routed to the network.
+ * The host will be resolved in advance if it's not an ip-address.
+ *
+ * @return a Promise
+ * resolved if removed; rejected, otherwise.
+ */
+ jsval removeHostRoute(in nsINetworkInfo network,
+ in DOMString host);
+};
diff --git a/dom/system/gonk/nsINetworkService.idl b/dom/system/gonk/nsINetworkService.idl
new file mode 100644
index 000000000..50a468494
--- /dev/null
+++ b/dom/system/gonk/nsINetworkService.idl
@@ -0,0 +1,619 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+[scriptable, function, uuid(91824160-fb25-11e1-a21f-0800200c9a66)]
+interface nsIWifiTetheringCallback : nsISupports
+{
+ /**
+ * Callback function used to report status to WifiManager.
+ *
+ * @param error
+ * An error message if the operation wasn't successful,
+ * or `null` if it was.
+ */
+ void wifiTetheringEnabledChange(in jsval error);
+};
+
+[scriptable, function, uuid(9c128e68-5e4b-4626-bb88-84ec54cce5d8)]
+interface nsINetworkStatsCallback : nsISupports
+{
+ void networkStatsAvailable(in boolean success,
+ in unsigned long rxBytes,
+ in unsigned long txBytes,
+ in unsigned long long timestamp);
+};
+
+[scriptable, function, uuid(0706bfa2-ac2d-11e2-9a8d-7b6d988d4767)]
+interface nsINetworkUsageAlarmCallback : nsISupports
+{
+ void networkUsageAlarmResult(in jsval error);
+};
+
+[scriptable, function, uuid(9ede8720-f8bc-11e2-b778-0800200c9a66)]
+interface nsIWifiOperationModeCallback : nsISupports
+{
+ /**
+ * Callback function used to report result to WifiManager.
+ *
+ * @param error
+ * An error message if the operation wasn't successful,
+ * or `null` if it was.
+ */
+ void wifiOperationModeResult(in jsval error);
+};
+
+[scriptable, function, uuid(097878b0-19fc-11e3-8ffd-0800200c9a66)]
+interface nsISetDhcpServerCallback : nsISupports
+{
+ /**
+ * Callback function used to report the DHCP server set result
+ *
+ * @param error
+ * An error message if the operation wasn't successful,
+ * or `null` if it was.
+ */
+ void dhcpServerResult(in jsval error);
+};
+
+[scriptable, function, uuid(32407c50-46c7-11e3-8f96-0800200c9a66)]
+interface nsIUsbTetheringCallback : nsISupports
+{
+ /**
+ * Callback function used to report status of enabling usb tethering.
+ *
+ * @param error
+ * An error message if the operation wasn't successful,
+ * or `null` if it was.
+ */
+ void usbTetheringEnabledChange(in jsval error);
+};
+
+[scriptable, function, uuid(055fd560-46ad-11e3-8f96-0800200c9a66)]
+interface nsIEnableUsbRndisCallback : nsISupports
+{
+ /**
+ * Callback function used to report the status of enabling/disabling usb rndis.
+ *
+ * @param success
+ * Boolean to indicate the operation is successful or not.
+ * @param enable
+ * Boolean to indicate if we are enabling or disabling usb rndis.
+ */
+ void enableUsbRndisResult(in boolean success, in boolean enable);
+};
+
+[scriptable, function, uuid(4f08cc30-46ad-11e3-8f96-0800200c9a66)]
+interface nsIUpdateUpStreamCallback : nsISupports
+{
+ /**
+ * Callback function used to report the result of updating upstream.
+ *
+ * @param success
+ * Boolean to indicate the operation is successful or not.
+ * @param externalIfname
+ * The external interface name.
+ */
+ void updateUpStreamResult(in boolean success, in DOMString externalIfname);
+};
+
+[scriptable, function, uuid(eedca6c0-1310-11e4-9191-0800200c9a66)]
+interface nsISetDnsCallback : nsISupports
+{
+ /**
+ * Callback function used to report the result of setting DNS server.
+ *
+ * @param error
+ * An error message if the operation wasn't successful,
+ * or `null` if it was.
+ */
+ void setDnsResult(in jsval error);
+};
+
+[scriptable, function, uuid(5d0e1a60-1187-11e4-9191-0800200c9a66)]
+interface nsINativeCommandCallback : nsISupports
+{
+ /**
+ * Callback function used to report the result of a network native command.
+ *
+ * @param success
+ * Boolean to indicate the operation is successful or not.
+ */
+ void nativeCommandResult(in boolean success);
+};
+
+[scriptable, function, uuid(694abb80-1187-11e4-9191-0800200c9a66)]
+interface nsIDhcpRequestCallback : nsISupports
+{
+ /**
+ * Callback function used to report the result of DHCP client request.
+ *
+ * @param success
+ * Boolean to indicate the operation is successful or not.
+ *
+ * @param dhcpInfo
+ * An object to represent the successful DHCP request:
+ *
+ * - gateway_str: string
+ * - dns1_str: string
+ * - dns2_str: string
+ * - mask_str: string
+ * - server_str: string
+ * - vendor_str: string
+ * - lease: long
+ * - mask: long
+ * - ipaddr: long
+ * - gateway: long
+ * - dns1: long
+ * - dns2: long
+ * - server: long
+ */
+ void dhcpRequestResult(in boolean success, in jsval dhcpInfo);
+};
+
+[scriptable, function, uuid(88e3ee22-f1b3-4fa0-8a5d-793fb827c42c)]
+interface nsIGetInterfacesCallback : nsISupports
+{
+ /**
+ * Callback function used to return the list of existing network interfaces.
+ *
+ * @param success
+ * Boolean to indicate the operation is successful or not.
+ * @param interfaceList
+ * An array of interface name.
+ */
+ void getInterfacesResult(in boolean success, in jsval interfaceList);
+};
+
+[scriptable, function, uuid(064e02a3-d2c0-42c5-a293-1efa84056100)]
+interface nsIGetInterfaceConfigCallback : nsISupports
+{
+ /**
+ * Callback function used to return the network config of a given interface.
+ *
+ * @param success
+ * Boolean to indicate the operation is successful or not.
+ * @param result
+ * .ip: Ip address.
+ * .prefix: mask length.
+ * .link: network link properties.
+ * .mac: mac address.
+ */
+ void getInterfaceConfigResult(in boolean success, in jsval result);
+};
+
+[scriptable, function, uuid(b370f360-6ba8-4517-a4f9-31e8f004ee91)]
+interface nsISetInterfaceConfigCallback : nsISupports
+{
+ /**
+ * Callback function used to set network config for a specified interface.
+ *
+ * @param success
+ * Boolean to indicate the operation is successful or not.
+ */
+ void setInterfaceConfigResult(in boolean success);
+};
+
+/**
+ * Provide network services.
+ */
+[scriptable, uuid(e16fe98f-9f63-48fe-82ba-8d1a1b7c6a57)]
+interface nsINetworkService : nsISupports
+{
+ const long MODIFY_ROUTE_ADD = 0;
+ const long MODIFY_ROUTE_REMOVE = 1;
+
+ /**
+ * Enable or disable Wifi Tethering
+ *
+ * @param enabled
+ * Boolean that indicates whether tethering should be enabled (true) or disabled (false).
+ * @param config
+ * The Wifi Tethering configuration from settings db.
+ * @param callback
+ * Callback function used to report status to WifiManager.
+ */
+ void setWifiTethering(in boolean enabled,
+ in jsval config,
+ in nsIWifiTetheringCallback callback);
+
+ /**
+ * Enable or disable DHCP server
+ *
+ * @param enabled
+ * Boolean that indicates enabling or disabling DHCP server.
+ *
+ * @param config
+ * Config used to enable the DHCP server. It contains
+ * .startIp start of the ip lease range (string)
+ * .endIp end of the ip lease range (string)
+ * .serverIp ip of the DHCP server (string)
+ * .maskLength the length of the subnet mask
+ * .ifname the interface name
+ *
+ * As for disabling the DHCP server, put this value |null|.
+ *
+ * @param callback
+ * Callback function used to report status.
+ */
+ void setDhcpServer(in boolean enabled,
+ in jsval config,
+ in nsISetDhcpServerCallback callback);
+
+
+ /**
+ * Retrieve network interface stats.
+ *
+ * @param networkName
+ * Select the Network interface to request estats.
+ *
+ * @param callback
+ * Callback to notify result and provide stats, connectionType
+ * and the date when stats are retrieved
+ */
+ void getNetworkInterfaceStats(in DOMString networkName, in nsINetworkStatsCallback callback);
+
+ /**
+ * Set Alarm of usage per interface
+ *
+ * @param networkName
+ * Select the Network interface to set an alarm.
+ *
+ * @param threshold
+ * Amount of data that will trigger the alarm.
+ *
+ * @param callback
+ * Callback to notify the result.
+ *
+ * @return false if there is no interface registered for the networkType param.
+ */
+ boolean setNetworkInterfaceAlarm(in DOMString networkName,
+ in long long threshold,
+ in nsINetworkUsageAlarmCallback callback);
+
+ /**
+ * Reload Wifi firmware to specific operation mode.
+ *
+ * @param interfaceName
+ * Wifi Network interface name.
+ *
+ * @param mode
+ * AP - Access pointer mode.
+ * P2P - Peer to peer connection mode.
+ * STA - Station mode.
+ *
+ * @param callback
+ * Callback to notify Wifi firmware reload result.
+ */
+ void setWifiOperationMode(in DOMString interfaceName,
+ in DOMString mode,
+ in nsIWifiOperationModeCallback callback);
+
+ /**
+ * Set USB tethering.
+ *
+ * @param enabled
+ * Boolean to indicate we are going to enable or disable usb tethering.
+ * @param config
+ * The usb tethering configuration.
+ * @param callback
+ * Callback function used to report the result enabling/disabling usb tethering.
+ */
+ void setUSBTethering(in boolean enabled,
+ in jsval config,
+ in nsIUsbTetheringCallback callback);
+
+ /**
+ * Reset routing table.
+ *
+ * @param interfaceName
+ * The name of the network interface we want to remove from the routing
+ * table.
+ *
+ * @param callback
+ * Callback to notify the result of resetting routing table.
+ */
+ void resetRoutingTable(in DOMString interfaceName,
+ in nsINativeCommandCallback callback);
+
+ /**
+ * Set DNS.
+ *
+ * @param interfaceName
+ * The network interface name of the DNS we want to set.
+ * @param dnsesCount
+ * Number of elements in dnses.
+ * @param dnses
+ * Dnses to set.
+ * @param gatewaysCount
+ * Number of elements in gateways.
+ * @param gateways
+ * Gateways for the dnses, the most suitable, usually the one with the
+ * same address family, will be selected for each dns.
+ *
+ * @param callback
+ * Callback to notify the result of setting DNS server.
+ */
+ void setDNS(in DOMString interfaceName,
+ in unsigned long dnsesCount,
+ [array, size_is(dnsesCount)] in wstring dnses,
+ in unsigned long gatewaysCount,
+ [array, size_is(gatewaysCount)] in wstring gateways,
+ in nsISetDnsCallback callback);
+
+ /**
+ * Set default route.
+ *
+ * @param interfaceName
+ * The network interface name of the default route we want to set.
+ * @param count
+ * Number of elements in gateways.
+ * @param gateways
+ * Default gateways for setting default route.
+ *
+ * @param callback
+ * Callback to notify the result of setting default route.
+ */
+ void setDefaultRoute(in DOMString interfaceName,
+ in unsigned long count,
+ [array, size_is(count)] in wstring gateways,
+ in nsINativeCommandCallback callback);
+
+ /**
+ * Remove default route.
+ *
+ * @param interfaceName
+ * The network interface name of the default route we want to remove.
+ * @param count
+ * Number of elements in gateways.
+ * @param gatways
+ * Default gateways for removing default route.
+ *
+ * @param callback
+ * Callback to notify the result of removing default route.
+ */
+ void removeDefaultRoute(in DOMString interfaceName,
+ in unsigned long count,
+ [array, size_is(count)] in wstring gateways,
+ in nsINativeCommandCallback callback);
+
+ /**
+ * Modify route.
+ *
+ * @param action
+ * nsINetworkService.MODIFY_ROUTE_ADD to add route and
+ * nsINetworkService.MODIFY_ROUTE_REMOVE to remove.
+ * @param interfaceName
+ * Network interface name for the output of the host route.
+ * @param host
+ * Host ip we want to remove route for.
+ * @param prefixLength
+ * The prefix length of the route we'd like to modify.
+ * @param [optional] gateway
+ * Gateway ip for the output of the host route.
+ *
+ * @return A deferred promise that resolves on success or rejects with a
+ * specified reason otherwise.
+ */
+ jsval modifyRoute(in long action,
+ in DOMString interfaceName,
+ in DOMString host,
+ in long prefixLength,
+ [optional] in DOMString gateway);
+
+ /**
+ * Add route to secondary routing table.
+ *
+ * @param interfaceName
+ * The network interface for this route.
+ * @param route
+ * The route info should have the following fields:
+ * .ip: destination ip address
+ * .prefix: destination prefix
+ * .gateway: gateway ip address
+ */
+ void addSecondaryRoute(in DOMString interfaceName, in jsval route,
+ in nsINativeCommandCallback callback);
+
+ /**
+ * Remove route from secondary routing table.
+ *
+ * @param interfaceName
+ * The network interface for the route we want to remove.
+ * @param route
+ * The route info should have the following fields:
+ * .ip: destination ip address
+ * .prefix: destination prefix
+ * .gateway: gateway ip address
+ */
+ void removeSecondaryRoute(in DOMString interfaceName, in jsval route,
+ in nsINativeCommandCallback callback);
+
+ /**
+ * Enable or disable usb rndis.
+ *
+ * @param enable
+ * Boolean to indicate we want enable or disable usb rndis.
+ * @param callback
+ * Callback function to report the result.
+ */
+ void enableUsbRndis(in boolean enable,
+ in nsIEnableUsbRndisCallback callback);
+
+ /**
+ * Update upstream.
+ *
+ * @param previous
+ * The previous internal and external interface.
+ * @param current
+ * The current internal and external interface.
+ * @param callback
+ * Callback function to report the result.
+ */
+ void updateUpStream(in jsval previous,
+ in jsval current,
+ in nsIUpdateUpStreamCallback callback);
+
+ /*
+ * Obtain interfaces list.
+ *
+ * @param callback
+ * Callback function to return the result.
+ */
+ void getInterfaces(in nsIGetInterfacesCallback callback);
+
+ /**
+ * Get config of a network interface.
+ *
+ * @param ifname
+ * Target interface.
+ * @param callback
+ * Callback function to report the result.
+ */
+ void getInterfaceConfig(in DOMString ifname, in nsIGetInterfaceConfigCallback callback);
+
+ /**
+ * Set config for a network interface.
+ *
+ * @param config
+ * .ifname: Target interface.
+ * .ip: Ip address.
+ * .prefix: mask length.
+ * .link: network link properties.
+ * @param callback
+ * Callback function to report the result.
+ */
+ void setInterfaceConfig(in jsval config, in nsISetInterfaceConfigCallback callback);
+
+ /**
+ * Configure a network interface.
+ *
+ * @param config
+ * An object containing the detail that we want to configure the interface:
+ *
+ * - ifname: string
+ * - ipaddr: long
+ * - mask: long
+ * - gateway: long
+ * - dns1: long
+ * - dns2: long
+ *
+ * @param callback
+ * Callback to notify the result of configurating network interface.
+ */
+ void configureInterface(in jsval config,
+ in nsINativeCommandCallback callback);
+
+ /**
+ * Issue a DHCP client request.
+ *
+ * @param networkInterface
+ * The network interface which we wnat to do the DHCP request on.
+ *
+ * @param callback
+ * Callback to notify the result of the DHCP request.
+ */
+ void dhcpRequest(in DOMString interfaceName,
+ in nsIDhcpRequestCallback callback);
+
+ /**
+ * Stop Dhcp daemon.
+ *
+ * @param ifname
+ * Target interface.
+ *
+ * @param callback
+ * Callback to notify the result of stopping dhcp request.
+ */
+ void stopDhcp(in DOMString ifname,
+ in nsINativeCommandCallback callback);
+
+ /**
+ * Enable a network interface.
+ *
+ * @param networkInterface
+ * The network interface name which we want to enable.
+ *
+ * @param callback
+ * Callback to notify the result of disabling network interface.
+ */
+ void enableInterface(in DOMString interfaceName,
+ in nsINativeCommandCallback callback);
+
+ /**
+ * Disable a network interface.
+ *
+ * @param networkInterface
+ * The network interface name which we want to disable.
+ *
+ * @param callback
+ * Callback to notify the result of disabling network interface.
+ */
+ void disableInterface(in DOMString interfaceName,
+ in nsINativeCommandCallback callback);
+
+ /**
+ * Reset all connections on a specified network interface.
+ *
+ * @param interfaceName
+ * The network interface name which we want to reset.
+ *
+ * @param callback
+ * Callback to notify the result of resetting connections.
+ */
+ void resetConnections(in DOMString interfaceName,
+ in nsINativeCommandCallback callback);
+
+ /**
+ * Create network (required to call prior to any networking operation).
+ *
+ * @param interfaceName
+ * The network interface name which we want to create network for.
+ *
+ * @param callback
+ * Callback to notify the result of creating network.
+ */
+ void createNetwork(in DOMString interfaceName,
+ in nsINativeCommandCallback callback);
+
+ /**
+ * Destroy network.
+ *
+ * @param interfaceName
+ * The network interface name of the network we want to destroy.
+ *
+ * @param callback
+ * Callback to notify the result of destroying network.
+ */
+ void destroyNetwork(in DOMString interfaceName,
+ in nsINativeCommandCallback callback);
+
+ /**
+ * Query the netId associated with given network interface name.
+ *
+ * @param interfaceName
+ * The network interface name which we want to query.
+ *
+ * @return A deferred promise that resolves with a string to indicate.
+ * the queried netId on success and rejects if the interface name
+ * is invalid.
+ *
+ */
+ jsval getNetId(in DOMString interfaceName);
+
+ /**
+ * Set maximum transmission unit on a network interface.
+ *
+ * @param interfaceName
+ * The name of the network interface that we want to set mtu.
+ * @param mtu
+ * Size of maximum tranmission unit.
+ *
+ * @param callback
+ * Callback to notify the result of setting mtu.
+ */
+ void setMtu(in DOMString interfaceName, in long mtu,
+ in nsINativeCommandCallback callback);
+};
diff --git a/dom/system/gonk/nsINetworkWorker.idl b/dom/system/gonk/nsINetworkWorker.idl
new file mode 100644
index 000000000..8fe19be69
--- /dev/null
+++ b/dom/system/gonk/nsINetworkWorker.idl
@@ -0,0 +1,18 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+[scriptable, uuid(98e31d3b-6cad-4cab-b4b3-4afff566ea65)]
+interface nsINetworkEventListener : nsISupports {
+ void onEvent(in jsval result);
+};
+
+[scriptable, uuid(f9d9c694-0aac-4f9a-98ac-7788f954239a)]
+interface nsINetworkWorker : nsISupports {
+ void start(in nsINetworkEventListener listener);
+ void shutdown();
+ [implicit_jscontext]
+ void postMessage(in jsval options);
+};
diff --git a/dom/system/gonk/nsIRadioInterfaceLayer.idl b/dom/system/gonk/nsIRadioInterfaceLayer.idl
new file mode 100644
index 000000000..168fe3894
--- /dev/null
+++ b/dom/system/gonk/nsIRadioInterfaceLayer.idl
@@ -0,0 +1,53 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIIccInfo;
+interface nsIMobileConnectionInfo;
+interface nsIMobileMessageCallback;
+
+[scriptable, function, uuid(3bc96351-53b0-47a1-a888-c74c64b60f25)]
+interface nsIRilSendWorkerMessageCallback : nsISupports
+{
+ boolean handleResponse(in jsval response);
+};
+
+[scriptable, uuid(1a3ef88a-e4d1-11e4-8512-176220f2b32b)]
+interface nsIRadioInterface : nsISupports
+{
+ /**
+ * PDP APIs
+ *
+ * @param networkType
+ * Mobile network type, that is, nsINetworkInterface.NETWORK_TYPE_MOBILE
+ * or one of the nsINetworkInterface.NETWORK_TYPE_MOBILE_* values.
+ */
+ void setupDataCallByType(in long networkType);
+ void deactivateDataCallByType(in long networkType);
+ long getDataCallStateByType(in long networkType);
+
+ void updateRILNetworkInterface();
+
+ void sendWorkerMessage(in DOMString type,
+ [optional] in jsval message,
+ [optional] in nsIRilSendWorkerMessageCallback callback);
+};
+
+%{C++
+#define NS_RADIOINTERFACELAYER_CID \
+ { 0x2d831c8d, 0x6017, 0x435b, \
+ { 0xa8, 0x0c, 0xe5, 0xd4, 0x22, 0x81, 0x0c, 0xea } }
+#define NS_RADIOINTERFACELAYER_CONTRACTID "@mozilla.org/ril;1"
+%}
+
+[scriptable, uuid(09730e0d-75bb-4f21-8540-062a2eadc8ff)]
+interface nsIRadioInterfaceLayer : nsISupports
+{
+ readonly attribute unsigned long numRadioInterfaces;
+
+ nsIRadioInterface getRadioInterface(in unsigned long clientId);
+
+ void setMicrophoneMuted(in boolean muted);
+};
diff --git a/dom/system/gonk/nsISystemWorkerManager.idl b/dom/system/gonk/nsISystemWorkerManager.idl
new file mode 100644
index 000000000..a77e253e4
--- /dev/null
+++ b/dom/system/gonk/nsISystemWorkerManager.idl
@@ -0,0 +1,16 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+/**
+ * Information about networks that is exposed to network manager API consumers.
+ */
+[scriptable, builtinclass, uuid(4984b669-0ee0-4809-ae96-3358a325a6b0)]
+interface nsISystemWorkerManager : nsISupports
+{
+ [implicit_jscontext]
+ void registerRilWorker(in unsigned long aClientId,
+ in jsval aWorker);
+};
diff --git a/dom/system/gonk/nsITetheringService.idl b/dom/system/gonk/nsITetheringService.idl
new file mode 100644
index 000000000..530ab0069
--- /dev/null
+++ b/dom/system/gonk/nsITetheringService.idl
@@ -0,0 +1,39 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsINetworkInterface;
+interface nsIWifiTetheringCallback;
+
+[scriptable, uuid(779de2d3-6d29-4ee6-b069-6251839f757a)]
+interface nsITetheringService : nsISupports
+{
+ const long TETHERING_STATE_INACTIVE = 0;
+ const long TETHERING_STATE_WIFI = 1;
+ const long TETHERING_STATE_USB = 2;
+
+ /**
+ * Current tethering state. One of the TETHERING_STATE_* constants.
+ */
+ readonly attribute long state;
+
+ /**
+ * Enable or disable Wifi Tethering.
+ *
+ * @param enabled
+ * Boolean that indicates whether tethering should be enabled (true) or
+ * disabled (false).
+ * @param interfaceName
+ * The Wifi network interface name for internal interface.
+ * @param config
+ * The Wifi Tethering configuration from settings db.
+ * @param callback
+ * Callback function used to report status to WifiManager.
+ */
+ void setWifiTethering(in boolean enabled,
+ in DOMString interfaceName,
+ in jsval config,
+ in nsIWifiTetheringCallback callback);
+}; \ No newline at end of file
diff --git a/dom/system/gonk/nsIVolume.idl b/dom/system/gonk/nsIVolume.idl
new file mode 100644
index 000000000..60785f0a4
--- /dev/null
+++ b/dom/system/gonk/nsIVolume.idl
@@ -0,0 +1,114 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+#include "nsIVolumeStat.idl"
+
+[scriptable, uuid(EE752CB8-8FD7-11E4-A602-70221D5D46B0)]
+interface nsIVolume : nsISupports
+{
+ // These MUST match the states from android's system/vold/Volume.h header
+ // Note: Changes made to the STATE_xxx names should also be reflected in the
+ // NS_VolumeStateStr function found in Volume.cpp
+ const long STATE_INIT = -1;
+ const long STATE_NOMEDIA = 0;
+ const long STATE_IDLE = 1;
+ const long STATE_PENDING = 2;
+ const long STATE_CHECKING = 3;
+ const long STATE_MOUNTED = 4;
+ const long STATE_UNMOUNTING = 5;
+ const long STATE_FORMATTING = 6;
+ const long STATE_SHARED = 7;
+ const long STATE_SHAREDMNT = 8;
+ const long STATE_CHECKMNT = 100;
+ const long STATE_MOUNT_FAIL = 101;
+
+ // The name of the volume. Often there is only one volume, called sdcard.
+ // But some phones support multiple volumes.
+ readonly attribute DOMString name;
+
+ // The mount point is the path on the system where the volume is mounted
+ // and is only valid when state == STATE_MOUNTED.
+ readonly attribute DOMString mountPoint;
+
+ // Reflects the current state of the volume, using STATE_xxx constants
+ // from above.
+ readonly attribute long state;
+
+ // mountGeneration is a unique number which is used distinguish between
+ // periods of time that a volume is in the mounted state. Each time a
+ // volume transitions to the mounted state, the mountGeneration will
+ // be different from the last time it transitioned to the mounted state.
+ readonly attribute long mountGeneration;
+
+ // While a volume is mounted, it can be locked, preventing it from being
+ // shared with the PC. To lock a volume, acquire an MozWakeLock
+ // using the name of this attribute. Note that mountLockName changes
+ // every time the mountGeneration changes, so you'll need to reacquire
+ // the wakelock every time the volume becomes mounted.
+ readonly attribute DOMString mountLockName;
+
+ // Determines if a mountlock is currently being held against this volume.
+ readonly attribute boolean isMountLocked;
+
+ // Determines if media is actually present or not. Note, that when an sdcard
+ // is ejected, it may go through several tranistory states before finally
+ // arriving at STATE_NOMEDIA. So isMediaPresent may be false even when the
+ // current state isn't STATE_NOMEDIA.
+ readonly attribute boolean isMediaPresent;
+
+ // Determines if the volume is currently being shared. This covers off
+ // more than just state == STATE_SHARED. isSharing will return true from the
+ // time that the volume leaves the mounted state, until it gets back to
+ // mounted, nomedia, or formatting states. This attribute is to allow
+ // device storage to suppress unwanted 'unavailable' status when
+ // transitioning from mounted to sharing and back again.
+ readonly attribute boolean isSharing;
+
+ // Determines if the volume is currently formatting. This sets true once
+ // mFormatRequest == true and mState == STATE_MOUNTED, and sets false
+ // once the volume has been formatted and mounted again.
+ readonly attribute boolean isFormatting;
+
+ readonly attribute boolean isUnmounting;
+
+ nsIVolumeStat getStats();
+
+ // Formats the volume in IO thread, if the volume is ready to be formatted.
+ // Automounter will unmount it, format it and then mount it again.
+ void format();
+
+ // Mounts the volume in IO thread, if the volume is already unmounted.
+ // Automounter will mount it. Otherwise Automounter will skip this.
+ void mount();
+
+ // Unmounts the volume in IO thread, if the volume is already mounted.
+ // Automounter will unmount it. Otherwise Automounter will skip this.
+ void unmount();
+
+ // Whether this is a fake volume.
+ readonly attribute boolean isFake;
+
+ // Whether this is a removable volume
+ readonly attribute boolean isRemovable;
+
+ // Whether this is a hot-swappable volume
+ readonly attribute boolean isHotSwappable;
+
+};
+
+%{C++
+// For use with the ObserverService
+#define NS_VOLUME_STATE_CHANGED "volume-state-changed"
+#define NS_VOLUME_REMOVED "volume-removed"
+
+namespace mozilla {
+namespace system {
+
+// Convert a state into a loggable/printable string.
+const char* NS_VolumeStateStr(int32_t aState);
+
+} // system
+} // mozilla
+%}
diff --git a/dom/system/gonk/nsIVolumeMountLock.idl b/dom/system/gonk/nsIVolumeMountLock.idl
new file mode 100644
index 000000000..0a9a1a5c2
--- /dev/null
+++ b/dom/system/gonk/nsIVolumeMountLock.idl
@@ -0,0 +1,12 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+[scriptable, uuid(44449f34-5ca1-4aff-bce6-22c79263de24)]
+interface nsIVolumeMountLock : nsISupports
+{
+ void unlock();
+};
+
diff --git a/dom/system/gonk/nsIVolumeService.idl b/dom/system/gonk/nsIVolumeService.idl
new file mode 100644
index 000000000..d3752e201
--- /dev/null
+++ b/dom/system/gonk/nsIVolumeService.idl
@@ -0,0 +1,36 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+#include "nsIVolume.idl"
+#include "nsIVolumeMountLock.idl"
+
+interface nsIArray;
+
+[scriptable, uuid(cfbf9880-cba5-11e4-8830-0800200c9a66)]
+interface nsIVolumeService : nsISupports
+{
+ nsIVolume getVolumeByName(in DOMString volName);
+ nsIVolume getVolumeByPath(in DOMString path);
+ nsIVolume createOrGetVolumeByPath(in DOMString path);
+
+ nsIVolumeMountLock createMountLock(in DOMString volName);
+
+ nsIArray getVolumeNames();
+
+ void Dump(in DOMString label);
+
+ /* for test case only to simulate sdcard insertion/removal */
+ void createFakeVolume(in DOMString name, in DOMString path);
+ void SetFakeVolumeState(in DOMString name, in long state);
+
+ /* for test case only to test removal of storage area */
+ void removeFakeVolume(in DOMString name);
+};
+
+%{C++
+#define NS_VOLUMESERVICE_CID \
+ {0x7c179fb7, 0x67a0, 0x43a3, {0x93, 0x37, 0x29, 0x4e, 0x03, 0x60, 0xb8, 0x58}}
+#define NS_VOLUMESERVICE_CONTRACTID "@mozilla.org/telephony/volume-service;1"
+%}
diff --git a/dom/system/gonk/nsIVolumeStat.idl b/dom/system/gonk/nsIVolumeStat.idl
new file mode 100644
index 000000000..1d725689d
--- /dev/null
+++ b/dom/system/gonk/nsIVolumeStat.idl
@@ -0,0 +1,12 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+[scriptable, uuid(b4c050d0-c57a-11e1-9b21-0800200c9a66)]
+interface nsIVolumeStat : nsISupports
+{
+ readonly attribute long long totalBytes;
+ readonly attribute long long freeBytes;
+};
diff --git a/dom/system/gonk/nsIWorkerHolder.idl b/dom/system/gonk/nsIWorkerHolder.idl
new file mode 100644
index 000000000..e5cc82c9e
--- /dev/null
+++ b/dom/system/gonk/nsIWorkerHolder.idl
@@ -0,0 +1,11 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+[scriptable, uuid(c04f3102-1ce8-4d57-9c27-8aece9c2740a)]
+interface nsIWorkerHolder : nsISupports
+{
+ readonly attribute jsval worker;
+};
diff --git a/dom/system/gonk/nsVolume.cpp b/dom/system/gonk/nsVolume.cpp
new file mode 100644
index 000000000..77a1628e4
--- /dev/null
+++ b/dom/system/gonk/nsVolume.cpp
@@ -0,0 +1,467 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsVolume.h"
+
+#include "base/message_loop.h"
+#include "base/task.h"
+#include "nsIPowerManagerService.h"
+#include "nsISupportsUtils.h"
+#include "nsIVolume.h"
+#include "nsServiceManagerUtils.h"
+#include "nsThreadUtils.h"
+#include "nsVolumeStat.h"
+#include "nsXULAppAPI.h"
+#include "Volume.h"
+#include "AutoMounter.h"
+#include "VolumeManager.h"
+
+#undef VOLUME_MANAGER_LOG_TAG
+#define VOLUME_MANAGER_LOG_TAG "nsVolume"
+#include "VolumeManagerLog.h"
+
+namespace mozilla {
+namespace system {
+
+const char *
+NS_VolumeStateStr(int32_t aState)
+{
+ switch (aState) {
+ case nsIVolume::STATE_INIT: return "Init";
+ case nsIVolume::STATE_NOMEDIA: return "NoMedia";
+ case nsIVolume::STATE_IDLE: return "Idle";
+ case nsIVolume::STATE_PENDING: return "Pending";
+ case nsIVolume::STATE_CHECKING: return "Checking";
+ case nsIVolume::STATE_MOUNTED: return "Mounted";
+ case nsIVolume::STATE_UNMOUNTING: return "Unmounting";
+ case nsIVolume::STATE_FORMATTING: return "Formatting";
+ case nsIVolume::STATE_SHARED: return "Shared";
+ case nsIVolume::STATE_SHAREDMNT: return "Shared-Mounted";
+ case nsIVolume::STATE_CHECKMNT: return "Check-Mounted";
+ case nsIVolume::STATE_MOUNT_FAIL: return "Mount-Fail";
+ }
+ return "???";
+}
+
+// While nsVolumes can only be used on the main thread, in the
+// UpdateVolumeRunnable constructor (which is called from IOThread) we
+// allocate an nsVolume which is then passed to MainThread. Since we
+// have a situation where we allocate on one thread and free on another
+// we use a thread safe AddRef implementation.
+NS_IMPL_ISUPPORTS(nsVolume, nsIVolume)
+
+nsVolume::nsVolume(const Volume* aVolume)
+ : mName(NS_ConvertUTF8toUTF16(aVolume->Name())),
+ mMountPoint(NS_ConvertUTF8toUTF16(aVolume->MountPoint())),
+ mState(aVolume->State()),
+ mMountGeneration(aVolume->MountGeneration()),
+ mMountLocked(aVolume->IsMountLocked()),
+ mIsFake(!aVolume->CanBeShared()),
+ mIsMediaPresent(aVolume->MediaPresent()),
+ mIsSharing(aVolume->IsSharing()),
+ mIsFormatting(aVolume->IsFormatting()),
+ mIsUnmounting(aVolume->IsUnmounting()),
+ mIsRemovable(aVolume->IsRemovable()),
+ mIsHotSwappable(aVolume->IsHotSwappable())
+{
+}
+
+nsVolume::nsVolume(const nsVolume* aVolume)
+ : mName(aVolume->mName),
+ mMountPoint(aVolume->mMountPoint),
+ mState(aVolume->mState),
+ mMountGeneration(aVolume->mMountGeneration),
+ mMountLocked(aVolume->mMountLocked),
+ mIsFake(aVolume->mIsFake),
+ mIsMediaPresent(aVolume->mIsMediaPresent),
+ mIsSharing(aVolume->mIsSharing),
+ mIsFormatting(aVolume->mIsFormatting),
+ mIsUnmounting(aVolume->mIsUnmounting),
+ mIsRemovable(aVolume->mIsRemovable),
+ mIsHotSwappable(aVolume->mIsHotSwappable)
+{
+}
+
+void nsVolume::Dump(const char* aLabel) const
+{
+ LOG("%s: Volume: %s is %s and %s @ %s gen %d locked %d",
+ aLabel,
+ NameStr().get(),
+ StateStr(),
+ IsMediaPresent() ? "inserted" : "missing",
+ MountPointStr().get(),
+ MountGeneration(),
+ (int)IsMountLocked());
+ LOG("%s: IsSharing %s IsFormating %s IsUnmounting %s",
+ aLabel,
+ (IsSharing() ? "y" : "n"),
+ (IsFormatting() ? "y" : "n"),
+ (IsUnmounting() ? "y" : "n"));
+}
+
+bool nsVolume::Equals(nsIVolume* aVolume)
+{
+ nsString volName;
+ aVolume->GetName(volName);
+ if (!mName.Equals(volName)) {
+ return false;
+ }
+
+ nsString volMountPoint;
+ aVolume->GetMountPoint(volMountPoint);
+ if (!mMountPoint.Equals(volMountPoint)) {
+ return false;
+ }
+
+ int32_t volState;
+ aVolume->GetState(&volState);
+ if (mState != volState){
+ return false;
+ }
+
+ int32_t volMountGeneration;
+ aVolume->GetMountGeneration(&volMountGeneration);
+ if (mMountGeneration != volMountGeneration) {
+ return false;
+ }
+
+ bool volIsMountLocked;
+ aVolume->GetIsMountLocked(&volIsMountLocked);
+ if (mMountLocked != volIsMountLocked) {
+ return false;
+ }
+
+ bool isFake;
+ aVolume->GetIsFake(&isFake);
+ if (mIsFake != isFake) {
+ return false;
+ }
+
+ bool isSharing;
+ aVolume->GetIsSharing(&isSharing);
+ if (mIsSharing != isSharing) {
+ return false;
+ }
+
+ bool isFormatting;
+ aVolume->GetIsFormatting(&isFormatting);
+ if (mIsFormatting != isFormatting) {
+ return false;
+ }
+
+ bool isUnmounting;
+ aVolume->GetIsUnmounting(&isUnmounting);
+ if (mIsUnmounting != isUnmounting) {
+ return false;
+ }
+
+ bool isRemovable;
+ aVolume->GetIsRemovable(&isRemovable);
+ if (mIsRemovable != isRemovable) {
+ return false;
+ }
+
+ bool isHotSwappable;
+ aVolume->GetIsHotSwappable(&isHotSwappable);
+ if (mIsHotSwappable != isHotSwappable) {
+ return false;
+ }
+
+ return true;
+}
+
+NS_IMETHODIMP nsVolume::GetIsMediaPresent(bool* aIsMediaPresent)
+{
+ *aIsMediaPresent = mIsMediaPresent;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsVolume::GetIsMountLocked(bool* aIsMountLocked)
+{
+ *aIsMountLocked = mMountLocked;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsVolume::GetIsSharing(bool* aIsSharing)
+{
+ *aIsSharing = mIsSharing;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsVolume::GetIsFormatting(bool* aIsFormatting)
+{
+ *aIsFormatting = mIsFormatting;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsVolume::GetIsUnmounting(bool* aIsUnmounting)
+{
+ *aIsUnmounting = mIsUnmounting;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsVolume::GetName(nsAString& aName)
+{
+ aName = mName;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsVolume::GetMountGeneration(int32_t* aMountGeneration)
+{
+ *aMountGeneration = mMountGeneration;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsVolume::GetMountLockName(nsAString& aMountLockName)
+{
+ aMountLockName = NS_LITERAL_STRING("volume-") + Name();
+ aMountLockName.AppendPrintf("-%d", mMountGeneration);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsVolume::GetMountPoint(nsAString& aMountPoint)
+{
+ aMountPoint = mMountPoint;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsVolume::GetState(int32_t* aState)
+{
+ *aState = mState;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsVolume::GetStats(nsIVolumeStat **aResult)
+{
+ if (mState != STATE_MOUNTED) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ NS_IF_ADDREF(*aResult = new nsVolumeStat(mMountPoint));
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsVolume::GetIsFake(bool *aIsFake)
+{
+ *aIsFake = mIsFake;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsVolume::GetIsRemovable(bool *aIsRemovable)
+{
+ *aIsRemovable = mIsRemovable;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsVolume::GetIsHotSwappable(bool *aIsHotSwappable)
+{
+ *aIsHotSwappable = mIsHotSwappable;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsVolume::Format()
+{
+ MOZ_ASSERT(XRE_IsParentProcess());
+
+ XRE_GetIOMessageLoop()->PostTask(
+ NewRunnableFunction(FormatVolumeIOThread, NameStr()));
+
+ return NS_OK;
+}
+
+/* static */
+void nsVolume::FormatVolumeIOThread(const nsCString& aVolume)
+{
+ MOZ_ASSERT(XRE_IsParentProcess());
+
+ MOZ_ASSERT(MessageLoop::current() == XRE_GetIOMessageLoop());
+ if (VolumeManager::State() != VolumeManager::VOLUMES_READY) {
+ return;
+ }
+
+ AutoMounterFormatVolume(aVolume);
+}
+
+NS_IMETHODIMP nsVolume::Mount()
+{
+ MOZ_ASSERT(XRE_IsParentProcess());
+
+ XRE_GetIOMessageLoop()->PostTask(
+ NewRunnableFunction(MountVolumeIOThread, NameStr()));
+
+ return NS_OK;
+}
+
+/* static */
+void nsVolume::MountVolumeIOThread(const nsCString& aVolume)
+{
+ MOZ_ASSERT(XRE_IsParentProcess());
+
+ MOZ_ASSERT(MessageLoop::current() == XRE_GetIOMessageLoop());
+ if (VolumeManager::State() != VolumeManager::VOLUMES_READY) {
+ return;
+ }
+
+ AutoMounterMountVolume(aVolume);
+}
+
+NS_IMETHODIMP nsVolume::Unmount()
+{
+ MOZ_ASSERT(XRE_IsParentProcess());
+
+ XRE_GetIOMessageLoop()->PostTask(
+ NewRunnableFunction(UnmountVolumeIOThread, NameStr()));
+
+ return NS_OK;
+}
+
+/* static */
+void nsVolume::UnmountVolumeIOThread(const nsCString& aVolume)
+{
+ MOZ_ASSERT(XRE_IsParentProcess());
+
+ MOZ_ASSERT(MessageLoop::current() == XRE_GetIOMessageLoop());
+ if (VolumeManager::State() != VolumeManager::VOLUMES_READY) {
+ return;
+ }
+
+ AutoMounterUnmountVolume(aVolume);
+}
+
+void
+nsVolume::LogState() const
+{
+ if (mState == nsIVolume::STATE_MOUNTED) {
+ LOG("nsVolume: %s state %s @ '%s' gen %d locked %d fake %d "
+ "media %d sharing %d formatting %d unmounting %d removable %d hotswappable %d",
+ NameStr().get(), StateStr(), MountPointStr().get(),
+ MountGeneration(), (int)IsMountLocked(), (int)IsFake(),
+ (int)IsMediaPresent(), (int)IsSharing(),
+ (int)IsFormatting(), (int)IsUnmounting(),
+ (int)IsRemovable(), (int)IsHotSwappable());
+ return;
+ }
+
+ LOG("nsVolume: %s state %s", NameStr().get(), StateStr());
+}
+
+void nsVolume::UpdateMountLock(nsVolume* aOldVolume)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ bool oldMountLocked = aOldVolume ? aOldVolume->mMountLocked : false;
+ if (mState != nsIVolume::STATE_MOUNTED) {
+ // Since we're not in the mounted state, we need to
+ // forgot whatever mount generation we may have had.
+ mMountGeneration = -1;
+ mMountLocked = oldMountLocked;
+ return;
+ }
+
+ int32_t oldMountGeneration = aOldVolume ? aOldVolume->mMountGeneration : -1;
+ if (mMountGeneration == oldMountGeneration) {
+ // No change in mount generation, nothing else to do
+ mMountLocked = oldMountLocked;
+ return;
+ }
+
+ if (!XRE_IsParentProcess()) {
+ // Child processes just track the state, not maintain it.
+ return;
+ }
+
+ // Notify the Volume on IOThread whether the volume is locked or not.
+ nsCOMPtr<nsIPowerManagerService> pmService =
+ do_GetService(POWERMANAGERSERVICE_CONTRACTID);
+ if (!pmService) {
+ return;
+ }
+ nsString mountLockName;
+ GetMountLockName(mountLockName);
+ nsString mountLockState;
+ pmService->GetWakeLockState(mountLockName, mountLockState);
+ UpdateMountLock(mountLockState);
+}
+
+void
+nsVolume::UpdateMountLock(const nsAString& aMountLockState)
+{
+ MOZ_ASSERT(XRE_IsParentProcess());
+ MOZ_ASSERT(NS_IsMainThread());
+
+ // There are 3 states, unlocked, locked-background, and locked-foreground
+ // I figured it was easier to use negtive logic and compare for unlocked.
+ UpdateMountLock(!aMountLockState.EqualsLiteral("unlocked"));
+}
+
+void
+nsVolume::UpdateMountLock(bool aMountLocked)
+{
+ MOZ_ASSERT(XRE_IsParentProcess());
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (aMountLocked == mMountLocked) {
+ return;
+ }
+ // The locked/unlocked state changed. Tell IOThread about it.
+ mMountLocked = aMountLocked;
+ LogState();
+ XRE_GetIOMessageLoop()->PostTask(
+ NewRunnableFunction(Volume::UpdateMountLock,
+ NS_LossyConvertUTF16toASCII(Name()),
+ MountGeneration(), aMountLocked));
+}
+
+void
+nsVolume::SetIsFake(bool aIsFake)
+{
+ mIsFake = aIsFake;
+ if (mIsFake) {
+ // The media is always present for fake volumes.
+ mIsMediaPresent = true;
+ MOZ_ASSERT(!mIsSharing);
+ }
+}
+
+void
+nsVolume::SetIsRemovable(bool aIsRemovable)
+{
+ mIsRemovable = aIsRemovable;
+ if (!mIsRemovable) {
+ mIsHotSwappable = false;
+ }
+}
+
+void
+nsVolume::SetIsHotSwappable(bool aIsHotSwappable)
+{
+ mIsHotSwappable = aIsHotSwappable;
+ if (mIsHotSwappable) {
+ mIsRemovable = true;
+ }
+}
+
+void
+nsVolume::SetState(int32_t aState)
+{
+ static int32_t sMountGeneration = 0;
+
+ MOZ_ASSERT(XRE_IsParentProcess());
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(IsFake());
+
+ if (aState == mState) {
+ return;
+ }
+
+ if (aState == nsIVolume::STATE_MOUNTED) {
+ mMountGeneration = ++sMountGeneration;
+ }
+
+ mState = aState;
+}
+
+} // system
+} // mozilla
diff --git a/dom/system/gonk/nsVolume.h b/dom/system/gonk/nsVolume.h
new file mode 100644
index 000000000..88be425f6
--- /dev/null
+++ b/dom/system/gonk/nsVolume.h
@@ -0,0 +1,114 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_system_nsvolume_h__
+#define mozilla_system_nsvolume_h__
+
+#include "nsCOMPtr.h"
+#include "nsIVolume.h"
+#include "nsString.h"
+#include "nsTArray.h"
+
+namespace mozilla {
+namespace system {
+
+class Volume;
+
+class nsVolume : public nsIVolume
+{
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIVOLUME
+
+ // This constructor is used by the UpdateVolumeRunnable constructor
+ nsVolume(const Volume* aVolume);
+
+ // This constructor is used by nsVolumeService::SetFakeVolumeState
+ nsVolume(const nsVolume* aVolume);
+
+ // This constructor is used by ContentChild::RecvFileSystemUpdate which is
+ // used to update the volume cache maintained in the child process.
+ nsVolume(const nsAString& aName, const nsAString& aMountPoint,
+ const int32_t& aState, const int32_t& aMountGeneration,
+ const bool& aIsMediaPresent, const bool& aIsSharing,
+ const bool& aIsFormatting, const bool& aIsFake,
+ const bool& aIsUnmounting, const bool& aIsRemovable,
+ const bool& aIsHotSwappable)
+ : mName(aName),
+ mMountPoint(aMountPoint),
+ mState(aState),
+ mMountGeneration(aMountGeneration),
+ mMountLocked(false),
+ mIsFake(aIsFake),
+ mIsMediaPresent(aIsMediaPresent),
+ mIsSharing(aIsSharing),
+ mIsFormatting(aIsFormatting),
+ mIsUnmounting(aIsUnmounting),
+ mIsRemovable(aIsRemovable),
+ mIsHotSwappable(aIsHotSwappable)
+ {
+ }
+
+ bool Equals(nsIVolume* aVolume);
+ void UpdateMountLock(nsVolume* aOldVolume);
+
+ void LogState() const;
+
+ const nsString& Name() const { return mName; }
+ nsCString NameStr() const { return NS_LossyConvertUTF16toASCII(mName); }
+
+ void Dump(const char* aLabel) const;
+
+ int32_t MountGeneration() const { return mMountGeneration; }
+ bool IsMountLocked() const { return mMountLocked; }
+
+ const nsString& MountPoint() const { return mMountPoint; }
+ nsCString MountPointStr() const { return NS_LossyConvertUTF16toASCII(mMountPoint); }
+
+ int32_t State() const { return mState; }
+ const char* StateStr() const { return NS_VolumeStateStr(mState); }
+
+ bool IsFake() const { return mIsFake; }
+ bool IsMediaPresent() const { return mIsMediaPresent; }
+ bool IsSharing() const { return mIsSharing; }
+ bool IsFormatting() const { return mIsFormatting; }
+ bool IsUnmounting() const { return mIsUnmounting; }
+ bool IsRemovable() const { return mIsRemovable; }
+ bool IsHotSwappable() const { return mIsHotSwappable; }
+
+ typedef nsTArray<RefPtr<nsVolume> > Array;
+
+private:
+ virtual ~nsVolume() {} // MozExternalRefCountType complains if this is non-virtual
+
+ friend class nsVolumeService; // Calls the following XxxMountLock functions
+ void UpdateMountLock(const nsAString& aMountLockState);
+ void UpdateMountLock(bool aMountLocked);
+
+ void SetIsFake(bool aIsFake);
+ void SetIsRemovable(bool aIsRemovable);
+ void SetIsHotSwappable(bool aIsHotSwappble);
+ void SetState(int32_t aState);
+ static void FormatVolumeIOThread(const nsCString& aVolume);
+ static void MountVolumeIOThread(const nsCString& aVolume);
+ static void UnmountVolumeIOThread(const nsCString& aVolume);
+
+ nsString mName;
+ nsString mMountPoint;
+ int32_t mState;
+ int32_t mMountGeneration;
+ bool mMountLocked;
+ bool mIsFake;
+ bool mIsMediaPresent;
+ bool mIsSharing;
+ bool mIsFormatting;
+ bool mIsUnmounting;
+ bool mIsRemovable;
+ bool mIsHotSwappable;
+};
+
+} // system
+} // mozilla
+
+#endif // mozilla_system_nsvolume_h__
diff --git a/dom/system/gonk/nsVolumeMountLock.cpp b/dom/system/gonk/nsVolumeMountLock.cpp
new file mode 100644
index 000000000..288c0f689
--- /dev/null
+++ b/dom/system/gonk/nsVolumeMountLock.cpp
@@ -0,0 +1,171 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsVolumeMountLock.h"
+
+#include "mozilla/dom/ContentChild.h"
+#include "mozilla/Services.h"
+
+#include "nsIObserverService.h"
+#include "nsIPowerManagerService.h"
+#include "nsIVolume.h"
+#include "nsIVolumeService.h"
+#include "nsString.h"
+#include "nsXULAppAPI.h"
+
+#undef VOLUME_MANAGER_LOG_TAG
+#define VOLUME_MANAGER_LOG_TAG "nsVolumeMountLock"
+#include "VolumeManagerLog.h"
+#include "nsServiceManagerUtils.h"
+#include "mozilla/dom/power/PowerManagerService.h"
+
+using namespace mozilla::dom;
+using namespace mozilla::services;
+
+namespace mozilla {
+namespace system {
+
+NS_IMPL_ISUPPORTS(nsVolumeMountLock, nsIVolumeMountLock,
+ nsIObserver, nsISupportsWeakReference)
+
+// static
+already_AddRefed<nsVolumeMountLock>
+nsVolumeMountLock::Create(const nsAString& aVolumeName)
+{
+ DBG("nsVolumeMountLock::Create called");
+
+ RefPtr<nsVolumeMountLock> mountLock = new nsVolumeMountLock(aVolumeName);
+ nsresult rv = mountLock->Init();
+ NS_ENSURE_SUCCESS(rv, nullptr);
+
+ return mountLock.forget();
+}
+
+nsVolumeMountLock::nsVolumeMountLock(const nsAString& aVolumeName)
+ : mVolumeName(aVolumeName),
+ mVolumeGeneration(-1),
+ mUnlocked(false)
+{
+}
+
+//virtual
+nsVolumeMountLock::~nsVolumeMountLock()
+{
+ Unlock();
+}
+
+nsresult nsVolumeMountLock::Init()
+{
+ LOG("nsVolumeMountLock created for '%s'",
+ NS_LossyConvertUTF16toASCII(mVolumeName).get());
+
+ // Add ourselves as an Observer. It's important that we use a weak
+ // reference here. If we used a strong reference, then that reference
+ // would prevent this object from being destructed.
+ nsCOMPtr<nsIObserverService> obs = GetObserverService();
+ obs->AddObserver(this, NS_VOLUME_STATE_CHANGED, true /*weak*/);
+
+ // Get the initial mountGeneration and grab a lock.
+ nsCOMPtr<nsIVolumeService> vs = do_GetService(NS_VOLUMESERVICE_CONTRACTID);
+ NS_ENSURE_TRUE(vs, NS_ERROR_FAILURE);
+
+ nsCOMPtr<nsIVolume> vol;
+ nsresult rv = vs->GetVolumeByName(mVolumeName, getter_AddRefs(vol));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ rv = vol->GetMountGeneration(&mVolumeGeneration);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return Lock(vol);
+}
+
+NS_IMETHODIMP nsVolumeMountLock::Unlock()
+{
+ LOG("nsVolumeMountLock released for '%s'",
+ NS_LossyConvertUTF16toASCII(mVolumeName).get());
+
+ mUnlocked = true;
+ mWakeLock = nullptr;
+
+ // While we don't really need to remove weak observers, we do so anyways
+ // since it will reduce the number of times Observe gets called.
+ nsCOMPtr<nsIObserverService> obs = GetObserverService();
+ obs->RemoveObserver(this, NS_VOLUME_STATE_CHANGED);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsVolumeMountLock::Observe(nsISupports* aSubject, const char* aTopic, const char16_t* aData)
+{
+ if (strcmp(aTopic, NS_VOLUME_STATE_CHANGED) != 0) {
+ return NS_OK;
+ }
+ if (mUnlocked) {
+ // We're not locked anymore, so we don't need to look at the notifications.
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIVolume> vol = do_QueryInterface(aSubject);
+ if (!vol) {
+ return NS_OK;
+ }
+ nsString volName;
+ vol->GetName(volName);
+ if (!volName.Equals(mVolumeName)) {
+ return NS_OK;
+ }
+ int32_t state;
+ nsresult rv = vol->GetState(&state);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (state != nsIVolume::STATE_MOUNTED) {
+ mWakeLock = nullptr;
+ mVolumeGeneration = -1;
+ return NS_OK;
+ }
+
+ int32_t mountGeneration;
+ rv = vol->GetMountGeneration(&mountGeneration);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ DBG("nsVolumeMountLock::Observe mountGeneration = %d mVolumeGeneration = %d",
+ mountGeneration, mVolumeGeneration);
+
+ if (mVolumeGeneration == mountGeneration) {
+ return NS_OK;
+ }
+
+ // The generation changed, which means that any wakelock we may have
+ // been holding is now invalid. Grab a new wakelock for the new generation
+ // number.
+
+ mWakeLock = nullptr;
+ mVolumeGeneration = mountGeneration;
+
+ return Lock(vol);
+}
+
+nsresult
+nsVolumeMountLock::Lock(nsIVolume* aVolume)
+{
+ RefPtr<power::PowerManagerService> pmService =
+ power::PowerManagerService::GetInstance();
+ NS_ENSURE_TRUE(pmService, NS_ERROR_FAILURE);
+
+ nsString mountLockName;
+ aVolume->GetMountLockName(mountLockName);
+
+ ErrorResult err;
+ mWakeLock = pmService->NewWakeLock(mountLockName, nullptr, err);
+ if (err.Failed()) {
+ return err.StealNSResult();
+ }
+
+ LOG("nsVolumeMountLock acquired for '%s' gen %d",
+ NS_LossyConvertUTF16toASCII(mVolumeName).get(), mVolumeGeneration);
+ return NS_OK;
+}
+
+} // namespace system
+} // namespace mozilla
diff --git a/dom/system/gonk/nsVolumeMountLock.h b/dom/system/gonk/nsVolumeMountLock.h
new file mode 100644
index 000000000..caf5b2ad5
--- /dev/null
+++ b/dom/system/gonk/nsVolumeMountLock.h
@@ -0,0 +1,56 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_system_nsvolumemountlock_h__
+#define mozilla_system_nsvolumemountlock_h__
+
+#include "nsIVolumeMountLock.h"
+
+#include "mozilla/dom/WakeLock.h"
+#include "nsIObserver.h"
+#include "nsString.h"
+#include "nsTArray.h"
+#include "nsWeakReference.h"
+
+class nsIVolume;
+
+namespace mozilla {
+namespace system {
+
+/* The VolumeMountLock is designed so that it can be used in the Child or
+ * Parent process. While the VolumeMountLock object exists, then the
+ * VolumeManager/AutoMounter will prevent a mounted volume from being
+ * shared with the PC.
+ */
+
+class nsVolumeMountLock final : public nsIVolumeMountLock,
+ public nsIObserver,
+ public nsSupportsWeakReference
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIOBSERVER
+ NS_DECL_NSIVOLUMEMOUNTLOCK
+
+ static already_AddRefed<nsVolumeMountLock> Create(const nsAString& volumeName);
+
+ const nsString& VolumeName() const { return mVolumeName; }
+
+private:
+ nsVolumeMountLock(const nsAString& aVolumeName);
+ ~nsVolumeMountLock();
+
+ nsresult Init();
+ nsresult Lock(nsIVolume* aVolume);
+
+ RefPtr<dom::WakeLock> mWakeLock;
+ nsString mVolumeName;
+ int32_t mVolumeGeneration;
+ bool mUnlocked;
+};
+
+} // namespace system
+} // namespace mozilla
+
+#endif // mozilla_system_nsvolumemountlock_h__
diff --git a/dom/system/gonk/nsVolumeService.cpp b/dom/system/gonk/nsVolumeService.cpp
new file mode 100644
index 000000000..48d95c26a
--- /dev/null
+++ b/dom/system/gonk/nsVolumeService.cpp
@@ -0,0 +1,553 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsVolumeService.h"
+
+#include "Volume.h"
+#include "VolumeManager.h"
+#include "VolumeServiceIOThread.h"
+
+#include "nsCOMPtr.h"
+#include "nsDependentSubstring.h"
+#include "nsIDOMWakeLockListener.h"
+#include "nsIMutableArray.h"
+#include "nsIObserver.h"
+#include "nsIObserverService.h"
+#include "nsIPowerManagerService.h"
+#include "nsISupportsPrimitives.h"
+#include "nsISupportsUtils.h"
+#include "nsIVolume.h"
+#include "nsIVolumeService.h"
+#include "nsLocalFile.h"
+#include "nsServiceManagerUtils.h"
+#include "nsString.h"
+#include "nsTArray.h"
+#include "nsThreadUtils.h"
+#include "nsVolumeMountLock.h"
+#include "nsXULAppAPI.h"
+#include "mozilla/dom/ContentChild.h"
+#include "mozilla/Services.h"
+#include "base/task.h"
+
+#undef VOLUME_MANAGER_LOG_TAG
+#define VOLUME_MANAGER_LOG_TAG "nsVolumeService"
+#include "VolumeManagerLog.h"
+
+#include <stdlib.h>
+
+using namespace mozilla::dom;
+using namespace mozilla::services;
+
+namespace mozilla {
+namespace system {
+
+NS_IMPL_ISUPPORTS(nsVolumeService,
+ nsIVolumeService,
+ nsIDOMMozWakeLockListener)
+
+StaticRefPtr<nsVolumeService> nsVolumeService::sSingleton;
+
+// static
+already_AddRefed<nsVolumeService>
+nsVolumeService::GetSingleton()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (!sSingleton) {
+ sSingleton = new nsVolumeService();
+ }
+ RefPtr<nsVolumeService> volumeService = sSingleton.get();
+ return volumeService.forget();
+}
+
+// static
+void
+nsVolumeService::Shutdown()
+{
+ if (!sSingleton) {
+ return;
+ }
+ if (!XRE_IsParentProcess()) {
+ sSingleton = nullptr;
+ return;
+ }
+
+ nsCOMPtr<nsIPowerManagerService> pmService =
+ do_GetService(POWERMANAGERSERVICE_CONTRACTID);
+ if (pmService) {
+ pmService->RemoveWakeLockListener(sSingleton.get());
+ }
+
+ XRE_GetIOMessageLoop()->PostTask(
+ NewRunnableFunction(ShutdownVolumeServiceIOThread));
+
+ sSingleton = nullptr;
+}
+
+nsVolumeService::nsVolumeService()
+ : mArrayMonitor("nsVolumeServiceArray"),
+ mGotVolumesFromParent(false)
+{
+ sSingleton = this;
+
+ if (!XRE_IsParentProcess()) {
+ // VolumeServiceIOThread and the WakeLock listener should only run in the
+ // parent, so we return early.
+ return;
+ }
+
+ // Startup the IOThread side of things. The actual volume changes
+ // are captured by the IOThread and forwarded to main thread.
+ XRE_GetIOMessageLoop()->PostTask(
+ NewRunnableFunction(InitVolumeServiceIOThread, this));
+
+ nsCOMPtr<nsIPowerManagerService> pmService =
+ do_GetService(POWERMANAGERSERVICE_CONTRACTID);
+ if (!pmService) {
+ return;
+ }
+ pmService->AddWakeLockListener(this);
+}
+
+nsVolumeService::~nsVolumeService()
+{
+}
+
+// Callback for nsIDOMMozWakeLockListener
+NS_IMETHODIMP
+nsVolumeService::Callback(const nsAString& aTopic, const nsAString& aState)
+{
+ CheckMountLock(aTopic, aState);
+ return NS_OK;
+}
+
+void nsVolumeService::DumpNoLock(const char* aLabel)
+{
+ mArrayMonitor.AssertCurrentThreadOwns();
+
+ nsVolume::Array::size_type numVolumes = mVolumeArray.Length();
+
+ if (numVolumes == 0) {
+ LOG("%s: No Volumes!", aLabel);
+ return;
+ }
+ nsVolume::Array::index_type volIndex;
+ for (volIndex = 0; volIndex < numVolumes; volIndex++) {
+ RefPtr<nsVolume> vol = mVolumeArray[volIndex];
+ vol->Dump(aLabel);
+ }
+}
+
+NS_IMETHODIMP
+nsVolumeService::Dump(const nsAString& aLabel)
+{
+ MonitorAutoLock autoLock(mArrayMonitor);
+ DumpNoLock(NS_LossyConvertUTF16toASCII(aLabel).get());
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsVolumeService::GetVolumeByName(const nsAString& aVolName, nsIVolume **aResult)
+{
+ MonitorAutoLock autoLock(mArrayMonitor);
+
+ RefPtr<nsVolume> vol = FindVolumeByName(aVolName);
+ if (!vol) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ vol.forget(aResult);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsVolumeService::GetVolumeByPath(const nsAString& aPath, nsIVolume **aResult)
+{
+ NS_ConvertUTF16toUTF8 utf8Path(aPath);
+ char realPathBuf[PATH_MAX];
+
+ while (realpath(utf8Path.get(), realPathBuf) < 0) {
+ if (errno != ENOENT) {
+ ERR("GetVolumeByPath: realpath on '%s' failed: %d", utf8Path.get(), errno);
+ return NSRESULT_FOR_ERRNO();
+ }
+ // The pathname we were passed doesn't exist, so we try stripping off trailing
+ // components until we get a successful call to realpath, or until we run out
+ // of components (if we finally get to /something then we also stop).
+ int32_t slashIndex = utf8Path.RFindChar('/');
+ if ((slashIndex == kNotFound) || (slashIndex == 0)) {
+ errno = ENOENT;
+ ERR("GetVolumeByPath: realpath on '%s' failed.", utf8Path.get());
+ return NSRESULT_FOR_ERRNO();
+ }
+ utf8Path.Assign(Substring(utf8Path, 0, slashIndex));
+ }
+
+ // The volume mount point is always a directory. Something like /mnt/sdcard
+ // Once we have a full qualified pathname with symlinks removed (which is
+ // what realpath does), we basically check if aPath starts with the mount
+ // point, but we don't want to have /mnt/sdcard match /mnt/sdcardfoo but we
+ // do want it to match /mnt/sdcard/foo
+ // So we add a trailing slash to the mount point and the pathname passed in
+ // prior to doing the comparison.
+
+ strlcat(realPathBuf, "/", sizeof(realPathBuf));
+
+ MonitorAutoLock autoLock(mArrayMonitor);
+
+ nsVolume::Array::size_type numVolumes = mVolumeArray.Length();
+ nsVolume::Array::index_type volIndex;
+ for (volIndex = 0; volIndex < numVolumes; volIndex++) {
+ RefPtr<nsVolume> vol = mVolumeArray[volIndex];
+ NS_ConvertUTF16toUTF8 volMountPointSlash(vol->MountPoint());
+ volMountPointSlash.Append('/');
+ nsDependentCSubstring testStr(realPathBuf, volMountPointSlash.Length());
+ if (volMountPointSlash.Equals(testStr)) {
+ vol.forget(aResult);
+ return NS_OK;
+ }
+ }
+ return NS_ERROR_FILE_NOT_FOUND;
+}
+
+NS_IMETHODIMP
+nsVolumeService::CreateOrGetVolumeByPath(const nsAString& aPath, nsIVolume** aResult)
+{
+ nsresult rv = GetVolumeByPath(aPath, aResult);
+ if (rv == NS_OK) {
+ return NS_OK;
+ }
+
+ // In order to support queries by the updater, we will fabricate a volume
+ // from the pathname, so that the caller can determine the volume size.
+ nsCOMPtr<nsIVolume> vol = new nsVolume(NS_LITERAL_STRING("fake"),
+ aPath, nsIVolume::STATE_MOUNTED,
+ -1 /* generation */,
+ true /* isMediaPresent*/,
+ false /* isSharing */,
+ false /* isFormatting */,
+ true /* isFake */,
+ false /* isUnmounting */,
+ false /* isRemovable */,
+ false /* isHotSwappable*/);
+ vol.forget(aResult);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsVolumeService::GetVolumeNames(nsIArray** aVolNames)
+{
+ NS_ENSURE_ARG_POINTER(aVolNames);
+ MonitorAutoLock autoLock(mArrayMonitor);
+
+ *aVolNames = nullptr;
+
+ nsresult rv;
+ nsCOMPtr<nsIMutableArray> volNames =
+ do_CreateInstance(NS_ARRAY_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsVolume::Array::size_type numVolumes = mVolumeArray.Length();
+ nsVolume::Array::index_type volIndex;
+ for (volIndex = 0; volIndex < numVolumes; volIndex++) {
+ RefPtr<nsVolume> vol = mVolumeArray[volIndex];
+ nsCOMPtr<nsISupportsString> isupportsString =
+ do_CreateInstance(NS_SUPPORTS_STRING_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = isupportsString->SetData(vol->Name());
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = volNames->AppendElement(isupportsString, false);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ volNames.forget(aVolNames);
+ return NS_OK;
+}
+
+void
+nsVolumeService::GetVolumesForIPC(nsTArray<VolumeInfo>* aResult)
+{
+ MOZ_ASSERT(XRE_IsParentProcess());
+ MOZ_ASSERT(NS_IsMainThread());
+
+ MonitorAutoLock autoLock(mArrayMonitor);
+
+ nsVolume::Array::size_type numVolumes = mVolumeArray.Length();
+ nsVolume::Array::index_type volIndex;
+ for (volIndex = 0; volIndex < numVolumes; volIndex++) {
+ RefPtr<nsVolume> vol = mVolumeArray[volIndex];
+ VolumeInfo* volInfo = aResult->AppendElement();
+
+ volInfo->name() = vol->mName;
+ volInfo->mountPoint() = vol->mMountPoint;
+ volInfo->volState() = vol->mState;
+ volInfo->mountGeneration() = vol->mMountGeneration;
+ volInfo->isMediaPresent() = vol->mIsMediaPresent;
+ volInfo->isSharing() = vol->mIsSharing;
+ volInfo->isFormatting() = vol->mIsFormatting;
+ volInfo->isFake() = vol->mIsFake;
+ volInfo->isUnmounting() = vol->mIsUnmounting;
+ volInfo->isRemovable() = vol->mIsRemovable;
+ volInfo->isHotSwappable() = vol->mIsHotSwappable;
+ }
+}
+
+void
+nsVolumeService::RecvVolumesFromParent(const nsTArray<VolumeInfo>& aVolumes)
+{
+ if (XRE_IsParentProcess()) {
+ // We are the parent. Therefore our volumes are already correct.
+ return;
+ }
+ if (mGotVolumesFromParent) {
+ // We've already done this, no need to do it again.
+ return;
+ }
+
+ for (uint32_t i = 0; i < aVolumes.Length(); i++) {
+ const VolumeInfo& volInfo(aVolumes[i]);
+ RefPtr<nsVolume> vol = new nsVolume(volInfo.name(),
+ volInfo.mountPoint(),
+ volInfo.volState(),
+ volInfo.mountGeneration(),
+ volInfo.isMediaPresent(),
+ volInfo.isSharing(),
+ volInfo.isFormatting(),
+ volInfo.isFake(),
+ volInfo.isUnmounting(),
+ volInfo.isRemovable(),
+ volInfo.isHotSwappable());
+ UpdateVolume(vol, false);
+ }
+}
+
+NS_IMETHODIMP
+nsVolumeService::CreateMountLock(const nsAString& aVolumeName, nsIVolumeMountLock **aResult)
+{
+ nsCOMPtr<nsIVolumeMountLock> mountLock = nsVolumeMountLock::Create(aVolumeName);
+ if (!mountLock) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ mountLock.forget(aResult);
+ return NS_OK;
+}
+
+void
+nsVolumeService::CheckMountLock(const nsAString& aMountLockName,
+ const nsAString& aMountLockState)
+{
+ MOZ_ASSERT(XRE_IsParentProcess());
+ MOZ_ASSERT(NS_IsMainThread());
+
+ RefPtr<nsVolume> vol = FindVolumeByMountLockName(aMountLockName);
+ if (vol) {
+ vol->UpdateMountLock(aMountLockState);
+ }
+}
+
+already_AddRefed<nsVolume>
+nsVolumeService::FindVolumeByMountLockName(const nsAString& aMountLockName)
+{
+ MonitorAutoLock autoLock(mArrayMonitor);
+
+ nsVolume::Array::size_type numVolumes = mVolumeArray.Length();
+ nsVolume::Array::index_type volIndex;
+ for (volIndex = 0; volIndex < numVolumes; volIndex++) {
+ RefPtr<nsVolume> vol = mVolumeArray[volIndex];
+ nsString mountLockName;
+ vol->GetMountLockName(mountLockName);
+ if (mountLockName.Equals(aMountLockName)) {
+ return vol.forget();
+ }
+ }
+ return nullptr;
+}
+
+already_AddRefed<nsVolume>
+nsVolumeService::FindVolumeByName(const nsAString& aName, nsVolume::Array::index_type* aIndex)
+{
+ mArrayMonitor.AssertCurrentThreadOwns();
+
+ nsVolume::Array::size_type numVolumes = mVolumeArray.Length();
+ nsVolume::Array::index_type volIndex;
+ for (volIndex = 0; volIndex < numVolumes; volIndex++) {
+ RefPtr<nsVolume> vol = mVolumeArray[volIndex];
+ if (vol->Name().Equals(aName)) {
+ if (aIndex) {
+ *aIndex = volIndex;
+ }
+ return vol.forget();
+ }
+ }
+ return nullptr;
+}
+
+void
+nsVolumeService::UpdateVolume(nsVolume* aVolume, bool aNotifyObservers)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ {
+ MonitorAutoLock autoLock(mArrayMonitor);
+ nsVolume::Array::index_type volIndex;
+ RefPtr<nsVolume> vol = FindVolumeByName(aVolume->Name(), &volIndex);
+ if (!vol) {
+ mVolumeArray.AppendElement(aVolume);
+ } else if (vol->Equals(aVolume) || (!vol->IsFake() && aVolume->IsFake())) {
+ // Ignore if nothing changed or if a fake tries to override a real volume.
+ return;
+ } else {
+ mVolumeArray.ReplaceElementAt(volIndex, aVolume);
+ }
+ aVolume->UpdateMountLock(vol);
+ }
+
+ if (!aNotifyObservers) {
+ return;
+ }
+
+ nsCOMPtr<nsIObserverService> obs = GetObserverService();
+ if (!obs) {
+ return;
+ }
+ NS_ConvertUTF8toUTF16 stateStr(aVolume->StateStr());
+ obs->NotifyObservers(aVolume, NS_VOLUME_STATE_CHANGED, stateStr.get());
+}
+
+NS_IMETHODIMP
+nsVolumeService::CreateFakeVolume(const nsAString& name, const nsAString& path)
+{
+ if (XRE_IsParentProcess()) {
+ RefPtr<nsVolume> vol = new nsVolume(name, path, nsIVolume::STATE_INIT,
+ -1 /* mountGeneration */,
+ true /* isMediaPresent */,
+ false /* isSharing */,
+ false /* isFormatting */,
+ true /* isFake */,
+ false /* isUnmounting */,
+ false /* isRemovable */,
+ false /* isHotSwappable */);
+ vol->SetState(nsIVolume::STATE_MOUNTED);
+ vol->LogState();
+ UpdateVolume(vol.get());
+ return NS_OK;
+ }
+
+ ContentChild::GetSingleton()->SendCreateFakeVolume(nsString(name), nsString(path));
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsVolumeService::SetFakeVolumeState(const nsAString& name, int32_t state)
+{
+ if (XRE_IsParentProcess()) {
+ RefPtr<nsVolume> vol;
+ {
+ MonitorAutoLock autoLock(mArrayMonitor);
+ vol = FindVolumeByName(name);
+ }
+ if (!vol || !vol->IsFake()) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ // Clone the existing volume so we can replace it
+ RefPtr<nsVolume> volume = new nsVolume(vol);
+ volume->SetState(state);
+ volume->LogState();
+ UpdateVolume(volume.get());
+ return NS_OK;
+ }
+
+ ContentChild::GetSingleton()->SendSetFakeVolumeState(nsString(name), state);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsVolumeService::RemoveFakeVolume(const nsAString& name)
+{
+ if (XRE_IsParentProcess()) {
+ SetFakeVolumeState(name, nsIVolume::STATE_NOMEDIA);
+ RemoveVolumeByName(name);
+ return NS_OK;
+ }
+
+ ContentChild::GetSingleton()->SendRemoveFakeVolume(nsString(name));
+ return NS_OK;
+}
+
+void
+nsVolumeService::RemoveVolumeByName(const nsAString& aName)
+{
+ {
+ MonitorAutoLock autoLock(mArrayMonitor);
+ nsVolume::Array::index_type volIndex;
+ RefPtr<nsVolume> vol = FindVolumeByName(aName, &volIndex);
+ if (!vol) {
+ return;
+ }
+ mVolumeArray.RemoveElementAt(volIndex);
+ }
+
+ if (XRE_IsParentProcess()) {
+ nsCOMPtr<nsIObserverService> obs = GetObserverService();
+ if (!obs) {
+ return;
+ }
+ obs->NotifyObservers(nullptr, NS_VOLUME_REMOVED, nsString(aName).get());
+ }
+}
+
+/***************************************************************************
+* The UpdateVolumeRunnable creates an nsVolume and updates the main thread
+* data structure while running on the main thread.
+*/
+class UpdateVolumeRunnable : public Runnable
+{
+public:
+ UpdateVolumeRunnable(nsVolumeService* aVolumeService, const Volume* aVolume)
+ : mVolumeService(aVolumeService),
+ mVolume(new nsVolume(aVolume))
+ {
+ MOZ_ASSERT(MessageLoop::current() == XRE_GetIOMessageLoop());
+ }
+
+ NS_IMETHOD Run() override
+ {
+ MOZ_ASSERT(NS_IsMainThread());
+ DBG("UpdateVolumeRunnable::Run '%s' state %s gen %d locked %d "
+ "media %d sharing %d formatting %d unmounting %d removable %d hotswappable %d",
+ mVolume->NameStr().get(), mVolume->StateStr(),
+ mVolume->MountGeneration(), (int)mVolume->IsMountLocked(),
+ (int)mVolume->IsMediaPresent(), mVolume->IsSharing(),
+ mVolume->IsFormatting(), mVolume->IsUnmounting(),
+ (int)mVolume->IsRemovable(), (int)mVolume->IsHotSwappable());
+
+ mVolumeService->UpdateVolume(mVolume);
+ mVolumeService = nullptr;
+ mVolume = nullptr;
+ return NS_OK;
+ }
+
+private:
+ RefPtr<nsVolumeService> mVolumeService;
+ RefPtr<nsVolume> mVolume;
+};
+
+void
+nsVolumeService::UpdateVolumeIOThread(const Volume* aVolume)
+{
+ DBG("UpdateVolumeIOThread: Volume '%s' state %s mount '%s' gen %d locked %d "
+ "media %d sharing %d formatting %d unmounting %d removable %d hotswappable %d",
+ aVolume->NameStr(), aVolume->StateStr(), aVolume->MountPoint().get(),
+ aVolume->MountGeneration(), (int)aVolume->IsMountLocked(),
+ (int)aVolume->MediaPresent(), (int)aVolume->IsSharing(),
+ (int)aVolume->IsFormatting(), (int)aVolume->IsUnmounting(),
+ (int)aVolume->IsRemovable(), (int)aVolume->IsHotSwappable());
+ MOZ_ASSERT(MessageLoop::current() == XRE_GetIOMessageLoop());
+ NS_DispatchToMainThread(new UpdateVolumeRunnable(this, aVolume));
+}
+
+} // namespace system
+} // namespace mozilla
diff --git a/dom/system/gonk/nsVolumeService.h b/dom/system/gonk/nsVolumeService.h
new file mode 100644
index 000000000..9bddc0b8f
--- /dev/null
+++ b/dom/system/gonk/nsVolumeService.h
@@ -0,0 +1,78 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_system_nsvolumeservice_h__
+#define mozilla_system_nsvolumeservice_h__
+
+#include "mozilla/Monitor.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/StaticPtr.h"
+#include "nsCOMPtr.h"
+#include "nsIDOMWakeLockListener.h"
+#include "nsIVolume.h"
+#include "nsIVolumeService.h"
+#include "nsVolume.h"
+
+namespace mozilla {
+
+namespace dom {
+class VolumeInfo;
+} // dom
+
+namespace system {
+
+class Volume;
+
+/***************************************************************************
+* The nsVolumeData class encapsulates the data that is updated/maintained
+* on the main thread in order to support the nsIVolume and nsIVolumeService
+* classes.
+*/
+
+class nsVolumeService final : public nsIVolumeService,
+ public nsIDOMMozWakeLockListener
+{
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIVOLUMESERVICE
+ NS_DECL_NSIDOMMOZWAKELOCKLISTENER
+
+ nsVolumeService();
+
+ static already_AddRefed<nsVolumeService> GetSingleton();
+ //static nsVolumeService* GetSingleton();
+ static void Shutdown();
+
+ void DumpNoLock(const char* aLabel);
+
+ // To use this function, you have to create a new volume and pass it in.
+ void UpdateVolume(nsVolume* aVolume, bool aNotifyObservers = true);
+ void UpdateVolumeIOThread(const Volume* aVolume);
+
+ void RecvVolumesFromParent(const nsTArray<dom::VolumeInfo>& aVolumes);
+ void GetVolumesForIPC(nsTArray<dom::VolumeInfo>* aResult);
+
+ void RemoveVolumeByName(const nsAString& aName);
+
+private:
+ ~nsVolumeService();
+
+ void CheckMountLock(const nsAString& aMountLockName,
+ const nsAString& aMountLockState);
+ already_AddRefed<nsVolume> FindVolumeByMountLockName(const nsAString& aMountLockName);
+
+ already_AddRefed<nsVolume> FindVolumeByName(const nsAString& aName,
+ nsVolume::Array::index_type* aIndex = nullptr);
+
+ Monitor mArrayMonitor;
+ nsVolume::Array mVolumeArray;
+
+ static StaticRefPtr<nsVolumeService> sSingleton;
+ bool mGotVolumesFromParent;
+};
+
+} // system
+} // mozilla
+
+#endif // mozilla_system_nsvolumeservice_h__
diff --git a/dom/system/gonk/nsVolumeStat.cpp b/dom/system/gonk/nsVolumeStat.cpp
new file mode 100644
index 000000000..11976237f
--- /dev/null
+++ b/dom/system/gonk/nsVolumeStat.cpp
@@ -0,0 +1,33 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsVolumeStat.h"
+#include "nsString.h"
+
+namespace mozilla {
+namespace system {
+
+NS_IMPL_ISUPPORTS(nsVolumeStat, nsIVolumeStat)
+
+nsVolumeStat::nsVolumeStat(const nsAString& aPath)
+{
+ if (statfs(NS_ConvertUTF16toUTF8(aPath).get(), &mStat) != 0) {
+ memset(&mStat, 0, sizeof(mStat));
+ }
+}
+
+NS_IMETHODIMP nsVolumeStat::GetTotalBytes(int64_t* aTotalBytes)
+{
+ *aTotalBytes = mStat.f_blocks * mStat.f_bsize;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsVolumeStat::GetFreeBytes(int64_t* aFreeBytes)
+{
+ *aFreeBytes = mStat.f_bfree * mStat.f_bsize;
+ return NS_OK;
+}
+
+} // system
+} // mozilla
diff --git a/dom/system/gonk/nsVolumeStat.h b/dom/system/gonk/nsVolumeStat.h
new file mode 100644
index 000000000..2ca03ed46
--- /dev/null
+++ b/dom/system/gonk/nsVolumeStat.h
@@ -0,0 +1,33 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_system_nsvolumestat_h__
+#define mozilla_system_nsvolumestat_h__
+
+#include "nsIVolumeStat.h"
+#include "nsString.h"
+#include <sys/statfs.h>
+
+namespace mozilla {
+namespace system {
+
+class nsVolumeStat final : public nsIVolumeStat
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIVOLUMESTAT
+
+ nsVolumeStat(const nsAString& aPath);
+
+protected:
+ ~nsVolumeStat() {}
+
+private:
+ struct statfs mStat;
+};
+
+} // system
+} // mozilla
+
+#endif // mozilla_system_nsvolumestat_h__
diff --git a/dom/system/gonk/ril_consts.js b/dom/system/gonk/ril_consts.js
new file mode 100644
index 000000000..af5b9d8e1
--- /dev/null
+++ b/dom/system/gonk/ril_consts.js
@@ -0,0 +1,3338 @@
+/* Copyright 2012 Mozilla Foundation and Mozilla contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// Set to true to debug all RIL layers
+this.DEBUG_ALL = false;
+
+// Set individually to debug specific layers
+this.DEBUG_WORKER = false || DEBUG_ALL;
+this.DEBUG_CONTENT_HELPER = false || DEBUG_ALL;
+this.DEBUG_RIL = false || DEBUG_ALL;
+
+this.REQUEST_GET_SIM_STATUS = 1;
+this.REQUEST_ENTER_SIM_PIN = 2;
+this.REQUEST_ENTER_SIM_PUK = 3;
+this.REQUEST_ENTER_SIM_PIN2 = 4;
+this.REQUEST_ENTER_SIM_PUK2 = 5;
+this.REQUEST_CHANGE_SIM_PIN = 6;
+this.REQUEST_CHANGE_SIM_PIN2 = 7;
+this.REQUEST_ENTER_NETWORK_DEPERSONALIZATION_CODE = 8;
+this.REQUEST_GET_CURRENT_CALLS = 9;
+this.REQUEST_DIAL = 10;
+this.REQUEST_GET_IMSI = 11;
+this.REQUEST_HANGUP = 12;
+this.REQUEST_HANGUP_WAITING_OR_BACKGROUND = 13;
+this.REQUEST_HANGUP_FOREGROUND_RESUME_BACKGROUND = 14;
+this.REQUEST_SWITCH_WAITING_OR_HOLDING_AND_ACTIVE = 15;
+this.REQUEST_CONFERENCE = 16;
+this.REQUEST_UDUB = 17;
+this.REQUEST_LAST_CALL_FAIL_CAUSE = 18;
+this.REQUEST_SIGNAL_STRENGTH = 19;
+this.REQUEST_VOICE_REGISTRATION_STATE = 20;
+this.REQUEST_DATA_REGISTRATION_STATE = 21;
+this.REQUEST_OPERATOR = 22;
+this.REQUEST_RADIO_POWER = 23;
+this.REQUEST_DTMF = 24;
+this.REQUEST_SEND_SMS = 25;
+this.REQUEST_SEND_SMS_EXPECT_MORE = 26;
+this.REQUEST_SETUP_DATA_CALL = 27;
+this.REQUEST_SIM_IO = 28;
+this.REQUEST_SEND_USSD = 29;
+this.REQUEST_CANCEL_USSD = 30;
+this.REQUEST_GET_CLIR = 31;
+this.REQUEST_SET_CLIR = 32;
+this.REQUEST_QUERY_CALL_FORWARD_STATUS = 33;
+this.REQUEST_SET_CALL_FORWARD = 34;
+this.REQUEST_QUERY_CALL_WAITING = 35;
+this.REQUEST_SET_CALL_WAITING = 36;
+this.REQUEST_SMS_ACKNOWLEDGE = 37;
+this.REQUEST_GET_IMEI = 38;
+this.REQUEST_GET_IMEISV = 39;
+this.REQUEST_ANSWER = 40;
+this.REQUEST_DEACTIVATE_DATA_CALL = 41;
+this.REQUEST_QUERY_FACILITY_LOCK = 42;
+this.REQUEST_SET_FACILITY_LOCK = 43;
+this.REQUEST_CHANGE_BARRING_PASSWORD = 44;
+this.REQUEST_QUERY_NETWORK_SELECTION_MODE = 45;
+this.REQUEST_SET_NETWORK_SELECTION_AUTOMATIC = 46;
+this.REQUEST_SET_NETWORK_SELECTION_MANUAL = 47;
+this.REQUEST_QUERY_AVAILABLE_NETWORKS = 48;
+this.REQUEST_DTMF_START = 49;
+this.REQUEST_DTMF_STOP = 50;
+this.REQUEST_BASEBAND_VERSION = 51;
+this.REQUEST_SEPARATE_CONNECTION = 52;
+this.REQUEST_SET_MUTE = 53;
+this.REQUEST_GET_MUTE = 54;
+this.REQUEST_QUERY_CLIP = 55;
+this.REQUEST_LAST_DATA_CALL_FAIL_CAUSE = 56;
+this.REQUEST_DATA_CALL_LIST = 57;
+this.REQUEST_RESET_RADIO = 58;
+this.REQUEST_OEM_HOOK_RAW = 59;
+this.REQUEST_OEM_HOOK_STRINGS = 60;
+this.REQUEST_SCREEN_STATE = 61;
+this.REQUEST_SET_SUPP_SVC_NOTIFICATION = 62;
+this.REQUEST_WRITE_SMS_TO_SIM = 63;
+this.REQUEST_DELETE_SMS_ON_SIM = 64;
+this.REQUEST_SET_BAND_MODE = 65;
+this.REQUEST_QUERY_AVAILABLE_BAND_MODE = 66;
+this.REQUEST_STK_GET_PROFILE = 67;
+this.REQUEST_STK_SET_PROFILE = 68;
+this.REQUEST_STK_SEND_ENVELOPE_COMMAND = 69;
+this.REQUEST_STK_SEND_TERMINAL_RESPONSE = 70;
+this.REQUEST_STK_HANDLE_CALL_SETUP_REQUESTED_FROM_SIM = 71;
+this.REQUEST_EXPLICIT_CALL_TRANSFER = 72;
+this.REQUEST_SET_PREFERRED_NETWORK_TYPE = 73;
+this.REQUEST_GET_PREFERRED_NETWORK_TYPE = 74;
+this.REQUEST_GET_NEIGHBORING_CELL_IDS = 75;
+this.REQUEST_SET_LOCATION_UPDATES = 76;
+this.REQUEST_CDMA_SET_SUBSCRIPTION_SOURCE = 77;
+this.REQUEST_CDMA_SET_ROAMING_PREFERENCE = 78;
+this.REQUEST_CDMA_QUERY_ROAMING_PREFERENCE = 79;
+this.REQUEST_SET_TTY_MODE = 80;
+this.REQUEST_QUERY_TTY_MODE = 81;
+this.REQUEST_CDMA_SET_PREFERRED_VOICE_PRIVACY_MODE = 82;
+this.REQUEST_CDMA_QUERY_PREFERRED_VOICE_PRIVACY_MODE = 83;
+this.REQUEST_CDMA_FLASH = 84;
+this.REQUEST_CDMA_BURST_DTMF = 85;
+this.REQUEST_CDMA_VALIDATE_AND_WRITE_AKEY = 86;
+this.REQUEST_CDMA_SEND_SMS = 87;
+this.REQUEST_CDMA_SMS_ACKNOWLEDGE = 88;
+this.REQUEST_GSM_GET_BROADCAST_SMS_CONFIG = 89;
+this.REQUEST_GSM_SET_BROADCAST_SMS_CONFIG = 90;
+this.REQUEST_GSM_SMS_BROADCAST_ACTIVATION = 91;
+this.REQUEST_CDMA_GET_BROADCAST_SMS_CONFIG = 92;
+this.REQUEST_CDMA_SET_BROADCAST_SMS_CONFIG = 93;
+this.REQUEST_CDMA_SMS_BROADCAST_ACTIVATION = 94;
+this.REQUEST_CDMA_SUBSCRIPTION = 95;
+this.REQUEST_CDMA_WRITE_SMS_TO_RUIM = 96;
+this.REQUEST_CDMA_DELETE_SMS_ON_RUIM = 97;
+this.REQUEST_DEVICE_IDENTITY = 98;
+this.REQUEST_EXIT_EMERGENCY_CALLBACK_MODE = 99;
+this.REQUEST_GET_SMSC_ADDRESS = 100;
+this.REQUEST_SET_SMSC_ADDRESS = 101;
+this.REQUEST_REPORT_SMS_MEMORY_STATUS = 102;
+this.REQUEST_REPORT_STK_SERVICE_IS_RUNNING = 103;
+this.REQUEST_CDMA_GET_SUBSCRIPTION_SOURCE = 104;
+this.REQUEST_ISIM_AUTHENTICATION = 105;
+this.REQUEST_ACKNOWLEDGE_INCOMING_GSM_SMS_WITH_PDU = 106;
+this.REQUEST_STK_SEND_ENVELOPE_WITH_STATUS = 107;
+this.REQUEST_VOICE_RADIO_TECH = 108;
+this.REQUEST_GET_CELL_INFO_LIST = 109;
+this.REQUEST_SET_UNSOL_CELL_INFO_LIST_RATE = 110;
+this.REQUEST_SET_INITIAL_ATTACH_APN = 111;
+this.REQUEST_IMS_REGISTRATION_STATE = 112;
+this.REQUEST_IMS_SEND_SMS = 113;
+this.REQUEST_SIM_TRANSMIT_APDU_BASIC = 114;
+this.REQUEST_SIM_OPEN_CHANNEL = 115;
+this.REQUEST_SIM_CLOSE_CHANNEL = 116;
+this.REQUEST_SIM_TRANSMIT_APDU_CHANNEL = 117;
+this.REQUEST_NV_READ_ITEM = 118;
+this.REQUEST_NV_WRITE_ITEM = 119;
+this.REQUEST_NV_WRITE_CDMA_PRL = 120;
+this.REQUEST_NV_RESET_CONFIG = 121;
+this.REQUEST_SET_UICC_SUBSCRIPTION = 122;
+this.REQUEST_ALLOW_DATA = 123;
+this.REQUEST_GET_HARDWARE_CONFIG = 124;
+this.REQUEST_SIM_AUTHENTICATION = 125;
+this.REQUEST_GET_DC_RT_INFO = 126;
+this.REQUEST_SET_DC_RT_INFO_RATE = 127;
+this.REQUEST_SET_DATA_PROFILE = 128;
+this.REQUEST_SHUTDOWN = 129;
+
+// CAF specific parcel type. It should be synced with latest version. But CAF
+// doesn't have l version for b2g yet, so we set REQUEST_SET_DATA_SUBSCRIPTION
+// to a value that won't get conflict with known AOSP parcel.
+this.REQUEST_SET_DATA_SUBSCRIPTION = 130;
+
+// Mozilla specific parcel type.
+this.REQUEST_GET_UNLOCK_RETRY_COUNT = 150;
+
+// Fugu specific parcel types.
+this.RIL_REQUEST_GPRS_ATTACH = 5018;
+this.RIL_REQUEST_GPRS_DETACH = 5019;
+
+// Galaxy S2 specific parcel type.
+this.REQUEST_DIAL_EMERGENCY_CALL = 10016;
+
+this.RESPONSE_TYPE_SOLICITED = 0;
+this.RESPONSE_TYPE_UNSOLICITED = 1;
+
+this.UNSOLICITED_RESPONSE_BASE = 1000;
+this.UNSOLICITED_RESPONSE_RADIO_STATE_CHANGED = 1000;
+this.UNSOLICITED_RESPONSE_CALL_STATE_CHANGED = 1001;
+this.UNSOLICITED_RESPONSE_VOICE_NETWORK_STATE_CHANGED = 1002;
+this.UNSOLICITED_RESPONSE_NEW_SMS = 1003;
+this.UNSOLICITED_RESPONSE_NEW_SMS_STATUS_REPORT = 1004;
+this.UNSOLICITED_RESPONSE_NEW_SMS_ON_SIM = 1005;
+this.UNSOLICITED_ON_USSD = 1006;
+this.UNSOLICITED_ON_USSD_REQUEST = 1007;
+this.UNSOLICITED_NITZ_TIME_RECEIVED = 1008;
+this.UNSOLICITED_SIGNAL_STRENGTH = 1009;
+this.UNSOLICITED_DATA_CALL_LIST_CHANGED = 1010;
+this.UNSOLICITED_SUPP_SVC_NOTIFICATION = 1011;
+this.UNSOLICITED_STK_SESSION_END = 1012;
+this.UNSOLICITED_STK_PROACTIVE_COMMAND = 1013;
+this.UNSOLICITED_STK_EVENT_NOTIFY = 1014;
+this.UNSOLICITED_STK_CALL_SETUP = 1015;
+this.UNSOLICITED_SIM_SMS_STORAGE_FULL = 1016;
+this.UNSOLICITED_SIM_REFRESH = 1017;
+this.UNSOLICITED_CALL_RING = 1018;
+this.UNSOLICITED_RESPONSE_SIM_STATUS_CHANGED = 1019;
+this.UNSOLICITED_RESPONSE_CDMA_NEW_SMS = 1020;
+this.UNSOLICITED_RESPONSE_NEW_BROADCAST_SMS = 1021;
+this.UNSOLICITED_CDMA_RUIM_SMS_STORAGE_FULL = 1022;
+this.UNSOLICITED_RESTRICTED_STATE_CHANGED = 1023;
+this.UNSOLICITED_ENTER_EMERGENCY_CALLBACK_MODE = 1024;
+this.UNSOLICITED_CDMA_CALL_WAITING = 1025;
+this.UNSOLICITED_CDMA_OTA_PROVISION_STATUS = 1026;
+this.UNSOLICITED_CDMA_INFO_REC = 1027;
+this.UNSOLICITED_OEM_HOOK_RAW = 1028;
+this.UNSOLICITED_RINGBACK_TONE = 1029;
+this.UNSOLICITED_RESEND_INCALL_MUTE = 1030;
+this.UNSOLICITED_CDMA_SUBSCRIPTION_SOURCE_CHANGED = 1031;
+this.UNSOLICITED_CDMA_PRL_CHANGED = 1032;
+this.UNSOLICITED_EXIT_EMERGENCY_CALLBACK_MODE = 1033;
+this.UNSOLICITED_RIL_CONNECTED = 1034;
+this.UNSOLICITED_VOICE_RADIO_TECH_CHANGED = 1035;
+this.UNSOLICITED_CELL_INFO_LIST = 1036;
+this.UNSOLICITED_RESPONSE_IMS_NETWORK_STATE_CHANGED = 1037;
+this.UNSOLICITED_UICC_SUBSCRIPTION_STATUS_CHANGED = 1038;
+this.UNSOLICITED_SRVCC_STATE_NOTIFY = 1039;
+this.UNSOLICITED_HARDWARE_CONFIG_CHANGED = 1040;
+this.UNSOLICITED_DC_RT_INFO_CHANGED = 1041;
+
+this.ERROR_SUCCESS = 0;
+this.ERROR_RADIO_NOT_AVAILABLE = 1;
+this.ERROR_GENERIC_FAILURE = 2;
+this.ERROR_PASSWORD_INCORRECT = 3;
+this.ERROR_SIM_PIN2 = 4;
+this.ERROR_SIM_PUK2 = 5;
+this.ERROR_REQUEST_NOT_SUPPORTED = 6;
+this.ERROR_CANCELLED = 7;
+this.ERROR_OP_NOT_ALLOWED_DURING_VOICE_CALL = 8;
+this.ERROR_OP_NOT_ALLOWED_BEFORE_REG_TO_NW = 9;
+this.ERROR_SMS_SEND_FAIL_RETRY = 10;
+this.ERROR_SIM_ABSENT = 11;
+this.ERROR_SUBSCRIPTION_NOT_AVAILABLE = 12;
+this.ERROR_MODE_NOT_SUPPORTED = 13;
+this.ERROR_FDN_CHECK_FAILURE = 14;
+this.ERROR_ILLEGAL_SIM_OR_ME = 15;
+this.ERROR_MISSING_RESOURCE = 16;
+this.ERROR_NO_SUCH_ELEMENT = 17;
+
+this.GECKO_ERROR_RADIO_NOT_AVAILABLE = "RadioNotAvailable";
+this.GECKO_ERROR_GENERIC_FAILURE = "GenericFailure";
+this.GECKO_ERROR_PASSWORD_INCORRECT = "IncorrectPassword";
+this.GECKO_ERROR_SIM_PIN2 = "SimPin2";
+this.GECKO_ERROR_SIM_PUK2 = "SimPuk2";
+this.GECKO_ERROR_REQUEST_NOT_SUPPORTED = "RequestNotSupported";
+this.GECKO_ERROR_CANCELLED = "Cancelled";
+this.GECKO_ERROR_OP_NOT_ALLOWED_DURING_VOICE_CALL = "OpNotAllowedDuringVoiceCall";
+this.GECKO_ERROR_OP_NOT_ALLOWED_BEFORE_REG_TO_NW = "OpNotAllowedBeforeRegToNw";
+this.GECKO_ERROR_SMS_SEND_FAIL_RETRY = "SmsSendFailRetry";
+this.GECKO_ERROR_SIM_ABSENT = "SimAbsent";
+this.GECKO_ERROR_SUBSCRIPTION_NOT_AVAILABLE = "SubscriptionNotAvailable";
+this.GECKO_ERROR_MODE_NOT_SUPPORTED = "ModeNotSupported";
+this.GECKO_ERROR_FDN_CHECK_FAILURE = "FdnCheckFailure";
+this.GECKO_ERROR_ILLEGAL_SIM_OR_ME = "IllegalSIMorME";
+this.GECKO_ERROR_MISSING_RESOURCE = "MissingResource";
+this.GECKO_ERROR_NO_SUCH_ELEMENT = "NoSuchElement";
+this.GECKO_ERROR_INVALID_PARAMETER = "InvalidParameter";
+this.GECKO_ERROR_UNSPECIFIED_ERROR = "UnspecifiedError";
+
+this.RIL_ERROR_TO_GECKO_ERROR = {};
+RIL_ERROR_TO_GECKO_ERROR[ERROR_RADIO_NOT_AVAILABLE] = GECKO_ERROR_RADIO_NOT_AVAILABLE;
+RIL_ERROR_TO_GECKO_ERROR[ERROR_GENERIC_FAILURE] = GECKO_ERROR_GENERIC_FAILURE;
+RIL_ERROR_TO_GECKO_ERROR[ERROR_PASSWORD_INCORRECT] = GECKO_ERROR_PASSWORD_INCORRECT;
+RIL_ERROR_TO_GECKO_ERROR[ERROR_SIM_PIN2] = GECKO_ERROR_SIM_PIN2;
+RIL_ERROR_TO_GECKO_ERROR[ERROR_SIM_PUK2] = GECKO_ERROR_SIM_PUK2;
+RIL_ERROR_TO_GECKO_ERROR[ERROR_REQUEST_NOT_SUPPORTED] = GECKO_ERROR_REQUEST_NOT_SUPPORTED;
+RIL_ERROR_TO_GECKO_ERROR[ERROR_CANCELLED] = GECKO_ERROR_CANCELLED;
+RIL_ERROR_TO_GECKO_ERROR[ERROR_OP_NOT_ALLOWED_DURING_VOICE_CALL] = GECKO_ERROR_OP_NOT_ALLOWED_DURING_VOICE_CALL;
+RIL_ERROR_TO_GECKO_ERROR[ERROR_OP_NOT_ALLOWED_BEFORE_REG_TO_NW] = GECKO_ERROR_OP_NOT_ALLOWED_BEFORE_REG_TO_NW;
+RIL_ERROR_TO_GECKO_ERROR[ERROR_SMS_SEND_FAIL_RETRY] = GECKO_ERROR_SMS_SEND_FAIL_RETRY;
+RIL_ERROR_TO_GECKO_ERROR[ERROR_SIM_ABSENT] = GECKO_ERROR_SIM_ABSENT;
+RIL_ERROR_TO_GECKO_ERROR[ERROR_SUBSCRIPTION_NOT_AVAILABLE] = GECKO_ERROR_SUBSCRIPTION_NOT_AVAILABLE;
+RIL_ERROR_TO_GECKO_ERROR[ERROR_MODE_NOT_SUPPORTED] = GECKO_ERROR_MODE_NOT_SUPPORTED;
+RIL_ERROR_TO_GECKO_ERROR[ERROR_FDN_CHECK_FAILURE] = GECKO_ERROR_FDN_CHECK_FAILURE;
+RIL_ERROR_TO_GECKO_ERROR[ERROR_ILLEGAL_SIM_OR_ME] = GECKO_ERROR_ILLEGAL_SIM_OR_ME;
+RIL_ERROR_TO_GECKO_ERROR[ERROR_MISSING_RESOURCE] = GECKO_ERROR_MISSING_RESOURCE;
+RIL_ERROR_TO_GECKO_ERROR[ERROR_NO_SUCH_ELEMENT] = GECKO_ERROR_NO_SUCH_ELEMENT;
+
+// 3GPP 23.040 clause 9.2.3.6 TP-Message-Reference(TP-MR):
+// The number of times the MS automatically repeats the SMS-SUBMIT shall be in
+// the range 1 to 3 but the precise number is an implementation matter.
+this.SMS_RETRY_MAX = 3;
+
+this.RADIO_STATE_OFF = 0;
+this.RADIO_STATE_UNAVAILABLE = 1;
+this.RADIO_STATE_ON = 10; // since RIL v7
+
+this.CARD_STATE_ABSENT = 0;
+this.CARD_STATE_PRESENT = 1;
+this.CARD_STATE_ERROR = 2;
+
+this.CARD_PERSOSUBSTATE_UNKNOWN = 0;
+this.CARD_PERSOSUBSTATE_IN_PROGRESS = 1;
+this.CARD_PERSOSUBSTATE_READY = 2;
+this.CARD_PERSOSUBSTATE_SIM_NETWORK = 3;
+this.CARD_PERSOSUBSTATE_SIM_NETWORK_SUBSET = 4;
+this.CARD_PERSOSUBSTATE_SIM_CORPORATE = 5;
+this.CARD_PERSOSUBSTATE_SIM_SERVICE_PROVIDER = 6;
+this.CARD_PERSOSUBSTATE_SIM_SIM = 7;
+this.CARD_PERSOSUBSTATE_SIM_NETWORK_PUK = 8;
+this.CARD_PERSOSUBSTATE_SIM_NETWORK_SUBSET_PUK = 9;
+this.CARD_PERSOSUBSTATE_SIM_CORPORATE_PUK = 10;
+this.CARD_PERSOSUBSTATE_SIM_SERVICE_PROVIDER_PUK = 11;
+this.CARD_PERSOSUBSTATE_SIM_SIM_PUK = 12;
+this.CARD_PERSOSUBSTATE_RUIM_NETWORK1 = 13;
+this.CARD_PERSOSUBSTATE_RUIM_NETWORK2 = 14;
+this.CARD_PERSOSUBSTATE_RUIM_HRPD = 15;
+this.CARD_PERSOSUBSTATE_RUIM_CORPORATE = 16;
+this.CARD_PERSOSUBSTATE_RUIM_SERVICE_PROVIDER = 17;
+this.CARD_PERSOSUBSTATE_RUIM_RUIM = 18;
+this.CARD_PERSOSUBSTATE_RUIM_NETWORK1_PUK = 19;
+this.CARD_PERSOSUBSTATE_RUIM_NETWORK2_PUK = 20;
+this.CARD_PERSOSUBSTATE_RUIM_HRPD_PUK = 21;
+this.CARD_PERSOSUBSTATE_RUIM_CORPORATE_PUK = 22;
+this.CARD_PERSOSUBSTATE_RUIM_SERVICE_PROVIDER_PUK = 23;
+this.CARD_PERSOSUBSTATE_RUIM_RUIM_PUK = 24;
+
+this.CARD_APPSTATE_ILLEGAL = -1;
+this.CARD_APPSTATE_UNKNOWN = 0;
+this.CARD_APPSTATE_DETECTED = 1;
+this.CARD_APPSTATE_PIN = 2; // If PIN1 or UPin is required.
+this.CARD_APPSTATE_PUK = 3; // If PUK1 or Puk for UPin is required.
+this.CARD_APPSTATE_SUBSCRIPTION_PERSO = 4; // perso_substate should be looked
+ // at when app_state is assigned
+ // to this value.
+this.CARD_APPSTATE_READY = 5;
+
+this.CARD_PINSTATE_UNKNOWN = 0;
+this.CARD_PINSTATE_ENABLED_NOT_VERIFIED = 1;
+this.CARD_PINSTATE_ENABLED_VERIFIED = 2;
+this.CARD_PINSTATE_DISABLED = 3;
+this.CARD_PINSTATE_ENABLED_BLOCKED = 4;
+this.CARD_PINSTATE_ENABLED_PERM_BLOCKED = 5;
+
+this.CARD_APPTYPE_UNKNOWN = 0;
+this.CARD_APPTYPE_SIM = 1;
+this.CARD_APPTYPE_USIM = 2;
+this.CARD_APPTYPE_RUIM = 3;
+this.CARD_APPTYPE_CSIM = 4;
+this.CARD_APPTYPE_ISIM = 5;
+
+this.CARD_MAX_APPS = 8;
+
+this.GECKO_CARD_TYPE = [
+ null,
+ "sim",
+ "usim",
+ "ruim",
+ "csim",
+ "isim"
+];
+
+
+// Used for QUERY_AVAILABLE_NETWORKS status.
+this.QAN_STATE_UNKNOWN = "unknown";
+this.QAN_STATE_AVAILABLE = "available";
+this.QAN_STATE_CURRENT = "current";
+this.QAN_STATE_FORBIDDEN = "forbidden";
+
+// Must be in sync with MobileNetworkState of MozMobileNetworkInfo.webidl
+this.GECKO_QAN_STATE_UNKNOWN = null;
+this.GECKO_QAN_STATE_AVAILABLE = "available";
+this.GECKO_QAN_STATE_CONNECTED = "connected";
+this.GECKO_QAN_STATE_FORBIDDEN = "forbidden";
+
+this.RIL_QAN_STATE_TO_GECKO_STATE = {};
+this.RIL_QAN_STATE_TO_GECKO_STATE[this.QAN_STATE_UNKNOWN] = this.GECKO_QAN_STATE_UNKNOWN;
+this.RIL_QAN_STATE_TO_GECKO_STATE[this.QAN_STATE_AVAILABLE] = this.GECKO_QAN_STATE_AVAILABLE;
+this.RIL_QAN_STATE_TO_GECKO_STATE[this.QAN_STATE_CURRENT] = this.GECKO_QAN_STATE_CONNECTED;
+this.RIL_QAN_STATE_TO_GECKO_STATE[this.QAN_STATE_FORBIDDEN] = this.GECKO_QAN_STATE_FORBIDDEN;
+
+this.NETWORK_SELECTION_MODE_AUTOMATIC = 0;
+this.NETWORK_SELECTION_MODE_MANUAL = 1;
+
+this.NETWORK_INFO_VOICE_REGISTRATION_STATE = "voiceRegistrationState";
+this.NETWORK_INFO_DATA_REGISTRATION_STATE = "dataRegistrationState";
+this.NETWORK_INFO_OPERATOR = "operator";
+this.NETWORK_INFO_NETWORK_SELECTION_MODE = "networkSelectionMode";
+this.NETWORK_INFO_SIGNAL = "signal";
+this.NETWORK_INFO_MESSAGE_TYPES = [
+ NETWORK_INFO_VOICE_REGISTRATION_STATE,
+ NETWORK_INFO_DATA_REGISTRATION_STATE,
+ NETWORK_INFO_OPERATOR,
+ NETWORK_INFO_NETWORK_SELECTION_MODE,
+ NETWORK_INFO_SIGNAL
+];
+
+this.GECKO_PREFERRED_NETWORK_TYPE_WCDMA_GSM = "wcdma/gsm";
+this.GECKO_PREFERRED_NETWORK_TYPE_GSM_ONLY = "gsm";
+this.GECKO_PREFERRED_NETWORK_TYPE_WCDMA_ONLY = "wcdma";
+this.GECKO_PREFERRED_NETWORK_TYPE_WCDMA_GSM_AUTO = "wcdma/gsm-auto";
+this.GECKO_PREFERRED_NETWORK_TYPE_CDMA_EVDO = "cdma/evdo";
+this.GECKO_PREFERRED_NETWORK_TYPE_CDMA_ONLY = "cdma";
+this.GECKO_PREFERRED_NETWORK_TYPE_EVDO_ONLY = "evdo";
+this.GECKO_PREFERRED_NETWORK_TYPE_WCDMA_GSM_CDMA_EVDO = "wcdma/gsm/cdma/evdo";
+this.GECKO_PREFERRED_NETWORK_TYPE_LTE_CDMA_EVDO = "lte/cdma/evdo";
+this.GECKO_PREFERRED_NETWORK_TYPE_LTE_WCDMA_GSM = "lte/wcdma/gsm";
+this.GECKO_PREFERRED_NETWORK_TYPE_LTE_WCDMA = "lte/wcdma";
+this.GECKO_PREFERRED_NETWORK_TYPE_LTE_WCDMA_GSM_CDMA_EVDO = "lte/wcdma/gsm/cdma/evdo";
+this.GECKO_PREFERRED_NETWORK_TYPE_LTE_ONLY = "lte";
+this.RIL_PREFERRED_NETWORK_TYPE_TO_GECKO = [
+ GECKO_PREFERRED_NETWORK_TYPE_WCDMA_GSM,
+ GECKO_PREFERRED_NETWORK_TYPE_GSM_ONLY,
+ GECKO_PREFERRED_NETWORK_TYPE_WCDMA_ONLY,
+ GECKO_PREFERRED_NETWORK_TYPE_WCDMA_GSM_AUTO,
+ GECKO_PREFERRED_NETWORK_TYPE_CDMA_EVDO,
+ GECKO_PREFERRED_NETWORK_TYPE_CDMA_ONLY,
+ GECKO_PREFERRED_NETWORK_TYPE_EVDO_ONLY,
+ GECKO_PREFERRED_NETWORK_TYPE_WCDMA_GSM_CDMA_EVDO,
+ GECKO_PREFERRED_NETWORK_TYPE_LTE_CDMA_EVDO,
+ GECKO_PREFERRED_NETWORK_TYPE_LTE_WCDMA_GSM,
+ GECKO_PREFERRED_NETWORK_TYPE_LTE_WCDMA_GSM_CDMA_EVDO,
+ GECKO_PREFERRED_NETWORK_TYPE_LTE_ONLY,
+ GECKO_PREFERRED_NETWORK_TYPE_LTE_WCDMA
+];
+
+this.GECKO_SUPPORTED_NETWORK_TYPES_DEFAULT = "gsm,wcdma";
+// Index-item pair must be in sync with nsIMobileConnection.MOBILE_NETWORK_TYPE_*
+this.GECKO_SUPPORTED_NETWORK_TYPES = [
+ "gsm",
+ "wcdma",
+ "cdma",
+ "evdo",
+ "lte"
+];
+
+// Network registration states. See TS 27.007 7.2
+this.NETWORK_CREG_STATE_NOT_SEARCHING = 0;
+this.NETWORK_CREG_STATE_REGISTERED_HOME = 1;
+this.NETWORK_CREG_STATE_SEARCHING = 2;
+this.NETWORK_CREG_STATE_DENIED = 3;
+this.NETWORK_CREG_STATE_UNKNOWN = 4;
+this.NETWORK_CREG_STATE_REGISTERED_ROAMING = 5;
+this.NETWORK_CREG_STATE_NOT_SEARCHING_EMERGENCY_CALLS = 10;
+this.NETWORK_CREG_STATE_SEARCHING_EMERGENCY_CALLS = 12;
+this.NETWORK_CREG_STATE_DENIED_EMERGENCY_CALLS = 13;
+this.NETWORK_CREG_STATE_UNKNOWN_EMERGENCY_CALLS = 14;
+
+this.NETWORK_CREG_TECH_UNKNOWN = 0;
+this.NETWORK_CREG_TECH_GPRS = 1;
+this.NETWORK_CREG_TECH_EDGE = 2;
+this.NETWORK_CREG_TECH_UMTS = 3;
+this.NETWORK_CREG_TECH_IS95A = 4;
+this.NETWORK_CREG_TECH_IS95B = 5;
+this.NETWORK_CREG_TECH_1XRTT = 6;
+this.NETWORK_CREG_TECH_EVDO0 = 7;
+this.NETWORK_CREG_TECH_EVDOA = 8;
+this.NETWORK_CREG_TECH_HSDPA = 9;
+this.NETWORK_CREG_TECH_HSUPA = 10;
+this.NETWORK_CREG_TECH_HSPA = 11;
+this.NETWORK_CREG_TECH_EVDOB = 12;
+this.NETWORK_CREG_TECH_EHRPD = 13;
+this.NETWORK_CREG_TECH_LTE = 14;
+this.NETWORK_CREG_TECH_HSPAP = 15;
+this.NETWORK_CREG_TECH_GSM = 16;
+this.NETWORK_CREG_TECH_DCHSPAP_1 = 18; // Some devices reports as 18
+this.NETWORK_CREG_TECH_DCHSPAP_2 = 19; // Some others report it as 19
+
+this.CELL_INFO_TYPE_GSM = 1;
+this.CELL_INFO_TYPE_CDMA = 2;
+this.CELL_INFO_TYPE_LTE = 3;
+this.CELL_INFO_TYPE_WCDMA = 4;
+
+this.CALL_STATE_UNKNOWN = -1;
+this.CALL_STATE_ACTIVE = 0;
+this.CALL_STATE_HOLDING = 1;
+this.CALL_STATE_DIALING = 2;
+this.CALL_STATE_ALERTING = 3;
+this.CALL_STATE_INCOMING = 4;
+this.CALL_STATE_WAITING = 5;
+
+this.TOA_INTERNATIONAL = 0x91;
+this.TOA_UNKNOWN = 0x81;
+
+this.CALL_PRESENTATION_ALLOWED = 0;
+this.CALL_PRESENTATION_RESTRICTED = 1;
+this.CALL_PRESENTATION_UNKNOWN = 2;
+this.CALL_PRESENTATION_PAYPHONE = 3;
+
+// Call forwarding actions, see TS 27.007 7.11 "mode"
+this.CALL_FORWARD_ACTION_QUERY_STATUS = 2;
+
+// ICC commands, see TS 27.007 +CRSM commands
+this.ICC_COMMAND_SEEK = 0xa2;
+this.ICC_COMMAND_READ_BINARY = 0xb0;
+this.ICC_COMMAND_READ_RECORD = 0xb2;
+this.ICC_COMMAND_GET_RESPONSE = 0xc0;
+this.ICC_COMMAND_UPDATE_BINARY = 0xd6;
+this.ICC_COMMAND_UPDATE_RECORD = 0xdc;
+
+// ICC constants, GSM SIM file ids from TS 51.011
+this.ICC_EF_ICCID = 0x2fe2;
+this.ICC_EF_IMG = 0x4f20;
+this.ICC_EF_PBR = 0x4f30;
+this.ICC_EF_PLMNsel = 0x6f30; // PLMN for SIM
+this.ICC_EF_SST = 0x6f38;
+this.ICC_EF_UST = 0x6f38; // For USIM
+this.ICC_EF_ADN = 0x6f3a;
+this.ICC_EF_FDN = 0x6f3b;
+this.ICC_EF_SMS = 0x6f3c;
+this.ICC_EF_GID1 = 0x6f3e;
+this.ICC_EF_MSISDN = 0x6f40;
+this.ICC_EF_CBMI = 0x6f45;
+this.ICC_EF_SPN = 0x6f46;
+this.ICC_EF_CBMID = 0x6f48;
+this.ICC_EF_SDN = 0x6f49;
+this.ICC_EF_EXT1 = 0x6f4a;
+this.ICC_EF_EXT2 = 0x6f4b;
+this.ICC_EF_EXT3 = 0x6f4c;
+this.ICC_EF_CBMIR = 0x6f50;
+this.ICC_EF_AD = 0x6fad;
+this.ICC_EF_PHASE = 0x6fae;
+this.ICC_EF_PNN = 0x6fc5;
+this.ICC_EF_OPL = 0x6fc6;
+this.ICC_EF_MBDN = 0x6fc7;
+this.ICC_EF_EXT6 = 0x6fc8; // Ext record for EF[MBDN]
+this.ICC_EF_MBI = 0x6fc9;
+this.ICC_EF_MWIS = 0x6fca;
+this.ICC_EF_CFIS = 0x6fcb;
+this.ICC_EF_SPDI = 0x6fcd;
+
+// CPHS files to be supported
+this.ICC_EF_CPHS_INFO = 0x6f16; // CPHS Information
+this.ICC_EF_CPHS_MBN = 0x6f17; // Mailbox Numbers
+
+// CSIM files
+this.ICC_EF_CSIM_IMSI_M = 0x6f22;
+this.ICC_EF_CSIM_CDMAHOME = 0x6f28;
+this.ICC_EF_CSIM_CST = 0x6f32; // CDMA Service table
+this.ICC_EF_CSIM_SPN = 0x6f41;
+
+this.ICC_PHASE_1 = 0x00;
+this.ICC_PHASE_2 = 0x02;
+this.ICC_PHASE_2_PROFILE_DOWNLOAD_REQUIRED = 0x03;
+
+// Types of files TS 11.11 9.3
+this.TYPE_RFU = 0;
+this.TYPE_MF = 1;
+this.TYPE_DF = 2;
+this.TYPE_EF = 4;
+
+this.RESPONSE_DATA_FILE_SIZE = 2;
+this.RESPONSE_DATA_FILE_ID_1 = 4;
+this.RESPONSE_DATA_FILE_ID_2 = 5;
+this.RESPONSE_DATA_FILE_TYPE = 6;
+this.RESPONSE_DATA_RFU_3 = 7;
+this.RESPONSE_DATA_ACCESS_CONDITION_1 = 8;
+this.RESPONSE_DATA_ACCESS_CONDITION_2 = 9;
+this.RESPONSE_DATA_ACCESS_CONDITION_3 = 10;
+this.RESPONSE_DATA_FILE_STATUS = 11;
+this.RESPONSE_DATA_LENGTH = 12;
+this.RESPONSE_DATA_STRUCTURE = 13;
+this.RESPONSE_DATA_RECORD_LENGTH = 14;
+
+// Structure of files TS 11.11 9.3
+this.EF_STRUCTURE_TRANSPARENT = 0;
+this.EF_STRUCTURE_LINEAR_FIXED = 1;
+this.EF_STRUCTURE_CYCLIC = 3;
+
+// TS 102.221 11.1.1.4.3 Table 11.5: File descriptor byte.
+this.UICC_EF_STRUCTURE = {};
+this.UICC_EF_STRUCTURE[this.EF_STRUCTURE_TRANSPARENT]= 1;
+this.UICC_EF_STRUCTURE[this.EF_STRUCTURE_LINEAR_FIXED]= 2;
+this.UICC_EF_STRUCTURE[this.EF_STRUCTURE_CYCLIC]= 6;
+
+// Status code of EFsms
+// see 3GPP TS 51.011 clause 10.5.3
+this.EFSMS_STATUS_FREE = 0x00;
+this.EFSMS_STATUS_READ = 0x01;
+this.EFSMS_STATUS_TO_BE_READ = 0x03;
+this.EFSMS_STATUS_TO_BE_SENT = 0x07;
+
+// Total size of ADN footer(the size of Alpha identifier excluded).
+// See TS 151.011 clause 10.5.1 EF_ADN.
+this.ADN_FOOTER_SIZE_BYTES = 14;
+// Maximum size of BCD numbers in ADN.
+// See TS 151.011 clause 10.5.1 EF_ADN, 'Length of BCD number/SSC contents'.
+this.ADN_MAX_BCD_NUMBER_BYTES = 11;
+// Maximum digits of the Dialling Number in ADN.
+// See TS 151.011 clause 10.5.1 EF_ADN, 'Dialling Number'.
+this.ADN_MAX_NUMBER_DIGITS = 20;
+// Maximum size of BCD numbers in EXT.
+// See TS 151.011 clause 10.5.10 EF_EXT1, 'Extension data'.
+this.EXT_MAX_BCD_NUMBER_BYTES = 10;
+// Maximum digits of the Dialling Number in EXT.
+// See TS 151.011 clause 10.5.10 EF_EXT1, 'Extension data'.
+this.EXT_MAX_NUMBER_DIGITS = 20;
+
+// READ_RECORD mode, TS 102.221
+this.READ_RECORD_ABSOLUTE_MODE = 4;
+
+// TS 102.221 Table 11.2, return FCP template
+this.GET_RESPONSE_FCP_TEMPLATE = 4;
+
+// GET_RESPONSE mandatory response size for EF, see TS 51.011 clause 9,
+// 'Response data in case of an EF.'
+this.GET_RESPONSE_EF_SIZE_BYTES = 15;
+
+// EF path
+this.EF_PATH_MF_SIM = "3f00";
+this.EF_PATH_DF_PHONEBOOK = "5f3a";
+this.EF_PATH_GRAPHICS = "5f50";
+this.EF_PATH_DF_TELECOM = "7f10";
+this.EF_PATH_DF_GSM = "7f20";
+this.EF_PATH_DF_CDMA = "7f25";
+this.EF_PATH_ADF_USIM = "7fff";
+
+// Status code of sw1 for ICC I/O,
+// see GSM11.11 and TS 51.011 clause 9.4, and ISO 7816-4
+this.ICC_STATUS_NORMAL_ENDING = 0x90;
+this.ICC_STATUS_NORMAL_ENDING_WITH_EXTRA = 0x91;
+this.ICC_STATUS_SAT_BUSY = 0x93;
+this.ICC_STATUS_WITH_SIM_DATA = 0x9e;
+this.ICC_STATUS_WITH_RESPONSE_DATA = 0x9f;
+this.ICC_STATUS_ERROR_WRONG_LENGTH = 0x67;
+this.ICC_STATUS_ERROR_COMMAND_NOT_ALLOWED = 0x69;
+this.ICC_STATUS_ERROR_WRONG_PARAMETERS = 0x6a;
+
+// ICC call barring facility.
+// TS 27.007, clause 7.4, +CLCK
+this.ICC_CB_FACILITY_SIM = "SC";
+this.ICC_CB_FACILITY_FDN = "FD";
+this.ICC_CB_FACILITY_BAOC = "AO";
+this.ICC_CB_FACILITY_BAOIC = "OI";
+this.ICC_CB_FACILITY_BAOICxH = "OX";
+this.ICC_CB_FACILITY_BAIC = "AI";
+this.ICC_CB_FACILITY_BAICr = "IR";
+this.ICC_CB_FACILITY_BA_ALL = "AB";
+this.ICC_CB_FACILITY_BA_MO = "AG";
+this.ICC_CB_FACILITY_BA_MT = "AC";
+
+// ICC service class
+// TS 27.007, clause 7.4, +CLCK
+this.ICC_SERVICE_CLASS_NONE = 0; // no user input
+this.ICC_SERVICE_CLASS_VOICE = (1 << 0);
+this.ICC_SERVICE_CLASS_DATA = (1 << 1);
+this.ICC_SERVICE_CLASS_FAX = (1 << 2);
+this.ICC_SERVICE_CLASS_SMS = (1 << 3);
+this.ICC_SERVICE_CLASS_DATA_SYNC = (1 << 4);
+this.ICC_SERVICE_CLASS_DATA_ASYNC = (1 << 5);
+this.ICC_SERVICE_CLASS_PACKET = (1 << 6);
+this.ICC_SERVICE_CLASS_PAD = (1 << 7);
+this.ICC_SERVICE_CLASS_MAX = (1 << 7); // Max ICC_SERVICE_CLASS value
+
+// ICC lock-selection codes
+// TS 27.007, clause 8.65, +CPINR
+this.ICC_SEL_CODE_SIM_PIN = "SIM PIN";
+this.ICC_SEL_CODE_SIM_PUK = "SIM PUK";
+this.ICC_SEL_CODE_PH_SIM_PIN = "PH-SIM PIN";
+this.ICC_SEL_CODE_PH_FSIM_PIN = "PH-FSIM PIN";
+this.ICC_SEL_CODE_PH_FSIM_PUK = "PH-FSIM PUK";
+this.ICC_SEL_CODE_SIM_PIN2 = "SIM PIN2";
+this.ICC_SEL_CODE_SIM_PUK2 = "SIM PUK2";
+this.ICC_SEL_CODE_PH_NET_PIN = "PH-NET PIN";
+this.ICC_SEL_CODE_PH_NET_PUK = "PH-NET PUK";
+this.ICC_SEL_CODE_PH_NETSUB_PIN = "PH-NETSUB PIN";
+this.ICC_SEL_CODE_PH_NETSUB_PUK = "PH-NETSUB PUK";
+this.ICC_SEL_CODE_PH_SP_PIN = "PH-SP PIN";
+this.ICC_SEL_CODE_PH_SP_PUK = "PH-SP PUK";
+this.ICC_SEL_CODE_PH_CORP_PIN = "PH-CORP PIN";
+this.ICC_SEL_CODE_PH_CORP_PUK = "PH-CORP PUK";
+// TODO: Bug 1116072: identify the mapping between RIL_PERSOSUBSTATE_SIM_SIM @
+// ril.h and TS 27.007, clause 8.65 for GECKO_CARDLOCK_PCK.
+
+this.ICC_USIM_TYPE1_TAG = 0xa8;
+this.ICC_USIM_TYPE2_TAG = 0xa9;
+this.ICC_USIM_TYPE3_TAG = 0xaa;
+this.ICC_USIM_EFADN_TAG = 0xc0;
+this.ICC_USIM_EFIAP_TAG = 0xc1;
+this.ICC_USIM_EFEXT1_TAG = 0xc2;
+this.ICC_USIM_EFSNE_TAG = 0xc3;
+this.ICC_USIM_EFANR_TAG = 0xc4;
+this.ICC_USIM_EFPBC_TAG = 0xc5;
+this.ICC_USIM_EFGRP_TAG = 0xc6;
+this.ICC_USIM_EFAAS_TAG = 0xc7;
+this.ICC_USIM_EFGSD_TAG = 0xc8;
+this.ICC_USIM_EFUID_TAG = 0xc9;
+this.ICC_USIM_EFEMAIL_TAG = 0xca;
+this.ICC_USIM_EFCCP1_TAG = 0xcb;
+
+// ICC image coding scheme
+// TS 31.102, sub-clause 4.6.1.1
+this.ICC_IMG_CODING_SCHEME_BASIC = 0x11;
+this.ICC_IMG_CODING_SCHEME_COLOR = 0x21;
+this.ICC_IMG_CODING_SCHEME_COLOR_TRANSPARENCY = 0x22;
+
+// Must be in sync with enum IccImageCodingScheme in MozStkCommandEvent.webidl.
+this.GECKO_IMG_CODING_SCHEME_BASIC = "basic";
+this.GECKO_IMG_CODING_SCHEME_COLOR = "color";
+this.GECKO_IMG_CODING_SCHEME_COLOR_TRANSPARENCY = "color-transparency";
+
+this.ICC_IMG_CODING_SCHEME_TO_GECKO = {};
+ICC_IMG_CODING_SCHEME_TO_GECKO[ICC_IMG_CODING_SCHEME_BASIC] = GECKO_IMG_CODING_SCHEME_BASIC;
+ICC_IMG_CODING_SCHEME_TO_GECKO[ICC_IMG_CODING_SCHEME_COLOR] = GECKO_IMG_CODING_SCHEME_COLOR;
+ICC_IMG_CODING_SCHEME_TO_GECKO[ICC_IMG_CODING_SCHEME_COLOR_TRANSPARENCY] = GECKO_IMG_CODING_SCHEME_COLOR_TRANSPARENCY;
+
+// ICC image header size per coding scheme
+// TS 31.102, Annex B
+this.ICC_IMG_HEADER_SIZE_BASIC = 2;
+this.ICC_IMG_HEADER_SIZE_COLOR = 6;
+
+this.ICC_CLUT_ENTRY_SIZE = 3;
+
+this.USIM_PBR_ANR = "anr";
+this.USIM_PBR_ANR0 = "anr0";
+this.USIM_PBR_EMAIL = "email";
+
+// Current supported fields. Adding more fields to read will increasing I/O
+// time dramatically, do check the performance is acceptable when you add
+// more fields.
+this.USIM_PBR_FIELDS = [USIM_PBR_EMAIL, USIM_PBR_ANR0];
+
+this.USIM_TAG_NAME = {};
+this.USIM_TAG_NAME[ICC_USIM_EFADN_TAG] = "adn";
+this.USIM_TAG_NAME[ICC_USIM_EFIAP_TAG] ="iap";
+this.USIM_TAG_NAME[ICC_USIM_EFEXT1_TAG] = "ext1";
+this.USIM_TAG_NAME[ICC_USIM_EFSNE_TAG] = "sne";
+this.USIM_TAG_NAME[ICC_USIM_EFANR_TAG] = "anr";
+this.USIM_TAG_NAME[ICC_USIM_EFPBC_TAG] = "pbc";
+this.USIM_TAG_NAME[ICC_USIM_EFGRP_TAG] = "grp";
+this.USIM_TAG_NAME[ICC_USIM_EFAAS_TAG] = "aas";
+this.USIM_TAG_NAME[ICC_USIM_EFGSD_TAG] = "gsd";
+this.USIM_TAG_NAME[ICC_USIM_EFUID_TAG] = "uid";
+this.USIM_TAG_NAME[ICC_USIM_EFEMAIL_TAG] = "email";
+this.USIM_TAG_NAME[ICC_USIM_EFCCP1_TAG] = "ccp1";
+
+// Error message for ICC contact.
+this.CONTACT_ERR_REQUEST_NOT_SUPPORTED = GECKO_ERROR_REQUEST_NOT_SUPPORTED;
+this.CONTACT_ERR_CONTACT_TYPE_NOT_SUPPORTED = "ContactTypeNotSupported";
+this.CONTACT_ERR_FIELD_NOT_SUPPORTED = "FieldNotSupported";
+this.CONTACT_ERR_NO_FREE_RECORD_FOUND = "NoFreeRecordFound";
+this.CONTACT_ERR_CANNOT_ACCESS_PHONEBOOK = "CannotAccessPhoneBook";
+
+// CDMA IMSI_M's byte const.
+// 3GPP2 C.S0065 Sec. 5.2.2
+this.CSIM_IMSI_M_MIN2_BYTE = 1;
+this.CSIM_IMSI_M_MIN1_BYTE = 3;
+this.CSIM_IMSI_M_MNC_BYTE = 6;
+this.CSIM_IMSI_M_PROGRAMMED_BYTE = 7;
+this.CSIM_IMSI_M_MCC_BYTE = 8;
+
+/**
+ * Tags for Ber Tlv.
+ * See 3GPP TS 101 220 clause 7.2 - Assigned TLV tag values.
+ */
+this.BER_UNKNOWN_TAG = 0x00;
+this.BER_FCP_TEMPLATE_TAG = 0x62;
+this.BER_FCP_FILE_SIZE_DATA_TAG = 0x80;
+this.BER_FCP_FILE_SIZE_TOTAL_TAG = 0x81;
+this.BER_FCP_FILE_DESCRIPTOR_TAG = 0x82;
+this.BER_FCP_FILE_IDENTIFIER_TAG = 0x83;
+this.BER_FCP_DF_NAME_TAG = 0x84; // AID.
+this.BER_FCP_PROPRIETARY_PRIMITIVE_TAG = 0x85;
+this.BER_FCP_SFI_SUPPORT_TAG = 0x88;
+this.BER_FCP_LIFE_CYCLE_STATUS_TAG = 0x8a;
+this.BER_FCP_SA_REFERENCE_FORMAT_TAG = 0x8b; // Security Attribute - Reference Format.
+this.BER_FCP_SA_COMPACT_FORMAT_TAG = 0x8c; // Security Attribute - Compact Format.
+this.BER_FCP_SAT_EXPANDED_FORMAT_TAG = 0xab; // Security Attribute Template - Expanded Format.
+this.BER_FCP_PROPRIETARY_TEMPLATE_TAG = 0xa5;
+this.BER_FCP_PIN_STATUS_DATA_OBJECTS_TAG = 0xc6;
+this.BER_PROACTIVE_COMMAND_TAG = 0xd0;
+this.BER_SMS_PP_DOWNLOAD_TAG = 0xd1;
+this.BER_MENU_SELECTION_TAG = 0xd3;
+this.BER_EVENT_DOWNLOAD_TAG = 0xd6;
+this.BER_TIMER_EXPIRATION_TAG = 0xd7;
+
+// Flags in Comprehension TLV.
+this.COMPREHENSIONTLV_FLAG_CR = 0x80; // Comprehension required.
+
+// Tags for Comprehension TLV.
+this.COMPREHENSIONTLV_TAG_COMMAND_DETAILS = 0x01;
+this.COMPREHENSIONTLV_TAG_DEVICE_ID = 0x02;
+this.COMPREHENSIONTLV_TAG_RESULT = 0x03;
+this.COMPREHENSIONTLV_TAG_DURATION = 0x04;
+this.COMPREHENSIONTLV_TAG_ALPHA_ID = 0x05;
+this.COMPREHENSIONTLV_TAG_ADDRESS = 0x06;
+this.COMPREHENSIONTLV_TAG_SUBADDRESS = 0x08;
+this.COMPREHENSIONTLV_TAG_SMS_TPDU = 0x0b;
+this.COMPREHENSIONTLV_TAG_TEXT_STRING = 0x0d;
+this.COMPREHENSIONTLV_TAG_TONE = 0x0e;
+this.COMPREHENSIONTLV_TAG_ITEM = 0x0f;
+this.COMPREHENSIONTLV_TAG_ITEM_ID = 0x10;
+this.COMPREHENSIONTLV_TAG_RESPONSE_LENGTH = 0x11;
+this.COMPREHENSIONTLV_TAG_FILE_LIST = 0x12;
+this.COMPREHENSIONTLV_TAG_LOCATION_INFO = 0x13;
+this.COMPREHENSIONTLV_TAG_IMEI = 0x14;
+this.COMPREHENSIONTLV_TAG_HELP_REQUEST = 0x15;
+this.COMPREHENSIONTLV_TAG_NMR = 0x16;
+this.COMPREHENSIONTLV_TAG_DEFAULT_TEXT = 0x17;
+this.COMPREHENSIONTLV_TAG_NEXT_ACTION_IND = 0x18;
+this.COMPREHENSIONTLV_TAG_CAUSE = 0x1a;
+this.COMPREHENSIONTLV_TAG_LOCATION_STATUS = 0x1b;
+this.COMPREHENSIONTLV_TAG_TRANSACTION_ID = 0x1c;
+this.COMPREHENSIONTLV_TAG_EVENT_LIST = 0x19;
+this.COMPREHENSIONTLV_TAG_ICON_ID = 0x1e;
+this.COMPREHENSIONTLV_TAG_ICON_ID_LIST = 0x1f;
+this.COMPREHENSIONTLV_TAG_TIMER_IDENTIFIER = 0x24;
+this.COMPREHENSIONTLV_TAG_TIMER_VALUE = 0x25;
+this.COMPREHENSIONTLV_TAG_DATE_TIME_ZONE = 0x26;
+this.COMPREHENSIONTLV_TAG_IMMEDIATE_RESPONSE = 0x2b;
+this.COMPREHENSIONTLV_TAG_LANGUAGE = 0x2d;
+this.COMPREHENSIONTLV_TAG_URL = 0x31;
+this.COMPREHENSIONTLV_TAG_BROWSER_TERMINATION_CAUSE = 0x34;
+this.COMPREHENSIONTLV_TAG_ACCESS_TECH = 0x3f;
+this.COMPREHENSIONTLV_TAG_SERVICE_RECORD = 0x41;
+this.COMPREHENSIONTLV_TAG_IMEISV = 0x62;
+this.COMPREHENSIONTLV_TAG_BATTERY_STATE = 0x63;
+this.COMPREHENSIONTLV_TAG_NETWORK_SEARCH_MODE = 0x65;
+this.COMPREHENSIONTLV_TAG_MEID = 0x6d;
+this.COMPREHENSIONTLV_TAG_BROADCAST_NETWORK_INFO = 0x7a;
+
+// Tags for Service Provider Display Information TLV
+this.SPDI_TAG_SPDI = 0xa3;
+this.SPDI_TAG_PLMN_LIST = 0x80;
+
+// MM INFORMATION message content IEIs
+// See 3GPP TS 24.008 table 9.2.18
+this.PNN_IEI_FULL_NETWORK_NAME = 0x43;
+this.PNN_IEI_SHORT_NETWORK_NAME = 0x45;
+
+// Device identifiers, see TS 11.14, clause 12.7
+this.STK_DEVICE_ID_KEYPAD = 0x01;
+this.STK_DEVICE_ID_DISPLAY = 0x02;
+this.STK_DEVICE_ID_EARPIECE = 0x03;
+this.STK_DEVICE_ID_SIM = 0x81;
+this.STK_DEVICE_ID_ME = 0x82;
+this.STK_DEVICE_ID_NETWORK = 0x83;
+
+// STK Proactive commands.
+this.STK_CMD_REFRESH = 0x01;
+this.STK_CMD_MORE_TIME = 0x02;
+this.STK_CMD_POLL_INTERVAL = 0x03;
+this.STK_CMD_POLL_OFF = 0x04;
+this.STK_CMD_SET_UP_EVENT_LIST = 0x05;
+this.STK_CMD_SET_UP_CALL = 0x10;
+this.STK_CMD_SEND_SS = 0x11;
+this.STK_CMD_SEND_USSD = 0x12;
+this.STK_CMD_SEND_SMS = 0x13;
+this.STK_CMD_SEND_DTMF = 0x14;
+this.STK_CMD_LAUNCH_BROWSER = 0x15;
+this.STK_CMD_PLAY_TONE = 0x20;
+this.STK_CMD_DISPLAY_TEXT = 0x21;
+this.STK_CMD_GET_INKEY = 0x22;
+this.STK_CMD_GET_INPUT = 0x23;
+this.STK_CMD_SELECT_ITEM = 0x24;
+this.STK_CMD_SET_UP_MENU = 0x25;
+this.STK_CMD_PROVIDE_LOCAL_INFO = 0x26;
+this.STK_CMD_TIMER_MANAGEMENT = 0x27;
+this.STK_CMD_SET_UP_IDLE_MODE_TEXT = 0x28;
+this.STK_CMD_OPEN_CHANNEL = 0x40;
+this.STK_CMD_CLOSE_CHANNEL = 0x41;
+this.STK_CMD_RECEIVE_DATA = 0x42;
+this.STK_CMD_SEND_DATA = 0x43;
+
+// STK Result code.
+// TS 11.14, clause 12.12
+
+// Results '0X' and '1X' indicate that the command has been performed.
+
+// Command performed successfully.
+this.STK_RESULT_OK = 0x00;
+
+// Command performed with partial comprehension.
+this.STK_RESULT_PRFRMD_WITH_PARTIAL_COMPREHENSION = 0x01;
+
+// Command performed, with missing information.
+this.STK_RESULT_PRFRMD_WITH_MISSING_INFO = 0x02;
+
+// REFRESH performed with additional EFs read.
+this.STK_RESULT_PRFRMD_WITH_ADDITIONAL_EFS_READ = 0x03;
+
+// Command performed successfully, but requested icon could not be
+// displayed.
+this.STK_RESULT_PRFRMD_ICON_NOT_DISPLAYED = 0x04;
+
+// Command performed, but modified by call control by NAA.
+this.STK_RESULT_PRFRMD_MODIFIED_BY_NAA = 0x05;
+
+// Command performed successfully, limited service.
+this.STK_RESULT_PRFRMD_LIMITED_SERVICE = 0x06;
+
+// Command performed with modification.
+this.STK_RESULT_PRFRMD_WITH_MODIFICATION = 0x07;
+
+// REFRESH performed but indicated NAA was not active.
+this.STK_RESULT_PRFRMD_NAA_NOT_ACTIVE = 0x08;
+
+// Command performed successfully; tone not played.
+this.STK_RESULT_PRFRMD_TONE_NOT_PLAYED = 0x09;
+
+// Proactive UICC session terminated by the user.
+this.STK_RESULT_UICC_SESSION_TERM_BY_USER = 0x10;
+
+// Backward move in the proactive UICC session requested by the user.
+this.STK_RESULT_BACKWARD_MOVE_BY_USER = 0x11;
+
+// No response from user.
+this.STK_RESULT_NO_RESPONSE_FROM_USER = 0x12;
+
+// Help information required by the user.
+this.STK_RESULT_HELP_INFO_REQUIRED = 0x13;
+
+// USSD or SS transaction terminated by the user.
+this.STK_RESULT_USSD_SS_SESSION_TERM_BY_USER = 0x14;
+
+// Results '2X' indicate to the UICC that it may be worth re-trying the
+// command at a later opportunity.
+
+// Terminal currently unable to process command.
+this.STK_RESULT_TERMINAL_CRNTLY_UNABLE_TO_PROCESS = 0x20;
+
+// Network currently unable to process command.
+this.STK_RESULT_NETWORK_CRNTLY_UNABLE_TO_PROCESS = 0x21;
+
+// User did not accept the proactive command.
+this.STK_RESULT_USER_NOT_ACCEPT = 0x22;
+
+// User cleared down call before connection or network release.
+this.STK_RESULT_USER_CLEAR_DOWN_CALL = 0x23;
+
+// Action in contradiction with the current timer state.
+this.STK_RESULT_CONTRADICTION_WITH_TIMER = 0x24;
+
+// Interaction with call control by NAA; temporary problem.
+this.STK_RESULT_NAA_CALL_CONTROL_TEMPORARY = 0x25;
+
+// Launch browser generic error code.
+this.STK_RESULT_LAUNCH_BROWSER_ERROR = 0x26;
+
+// MMS temporary problem.
+this.STK_RESULT_MMS_TEMPORARY = 0x27;
+
+// Results '3X' indicate that it is not worth the UICC re-trying with an
+// identical command; as it will only get the same response. However, the
+// decision to retry lies with the application.
+
+// Command beyond terminal's capabilities.
+this.STK_RESULT_BEYOND_TERMINAL_CAPABILITY = 0x30;
+
+// Command type not understood by terminal.
+this.STK_RESULT_CMD_TYPE_NOT_UNDERSTOOD = 0x31;
+
+// Command data not understood by terminal.
+this.STK_RESULT_CMD_DATA_NOT_UNDERSTOOD = 0x32;
+
+// Command number not known by terminal.
+this.STK_RESULT_CMD_NUM_NOT_KNOWN = 0x33;
+
+// SS Return Error.
+this.STK_RESULT_SS_RETURN_ERROR = 0x34;
+
+// SMS RP-ERROR.
+this.STK_RESULT_SMS_RP_ERROR = 0x35;
+
+// Error, required values are missing.
+this.STK_RESULT_REQUIRED_VALUES_MISSING = 0x36;
+
+// USSD Return Error.
+this.STK_RESULT_USSD_RETURN_ERROR = 0x37;
+
+// MultipleCard commands error.
+this.STK_RESULT_MULTI_CARDS_CMD_ERROR = 0x38;
+
+// Interaction with call control by USIM or MO short message control by
+// USIM; permanent problem.
+this.STK_RESULT_USIM_CALL_CONTROL_PERMANENT = 0x39;
+
+// Bearer Independent Protocol error.
+this.STK_RESULT_BIP_ERROR = 0x3a;
+
+// Access Technology unable to process command.
+this.STK_RESULT_ACCESS_TECH_UNABLE_TO_PROCESS = 0x3b;
+
+// Frames error.
+this.STK_RESULT_FRAMES_ERROR = 0x3c;
+
+// MMS Error.
+this.STK_RESULT_MMS_ERROR = 0x3d;
+
+// STK presentation types, TS 11.14, clause 12.6, Command Qualifier: Select Item
+this.STK_PRESENTATION_TYPE_NOT_SPECIFIED = 0x00; // Bit 1 is 0.
+this.STK_PRESENTATION_TYPE_DATA_VALUES = 0x01; // Bit 1 is 1, bit 2 is 0.
+this.STK_PRESENTATION_TYPE_NAVIGATION_OPTIONS = 0x03; // Bit 1 is 1, bit 2 is 1.
+
+// STK Coding Scheme.
+this.STK_TEXT_CODING_GSM_7BIT_PACKED = 0x00;
+this.STK_TEXT_CODING_GSM_8BIT = 0x04;
+this.STK_TEXT_CODING_UCS2 = 0x08;
+
+// STK Event List.
+this.STK_EVENT_TYPE_MT_CALL = 0x00;
+this.STK_EVENT_TYPE_CALL_CONNECTED = 0x01;
+this.STK_EVENT_TYPE_CALL_DISCONNECTED = 0x02;
+this.STK_EVENT_TYPE_LOCATION_STATUS = 0x03;
+this.STK_EVENT_TYPE_USER_ACTIVITY = 0x04;
+this.STK_EVENT_TYPE_IDLE_SCREEN_AVAILABLE = 0x05;
+this.STK_EVENT_TYPE_CARD_READER_STATUS = 0x06;
+this.STK_EVENT_TYPE_LANGUAGE_SELECTION = 0x07;
+this.STK_EVENT_TYPE_BROWSER_TERMINATION = 0x08;
+this.STK_EVENT_TYPE_DATA_AVAILABLE = 0x09;
+this.STK_EVENT_TYPE_CHANNEL_STATUS = 0x0a;
+this.STK_EVENT_TYPE_SINGLE_ACCESS_TECHNOLOGY_CHANGED = 0x0b;
+this.STK_EVENT_TYPE_DISPLAY_PARAMETER_CHANGED = 0x0c;
+this.STK_EVENT_TYPE_LOCAL_CONNECTION = 0x0d;
+this.STK_EVENT_TYPE_NETWORK_SEARCH_MODE_CHANGED = 0x0e;
+this.STK_EVENT_TYPE_BROWSING_STATUS = 0x0f;
+
+// STK Service state of Location Status.
+this.STK_SERVICE_STATE_NORMAL = 0x00;
+this.STK_SERVICE_STATE_LIMITED = 0x01;
+this.STK_SERVICE_STATE_UNAVAILABLE = 0x02;
+
+// Refresh mode.
+this.STK_REFRESH_NAA_INIT_AND_FULL_FILE_CHANGE = 0x00;
+this.STK_REFRESH_FILE_CHANGE = 0x01;
+this.STK_REFRESH_NAA_INIT_AND_FILE_CHANGE = 0x02;
+this.STK_REFRESH_NAA_INIT = 0x03;
+this.STK_REFRESH_UICC_RESET = 0x04;
+
+// Tone type.
+this.STK_TONE_TYPE_DIAL_TONE = 0x01;
+this.STK_TONE_TYPE_CALLED_SUBSCRIBER_BUSY = 0x02;
+this.STK_TONE_TYPE_CONGESTION = 0x03;
+this.STK_TONE_TYPE_RADIO_PATH_ACK = 0x04;
+this.STK_TONE_TYPE_RADIO_PATH_NOT_AVAILABLE = 0x05;
+this.STK_TONE_TYPE_ERROR = 0x06;
+this.STK_TONE_TYPE_CALL_WAITING_TONE = 0x07;
+this.STK_TONE_TYPE_RINGING_TONE = 0x08;
+this.STK_TONE_TYPE_GENERAL_BEEP = 0x10;
+this.STK_TONE_TYPE_POSITIVE_ACK_TONE = 0x11;
+this.STK_TONE_TYPE_NEGATIVE_ACK_TONE = 0x12;
+
+// Time unit.
+this.STK_TIME_UNIT_MINUTE = 0x00;
+this.STK_TIME_UNIT_SECOND = 0x01;
+this.STK_TIME_UNIT_TENTH_SECOND = 0x02;
+
+// Local Information type.
+this.STK_LOCAL_INFO_NNA = 0x00;
+this.STK_LOCAL_INFO_IMEI = 0x01;
+this.STK_LOCAL_INFO_NMR_FOR_NNA = 0x02;
+this.STK_LOCAL_INFO_DATE_TIME_ZONE = 0x03;
+this.STK_LOCAL_INFO_LANGUAGE = 0x04;
+this.STK_LOCAL_INFO_ACCESS_TECH = 0x06;
+this.STK_LOCAL_INFO_ESN = 0x07;
+this.STK_LOCAL_INFO_IMEISV = 0x08;
+this.STK_LOCAL_INFO_SEARCH_MODE = 0x09;
+this.STK_LOCAL_INFO_CHARGE_STATE = 0x0A;
+this.STK_LOCAL_INFO_MEID = 0x0B;
+this.STK_LOCAL_INFO_BROADCAST_NETWORK_INFO = 0x0D;
+this.STK_LOCAL_INFO_MULTIPLE_ACCESS_TECH = 0x0E;
+this.STK_LOCAL_INFO_INFO_FOR_MULTIPLE_ACCESS_TECH = 0x0F;
+this.STK_LOCAL_INFO_NMR_FOR_MULTIPLE_ACCESS_TECH = 0x10;
+
+// Timer Management.
+this.STK_TIMER_START = 0x00;
+this.STK_TIMER_DEACTIVATE = 0x01;
+this.STK_TMIER_GET_CURRENT_VALUE = 0x02;
+
+// Browser Launch Mode.
+this.STK_BROWSER_MODE_LAUNCH_IF_NOT_ALREADY_LAUNCHED = 0x00;
+this.STK_BROWSER_MODE_USING_EXISTING_BROWSER = 0x02;
+this.STK_BROWSER_MODE_USING_NEW_BROWSER = 0x03;
+
+// Browser Termination Cause.
+this.STK_BROWSER_TERMINATION_CAUSE_USER = 0x00;
+this.STK_BROWSER_TERMINATION_CAUSE_ERROR = 0x01;
+
+// Next Action Indicator.
+this.STK_NEXT_ACTION_NULL = 0x00;
+this.STK_NEXT_ACTION_END_PROACTIVE_SESSION = 0x81;
+
+/**
+ * Supported Terminal Facilities.
+ *
+ * value = 1, supported.
+ * 0, not supported.
+ */
+this.STK_TERMINAL_SUPPORT_PROFILE_DOWNLOAD = 1;
+this.STK_TERMINAL_SUPPORT_SMS_PP_DOWNLOAD = 1;
+this.STK_TERMINAL_SUPPORT_CELL_BROADCAST_DATA_DOWNLOAD = 0;
+this.STK_TERMINAL_SUPPORT_MENU_SELECTION = 1;
+this.STK_TERMINAL_SUPPORT_SIM_DATA_DOWNLOAD_ERROR = 0;
+this.STK_TERMINAL_SUPPORT_TIMER_EXPIRATION = 1;
+this.STK_TERMINAL_SUPPORT_USSD_IN_CALL_CONTROL = 0;
+this.STK_TERMINAL_SUPPORT_CALL_CONTROL_IN_REDIAL = 0;
+
+this.STK_TERMINAL_SUPPORT_COMMAND_RESULT = 1;
+this.STK_TERMINAL_SUPPORT_CALL_CONTROL = 1;
+this.STK_TERMINAL_SUPPORT_CALL_ID_INCLUDED = 0;
+this.STK_TERMINAL_SUPPORT_MO_SMS_CONTROL = 0;
+this.STK_TERMINAL_SUPPORT_ALPHA_ID_INDICATION = 0;
+this.STK_TERMINAL_SUPPORT_UCS2_ENTRY = 1;
+this.STK_TERMINAL_SUPPORT_UCS2_DISPLAY = 1;
+this.STK_TERMINAL_SUPPORT_EXTENSION_TEXT = 1;
+
+this.STK_TERMINAL_SUPPORT_PROACTIVE_DISPLAY_TEXT = 1;
+this.STK_TERMINAL_SUPPORT_PROACTIVE_GET_INKEY = 1;
+this.STK_TERMINAL_SUPPORT_PROACTIVE_GET_INPUT = 1;
+this.STK_TERMINAL_SUPPORT_PROACTIVE_MORE_TIME = 1;
+this.STK_TERMINAL_SUPPORT_PROACTIVE_PLAY_TONE = 1;
+this.STK_TERMINAL_SUPPORT_PROACTIVE_POLL_INTERVAL = 1;
+this.STK_TERMINAL_SUPPORT_PROACTIVE_POLL_OFF = 1;
+this.STK_TERMINAL_SUPPORT_PROACTIVE_REFRESH = 1;
+
+this.STK_TERMINAL_SUPPORT_PROACTIVE_SELECT_ITEM = 1;
+this.STK_TERMINAL_SUPPORT_PROACTIVE_SEND_SMS = 1;
+this.STK_TERMINAL_SUPPORT_PROACTIVE_SEND_SS = 1;
+this.STK_TERMINAL_SUPPORT_PROACTIVE_SEND_USSD = 1;
+this.STK_TERMINAL_SUPPORT_PROACTIVE_SET_UP_CALL = 1;
+this.STK_TERMINAL_SUPPORT_PROACTIVE_SET_UP_MENU = 1;
+this.STK_TERMINAL_SUPPORT_PROACTIVE_LOCAL_INFO = 1;
+this.STK_TERMINAL_SUPPORT_PROACTIVE_LOCAL_INFO_NMR = 0;
+
+this.STK_TERMINAL_SUPPORT_PROACTIVE_SET_UP_EVENT_LIST = 1;
+this.STK_TERMINAL_SUPPORT_EVENT_MT_CALL = 1;
+this.STK_TERMINAL_SUPPORT_EVENT_CALL_CONNECTED = 1;
+this.STK_TERMINAL_SUPPORT_EVENT_CALL_DISCONNECTED = 1;
+this.STK_TERMINAL_SUPPORT_EVENT_LOCATION_STATUS = 1;
+this.STK_TERMINAL_SUPPORT_EVENT_USER_ACTIVITY = 1;
+this.STK_TERMINAL_SUPPORT_EVENT_IDLE_SCREEN_AVAILABLE = 1;
+this.STK_TERMINAL_SUPPORT_EVENT_CARD_READER_STATUS = 0;
+
+this.STK_TERMINAL_SUPPORT_EVENT_LANGUAGE_SELECTION = 1;
+this.STK_TERMINAL_SUPPORT_EVENT_BROWSER_TERMINATION = 1;
+this.STK_TERMINAL_SUPPORT_EVENT_DATA_AVAILABLE = 0;
+this.STK_TERMINAL_SUPPORT_EVENT_CHANNEL_STATUS = 0;
+
+this.STK_TERMINAL_SUPPORT_PROACTIVE_TIMER_START_STOP = 1;
+this.STK_TERMINAL_SUPPORT_PROACTIVE_TIMER_GET_CURRENT = 1;
+this.STK_TERMINAL_SUPPORT_PROACTIVE_LOCAL_INFO_DATE = 1;
+this.STK_TERMINAL_SUPPORT_GET_INKEY = 1;
+this.STK_TERMINAL_SUPPORT_SET_UP_IDLE_MODE_TEXT = 1;
+this.STK_TERMINAL_SUPPORT_RUN_AT_COMMAND = 0;
+this.STK_TERMINAL_SUPPORT_SET_UP_CALL = 1;
+this.STK_TERMINAL_SUPPORT_CALL_CONTROL_BY_NNA = 0;
+
+this.STK_TERMINAL_SUPPORT_DISPLAY_TEXT = 1;
+this.STK_TERMINAL_SUPPORT_SEND_DTMF_COMMAND = 1;
+this.STK_TERMINAL_SUPPORT_PROACTIVE_LOCAL_INFO_NMR = 0;
+this.STK_TERMINAL_SUPPORT_PROACTIVE_LOCAL_INFO_LANGUAGE = 1;
+this.STK_TERMINAL_SUPPORT_PROACTIVE_LOCAL_INFO_TIME_ADVANCE = 0;
+this.STK_TERMINAL_SUPPORT_PROACTIVE_LANGUAGE_NOTIFICATION = 0;
+this.STK_TERMINAL_SUPPORT_PROACTIVE_LAUNCH_BROWSER = 1;
+this.STK_TERMINAL_SUPPORT_PROACTIVE_LOCAL_INFO_ACCESS_TECH = 0;
+
+this.STK_TERMINAL_SUPPORT_BIP_COMMAND_OPEN_CHANNEL = 1;
+this.STK_TERMINAL_SUPPORT_BIP_COMMAND_CLOSE_CHANNEL = 1;
+this.STK_TERMINAL_SUPPORT_BIP_COMMAND_RECEIVE_DATA = 1;
+this.STK_TERMINAL_SUPPORT_BIP_COMMAND_SEND_DATA = 1;
+this.STK_TERMINAL_SUPPORT_BIP_COMMAND_GET_CHANNEL_STATUS = 0;
+
+/**
+ * SAT profile
+ *
+ * @see ETSI TS 101.267, section 5.2.
+ */
+this.STK_TERMINAL_PROFILE_DOWNLOAD =
+ (STK_TERMINAL_SUPPORT_PROFILE_DOWNLOAD << 0) |
+ (STK_TERMINAL_SUPPORT_SMS_PP_DOWNLOAD << 1) |
+ (STK_TERMINAL_SUPPORT_CELL_BROADCAST_DATA_DOWNLOAD << 2) |
+ (STK_TERMINAL_SUPPORT_MENU_SELECTION << 3) |
+ (STK_TERMINAL_SUPPORT_SIM_DATA_DOWNLOAD_ERROR << 4) |
+ (STK_TERMINAL_SUPPORT_TIMER_EXPIRATION << 5) |
+ (STK_TERMINAL_SUPPORT_USSD_IN_CALL_CONTROL << 6) |
+ (STK_TERMINAL_SUPPORT_CALL_CONTROL_IN_REDIAL << 7);
+
+this.STK_TERMINAL_PROFILE_OTHER =
+ (STK_TERMINAL_SUPPORT_COMMAND_RESULT << 0) |
+ (STK_TERMINAL_SUPPORT_CALL_CONTROL << 1) |
+ (STK_TERMINAL_SUPPORT_CALL_ID_INCLUDED << 2) |
+ (STK_TERMINAL_SUPPORT_MO_SMS_CONTROL << 3) |
+ (STK_TERMINAL_SUPPORT_ALPHA_ID_INDICATION << 4) |
+ (STK_TERMINAL_SUPPORT_UCS2_ENTRY << 5) |
+ (STK_TERMINAL_SUPPORT_UCS2_DISPLAY << 6) |
+ (STK_TERMINAL_SUPPORT_EXTENSION_TEXT << 7);
+
+this.STK_TERMINAL_PROFILE_PROACTIVE_1 =
+ (STK_TERMINAL_SUPPORT_PROACTIVE_DISPLAY_TEXT << 0) |
+ (STK_TERMINAL_SUPPORT_PROACTIVE_GET_INKEY << 1) |
+ (STK_TERMINAL_SUPPORT_PROACTIVE_GET_INPUT << 2) |
+ (STK_TERMINAL_SUPPORT_PROACTIVE_MORE_TIME << 3) |
+ (STK_TERMINAL_SUPPORT_PROACTIVE_PLAY_TONE << 4) |
+ (STK_TERMINAL_SUPPORT_PROACTIVE_POLL_INTERVAL << 5) |
+ (STK_TERMINAL_SUPPORT_PROACTIVE_POLL_OFF << 6) |
+ (STK_TERMINAL_SUPPORT_PROACTIVE_REFRESH << 7);
+
+this.STK_TERMINAL_PROFILE_PROACTIVE_2 =
+ (STK_TERMINAL_SUPPORT_PROACTIVE_SELECT_ITEM << 0) |
+ (STK_TERMINAL_SUPPORT_PROACTIVE_SEND_SMS << 1) |
+ (STK_TERMINAL_SUPPORT_PROACTIVE_SEND_SS << 2) |
+ (STK_TERMINAL_SUPPORT_PROACTIVE_SEND_USSD << 3) |
+ (STK_TERMINAL_SUPPORT_PROACTIVE_SET_UP_CALL << 4) |
+ (STK_TERMINAL_SUPPORT_PROACTIVE_SET_UP_MENU << 5) |
+ (STK_TERMINAL_SUPPORT_PROACTIVE_LOCAL_INFO << 6) |
+ (STK_TERMINAL_SUPPORT_PROACTIVE_LOCAL_INFO_NMR << 7);
+
+this.STK_TERMINAL_PROFILE_EVENT =
+ (STK_TERMINAL_SUPPORT_PROACTIVE_SET_UP_EVENT_LIST << 0) |
+ (STK_TERMINAL_SUPPORT_EVENT_MT_CALL << 1) |
+ (STK_TERMINAL_SUPPORT_EVENT_CALL_CONNECTED << 2) |
+ (STK_TERMINAL_SUPPORT_EVENT_CALL_DISCONNECTED << 3) |
+ (STK_TERMINAL_SUPPORT_EVENT_LOCATION_STATUS << 4) |
+ (STK_TERMINAL_SUPPORT_EVENT_USER_ACTIVITY << 5) |
+ (STK_TERMINAL_SUPPORT_EVENT_IDLE_SCREEN_AVAILABLE << 6) |
+ (STK_TERMINAL_SUPPORT_EVENT_CARD_READER_STATUS << 7);
+
+this.STK_TERMINAL_PROFILE_EVENT_EXT =
+ (STK_TERMINAL_SUPPORT_EVENT_LANGUAGE_SELECTION << 0) |
+ (STK_TERMINAL_SUPPORT_EVENT_BROWSER_TERMINATION << 1) |
+ (STK_TERMINAL_SUPPORT_EVENT_DATA_AVAILABLE << 2) |
+ (STK_TERMINAL_SUPPORT_EVENT_CHANNEL_STATUS << 3);
+
+this.STK_TERMINAL_PROFILE_PROACTIVE_3 =
+ (STK_TERMINAL_SUPPORT_PROACTIVE_TIMER_START_STOP << 0) |
+ (STK_TERMINAL_SUPPORT_PROACTIVE_TIMER_GET_CURRENT << 1) |
+ (STK_TERMINAL_SUPPORT_PROACTIVE_LOCAL_INFO_DATE << 2) |
+ (STK_TERMINAL_SUPPORT_GET_INKEY << 3) |
+ (STK_TERMINAL_SUPPORT_SET_UP_IDLE_MODE_TEXT << 4) |
+ (STK_TERMINAL_SUPPORT_RUN_AT_COMMAND << 5) |
+ (STK_TERMINAL_SUPPORT_SET_UP_CALL << 6) |
+ (STK_TERMINAL_SUPPORT_CALL_CONTROL_BY_NNA << 7);
+
+this.STK_TERMINAL_PROFILE_PROACTIVE_4 =
+ (STK_TERMINAL_SUPPORT_DISPLAY_TEXT << 0) |
+ (STK_TERMINAL_SUPPORT_SEND_DTMF_COMMAND << 1) |
+ (STK_TERMINAL_SUPPORT_PROACTIVE_LOCAL_INFO_NMR << 2) |
+ (STK_TERMINAL_SUPPORT_PROACTIVE_LOCAL_INFO_LANGUAGE << 3) |
+ (STK_TERMINAL_SUPPORT_PROACTIVE_LOCAL_INFO_TIME_ADVANCE << 4) |
+ (STK_TERMINAL_SUPPORT_PROACTIVE_LANGUAGE_NOTIFICATION << 5) |
+ (STK_TERMINAL_SUPPORT_PROACTIVE_LAUNCH_BROWSER << 6) |
+ (STK_TERMINAL_SUPPORT_PROACTIVE_LOCAL_INFO_ACCESS_TECH << 7);
+
+this.STK_TERMINAL_PROFILE_BIP_COMMAND =
+ (STK_TERMINAL_SUPPORT_BIP_COMMAND_OPEN_CHANNEL << 0) |
+ (STK_TERMINAL_SUPPORT_BIP_COMMAND_CLOSE_CHANNEL << 1) |
+ (STK_TERMINAL_SUPPORT_BIP_COMMAND_RECEIVE_DATA << 2) |
+ (STK_TERMINAL_SUPPORT_BIP_COMMAND_SEND_DATA << 3) |
+ (STK_TERMINAL_SUPPORT_BIP_COMMAND_GET_CHANNEL_STATUS << 4);
+
+this.STK_SUPPORTED_TERMINAL_PROFILE = [
+ STK_TERMINAL_PROFILE_DOWNLOAD,
+ STK_TERMINAL_PROFILE_OTHER,
+ STK_TERMINAL_PROFILE_PROACTIVE_1,
+ STK_TERMINAL_PROFILE_PROACTIVE_2,
+ STK_TERMINAL_PROFILE_EVENT,
+ STK_TERMINAL_PROFILE_EVENT_EXT, // Event extension
+ 0x00, // Multiple card proactive commands
+ STK_TERMINAL_PROFILE_PROACTIVE_3,
+ STK_TERMINAL_PROFILE_PROACTIVE_4,
+ 0x00, // Softkey support
+ 0x00, // Softkey information
+ STK_TERMINAL_PROFILE_BIP_COMMAND,
+ 0x00, // BIP supported bearers
+ 0x00, // Screen height
+ 0x00, // Screen width
+ 0x00, // 16, Screen effects
+ 0x00, // 17, BIP supported transport interface
+ 0x00, // 18, RFU
+ 0x00, // 19, RFU
+ 0x00, // 20, RFU
+];
+
+/**
+ * ICC Services Table.
+ *
+ * @see 3GPP TS 51.011 10.3.7 (SIM) and 3GPP TS 31.102 4.2.8 (USIM).
+ */
+this.GECKO_ICC_SERVICES = {
+ // @see 3GPP TS 51.011 10.3.7 (SIM).
+ sim: {
+ ADN: 2,
+ FDN: 3,
+ PLMNSEL: 7,
+ MSISDN: 9,
+ EXT1: 10,
+ EXT2: 11,
+ CBMI: 14,
+ GID1: 15,
+ SPN: 17,
+ SDN: 18,
+ EXT3: 19,
+ DATA_DOWNLOAD_SMS_CB: 25,
+ DATA_DOWNLOAD_SMS_PP: 26,
+ CBMIR: 30,
+ BDN: 31,
+ IMG: 39,
+ PNN: 51,
+ OPL: 52,
+ MDN: 53,
+ MWIS: 54,
+ SPDI: 56
+ },
+ // @see 3GPP TS 31.102 4.2.8 (USIM).
+ usim: {
+ FDN: 2,
+ EXT2: 3,
+ SDN: 4,
+ EXT3: 5,
+ BDN: 6,
+ CBMI: 15,
+ CBMIR: 16,
+ GID1: 17,
+ SPN: 19,
+ MSISDN: 21,
+ IMG: 22,
+ DATA_DOWNLOAD_SMS_PP: 28,
+ DATA_DOWNLOAD_SMS_CB: 29,
+ PNN: 45,
+ OPL: 46,
+ MDN: 47,
+ MWIS: 48,
+ SPDI: 51
+ },
+ // @see 3GPP2 C.S0023-D 3.4.18 (RUIM).
+ ruim: {
+ FDN: 3,
+ ENHANCED_PHONEBOOK: 6,
+ EXT1: 10,
+ EXT2: 11,
+ SPN: 17,
+ SDN: 18,
+ EXT3: 19,
+ },
+ // @see B.3.1.1 CPHS Information in CPHS Phase 2:
+ // Indicates which of the CPHS 'optional' data-fields are present in the SIM card:
+ // EF_CPHS_CSP, EF_CPHS_SST, EF_CPHS_MBN, EF_CPHS_ONSF, EF_CPHS_INFO_NUM
+ // Note: Mandatory EFs are: (B.3.1 Enhanced SIM Requirements)
+ // EF_CPHS_CFF, EF_CPHS_VMI, EF_CPHS_ONS, EF_CPHS_INFO
+ cphs: {
+ CSP: 1,
+ SST: 2,
+ MBN: 3,
+ ONSF: 4,
+ INFO_NUM: 5
+ }
+};
+
+/**
+ * Cell Broadcast constants
+ */
+
+this.CB_FORMAT_GSM = 0;
+this.CB_FORMAT_ETWS = 1;
+this.CB_FORMAT_CMAS = 2;
+this.CB_FORMAT_UMTS = 3;
+
+// CBS Data Coding Scheme: Language groups
+// see 3GPP TS 23.038 section 5
+this.CB_DCS_LANG_GROUP_1 = [
+ "de", "en", "it", "fr", "es", "nl", "sv", "da", "pt", "fi",
+ "no", "el", "tr", "hu", "pl", null
+];
+this.CB_DCS_LANG_GROUP_2 = [
+ "cs", "he", "ar", "ru", "is", null, null, null, null, null,
+ null, null, null, null, null, null
+];
+
+// See 3GPP TS 23.041 v11.2.0 section 9.4.1.2.2
+this.CB_NON_MMI_SETTABLE_RANGES = [
+ /*0x1000 - 0x107F*/4096, 4224, /*0x1080 - 0x10FF*/4224, 4352,
+ /*0x1112 - 0x1112*/4370, 4371, /*0x111F - 0x111F*/4383, 4384,
+ /*0xF000 - 0xFFFE*/61440, 65535, /*0xFFFF - 0xFFFF*/65535, 65536
+];
+
+// User Data max length in septets
+this.CB_MAX_CONTENT_7BIT = 93;
+// User Data max length in octets
+this.CB_MAX_CONTENT_8BIT = 82;
+// User Data max length in chars
+this.CB_MAX_CONTENT_UCS2 = 41;
+
+// See 3GPP TS 23.041 v11.6.0 senction 9.3.19
+this.CB_MSG_PAGE_INFO_SIZE = 82;
+
+this.CB_MESSAGE_SIZE_ETWS = 56;
+this.CB_MESSAGE_SIZE_GSM = 88;
+this.CB_MESSAGE_SIZE_UMTS_MIN = 90;
+this.CB_MESSAGE_SIZE_UMTS_MAX = 1252;
+
+
+
+// GSM Cell Broadcast Geographical Scope
+// See 3GPP TS 23.041 clause 9.4.1.2.1
+this.CB_GSM_GEOGRAPHICAL_SCOPE_CELL_WIDE_IMMEDIATE = 0;
+this.CB_GSM_GEOGRAPHICAL_SCOPE_PLMN_WIDE = 1;
+this.CB_GSM_GEOGRAPHICAL_SCOPE_LOCATION_AREA_WIDE = 2;
+this.CB_GSM_GEOGRAPHICAL_SCOPE_CELL_WIDE = 3;
+
+// GSM Cell Broadcast Geographical Scope
+// See 3GPP TS 23.041 clause 9.4.1.2.1
+this.CB_GSM_GEOGRAPHICAL_SCOPE_NAMES = [
+ "cell-immediate",
+ "plmn",
+ "location-area",
+ "cell"
+];
+
+// GSM Cell Broadcast Message Identifiers
+// see 3GPP TS 23.041 clause 9.4.1.2.2
+this.CB_GSM_MESSAGEID_ETWS_BEGIN = 0x1100;
+this.CB_GSM_MESSAGEID_ETWS_END = 0x1107;
+
+// ETWS Warning-Type
+// see 3GPP TS 23.041 clause 9.3.24
+this.CB_ETWS_WARNING_TYPE_NAMES = [
+ "earthquake",
+ "tsunami",
+ "earthquake-tsunami",
+ "test",
+ "other"
+];
+
+// UMTS Message Type
+// see 3GPP TS 25.324 section 11.1
+this.CB_UMTS_MESSAGE_TYPE_CBS = 1;
+this.CB_UMTS_MESSAGE_TYPE_SCHEDULE = 2;
+this.CB_UMTS_MESSAGE_TYPE_CBS41 = 3;
+
+/**
+ * Number plan identification defined in
+ * |Table 10.5.118: Called party BCD number| of 3GPP TS 24.008.
+ */
+this.CALLED_PARTY_BCD_NPI_UNKNOWN = 0;
+this.CALLED_PARTY_BCD_NPI_ISDN = 1;
+this.CALLED_PARTY_BCD_NPI_DATA = 3;
+this.CALLED_PARTY_BCD_NPI_TELEX = 4;
+this.CALLED_PARTY_BCD_NPI_NATIONAL = 8;
+this.CALLED_PARTY_BCD_NPI_PRIVATE = 9;
+
+/**
+ * Array of number plan identification values which can be used to map an
+ * enumeration to the corresponding value. The indices should be consistent
+ * with nsISmsService::NUMBER_PLAN_IDENTIFICATION_* constants.
+ */
+this.CALLED_PARTY_BCD_NPI = [
+ CALLED_PARTY_BCD_NPI_UNKNOWN,
+ CALLED_PARTY_BCD_NPI_ISDN,
+ CALLED_PARTY_BCD_NPI_DATA,
+ CALLED_PARTY_BCD_NPI_TELEX,
+ CALLED_PARTY_BCD_NPI_NATIONAL,
+ CALLED_PARTY_BCD_NPI_PRIVATE
+];
+
+/**
+ * GSM PDU constants
+ */
+
+// PDU TYPE-OF-ADDRESS
+this.PDU_TOA_UNKNOWN = 0x80; // Unknown. This is used when the user or
+ // network has no a priori information
+ // about the numbering plan.
+this.PDU_TOA_ISDN = 0x81; // ISDN/Telephone numbering
+this.PDU_TOA_DATA_NUM = 0x83; // Data numbering plan
+this.PDU_TOA_TELEX_NUM = 0x84; // Telex numbering plan
+this.PDU_TOA_NATIONAL_NUM = 0x88; // National numbering plan
+this.PDU_TOA_PRIVATE_NUM = 0x89; // Private numbering plan
+this.PDU_TOA_ERMES_NUM = 0x8A; // Ermes numbering plan
+this.PDU_TOA_INTERNATIONAL = 0x90; // International number
+this.PDU_TOA_NATIONAL = 0xA0; // National number. Prefix or escape digits
+ // shall not be included
+this.PDU_TOA_NETWORK_SPEC = 0xB0; // Network specific number This is used to
+ // indicate administration/service number
+ // specific to the serving network
+this.PDU_TOA_SUBSCRIBER = 0xC0; // Subscriber number. This is used when a
+ // specific short number representation is
+ // stored in one or more SCs as part of a
+ // higher layer application
+this.PDU_TOA_ALPHANUMERIC = 0xD0; // Alphanumeric, (coded according to GSM TS
+ // 03.38 7-bit default alphabet)
+this.PDU_TOA_ABBREVIATED = 0xE0; // Abbreviated number
+
+/**
+ * First octet of the SMS-DELIVER PDU
+ *
+ * RP: 0 Reply Path parameter is not set in this PDU
+ * 1 Reply Path parameter is set in this PDU
+ *
+ * UDHI: 0 The UD field contains only the short message
+ * 1 The beginning of the UD field contains a header in addition of
+ * the short message
+ *
+ * SRI: (is only set by the SMSC)
+ * 0 A status report will not be returned to the SME
+ * 1 A status report will be returned to the SME
+ *
+ * MMS: (is only set by the SMSC)
+ * 0 More messages are waiting for the MS in the SMSC
+ * 1 No more messages are waiting for the MS in the SMSC
+ *
+ * MTI: bit1 bit0 Message type
+ * 0 0 SMS-DELIVER (SMSC ==> MS)
+ * 0 0 SMS-DELIVER REPORT (MS ==> SMSC, is generated
+ * automatically by the M20, after receiving a
+ * SMS-DELIVER)
+ * 0 1 SMS-SUBMIT (MS ==> SMSC)
+ * 0 1 SMS-SUBMIT REPORT (SMSC ==> MS)
+ * 1 0 SMS-STATUS REPORT (SMSC ==> MS)
+ * 1 0 SMS-COMMAND (MS ==> SMSC)
+ * 1 1 Reserved
+ */
+this.PDU_RP = 0x80; // Reply path. Parameter indicating that
+ // reply path exists.
+this.PDU_UDHI = 0x40; // User data header indicator. This bit is
+ // set to 1 if the User Data field starts
+ // with a header
+this.PDU_SRI_SRR = 0x20; // Status report indication (SMS-DELIVER)
+ // or request (SMS-SUBMIT)
+this.PDU_VPF_ABSOLUTE = 0x18;// Validity period aboslute format
+ // (SMS-SUBMIT only)
+this.PDU_VPF_RELATIVE = 0x10;// Validity period relative format
+ // (SMS-SUBMIT only)
+this.PDU_VPF_ENHANCED = 0x8; // Validity period enhance format
+ // (SMS-SUBMIT only)
+this.PDU_MMS_RD = 0x04;// More messages to send. (SMS-DELIVER only) or
+ // Reject duplicates (SMS-SUBMIT only)
+
+// MTI - Message Type Indicator
+this.PDU_MTI_SMS_RESERVED = 0x03;
+this.PDU_MTI_SMS_STATUS_REPORT = 0x02;
+this.PDU_MTI_SMS_COMMAND = 0x02;
+this.PDU_MTI_SMS_SUBMIT = 0x01;
+this.PDU_MTI_SMS_DELIVER = 0x00;
+
+// PI - Parameter Indicator
+this.PDU_PI_EXTENSION = 0x80;
+this.PDU_PI_USER_DATA_LENGTH = 0x04;
+this.PDU_PI_DATA_CODING_SCHEME = 0x02;
+this.PDU_PI_PROTOCOL_IDENTIFIER = 0x01;
+this.PDU_PI_RESERVED = 0x78;
+
+// FCS - Failure Cause
+// 0...127 see 3GPP TS 24.011 clause E.2
+// 128...255 see 3GPP TS 23.040 clause 9.2.3.22
+// others see 3GPP TS 27.005 clause 3.2.5
+this.PDU_FCS_OK = 0x00;
+this.PDU_FCS_PROTOCOL_ERROR = 0x6F;
+this.PDU_FCS_MEMORY_CAPACITY_EXCEEDED = 0XD3;
+this.PDU_FCS_USAT_BUSY = 0XD4;
+this.PDU_FCS_USIM_DATA_DOWNLOAD_ERROR = 0xD5;
+this.PDU_FCS_RESERVED = 0xE0;
+this.PDU_FCS_UNSPECIFIED = 0xFF;
+// Special internal value that means we should not acknowledge an
+// incoming text right away, but need to wait for other components
+// (e.g. storage) to complete. This can be any value, so long it
+// doesn't conflict with the PDU_FCS_* constants above.
+this.MOZ_FCS_WAIT_FOR_EXPLICIT_ACK = 0x0F;
+
+// ST - Status
+// Bit 7..0 = 000xxxxx, short message transaction completed
+this.PDU_ST_0_RECEIVED = 0x00;
+this.PDU_ST_0_FORWARDED_NO_CONFIRM = 0x01;
+this.PDU_ST_0_REPLACED_BY_SC = 0x02;
+this.PDU_ST_0_RESERVED_BEGIN = 0x03;
+this.PDU_ST_0_SC_SPECIFIC_BEGIN = 0x10;
+this.PDU_ST_0_SC_SPECIFIC_END = 0x1F;
+// Bit 7..0 = 001xxxxx, temporary error, SC still trying to transfer SM
+this.PDU_ST_1_CONGESTION = 0x20;
+this.PDU_ST_1_SME_BUSY = 0x21;
+this.PDU_ST_1_SME_NO_RESPONSE = 0x22;
+this.PDU_ST_1_SERVICE_REJECTED = 0x23;
+this.PDU_ST_1_QOS_UNAVAILABLE = 0x24;
+this.PDU_ST_1_SME_ERROR = 0x25;
+this.PDU_ST_1_RESERVED_BEGIN = 0x26;
+this.PDU_ST_1_SC_SPECIFIC_BEGIN = 0x30;
+this.PDU_ST_1_SC_SPECIFIC_END = 0x3F;
+// Bit 7..0 = 010xxxxx, permanent error, SC is not making any more transfer
+// attempts
+this.PDU_ST_2_RPC_ERROR = 0x40;
+this.PDU_ST_2_DEST_INCOMPATIBLE = 0x41;
+this.PDU_ST_2_CONNECTION_REJECTED = 0x42;
+this.PDU_ST_2_NOT_OBTAINABLE = 0x43;
+this.PDU_ST_2_QOS_UNAVAILABLE = 0x44;
+this.PDU_ST_2_INTERWORKING_UNAVALIABLE = 0x45;
+this.PDU_ST_2_VALIDITY_EXPIRED = 0x46;
+this.PDU_ST_2_DELETED_BY_SME = 0x47;
+this.PDU_ST_2_DELETED_BY_SC = 0x48;
+this.PDU_ST_2_SM_MISSING = 0x49;
+this.PDU_ST_2_RESERVED_BEGIN = 0x4A;
+this.PDU_ST_2_SC_SPECIFIC_BEGIN = 0x50;
+this.PDU_ST_2_SC_SPECIFIC_END = 0x5F;
+// Bit 7..0 = 011xxxxx, temporary error, SC is not making any more transfer
+// attempts
+this.PDU_ST_3_CONGESTION = 0x60;
+this.PDU_ST_3_SME_BUSY = 0x61;
+this.PDU_ST_3_SME_NO_RESPONSE = 0x62;
+this.PDU_ST_3_SERVICE_REJECTED = 0x63;
+this.PDU_ST_3_QOS_UNAVAILABLE = 0x64;
+this.PDU_ST_3_SME_ERROR = 0x65;
+this.PDU_ST_3_RESERVED_BEGIN = 0x66;
+this.PDU_ST_3_SC_SPECIFIC_BEGIN = 0x70;
+this.PDU_ST_3_SC_SPECIFIC_END = 0x7F;
+
+this.GECKO_SMS_DELIVERY_STATUS_NOT_APPLICABLE = "not-applicable";
+this.GECKO_SMS_DELIVERY_STATUS_SUCCESS = "success";
+this.GECKO_SMS_DELIVERY_STATUS_PENDING = "pending";
+this.GECKO_SMS_DELIVERY_STATUS_ERROR = "error";
+
+// User Data max length in septets
+this.PDU_MAX_USER_DATA_7BIT = 160;
+// User Data max length in octets
+this.PDU_MAX_USER_DATA_8BIT = 140;
+// User Data max length in chars
+this.PDU_MAX_USER_DATA_UCS2 = 70;
+
+// PID - Protocol Indicator
+this.PDU_PID_DEFAULT = 0x00;
+this.PDU_PID_TELEMATIC_INTERWORKING = 0x20;
+this.PDU_PID_SHORT_MESSAGE_TYPE_0 = 0x40;
+this.PDU_PID_REPLACE_SHORT_MESSAGE_TYPE_1 = 0x41;
+this.PDU_PID_REPLACE_SHORT_MESSAGE_TYPE_2 = 0x42;
+this.PDU_PID_REPLACE_SHORT_MESSAGE_TYPE_3 = 0x43;
+this.PDU_PID_REPLACE_SHORT_MESSAGE_TYPE_4 = 0x44;
+this.PDU_PID_REPLACE_SHORT_MESSAGE_TYPE_5 = 0x45;
+this.PDU_PID_REPLACE_SHORT_MESSAGE_TYPE_6 = 0x46;
+this.PDU_PID_REPLACE_SHORT_MESSAGE_TYPE_7 = 0x47;
+this.PDU_PID_ENHANDED_MESSAGE_SERVICE = 0x5E;
+this.PDU_PID_RETURN_CALL_MESSAGE = 0x5F;
+this.PDU_PID_ANSI_136_R_DATA = 0x7C;
+this.PDU_PID_ME_DATA_DOWNLOAD = 0x7D;
+this.PDU_PID_ME_DEPERSONALIZATION = 0x7E;
+this.PDU_PID_USIM_DATA_DOWNLOAD = 0x7F;
+
+// DCS - Data Coding Scheme
+this.PDU_DCS_MSG_CODING_7BITS_ALPHABET = 0x00;
+this.PDU_DCS_MSG_CODING_8BITS_ALPHABET = 0x04;
+this.PDU_DCS_MSG_CODING_16BITS_ALPHABET = 0x08;
+this.PDU_DCS_MSG_CLASS_0 = 0x00;
+this.PDU_DCS_MSG_CLASS_1 = 0x01;
+this.PDU_DCS_MSG_CLASS_2 = 0x02;
+this.PDU_DCS_MSG_CLASS_3 = 0x03;
+this.PDU_DCS_MSG_CLASS_USER_1 = 0x04;
+this.PDU_DCS_MSG_CLASS_USER_2 = 0x05;
+this.PDU_DCS_MSG_CLASS_NORMAL = 0x06;
+this.PDU_DCS_CODING_GROUP_BITS = 0xF0;
+this.PDU_DCS_MSG_CLASS_BITS = 0x03;
+this.PDU_DCS_MWI_ACTIVE_BITS = 0x08;
+this.PDU_DCS_MWI_ACTIVE_VALUE = 0x08;
+this.PDU_DCS_MWI_TYPE_BITS = 0x03;
+this.PDU_DCS_MWI_TYPE_VOICEMAIL = 0x00;
+this.PDU_DCS_MWI_TYPE_FAX = 0x01;
+this.PDU_DCS_MWI_TYPE_EMAIL = 0x02;
+this.PDU_DCS_MWI_TYPE_OTHER = 0x03;
+
+// Set as Array instead of Object for reversed-mapping with Array.indexOf().
+this.GECKO_SMS_MESSAGE_CLASSES = [];
+GECKO_SMS_MESSAGE_CLASSES[PDU_DCS_MSG_CLASS_0] = "class-0";
+GECKO_SMS_MESSAGE_CLASSES[PDU_DCS_MSG_CLASS_1] = "class-1";
+GECKO_SMS_MESSAGE_CLASSES[PDU_DCS_MSG_CLASS_2] = "class-2";
+GECKO_SMS_MESSAGE_CLASSES[PDU_DCS_MSG_CLASS_3] = "class-3";
+GECKO_SMS_MESSAGE_CLASSES[PDU_DCS_MSG_CLASS_USER_1] = "user-1";
+GECKO_SMS_MESSAGE_CLASSES[PDU_DCS_MSG_CLASS_USER_2] = "user-2";
+GECKO_SMS_MESSAGE_CLASSES[PDU_DCS_MSG_CLASS_NORMAL] = "normal";
+
+// Because service center timestamp omit the century. Yay.
+this.PDU_TIMESTAMP_YEAR_OFFSET = 2000;
+
+// See 9.2.3.24 TP‑User Data (TP‑UD)
+this.PDU_IEI_CONCATENATED_SHORT_MESSAGES_8BIT = 0x00;
+this.PDU_IEI_SPECIAL_SMS_MESSAGE_INDICATION = 0x01;
+this.PDU_IEI_APPLICATION_PORT_ADDRESSING_SCHEME_8BIT = 0x04;
+this.PDU_IEI_APPLICATION_PORT_ADDRESSING_SCHEME_16BIT = 0x05;
+this.PDU_IEI_SMSC_CONTROL_PARAMS = 0x06;
+this.PDU_IEI_UDH_SOURCE_INDICATOR = 0x07;
+this.PDU_IEI_CONCATENATED_SHORT_MESSAGES_16BIT = 0x08;
+this.PDU_IEI_WIRELESS_CONTROL_MESSAGE_PROTOCOL = 0x09;
+this.PDU_IEI_TEXT_FORMATING = 0x0A;
+this.PDU_IEI_PREDEFINED_SOUND = 0x0B;
+this.PDU_IEI_USER_DATA_SOUND = 0x0C;
+this.PDU_IEI_PREDEFINED_ANIMATION = 0x0D;
+this.PDU_IEI_LARGE_ANIMATION = 0x0E;
+this.PDU_IEI_SMALL_ANIMATION = 0x0F;
+this.PDU_IEI_LARGE_PICTURE = 0x10;
+this.PDU_IEI_SMALL_PICTURE = 0x11;
+this.PDU_IEI_VARIABLE_PICTURE = 0x12;
+this.PDU_IEI_USER_PROMPT_INDICATOR = 0x13;
+this.PDU_IEI_EXTENDED_OBJECT = 0x14;
+this.PDU_IEI_REUSED_EXTENDED_OBJECT = 0x15;
+this.PDU_IEI_COMPRESS_CONTROL = 0x16;
+this.PDU_IEI_OBJECT_DISTRIBUTION_INDICATOR = 0x17;
+this.PDU_IEI_STANDARD_WVG_OBJECT = 0x18;
+this.PDU_IEI_CHARACTER_SIZE_WVG_OBJECT = 0x19;
+this.PDU_IEI_EXTENDED_OBJECT_DATA_REQUEST_COMMAND = 0x1A;
+this.PDU_IEI_RFC822_EMAIL_HEADER = 0x20;
+this.PDU_IEI_HYPERLINK_FORMAT_ELEMENT = 0x21;
+this.PDU_IEI_REPLY_ADDRESS_ELEMENT = 0x22;
+this.PDU_IEI_ENHANCED_VOICE_MAIL_INFORMATION = 0x23;
+this.PDU_IEI_NATIONAL_LANGUAGE_SINGLE_SHIFT = 0x24;
+this.PDU_IEI_NATIONAL_LANGUAGE_LOCKING_SHIFT = 0x25;
+
+// Application Port Addressing, see 3GPP TS 23.040 9.2.3.24.3
+this.PDU_APA_RESERVED_8BIT_PORTS = 240;
+this.PDU_APA_VALID_16BIT_PORTS = 49152;
+
+// 7bit alphabet escape character. The encoded value of this code point is left
+// undefined in official spec. Its code value is internally assigned to \uffff,
+// <noncharacter-FFFF> in Unicode basic multilingual plane.
+this.PDU_NL_EXTENDED_ESCAPE = 0x1B;
+
+// <SP>, <LF>, <CR> are only defined in locking shift tables.
+this.PDU_NL_SPACE = 0x20;
+this.PDU_NL_LINE_FEED = 0x0A;
+this.PDU_NL_CARRIAGE_RETURN = 0x0D;
+
+// 7bit alphabet page break character, only defined in single shift tables.
+// The encoded value of this code point is left undefined in official spec, but
+// the code point itself maybe be used for example in compressed CBS messages.
+// Its code value is internally assigned to \u000c, ASCII form feed, or new page.
+this.PDU_NL_PAGE_BREAK = 0x0A;
+// 7bit alphabet reserved control character, only defined in single shift
+// tables. The encoded value of this code point is left undefined in official
+// spec. Its code value is internally assigned to \ufffe, <noncharacter-FFFE>
+// in Unicode basic multilingual plane.
+this.PDU_NL_RESERVED_CONTROL = 0x0D;
+
+this.PDU_NL_IDENTIFIER_DEFAULT = 0;
+this.PDU_NL_IDENTIFIER_TURKISH = 1;
+this.PDU_NL_IDENTIFIER_SPANISH = 2;
+this.PDU_NL_IDENTIFIER_PORTUGUESE = 3;
+this.PDU_NL_IDENTIFIER_BENGALI = 4;
+this.PDU_NL_IDENTIFIER_GUJARATI = 5;
+this.PDU_NL_IDENTIFIER_HINDI = 6;
+this.PDU_NL_IDENTIFIER_KANNADA = 7;
+this.PDU_NL_IDENTIFIER_MALAYALAM = 8;
+this.PDU_NL_IDENTIFIER_ORIYA = 9;
+this.PDU_NL_IDENTIFIER_PUNJABI = 10;
+this.PDU_NL_IDENTIFIER_TAMIL = 11;
+this.PDU_NL_IDENTIFIER_TELUGU = 12;
+this.PDU_NL_IDENTIFIER_URDU = 13;
+
+// The mapping of mcc and their extra GSM national language locking / single
+// shift table tuples to enable. The default GSM alphabet and extension table
+// are always enabled and need not to be list here.
+//
+// The content should be updated when a relevant national regulatory body
+// requests. See 'NOTE 2' of 6.2.1.2.5 in 3GPP TS 23.038:
+// "
+// Encoding of a message using the national locking shift mechanism is not
+// intended to be implemented until a formal request is issued by the
+// relevant national regulatory body. This is because a receiving entity
+// not supporting the relevant locking-shift decoding will present different
+// characters from the ones intended by the sending entity.
+// "
+this.PDU_MCC_NL_TABLE_TUPLES_MAPPING = {
+ // Configuration for Turkey.
+ //
+ // The Turkish single shift table contains 7 extra characters
+ // (Ğ, İ, Ş, ç, ğ, ı, ş) than the GSM default alphabet extension table. Since
+ // all the 7 characters are also included in Turkish locking shift table, it's
+ // not necessary to enable Turkish single shift table. Using GSM default
+ // alphabet extension table instead saves 3 octets when these extension table
+ // characters present in a message.
+ 286: [[PDU_NL_IDENTIFIER_TURKISH, PDU_NL_IDENTIFIER_DEFAULT]]
+};
+
+/*
+ * 3GPP TS 23.038 - 6.2.1 GSM 7 bit Default Alphabet
+ */
+this.PDU_NL_GSM_DEFAULT_ALPHABET =
+ // 01.....23.....4.....5.....6.....7.....8.....9.....A.B.....C.....D.E.....F.....
+ "@\u00a3$\u00a5\u00e8\u00e9\u00f9\u00ec\u00f2\u00c7\n\u00d8\u00f8\r\u00c5\u00e5"
+ // 0.....12.....3.....4.....5.....6.....7.....8.....9.....A.....B.....C.....D.....E.....F.....
+ + "\u0394_\u03a6\u0393\u039b\u03a9\u03a0\u03a8\u03a3\u0398\u039e\uffff\u00c6\u00e6\u00df\u00c9"
+ // 012.34.....56789ABCDEF
+ + " !\"#\u00a4%&'()*+,-./"
+ // 0123456789ABCDEF
+ + "0123456789:;<=>?"
+ // 0.....123456789ABCDEF
+ + "\u00a1ABCDEFGHIJKLMNO"
+ // 0123456789AB.....C.....D.....E.....F.....
+ + "PQRSTUVWXYZ\u00c4\u00d6\u00d1\u00dc\u00a7"
+ // 0.....123456789ABCDEF
+ + "\u00bfabcdefghijklmno"
+ // 0123456789AB.....C.....D.....E.....F.....
+ + "pqrstuvwxyz\u00e4\u00f6\u00f1\u00fc\u00e0";
+
+// National Language Locking Shift Tables, see 3GPP TS 23.038
+this.PDU_NL_LOCKING_SHIFT_TABLES = [
+ /**
+ * National Language Identifier: 0x00
+ * 6.2.1 GSM 7 bit Default Alphabet
+ */
+ PDU_NL_GSM_DEFAULT_ALPHABET,
+
+ /**
+ * National Language Identifier: 0x01
+ * A.3.1 Turkish National Language Locking Shift Table
+ */
+ // 01.....23.....4.....5.....6.....7.....8.....9.....A.B.....C.....D.E.....F.....
+ "@\u00a3$\u00a5\u20ac\u00e9\u00f9\u0131\u00f2\u00c7\n\u011e\u011f\r\u00c5\u00e5"
+ // 0.....12.....3.....4.....5.....6.....7.....8.....9.....A.....B.....C.....D.....E.....F.....
+ + "\u0394_\u03a6\u0393\u039b\u03a9\u03a0\u03a8\u03a3\u0398\u039e\uffff\u015e\u015f\u00df\u00c9"
+ // 012.34.....56789ABCDEF
+ + " !\"#\u00a4%&'()*+,-./"
+ // 0123456789ABCDEF
+ + "0123456789:;<=>?"
+ // 0.....123456789ABCDEF
+ + "\u0130ABCDEFGHIJKLMNO"
+ // 0123456789AB.....C.....D.....E.....F.....
+ + "PQRSTUVWXYZ\u00c4\u00d6\u00d1\u00dc\u00a7"
+ // 0.....123456789ABCDEF
+ + "\u00e7abcdefghijklmno"
+ // 0123456789AB.....C.....D.....E.....F.....
+ + "pqrstuvwxyz\u00e4\u00f6\u00f1\u00fc\u00e0",
+
+ /**
+ * National Language Identifier: 0x02
+ * A.3.2 Void
+ * Fallback to GSM Default Alphabet
+ */
+ PDU_NL_GSM_DEFAULT_ALPHABET,
+
+ /**
+ * National Language Identifier: 0x03
+ * A.3.3 Portuguese National Language Locking Shift Table
+ */
+ // 01.....23.....4.....5.....6.....7.....8.....9.....A.B.....C.....D.E.....F.....
+ "@\u00a3$\u00a5\u00ea\u00e9\u00fa\u00ed\u00f3\u00e7\n\u00d4\u00f4\r\u00c1\u00e1"
+ // 0.....12.....3.....4.....5.....67.8.....9.....AB.....C.....D.....E.....F.....
+ + "\u0394_\u00aa\u00c7\u00c0\u221e^\\\u20ac\u00d3|\uffff\u00c2\u00e2\u00ca\u00c9"
+ // 012.34.....56789ABCDEF
+ + " !\"#\u00ba%&'()*+,-./"
+ // 0123456789ABCDEF
+ + "0123456789:;<=>?"
+ // 0.....123456789ABCDEF
+ + "\u00cdABCDEFGHIJKLMNO"
+ // 0123456789AB.....C.....D.....E.....F.....
+ + "PQRSTUVWXYZ\u00c3\u00d5\u00da\u00dc\u00a7"
+ // 0123456789ABCDEF
+ + "~abcdefghijklmno"
+ // 0123456789AB.....C.....DE.....F.....
+ + "pqrstuvwxyz\u00e3\u00f5`\u00fc\u00e0",
+
+ /**
+ * National Language Identifier: 0x04
+ * A.3.4 Bengali National Language Locking Shift Table
+ */
+ // 0.....1.....2.....3.....4.....5.....6.....7.....8.....9.....A.B.....CD.EF.....
+ "\u0981\u0982\u0983\u0985\u0986\u0987\u0988\u0989\u098a\u098b\n\u098c \r \u098f"
+ // 0.....123.....4.....5.....6.....7.....8.....9.....A.....B.....C.....D.....E.....F.....
+ + "\u0990 \u0993\u0994\u0995\u0996\u0997\u0998\u0999\u099a\uffff\u099b\u099c\u099d\u099e"
+ // 012.....3.....4.....5.....6.....7.....89A.....B.....CD.....EF.....
+ + " !\u099f\u09a0\u09a1\u09a2\u09a3\u09a4)(\u09a5\u09a6,\u09a7.\u09a8"
+ // 0123456789ABCD.....E.....F
+ + "0123456789:; \u09aa\u09ab?"
+ // 0.....1.....2.....3.....4.....56.....789A.....B.....C.....D.....E.....F.....
+ + "\u09ac\u09ad\u09ae\u09af\u09b0 \u09b2 \u09b6\u09b7\u09b8\u09b9\u09bc\u09bd"
+ // 0.....1.....2.....3.....4.....5.....6.....789.....A.....BCD.....E.....F.....
+ + "\u09be\u09bf\u09c0\u09c1\u09c2\u09c3\u09c4 \u09c7\u09c8 \u09cb\u09cc\u09cd"
+ // 0.....123456789ABCDEF
+ + "\u09ceabcdefghijklmno"
+ // 0123456789AB.....C.....D.....E.....F.....
+ + "pqrstuvwxyz\u09d7\u09dc\u09dd\u09f0\u09f1",
+
+ /**
+ * National Language Identifier: 0x05
+ * A.3.5 Gujarati National Language Locking Shift Table
+ */
+ // 0.....1.....2.....3.....4.....5.....6.....7.....8.....9.....A.B.....C.....D.EF.....
+ "\u0a81\u0a82\u0a83\u0a85\u0a86\u0a87\u0a88\u0a89\u0a8a\u0a8b\n\u0a8c\u0a8d\r \u0a8f"
+ // 0.....1.....23.....4.....5.....6.....7.....8.....9.....A.....B.....C.....D.....E.....F.....
+ + "\u0a90\u0a91 \u0a93\u0a94\u0a95\u0a96\u0a97\u0a98\u0a99\u0a9a\uffff\u0a9b\u0a9c\u0a9d\u0a9e"
+ // 012.....3.....4.....5.....6.....7.....89A.....B.....CD.....EF.....
+ + " !\u0a9f\u0aa0\u0aa1\u0aa2\u0aa3\u0aa4)(\u0aa5\u0aa6,\u0aa7.\u0aa8"
+ // 0123456789ABCD.....E.....F
+ + "0123456789:; \u0aaa\u0aab?"
+ // 0.....1.....2.....3.....4.....56.....7.....89.....A.....B.....C.....D.....E.....F.....
+ + "\u0aac\u0aad\u0aae\u0aaf\u0ab0 \u0ab2\u0ab3 \u0ab5\u0ab6\u0ab7\u0ab8\u0ab9\u0abc\u0abd"
+ // 0.....1.....2.....3.....4.....5.....6.....7.....89.....A.....B.....CD.....E.....F.....
+ + "\u0abe\u0abf\u0ac0\u0ac1\u0ac2\u0ac3\u0ac4\u0ac5 \u0ac7\u0ac8\u0ac9 \u0acb\u0acc\u0acd"
+ // 0.....123456789ABCDEF
+ + "\u0ad0abcdefghijklmno"
+ // 0123456789AB.....C.....D.....E.....F.....
+ + "pqrstuvwxyz\u0ae0\u0ae1\u0ae2\u0ae3\u0af1",
+
+ /**
+ * National Language Identifier: 0x06
+ * A.3.6 Hindi National Language Locking Shift Table
+ */
+ // 0.....1.....2.....3.....4.....5.....6.....7.....8.....9.....A.B.....C.....D.E.....F.....
+ "\u0901\u0902\u0903\u0905\u0906\u0907\u0908\u0909\u090a\u090b\n\u090c\u090d\r\u090e\u090f"
+ // 0.....1.....2.....3.....4.....5.....6.....7.....8.....9.....A.....B.....C.....D.....E.....F.....
+ + "\u0910\u0911\u0912\u0913\u0914\u0915\u0916\u0917\u0918\u0919\u091a\uffff\u091b\u091c\u091d\u091e"
+ // 012.....3.....4.....5.....6.....7.....89A.....B.....CD.....EF.....
+ + " !\u091f\u0920\u0921\u0922\u0923\u0924)(\u0925\u0926,\u0927.\u0928"
+ // 0123456789ABC.....D.....E.....F
+ + "0123456789:;\u0929\u092a\u092b?"
+ // 0.....1.....2.....3.....4.....5.....6.....7.....8.....9.....A.....B.....C.....D.....E.....F.....
+ + "\u092c\u092d\u092e\u092f\u0930\u0931\u0932\u0933\u0934\u0935\u0936\u0937\u0938\u0939\u093c\u093d"
+ // 0.....1.....2.....3.....4.....5.....6.....7.....8.....9.....A.....B.....C.....D.....E.....F.....
+ + "\u093e\u093f\u0940\u0941\u0942\u0943\u0944\u0945\u0946\u0947\u0948\u0949\u094a\u094b\u094c\u094d"
+ // 0.....123456789ABCDEF
+ + "\u0950abcdefghijklmno"
+ // 0123456789AB.....C.....D.....E.....F.....
+ + "pqrstuvwxyz\u0972\u097b\u097c\u097e\u097f",
+
+ /**
+ * National Language Identifier: 0x07
+ * A.3.7 Kannada National Language Locking Shift Table
+ */
+ // 01.....2.....3.....4.....5.....6.....7.....8.....9.....A.B.....CD.E.....F.....
+ " \u0c82\u0c83\u0c85\u0c86\u0c87\u0c88\u0c89\u0c8a\u0c8b\n\u0c8c \r\u0c8e\u0c8f"
+ // 0.....12.....3.....4.....5.....6.....7.....8.....9.....A.....B.....C.....D.....E.....F.....
+ + "\u0c90 \u0c92\u0c93\u0c94\u0c95\u0c96\u0c97\u0c98\u0c99\u0c9a\uffff\u0c9b\u0c9c\u0c9d\u0c9e"
+ // 012.....3.....4.....5.....6.....7.....89A.....B.....CD.....EF.....
+ + " !\u0c9f\u0ca0\u0ca1\u0ca2\u0ca3\u0ca4)(\u0ca5\u0ca6,\u0ca7.\u0ca8"
+ // 0123456789ABCD.....E.....F
+ + "0123456789:; \u0caa\u0cab?"
+ // 0.....1.....2.....3.....4.....5.....6.....7.....89.....A.....B.....C.....D.....E.....F.....
+ + "\u0cac\u0cad\u0cae\u0caf\u0cb0\u0cb1\u0cb2\u0cb3 \u0cb5\u0cb6\u0cb7\u0cb8\u0cb9\u0cbc\u0cbd"
+ // 0.....1.....2.....3.....4.....5.....6.....78.....9.....A.....BC.....D.....E.....F.....
+ + "\u0cbe\u0cbf\u0cc0\u0cc1\u0cc2\u0cc3\u0cc4 \u0cc6\u0cc7\u0cc8 \u0cca\u0ccb\u0ccc\u0ccd"
+ // 0.....123456789ABCDEF
+ + "\u0cd5abcdefghijklmno"
+ // 0123456789AB.....C.....D.....E.....F.....
+ + "pqrstuvwxyz\u0cd6\u0ce0\u0ce1\u0ce2\u0ce3",
+
+ /**
+ * National Language Identifier: 0x08
+ * A.3.8 Malayalam National Language Locking Shift Table
+ */
+ // 01.....2.....3.....4.....5.....6.....7.....8.....9.....A.B.....CD.E.....F.....
+ " \u0d02\u0d03\u0d05\u0d06\u0d07\u0d08\u0d09\u0d0a\u0d0b\n\u0d0c \r\u0d0e\u0d0f"
+ // 0.....12.....3.....4.....5.....6.....7.....8.....9.....A.....B.....C.....D.....E.....F.....
+ + "\u0d10 \u0d12\u0d13\u0d14\u0d15\u0d16\u0d17\u0d18\u0d19\u0d1a\uffff\u0d1b\u0d1c\u0d1d\u0d1e"
+ // 012.....3.....4.....5.....6.....7.....89A.....B.....CD.....EF.....
+ + " !\u0d1f\u0d20\u0d21\u0d22\u0d23\u0d24)(\u0d25\u0d26,\u0d27.\u0d28"
+ // 0123456789ABCD.....E.....F
+ + "0123456789:; \u0d2a\u0d2b?"
+ // 0.....1.....2.....3.....4.....5.....6.....7.....8.....9.....A.....B.....C.....D.....EF.....
+ + "\u0d2c\u0d2d\u0d2e\u0d2f\u0d30\u0d31\u0d32\u0d33\u0d34\u0d35\u0d36\u0d37\u0d38\u0d39 \u0d3d"
+ // 0.....1.....2.....3.....4.....5.....6.....78.....9.....A.....BC.....D.....E.....F.....
+ + "\u0d3e\u0d3f\u0d40\u0d41\u0d42\u0d43\u0d44 \u0d46\u0d47\u0d48 \u0d4a\u0d4b\u0d4c\u0d4d"
+ // 0.....123456789ABCDEF
+ + "\u0d57abcdefghijklmno"
+ // 0123456789AB.....C.....D.....E.....F.....
+ + "pqrstuvwxyz\u0d60\u0d61\u0d62\u0d63\u0d79",
+
+ /**
+ * National Language Identifier: 0x09
+ * A.3.9 Oriya National Language Locking Shift Table
+ */
+ // 0.....1.....2.....3.....4.....5.....6.....7.....8.....9.....A.B.....CD.EF.....
+ "\u0b01\u0b02\u0b03\u0b05\u0b06\u0b07\u0b08\u0b09\u0b0a\u0b0b\n\u0b0c \r \u0b0f"
+ // 0.....123.....4.....5.....6.....7.....8.....9.....A.....B.....C.....D.....E.....F.....
+ + "\u0b10 \u0b13\u0b14\u0b15\u0b16\u0b17\u0b18\u0b19\u0b1a\uffff\u0b1b\u0b1c\u0b1d\u0b1e"
+ // 012.....3.....4.....5.....6.....7.....89A.....B.....CD.....EF.....
+ + " !\u0b1f\u0b20\u0b21\u0b22\u0b23\u0b24)(\u0b25\u0b26,\u0b27.\u0b28"
+ // 0123456789ABCD.....E.....F
+ + "0123456789:; \u0b2a\u0b2b?"
+ // 0.....1.....2.....3.....4.....56.....7.....89.....A.....B.....C.....D.....E.....F.....
+ + "\u0b2c\u0b2d\u0b2e\u0b2f\u0b30 \u0b32\u0b33 \u0b35\u0b36\u0b37\u0b38\u0b39\u0b3c\u0b3d"
+ // 0.....1.....2.....3.....4.....5.....6.....789.....A.....BCD.....E.....F.....
+ + "\u0b3e\u0b3f\u0b40\u0b41\u0b42\u0b43\u0b44 \u0b47\u0b48 \u0b4b\u0b4c\u0b4d"
+ // 0.....123456789ABCDEF
+ + "\u0b56abcdefghijklmno"
+ // 0123456789AB.....C.....D.....E.....F.....
+ + "pqrstuvwxyz\u0b57\u0b60\u0b61\u0b62\u0b63",
+
+ /**
+ * National Language Identifier: 0x0A
+ * A.3.10 Punjabi National Language Locking Shift Table
+ */
+ // 0.....1.....2.....3.....4.....5.....6.....7.....8.....9A.BCD.EF.....
+ "\u0a01\u0a02\u0a03\u0a05\u0a06\u0a07\u0a08\u0a09\u0a0a \n \r \u0a0f"
+ // 0.....123.....4.....5.....6.....7.....8.....9.....A.....B.....C.....D.....E.....F.....
+ + "\u0a10 \u0a13\u0a14\u0a15\u0a16\u0a17\u0a18\u0a19\u0a1a\uffff\u0a1b\u0a1c\u0a1d\u0a1e"
+ // 012.....3.....4.....5.....6.....7.....89A.....B.....CD.....EF.....
+ + " !\u0a1f\u0a20\u0a21\u0a22\u0a23\u0a24)(\u0a25\u0a26,\u0a27.\u0a28"
+ // 0123456789ABCD.....E.....F
+ + "0123456789:; \u0a2a\u0a2b?"
+ // 0.....1.....2.....3.....4.....56.....7.....89.....A.....BC.....D.....E.....F
+ + "\u0a2c\u0a2d\u0a2e\u0a2f\u0a30 \u0a32\u0a33 \u0a35\u0a36 \u0a38\u0a39\u0a3c "
+ // 0.....1.....2.....3.....4.....56789.....A.....BCD.....E.....F.....
+ + "\u0a3e\u0a3f\u0a40\u0a41\u0a42 \u0a47\u0a48 \u0a4b\u0a4c\u0a4d"
+ // 0.....123456789ABCDEF
+ + "\u0a51abcdefghijklmno"
+ // 0123456789AB.....C.....D.....E.....F.....
+ + "pqrstuvwxyz\u0a70\u0a71\u0a72\u0a73\u0a74",
+
+ /**
+ * National Language Identifier: 0x0B
+ * A.3.11 Tamil National Language Locking Shift Table
+ */
+ // 01.....2.....3.....4.....5.....6.....7.....8.....9A.BCD.E.....F.....
+ " \u0b82\u0b83\u0b85\u0b86\u0b87\u0b88\u0b89\u0b8a \n \r\u0b8e\u0b8f"
+ // 0.....12.....3.....4.....5.....6789.....A.....B.....CD.....EF.....
+ + "\u0b90 \u0b92\u0b93\u0b94\u0b95 \u0b99\u0b9a\uffff \u0b9c \u0b9e"
+ // 012.....3456.....7.....89ABCDEF.....
+ + " !\u0b9f \u0ba3\u0ba4)( , .\u0ba8"
+ // 0123456789ABC.....D.....EF
+ + "0123456789:;\u0ba9\u0baa ?"
+ // 012.....3.....4.....5.....6.....7.....8.....9.....A.....B.....C.....D.....EF
+ + " \u0bae\u0baf\u0bb0\u0bb1\u0bb2\u0bb3\u0bb4\u0bb5\u0bb6\u0bb7\u0bb8\u0bb9 "
+ // 0.....1.....2.....3.....4.....5678.....9.....A.....BC.....D.....E.....F.....
+ + "\u0bbe\u0bbf\u0bc0\u0bc1\u0bc2 \u0bc6\u0bc7\u0bc8 \u0bca\u0bcb\u0bcc\u0bcd"
+ // 0.....123456789ABCDEF
+ + "\u0bd0abcdefghijklmno"
+ // 0123456789AB.....C.....D.....E.....F.....
+ + "pqrstuvwxyz\u0bd7\u0bf0\u0bf1\u0bf2\u0bf9",
+
+ /**
+ * National Language Identifier: 0x0C
+ * A.3.12 Telugu National Language Locking Shift Table
+ */
+ // 0.....1.....2.....3.....4.....5.....6.....7.....8.....9.....A.B.....CD.E.....F.....
+ "\u0c01\u0c02\u0c03\u0c05\u0c06\u0c07\u0c08\u0c09\u0c0a\u0c0b\n\u0c0c \r\u0c0e\u0c0f"
+ // 0.....12.....3.....4.....5.....6.....7.....8.....9.....A.....B.....C.....D.....E.....F.....
+ + "\u0c10 \u0c12\u0c13\u0c14\u0c15\u0c16\u0c17\u0c18\u0c19\u0c1a\uffff\u0c1b\u0c1c\u0c1d\u0c1e"
+ // 012.....3.....4.....5.....6.....7.....89A.....B.....CD.....EF.....
+ + " !\u0c1f\u0c20\u0c21\u0c22\u0c23\u0c24)(\u0c25\u0c26,\u0c27.\u0c28"
+ // 0123456789ABCD.....E.....F
+ + "0123456789:; \u0c2a\u0c2b?"
+ // 0.....1.....2.....3.....4.....5.....6.....7.....89.....A.....B.....C.....D.....EF.....
+ + "\u0c2c\u0c2d\u0c2e\u0c2f\u0c30\u0c31\u0c32\u0c33 \u0c35\u0c36\u0c37\u0c38\u0c39 \u0c3d"
+ // 0.....1.....2.....3.....4.....5.....6.....78.....9.....A.....BC.....D.....E.....F.....
+ + "\u0c3e\u0c3f\u0c40\u0c41\u0c42\u0c43\u0c44 \u0c46\u0c47\u0c48 \u0c4a\u0c4b\u0c4c\u0c4d"
+ // 0.....123456789ABCDEF
+ + "\u0c55abcdefghijklmno"
+ // 0123456789AB.....C.....D.....E.....F.....
+ + "pqrstuvwxyz\u0c56\u0c60\u0c61\u0c62\u0c63",
+
+ /**
+ * National Language Identifier: 0x0D
+ * A.3.13 Urdu National Language Locking Shift Table
+ */
+ // 0.....1.....2.....3.....4.....5.....6.....7.....8.....9.....A.B.....C.....D.E.....F.....
+ "\u0627\u0622\u0628\u067b\u0680\u067e\u06a6\u062a\u06c2\u067f\n\u0679\u067d\r\u067a\u067c"
+ // 0.....1.....2.....3.....4.....5.....6.....7.....8.....9.....A.....B.....C.....D.....E.....F.....
+ + "\u062b\u062c\u0681\u0684\u0683\u0685\u0686\u0687\u062d\u062e\u062f\uffff\u068c\u0688\u0689\u068a"
+ // 012.....3.....4.....5.....6.....7.....89A.....B.....CD.....EF.....
+ + " !\u068f\u068d\u0630\u0631\u0691\u0693)(\u0699\u0632,\u0696.\u0698"
+ // 0123456789ABC.....D.....E.....F
+ + "0123456789:;\u069a\u0633\u0634?"
+ // 0.....1.....2.....3.....4.....5.....6.....7.....8.....9.....A.....B.....C.....D.....E.....F.....
+ + "\u0635\u0636\u0637\u0638\u0639\u0641\u0642\u06a9\u06aa\u06ab\u06af\u06b3\u06b1\u0644\u0645\u0646"
+ // 0.....1.....2.....3.....4.....5.....6.....7.....8.....9.....A.....B.....C.....D.....E.....F.....
+ + "\u06ba\u06bb\u06bc\u0648\u06c4\u06d5\u06c1\u06be\u0621\u06cc\u06d0\u06d2\u064d\u0650\u064f\u0657"
+ // 0.....123456789ABCDEF
+ + "\u0654abcdefghijklmno"
+ // 0123456789AB.....C.....D.....E.....F.....
+ + "pqrstuvwxyz\u0655\u0651\u0653\u0656\u0670"
+];
+
+// National Language Single Shift Tables, see 3GPP TS 23.038
+this.PDU_NL_SINGLE_SHIFT_TABLES = [
+ /**
+ * National Language Identifier: 0x00
+ * 6.2.1.1 GSM 7 bit default alphabet extension table
+ */
+ // 0123456789A.....BCD.....EF
+ " \u000c \ufffe "
+ // 0123456789AB.....CDEF
+ + " ^ \uffff "
+ // 0123456789ABCDEF.
+ + " {} \\"
+ // 0123456789ABCDEF
+ + " [~] "
+ // 0123456789ABCDEF
+ + "| "
+ // 0123456789ABCDEF
+ + " "
+ // 012345.....6789ABCDEF
+ + " \u20ac "
+ // 0123456789ABCDEF
+ + " ",
+
+ /**
+ * National Language Identifier: 0x01
+ * A.2.1 Turkish National Language Single Shift Table
+ */
+ // 0123456789A.....BCD.....EF
+ " \u000c \ufffe "
+ // 0123456789AB.....CDEF
+ + " ^ \uffff "
+ // 0123456789ABCDEF.
+ + " {} \\"
+ // 0123456789ABCDEF
+ + " [~] "
+ // 01234567.....89.....ABCDEF
+ + "| \u011e \u0130 "
+ // 0123.....456789ABCDEF
+ + " \u015e "
+ // 0123.....45.....67.....89.....ABCDEF
+ + " \u00e7 \u20ac \u011f \u0131 "
+ // 0123.....456789ABCDEF
+ + " \u015f ",
+
+ /**
+ * National Language Identifier: 0x02
+ * A.2.2 Spanish National Language Single Shift Table
+ */
+ // 0123456789.....A.....BCD.....EF
+ " \u00e7\u000c \ufffe "
+ // 0123456789AB.....CDEF
+ + " ^ \uffff "
+ // 0123456789ABCDEF.
+ + " {} \\"
+ // 0123456789ABCDEF
+ + " [~] "
+ // 01.....23456789.....ABCDEF.....
+ + "|\u00c1 \u00cd \u00d3"
+ // 012345.....6789ABCDEF
+ + " \u00da "
+ // 01.....2345.....6789.....ABCDEF.....
+ + " \u00e1 \u20ac \u00ed \u00f3"
+ // 012345.....6789ABCDEF
+ + " \u00fa ",
+
+ /**
+ * National Language Identifier: 0x03
+ * A.2.3 Portuguese National Language Single Shift Table
+ */
+ // 012345.....6789.....A.....B.....C.....D.....E.....F.....
+ " \u00ea \u00e7\u000c\u00d4\u00f4\ufffe\u00c1\u00e1"
+ // 012.....3.....45.....6.....7.....8.....9.....AB.....CDEF.....
+ + " \u03a6\u0393^\u03a9\u03a0\u03a8\u03a3\u0398 \uffff \u00ca"
+ // 0123456789ABCDEF.
+ + " {} \\"
+ // 0123456789ABCDEF
+ + " [~] "
+ // 01.....23456789.....ABCDEF.....
+ + "|\u00c0 \u00cd \u00d3"
+ // 012345.....6789AB.....C.....DEF
+ + " \u00da \u00c3\u00d5 "
+ // 01.....2345.....6789.....ABCDEF.....
+ + " \u00c2 \u20ac \u00ed \u00f3"
+ // 012345.....6789AB.....C.....DEF.....
+ + " \u00fa \u00e3\u00f5 \u00e2",
+
+ /**
+ * National Language Identifier: 0x04
+ * A.2.4 Bengali National Language Single Shift Table
+ */
+ // 01.....23.....4.....5.6.....789A.....BCD.....EF
+ "@\u00a3$\u00a5\u00bf\"\u00a4%&'\u000c*+\ufffe-/"
+ // 0123.....45.....6789.....A.....B.....C.....D.....E.....F.....
+ + "<=>\u00a1^\u00a1_#*\u09e6\u09e7\uffff\u09e8\u09e9\u09ea\u09eb"
+ // 0.....1.....2.....3.....4.....5.....6.....7.....89A.....B.....C.....D.....E.....F.
+ + "\u09ec\u09ed\u09ee\u09ef\u09df\u09e0\u09e1\u09e2{}\u09e3\u09f2\u09f3\u09f4\u09f5\\"
+ // 0.....1.....2.....3.....4.....56789ABCDEF
+ + "\u09f6\u09f7\u09f8\u09f9\u09fa [~] "
+ // 0123456789ABCDEF
+ + "|ABCDEFGHIJKLMNO"
+ // 0123456789ABCDEF
+ + "PQRSTUVWXYZ "
+ // 012345.....6789ABCDEF
+ + " \u20ac "
+ // 0123456789ABCDEF
+ + " ",
+
+ /**
+ * National Language Identifier: 0x05
+ * A.2.5 Gujarati National Language Single Shift Table
+ */
+ // 01.....23.....4.....5.6.....789A.....BCD.....EF
+ "@\u00a3$\u00a5\u00bf\"\u00a4%&'\u000c*+\ufffe-/"
+ // 0123.....45.....6789.....A.....B.....C.....D.....E.....F.....
+ + "<=>\u00a1^\u00a1_#*\u0964\u0965\uffff\u0ae6\u0ae7\u0ae8\u0ae9"
+ // 0.....1.....2.....3.....4.....5.....6789ABCDEF.
+ + "\u0aea\u0aeb\u0aec\u0aed\u0aee\u0aef {} \\"
+ // 0123456789ABCDEF
+ + " [~] "
+ // 0123456789ABCDEF
+ + "|ABCDEFGHIJKLMNO"
+ // 0123456789ABCDEF
+ + "PQRSTUVWXYZ "
+ // 012345.....6789ABCDEF
+ + " \u20ac "
+ // 0123456789ABCDEF
+ + " ",
+
+ /**
+ * National Language Identifier: 0x06
+ * A.2.6 Hindi National Language Single Shift Table
+ */
+ // 01.....23.....4.....5.6.....789A.....BCD.....EF
+ "@\u00a3$\u00a5\u00bf\"\u00a4%&'\u000c*+\ufffe-/"
+ // 0123.....45.....6789.....A.....B.....C.....D.....E.....F.....
+ + "<=>\u00a1^\u00a1_#*\u0964\u0965\uffff\u0966\u0967\u0968\u0969"
+ // 0.....1.....2.....3.....4.....5.....6.....7.....89A.....B.....C.....D.....E.....F.
+ + "\u096a\u096b\u096c\u096d\u096e\u096f\u0951\u0952{}\u0953\u0954\u0958\u0959\u095a\\"
+ // 0.....1.....2.....3.....4.....5.....6.....7.....8.....9.....A.....BCDEF
+ + "\u095b\u095c\u095d\u095e\u095f\u0960\u0961\u0962\u0963\u0970\u0971 [~] "
+ // 0123456789ABCDEF
+ + "|ABCDEFGHIJKLMNO"
+ // 0123456789ABCDEF
+ + "PQRSTUVWXYZ "
+ // 012345.....6789ABCDEF
+ + " \u20ac "
+ // 0123456789ABCDEF
+ + " ",
+
+ /**
+ * National Language Identifier: 0x07
+ * A.2.7 Kannada National Language Single Shift Table
+ */
+ // 01.....23.....4.....5.6.....789A.....BCD.....EF
+ "@\u00a3$\u00a5\u00bf\"\u00a4%&'\u000c*+\ufffe-/"
+ // 0123.....45.....6789.....A.....B.....C.....D.....E.....F.....
+ + "<=>\u00a1^\u00a1_#*\u0964\u0965\uffff\u0ce6\u0ce7\u0ce8\u0ce9"
+ // 0.....1.....2.....3.....4.....5.....6.....7.....89A.....BCDEF.
+ + "\u0cea\u0ceb\u0cec\u0ced\u0cee\u0cef\u0cde\u0cf1{}\u0cf2 \\"
+ // 0123456789ABCDEF
+ + " [~] "
+ // 0123456789ABCDEF
+ + "|ABCDEFGHIJKLMNO"
+ // 0123456789ABCDEF
+ + "PQRSTUVWXYZ "
+ // 012345.....6789ABCDEF
+ + " \u20ac "
+ // 0123456789ABCDEF
+ + " ",
+
+ /**
+ * National Language Identifier: 0x08
+ * A.2.8 Malayalam National Language Single Shift Table
+ */
+ // 01.....23.....4.....5.6.....789A.....BCD.....EF
+ "@\u00a3$\u00a5\u00bf\"\u00a4%&'\u000c*+\ufffe-/"
+ // 0123.....45.....6789.....A.....B.....C.....D.....E.....F.....
+ + "<=>\u00a1^\u00a1_#*\u0964\u0965\uffff\u0d66\u0d67\u0d68\u0d69"
+ // 0.....1.....2.....3.....4.....5.....6.....7.....89A.....B.....C.....D.....E.....F.
+ + "\u0d6a\u0d6b\u0d6c\u0d6d\u0d6e\u0d6f\u0d70\u0d71{}\u0d72\u0d73\u0d74\u0d75\u0d7a\\"
+ // 0.....1.....2.....3.....4.....56789ABCDEF
+ + "\u0d7b\u0d7c\u0d7d\u0d7e\u0d7f [~] "
+ // 0123456789ABCDEF
+ + "|ABCDEFGHIJKLMNO"
+ // 0123456789ABCDEF
+ + "PQRSTUVWXYZ "
+ // 012345.....6789ABCDEF
+ + " \u20ac "
+ // 0123456789ABCDEF
+ + " ",
+
+ /**
+ * National Language Identifier: 0x09
+ * A.2.9 Oriya National Language Single Shift Table
+ */
+ // 01.....23.....4.....5.6.....789A.....BCD.....EF
+ "@\u00a3$\u00a5\u00bf\"\u00a4%&'\u000c*+\ufffe-/"
+ // 0123.....45.....6789.....A.....B.....C.....D.....E.....F.....
+ + "<=>\u00a1^\u00a1_#*\u0964\u0965\uffff\u0b66\u0b67\u0b68\u0b69"
+ // 0.....1.....2.....3.....4.....5.....6.....7.....89A.....B.....C.....DEF.
+ + "\u0b6a\u0b6b\u0b6c\u0b6d\u0b6e\u0b6f\u0b5c\u0b5d{}\u0b5f\u0b70\u0b71 \\"
+ // 0123456789ABCDEF
+ + " [~] "
+ // 0123456789ABCDEF
+ + "|ABCDEFGHIJKLMNO"
+ // 0123456789ABCDEF
+ + "PQRSTUVWXYZ "
+ // 012345.....6789ABCDEF
+ + " \u20ac "
+ // 0123456789ABCDEF
+ + " ",
+
+ /**
+ * National Language Identifier: 0x0A
+ * A.2.10 Punjabi National Language Single Shift Table
+ */
+ // 01.....23.....4.....5.6.....789A.....BCD.....EF
+ "@\u00a3$\u00a5\u00bf\"\u00a4%&'\u000c*+\ufffe-/"
+ // 0123.....45.....6789.....A.....B.....C.....D.....E.....F.....
+ + "<=>\u00a1^\u00a1_#*\u0964\u0965\uffff\u0a66\u0a67\u0a68\u0a69"
+ // 0.....1.....2.....3.....4.....5.....6.....7.....89A.....B.....C.....D.....EF.
+ + "\u0a6a\u0a6b\u0a6c\u0a6d\u0a6e\u0a6f\u0a59\u0a5a{}\u0a5b\u0a5c\u0a5e\u0a75 \\"
+ // 0123456789ABCDEF
+ + " [~] "
+ // 0123456789ABCDEF
+ + "|ABCDEFGHIJKLMNO"
+ // 0123456789ABCDEF
+ + "PQRSTUVWXYZ "
+ // 012345.....6789ABCDEF
+ + " \u20ac "
+ // 0123456789ABCDEF
+ + " ",
+
+ /**
+ * National Language Identifier: 0x0B
+ * A.2.11 Tamil National Language Single Shift Table
+ */
+ // 01.....23.....4.....5.6.....789A.....BCD.....EF
+ "@\u00a3$\u00a5\u00bf\"\u00a4%&'\u000c*+\ufffe-/"
+ // 0123.....45.....6789.....A.....B.....C.....D.....E.....F.....
+ + "<=>\u00a1^\u00a1_#*\u0964\u0965\uffff\u0be6\u0be7\u0be8\u0be9"
+ // 0.....1.....2.....3.....4.....5.....6.....7.....89A.....B.....C.....D.....E.....F.
+ + "\u0bea\u0beb\u0bec\u0bed\u0bee\u0bef\u0bf3\u0bf4{}\u0bf5\u0bf6\u0bf7\u0bf8\u0bfa\\"
+ // 0123456789ABCDEF
+ + " [~] "
+ // 0123456789ABCDEF
+ + "|ABCDEFGHIJKLMNO"
+ // 0123456789ABCDEF
+ + "PQRSTUVWXYZ "
+ // 012345.....6789ABCDEF
+ + " \u20ac "
+ // 0123456789ABCDEF
+ + " ",
+
+ /**
+ * National Language Identifier: 0x0C
+ * A.2.12 Telugu National Language Single Shift Table
+ */
+ // 01.....23.....4.....5.6.....789A.....BCD.....EF
+ "@\u00a3$\u00a5\u00bf\"\u00a4%&'\u000c*+\ufffe-/"
+ // 0123.....45.....6789AB.....C.....D.....E.....F.....
+ + "<=>\u00a1^\u00a1_#* \uffff\u0c66\u0c67\u0c68\u0c69"
+ // 0.....1.....2.....3.....4.....5.....6.....7.....89A.....B.....C.....D.....E.....F.
+ + "\u0c6a\u0c6b\u0c6c\u0c6d\u0c6e\u0c6f\u0c58\u0c59{}\u0c78\u0c79\u0c7a\u0c7b\u0c7c\\"
+ // 0.....1.....2.....3456789ABCDEF
+ + "\u0c7d\u0c7e\u0c7f [~] "
+ // 0123456789ABCDEF
+ + "|ABCDEFGHIJKLMNO"
+ // 0123456789ABCDEF
+ + "PQRSTUVWXYZ "
+ // 012345.....6789ABCDEF
+ + " \u20ac "
+ // 0123456789ABCDEF
+ + " ",
+
+ /**
+ * National Language Identifier: 0x0D
+ * A.2.13 Urdu National Language Single Shift Table
+ */
+ // 01.....23.....4.....5.6.....789A.....BCD.....EF
+ "@\u00a3$\u00a5\u00bf\"\u00a4%&'\u000c*+\ufffe-/"
+ // 0123.....45.....6789.....A.....B.....C.....D.....E.....F.....
+ + "<=>\u00a1^\u00a1_#*\u0600\u0601\uffff\u06f0\u06f1\u06f2\u06f3"
+ // 0.....1.....2.....3.....4.....5.....6.....7.....89A.....B.....C.....D.....E.....F.
+ + "\u06f4\u06f5\u06f6\u06f7\u06f8\u06f9\u060c\u060d{}\u060e\u060f\u0610\u0611\u0612\\"
+ // 0.....1.....2.....3.....4.....5.....6.....7.....8.....9.....A.....B.....CDEF.....
+ + "\u0613\u0614\u061b\u061f\u0640\u0652\u0658\u066b\u066c\u0672\u0673\u06cd[~]\u06d4"
+ // 0123456789ABCDEF
+ + "|ABCDEFGHIJKLMNO"
+ // 0123456789ABCDEF
+ + "PQRSTUVWXYZ "
+ // 012345.....6789ABCDEF
+ + " \u20ac "
+ // 0123456789ABCDEF
+ + " "
+];
+
+// Special SMS Message Indication constants
+this.PDU_MWI_STORE_TYPE_BIT = 0x80;
+this.PDU_MWI_STORE_TYPE_DISCARD = 0x00;
+this.PDU_MWI_STORE_TYPE_STORE = 0x80;
+
+this.GSM_SMS_STRICT_7BIT_CHARMAP = {
+//"\u0024": "\u0024", // "$" => "$", already in default alphabet
+//"\u00a5": "\u00a5", // "¥" => "¥", already in default alphabet
+ "\u00c0": "\u0041", // "À" => "A"
+ "\u00c1": "\u0041", // "Á" => "A"
+ "\u00c2": "\u0041", // "Â" => "A"
+ "\u00c3": "\u0041", // "Ã" => "A"
+//"\u00c4": "\u00c4", // "Ä" => "Ä", already in default alphabet
+//"\u00c5": "\u00c5", // "Å" => "Å", already in default alphabet
+//"\u00c6": "\u00c6", // "Æ" => "Æ", already in default alphabet
+//"\u00c7": "\u00c7", // "Ç" => "Ç", already in default alphabet
+ "\u00c8": "\u0045", // "È" => "E"
+//"\u00c9": "\u00c9", // "É" => "É", already in default alphabet
+ "\u00ca": "\u0045", // "Ê" => "E"
+ "\u00cb": "\u0045", // "Ë" => "E"
+ "\u00cc": "\u0049", // "Ì" => "I"
+ "\u00cd": "\u0049", // "Í" => "I"
+ "\u00ce": "\u0049", // "Î" => "I"
+ "\u00cf": "\u0049", // "Ï" => "I"
+//"\u00d1": "\u00d1", // "Ñ" => "Ñ", already in default alphabet
+ "\u00d2": "\u004f", // "Ò" => "O"
+ "\u00d3": "\u004f", // "Ó" => "O"
+ "\u00d4": "\u004f", // "Ô" => "O"
+ "\u00d5": "\u004f", // "Õ" => "O"
+//"\u00d6": "\u00d6", // "Ö" => "Ö", already in default alphabet
+ "\u00d9": "\u0055", // "Ù" => "U"
+ "\u00da": "\u0055", // "Ú" => "U"
+ "\u00db": "\u0055", // "Û" => "U"
+//"\u00dc": "\u00dc", // "Ü" => "Ü", already in default alphabet
+//"\u00df": "\u00df", // "ß" => "ß", already in default alphabet
+//"\u00e0": "\u00e0", // "à" => "à", already in default alphabet
+ "\u00e1": "\u0061", // "á" => "a"
+ "\u00e2": "\u0061", // "â" => "a"
+ "\u00e3": "\u0061", // "ã" => "a"
+//"\u00e4": "\u00e4", // "ä" => "ä", already in default alphabet
+//"\u00e5": "\u00e5", // "å" => "å", already in default alphabet
+//"\u00e6": "\u00e6", // "æ" => "æ", already in default alphabet
+ "\u00e7": "\u00c7", // "ç" => "Ç"
+//"\u00e8": "\u00e8", // "è" => "è", already in default alphabet
+//"\u00e9": "\u00e9", // "é" => "é", already in default alphabet
+ "\u00ea": "\u0065", // "ê" => "e"
+ "\u00eb": "\u0065", // "ë" => "e"
+//"\u00ec": "\u00ec", // "ì" => "ì", already in default alphabet
+ "\u00ed": "\u0069", // "í" => "i"
+ "\u00ee": "\u0069", // "î" => "i"
+ "\u00ef": "\u0069", // "ï" => "i"
+//"\u00f1": "\u00f1", // "ñ" => "ñ", already in default alphabet
+//"\u00f2": "\u00f2", // "ò" => "ò", already in default alphabet
+ "\u00f3": "\u006f", // "ó" => "o"
+ "\u00f4": "\u006f", // "ô" => "o"
+ "\u00f5": "\u006f", // "õ" => "o"
+//"\u00f6": "\u00f6", // "ö" => "ö", already in default alphabet
+//"\u00f8": "\u00f8", // "ø" => "ø", already in default alphabet
+//"\u00f9": "\u00f9", // "ù" => "ù", already in default alphabet
+ "\u00fa": "\u0075", // "ú" => "u"
+ "\u00fb": "\u0075", // "û" => "u"
+//"\u00fc": "\u00fc", // "ü" => "ü", already in default alphabet
+ "\u00fe": "\u0074", // "þ" => "t"
+ "\u0100": "\u0041", // "Ā" => "A"
+ "\u0101": "\u0061", // "ā" => "a"
+ "\u0106": "\u0043", // "Ć" => "C"
+ "\u0107": "\u0063", // "ć" => "c"
+ "\u010c": "\u0043", // "Č" => "C"
+ "\u010d": "\u0063", // "č" => "c"
+ "\u010f": "\u0064", // "ď" => "d"
+ "\u0110": "\u0044", // "Đ" => "D"
+ "\u0111": "\u0064", // "đ" => "d"
+ "\u0112": "\u0045", // "Ē" => "E"
+ "\u0113": "\u0065", // "ē" => "e"
+ "\u0118": "\u0045", // "Ę" => "E"
+ "\u0119": "\u0065", // "ę" => "e"
+ "\u0128": "\u0049", // "Ĩ" => "I"
+ "\u0129": "\u0069", // "ĩ" => "i"
+ "\u012a": "\u0049", // "Ī" => "I"
+ "\u012b": "\u0069", // "ī" => "i"
+ "\u012e": "\u0049", // "Į" => "I"
+ "\u012f": "\u0069", // "į" => "i"
+ "\u0141": "\u004c", // "Ł" => "L"
+ "\u0142": "\u006c", // "ł" => "l"
+ "\u0143": "\u004e", // "Ń" => "N"
+ "\u0144": "\u006e", // "ń" => "n"
+ "\u0147": "\u004e", // "Ň" => "N"
+ "\u0148": "\u006e", // "ň" => "n"
+ "\u014c": "\u004f", // "Ō" => "O"
+ "\u014d": "\u006f", // "ō" => "o"
+ "\u0152": "\u004f", // "Œ" => "O"
+ "\u0153": "\u006f", // "œ" => "o"
+ "\u0158": "\u0052", // "Ř" => "R"
+ "\u0159": "\u0072", // "ř" => "r"
+ "\u0160": "\u0053", // "Š" => "S"
+ "\u0161": "\u0073", // "š" => "s"
+ "\u0165": "\u0074", // "ť" => "t"
+ "\u0168": "\u0055", // "Ū" => "U"
+ "\u0169": "\u0075", // "ū" => "u"
+ "\u016a": "\u0055", // "Ū" => "U"
+ "\u016b": "\u0075", // "ū" => "u"
+ "\u0178": "\u0059", // "Ÿ" => "Y"
+ "\u0179": "\u005a", // "Ź" => "Z"
+ "\u017a": "\u007a", // "ź" => "z"
+ "\u017b": "\u005a", // "Ż" => "Z"
+ "\u017c": "\u007a", // "ż" => "z"
+ "\u017d": "\u005a", // "Ž" => "Z"
+ "\u017e": "\u007a", // "ž" => "z"
+ "\u025b": "\u0045", // "ɛ" => "E"
+//"\u0398": "\u0398", // "Θ" => "Θ", already in default alphabet
+ "\u1e7c": "\u0056", // "Ṽ" => "V"
+ "\u1e7d": "\u0076", // "ṽ" => "v"
+ "\u1ebc": "\u0045", // "Ẽ" => "E"
+ "\u1ebd": "\u0065", // "ẽ" => "e"
+ "\u1ef8": "\u0059", // "Ỹ" => "Y"
+ "\u1ef9": "\u0079", // "ỹ" => "y"
+ "\u20a4": "\u00a3", // "₤" => "£"
+//"\u20ac": "\u20ac", // "€" => "€", already in default alphabet
+};
+
+this.RADIOTECH_FAMILY_3GPP = 1; // GSM, WCDMA, LTE
+this.RADIOTECH_FAMILY_3GPP2 = 2; // CDMA, EVDO
+
+this.DATACALL_RADIOTECHNOLOGY_CDMA = 0;
+this.DATACALL_RADIOTECHNOLOGY_GSM = 1;
+
+this.DATACALL_AUTH_NONE = 0;
+this.DATACALL_AUTH_PAP = 1;
+this.DATACALL_AUTH_CHAP = 2;
+this.DATACALL_AUTH_PAP_OR_CHAP = 3;
+
+this.GECKO_DATACALL_AUTH_NONE = "none";
+this.GECKO_DATACALL_AUTH_PAP = "pap";
+this.GECKO_DATACALL_AUTH_CHAP = "chap";
+this.GECKO_DATACALL_AUTH_PAP_OR_CHAP = "papOrChap";
+this.GECKO_DATACALL_AUTH_DEFAULT = GECKO_DATACALL_AUTH_PAP_OR_CHAP;
+this.RIL_DATACALL_AUTH_TO_GECKO = [
+ GECKO_DATACALL_AUTH_NONE, // DATACALL_AUTH_NONE
+ GECKO_DATACALL_AUTH_PAP, // DATACALL_AUTH_PAP
+ GECKO_DATACALL_AUTH_CHAP, // DATACALL_AUTH_CHAP
+ GECKO_DATACALL_AUTH_PAP_OR_CHAP // DATACALL_AUTH_PAP_OR_CHAP
+];
+
+this.GECKO_DATACALL_PDP_TYPE_IP = "IP";
+this.GECKO_DATACALL_PDP_TYPE_IPV4V6 = "IPV4V6";
+this.GECKO_DATACALL_PDP_TYPE_IPV6 = "IPV6";
+this.GECKO_DATACALL_PDP_TYPE_DEFAULT = GECKO_DATACALL_PDP_TYPE_IP;
+this.RIL_DATACALL_PDP_TYPES = [
+ GECKO_DATACALL_PDP_TYPE_IP,
+ GECKO_DATACALL_PDP_TYPE_IPV4V6,
+ GECKO_DATACALL_PDP_TYPE_IPV6,
+];
+
+this.DATACALL_PROFILE_DEFAULT = 0;
+this.DATACALL_PROFILE_TETHERED = 1;
+this.DATACALL_PROFILE_OEM_BASE = 1000;
+
+this.DATACALL_DEACTIVATE_NO_REASON = 0;
+this.DATACALL_DEACTIVATE_RADIO_SHUTDOWN = 1;
+
+this.DATACALL_ACTIVE_UNKNOWN = -1;
+this.DATACALL_INACTIVE = 0;
+this.DATACALL_ACTIVE_DOWN = 1;
+this.DATACALL_ACTIVE_UP = 2;
+
+this.DATACALL_FAIL_NONE = 0;
+this.DATACALL_FAIL_OPERATOR_BARRED = 0x08;
+this.DATACALL_FAIL_INSUFFICIENT_RESOURCES = 0x1A;
+this.DATACALL_FAIL_MISSING_UKNOWN_APN = 0x1B;
+this.DATACALL_FAIL_UNKNOWN_PDP_ADDRESS_TYPE = 0x1C;
+this.DATACALL_FAIL_USER_AUTHENTICATION = 0x1D;
+this.DATACALL_FAIL_ACTIVATION_REJECT_GGSN = 0x1E;
+this.DATACALL_FAIL_ACTIVATION_REJECT_UNSPECIFIED = 0x1F;
+this.DATACALL_FAIL_SERVICE_OPTION_NOT_SUPPORTED = 0x20;
+this.DATACALL_FAIL_SERVICE_OPTION_NOT_SUBSCRIBED = 0x21;
+this.DATACALL_FAIL_SERVICE_OPTION_OUT_OF_ORDER = 0x22;
+this.DATACALL_FAIL_NSAPI_IN_USE = 0x23;
+this.DATACALL_FAIL_ONLY_IPV4_ALLOWED = 0x32;
+this.DATACALL_FAIL_ONLY_IPV6_ALLOWED = 0x33;
+this.DATACALL_FAIL_ONLY_SINGLE_BEARER_ALLOWED = 0x34;
+this.DATACALL_FAIL_PROTOCOL_ERRORS = 0x6F;
+this.DATACALL_FAIL_VOICE_REGISTRATION_FAIL = -1;
+this.DATACALL_FAIL_DATA_REGISTRATION_FAIL = -2;
+this.DATACALL_FAIL_SIGNAL_LOST = -3;
+this.DATACALL_FAIL_PREF_RADIO_TECH_CHANGED = -4;
+this.DATACALL_FAIL_RADIO_POWER_OFF = -5;
+this.DATACALL_FAIL_TETHERED_CALL_ACTIVE = -6;
+this.DATACALL_FAIL_ERROR_UNSPECIFIED = 0xffff;
+
+// Keep consistent with nsINetworkManager.NETWORK_STATE_*.
+this.GECKO_NETWORK_STATE_UNKNOWN = -1;
+this.GECKO_NETWORK_STATE_CONNECTING = 0;
+this.GECKO_NETWORK_STATE_CONNECTED = 1;
+this.GECKO_NETWORK_STATE_DISCONNECTING = 2;
+this.GECKO_NETWORK_STATE_DISCONNECTED = 3;
+
+// 3GPP 24.008 Annex H.
+this.CALL_FAIL_UNOBTAINABLE_NUMBER = 1;
+this.CALL_FAIL_NO_ROUTE_TO_DESTINATION = 3;
+this.CALL_FAIL_CHANNEL_UNACCEPTABLE = 6;
+this.CALL_FAIL_OPERATOR_DETERMINED_BARRING = 8;
+this.CALL_FAIL_NORMAL = 16;
+this.CALL_FAIL_BUSY = 17;
+this.CALL_FAIL_NO_USER_RESPONDING = 18;
+this.CALL_FAIL_USER_ALERTING = 19;
+this.CALL_FAIL_CALL_REJECTED = 21;
+this.CALL_FAIL_NUMBER_CHANGED = 22;
+this.CALL_FAIL_CALL_REJECTED_DESTINATION_FEATURE = 24;
+this.CALL_FAIL_CALL_PRE_EMPTION = 25;
+this.CALL_FAIL_DEST_OUT_OF_ORDER = 27;
+this.CALL_FAIL_INVALID_FORMAT = 28;
+this.CALL_FAIL_FACILITY_REJECTED = 29;
+this.CALL_FAIL_RESPONSE_TO_STATUS_ENQUIRY = 30;
+this.CALL_FAIL_NORMAL_UNSPECIFIED = 31;
+this.CALL_FAIL_CONGESTION = 34;
+this.CALL_FAIL_NETWORK_OUT_OF_ORDER = 38;
+this.CALL_FAIL_NETWORK_TEMP_FAILURE = 41;
+this.CALL_FAIL_SWITCHING_EQUIP_CONGESTION = 42;
+this.CALL_FAIL_ACCESS_INFO_DISCARDED = 43;
+this.CALL_FAIL_REQUESTED_CHANNEL_NOT_AVAILABLE = 44;
+this.CALL_FAIL_RESOURCE_UNAVAILABLE = 47;
+this.CALL_FAIL_QOS_UNAVAILABLE = 49;
+this.CALL_FAIL_REQUESTED_FACILITY_NOT_SUBSCRIBED = 50;
+this.CALL_FAIL_INCOMING_CALLS_BARRED_WITHIN_CUG = 55;
+this.CALL_FAIL_BEARER_CAPABILITY_NOT_AUTHORIZED = 57;
+this.CALL_FAIL_BEARER_CAPABILITY_NOT_AVAILABLE = 58;
+this.CALL_FAIL_SERVICE_NOT_AVAILABLE = 63;
+this.CALL_FAIL_BEARER_NOT_IMPLEMENTED = 65;
+this.CALL_FAIL_ACM_LIMIT_EXCEEDED = 68;
+this.CALL_FAIL_REQUESTED_FACILITY_NOT_IMPLEMENTED = 69;
+this.CALL_FAIL_UNRESTRICTED_BEARER_NOT_AVAILABLE = 70;
+this.CALL_FAIL_SERVICE_NOT_IMPLEMENTED = 79;
+this.CALL_FAIL_INVALID_TRANSACTION_ID = 81;
+this.CALL_FAIL_USER_NOT_CUG_MEMBER = 87;
+this.CALL_FAIL_INCOMPATIBLE_DESTINATION = 88;
+this.CALL_FAIL_INVALID_TRANSIT_NETWORK_SELECTION = 91;
+this.CALL_FAIL_SEMANTICALLY_INCORRECT_MESSAGE = 95;
+this.CALL_FAIL_INVALID_MANDATORY_INFO = 96;
+this.CALL_FAIL_MESSAGE_TYPE_NOT_IMPLEMENTED = 97;
+this.CALL_FAIL_MESSAGE_TYPE_INCOMPATIBLE_PROTOCOL_STATE = 98;
+this.CALL_FAIL_INFO_ELEMENT_NOT_IMPLEMENTED = 99;
+this.CALL_FAIL_CONDITIONAL_IE_ERROR = 100;
+this.CALL_FAIL_MESSAGE_INCOMPABITLE_PROTOCOL_STATE = 101;
+this.CALL_FAIL_RECOVERY_ON_TIMER_EXPIRY = 102;
+this.CALL_FAIL_PROTOCOL_ERROR = 111;
+this.CALL_FAIL_INTERWORKING = 127;
+// AOSP ril.h
+this.CALL_FAIL_CALL_BARRED = 240;
+this.CALL_FAIL_FDN_BLOCKED = 241;
+this.CALL_FAIL_IMSI_UNKNOWN_IN_VLR = 242;
+this.CALL_FAIL_IMEI_NOT_ACCEPTED = 243;
+this.CALL_FAIL_DIAL_MODIFIED_TO_USSD = 244; // STK Call Control
+this.CALL_FAIL_DIAL_MODIFIED_TO_SS = 245;
+this.CALL_FAIL_DIAL_MODIFIED_TO_DIAL = 246;
+this.CALL_FAIL_CDMA_LOCKED_UNTIL_POWER_CYCLE = 1000;
+this.CALL_FAIL_CDMA_DROP = 1001;
+this.CALL_FAIL_CDMA_INTERCEPT = 1002;
+this.CALL_FAIL_CDMA_REORDER = 1003;
+this.CALL_FAIL_CDMA_SO_REJECT = 1004;
+this.CALL_FAIL_CDMA_RETRY_ORDER = 1005;
+this.CALL_FAIL_CDMA_ACCESS_FAILURE = 1006;
+this.CALL_FAIL_CDMA_PREEMPTED = 1007;
+this.CALL_FAIL_CDMA_NOT_EMERGENCY = 1008; // For non-emergency number dialed
+ // during emergency callback mode
+this.CALL_FAIL_CDMA_ACCESS_BLOCKED = 1009;
+this.CALL_FAIL_ERROR_UNSPECIFIED = 0xffff;
+
+// See nsIMobileConnection::MOBILE_RADIO_STATE_*
+this.GECKO_RADIOSTATE_UNKNOWN = -1;
+this.GECKO_RADIOSTATE_ENABLED = 0;
+this.GECKO_RADIOSTATE_DISABLED = 1;
+
+// Only used in ril_worker.js
+this.GECKO_CARDSTATE_UNINITIALIZED = 4294967294; // UINT32_MAX - 1
+// See nsIIcc::CARD_STATE_*
+this.GECKO_CARDSTATE_UNDETECTED = 4294967295; // UINT32_MAX
+this.GECKO_CARDSTATE_UNKNOWN = 0;
+this.GECKO_CARDSTATE_READY = 1;
+this.GECKO_CARDSTATE_PIN_REQUIRED = 2;
+this.GECKO_CARDSTATE_PUK_REQUIRED = 3;
+this.GECKO_CARDSTATE_PERMANENT_BLOCKED = 4;
+this.GECKO_CARDSTATE_PERSONALIZATION_IN_PROGRESS = 5;
+this.GECKO_CARDSTATE_PERSONALIZATION_READY = 6;
+this.GECKO_CARDSTATE_NETWORK_LOCKED = 7;
+this.GECKO_CARDSTATE_NETWORK_SUBSET_LOCKED = 8;
+this.GECKO_CARDSTATE_CORPORATE_LOCKED = 9;
+this.GECKO_CARDSTATE_SERVICE_PROVIDER_LOCKED = 10;
+this.GECKO_CARDSTATE_SIM_LOCKED = 11;
+this.GECKO_CARDSTATE_NETWORK_PUK_REQUIRED = 12;
+this.GECKO_CARDSTATE_NETWORK_SUBSET_PUK_REQUIRED = 13;
+this.GECKO_CARDSTATE_CORPORATE_PUK_REQUIRED = 14;
+this.GECKO_CARDSTATE_SERVICE_PROVIDER_PUK_REQUIRED = 15;
+this.GECKO_CARDSTATE_SIM_PUK_REQUIRED = 16;
+this.GECKO_CARDSTATE_NETWORK1_LOCKED = 17;
+this.GECKO_CARDSTATE_NETWORK2_LOCKED = 18;
+this.GECKO_CARDSTATE_HRPD_NETWORK_LOCKED = 19;
+this.GECKO_CARDSTATE_RUIM_CORPORATE_LOCKED = 20;
+this.GECKO_CARDSTATE_RUIM_SERVICE_PROVIDER_LOCKED = 21;
+this.GECKO_CARDSTATE_RUIM_LOCKED = 22;
+this.GECKO_CARDSTATE_NETWORK1_PUK_REQUIRED = 23;
+this.GECKO_CARDSTATE_NETWORK2_PUK_REQUIRED = 24;
+this.GECKO_CARDSTATE_HRPD_NETWORK_PUK_REQUIRED = 25;
+this.GECKO_CARDSTATE_RUIM_CORPORATE_PUK_REQUIRED = 26;
+this.GECKO_CARDSTATE_RUIM_SERVICE_PROVIDER_PUK_REQUIRED = 27;
+this.GECKO_CARDSTATE_RUIM_PUK_REQUIRED = 28;
+this.GECKO_CARDSTATE_ILLEGAL = 29;
+
+// See nsIIcc::CARD_LOCK_TYPE_*
+this.GECKO_CARDLOCK_PIN = 0;
+this.GECKO_CARDLOCK_PIN2 = 1;
+this.GECKO_CARDLOCK_PUK = 2;
+this.GECKO_CARDLOCK_PUK2 = 3;
+this.GECKO_CARDLOCK_NCK = 4;
+this.GECKO_CARDLOCK_NSCK = 5;
+this.GECKO_CARDLOCK_NCK1 = 6;
+this.GECKO_CARDLOCK_NCK2 = 7;
+this.GECKO_CARDLOCK_HNCK = 8;
+this.GECKO_CARDLOCK_CCK = 9;
+this.GECKO_CARDLOCK_SPCK = 10;
+this.GECKO_CARDLOCK_PCK = 11;
+this.GECKO_CARDLOCK_RCCK = 12;
+this.GECKO_CARDLOCK_RSPCK = 13;
+this.GECKO_CARDLOCK_NCK_PUK = 14;
+this.GECKO_CARDLOCK_NSCK_PUK = 15;
+this.GECKO_CARDLOCK_NCK1_PUK = 16;
+this.GECKO_CARDLOCK_NCK2_PUK = 17;
+this.GECKO_CARDLOCK_HNCK_PUK = 18;
+this.GECKO_CARDLOCK_CCK_PUK = 19;
+this.GECKO_CARDLOCK_SPCK_PUK = 20;
+this.GECKO_CARDLOCK_PCK_PUK = 21;
+this.GECKO_CARDLOCK_RCCK_PUK = 22;
+this.GECKO_CARDLOCK_RSPCK_PUK = 23;
+this.GECKO_CARDLOCK_FDN = 24;
+
+this.GECKO_CARDLOCK_TO_FACILITY = {};
+GECKO_CARDLOCK_TO_FACILITY[GECKO_CARDLOCK_PIN] = ICC_CB_FACILITY_SIM;
+GECKO_CARDLOCK_TO_FACILITY[GECKO_CARDLOCK_FDN] = ICC_CB_FACILITY_FDN;
+
+this.GECKO_CARDLOCK_TO_SEL_CODE = {};
+GECKO_CARDLOCK_TO_SEL_CODE[GECKO_CARDLOCK_PIN] = ICC_SEL_CODE_SIM_PIN;
+GECKO_CARDLOCK_TO_SEL_CODE[GECKO_CARDLOCK_PIN2] = ICC_SEL_CODE_SIM_PIN2;
+GECKO_CARDLOCK_TO_SEL_CODE[GECKO_CARDLOCK_PUK] = ICC_SEL_CODE_SIM_PUK;
+GECKO_CARDLOCK_TO_SEL_CODE[GECKO_CARDLOCK_PUK2] = ICC_SEL_CODE_SIM_PUK2;
+GECKO_CARDLOCK_TO_SEL_CODE[GECKO_CARDLOCK_NCK] = ICC_SEL_CODE_PH_NET_PIN;
+GECKO_CARDLOCK_TO_SEL_CODE[GECKO_CARDLOCK_NSCK] = ICC_SEL_CODE_PH_NETSUB_PIN;
+GECKO_CARDLOCK_TO_SEL_CODE[GECKO_CARDLOCK_CCK] = ICC_SEL_CODE_PH_CORP_PIN;
+GECKO_CARDLOCK_TO_SEL_CODE[GECKO_CARDLOCK_SPCK] = ICC_SEL_CODE_PH_SP_PIN;
+// TODO: Bug 1116072: identify the mapping between RIL_PERSOSUBSTATE_SIM_SIM @
+// ril.h and TS 27.007, clause 8.65 for GECKO_CARDLOCK_PCK.
+
+// See nsIIcc::CARD_CONTACT_TYPE_*
+this.GECKO_CARDCONTACT_TYPE_ADN = 0;
+this.GECKO_CARDCONTACT_TYPE_FDN = 1;
+this.GECKO_CARDCONTACT_TYPE_SDN = 2;
+
+// See nsIIcc::CARD_MVNO_TYPE_*
+this.GECKO_CARDMVNO_TYPE_IMSI = 0;
+this.GECKO_CARDMVNO_TYPE_SPN = 1;
+this.GECKO_CARDMVNO_TYPE_GID = 2;
+
+// See nsIIcc::CARD_SERVICE_*
+this.GECKO_CARDSERVICE_FDN = 0;
+
+// See ril.h RIL_PersoSubstate
+this.PERSONSUBSTATE = {};
+PERSONSUBSTATE[CARD_PERSOSUBSTATE_UNKNOWN] = GECKO_CARDSTATE_UNKNOWN;
+PERSONSUBSTATE[CARD_PERSOSUBSTATE_IN_PROGRESS] = GECKO_CARDSTATE_PERSONALIZATION_IN_PROGRESS;
+PERSONSUBSTATE[CARD_PERSOSUBSTATE_READY] = GECKO_CARDSTATE_PERSONALIZATION_READY;
+PERSONSUBSTATE[CARD_PERSOSUBSTATE_SIM_NETWORK] = GECKO_CARDSTATE_NETWORK_LOCKED;
+PERSONSUBSTATE[CARD_PERSOSUBSTATE_SIM_NETWORK_SUBSET] = GECKO_CARDSTATE_NETWORK_SUBSET_LOCKED;
+PERSONSUBSTATE[CARD_PERSOSUBSTATE_SIM_CORPORATE] = GECKO_CARDSTATE_CORPORATE_LOCKED;
+PERSONSUBSTATE[CARD_PERSOSUBSTATE_SIM_SERVICE_PROVIDER] = GECKO_CARDSTATE_SERVICE_PROVIDER_LOCKED;
+PERSONSUBSTATE[CARD_PERSOSUBSTATE_SIM_SIM] = GECKO_CARDSTATE_SIM_LOCKED;
+PERSONSUBSTATE[CARD_PERSOSUBSTATE_SIM_NETWORK_PUK] = GECKO_CARDSTATE_NETWORK_PUK_REQUIRED;
+PERSONSUBSTATE[CARD_PERSOSUBSTATE_SIM_NETWORK_SUBSET_PUK] = GECKO_CARDSTATE_NETWORK_SUBSET_PUK_REQUIRED;
+PERSONSUBSTATE[CARD_PERSOSUBSTATE_SIM_CORPORATE_PUK] = GECKO_CARDSTATE_CORPORATE_PUK_REQUIRED;
+PERSONSUBSTATE[CARD_PERSOSUBSTATE_SIM_SERVICE_PROVIDER_PUK] = GECKO_CARDSTATE_SERVICE_PROVIDER_PUK_REQUIRED;
+PERSONSUBSTATE[CARD_PERSOSUBSTATE_SIM_SIM_PUK] = GECKO_CARDSTATE_SIM_PUK_REQUIRED;
+PERSONSUBSTATE[CARD_PERSOSUBSTATE_RUIM_NETWORK1] = GECKO_CARDSTATE_NETWORK1_LOCKED;
+PERSONSUBSTATE[CARD_PERSOSUBSTATE_RUIM_NETWORK2] = GECKO_CARDSTATE_NETWORK2_LOCKED;
+PERSONSUBSTATE[CARD_PERSOSUBSTATE_RUIM_HRPD] = GECKO_CARDSTATE_HRPD_NETWORK_LOCKED;
+PERSONSUBSTATE[CARD_PERSOSUBSTATE_RUIM_CORPORATE] = GECKO_CARDSTATE_RUIM_CORPORATE_LOCKED;
+PERSONSUBSTATE[CARD_PERSOSUBSTATE_RUIM_SERVICE_PROVIDER] = GECKO_CARDSTATE_RUIM_SERVICE_PROVIDER_LOCKED;
+PERSONSUBSTATE[CARD_PERSOSUBSTATE_RUIM_RUIM] = GECKO_CARDSTATE_RUIM_LOCKED;
+PERSONSUBSTATE[CARD_PERSOSUBSTATE_RUIM_NETWORK1_PUK] = GECKO_CARDSTATE_NETWORK1_PUK_REQUIRED;
+PERSONSUBSTATE[CARD_PERSOSUBSTATE_RUIM_NETWORK2_PUK] = GECKO_CARDSTATE_NETWORK2_PUK_REQUIRED;
+PERSONSUBSTATE[CARD_PERSOSUBSTATE_RUIM_HRPD_PUK] = GECKO_CARDSTATE_HRPD_NETWORK_PUK_REQUIRED;
+PERSONSUBSTATE[CARD_PERSOSUBSTATE_RUIM_CORPORATE_PUK] = GECKO_CARDSTATE_RUIM_CORPORATE_PUK_REQUIRED;
+PERSONSUBSTATE[CARD_PERSOSUBSTATE_RUIM_SERVICE_PROVIDER_PUK] = GECKO_CARDSTATE_RUIM_SERVICE_PROVIDER_PUK_REQUIRED;
+PERSONSUBSTATE[CARD_PERSOSUBSTATE_RUIM_RUIM_PUK] = GECKO_CARDSTATE_RUIM_PUK_REQUIRED;
+
+// See nsIMobileConnection::NETWORK_SELECTION_MODE_*
+this.GECKO_NETWORK_SELECTION_UNKNOWN = -1;
+this.GECKO_NETWORK_SELECTION_AUTOMATIC = 0;
+this.GECKO_NETWORK_SELECTION_MANUAL = 1;
+
+this.GECKO_MOBILE_CONNECTION_STATE_UNKNOWN = null;
+this.GECKO_MOBILE_CONNECTION_STATE_NOTSEARCHING = "notSearching";
+this.GECKO_MOBILE_CONNECTION_STATE_SEARCHING = "searching";
+this.GECKO_MOBILE_CONNECTION_STATE_REGISTERED = "registered";
+this.GECKO_MOBILE_CONNECTION_STATE_DENIED = "denied";
+
+this.NETWORK_CREG_TO_GECKO_MOBILE_CONNECTION_STATE = {};
+NETWORK_CREG_TO_GECKO_MOBILE_CONNECTION_STATE[NETWORK_CREG_STATE_NOT_SEARCHING] = GECKO_MOBILE_CONNECTION_STATE_NOTSEARCHING;
+NETWORK_CREG_TO_GECKO_MOBILE_CONNECTION_STATE[NETWORK_CREG_STATE_REGISTERED_HOME] = GECKO_MOBILE_CONNECTION_STATE_REGISTERED;
+NETWORK_CREG_TO_GECKO_MOBILE_CONNECTION_STATE[NETWORK_CREG_STATE_SEARCHING] = GECKO_MOBILE_CONNECTION_STATE_SEARCHING;
+NETWORK_CREG_TO_GECKO_MOBILE_CONNECTION_STATE[NETWORK_CREG_STATE_DENIED] = GECKO_MOBILE_CONNECTION_STATE_DENIED;
+NETWORK_CREG_TO_GECKO_MOBILE_CONNECTION_STATE[NETWORK_CREG_STATE_UNKNOWN] = GECKO_MOBILE_CONNECTION_STATE_UNKNOWN;
+NETWORK_CREG_TO_GECKO_MOBILE_CONNECTION_STATE[NETWORK_CREG_STATE_REGISTERED_ROAMING] = GECKO_MOBILE_CONNECTION_STATE_REGISTERED;
+NETWORK_CREG_TO_GECKO_MOBILE_CONNECTION_STATE[NETWORK_CREG_STATE_NOT_SEARCHING_EMERGENCY_CALLS] = GECKO_MOBILE_CONNECTION_STATE_NOTSEARCHING;
+NETWORK_CREG_TO_GECKO_MOBILE_CONNECTION_STATE[NETWORK_CREG_STATE_SEARCHING_EMERGENCY_CALLS] = GECKO_MOBILE_CONNECTION_STATE_SEARCHING;
+NETWORK_CREG_TO_GECKO_MOBILE_CONNECTION_STATE[NETWORK_CREG_STATE_DENIED_EMERGENCY_CALLS] = GECKO_MOBILE_CONNECTION_STATE_DENIED;
+NETWORK_CREG_TO_GECKO_MOBILE_CONNECTION_STATE[NETWORK_CREG_STATE_UNKNOWN_EMERGENCY_CALLS] = GECKO_MOBILE_CONNECTION_STATE_UNKNOWN;
+
+
+// Should match enum TelephonyCallDisconnectedReason defined in TelephonyCall.webidl
+this.GECKO_CALL_ERROR_BAD_NUMBER = "BadNumberError";
+this.GECKO_CALL_ERROR_NO_ROUTE_TO_DESTINATION = "NoRouteToDestinationError";
+this.GECKO_CALL_ERROR_CHANNEL_UNACCEPTABLE = "ChannelUnacceptableError";
+this.GECKO_CALL_ERROR_OPERATOR_DETERMINED_BARRING = "OperatorDeterminedBarringError";
+this.GECKO_CALL_ERROR_NORMAL_CALL_CLEARING = "NormalCallClearingError";
+this.GECKO_CALL_ERROR_BUSY = "BusyError";
+this.GECKO_CALL_ERROR_NO_USER_RESPONDING = "NoUserRespondingError";
+this.GECKO_CALL_ERROR_USER_ALERTING = "UserAlertingNoAnswerError";
+this.GECKO_CALL_ERROR_REJECTED = "CallRejectedError";
+this.GECKO_CALL_ERROR_NUMBER_CHANGED = "NumberChangedError";
+this.GECKO_CALL_ERROR_REJECTED_DETINATION_FEATURE = "CallRejectedDestinationFeatureError";
+this.GECKO_CALL_ERROR_PRE_EMPTION = "PreEmptionError";
+this.GECKO_CALL_ERROR_DEST_OUT_OF_ORDER = "DestinationOutOfOrderError";
+this.GECKO_CALL_ERROR_INVALID_NUMBER_FORMAT = "InvalidNumberFormatError";
+this.GECKO_CALL_ERROR_FACILITY_REJECTED = "FacilityRejectedError";
+this.GECKO_CALL_ERROR_RESPONSE_TO_STATUS_ENQUIRY = "ResponseToStatusEnquiryError";
+this.GECKO_CALL_ERROR_CONGESTION = "CongestionError";
+this.GECKO_CALL_ERROR_NETWORK_OUT_OF_ORDER = "NetworkOutOfOrderError";
+this.GECKO_CALL_ERROR_NETWORK_TEMP_FAILURE = "NetworkTempFailureError";
+this.GECKO_CALL_ERROR_SWITCHING_EQUIP_CONGESTION = "SwitchingEquipCongestionError";
+this.GECKO_CALL_ERROR_ACCESS_INFO_DISCARDED = "AccessInfoDiscardedError";
+this.GECKO_CALL_ERROR_REQUESTED_CHANNEL_NOT_AVAILABLE = "RequestedChannelNotAvailableError";
+this.GECKO_CALL_ERROR_RESOURCE_UNAVAILABLE = "ResourceUnavailableError";
+this.GECKO_CALL_ERROR_QOS_UNAVAILABLE = "QosUnavailableError";
+this.GECKO_CALL_ERROR_REQUESTED_FACILITY_NOT_SUBSCRIBED = "RequestedFacilityNotSubscribedError";
+this.GECKO_CALL_ERROR_INCOMING_CALLS_BARRED_WITHIN_CUG = "IncomingCallsBarredWithinCugError";
+this.GECKO_CALL_ERROR_BEARER_CAPABILITY_NOT_AUTHORIZED = "BearerCapabilityNotAuthorizedError";
+this.GECKO_CALL_ERROR_BEARER_CAPABILITY_NOT_AVAILABLE = "BearerCapabilityNotAvailableError";
+this.GECKO_CALL_ERROR_BEARER_NOT_IMPLEMENTED = "BearerNotImplementedError";
+this.GECKO_CALL_ERROR_SERVICE_NOT_AVAILABLE = "ServiceNotAvailableError";
+this.GECKO_CALL_ERROR_INCOMING_CALL_EXCEEDED = "IncomingCallExceededError";
+this.GECKO_CALL_ERROR_REQUESTED_FACILITY_NOT_IMPLEMENTED = "RequestedFacilityNotImplementedError";
+this.GECKO_CALL_ERROR_UNRESTRICTED_BEARER_NOT_AVAILABLE = "UnrestrictedBearerNotAvailableError";
+this.GECKO_CALL_ERROR_SERVICE_NOT_IMPLEMENTED = "ServiceNotImplementedError";
+this.GECKO_CALL_ERROR_INVALID_TRANSACTION_ID = "InvalidTransactionIdError";
+this.GECKO_CALL_ERROR_USER_NOT_CUG_MEMBER = "NotCugMemberError";
+this.GECKO_CALL_ERROR_INCOMPATIBLE_DESTINATION = "IncompatibleDestinationError";
+this.GECKO_CALL_ERROR_INVALID_TRANSIT_NETWORK_SELECTION = "InvalidTransitNetworkSelectionError";
+this.GECKO_CALL_ERROR_SEMANTICALLY_INCORRECT_MESSAGE = "SemanticallyIncorrectMessageError";
+this.GECKO_CALL_ERROR_INVALID_MANDATORY_INFO = "InvalidMandatoryInfoError";
+this.GECKO_CALL_ERROR_MESSAGE_TYPE_NOT_IMPLEMENTED = "MessageTypeNotImplementedError";
+this.GECKO_CALL_ERROR_MESSAGE_TYPE_INCOMPATIBLE_PROTOCOL_STATE = "MessageTypeIncompatibleProtocolStateError";
+this.GECKO_CALL_ERROR_INFO_ELEMENT_NOT_IMPLEMENTED = "InfoElementNotImplementedError";
+this.GECKO_CALL_ERROR_CONDITIONAL_IE = "ConditionalIeError";
+this.GECKO_CALL_ERROR_MESSAGE_INCOMPATIBLE_PROTOCOL_STATE = "MessageIncompatibleProtocolStateError";
+this.GECKO_CALL_ERROR_RECOVERY_ON_TIMER_EXPIRY = "RecoveryOnTimerExpiryError";
+this.GECKO_CALL_ERROR_PROTOCOL = "ProtocolError";
+this.GECKO_CALL_ERROR_INTERWORKING = "InterworkingError";
+this.GECKO_CALL_ERROR_BARRED = "BarredError";
+this.GECKO_CALL_ERROR_FDN_BLOCKED = "FDNBlockedError";
+this.GECKO_CALL_ERROR_SUBSCRIBER_UNKNOWN = "SubscriberUnknownError";
+this.GECKO_CALL_ERROR_DEVICE_NOT_ACCEPTED = "DeviceNotAcceptedError";
+this.GECKO_CALL_ERROR_MODIFIED_TO_DIAL_FAILED = "ModifiedDialError";
+this.GECKO_CALL_ERROR_CDMA_LOCKED_UNTIL_POWER_CYCLE = "CdmaLockedUntilPowerCycleError";
+this.GECKO_CALL_ERROR_CDMA_DROP = "CdmaDropError";
+this.GECKO_CALL_ERROR_CDMA_INTERCEPT = "CdmaInterceptError";
+this.GECKO_CALL_ERROR_CDMA_REORDER = "CdmaReorderError";
+this.GECKO_CALL_ERROR_CDMA_SO_REJECT = "CdmaSoRejectError";
+this.GECKO_CALL_ERROR_CDMA_RETRY_ORDER = "CdmaRetryOrderError";
+this.GECKO_CALL_ERROR_CDMA_ACCESS_FAILURE = "CdmaAcessError";
+this.GECKO_CALL_ERROR_CDMA_PREEMPTED = "CdmaPreemptedError";
+this.GECKO_CALL_ERROR_CDMA_NOT_EMERGENCY = "CdmaNotEmergencyError";
+this.GECKO_CALL_ERROR_CDMA_ACCESS_BLOCKED = "CdmaAccessBlockedError";
+this.GECKO_CALL_ERROR_UNSPECIFIED = "UnspecifiedError";
+
+this.RIL_CALL_FAILCAUSE_TO_GECKO_CALL_ERROR = {};
+RIL_CALL_FAILCAUSE_TO_GECKO_CALL_ERROR[CALL_FAIL_UNOBTAINABLE_NUMBER] = GECKO_CALL_ERROR_BAD_NUMBER;
+RIL_CALL_FAILCAUSE_TO_GECKO_CALL_ERROR[CALL_FAIL_NO_ROUTE_TO_DESTINATION] = GECKO_CALL_ERROR_NO_ROUTE_TO_DESTINATION;
+RIL_CALL_FAILCAUSE_TO_GECKO_CALL_ERROR[CALL_FAIL_CHANNEL_UNACCEPTABLE] = GECKO_CALL_ERROR_CHANNEL_UNACCEPTABLE;
+RIL_CALL_FAILCAUSE_TO_GECKO_CALL_ERROR[CALL_FAIL_OPERATOR_DETERMINED_BARRING] = GECKO_CALL_ERROR_OPERATOR_DETERMINED_BARRING;
+RIL_CALL_FAILCAUSE_TO_GECKO_CALL_ERROR[CALL_FAIL_NORMAL] = GECKO_CALL_ERROR_NORMAL_CALL_CLEARING;
+RIL_CALL_FAILCAUSE_TO_GECKO_CALL_ERROR[CALL_FAIL_BUSY] = GECKO_CALL_ERROR_BUSY;
+RIL_CALL_FAILCAUSE_TO_GECKO_CALL_ERROR[CALL_FAIL_NO_USER_RESPONDING] = GECKO_CALL_ERROR_NO_USER_RESPONDING;
+RIL_CALL_FAILCAUSE_TO_GECKO_CALL_ERROR[CALL_FAIL_USER_ALERTING] = GECKO_CALL_ERROR_USER_ALERTING;
+RIL_CALL_FAILCAUSE_TO_GECKO_CALL_ERROR[CALL_FAIL_CALL_REJECTED] = GECKO_CALL_ERROR_REJECTED;
+RIL_CALL_FAILCAUSE_TO_GECKO_CALL_ERROR[CALL_FAIL_NUMBER_CHANGED] = GECKO_CALL_ERROR_NUMBER_CHANGED;
+RIL_CALL_FAILCAUSE_TO_GECKO_CALL_ERROR[CALL_FAIL_CALL_REJECTED_DESTINATION_FEATURE] = GECKO_CALL_ERROR_REJECTED_DETINATION_FEATURE;
+RIL_CALL_FAILCAUSE_TO_GECKO_CALL_ERROR[CALL_FAIL_CALL_PRE_EMPTION] = GECKO_CALL_ERROR_PRE_EMPTION;
+RIL_CALL_FAILCAUSE_TO_GECKO_CALL_ERROR[CALL_FAIL_DEST_OUT_OF_ORDER] = GECKO_CALL_ERROR_DEST_OUT_OF_ORDER;
+RIL_CALL_FAILCAUSE_TO_GECKO_CALL_ERROR[CALL_FAIL_INVALID_FORMAT] = GECKO_CALL_ERROR_INVALID_NUMBER_FORMAT;
+RIL_CALL_FAILCAUSE_TO_GECKO_CALL_ERROR[CALL_FAIL_FACILITY_REJECTED] = GECKO_CALL_ERROR_FACILITY_REJECTED;
+RIL_CALL_FAILCAUSE_TO_GECKO_CALL_ERROR[CALL_FAIL_RESPONSE_TO_STATUS_ENQUIRY] = GECKO_CALL_ERROR_RESPONSE_TO_STATUS_ENQUIRY;
+RIL_CALL_FAILCAUSE_TO_GECKO_CALL_ERROR[CALL_FAIL_NORMAL_UNSPECIFIED] = GECKO_CALL_ERROR_NORMAL_CALL_CLEARING;
+RIL_CALL_FAILCAUSE_TO_GECKO_CALL_ERROR[CALL_FAIL_CONGESTION] = GECKO_CALL_ERROR_CONGESTION;
+RIL_CALL_FAILCAUSE_TO_GECKO_CALL_ERROR[CALL_FAIL_NETWORK_OUT_OF_ORDER] = GECKO_CALL_ERROR_NETWORK_OUT_OF_ORDER;
+RIL_CALL_FAILCAUSE_TO_GECKO_CALL_ERROR[CALL_FAIL_NETWORK_TEMP_FAILURE] = GECKO_CALL_ERROR_NETWORK_TEMP_FAILURE;
+RIL_CALL_FAILCAUSE_TO_GECKO_CALL_ERROR[CALL_FAIL_SWITCHING_EQUIP_CONGESTION] = GECKO_CALL_ERROR_SWITCHING_EQUIP_CONGESTION;
+RIL_CALL_FAILCAUSE_TO_GECKO_CALL_ERROR[CALL_FAIL_ACCESS_INFO_DISCARDED] = GECKO_CALL_ERROR_ACCESS_INFO_DISCARDED;
+RIL_CALL_FAILCAUSE_TO_GECKO_CALL_ERROR[CALL_FAIL_REQUESTED_CHANNEL_NOT_AVAILABLE] = GECKO_CALL_ERROR_REQUESTED_CHANNEL_NOT_AVAILABLE;
+RIL_CALL_FAILCAUSE_TO_GECKO_CALL_ERROR[CALL_FAIL_RESOURCE_UNAVAILABLE] = GECKO_CALL_ERROR_RESOURCE_UNAVAILABLE;
+RIL_CALL_FAILCAUSE_TO_GECKO_CALL_ERROR[CALL_FAIL_QOS_UNAVAILABLE] = GECKO_CALL_ERROR_QOS_UNAVAILABLE;
+RIL_CALL_FAILCAUSE_TO_GECKO_CALL_ERROR[CALL_FAIL_REQUESTED_FACILITY_NOT_SUBSCRIBED] = GECKO_CALL_ERROR_REQUESTED_FACILITY_NOT_SUBSCRIBED;
+RIL_CALL_FAILCAUSE_TO_GECKO_CALL_ERROR[CALL_FAIL_INCOMING_CALLS_BARRED_WITHIN_CUG] = GECKO_CALL_ERROR_INCOMING_CALLS_BARRED_WITHIN_CUG;
+RIL_CALL_FAILCAUSE_TO_GECKO_CALL_ERROR[CALL_FAIL_BEARER_CAPABILITY_NOT_AUTHORIZED] = GECKO_CALL_ERROR_BEARER_CAPABILITY_NOT_AUTHORIZED;
+RIL_CALL_FAILCAUSE_TO_GECKO_CALL_ERROR[CALL_FAIL_BEARER_CAPABILITY_NOT_AVAILABLE] = GECKO_CALL_ERROR_BEARER_CAPABILITY_NOT_AVAILABLE;
+RIL_CALL_FAILCAUSE_TO_GECKO_CALL_ERROR[CALL_FAIL_SERVICE_NOT_AVAILABLE] = GECKO_CALL_ERROR_SERVICE_NOT_AVAILABLE;
+RIL_CALL_FAILCAUSE_TO_GECKO_CALL_ERROR[CALL_FAIL_BEARER_NOT_IMPLEMENTED] = GECKO_CALL_ERROR_BEARER_NOT_IMPLEMENTED;
+RIL_CALL_FAILCAUSE_TO_GECKO_CALL_ERROR[CALL_FAIL_ACM_LIMIT_EXCEEDED] = GECKO_CALL_ERROR_INCOMING_CALL_EXCEEDED;
+RIL_CALL_FAILCAUSE_TO_GECKO_CALL_ERROR[CALL_FAIL_REQUESTED_FACILITY_NOT_IMPLEMENTED] = GECKO_CALL_ERROR_REQUESTED_FACILITY_NOT_IMPLEMENTED;
+RIL_CALL_FAILCAUSE_TO_GECKO_CALL_ERROR[CALL_FAIL_UNRESTRICTED_BEARER_NOT_AVAILABLE] = GECKO_CALL_ERROR_UNRESTRICTED_BEARER_NOT_AVAILABLE;
+RIL_CALL_FAILCAUSE_TO_GECKO_CALL_ERROR[CALL_FAIL_SERVICE_NOT_IMPLEMENTED] = GECKO_CALL_ERROR_SERVICE_NOT_IMPLEMENTED;
+RIL_CALL_FAILCAUSE_TO_GECKO_CALL_ERROR[CALL_FAIL_INVALID_TRANSACTION_ID] = GECKO_CALL_ERROR_INVALID_TRANSACTION_ID;
+RIL_CALL_FAILCAUSE_TO_GECKO_CALL_ERROR[CALL_FAIL_USER_NOT_CUG_MEMBER] = GECKO_CALL_ERROR_USER_NOT_CUG_MEMBER;
+RIL_CALL_FAILCAUSE_TO_GECKO_CALL_ERROR[CALL_FAIL_INCOMPATIBLE_DESTINATION] = GECKO_CALL_ERROR_INCOMPATIBLE_DESTINATION;
+RIL_CALL_FAILCAUSE_TO_GECKO_CALL_ERROR[CALL_FAIL_INVALID_TRANSIT_NETWORK_SELECTION] = GECKO_CALL_ERROR_INVALID_TRANSIT_NETWORK_SELECTION;
+RIL_CALL_FAILCAUSE_TO_GECKO_CALL_ERROR[CALL_FAIL_SEMANTICALLY_INCORRECT_MESSAGE] = GECKO_CALL_ERROR_SEMANTICALLY_INCORRECT_MESSAGE;
+RIL_CALL_FAILCAUSE_TO_GECKO_CALL_ERROR[CALL_FAIL_INVALID_MANDATORY_INFO] = GECKO_CALL_ERROR_INVALID_MANDATORY_INFO;
+RIL_CALL_FAILCAUSE_TO_GECKO_CALL_ERROR[CALL_FAIL_MESSAGE_TYPE_NOT_IMPLEMENTED] = GECKO_CALL_ERROR_MESSAGE_TYPE_NOT_IMPLEMENTED;
+RIL_CALL_FAILCAUSE_TO_GECKO_CALL_ERROR[CALL_FAIL_MESSAGE_TYPE_INCOMPATIBLE_PROTOCOL_STATE] = GECKO_CALL_ERROR_MESSAGE_TYPE_INCOMPATIBLE_PROTOCOL_STATE;
+RIL_CALL_FAILCAUSE_TO_GECKO_CALL_ERROR[CALL_FAIL_INFO_ELEMENT_NOT_IMPLEMENTED] = GECKO_CALL_ERROR_INFO_ELEMENT_NOT_IMPLEMENTED;
+RIL_CALL_FAILCAUSE_TO_GECKO_CALL_ERROR[CALL_FAIL_CONDITIONAL_IE_ERROR] = GECKO_CALL_ERROR_CONDITIONAL_IE;
+RIL_CALL_FAILCAUSE_TO_GECKO_CALL_ERROR[CALL_FAIL_MESSAGE_INCOMPABITLE_PROTOCOL_STATE] = GECKO_CALL_ERROR_MESSAGE_INCOMPATIBLE_PROTOCOL_STATE;
+RIL_CALL_FAILCAUSE_TO_GECKO_CALL_ERROR[CALL_FAIL_RECOVERY_ON_TIMER_EXPIRY] = GECKO_CALL_ERROR_RECOVERY_ON_TIMER_EXPIRY;
+RIL_CALL_FAILCAUSE_TO_GECKO_CALL_ERROR[CALL_FAIL_PROTOCOL_ERROR] = GECKO_CALL_ERROR_PROTOCOL;
+RIL_CALL_FAILCAUSE_TO_GECKO_CALL_ERROR[CALL_FAIL_INTERWORKING] = GECKO_CALL_ERROR_INTERWORKING;
+RIL_CALL_FAILCAUSE_TO_GECKO_CALL_ERROR[CALL_FAIL_CALL_BARRED] = GECKO_CALL_ERROR_BARRED;
+RIL_CALL_FAILCAUSE_TO_GECKO_CALL_ERROR[CALL_FAIL_FDN_BLOCKED] = GECKO_CALL_ERROR_FDN_BLOCKED;
+RIL_CALL_FAILCAUSE_TO_GECKO_CALL_ERROR[CALL_FAIL_IMSI_UNKNOWN_IN_VLR] = GECKO_CALL_ERROR_SUBSCRIBER_UNKNOWN;
+RIL_CALL_FAILCAUSE_TO_GECKO_CALL_ERROR[CALL_FAIL_IMEI_NOT_ACCEPTED] = GECKO_CALL_ERROR_DEVICE_NOT_ACCEPTED;
+RIL_CALL_FAILCAUSE_TO_GECKO_CALL_ERROR[CALL_FAIL_DIAL_MODIFIED_TO_USSD] = GECKO_CALL_ERROR_MODIFIED_TO_DIAL_FAILED;
+RIL_CALL_FAILCAUSE_TO_GECKO_CALL_ERROR[CALL_FAIL_DIAL_MODIFIED_TO_SS] = GECKO_CALL_ERROR_MODIFIED_TO_DIAL_FAILED;
+RIL_CALL_FAILCAUSE_TO_GECKO_CALL_ERROR[CALL_FAIL_DIAL_MODIFIED_TO_DIAL] = GECKO_CALL_ERROR_MODIFIED_TO_DIAL_FAILED;
+RIL_CALL_FAILCAUSE_TO_GECKO_CALL_ERROR[CALL_FAIL_CDMA_LOCKED_UNTIL_POWER_CYCLE] = GECKO_CALL_ERROR_CDMA_LOCKED_UNTIL_POWER_CYCLE;
+RIL_CALL_FAILCAUSE_TO_GECKO_CALL_ERROR[CALL_FAIL_CDMA_DROP] = GECKO_CALL_ERROR_CDMA_DROP;
+RIL_CALL_FAILCAUSE_TO_GECKO_CALL_ERROR[CALL_FAIL_CDMA_INTERCEPT] = GECKO_CALL_ERROR_CDMA_INTERCEPT;
+RIL_CALL_FAILCAUSE_TO_GECKO_CALL_ERROR[CALL_FAIL_CDMA_REORDER] = GECKO_CALL_ERROR_CDMA_REORDER;
+RIL_CALL_FAILCAUSE_TO_GECKO_CALL_ERROR[CALL_FAIL_CDMA_SO_REJECT] = GECKO_CALL_ERROR_CDMA_SO_REJECT;
+RIL_CALL_FAILCAUSE_TO_GECKO_CALL_ERROR[CALL_FAIL_CDMA_RETRY_ORDER] = GECKO_CALL_ERROR_CDMA_RETRY_ORDER;
+RIL_CALL_FAILCAUSE_TO_GECKO_CALL_ERROR[CALL_FAIL_CDMA_ACCESS_FAILURE] = GECKO_CALL_ERROR_CDMA_ACCESS_FAILURE;
+RIL_CALL_FAILCAUSE_TO_GECKO_CALL_ERROR[CALL_FAIL_CDMA_PREEMPTED] = GECKO_CALL_ERROR_CDMA_PREEMPTED;
+RIL_CALL_FAILCAUSE_TO_GECKO_CALL_ERROR[CALL_FAIL_CDMA_NOT_EMERGENCY] = GECKO_CALL_ERROR_CDMA_NOT_EMERGENCY;
+RIL_CALL_FAILCAUSE_TO_GECKO_CALL_ERROR[CALL_FAIL_CDMA_ACCESS_BLOCKED] = GECKO_CALL_ERROR_CDMA_ACCESS_BLOCKED;
+RIL_CALL_FAILCAUSE_TO_GECKO_CALL_ERROR[CALL_FAIL_ERROR_UNSPECIFIED] = GECKO_CALL_ERROR_UNSPECIFIED;
+
+this.GECKO_DATACALL_ERROR_OPERATOR_BARRED = "OperatorBarredError";
+this.GECKO_DATACALL_ERROR_INSUFFICIENT_RESOURCES = "InsufficientResourcesError";
+this.GECKO_DATACALL_ERROR_MISSING_UKNOWN_APN = "MissingUnknownAPNError";
+this.GECKO_DATACALL_ERROR_UNKNOWN_PDP_ADDRESS_TYPE = "UnknownPDPAddressTypeError";
+this.GECKO_DATACALL_ERROR_USER_AUTHENTICATION = "UserAuthenticationError";
+this.GECKO_DATACALL_ERROR_ACTIVATION_REJECT_GGSN = "ActivationRejectGGSNError";
+this.GECKO_DATACALL_ERROR_ACTIVATION_REJECT_UNSPECIFIED = "ActivationRejectUnspecifiedError";
+this.GECKO_DATACALL_ERROR_SERVICE_OPTION_NOT_SUPPORTED = "ServiceOptionNotSupportedError";
+this.GECKO_DATACALL_ERROR_SERVICE_OPTION_NOT_SUBSCRIBED = "ServiceOptionNotSubscribedError";
+this.GECKO_DATACALL_ERROR_SERVICE_OPTION_OUT_OF_ORDER = "ServiceOptionOutOfOrderError";
+this.GECKO_DATACALL_ERROR_NSAPI_IN_USE = "NSAPIInUseError";
+this.GECKO_DATACALL_ERROR_ONLY_IPV4_ALLOWED = "OnlyIPv4Error";
+this.GECKO_DATACALL_ERROR_ONLY_IPV6_ALLOWED = "OnlyIPv6Error";
+this.GECKO_DATACALL_ERROR_ONLY_SINGLE_BEARER_ALLOWED = "OnlySingleBearerAllowedError";
+this.GECKO_DATACALL_ERROR_PROTOCOL_ERRORS = "ProtocolErrorsError";
+this.GECKO_DATACALL_ERROR_VOICE_REGISTRATION_FAIL = "VoiceRegistrationFailError";
+this.GECKO_DATACALL_ERROR_DATA_REGISTRATION_FAIL = "DataRegistrationFailError";
+this.GECKO_DATACALL_ERROR_SIGNAL_LOST = "SignalLostError";
+this.GECKO_DATACALL_ERROR_PREF_RADIO_TECH_CHANGED = "PrefRadioTechChangedError";
+this.GECKO_DATACALL_ERROR_RADIO_POWER_OFF = "RadioPowerOffError";
+this.GECKO_DATACALL_ERROR_TETHERED_CALL_ACTIVE = "TetheredCallActiveError";
+this.GECKO_DATACALL_ERROR_UNSPECIFIED = "UnspecifiedError";
+
+this.RIL_DATACALL_FAILCAUSE_TO_GECKO_DATACALL_ERROR = {};
+RIL_DATACALL_FAILCAUSE_TO_GECKO_DATACALL_ERROR[DATACALL_FAIL_OPERATOR_BARRED] = GECKO_DATACALL_ERROR_OPERATOR_BARRED;
+RIL_DATACALL_FAILCAUSE_TO_GECKO_DATACALL_ERROR[DATACALL_FAIL_INSUFFICIENT_RESOURCES] = GECKO_DATACALL_ERROR_INSUFFICIENT_RESOURCES;
+RIL_DATACALL_FAILCAUSE_TO_GECKO_DATACALL_ERROR[DATACALL_FAIL_MISSING_UKNOWN_APN] = GECKO_DATACALL_ERROR_MISSING_UKNOWN_APN;
+RIL_DATACALL_FAILCAUSE_TO_GECKO_DATACALL_ERROR[DATACALL_FAIL_UNKNOWN_PDP_ADDRESS_TYPE] = GECKO_DATACALL_ERROR_UNKNOWN_PDP_ADDRESS_TYPE;
+RIL_DATACALL_FAILCAUSE_TO_GECKO_DATACALL_ERROR[DATACALL_FAIL_USER_AUTHENTICATION] = GECKO_DATACALL_ERROR_USER_AUTHENTICATION;
+RIL_DATACALL_FAILCAUSE_TO_GECKO_DATACALL_ERROR[DATACALL_FAIL_ACTIVATION_REJECT_GGSN] = GECKO_DATACALL_ERROR_ACTIVATION_REJECT_GGSN;
+RIL_DATACALL_FAILCAUSE_TO_GECKO_DATACALL_ERROR[DATACALL_FAIL_ACTIVATION_REJECT_UNSPECIFIED] = GECKO_DATACALL_ERROR_ACTIVATION_REJECT_UNSPECIFIED;
+RIL_DATACALL_FAILCAUSE_TO_GECKO_DATACALL_ERROR[DATACALL_FAIL_SERVICE_OPTION_NOT_SUPPORTED] = GECKO_DATACALL_ERROR_SERVICE_OPTION_NOT_SUPPORTED;
+RIL_DATACALL_FAILCAUSE_TO_GECKO_DATACALL_ERROR[DATACALL_FAIL_SERVICE_OPTION_NOT_SUBSCRIBED] = GECKO_DATACALL_ERROR_SERVICE_OPTION_NOT_SUBSCRIBED;
+RIL_DATACALL_FAILCAUSE_TO_GECKO_DATACALL_ERROR[DATACALL_FAIL_SERVICE_OPTION_OUT_OF_ORDER] = GECKO_DATACALL_ERROR_SERVICE_OPTION_OUT_OF_ORDER;
+RIL_DATACALL_FAILCAUSE_TO_GECKO_DATACALL_ERROR[DATACALL_FAIL_NSAPI_IN_USE] = GECKO_DATACALL_ERROR_NSAPI_IN_USE;
+RIL_DATACALL_FAILCAUSE_TO_GECKO_DATACALL_ERROR[DATACALL_FAIL_ONLY_IPV4_ALLOWED] = GECKO_DATACALL_ERROR_ONLY_IPV4_ALLOWED;
+RIL_DATACALL_FAILCAUSE_TO_GECKO_DATACALL_ERROR[DATACALL_FAIL_ONLY_IPV6_ALLOWED] = GECKO_DATACALL_ERROR_ONLY_IPV6_ALLOWED;
+RIL_DATACALL_FAILCAUSE_TO_GECKO_DATACALL_ERROR[DATACALL_FAIL_ONLY_SINGLE_BEARER_ALLOWED] = GECKO_DATACALL_ERROR_ONLY_SINGLE_BEARER_ALLOWED;
+RIL_DATACALL_FAILCAUSE_TO_GECKO_DATACALL_ERROR[DATACALL_FAIL_PROTOCOL_ERRORS] = GECKO_DATACALL_ERROR_PROTOCOL_ERRORS;
+RIL_DATACALL_FAILCAUSE_TO_GECKO_DATACALL_ERROR[DATACALL_FAIL_VOICE_REGISTRATION_FAIL] = GECKO_DATACALL_ERROR_VOICE_REGISTRATION_FAIL;
+RIL_DATACALL_FAILCAUSE_TO_GECKO_DATACALL_ERROR[DATACALL_FAIL_DATA_REGISTRATION_FAIL] = GECKO_DATACALL_ERROR_DATA_REGISTRATION_FAIL;
+RIL_DATACALL_FAILCAUSE_TO_GECKO_DATACALL_ERROR[DATACALL_FAIL_SIGNAL_LOST] = GECKO_DATACALL_ERROR_SIGNAL_LOST;
+RIL_DATACALL_FAILCAUSE_TO_GECKO_DATACALL_ERROR[DATACALL_FAIL_PREF_RADIO_TECH_CHANGED] = GECKO_DATACALL_ERROR_PREF_RADIO_TECH_CHANGED;
+RIL_DATACALL_FAILCAUSE_TO_GECKO_DATACALL_ERROR[DATACALL_FAIL_RADIO_POWER_OFF] = GECKO_DATACALL_ERROR_RADIO_POWER_OFF;
+RIL_DATACALL_FAILCAUSE_TO_GECKO_DATACALL_ERROR[DATACALL_FAIL_TETHERED_CALL_ACTIVE] = GECKO_DATACALL_ERROR_TETHERED_CALL_ACTIVE;
+RIL_DATACALL_FAILCAUSE_TO_GECKO_DATACALL_ERROR[DATACALL_FAIL_ERROR_UNSPECIFIED] = GECKO_DATACALL_ERROR_UNSPECIFIED;
+
+this.GECKO_RADIO_TECH = [
+ null,
+ "gprs",
+ "edge",
+ "umts",
+ "is95a",
+ "is95b",
+ "1xrtt",
+ "evdo0",
+ "evdoa",
+ "hsdpa",
+ "hsupa",
+ "hspa",
+ "evdob",
+ "ehrpd",
+ "lte",
+ "hspa+",
+ "gsm",
+ null,
+ "hspa+", // DC-HSPA+
+ "hspa+"
+];
+
+this.GECKO_VOICEMAIL_MESSAGE_COUNT_UNKNOWN = -1;
+
+// Call forwarding action. Must be in sync with nsIMobileConnectionService interface
+this.CALL_FORWARD_ACTION_DISABLE = 0;
+this.CALL_FORWARD_ACTION_ENABLE = 1;
+this.CALL_FORWARD_ACTION_QUERY_STATUS = 2;
+this.CALL_FORWARD_ACTION_REGISTRATION = 3;
+this.CALL_FORWARD_ACTION_ERASURE = 4;
+
+// Call forwarding reason. Must be in sync with nsIMobileConnectionService interface
+this.CALL_FORWARD_REASON_UNCONDITIONAL = 0;
+this.CALL_FORWARD_REASON_MOBILE_BUSY = 1;
+this.CALL_FORWARD_REASON_NO_REPLY = 2;
+this.CALL_FORWARD_REASON_NOT_REACHABLE = 3;
+this.CALL_FORWARD_REASON_ALL_CALL_FORWARDING = 4;
+this.CALL_FORWARD_REASON_ALL_CONDITIONAL_CALL_FORWARDING = 5;
+
+// Call barring program. Must be in sync with nsIMobileConnectionService interface
+this.CALL_BARRING_PROGRAM_ALL_OUTGOING = 0;
+this.CALL_BARRING_PROGRAM_OUTGOING_INTERNATIONAL = 1;
+this.CALL_BARRING_PROGRAM_OUTGOING_INTERNATIONAL_EXCEPT_HOME = 2;
+this.CALL_BARRING_PROGRAM_ALL_INCOMING = 3;
+this.CALL_BARRING_PROGRAM_INCOMING_ROAMING = 4;
+this.CALL_BARRING_PROGRAM_ALL_SERVICE = 5;
+this.CALL_BARRING_PROGRAM_OUTGOING_SERVICE = 6;
+this.CALL_BARRING_PROGRAM_INCOMING_SERVICE = 7;
+
+this.CALL_BARRING_PROGRAM_TO_FACILITY = {};
+CALL_BARRING_PROGRAM_TO_FACILITY[CALL_BARRING_PROGRAM_ALL_OUTGOING] = ICC_CB_FACILITY_BAOC;
+CALL_BARRING_PROGRAM_TO_FACILITY[CALL_BARRING_PROGRAM_OUTGOING_INTERNATIONAL] = ICC_CB_FACILITY_BAOIC;
+CALL_BARRING_PROGRAM_TO_FACILITY[CALL_BARRING_PROGRAM_OUTGOING_INTERNATIONAL_EXCEPT_HOME] = ICC_CB_FACILITY_BAOICxH;
+CALL_BARRING_PROGRAM_TO_FACILITY[CALL_BARRING_PROGRAM_ALL_INCOMING] = ICC_CB_FACILITY_BAIC;
+CALL_BARRING_PROGRAM_TO_FACILITY[CALL_BARRING_PROGRAM_INCOMING_ROAMING] = ICC_CB_FACILITY_BAICr;
+CALL_BARRING_PROGRAM_TO_FACILITY[CALL_BARRING_PROGRAM_ALL_SERVICE] = ICC_CB_FACILITY_BA_ALL;
+CALL_BARRING_PROGRAM_TO_FACILITY[CALL_BARRING_PROGRAM_OUTGOING_SERVICE] = ICC_CB_FACILITY_BA_MO;
+CALL_BARRING_PROGRAM_TO_FACILITY[CALL_BARRING_PROGRAM_INCOMING_SERVICE] = ICC_CB_FACILITY_BA_MT;
+
+/**
+ * CDMA PDU constants
+ */
+
+// SMS Message Type, as defined in 3GPP2 C.S0015-A v2.0, Table 3.4-1
+this.PDU_CDMA_MSG_TYPE_P2P = 0x00; // Point-to-Point
+this.PDU_CDMA_MSG_TYPE_BROADCAST = 0x01; // Broadcast
+this.PDU_CDMA_MSG_TYPE_ACK = 0x02; // Acknowledge
+
+// SMS Teleservice Identitifier, as defined in 3GPP2 N.S0005, Table 175
+this.PDU_CDMA_MSG_TELESERIVCIE_ID_SMS = 0x1002; // SMS
+this.PDU_CDMA_MSG_TELESERIVCIE_ID_WAP = 0x1004; // WAP
+this.PDU_CDMA_MSG_TELESERIVCIE_ID_WEMT = 0x1005; // Wireless Enhanced Messaging Teleservice
+ // required for fragmented SMS
+
+// SMS Service Category, as defined in 3GPP2 C.R1001-D, Table 9.3.1-1
+this.PDU_CDMA_MSG_CATEGORY_UNSPEC = 0x00; // Unknown/Unspecified
+
+// Address Information, Digit Mode, as defined in 3GPP2 C.S0015-A v2.0, sec 3.4.3.3
+this.PDU_CDMA_MSG_ADDR_DIGIT_MODE_DTMF = 0x00; // Digit Mode : DTMF
+this.PDU_CDMA_MSG_ADDR_DIGIT_MODE_ASCII = 0x01; // Digit Mode : 8-bit ASCII with MSB = 0
+
+// Address Information, Number Mode, as defined in 3GPP2 C.S0015-A v2.0, sec 3.4.3.3
+this.PDU_CDMA_MSG_ADDR_NUMBER_MODE_ANSI = 0x00; // Number Mode : ANSI T1.607-2000(R2004)
+this.PDU_CDMA_MSG_ADDR_NUMBER_MODE_ASCII = 0x01; // Number Mode : Data network address format
+
+// Address Information, Number Type, as defined in 3GPP2 C.S0015-A v2.0, Table 3.4.3.3-1
+this.PDU_CDMA_MSG_ADDR_NUMBER_TYPE_UNKNOWN = 0x00; // Number Type : Unknown
+this.PDU_CDMA_MSG_ADDR_NUMBER_TYPE_INTERNATIONAL = 0x01; // Number Type : Internaltional number(+XXXXX)
+this.PDU_CDMA_MSG_ADDR_NUMBER_TYPE_NATIONAL = 0x02; // Number Type : National number
+
+// Address Information, Number Plan, as defined in 3GPP2 C.S0005-D v2.0, Table 2.7.1.3.2.4-3
+this.PDU_CDMA_MSG_ADDR_NUMBER_PLAN_UNKNOWN = 0x00; // Number Plan : Unknown
+this.PDU_CDMA_MSG_ADDR_NUMBER_PLAN_ISDN = 0x01; // Number Plan : ISDN/Telephony numbering plan
+
+// SMS Encoding, as defined in 3GPP2 C.R1001-D, Table 9.1-1
+this.PDU_CDMA_MSG_CODING_OCTET = 0x00; // octet(8-bit), Not tested
+this.PDU_CDMA_MSG_CODING_IS_91 = 0x01; // IS-91 Extended Protocol Message(variable), Not tested
+this.PDU_CDMA_MSG_CODING_7BITS_ASCII = 0x02; // 7-bit ASCII(7-bit)
+this.PDU_CDMA_MSG_CODING_IA5 = 0x03; // IA5(7-bit), Not tested
+this.PDU_CDMA_MSG_CODING_UNICODE = 0x04; // Unicode(16-bit)
+this.PDU_CDMA_MSG_CODING_SHIFT_JIS = 0x05; // Shift-6 JIS(8/16-bit variable), Not supported
+this.PDU_CDMA_MSG_CODING_KOREAN = 0x06; // Korean(8/16-bit variable), Not supported
+this.PDU_CDMA_MSG_CODING_LATIN_HEBREW = 0x07; // Latin/ Hebrew(8-bit), ISO/IEC 8859-8, Not supported
+this.PDU_CDMA_MSG_CODING_LATIN = 0x08; // Latin(8-bit), ISO/IEC 8859-1, Not tested
+this.PDU_CDMA_MSG_CODING_7BITS_GSM = 0x09; // GSM 7-bit default alphabet(7-bit), Not tested
+this.PDU_CDMA_MSG_CODING_GSM_DCS = 0x0A; // GSM Data-Coding-Scheme, Not supported
+
+// SMS Message Type, as defined in 3GPP2 C.S0015-A v2.0, Table 4.5.1-1
+this.PDU_CDMA_MSG_TYPE_DELIVER = 0x01; // Deliver
+this.PDU_CDMA_MSG_TYPE_SUBMIT = 0x02; // Submit
+this.PDU_CDMA_MSG_TYPE_DELIVER_ACK = 0x04; // Delivery Acknowledgment
+
+// SMS User Data Subparameters, as defined in 3GPP2 C.S0015-A v2.0, Table 4.5-1
+this.PDU_CDMA_MSG_USERDATA_MSG_ID = 0x00; // Message Identifier
+this.PDU_CDMA_MSG_USERDATA_BODY = 0x01; // User Data Body
+this.PDU_CDMA_MSG_USERDATA_TIMESTAMP = 0x03; // Message Center Time Stamp
+this.PDU_CDMA_MSG_USERDATA_REPLY_OPTION = 0x0A; // Reply Option
+this.PDU_CDMA_LANGUAGE_INDICATOR = 0x0D; // Language Indicator
+this.PDU_CDMA_MSG_USERDATA_CALLBACK_NUMBER = 0x0E; // Callback Number
+this.PDU_CDMA_MSG_USER_DATA_MSG_STATUS = 0x14; // Message Status
+
+// CDMA Language Indicator: Language groups
+// see 3GPP2 C.R1001-F table 9.2-1
+this.CB_CDMA_LANG_GROUP = [
+ null, "en", "fr", "es", "ja", "ko", "zh", "he"
+];
+
+// IS-91 Message Type, as defined in TIA/EIA/IS-91-A, Table 9
+this.PDU_CDMA_MSG_CODING_IS_91_TYPE_VOICEMAIL_STATUS = 0x82;
+this.PDU_CDMA_MSG_CODING_IS_91_TYPE_SMS_FULL = 0x83;
+this.PDU_CDMA_MSG_CODING_IS_91_TYPE_CLI = 0x84;
+this.PDU_CDMA_MSG_CODING_IS_91_TYPE_SMS = 0x85;
+
+// Information Record Type, reference from ril.h
+this.PDU_CDMA_INFO_REC_TYPE_DISPLAY = 0;
+this.PDU_CDMA_INFO_REC_TYPE_CALLED_PARTY_NUMBER = 1;
+this.PDU_CDMA_INFO_REC_TYPE_CALLING_PARTY_NUMBER = 2;
+this.PDU_CDMA_INFO_REC_TYPE_CONNECTED_NUMBER =3;
+this.PDU_CDMA_INFO_REC_TYPE_SIGNAL = 4;
+this.PDU_CDMA_INFO_REC_TYPE_REDIRECTING_NUMBER = 5;
+this.PDU_CDMA_INFO_REC_TYPE_LINE_CONTROL = 6;
+this.PDU_CDMA_INFO_REC_TYPE_EXTENDED_DISPLAY = 7;
+this.PDU_CDMA_INFO_REC_TYPE_T53_CLIR = 8;
+this.PDU_CDMA_INFO_REC_TYPE_T53_RELEASE = 9;
+this.PDU_CDMA_INFO_REC_TYPE_T53_AUDIO_CONTROL = 10;
+
+// Display type of extended display of information record,
+// as defined in C.S0005-F v1.0, Table 3.7.5.16-2
+this.INFO_REC_EXTENDED_DISPLAY_BLANK = 0x80;
+this.INFO_REC_EXTENDED_DISPLAY_SKIP = 0x81;
+this.INFO_REC_EXTENDED_DISPLAY_CONTINATION = 0x82;
+this.INFO_REC_EXTENDED_DISPLAY_CALLED_ADDRESS = 0x83;
+this.INFO_REC_EXTENDED_DISPLAY_CAUSE = 0x84;
+this.INFO_REC_EXTENDED_DISPLAY_PROGRESS_INDICATOR = 0x85;
+this.INFO_REC_EXTENDED_DISPLAY_NOTIFICATION_INDICATOR = 0x86;
+this.INFO_REC_EXTENDED_DISPLAY_PROMPT = 0x87;
+this.INFO_REC_EXTENDED_DISPLAY_ACCUMULATED_DIGITS = 0x88;
+this.INFO_REC_EXTENDED_DISPLAY_STATUS = 0x89;
+this.INFO_REC_EXTENDED_DISPLAY_INBAND = 0x8A;
+this.INFO_REC_EXTENDED_DISPLAY_CALLING_ADDRESS = 0x8B;
+this.INFO_REC_EXTENDED_DISPLAY_REASON = 0x8C;
+this.INFO_REC_EXTENDED_DISPLAY_CALLING_PARTY_NAME = 0x8D;
+this.INFO_REC_EXTENDED_DISPLAY_CALLED_PARTY_NAME = 0x8E;
+this.INFO_REC_EXTENDED_DISPLAY_ORIGINAL_CALLED_NAME = 0x8F;
+this.INFO_REC_EXTENDED_DISPLAY_REDIRECT_NAME = 0x90;
+this.INFO_REC_EXTENDED_DISPLAY_CONNECTED_NAME = 0x91;
+this.INFO_REC_EXTENDED_DISPLAY_ORIGINATING_RESTRICTIONS = 0x92;
+this.INFO_REC_EXTENDED_DISPLAY_DATE_TIME_OF_DAY = 0x93;
+this.INFO_REC_EXTENDED_DISPLAY_CALL_APPEARANCE_ID = 0x94;
+this.INFO_REC_EXTENDED_DISPLAY_FEATURE_ADDRESS = 0x95;
+this.INFO_REC_EXTENDED_DISPLAY_REDIRECTION_NAME = 0x96;
+this.INFO_REC_EXTENDED_DISPLAY_REDIRECTION_NUMBER = 0x97;
+this.INFO_REC_EXTENDED_DISPLAY_REDIRECTING_NUMBER = 0x98;
+this.INFO_REC_EXTENDED_DISPLAY_ORIGINAL_CALLED_NUMBER = 0x99;
+this.INFO_REC_EXTENDED_DISPLAY_CONNECTED_NUMBER = 0x9A;
+this.INFO_REC_EXTENDED_DISPLAY_TEXT = 0x9B;
+
+/**
+ * The table for MCC/MNC which the length of MNC is 3.
+ *
+ * This table is built from below links.
+ * - http://www.itu.int/pub/T-SP-E.212B-2013
+ * - http://en.wikipedia.org/wiki/Mobile_Network_Code
+ */
+this.PLMN_HAVING_3DIGITS_MNC = {
+ // Puerto Rico.
+ "330":
+ ["110", // América Móvil
+ "120" // PR Wireless
+ ],
+ // Trinidad and Tobago.
+ "374":
+ ["130", // Digicel Trinidad and Tobago Ltd.
+ "140" // LaqTel Ltd.
+ ],
+ // India.
+ "405":
+ ["000", // Shyam Telelink Ltd.
+ "005", // Reliance, Delhi
+ "006", // Reliance, Gujarat
+ "007", // Reliance, Haryana
+ "009", // Reliance, J&K
+ "010", // Reliance, Karnataka
+ "011", // Reliance, Kerala
+ "012", // Reliance, Andhra Pradesh
+ "013", // Reliance, Maharashtr
+ "014", // Reliance, Madhya Pradesh
+ "018", // Reliance, Punjab
+ "020", // Reliance, Tamilnadu
+ "021", // Reliance, UP (East)
+ "022", // Reliance, UP (West)
+ "025", // TATA DOCOMO, Andhra Pradesh
+ "026", // TATA DOCOMO, Assam
+ "027", // TATA DOCOMO, Bihar
+ "028", // TATA DOCOMO, Chennai
+ "029", // TATA DOCOMO, Delhi
+ "030", // TATA DOCOMO, Gujarat
+ "031", // TATA DOCOMO, Haryana
+ "032", // TATA DOCOMO, Himachal Pradesh
+ "033", // Reliance, Bihar
+ "034", // TATA DOCOMO, Kamataka
+ "035", // TATA DOCOMO, Kerala
+ "036", // TATA DOCOMO, Kolkata
+ "037", // TATA DOCOMO, Maharashtra
+ "038", // TATA DOCOMO, Madhya Pradesh
+ "039", // TATA DOCOMO, Mumbai
+ "040", // Reliance, Chennai
+ "041", // TATA DOCOMO, Orissa
+ "042", // TATA DOCOMO, Punjab
+ "043", // TATA DOCOMO, Rajasthan
+ "044", // TATA DOCOMO, Tamilnadu
+ "045", // TATA DOCOMO, UP (East)
+ "046", // TATA DOCOMO, UP (West)
+ "047", // TATA DOCOMO, West Bengal
+ "750", // Vodafone IN, J&K
+ "751", // Vodafone IN, Assam
+ "752", // Vodafone IN, Bihar
+ "753", // Vodafone IN, Orissa
+ "754", // Vodafone IN, Himachal Pradesh
+ "755", // Vodafone IN, North East
+ "756", // Vodafone IN, Madhya Pradesh & Chhattisgarh
+ "799", // Idea, MUMBAI
+ "800", // Aircell, Delhi
+ "801", // Aircell, Andhra Pradesh
+ "802", // Aircell, Gujarat
+ "803", // Aircell, Kamataka
+ "804", // Aircell, Maharashtra
+ "805", // Aircell, Mumbai
+ "806", // Aircell, Rajasthan
+ "807", // Aircell, Haryana
+ "808", // Aircell, Madhya Pradesh
+ "809", // Aircell, Kerala
+ "810", // Aircell, Uttar Pradesh (East)
+ "811", // Aircell, Uttar Pradesh (West)
+ "812", // Aircell, Punjab
+ "818", // Uninor, Uttar Pradesh (West)
+ "819", // Uninor, Andhra Pradesh
+ "820", // Uninor, Karnataka
+ "821", // Uninor, Kerala
+ "822", // Uninor, Kolkata
+ "824", // Videocon, Assam
+ "827", // Videocon, Gujarat
+ "834", // Videocon, Madhya Pradesh
+ "840", // Jio, West Bengal
+ "844", // Uninor, Delhi & NCR
+ "845", // IDEA, Assam
+ "846", // IDEA, Jammu & Kashmir
+ "847", // IDEA, Karnataka
+ "848", // IDEA, Kolkata
+ "849", // IDEA, North East
+ "850", // IDEA, Orissa
+ "851", // IDEA, Punjab
+ "852", // IDEA, Tamil Nadu
+ "853", // IDEA, West Bengal
+ "854", // Jio, Andra Pradesh
+ "855", // Jio, Assam
+ "856", // Jio, Bihar
+ "857", // Jio, Gujarat
+ "858", // Jio, Haryana
+ "859", // Jio, Himachal Pradesh
+ "860", // Jio, Jammu Kashmir
+ "861", // Jio, Karnataka
+ "862", // Jio, Kerala
+ "863", // Jio, Madhyya Pradesh
+ "864", // Jio, Maharashtra
+ "865", // Jio, North East
+ "866", // Jio, Orissa
+ "867", // Jio, Punjab
+ "868", // Jio, Rajasthan
+ "869", // Jio, Tamil Nadu Chennai
+ "870", // Jio, Uttar Pradesh West
+ "871", // Jio, Uttar Pradesh East
+ "872", // Jio, Delhi
+ "873", // Jio, Kolkatta
+ "874", // Jio, Mumbai
+ "875", // Uninor, Assam
+ "880", // Uninor, West Bengal
+ "881", // S Tel, Assam
+ "908", // IDEA, Andhra Pradesh
+ "909", // IDEA, Delhi
+ "910", // IDEA, Haryana
+ "911", // Etisalat, Maharashtra
+ "912", // Etisalat, Andhra Pradesh
+ "913", // Etisalat, Delhi & NCR
+ "914", // Etisalat, Gujarat
+ "917", // Etisalat, Kerala
+ "927", // Uninor, Gujarat
+ "929" // Uninor, Maharashtra
+ ],
+ // Malaysia.
+ "502":
+ ["150", // Tune Talk Sdn Bhd
+ "151", // Baraka Telecom Sdn Bhd (MVNE)
+ "152", // YTL Communications Sdn Bhd
+ "156" // Altel Communications Sdn Bhd
+ ],
+ // Brazil.
+ "724":
+ ["055" // Sercomtel
+ ]
+};
+
+/**
+ * The table for MCC which the length of MNC is 3
+ *
+ * This table is built from below links.
+ * - http://www.itu.int/pub/T-SP-E.212B-2013
+ * - http://en.wikipedia.org/wiki/Mobile_Network_Code
+ */
+this.MCC_TABLE_FOR_MNC_LENGTH_IS_3 = [
+ "302", // Canada
+ "310", // United States of America
+ "311", // United States of America
+ "312", // United States of America
+ "313", // United States of America
+ "316", // United States of America
+ "330", // Puerto Rico
+ "334", // Mexico
+ "338", // Jamaica
+ "342", // Barbados
+ "344", // Antigua and Barbuda
+ "346", // Cayman Islands
+ "348", // British Virgin Islands
+ "350", // Bermuda
+ "352", // Grenada
+ "354", // Montserrat
+ "356", // Saint Kitts and Nevis
+ "358", // Saint Lucia
+ "360", // Saint Vincent and the Grenadines
+ "365", // Anguilla
+ "366", // Dominica
+ "376", // Turks and Caicos Islands
+ "708", // Honduras
+ "722", // Argentina
+ "732", // Colombia
+ "750" // Falkland Islands (Malvinas)
+];
+
+// Supplementary service notifications, code2, as defined in 3GPP 27.007 7.17
+this.SUPP_SVC_NOTIFICATION_CODE2_PUT_ON_HOLD = 2;
+this.SUPP_SVC_NOTIFICATION_CODE2_RETRIEVED = 3;
+
+this.GECKO_SUPP_SVC_NOTIFICATION_REMOTE_HELD = "RemoteHeld";
+this.GECKO_SUPP_SVC_NOTIFICATION_REMOTE_RESUMED = "RemoteResumed";
+
+this.GECKO_SUPP_SVC_NOTIFICATION_FROM_CODE2 = {};
+GECKO_SUPP_SVC_NOTIFICATION_FROM_CODE2[SUPP_SVC_NOTIFICATION_CODE2_PUT_ON_HOLD] = GECKO_SUPP_SVC_NOTIFICATION_REMOTE_HELD;
+GECKO_SUPP_SVC_NOTIFICATION_FROM_CODE2[SUPP_SVC_NOTIFICATION_CODE2_RETRIEVED] = GECKO_SUPP_SVC_NOTIFICATION_REMOTE_RESUMED;
+
+/**
+ * The status for an Over-the-Air Service Provisioning / Over-the-Air
+ * Parameter Administration (OTASP/OTAPA) session.
+ *
+ * @see 3GPP2 C.S0016
+ */
+this.GECKO_OTA_STATUS_SPL_UNLOCKED = "spl_unlocked";
+this.GECKO_OTA_STATUS_SPC_RETRIES_EXCEEDED = "spc_retries_exceeded";
+this.GECKO_OTA_STATUS_A_KEY_EXCHANGED = "a_key_exchanged";
+this.GECKO_OTA_STATUS_SSD_UPDATED = "ssd_updated";
+this.GECKO_OTA_STATUS_NAM_DOWNLOADED = "nam_downloaded";
+this.GECKO_OTA_STATUS_MDN_DOWNLOADED = "mdn_downloaded";
+this.GECKO_OTA_STATUS_IMSI_DOWNLOADED = "imsi_downloaded";
+this.GECKO_OTA_STATUS_PRL_DOWNLOADED = "prl_downloaded";
+this.GECKO_OTA_STATUS_COMMITTED = "committed";
+this.GECKO_OTA_STATUS_OTAPA_STARTED = "otapa_started";
+this.GECKO_OTA_STATUS_OTAPA_STOPPED = "otapa_stopped";
+this.GECKO_OTA_STATUS_OTAPA_ABORTED = "otapa_aborted";
+this.CDMA_OTA_PROVISION_STATUS_TO_GECKO = [
+ GECKO_OTA_STATUS_SPL_UNLOCKED,
+ GECKO_OTA_STATUS_SPC_RETRIES_EXCEEDED,
+ GECKO_OTA_STATUS_A_KEY_EXCHANGED,
+ GECKO_OTA_STATUS_SSD_UPDATED,
+ GECKO_OTA_STATUS_NAM_DOWNLOADED,
+ GECKO_OTA_STATUS_MDN_DOWNLOADED,
+ GECKO_OTA_STATUS_IMSI_DOWNLOADED,
+ GECKO_OTA_STATUS_PRL_DOWNLOADED,
+ GECKO_OTA_STATUS_COMMITTED,
+ GECKO_OTA_STATUS_OTAPA_STARTED,
+ GECKO_OTA_STATUS_OTAPA_STOPPED,
+ GECKO_OTA_STATUS_OTAPA_ABORTED
+];
+
+// Allow this file to be imported via Components.utils.import().
+this.EXPORTED_SYMBOLS = Object.keys(this);
diff --git a/dom/system/gonk/ril_worker.js b/dom/system/gonk/ril_worker.js
new file mode 100644
index 000000000..0608a5be3
--- /dev/null
+++ b/dom/system/gonk/ril_worker.js
@@ -0,0 +1,15206 @@
+/* Copyright 2012 Mozilla Foundation and Mozilla contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * This file implements the RIL worker thread. It communicates with
+ * the main thread to provide a high-level API to the phone's RIL
+ * stack, and with the RIL IPC thread to communicate with the RIL
+ * device itself. These communication channels use message events as
+ * known from Web Workers:
+ *
+ * - postMessage()/"message" events for main thread communication
+ *
+ * - postRILMessage()/"RILMessageEvent" events for RIL IPC thread
+ * communication.
+ *
+ * The two main objects in this file represent individual parts of this
+ * communication chain:
+ *
+ * - RILMessageEvent -> Buf -> RIL -> postMessage() -> nsIRadioInterfaceLayer
+ * - nsIRadioInterfaceLayer -> postMessage() -> RIL -> Buf -> postRILMessage()
+ *
+ * Note: The code below is purposely lean on abstractions to be as lean in
+ * terms of object allocations. As a result, it may look more like C than
+ * JavaScript, and that's intended.
+ */
+
+/* global BufObject */
+/* global TelephonyRequestQueue */
+
+"use strict";
+
+importScripts("ril_consts.js");
+importScripts("resource://gre/modules/workers/require.js");
+importScripts("ril_worker_buf_object.js");
+importScripts("ril_worker_telephony_request_queue.js");
+
+// set to true in ril_consts.js to see debug messages
+var DEBUG = DEBUG_WORKER;
+var GLOBAL = this;
+
+if (!this.debug) {
+ // Debugging stub that goes nowhere.
+ this.debug = function debug(message) {
+ dump("RIL Worker: " + message + "\n");
+ };
+}
+
+// Timeout value for emergency callback mode.
+const EMERGENCY_CB_MODE_TIMEOUT_MS = 300000; // 5 mins = 300000 ms.
+
+const ICC_MAX_LINEAR_FIXED_RECORDS = 0xfe;
+
+const GET_CURRENT_CALLS_RETRY_MAX = 3;
+
+var RILQUIRKS_CALLSTATE_EXTRA_UINT32;
+var RILQUIRKS_REQUEST_USE_DIAL_EMERGENCY_CALL;
+var RILQUIRKS_SIM_APP_STATE_EXTRA_FIELDS;
+var RILQUIRKS_SIGNAL_EXTRA_INT32;
+var RILQUIRKS_AVAILABLE_NETWORKS_EXTRA_STRING;
+// Needed for call-waiting on Peak device
+var RILQUIRKS_EXTRA_UINT32_2ND_CALL;
+// On the emulator we support querying the number of lock retries
+var RILQUIRKS_HAVE_QUERY_ICC_LOCK_RETRY_COUNT;
+// Ril quirk to Send STK Profile Download
+var RILQUIRKS_SEND_STK_PROFILE_DOWNLOAD;
+// Ril quirk to attach data registration on demand.
+var RILQUIRKS_DATA_REGISTRATION_ON_DEMAND;
+// Ril quirk to control the uicc/data subscription.
+var RILQUIRKS_SUBSCRIPTION_CONTROL;
+// Ril quirk to describe the SMSC address format.
+var RILQUIRKS_SMSC_ADDRESS_FORMAT;
+
+/**
+ * The RIL state machine.
+ *
+ * This object communicates with rild via parcels and with the main thread
+ * via post messages. It maintains state about the radio, ICC, calls, etc.
+ * and acts upon state changes accordingly.
+ */
+function RilObject(aContext) {
+ this.context = aContext;
+
+ this.telephonyRequestQueue = new TelephonyRequestQueue(this);
+ this.currentConferenceState = CALL_STATE_UNKNOWN;
+ this._pendingSentSmsMap = {};
+ this.pendingNetworkType = {};
+ this._receivedSmsCbPagesMap = {};
+ this._getCurrentCallsRetryCount = 0;
+}
+RilObject.prototype = {
+ context: null,
+
+ /**
+ * RIL version.
+ */
+ version: null,
+
+ /**
+ * Call state of current conference group.
+ */
+ currentConferenceState: null,
+
+ /**
+ * Outgoing messages waiting for SMS-STATUS-REPORT.
+ */
+ _pendingSentSmsMap: null,
+
+ /**
+ * Marker object.
+ */
+ pendingNetworkType: null,
+
+ /**
+ * Global Cell Broadcast switch.
+ */
+ cellBroadcastDisabled: false,
+
+ /**
+ * Parsed Cell Broadcast search lists.
+ * cellBroadcastConfigs.MMI should be preserved over rild reset.
+ */
+ cellBroadcastConfigs: null,
+ mergedCellBroadcastConfig: null,
+
+ _receivedSmsCbPagesMap: null,
+
+ initRILState: function() {
+ /**
+ * One of the RADIO_STATE_* constants.
+ */
+ this.radioState = GECKO_RADIOSTATE_UNKNOWN;
+
+ /**
+ * True if we are on a CDMA phone.
+ */
+ this._isCdma = false;
+
+ /**
+ * True if we are in emergency callback mode.
+ */
+ this._isInEmergencyCbMode = false;
+
+ /**
+ * Set when radio is ready but radio tech is unknown. That is, we are
+ * waiting for REQUEST_VOICE_RADIO_TECH
+ */
+ this._waitingRadioTech = false;
+
+ /**
+ * Card state
+ */
+ this.cardState = GECKO_CARDSTATE_UNINITIALIZED;
+
+ /**
+ * Device Identities including IMEI, IMEISV, ESN and MEID.
+ */
+ this.deviceIdentities = null;
+
+ /**
+ * ICC information that is not exposed to Gaia.
+ */
+ this.iccInfoPrivate = {};
+
+ /**
+ * ICC information, such as MSISDN, MCC, MNC, SPN...etc.
+ */
+ this.iccInfo = {};
+
+ /**
+ * CDMA specific information. ex. CDMA Network ID, CDMA System ID... etc.
+ */
+ this.cdmaHome = null;
+
+ /**
+ * Application identification for apps in ICC.
+ */
+ this.aid = null;
+
+ /**
+ * Application type for apps in ICC.
+ */
+ this.appType = null;
+
+ this.networkSelectionMode = GECKO_NETWORK_SELECTION_UNKNOWN;
+
+ this.voiceRegistrationState = {};
+ this.dataRegistrationState = {};
+
+ /**
+ * List of strings identifying the network operator.
+ */
+ this.operator = null;
+
+ /**
+ * String containing the baseband version.
+ */
+ this.basebandVersion = null;
+
+ // Clean up currentCalls: rild might have restarted.
+ this.sendChromeMessage({
+ rilMessageType: "currentCalls",
+ calls: {}
+ });
+
+ // Don't clean up this._pendingSentSmsMap
+ // because on rild restart: we may continue with the pending segments.
+
+ /**
+ * Whether or not the multiple requests in requestNetworkInfo() are currently
+ * being processed
+ */
+ this._processingNetworkInfo = false;
+
+ /**
+ * Multiple requestNetworkInfo() in a row before finishing the first
+ * request, hence we need to fire requestNetworkInfo() again after
+ * gathering all necessary stuffs. This is to make sure that ril_worker
+ * gets precise network information.
+ */
+ this._needRepollNetworkInfo = false;
+
+ /**
+ * Pending messages to be send in batch from requestNetworkInfo()
+ */
+ this._pendingNetworkInfo = {rilMessageType: "networkinfochanged"};
+
+ /**
+ * Cell Broadcast Search Lists.
+ */
+ let cbmmi = this.cellBroadcastConfigs && this.cellBroadcastConfigs.MMI;
+ this.cellBroadcastConfigs = {
+ MMI: cbmmi || null
+ };
+ this.mergedCellBroadcastConfig = null;
+
+ /**
+ * True when the request to report SMS Memory Status is pending.
+ */
+ this.pendingToReportSmsMemoryStatus = false;
+ this.smsStorageAvailable = true;
+ },
+
+ /**
+ * Parse an integer from a string, falling back to a default value
+ * if the the provided value is not a string or does not contain a valid
+ * number.
+ *
+ * @param string
+ * String to be parsed.
+ * @param defaultValue [optional]
+ * Default value to be used.
+ * @param radix [optional]
+ * A number that represents the numeral system to be used. Default 10.
+ */
+ parseInt: function(string, defaultValue, radix) {
+ let number = parseInt(string, radix || 10);
+ if (!isNaN(number)) {
+ return number;
+ }
+ if (defaultValue === undefined) {
+ defaultValue = null;
+ }
+ return defaultValue;
+ },
+
+
+ /**
+ * Outgoing requests to the RIL. These can be triggered from the
+ * main thread via messages that look like this:
+ *
+ * {rilMessageType: "methodName",
+ * extra: "parameters",
+ * go: "here"}
+ *
+ * So if one of the following methods takes arguments, it takes only one,
+ * an object, which then contains all of the parameters as attributes.
+ * The "@param" documentation is to be interpreted accordingly.
+ */
+
+ /**
+ * Retrieve the ICC's status.
+ */
+ getICCStatus: function() {
+ this.context.Buf.simpleRequest(REQUEST_GET_SIM_STATUS);
+ },
+
+ /**
+ * Helper function for unlocking ICC locks.
+ */
+ iccUnlockCardLock: function(options) {
+ switch (options.lockType) {
+ case GECKO_CARDLOCK_PIN:
+ this.enterICCPIN(options);
+ break;
+ case GECKO_CARDLOCK_PIN2:
+ this.enterICCPIN2(options);
+ break;
+ case GECKO_CARDLOCK_PUK:
+ this.enterICCPUK(options);
+ break;
+ case GECKO_CARDLOCK_PUK2:
+ this.enterICCPUK2(options);
+ break;
+ case GECKO_CARDLOCK_NCK:
+ case GECKO_CARDLOCK_NSCK:
+ case GECKO_CARDLOCK_NCK1:
+ case GECKO_CARDLOCK_NCK2:
+ case GECKO_CARDLOCK_HNCK:
+ case GECKO_CARDLOCK_CCK:
+ case GECKO_CARDLOCK_SPCK:
+ case GECKO_CARDLOCK_PCK:
+ case GECKO_CARDLOCK_RCCK:
+ case GECKO_CARDLOCK_RSPCK:
+ case GECKO_CARDLOCK_NCK_PUK:
+ case GECKO_CARDLOCK_NSCK_PUK:
+ case GECKO_CARDLOCK_NCK1_PUK:
+ case GECKO_CARDLOCK_NCK2_PUK:
+ case GECKO_CARDLOCK_HNCK_PUK:
+ case GECKO_CARDLOCK_CCK_PUK:
+ case GECKO_CARDLOCK_SPCK_PUK:
+ case GECKO_CARDLOCK_PCK_PUK:
+ case GECKO_CARDLOCK_RCCK_PUK: // Fall through.
+ case GECKO_CARDLOCK_RSPCK_PUK:
+ this.enterDepersonalization(options);
+ break;
+ default:
+ options.errorMsg = GECKO_ERROR_REQUEST_NOT_SUPPORTED;
+ this.sendChromeMessage(options);
+ }
+ },
+
+ /**
+ * Enter a PIN to unlock the ICC.
+ *
+ * @param password
+ * String containing the PIN.
+ * @param [optional] aid
+ * AID value.
+ */
+ enterICCPIN: function(options) {
+ let Buf = this.context.Buf;
+ Buf.newParcel(REQUEST_ENTER_SIM_PIN, options);
+ Buf.writeInt32(2);
+ Buf.writeString(options.password);
+ Buf.writeString(options.aid || this.aid);
+ Buf.sendParcel();
+ },
+
+ /**
+ * Enter a PIN2 to unlock the ICC.
+ *
+ * @param password
+ * String containing the PIN2.
+ * @param [optional] aid
+ * AID value.
+ */
+ enterICCPIN2: function(options) {
+ let Buf = this.context.Buf;
+ Buf.newParcel(REQUEST_ENTER_SIM_PIN2, options);
+ Buf.writeInt32(2);
+ Buf.writeString(options.password);
+ Buf.writeString(options.aid || this.aid);
+ Buf.sendParcel();
+ },
+
+ /**
+ * Requests a network personalization be deactivated.
+ *
+ * @param personlization
+ * One of CARD_PERSOSUBSTATE_*
+ * @param password
+ * String containing the password.
+ */
+ enterDepersonalization: function(options) {
+ let Buf = this.context.Buf;
+ Buf.newParcel(REQUEST_ENTER_NETWORK_DEPERSONALIZATION_CODE, options);
+ Buf.writeInt32(1);
+ Buf.writeString(options.password);
+ Buf.sendParcel();
+ },
+
+ /**
+ * Change the current ICC PIN number.
+ *
+ * @param password
+ * String containing the old PIN value
+ * @param newPassword
+ * String containing the new PIN value
+ * @param [optional] aid
+ * AID value.
+ */
+ changeICCPIN: function(options) {
+ let Buf = this.context.Buf;
+ Buf.newParcel(REQUEST_CHANGE_SIM_PIN, options);
+ Buf.writeInt32(3);
+ Buf.writeString(options.password);
+ Buf.writeString(options.newPassword);
+ Buf.writeString(options.aid || this.aid);
+ Buf.sendParcel();
+ },
+
+ /**
+ * Change the current ICC PIN2 number.
+ *
+ * @param password
+ * String containing the old PIN2 value
+ * @param newPassword
+ * String containing the new PIN2 value
+ * @param [optional] aid
+ * AID value.
+ */
+ changeICCPIN2: function(options) {
+ let Buf = this.context.Buf;
+ Buf.newParcel(REQUEST_CHANGE_SIM_PIN2, options);
+ Buf.writeInt32(3);
+ Buf.writeString(options.password);
+ Buf.writeString(options.newPassword);
+ Buf.writeString(options.aid || this.aid);
+ Buf.sendParcel();
+ },
+
+ /**
+ * Supplies ICC PUK and a new PIN to unlock the ICC.
+ *
+ * @param password
+ * String containing the PUK value.
+ * @param newPassword
+ * String containing the new PIN value.
+ * @param [optional] aid
+ * AID value.
+ */
+ enterICCPUK: function(options) {
+ let Buf = this.context.Buf;
+ Buf.newParcel(REQUEST_ENTER_SIM_PUK, options);
+ Buf.writeInt32(3);
+ Buf.writeString(options.password);
+ Buf.writeString(options.newPin);
+ Buf.writeString(options.aid || this.aid);
+ Buf.sendParcel();
+ },
+
+ /**
+ * Supplies ICC PUK2 and a new PIN2 to unlock the ICC.
+ *
+ * @param password
+ * String containing the PUK2 value.
+ * @param newPassword
+ * String containing the new PIN2 value.
+ * @param [optional] aid
+ * AID value.
+ */
+ enterICCPUK2: function(options) {
+ let Buf = this.context.Buf;
+ Buf.newParcel(REQUEST_ENTER_SIM_PUK2, options);
+ Buf.writeInt32(3);
+ Buf.writeString(options.password);
+ Buf.writeString(options.newPin);
+ Buf.writeString(options.aid || this.aid);
+ Buf.sendParcel();
+ },
+
+ /**
+ * Helper function for changing ICC locks.
+ */
+ iccChangeCardLockPassword: function(options) {
+ switch (options.lockType) {
+ case GECKO_CARDLOCK_PIN:
+ this.changeICCPIN(options);
+ break;
+ case GECKO_CARDLOCK_PIN2:
+ this.changeICCPIN2(options);
+ break;
+ default:
+ options.errorMsg = GECKO_ERROR_REQUEST_NOT_SUPPORTED;
+ this.sendChromeMessage(options);
+ }
+ },
+
+ /**
+ * Helper function for setting the state of ICC locks.
+ */
+ iccSetCardLockEnabled: function(options) {
+ switch (options.lockType) {
+ case GECKO_CARDLOCK_PIN: // Fall through.
+ case GECKO_CARDLOCK_FDN:
+ options.facility = GECKO_CARDLOCK_TO_FACILITY[options.lockType];
+ break;
+ default:
+ options.errorMsg = GECKO_ERROR_REQUEST_NOT_SUPPORTED;
+ this.sendChromeMessage(options);
+ return;
+ }
+
+ options.serviceClass = ICC_SERVICE_CLASS_VOICE |
+ ICC_SERVICE_CLASS_DATA |
+ ICC_SERVICE_CLASS_FAX;
+ this.setICCFacilityLock(options);
+ },
+
+ /**
+ * Helper function for fetching the state of ICC locks.
+ */
+ iccGetCardLockEnabled: function(options) {
+ switch (options.lockType) {
+ case GECKO_CARDLOCK_PIN: // Fall through.
+ case GECKO_CARDLOCK_FDN:
+ options.facility = GECKO_CARDLOCK_TO_FACILITY[options.lockType];
+ break;
+ default:
+ options.errorMsg = GECKO_ERROR_REQUEST_NOT_SUPPORTED;
+ this.sendChromeMessage(options);
+ return;
+ }
+
+ options.password = ""; // For query no need to provide pin.
+ options.serviceClass = ICC_SERVICE_CLASS_VOICE |
+ ICC_SERVICE_CLASS_DATA |
+ ICC_SERVICE_CLASS_FAX;
+ this.queryICCFacilityLock(options);
+ },
+
+ /**
+ * Helper function for fetching the number of unlock retries of ICC locks.
+ *
+ * We only query the retry count when we're on the emulator. The phones do
+ * not support the request id and their rild doesn't return an error.
+ */
+ iccGetCardLockRetryCount: function(options) {
+ if (!RILQUIRKS_HAVE_QUERY_ICC_LOCK_RETRY_COUNT) {
+ // Only the emulator supports this request.
+ options.errorMsg = GECKO_ERROR_REQUEST_NOT_SUPPORTED;
+ this.sendChromeMessage(options);
+ return;
+ }
+
+ switch (options.lockType) {
+ case GECKO_CARDLOCK_PIN:
+ case GECKO_CARDLOCK_PIN2:
+ case GECKO_CARDLOCK_PUK:
+ case GECKO_CARDLOCK_PUK2:
+ case GECKO_CARDLOCK_NCK:
+ case GECKO_CARDLOCK_NSCK:
+ case GECKO_CARDLOCK_CCK: // Fall through.
+ case GECKO_CARDLOCK_SPCK:
+ // TODO: Bug 1116072: identify the mapping between RIL_PERSOSUBSTATE_SIM_SIM
+ // @ ril.h and TS 27.007, clause 8.65 for GECKO_CARDLOCK_SPCK.
+ options.selCode = GECKO_CARDLOCK_TO_SEL_CODE[options.lockType];
+ break;
+ default:
+ options.errorMsg = GECKO_ERROR_REQUEST_NOT_SUPPORTED;
+ this.sendChromeMessage(options);
+ return;
+ }
+
+ this.queryICCLockRetryCount(options);
+ },
+
+ /**
+ * Query ICC lock retry count.
+ *
+ * @param selCode
+ * One of ICC_SEL_CODE_*.
+ * @param serviceClass
+ * One of ICC_SERVICE_CLASS_*.
+ */
+ queryICCLockRetryCount: function(options) {
+ let Buf = this.context.Buf;
+ Buf.newParcel(REQUEST_GET_UNLOCK_RETRY_COUNT, options);
+ Buf.writeInt32(1);
+ Buf.writeString(options.selCode);
+ Buf.sendParcel();
+ },
+
+ /**
+ * Query ICC facility lock.
+ *
+ * @param facility
+ * One of ICC_CB_FACILITY_*.
+ * @param password
+ * Password for the facility, or "" if not required.
+ * @param serviceClass
+ * One of ICC_SERVICE_CLASS_*.
+ * @param [optional] aid
+ * AID value.
+ */
+ queryICCFacilityLock: function(options) {
+ let Buf = this.context.Buf;
+ Buf.newParcel(REQUEST_QUERY_FACILITY_LOCK, options);
+ Buf.writeInt32(4);
+ Buf.writeString(options.facility);
+ Buf.writeString(options.password);
+ Buf.writeString(options.serviceClass.toString());
+ Buf.writeString(options.aid || this.aid);
+ Buf.sendParcel();
+ },
+
+ /**
+ * Set ICC facility lock.
+ *
+ * @param facility
+ * One of ICC_CB_FACILITY_*.
+ * @param enabled
+ * true to enable, false to disable.
+ * @param password
+ * Password for the facility, or "" if not required.
+ * @param serviceClass
+ * One of ICC_SERVICE_CLASS_*.
+ * @param [optional] aid
+ * AID value.
+ */
+ setICCFacilityLock: function(options) {
+ let Buf = this.context.Buf;
+ Buf.newParcel(REQUEST_SET_FACILITY_LOCK, options);
+ Buf.writeInt32(5);
+ Buf.writeString(options.facility);
+ Buf.writeString(options.enabled ? "1" : "0");
+ Buf.writeString(options.password);
+ Buf.writeString(options.serviceClass.toString());
+ Buf.writeString(options.aid || this.aid);
+ Buf.sendParcel();
+ },
+
+ /**
+ * Request an ICC I/O operation.
+ *
+ * See TS 27.007 "restricted SIM" operation, "AT Command +CRSM".
+ * The sequence is in the same order as how libril reads this parcel,
+ * see the struct RIL_SIM_IO_v5 or RIL_SIM_IO_v6 defined in ril.h
+ *
+ * @param command
+ * The I/O command, one of the ICC_COMMAND_* constants.
+ * @param fileId
+ * The file to operate on, one of the ICC_EF_* constants.
+ * @param pathId
+ * String type, check the 'pathid' parameter from TS 27.007 +CRSM.
+ * @param p1, p2, p3
+ * Arbitrary integer parameters for the command.
+ * @param [optional] dataWriter
+ * The function for writing string parameter for the ICC_COMMAND_UPDATE_RECORD.
+ * @param [optional] pin2
+ * String containing the PIN2.
+ * @param [optional] aid
+ * AID value.
+ */
+ iccIO: function(options) {
+ let Buf = this.context.Buf;
+ Buf.newParcel(REQUEST_SIM_IO, options);
+ Buf.writeInt32(options.command);
+ Buf.writeInt32(options.fileId);
+ Buf.writeString(options.pathId);
+ Buf.writeInt32(options.p1);
+ Buf.writeInt32(options.p2);
+ Buf.writeInt32(options.p3);
+
+ // Write data.
+ if (options.command == ICC_COMMAND_UPDATE_RECORD &&
+ options.dataWriter) {
+ options.dataWriter(options.p3);
+ } else {
+ Buf.writeString(null);
+ }
+
+ // Write pin2.
+ if (options.command == ICC_COMMAND_UPDATE_RECORD &&
+ options.pin2) {
+ Buf.writeString(options.pin2);
+ } else {
+ Buf.writeString(null);
+ }
+
+ Buf.writeString(options.aid || this.aid);
+ Buf.sendParcel();
+ },
+
+ /**
+ * Get IMSI.
+ *
+ * @param [optional] aid
+ * AID value.
+ */
+ getIMSI: function(aid) {
+ let Buf = this.context.Buf;
+ Buf.newParcel(REQUEST_GET_IMSI);
+ Buf.writeInt32(1);
+ Buf.writeString(aid || this.aid);
+ Buf.sendParcel();
+ },
+
+ /**
+ * Retrieve ICC's GID1 field.
+ */
+ getGID1: function(options) {
+ options.gid1 = this.iccInfoPrivate.gid1;
+ this.sendChromeMessage(options);
+ },
+
+ /**
+ * Read UICC Phonebook contacts.
+ *
+ * @param contactType
+ * One of GECKO_CARDCONTACT_TYPE_*.
+ * @param requestId
+ * Request id from RadioInterfaceLayer.
+ */
+ readICCContacts: function(options) {
+ if (!this.appType) {
+ options.errorMsg = CONTACT_ERR_REQUEST_NOT_SUPPORTED;
+ this.sendChromeMessage(options);
+ return;
+ }
+
+ this.context.ICCContactHelper.readICCContacts(
+ this.appType,
+ options.contactType,
+ function onsuccess(contacts) {
+ for (let i = 0; i < contacts.length; i++) {
+ let contact = contacts[i];
+ let pbrIndex = contact.pbrIndex || 0;
+ let recordIndex = pbrIndex * ICC_MAX_LINEAR_FIXED_RECORDS + contact.recordId;
+ contact.contactId = this.iccInfo.iccid + recordIndex;
+ }
+ // Reuse 'options' to get 'requestId' and 'contactType'.
+ options.contacts = contacts;
+ this.sendChromeMessage(options);
+ }.bind(this),
+ function onerror(errorMsg) {
+ options.errorMsg = errorMsg;
+ this.sendChromeMessage(options);
+ }.bind(this));
+ },
+
+ /**
+ * Update UICC Phonebook.
+ *
+ * @param contactType One of GECKO_CARDCONTACT_TYPE_*.
+ * @param contact The contact will be updated.
+ * @param pin2 PIN2 is required for updating FDN.
+ * @param requestId Request id from RadioInterfaceLayer.
+ */
+ updateICCContact: function(options) {
+ let onsuccess = function onsuccess(updatedContact) {
+ let recordIndex =
+ updatedContact.pbrIndex * ICC_MAX_LINEAR_FIXED_RECORDS + updatedContact.recordId;
+ updatedContact.contactId = this.iccInfo.iccid + recordIndex;
+ options.contact = updatedContact;
+ // Reuse 'options' to get 'requestId' and 'contactType'.
+ this.sendChromeMessage(options);
+ }.bind(this);
+
+ let onerror = function onerror(errorMsg) {
+ options.errorMsg = errorMsg;
+ this.sendChromeMessage(options);
+ }.bind(this);
+
+ if (!this.appType || !options.contact) {
+ onerror(CONTACT_ERR_REQUEST_NOT_SUPPORTED );
+ return;
+ }
+
+ let contact = options.contact;
+ let iccid = this.iccInfo.iccid;
+ let isValidRecordId = false;
+ if (typeof contact.contactId === "string" &&
+ contact.contactId.startsWith(iccid)) {
+ let recordIndex = contact.contactId.substring(iccid.length);
+ contact.pbrIndex = Math.floor(recordIndex / ICC_MAX_LINEAR_FIXED_RECORDS);
+ contact.recordId = recordIndex % ICC_MAX_LINEAR_FIXED_RECORDS;
+ isValidRecordId = contact.recordId > 0 && contact.recordId < 0xff;
+ }
+
+ if (DEBUG) {
+ this.context.debug("Update ICC Contact " + JSON.stringify(contact));
+ }
+
+ let ICCContactHelper = this.context.ICCContactHelper;
+ // If contact has 'recordId' property, updates corresponding record.
+ // If not, inserts the contact into a free record.
+ if (isValidRecordId) {
+ ICCContactHelper.updateICCContact(
+ this.appType, options.contactType, contact, options.pin2, onsuccess, onerror);
+ } else {
+ ICCContactHelper.addICCContact(
+ this.appType, options.contactType, contact, options.pin2, onsuccess, onerror);
+ }
+ },
+
+ /**
+ * Check if operator name needs to be overriden by current voiceRegistrationState
+ * , EFOPL and EFPNN. See 3GPP TS 31.102 clause 4.2.58 EFPNN and 4.2.59 EFOPL
+ * for detail.
+ *
+ * @return true if operator name is overridden, false otherwise.
+ */
+ overrideICCNetworkName: function() {
+ if (!this.operator) {
+ return false;
+ }
+
+ // We won't get network name using PNN and OPL if voice registration isn't
+ // ready.
+ if (!this.voiceRegistrationState.cell ||
+ this.voiceRegistrationState.cell.gsmLocationAreaCode == -1) {
+ return false;
+ }
+
+ let ICCUtilsHelper = this.context.ICCUtilsHelper;
+ let networkName = ICCUtilsHelper.getNetworkNameFromICC(
+ this.operator.mcc,
+ this.operator.mnc,
+ this.voiceRegistrationState.cell.gsmLocationAreaCode);
+
+ if (!networkName) {
+ return false;
+ }
+
+ if (DEBUG) {
+ this.context.debug("Operator names will be overriden: " +
+ "longName = " + networkName.fullName + ", " +
+ "shortName = " + networkName.shortName);
+ }
+
+ this.operator.longName = networkName.fullName;
+ this.operator.shortName = networkName.shortName;
+
+ this._sendNetworkInfoMessage(NETWORK_INFO_OPERATOR, this.operator);
+ return true;
+ },
+
+ /**
+ * Request the phone's radio to be enabled or disabled.
+ *
+ * @param enabled
+ * Boolean indicating the desired state.
+ */
+ setRadioEnabled: function(options) {
+ let Buf = this.context.Buf;
+ Buf.newParcel(REQUEST_RADIO_POWER, options);
+ Buf.writeInt32(1);
+ Buf.writeInt32(options.enabled ? 1 : 0);
+ Buf.sendParcel();
+ },
+
+ /**
+ * Query call waiting status.
+ *
+ */
+ queryCallWaiting: function(options) {
+ let Buf = this.context.Buf;
+ Buf.newParcel(REQUEST_QUERY_CALL_WAITING, options);
+ Buf.writeInt32(1);
+ // As per 3GPP TS 24.083, section 1.6 UE doesn't need to send service
+ // class parameter in call waiting interrogation to network.
+ Buf.writeInt32(ICC_SERVICE_CLASS_NONE);
+ Buf.sendParcel();
+ },
+
+ /**
+ * Set call waiting status.
+ *
+ * @param enabled
+ * Boolean indicating the desired waiting status.
+ * @param serviceClass
+ * One of ICC_SERVICE_CLASS_* constants.
+ */
+ setCallWaiting: function(options) {
+ let Buf = this.context.Buf;
+ Buf.newParcel(REQUEST_SET_CALL_WAITING, options);
+ Buf.writeInt32(2);
+ Buf.writeInt32(options.enabled ? 1 : 0);
+ Buf.writeInt32(options.serviceClass);
+ Buf.sendParcel();
+ },
+
+ /**
+ * Queries current CLIP status.
+ */
+ queryCLIP: function(options) {
+ this.context.Buf.simpleRequest(REQUEST_QUERY_CLIP, options);
+ },
+
+ /**
+ * Queries current CLIR status.
+ *
+ */
+ getCLIR: function(options) {
+ this.context.Buf.simpleRequest(REQUEST_GET_CLIR, options);
+ },
+
+ /**
+ * Enables or disables the presentation of the calling line identity (CLI) to
+ * the called party when originating a call.
+ *
+ * @param options.clirMode
+ * One of the CLIR_* constants.
+ */
+ setCLIR: function(options) {
+ let Buf = this.context.Buf;
+ Buf.newParcel(REQUEST_SET_CLIR, options);
+ Buf.writeInt32(1);
+ Buf.writeInt32(options.clirMode);
+ Buf.sendParcel();
+ },
+
+ /**
+ * Set screen state.
+ *
+ * @param on
+ * Boolean indicating whether the screen should be on or off.
+ */
+ setScreenState: function(options) {
+ let Buf = this.context.Buf;
+ Buf.newParcel(REQUEST_SCREEN_STATE);
+ Buf.writeInt32(1);
+ Buf.writeInt32(options.on ? 1 : 0);
+ Buf.sendParcel();
+ },
+
+ getVoiceRegistrationState: function() {
+ this.context.Buf.simpleRequest(REQUEST_VOICE_REGISTRATION_STATE);
+ },
+
+ getVoiceRadioTechnology: function() {
+ this.context.Buf.simpleRequest(REQUEST_VOICE_RADIO_TECH);
+ },
+
+ getDataRegistrationState: function() {
+ this.context.Buf.simpleRequest(REQUEST_DATA_REGISTRATION_STATE);
+ },
+
+ getOperator: function() {
+ this.context.Buf.simpleRequest(REQUEST_OPERATOR);
+ },
+
+ /**
+ * Set the preferred network type.
+ *
+ * @param options An object contains a valid value of
+ * RIL_PREFERRED_NETWORK_TYPE_TO_GECKO as its `type` attribute.
+ */
+ setPreferredNetworkType: function(options) {
+ let networkType = options.type;
+ if (networkType < 0 || networkType >= RIL_PREFERRED_NETWORK_TYPE_TO_GECKO.length) {
+ options.errorMsg = GECKO_ERROR_INVALID_PARAMETER;
+ this.sendChromeMessage(options);
+ return;
+ }
+
+ let Buf = this.context.Buf;
+ Buf.newParcel(REQUEST_SET_PREFERRED_NETWORK_TYPE, options);
+ Buf.writeInt32(1);
+ Buf.writeInt32(networkType);
+ Buf.sendParcel();
+ },
+
+ /**
+ * Get the preferred network type.
+ */
+ getPreferredNetworkType: function(options) {
+ this.context.Buf.simpleRequest(REQUEST_GET_PREFERRED_NETWORK_TYPE, options);
+ },
+
+ /**
+ * Request neighboring cell ids in GSM network.
+ */
+ getNeighboringCellIds: function(options) {
+ this.context.Buf.simpleRequest(REQUEST_GET_NEIGHBORING_CELL_IDS, options);
+ },
+
+ /**
+ * Request all of the current cell information known to the radio.
+ */
+ getCellInfoList: function(options) {
+ this.context.Buf.simpleRequest(REQUEST_GET_CELL_INFO_LIST, options);
+ },
+
+ /**
+ * Request various states about the network.
+ */
+ requestNetworkInfo: function() {
+ if (this._processingNetworkInfo) {
+ if (DEBUG) {
+ this.context.debug("Network info requested, but we're already " +
+ "requesting network info.");
+ }
+ this._needRepollNetworkInfo = true;
+ return;
+ }
+
+ if (DEBUG) this.context.debug("Requesting network info");
+
+ this._processingNetworkInfo = true;
+ this.getVoiceRegistrationState();
+ this.getDataRegistrationState(); //TODO only GSM
+ this.getOperator();
+ this.getNetworkSelectionMode();
+ this.getSignalStrength();
+ },
+
+ /**
+ * Get the available networks
+ */
+ getAvailableNetworks: function(options) {
+ if (DEBUG) this.context.debug("Getting available networks");
+ let Buf = this.context.Buf;
+ Buf.newParcel(REQUEST_QUERY_AVAILABLE_NETWORKS, options);
+ Buf.sendParcel();
+ },
+
+ /**
+ * Request the radio's network selection mode
+ */
+ getNetworkSelectionMode: function() {
+ if (DEBUG) this.context.debug("Getting network selection mode");
+ this.context.Buf.simpleRequest(REQUEST_QUERY_NETWORK_SELECTION_MODE);
+ },
+
+ /**
+ * Tell the radio to automatically choose a voice/data network
+ */
+ selectNetworkAuto: function(options) {
+ if (DEBUG) this.context.debug("Setting automatic network selection");
+ this.context.Buf.simpleRequest(REQUEST_SET_NETWORK_SELECTION_AUTOMATIC, options);
+ },
+
+ /**
+ * Set the roaming preference mode
+ */
+ setRoamingPreference: function(options) {
+ let Buf = this.context.Buf;
+ Buf.newParcel(REQUEST_CDMA_SET_ROAMING_PREFERENCE, options);
+ Buf.writeInt32(1);
+ Buf.writeInt32(options.mode);
+ Buf.sendParcel();
+ },
+
+ /**
+ * Get the roaming preference mode
+ */
+ queryRoamingPreference: function(options) {
+ this.context.Buf.simpleRequest(REQUEST_CDMA_QUERY_ROAMING_PREFERENCE, options);
+ },
+
+ /**
+ * Set the voice privacy mode
+ */
+ setVoicePrivacyMode: function(options) {
+ let Buf = this.context.Buf;
+ Buf.newParcel(REQUEST_CDMA_SET_PREFERRED_VOICE_PRIVACY_MODE, options);
+ Buf.writeInt32(1);
+ Buf.writeInt32(options.enabled ? 1 : 0);
+ Buf.sendParcel();
+ },
+
+ /**
+ * Get the voice privacy mode
+ */
+ queryVoicePrivacyMode: function(options) {
+ this.context.Buf.simpleRequest(REQUEST_CDMA_QUERY_PREFERRED_VOICE_PRIVACY_MODE, options);
+ },
+
+ /**
+ * Open Logical UICC channel (aid) for Secure Element access
+ */
+ iccOpenChannel: function(options) {
+ let Buf = this.context.Buf;
+ Buf.newParcel(REQUEST_SIM_OPEN_CHANNEL, options);
+ Buf.writeString(options.aid);
+ Buf.sendParcel();
+ },
+
+ /**
+ * Exchange APDU data on an open Logical UICC channel
+ */
+ iccExchangeAPDU: function(options) {
+ let Buf = this.context.Buf;
+ Buf.newParcel(REQUEST_SIM_TRANSMIT_APDU_CHANNEL, options);
+ Buf.writeInt32(options.channel);
+ Buf.writeInt32(options.apdu.cla);
+ Buf.writeInt32(options.apdu.command);
+ Buf.writeInt32(options.apdu.p1);
+ Buf.writeInt32(options.apdu.p2);
+ Buf.writeInt32(options.apdu.p3);
+ Buf.writeString(options.apdu.data);
+ Buf.sendParcel();
+ },
+
+ /**
+ * Close Logical UICC channel
+ */
+ iccCloseChannel: function(options) {
+ let Buf = this.context.Buf;
+ Buf.newParcel(REQUEST_SIM_CLOSE_CHANNEL, options);
+ Buf.writeInt32(1);
+ Buf.writeInt32(options.channel);
+ Buf.sendParcel();
+ },
+
+ /**
+ * Get UICC service state
+ */
+ getIccServiceState: function(options) {
+ switch (options.service) {
+ case GECKO_CARDSERVICE_FDN:
+ let ICCUtilsHelper = this.context.ICCUtilsHelper;
+ options.result = ICCUtilsHelper.isICCServiceAvailable("FDN");
+ break;
+ default:
+ options.errorMsg = GECKO_ERROR_REQUEST_NOT_SUPPORTED;
+ break;
+ }
+ this.sendChromeMessage(options);
+ },
+
+ /**
+ * Enable/Disable UICC subscription
+ */
+ setUiccSubscription: function(options) {
+ if (DEBUG) {
+ this.context.debug("setUiccSubscription: " + JSON.stringify(options));
+ }
+
+ let Buf = this.context.Buf;
+ Buf.newParcel(REQUEST_SET_UICC_SUBSCRIPTION, options);
+ Buf.writeInt32(this.context.clientId);
+ Buf.writeInt32(options.appIndex);
+ Buf.writeInt32(this.context.clientId);
+ Buf.writeInt32(options.enabled ? 1 : 0);
+ Buf.sendParcel();
+ },
+
+ /**
+ * Tell the radio to choose a specific voice/data network
+ */
+ selectNetwork: function(options) {
+ if (DEBUG) {
+ this.context.debug("Setting manual network selection: " +
+ options.mcc + ", " + options.mnc);
+ }
+
+ let numeric = (options.mcc && options.mnc) ? options.mcc + options.mnc : null;
+ let Buf = this.context.Buf;
+ Buf.newParcel(REQUEST_SET_NETWORK_SELECTION_MANUAL, options);
+ Buf.writeString(numeric);
+ Buf.sendParcel();
+ },
+
+ /**
+ * Get the signal strength.
+ */
+ getSignalStrength: function() {
+ this.context.Buf.simpleRequest(REQUEST_SIGNAL_STRENGTH);
+ },
+
+ getDeviceIdentity: function() {
+ this.deviceIdentities || this.context.Buf.simpleRequest(REQUEST_DEVICE_IDENTITY);
+ },
+
+ getBasebandVersion: function() {
+ this.context.Buf.simpleRequest(REQUEST_BASEBAND_VERSION);
+ },
+
+ sendExitEmergencyCbModeRequest: function(options) {
+ this.context.Buf.simpleRequest(REQUEST_EXIT_EMERGENCY_CALLBACK_MODE, options);
+ },
+
+ getCdmaSubscription: function() {
+ this.context.Buf.simpleRequest(REQUEST_CDMA_SUBSCRIPTION);
+ },
+
+ exitEmergencyCbMode: function(options) {
+ // The function could be called by an API from RadioInterfaceLayer or by
+ // ril_worker itself. From ril_worker, we won't pass the parameter
+ // 'options'. In this case, it is marked as internal.
+ if (!options) {
+ options = {internal: true};
+ }
+ this._cancelEmergencyCbModeTimeout();
+ this.sendExitEmergencyCbModeRequest(options);
+ },
+
+ /**
+ * Dial a non-emergency number.
+ *
+ * @param isEmergency
+ * Whether the number is an emergency number.
+ * @param number
+ * String containing the number to dial.
+ * @param clirMode
+ * Integer for showing/hidding the caller Id to the called party.
+ * @param uusInfo
+ * Integer doing something XXX TODO
+ */
+ dial: function(options) {
+ if (options.isEmergency) {
+ options.request = RILQUIRKS_REQUEST_USE_DIAL_EMERGENCY_CALL ?
+ REQUEST_DIAL_EMERGENCY_CALL : REQUEST_DIAL;
+
+ } else {
+ options.request = REQUEST_DIAL;
+
+ // Exit emergency callback mode when user dial a non-emergency call.
+ if (this._isInEmergencyCbMode) {
+ this.exitEmergencyCbMode();
+ }
+ }
+
+ this.telephonyRequestQueue.push(options.request, () => {
+ let Buf = this.context.Buf;
+ Buf.newParcel(options.request, options);
+ Buf.writeString(options.number);
+ Buf.writeInt32(options.clirMode || 0);
+ Buf.writeInt32(options.uusInfo || 0);
+ // TODO Why do we need this extra 0? It was put it in to make this
+ // match the format of the binary message.
+ Buf.writeInt32(0);
+ Buf.sendParcel();
+ });
+ },
+
+ /**
+ * CDMA flash.
+ *
+ * @param featureStr (optional)
+ * Dialing number when the command is used for three-way-calling
+ */
+ cdmaFlash: function(options) {
+ let Buf = this.context.Buf;
+ options.request = REQUEST_CDMA_FLASH;
+ Buf.newParcel(options.request, options);
+ Buf.writeString(options.featureStr || "");
+ Buf.sendParcel();
+ },
+
+ /**
+ * Hang up the phone.
+ *
+ * @param callIndex
+ * Call index (1-based) as reported by REQUEST_GET_CURRENT_CALLS.
+ */
+ hangUpCall: function(options) {
+ this.telephonyRequestQueue.push(REQUEST_HANGUP, () => {
+ let Buf = this.context.Buf;
+ Buf.newParcel(REQUEST_HANGUP, options);
+ Buf.writeInt32(1);
+ Buf.writeInt32(options.callIndex);
+ Buf.sendParcel();
+ });
+ },
+
+ hangUpForeground: function(options) {
+ this.telephonyRequestQueue.push(REQUEST_HANGUP_FOREGROUND_RESUME_BACKGROUND, () => {
+ this.context.Buf.simpleRequest(REQUEST_HANGUP_FOREGROUND_RESUME_BACKGROUND,
+ options);
+ });
+ },
+
+ hangUpBackground: function(options) {
+ this.telephonyRequestQueue.push(REQUEST_HANGUP_WAITING_OR_BACKGROUND, () => {
+ this.context.Buf.simpleRequest(REQUEST_HANGUP_WAITING_OR_BACKGROUND,
+ options);
+ });
+ },
+
+ switchActiveCall: function(options) {
+ this.telephonyRequestQueue.push(REQUEST_SWITCH_WAITING_OR_HOLDING_AND_ACTIVE, () => {
+ this.context.Buf.simpleRequest(REQUEST_SWITCH_WAITING_OR_HOLDING_AND_ACTIVE,
+ options);
+ });
+ },
+
+ udub: function(options) {
+ this.telephonyRequestQueue.push(REQUEST_UDUB, () => {
+ this.context.Buf.simpleRequest(REQUEST_UDUB, options);
+ });
+ },
+
+ answerCall: function(options) {
+ this.telephonyRequestQueue.push(REQUEST_ANSWER, () => {
+ this.context.Buf.simpleRequest(REQUEST_ANSWER, options);
+ });
+ },
+
+ conferenceCall: function(options) {
+ this.telephonyRequestQueue.push(REQUEST_CONFERENCE, () => {
+ this.context.Buf.simpleRequest(REQUEST_CONFERENCE, options);
+ });
+ },
+
+ separateCall: function(options) {
+ this.telephonyRequestQueue.push(REQUEST_SEPARATE_CONNECTION, () => {
+ let Buf = this.context.Buf;
+ Buf.newParcel(REQUEST_SEPARATE_CONNECTION, options);
+ Buf.writeInt32(1);
+ Buf.writeInt32(options.callIndex);
+ Buf.sendParcel();
+ });
+ },
+
+ /**
+ * Get current calls.
+ */
+ getCurrentCalls: function(options) {
+ this.telephonyRequestQueue.push(REQUEST_GET_CURRENT_CALLS, () => {
+ this.context.Buf.simpleRequest(REQUEST_GET_CURRENT_CALLS, options);
+ });
+ },
+
+ /**
+ * Mute or unmute the radio.
+ *
+ * @param mute
+ * Boolean to indicate whether to mute or unmute the radio.
+ */
+ setMute: function(options) {
+ let Buf = this.context.Buf;
+ Buf.newParcel(REQUEST_SET_MUTE);
+ Buf.writeInt32(1);
+ Buf.writeInt32(options.muted ? 1 : 0);
+ Buf.sendParcel();
+ },
+
+ /**
+ * Send an SMS.
+ *
+ * The `options` parameter object should contain the following attributes:
+ *
+ * @param number
+ * String containing the recipient number.
+ * @param body
+ * String containing the message text.
+ * @param envelopeId
+ * Numeric value identifying the sms request.
+ */
+ sendSMS: function(options) {
+ options.langIndex = options.langIndex || PDU_NL_IDENTIFIER_DEFAULT;
+ options.langShiftIndex = options.langShiftIndex || PDU_NL_IDENTIFIER_DEFAULT;
+
+ if (!options.segmentSeq) {
+ // Fist segment to send
+ options.segmentSeq = 1;
+ options.body = options.segments[0].body;
+ options.encodedBodyLength = options.segments[0].encodedBodyLength;
+ }
+
+ let Buf = this.context.Buf;
+ if (this._isCdma) {
+ Buf.newParcel(REQUEST_CDMA_SEND_SMS, options);
+ this.context.CdmaPDUHelper.writeMessage(options);
+ } else {
+ Buf.newParcel(REQUEST_SEND_SMS, options);
+ Buf.writeInt32(2);
+ Buf.writeString(options.SMSC);
+ this.context.GsmPDUHelper.writeMessage(options);
+ }
+ Buf.sendParcel();
+ },
+
+ /**
+ * Acknowledge the receipt and handling of an SMS.
+ *
+ * @param success
+ * Boolean indicating whether the message was successfuly handled.
+ * @param cause
+ * SMS_* constant indicating the reason for unsuccessful handling.
+ */
+ acknowledgeGsmSms: function(success, cause) {
+ let Buf = this.context.Buf;
+ Buf.newParcel(REQUEST_SMS_ACKNOWLEDGE);
+ Buf.writeInt32(2);
+ Buf.writeInt32(success ? 1 : 0);
+ Buf.writeInt32(cause);
+ Buf.sendParcel();
+ },
+
+ /**
+ * Acknowledge the receipt and handling of an SMS.
+ *
+ * @param success
+ * Boolean indicating whether the message was successfuly handled.
+ */
+ ackSMS: function(options) {
+ if (options.result == PDU_FCS_RESERVED) {
+ return;
+ }
+ if (this._isCdma) {
+ this.acknowledgeCdmaSms(options.result == PDU_FCS_OK, options.result);
+ } else {
+ this.acknowledgeGsmSms(options.result == PDU_FCS_OK, options.result);
+ }
+ },
+
+ /**
+ * Acknowledge the receipt and handling of a CDMA SMS.
+ *
+ * @param success
+ * Boolean indicating whether the message was successfuly handled.
+ * @param cause
+ * SMS_* constant indicating the reason for unsuccessful handling.
+ */
+ acknowledgeCdmaSms: function(success, cause) {
+ let Buf = this.context.Buf;
+ Buf.newParcel(REQUEST_CDMA_SMS_ACKNOWLEDGE);
+ Buf.writeInt32(success ? 0 : 1);
+ Buf.writeInt32(cause);
+ Buf.sendParcel();
+ },
+
+ /**
+ * Update received MWI into EF_MWIS.
+ */
+ updateMwis: function(options) {
+ if (this.context.ICCUtilsHelper.isICCServiceAvailable("MWIS")) {
+ this.context.SimRecordHelper.updateMWIS(options.mwi);
+ }
+ },
+
+ /**
+ * Report SMS storage status to modem.
+ */
+ _updateSmsMemoryStatus: function() {
+ let Buf = this.context.Buf;
+ Buf.newParcel(REQUEST_REPORT_SMS_MEMORY_STATUS);
+ Buf.writeInt32(1);
+ Buf.writeInt32(this.smsStorageAvailable ? 1 : 0);
+ Buf.sendParcel();
+ },
+
+ reportSmsMemoryStatus: function(options) {
+ this.pendingToReportSmsMemoryStatus = true;
+ this.smsStorageAvailable = options.isAvailable;
+ this._updateSmsMemoryStatus();
+ },
+
+ setCellBroadcastDisabled: function(options) {
+ this.cellBroadcastDisabled = options.disabled;
+
+ // If |this.mergedCellBroadcastConfig| is null, either we haven't finished
+ // reading required SIM files, or no any channel is ever configured. In
+ // the former case, we'll call |this.updateCellBroadcastConfig()| later
+ // with correct configs; in the latter case, we don't bother resetting CB
+ // to disabled again.
+ if (this.mergedCellBroadcastConfig) {
+ this.updateCellBroadcastConfig();
+ }
+ },
+
+ setCellBroadcastSearchList: function(options) {
+ let getSearchListStr = function(aSearchList) {
+ if (typeof aSearchList === "string" || aSearchList instanceof String) {
+ return aSearchList;
+ }
+
+ // TODO: Set search list for CDMA/GSM individually. Bug 990926
+ let prop = this._isCdma ? "cdma" : "gsm";
+
+ return aSearchList && aSearchList[prop];
+ }.bind(this);
+
+ try {
+ let str = getSearchListStr(options.searchList);
+ this.cellBroadcastConfigs.MMI = this._convertCellBroadcastSearchList(str);
+ } catch (e) {
+ if (DEBUG) {
+ this.context.debug("Invalid Cell Broadcast search list: " + e);
+ }
+ options.errorMsg = GECKO_ERROR_UNSPECIFIED_ERROR;
+ }
+
+ this.sendChromeMessage(options);
+ if (options.errorMsg) {
+ return;
+ }
+
+ this._mergeAllCellBroadcastConfigs();
+ },
+
+ updateCellBroadcastConfig: function() {
+ let activate = !this.cellBroadcastDisabled &&
+ (this.mergedCellBroadcastConfig != null) &&
+ (this.mergedCellBroadcastConfig.length > 0);
+ if (activate) {
+ this.setSmsBroadcastConfig(this.mergedCellBroadcastConfig);
+ } else {
+ // It's unnecessary to set config first if we're deactivating.
+ this.setSmsBroadcastActivation(false);
+ }
+ },
+
+ setGsmSmsBroadcastConfig: function(config) {
+ let Buf = this.context.Buf;
+ Buf.newParcel(REQUEST_GSM_SET_BROADCAST_SMS_CONFIG);
+
+ let numConfigs = config ? config.length / 2 : 0;
+ Buf.writeInt32(numConfigs);
+ for (let i = 0; i < config.length;) {
+ // convert [from, to) to [from, to - 1]
+ Buf.writeInt32(config[i++]);
+ Buf.writeInt32(config[i++] - 1);
+ Buf.writeInt32(0x00);
+ Buf.writeInt32(0xFF);
+ Buf.writeInt32(1);
+ }
+
+ Buf.sendParcel();
+ },
+
+ /**
+ * Send CDMA SMS broadcast config.
+ *
+ * @see 3GPP2 C.R1001 Sec. 9.2 and 9.3
+ */
+ setCdmaSmsBroadcastConfig: function(config) {
+ let Buf = this.context.Buf;
+ // |config| is an array of half-closed range: [[from, to), [from, to), ...].
+ // It will be further decomposed, ex: [1, 4) => 1, 2, 3.
+ Buf.newParcel(REQUEST_CDMA_SET_BROADCAST_SMS_CONFIG);
+
+ let numConfigs = 0;
+ for (let i = 0; i < config.length; i += 2) {
+ numConfigs += (config[i+1] - config[i]);
+ }
+
+ Buf.writeInt32(numConfigs);
+ for (let i = 0; i < config.length;) {
+ let begin = config[i++];
+ let end = config[i++];
+
+ for (let j = begin; j < end; ++j) {
+ Buf.writeInt32(j);
+ Buf.writeInt32(0); // Language Indicator: Unknown or unspecified.
+ Buf.writeInt32(1);
+ }
+ }
+
+ Buf.sendParcel();
+ },
+
+ setSmsBroadcastConfig: function(config) {
+ if (this._isCdma) {
+ this.setCdmaSmsBroadcastConfig(config);
+ } else {
+ this.setGsmSmsBroadcastConfig(config);
+ }
+ },
+
+ setSmsBroadcastActivation: function(activate) {
+ let parcelType = this._isCdma ? REQUEST_CDMA_SMS_BROADCAST_ACTIVATION :
+ REQUEST_GSM_SMS_BROADCAST_ACTIVATION;
+ let Buf = this.context.Buf;
+ Buf.newParcel(parcelType);
+ Buf.writeInt32(1);
+ // See hardware/ril/include/telephony/ril.h, 0 - Activate, 1 - Turn off.
+ Buf.writeInt32(activate ? 0 : 1);
+ Buf.sendParcel();
+ },
+
+ /**
+ * Start a DTMF Tone.
+ *
+ * @param dtmfChar
+ * DTMF signal to send, 0-9, *, +
+ */
+ startTone: function(options) {
+ let Buf = this.context.Buf;
+ Buf.newParcel(REQUEST_DTMF_START, options);
+ Buf.writeString(options.dtmfChar);
+ Buf.sendParcel();
+ },
+
+ stopTone: function() {
+ this.context.Buf.simpleRequest(REQUEST_DTMF_STOP);
+ },
+
+ /**
+ * Send a DTMF tone.
+ *
+ * @param dtmfChar
+ * DTMF signal to send, 0-9, *, +
+ */
+ sendTone: function(options) {
+ let Buf = this.context.Buf;
+ Buf.newParcel(REQUEST_DTMF);
+ Buf.writeString(options.dtmfChar);
+ Buf.sendParcel();
+ },
+
+ /**
+ * Get the Short Message Service Center address.
+ */
+ getSmscAddress: function(options) {
+ this.context.Buf.simpleRequest(REQUEST_GET_SMSC_ADDRESS, options);
+ },
+
+ /**
+ * Set the Short Message Service Center address.
+ *
+ * @param smscAddress
+ * Number part of the SMSC address.
+ * @param typeOfNumber
+ * Type of number in integer, as defined in
+ * |Table 10.5.118: Called party BCD number| of 3GPP TS 24.008.
+ * @param numberPlanIdentification
+ * The index of number plan identification value in
+ * CALLED_PARTY_BCD_NPI array.
+ */
+ setSmscAddress: function(options) {
+ let ton = options.typeOfNumber;
+ let npi = CALLED_PARTY_BCD_NPI[options.numberPlanIdentification];
+
+ // If any of the mandatory arguments is not available, return an error
+ // immediately.
+ if (ton === undefined || npi === undefined || !options.smscAddress) {
+ options.errorMsg = GECKO_ERROR_INVALID_PARAMETER;
+ this.sendChromeMessage(options);
+ return;
+ }
+
+ // Remove all illegal characters in the number string for user-input fault
+ // tolerance.
+ let numStart = options.smscAddress[0] === "+" ? 1 : 0;
+ let number = options.smscAddress.substring(0, numStart) +
+ options.smscAddress.substring(numStart)
+ .replace(/[^0-9*#abc]/ig, "");
+
+ // If the filtered number is an empty string, return an error immediately.
+ if (number.length === 0) {
+ options.errorMsg = GECKO_ERROR_INVALID_PARAMETER;
+ this.sendChromeMessage(options);
+ return;
+ }
+
+ // Init parcel.
+ let Buf = this.context.Buf;
+ Buf.newParcel(REQUEST_SET_SMSC_ADDRESS, options);
+
+ // +---+-----------+---------------+
+ // | 1 | TON | NPI |
+ // +---+-----------+---------------+
+ let tosca = (0x1 << 7) + (ton << 4) + npi;
+ if (RILQUIRKS_SMSC_ADDRESS_FORMAT === "pdu") {
+ let pduHelper = this.context.GsmPDUHelper;
+
+ // Remove the preceding '+', and covert the special BCD digits defined in
+ // |Called party BCD number| of 3GPP TS 24.008 to corresponding
+ // hexadecimal values (refer the following table).
+ //
+ // +=========+=======+=====+
+ // | value | digit | hex |
+ // +========================
+ // | 1 0 1 0 | * | 0xA |
+ // | 1 0 1 1 | # | 0xB |
+ // | 1 1 0 0 | a | 0xC |
+ // | 1 1 0 1 | b | 0xD |
+ // | 1 1 1 0 | c | 0xE |
+ // +=========+=======+=====+
+ //
+ // The replace order is reversed intentionally, because if the digits are
+ // replaced in ascending order, "#" will be converted to "b" and then be
+ // converted again to "d", which generates incorrect result.
+ let pureNumber = number.substring(numStart)
+ .replace(/c/ig, "e")
+ .replace(/b/ig, "d")
+ .replace(/a/ig, "c")
+ .replace(/\#/g, "b")
+ .replace(/\*/g, "a");
+
+ // address length and string length
+ let length = Math.ceil(pureNumber.length / 2) + 1; // +1 octet for TOA
+ let strlen = length * 2 + 2; // +2 semi-octets for length octet
+
+ Buf.writeInt32(strlen);
+ pduHelper.writeHexOctet(length);
+ pduHelper.writeHexOctet(tosca);
+ pduHelper.writeSwappedNibbleBCD(pureNumber);
+ Buf.writeStringDelimiter(strlen);
+ } else /* RILQUIRKS_SMSC_ADDRESS_FORMAT === "text" */ {
+ let sca;
+ sca = '"' + number + '"' + ',' + tosca;
+ Buf.writeString(sca);
+ }
+
+ Buf.sendParcel();
+ },
+
+ /**
+ * Setup a data call.
+ *
+ * @param radioTech
+ * Integer to indicate radio technology.
+ * DATACALL_RADIOTECHNOLOGY_CDMA => CDMA.
+ * DATACALL_RADIOTECHNOLOGY_GSM => GSM.
+ * @param apn
+ * String containing the name of the APN to connect to.
+ * @param user
+ * String containing the username for the APN.
+ * @param passwd
+ * String containing the password for the APN.
+ * @param chappap
+ * Integer containing CHAP/PAP auth type.
+ * DATACALL_AUTH_NONE => PAP and CHAP is never performed.
+ * DATACALL_AUTH_PAP => PAP may be performed.
+ * DATACALL_AUTH_CHAP => CHAP may be performed.
+ * DATACALL_AUTH_PAP_OR_CHAP => PAP / CHAP may be performed.
+ * @param pdptype
+ * String containing PDP type to request. ("IP", "IPV6", ...)
+ */
+ setupDataCall: function(options) {
+ // From ./hardware/ril/include/telephony/ril.h:
+ // ((const char **)data)[0] Radio technology to use: 0-CDMA, 1-GSM/UMTS, 2...
+ // for values above 2 this is RIL_RadioTechnology + 2.
+ //
+ // From frameworks/base/telephony/java/com/android/internal/telephony/DataConnection.java:
+ // if the mRilVersion < 6, radio technology must be GSM/UMTS or CDMA.
+ // Otherwise, it must be + 2
+ //
+ // See also bug 901232 and 867873
+ let radioTech = options.radioTech + 2;
+ let Buf = this.context.Buf;
+ let token = Buf.newParcel(REQUEST_SETUP_DATA_CALL, options);
+ Buf.writeInt32(7);
+ Buf.writeString(radioTech.toString());
+ Buf.writeString(DATACALL_PROFILE_DEFAULT.toString());
+ Buf.writeString(options.apn);
+ Buf.writeString(options.user);
+ Buf.writeString(options.passwd);
+ Buf.writeString(options.chappap.toString());
+ Buf.writeString(options.pdptype);
+ Buf.sendParcel();
+ return token;
+ },
+
+ /**
+ * Deactivate a data call.
+ *
+ * @param cid
+ * String containing CID.
+ * @param reason
+ * One of DATACALL_DEACTIVATE_* constants.
+ */
+ deactivateDataCall: function(options) {
+ let Buf = this.context.Buf;
+ Buf.newParcel(REQUEST_DEACTIVATE_DATA_CALL, options);
+ Buf.writeInt32(2);
+ Buf.writeString(options.cid.toString());
+ Buf.writeString(options.reason !== undefined ?
+ options.reason.toString() :
+ DATACALL_DEACTIVATE_NO_REASON.toString());
+ Buf.sendParcel();
+ },
+
+ /**
+ * Get a list of data calls.
+ */
+ getDataCallList: function(options) {
+ this.context.Buf.simpleRequest(REQUEST_DATA_CALL_LIST, options);
+ },
+
+ _attachDataRegistration: false,
+ /**
+ * Manually attach/detach data registration.
+ *
+ * @param attach
+ * Boolean value indicating attach or detach.
+ */
+ setDataRegistration: function(options) {
+ this._attachDataRegistration = options.attach;
+
+ if (RILQUIRKS_DATA_REGISTRATION_ON_DEMAND) {
+ let request = options.attach ? RIL_REQUEST_GPRS_ATTACH :
+ RIL_REQUEST_GPRS_DETACH;
+ this.context.Buf.simpleRequest(request, options);
+ return;
+ } else if (RILQUIRKS_SUBSCRIPTION_CONTROL && options.attach) {
+ this.context.Buf.simpleRequest(REQUEST_SET_DATA_SUBSCRIPTION, options);
+ return;
+ }
+
+ // We don't really send a request to rild, so instantly reply success to
+ // RadioInterfaceLayer.
+ this.sendChromeMessage(options);
+ },
+
+ /**
+ * Get failure casue code for the most recently failed PDP context.
+ */
+ getFailCause: function(options) {
+ this.context.Buf.simpleRequest(REQUEST_LAST_CALL_FAIL_CAUSE, options);
+ },
+
+ /**
+ * Send USSD.
+ *
+ * @param ussd
+ * String containing the USSD code.
+ */
+ sendUSSD: function(options) {
+ let Buf = this.context.Buf;
+ Buf.newParcel(REQUEST_SEND_USSD, options);
+ Buf.writeString(options.ussd);
+ Buf.sendParcel();
+ },
+
+ /**
+ * Cancel pending USSD.
+ */
+ cancelUSSD: function(options) {
+ this.context.Buf.simpleRequest(REQUEST_CANCEL_USSD, options);
+ },
+
+ /**
+ * Queries current call forward rules.
+ *
+ * @param reason
+ * One of CALL_FORWARD_REASON_* constants.
+ * @param serviceClass
+ * One of ICC_SERVICE_CLASS_* constants.
+ * @param number
+ * Phone number of forwarding address.
+ */
+ queryCallForwardStatus: function(options) {
+ let Buf = this.context.Buf;
+ let number = options.number || "";
+ Buf.newParcel(REQUEST_QUERY_CALL_FORWARD_STATUS, options);
+ Buf.writeInt32(CALL_FORWARD_ACTION_QUERY_STATUS);
+ Buf.writeInt32(options.reason);
+ Buf.writeInt32(options.serviceClass || ICC_SERVICE_CLASS_NONE);
+ Buf.writeInt32(this._toaFromString(number));
+ Buf.writeString(number);
+ Buf.writeInt32(0);
+ Buf.sendParcel();
+ },
+
+ /**
+ * Configures call forward rule.
+ *
+ * @param action
+ * One of CALL_FORWARD_ACTION_* constants.
+ * @param reason
+ * One of CALL_FORWARD_REASON_* constants.
+ * @param serviceClass
+ * One of ICC_SERVICE_CLASS_* constants.
+ * @param number
+ * Phone number of forwarding address.
+ * @param timeSeconds
+ * Time in seconds to wait beforec all is forwarded.
+ */
+ setCallForward: function(options) {
+ let Buf = this.context.Buf;
+ Buf.newParcel(REQUEST_SET_CALL_FORWARD, options);
+ Buf.writeInt32(options.action);
+ Buf.writeInt32(options.reason);
+ Buf.writeInt32(options.serviceClass);
+ Buf.writeInt32(this._toaFromString(options.number));
+ Buf.writeString(options.number);
+ Buf.writeInt32(options.timeSeconds);
+ Buf.sendParcel();
+ },
+
+ /**
+ * Queries current call barring rules.
+ *
+ * @param program
+ * One of CALL_BARRING_PROGRAM_* constants.
+ * @param serviceClass
+ * One of ICC_SERVICE_CLASS_* constants.
+ */
+ queryCallBarringStatus: function(options) {
+ options.facility = CALL_BARRING_PROGRAM_TO_FACILITY[options.program];
+ options.password = ""; // For query no need to provide it.
+
+ // For some operators, querying specific serviceClass doesn't work. We use
+ // serviceClass 0 instead, and then process the response to extract the
+ // answer for queryServiceClass.
+ options.queryServiceClass = options.serviceClass;
+ options.serviceClass = 0;
+
+ this.queryICCFacilityLock(options);
+ },
+
+ /**
+ * Configures call barring rule.
+ *
+ * @param program
+ * One of CALL_BARRING_PROGRAM_* constants.
+ * @param enabled
+ * Enable or disable the call barring.
+ * @param password
+ * Barring password.
+ * @param serviceClass
+ * One of ICC_SERVICE_CLASS_* constants.
+ */
+ setCallBarring: function(options) {
+ options.facility = CALL_BARRING_PROGRAM_TO_FACILITY[options.program];
+ this.setICCFacilityLock(options);
+ },
+
+ /**
+ * Change call barring facility password.
+ *
+ * @param pin
+ * Old password.
+ * @param newPin
+ * New password.
+ */
+ changeCallBarringPassword: function(options) {
+ let Buf = this.context.Buf;
+ Buf.newParcel(REQUEST_CHANGE_BARRING_PASSWORD, options);
+ Buf.writeInt32(3);
+ // Set facility to ICC_CB_FACILITY_BA_ALL by following TS.22.030 clause
+ // 6.5.4 and Table B.1.
+ Buf.writeString(ICC_CB_FACILITY_BA_ALL);
+ Buf.writeString(options.pin);
+ Buf.writeString(options.newPin);
+ Buf.sendParcel();
+ },
+
+ /**
+ * Handle STK CALL_SET_UP request.
+ *
+ * @param hasConfirmed
+ * Does use have confirmed the call requested from ICC?
+ */
+ stkHandleCallSetup: function(options) {
+ let Buf = this.context.Buf;
+ Buf.newParcel(REQUEST_STK_HANDLE_CALL_SETUP_REQUESTED_FROM_SIM);
+ Buf.writeInt32(1);
+ Buf.writeInt32(options.hasConfirmed ? 1 : 0);
+ Buf.sendParcel();
+ },
+
+ /**
+ * Send STK Profile Download.
+ *
+ * @param profile Profile supported by ME.
+ */
+ sendStkTerminalProfile: function(profile) {
+ let Buf = this.context.Buf;
+ let GsmPDUHelper = this.context.GsmPDUHelper;
+
+ Buf.newParcel(REQUEST_STK_SET_PROFILE);
+ Buf.writeInt32(profile.length * 2);
+ for (let i = 0; i < profile.length; i++) {
+ GsmPDUHelper.writeHexOctet(profile[i]);
+ }
+ Buf.writeInt32(0);
+ Buf.sendParcel();
+ },
+
+ /**
+ * Send STK terminal response.
+ *
+ * @param command
+ * @param deviceIdentities
+ * @param resultCode
+ * @param [optional] additionalInformation
+ * @param [optional] itemIdentifier
+ * @param [optional] input
+ * @param [optional] isYesNo
+ * @param [optional] hasConfirmed
+ * @param [optional] localInfo
+ * @param [optional] timer
+ */
+ sendStkTerminalResponse: function(response) {
+ if (response.hasConfirmed !== undefined) {
+ this.stkHandleCallSetup(response);
+ return;
+ }
+
+ let Buf = this.context.Buf;
+ let ComprehensionTlvHelper = this.context.ComprehensionTlvHelper;
+ let GsmPDUHelper = this.context.GsmPDUHelper;
+
+ let command = response.command;
+ Buf.newParcel(REQUEST_STK_SEND_TERMINAL_RESPONSE);
+
+ // 1st mark for Parcel size
+ Buf.startCalOutgoingSize(function(size) {
+ // Parcel size is in string length, which costs 2 uint8 per char.
+ Buf.writeInt32(size / 2);
+ });
+
+ // Command Details
+ GsmPDUHelper.writeHexOctet(COMPREHENSIONTLV_TAG_COMMAND_DETAILS |
+ COMPREHENSIONTLV_FLAG_CR);
+ GsmPDUHelper.writeHexOctet(3);
+ if (command) {
+ GsmPDUHelper.writeHexOctet(command.commandNumber);
+ GsmPDUHelper.writeHexOctet(command.typeOfCommand);
+ GsmPDUHelper.writeHexOctet(command.commandQualifier);
+ } else {
+ GsmPDUHelper.writeHexOctet(0x00);
+ GsmPDUHelper.writeHexOctet(0x00);
+ GsmPDUHelper.writeHexOctet(0x00);
+ }
+
+ // Device Identifier
+ // According to TS102.223/TS31.111 section 6.8 Structure of
+ // TERMINAL RESPONSE, "For all SIMPLE-TLV objects with Min=N,
+ // the ME should set the CR(comprehension required) flag to
+ // comprehension not required.(CR=0)"
+ // Since DEVICE_IDENTITIES and DURATION TLVs have Min=N,
+ // the CR flag is not set.
+ GsmPDUHelper.writeHexOctet(COMPREHENSIONTLV_TAG_DEVICE_ID);
+ GsmPDUHelper.writeHexOctet(2);
+ GsmPDUHelper.writeHexOctet(STK_DEVICE_ID_ME);
+ GsmPDUHelper.writeHexOctet(STK_DEVICE_ID_SIM);
+
+ // Result
+ GsmPDUHelper.writeHexOctet(COMPREHENSIONTLV_TAG_RESULT |
+ COMPREHENSIONTLV_FLAG_CR);
+ if ("additionalInformation" in response) {
+ // In |12.12 Result| TS 11.14, the length of additional information is
+ // varied and all possible values are addressed in 12.12.1-11 of TS 11.14
+ // and 8.12.1-13 in TS 31.111.
+ // However,
+ // 1. Only SEND SS requires info with more than 1 octet.
+ // 2. In rild design, SEND SS is expected to be handled by modem and
+ // UNSOLICITED_STK_EVENT_NOTIFY will be sent to application layer to
+ // indicate appropriate messages to users. TR is not required in this
+ // case.
+ // Hence, we simplify the structure of |additionalInformation| to a
+ // numeric value instead of a octet array.
+ GsmPDUHelper.writeHexOctet(2);
+ GsmPDUHelper.writeHexOctet(response.resultCode);
+ GsmPDUHelper.writeHexOctet(response.additionalInformation);
+ } else {
+ GsmPDUHelper.writeHexOctet(1);
+ GsmPDUHelper.writeHexOctet(response.resultCode);
+ }
+
+ // Item Identifier
+ if (response.itemIdentifier != null) {
+ GsmPDUHelper.writeHexOctet(COMPREHENSIONTLV_TAG_ITEM_ID |
+ COMPREHENSIONTLV_FLAG_CR);
+ GsmPDUHelper.writeHexOctet(1);
+ GsmPDUHelper.writeHexOctet(response.itemIdentifier);
+ }
+
+ // No need to process Text data if user requests help information.
+ if (response.resultCode != STK_RESULT_HELP_INFO_REQUIRED) {
+ let text;
+ let coding = command.options.isUCS2 ?
+ STK_TEXT_CODING_UCS2 :
+ (command.options.isPacked ?
+ STK_TEXT_CODING_GSM_7BIT_PACKED :
+ STK_TEXT_CODING_GSM_8BIT);
+ if (response.isYesNo !== undefined) {
+ // Tag: GET_INKEY
+ // When the ME issues a successful TERMINAL RESPONSE for a GET INKEY
+ // ("Yes/No") command with command qualifier set to "Yes/No", it shall
+ // supply the value '01' when the answer is "positive" and the value
+ // '00' when the answer is "negative" in the Text string data object.
+ GsmPDUHelper.writeHexOctet(COMPREHENSIONTLV_TAG_TEXT_STRING |
+ COMPREHENSIONTLV_FLAG_CR);
+ // Length: 2
+ GsmPDUHelper.writeHexOctet(2);
+ // Value: Coding, Yes/No.
+ GsmPDUHelper.writeHexOctet(coding);
+ GsmPDUHelper.writeHexOctet(response.isYesNo ? 0x01 : 0x00);
+ } else {
+ if (response.input !== undefined) {
+ ComprehensionTlvHelper.writeTextStringTlv(response.input, coding);
+ }
+ }
+ }
+
+ // Duration
+ if (response.resultCode === STK_RESULT_NO_RESPONSE_FROM_USER) {
+ // In TS102 223, 6.4.2 GET INKEY, "if the UICC requests a variable timeout,
+ // the terminal shall wait until either the user enters a single character
+ // or the timeout expires. The timer starts when the text is displayed on
+ // the screen and stops when the TERMINAL RESPONSE is sent. The terminal
+ // shall pass the total display text duration (command execution duration)
+ // to the UICC using the TERMINAL RESPONSE. The time unit of the response
+ // is identical to the time unit of the requested variable timeout."
+ let duration = command && command.options && command.options.duration;
+ if (duration) {
+ GsmPDUHelper.writeHexOctet(COMPREHENSIONTLV_TAG_DURATION);
+ GsmPDUHelper.writeHexOctet(2);
+ GsmPDUHelper.writeHexOctet(duration.timeUnit);
+ GsmPDUHelper.writeHexOctet(duration.timeInterval);
+ }
+ }
+
+ // Local Information
+ if (response.localInfo) {
+ let localInfo = response.localInfo;
+
+ // Location Infomation
+ if (localInfo.locationInfo) {
+ ComprehensionTlvHelper.writeLocationInfoTlv(localInfo.locationInfo);
+ }
+
+ // IMEI
+ if (localInfo.imei != null) {
+ let imei = localInfo.imei;
+ if (imei.length == 15) {
+ imei = imei + "0";
+ }
+
+ GsmPDUHelper.writeHexOctet(COMPREHENSIONTLV_TAG_IMEI);
+ GsmPDUHelper.writeHexOctet(8);
+ for (let i = 0; i < imei.length / 2; i++) {
+ GsmPDUHelper.writeHexOctet(parseInt(imei.substr(i * 2, 2), 16));
+ }
+ }
+
+ // Date and Time Zone
+ if (localInfo.date != null) {
+ ComprehensionTlvHelper.writeDateTimeZoneTlv(localInfo.date);
+ }
+
+ // Language
+ if (localInfo.language) {
+ ComprehensionTlvHelper.writeLanguageTlv(localInfo.language);
+ }
+ }
+
+ // Timer
+ if (response.timer) {
+ let timer = response.timer;
+
+ if (timer.timerId) {
+ GsmPDUHelper.writeHexOctet(COMPREHENSIONTLV_TAG_TIMER_IDENTIFIER);
+ GsmPDUHelper.writeHexOctet(1);
+ GsmPDUHelper.writeHexOctet(timer.timerId);
+ }
+
+ if (timer.timerValue) {
+ ComprehensionTlvHelper.writeTimerValueTlv(timer.timerValue, false);
+ }
+ }
+
+ // Calculate and write Parcel size to 1st mark
+ Buf.stopCalOutgoingSize();
+
+ Buf.writeInt32(0);
+ Buf.sendParcel();
+ },
+
+ /**
+ * Send STK Envelope(Menu Selection) command.
+ *
+ * @param itemIdentifier
+ * @param helpRequested
+ */
+ sendStkMenuSelection: function(command) {
+ command.tag = BER_MENU_SELECTION_TAG;
+ command.deviceId = {
+ sourceId :STK_DEVICE_ID_KEYPAD,
+ destinationId: STK_DEVICE_ID_SIM
+ };
+ this.sendICCEnvelopeCommand(command);
+ },
+
+ /**
+ * Send STK Envelope(Timer Expiration) command.
+ *
+ * @param timer
+ */
+ sendStkTimerExpiration: function(command) {
+ command.tag = BER_TIMER_EXPIRATION_TAG;
+ command.deviceId = {
+ sourceId: STK_DEVICE_ID_ME,
+ destinationId: STK_DEVICE_ID_SIM
+ };
+ command.timerId = command.timer.timerId;
+ command.timerValue = command.timer.timerValue;
+ this.sendICCEnvelopeCommand(command);
+ },
+
+ /**
+ * Send STK Envelope(Event Download) command.
+ * @param event
+ */
+ sendStkEventDownload: function(command) {
+ command.tag = BER_EVENT_DOWNLOAD_TAG;
+ command.eventList = command.event.eventType;
+ switch (command.eventList) {
+ case STK_EVENT_TYPE_LOCATION_STATUS:
+ command.deviceId = {
+ sourceId :STK_DEVICE_ID_ME,
+ destinationId: STK_DEVICE_ID_SIM
+ };
+ command.locationStatus = command.event.locationStatus;
+ // Location info should only be provided when locationStatus is normal.
+ if (command.locationStatus == STK_SERVICE_STATE_NORMAL) {
+ command.locationInfo = command.event.locationInfo;
+ }
+ break;
+ case STK_EVENT_TYPE_MT_CALL:
+ command.deviceId = {
+ sourceId: STK_DEVICE_ID_NETWORK,
+ destinationId: STK_DEVICE_ID_SIM
+ };
+ command.transactionId = 0;
+ command.address = command.event.number;
+ break;
+ case STK_EVENT_TYPE_CALL_DISCONNECTED:
+ command.cause = command.event.error;
+ // Fall through.
+ case STK_EVENT_TYPE_CALL_CONNECTED:
+ command.deviceId = {
+ sourceId: (command.event.isIssuedByRemote ?
+ STK_DEVICE_ID_NETWORK : STK_DEVICE_ID_ME),
+ destinationId: STK_DEVICE_ID_SIM
+ };
+ command.transactionId = 0;
+ break;
+ case STK_EVENT_TYPE_USER_ACTIVITY:
+ command.deviceId = {
+ sourceId: STK_DEVICE_ID_ME,
+ destinationId: STK_DEVICE_ID_SIM
+ };
+ break;
+ case STK_EVENT_TYPE_IDLE_SCREEN_AVAILABLE:
+ command.deviceId = {
+ sourceId: STK_DEVICE_ID_DISPLAY,
+ destinationId: STK_DEVICE_ID_SIM
+ };
+ break;
+ case STK_EVENT_TYPE_LANGUAGE_SELECTION:
+ command.deviceId = {
+ sourceId: STK_DEVICE_ID_ME,
+ destinationId: STK_DEVICE_ID_SIM
+ };
+ command.language = command.event.language;
+ break;
+ case STK_EVENT_TYPE_BROWSER_TERMINATION:
+ command.deviceId = {
+ sourceId: STK_DEVICE_ID_ME,
+ destinationId: STK_DEVICE_ID_SIM
+ };
+ command.terminationCause = command.event.terminationCause;
+ break;
+ }
+ this.sendICCEnvelopeCommand(command);
+ },
+
+ /**
+ * Send REQUEST_STK_SEND_ENVELOPE_COMMAND to ICC.
+ *
+ * @param tag
+ * @patam deviceId
+ * @param [optioanl] itemIdentifier
+ * @param [optional] helpRequested
+ * @param [optional] eventList
+ * @param [optional] locationStatus
+ * @param [optional] locationInfo
+ * @param [optional] address
+ * @param [optional] transactionId
+ * @param [optional] cause
+ * @param [optional] timerId
+ * @param [optional] timerValue
+ * @param [optional] terminationCause
+ */
+ sendICCEnvelopeCommand: function(options) {
+ if (DEBUG) {
+ this.context.debug("Stk Envelope " + JSON.stringify(options));
+ }
+
+ let Buf = this.context.Buf;
+ let ComprehensionTlvHelper = this.context.ComprehensionTlvHelper;
+ let GsmPDUHelper = this.context.GsmPDUHelper;
+
+ Buf.newParcel(REQUEST_STK_SEND_ENVELOPE_COMMAND);
+
+ // 1st mark for Parcel size
+ Buf.startCalOutgoingSize(function(size) {
+ // Parcel size is in string length, which costs 2 uint8 per char.
+ Buf.writeInt32(size / 2);
+ });
+
+ // Write a BER-TLV
+ GsmPDUHelper.writeHexOctet(options.tag);
+ // 2nd mark for BER length
+ Buf.startCalOutgoingSize(function(size) {
+ // BER length is in number of hexOctets, which costs 4 uint8 per hexOctet.
+ GsmPDUHelper.writeHexOctet(size / 4);
+ });
+
+ // Event List
+ if (options.eventList != null) {
+ GsmPDUHelper.writeHexOctet(COMPREHENSIONTLV_TAG_EVENT_LIST |
+ COMPREHENSIONTLV_FLAG_CR);
+ GsmPDUHelper.writeHexOctet(1);
+ GsmPDUHelper.writeHexOctet(options.eventList);
+ }
+
+ // Device Identifies
+ GsmPDUHelper.writeHexOctet(COMPREHENSIONTLV_TAG_DEVICE_ID |
+ COMPREHENSIONTLV_FLAG_CR);
+ GsmPDUHelper.writeHexOctet(2);
+ GsmPDUHelper.writeHexOctet(options.deviceId.sourceId);
+ GsmPDUHelper.writeHexOctet(options.deviceId.destinationId);
+
+ // Item Identifier
+ if (options.itemIdentifier != null) {
+ GsmPDUHelper.writeHexOctet(COMPREHENSIONTLV_TAG_ITEM_ID |
+ COMPREHENSIONTLV_FLAG_CR);
+ GsmPDUHelper.writeHexOctet(1);
+ GsmPDUHelper.writeHexOctet(options.itemIdentifier);
+ }
+
+ // Help Request
+ if (options.helpRequested) {
+ GsmPDUHelper.writeHexOctet(COMPREHENSIONTLV_TAG_HELP_REQUEST |
+ COMPREHENSIONTLV_FLAG_CR);
+ GsmPDUHelper.writeHexOctet(0);
+ // Help Request doesn't have value
+ }
+
+ // Location Status
+ if (options.locationStatus != null) {
+ let len = options.locationStatus.length;
+ GsmPDUHelper.writeHexOctet(COMPREHENSIONTLV_TAG_LOCATION_STATUS |
+ COMPREHENSIONTLV_FLAG_CR);
+ GsmPDUHelper.writeHexOctet(1);
+ GsmPDUHelper.writeHexOctet(options.locationStatus);
+ }
+
+ // Location Info
+ if (options.locationInfo) {
+ ComprehensionTlvHelper.writeLocationInfoTlv(options.locationInfo);
+ }
+
+ // Transaction Id
+ if (options.transactionId != null) {
+ GsmPDUHelper.writeHexOctet(COMPREHENSIONTLV_TAG_TRANSACTION_ID |
+ COMPREHENSIONTLV_FLAG_CR);
+ GsmPDUHelper.writeHexOctet(1);
+ GsmPDUHelper.writeHexOctet(options.transactionId);
+ }
+
+ // Address
+ if (options.address) {
+ GsmPDUHelper.writeHexOctet(COMPREHENSIONTLV_TAG_ADDRESS |
+ COMPREHENSIONTLV_FLAG_CR);
+ let addressLength = options.address[0] == '+' ? options.address.length - 1
+ : options.address.length;
+ ComprehensionTlvHelper.writeLength(
+ Math.ceil(addressLength / 2) + 1 // address BCD + TON
+ );
+ this.context.ICCPDUHelper.writeDiallingNumber(options.address);
+ }
+
+ // Cause of disconnection.
+ if (options.cause != null) {
+ ComprehensionTlvHelper.writeCauseTlv(options.cause);
+ }
+
+ // Timer Identifier
+ if (options.timerId != null) {
+ GsmPDUHelper.writeHexOctet(COMPREHENSIONTLV_TAG_TIMER_IDENTIFIER |
+ COMPREHENSIONTLV_FLAG_CR);
+ GsmPDUHelper.writeHexOctet(1);
+ GsmPDUHelper.writeHexOctet(options.timerId);
+ }
+
+ // Timer Value
+ if (options.timerValue != null) {
+ ComprehensionTlvHelper.writeTimerValueTlv(options.timerValue, true);
+ }
+
+ // Language
+ if (options.language) {
+ ComprehensionTlvHelper.writeLanguageTlv(options.language);
+ }
+
+ // Browser Termination
+ if (options.terminationCause != null) {
+ GsmPDUHelper.writeHexOctet(COMPREHENSIONTLV_TAG_BROWSER_TERMINATION_CAUSE |
+ COMPREHENSIONTLV_FLAG_CR);
+ GsmPDUHelper.writeHexOctet(1);
+ GsmPDUHelper.writeHexOctet(options.terminationCause);
+ }
+
+ // Calculate and write BER length to 2nd mark
+ Buf.stopCalOutgoingSize();
+
+ // Calculate and write Parcel size to 1st mark
+ Buf.stopCalOutgoingSize();
+
+ Buf.writeInt32(0);
+ Buf.sendParcel();
+ },
+
+ /**
+ * Report STK Service is running.
+ */
+ reportStkServiceIsRunning: function() {
+ this.context.Buf.simpleRequest(REQUEST_REPORT_STK_SERVICE_IS_RUNNING);
+ },
+
+ /**
+ * Process ICC status.
+ */
+ _processICCStatus: function(iccStatus) {
+ // If |_waitingRadioTech| is true, we should not get app information because
+ // the |_isCdma| flag is not ready yet. Otherwise we may use wrong index to
+ // get app information, especially for the case that icc card has both cdma
+ // and gsm subscription.
+ if (this._waitingRadioTech) {
+ return;
+ }
+
+ // When |iccStatus.cardState| is not CARD_STATE_PRESENT, set cardState to
+ // undetected.
+ if (iccStatus.cardState !== CARD_STATE_PRESENT) {
+ if (this.cardState !== GECKO_CARDSTATE_UNDETECTED) {
+ this.operator = null;
+ // We should send |cardstatechange| before |iccinfochange|, otherwise we
+ // may lost cardstatechange event when icc card becomes undetected.
+ this.cardState = GECKO_CARDSTATE_UNDETECTED;
+ this.sendChromeMessage({rilMessageType: "cardstatechange",
+ cardState: this.cardState});
+
+ this.iccInfo = {iccType: null};
+ this.context.ICCUtilsHelper.handleICCInfoChange();
+ }
+ return;
+ }
+
+ if (RILQUIRKS_SUBSCRIPTION_CONTROL) {
+ // All appIndex is -1 means the subscription is not activated yet.
+ // Note that we don't support "ims" for now, so we don't take it into
+ // account.
+ let neetToActivate = iccStatus.cdmaSubscriptionAppIndex === -1 &&
+ iccStatus.gsmUmtsSubscriptionAppIndex === -1;
+ if (neetToActivate &&
+ // Note: setUiccSubscription works abnormally when RADIO is OFF,
+ // which causes SMS function broken in Flame.
+ // See bug 1008557 for detailed info.
+ this.radioState === GECKO_RADIOSTATE_ENABLED) {
+ for (let i = 0; i < iccStatus.apps.length; i++) {
+ this.setUiccSubscription({appIndex: i, enabled: true});
+ }
+ }
+ }
+
+ let newCardState;
+ let index = this._isCdma ? iccStatus.cdmaSubscriptionAppIndex
+ : iccStatus.gsmUmtsSubscriptionAppIndex;
+ let app = iccStatus.apps[index];
+ if (app) {
+ // fetchICCRecords will need to read aid, so read aid here.
+ this.aid = app.aid;
+ this.appType = app.app_type;
+ this.iccInfo.iccType = GECKO_CARD_TYPE[this.appType];
+
+ switch (app.app_state) {
+ case CARD_APPSTATE_ILLEGAL:
+ newCardState = GECKO_CARDSTATE_ILLEGAL;
+ break;
+ case CARD_APPSTATE_PIN:
+ newCardState = GECKO_CARDSTATE_PIN_REQUIRED;
+ break;
+ case CARD_APPSTATE_PUK:
+ newCardState = GECKO_CARDSTATE_PUK_REQUIRED;
+ break;
+ case CARD_APPSTATE_SUBSCRIPTION_PERSO:
+ newCardState = PERSONSUBSTATE[app.perso_substate];
+ break;
+ case CARD_APPSTATE_READY:
+ newCardState = GECKO_CARDSTATE_READY;
+ break;
+ case CARD_APPSTATE_UNKNOWN:
+ case CARD_APPSTATE_DETECTED:
+ // Fall through.
+ default:
+ newCardState = GECKO_CARDSTATE_UNKNOWN;
+ }
+
+ let pin1State = app.pin1_replaced ? iccStatus.universalPINState :
+ app.pin1;
+ if (pin1State === CARD_PINSTATE_ENABLED_PERM_BLOCKED) {
+ newCardState = GECKO_CARDSTATE_PERMANENT_BLOCKED;
+ }
+ } else {
+ // Having incorrect app information, set card state to unknown.
+ newCardState = GECKO_CARDSTATE_UNKNOWN;
+ }
+
+ let ICCRecordHelper = this.context.ICCRecordHelper;
+ // Try to get iccId only when cardState left GECKO_CARDSTATE_UNDETECTED.
+ if (iccStatus.cardState === CARD_STATE_PRESENT &&
+ (this.cardState === GECKO_CARDSTATE_UNINITIALIZED ||
+ this.cardState === GECKO_CARDSTATE_UNDETECTED)) {
+ ICCRecordHelper.readICCID();
+ }
+
+ if (this.cardState == newCardState) {
+ return;
+ }
+
+ // This was moved down from CARD_APPSTATE_READY
+ this.requestNetworkInfo();
+ if (newCardState == GECKO_CARDSTATE_READY) {
+ // For type SIM, we need to check EF_phase first.
+ // Other types of ICC we can send Terminal_Profile immediately.
+ if (this.appType == CARD_APPTYPE_SIM) {
+ this.context.SimRecordHelper.readSimPhase();
+ } else if (RILQUIRKS_SEND_STK_PROFILE_DOWNLOAD) {
+ this.sendStkTerminalProfile(STK_SUPPORTED_TERMINAL_PROFILE);
+ }
+
+ ICCRecordHelper.fetchICCRecords();
+ }
+
+ this.cardState = newCardState;
+ this.sendChromeMessage({rilMessageType: "cardstatechange",
+ cardState: this.cardState});
+ },
+
+ /**
+ * Helper for processing responses of functions such as enterICC* and changeICC*.
+ */
+ _processEnterAndChangeICCResponses: function(length, options) {
+ options.retryCount = length ? this.context.Buf.readInt32List()[0] : -1;
+ this.sendChromeMessage(options);
+ },
+
+ // We combine all of the NETWORK_INFO_MESSAGE_TYPES into one "networkinfochange"
+ // message to the RadioInterfaceLayer, so we can avoid sending multiple
+ // VoiceInfoChanged events for both operator / voice_data_registration
+ //
+ // State management here is a little tricky. We need to know both:
+ // 1. Whether or not a response was received for each of the
+ // NETWORK_INFO_MESSAGE_TYPES
+ // 2. The outbound message that corresponds with that response -- but this
+ // only happens when internal state changes (i.e. it isn't guaranteed)
+ //
+ // To collect this state, each message response function first calls
+ // _receivedNetworkInfo, to mark the response as received. When the
+ // final response is received, a call to _sendPendingNetworkInfo is placed
+ // on the next tick of the worker thread.
+ //
+ // Since the original call to _receivedNetworkInfo happens at the top
+ // of the response handler, this gives the final handler a chance to
+ // queue up it's "changed" message by calling _sendNetworkInfoMessage if/when
+ // the internal state has actually changed.
+ _sendNetworkInfoMessage: function(type, message) {
+ if (!this._processingNetworkInfo) {
+ // We only combine these messages in the case of the combined request
+ // in requestNetworkInfo()
+ this.sendChromeMessage(message);
+ return;
+ }
+
+ if (DEBUG) {
+ this.context.debug("Queuing " + type + " network info message: " +
+ JSON.stringify(message));
+ }
+ this._pendingNetworkInfo[type] = message;
+ },
+
+ _receivedNetworkInfo: function(type) {
+ if (DEBUG) this.context.debug("Received " + type + " network info.");
+ if (!this._processingNetworkInfo) {
+ return;
+ }
+
+ let pending = this._pendingNetworkInfo;
+
+ // We still need to track states for events that aren't fired.
+ if (!(type in pending)) {
+ pending[type] = this.pendingNetworkType;
+ }
+
+ // Pending network info is ready to be sent when no more messages
+ // are waiting for responses, but the combined payload hasn't been sent.
+ for (let i = 0; i < NETWORK_INFO_MESSAGE_TYPES.length; i++) {
+ let msgType = NETWORK_INFO_MESSAGE_TYPES[i];
+ if (!(msgType in pending)) {
+ if (DEBUG) {
+ this.context.debug("Still missing some more network info, not " +
+ "notifying main thread.");
+ }
+ return;
+ }
+ }
+
+ // Do a pass to clean up the processed messages that didn't create
+ // a response message, so we don't have unused keys in the outbound
+ // networkinfochanged message.
+ for (let key in pending) {
+ if (pending[key] == this.pendingNetworkType) {
+ delete pending[key];
+ }
+ }
+
+ if (DEBUG) {
+ this.context.debug("All pending network info has been received: " +
+ JSON.stringify(pending));
+ }
+
+ // Send the message on the next tick of the worker's loop, so we give the
+ // last message a chance to call _sendNetworkInfoMessage first.
+ setTimeout(this._sendPendingNetworkInfo.bind(this), 0);
+ },
+
+ _sendPendingNetworkInfo: function() {
+ this.sendChromeMessage(this._pendingNetworkInfo);
+
+ this._processingNetworkInfo = false;
+ for (let i = 0; i < NETWORK_INFO_MESSAGE_TYPES.length; i++) {
+ delete this._pendingNetworkInfo[NETWORK_INFO_MESSAGE_TYPES[i]];
+ }
+
+ if (this._needRepollNetworkInfo) {
+ this._needRepollNetworkInfo = false;
+ this.requestNetworkInfo();
+ }
+ },
+
+ /**
+ * Normalize the signal strength in dBm to the signal level from 0 to 100.
+ *
+ * @param signal
+ * The signal strength in dBm to normalize.
+ * @param min
+ * The signal strength in dBm maps to level 0.
+ * @param max
+ * The signal strength in dBm maps to level 100.
+ *
+ * @return level
+ * The signal level from 0 to 100.
+ */
+ _processSignalLevel: function(signal, min, max) {
+ if (signal <= min) {
+ return 0;
+ }
+
+ if (signal >= max) {
+ return 100;
+ }
+
+ return Math.floor((signal - min) * 100 / (max - min));
+ },
+
+ /**
+ * Process LTE signal strength to the signal info object.
+ *
+ * @param signal
+ * The signal object reported from RIL/modem.
+ *
+ * @return The object of signal strength info.
+ * Or null if invalid signal input.
+ *
+ * TODO: Bug 982013: reconsider the format of signal strength APIs for
+ * GSM/CDMA/LTE to expose details, such as rsrp and rsnnr,
+ * individually.
+ */
+ _processLteSignal: function(signal) {
+ let info = {
+ voice: {
+ signalStrength: null,
+ relSignalStrength: null
+ },
+ data: {
+ signalStrength: null,
+ relSignalStrength: null
+ }
+ };
+
+ // Referring to AOSP, use lteRSRP for signalStrength in dBm.
+ let signalStrength = (signal.lteRSRP === undefined || signal.lteRSRP === 0x7FFFFFFF) ?
+ null : signal.lteRSRP;
+ info.voice.signalStrength = info.data.signalStrength = signalStrength;
+
+ // Referring to AOSP, first determine signalLevel based on RSRP and RSSNR,
+ // then on lteSignalStrength if RSRP and RSSNR are invalid.
+ let rsrpLevel = -1;
+ let rssnrLevel = -1;
+ if (signal.lteRSRP !== undefined &&
+ signal.lteRSRP !== 0x7FFFFFFF &&
+ signal.lteRSRP >= 44 &&
+ signal.lteRSRP <= 140) {
+ rsrpLevel = this._processSignalLevel(signal.lteRSRP * -1, -115, -85);
+ }
+
+ if (signal.lteRSSNR !== undefined &&
+ signal.lteRSSNR !== 0x7FFFFFFF &&
+ signal.lteRSSNR >= -200 &&
+ signal.lteRSSNR <= 300) {
+ rssnrLevel = this._processSignalLevel(signal.lteRSSNR, -30, 130);
+ }
+
+ if (rsrpLevel !== -1 && rssnrLevel !== -1) {
+ info.voice.relSignalStrength = info.data.relSignalStrength =
+ Math.min(rsrpLevel, rssnrLevel);
+ return info;
+ }
+
+ let level = Math.max(rsrpLevel, rssnrLevel);
+ if (level !== -1) {
+ info.voice.relSignalStrength = info.data.relSignalStrength = level;
+ return info;
+ }
+
+ // Valid values are 0-63 as defined in TS 27.007 clause 8.69.
+ if (signal.lteSignalStrength !== undefined &&
+ signal.lteSignalStrength >= 0 &&
+ signal.lteSignalStrength <= 63) {
+ level = this._processSignalLevel(signal.lteSignalStrength, 0, 12);
+ info.voice.relSignalStrength = info.data.relSignalStrength = level;
+ return info;
+ }
+
+ return null;
+ },
+
+ _processSignalStrength: function(signal) {
+ let info = {
+ voice: {
+ signalStrength: null,
+ relSignalStrength: null
+ },
+ data: {
+ signalStrength: null,
+ relSignalStrength: null
+ }
+ };
+
+ // During startup, |radioTech| is not yet defined, so we need to
+ // check it separately.
+ if (("radioTech" in this.voiceRegistrationState) &&
+ !this._isGsmTechGroup(this.voiceRegistrationState.radioTech)) {
+ // CDMA RSSI.
+ // Valid values are positive integers. This value is the actual RSSI value
+ // multiplied by -1. Example: If the actual RSSI is -75, then this
+ // response value will be 75.
+ if (signal.cdmaDBM && signal.cdmaDBM > 0) {
+ let signalStrength = -1 * signal.cdmaDBM;
+ info.voice.signalStrength = signalStrength;
+
+ // -105 and -70 are referred to AOSP's implementation. These values are
+ // not constants and can be customized based on different requirement.
+ let signalLevel = this._processSignalLevel(signalStrength, -105, -70);
+ info.voice.relSignalStrength = signalLevel;
+ }
+
+ // EVDO RSSI.
+ // Valid values are positive integers. This value is the actual RSSI value
+ // multiplied by -1. Example: If the actual RSSI is -75, then this
+ // response value will be 75.
+ if (signal.evdoDBM && signal.evdoDBM > 0) {
+ let signalStrength = -1 * signal.evdoDBM;
+ info.data.signalStrength = signalStrength;
+
+ // -105 and -70 are referred to AOSP's implementation. These values are
+ // not constants and can be customized based on different requirement.
+ let signalLevel = this._processSignalLevel(signalStrength, -105, -70);
+ info.data.relSignalStrength = signalLevel;
+ }
+ } else {
+ // Check LTE level first, and check GSM/UMTS level next if LTE one is not
+ // valid.
+ let lteInfo = this._processLteSignal(signal);
+ if (lteInfo) {
+ info = lteInfo;
+ } else {
+ // GSM signal strength.
+ // Valid values are 0-31 as defined in TS 27.007 8.5.
+ // 0 : -113 dBm or less
+ // 1 : -111 dBm
+ // 2...30: -109...-53 dBm
+ // 31 : -51 dBm
+ if (signal.gsmSignalStrength &&
+ signal.gsmSignalStrength >= 0 &&
+ signal.gsmSignalStrength <= 31) {
+ let signalStrength = -113 + 2 * signal.gsmSignalStrength;
+ info.voice.signalStrength = info.data.signalStrength = signalStrength;
+
+ // -115 and -85 are referred to AOSP's implementation. These values are
+ // not constants and can be customized based on different requirement.
+ let signalLevel = this._processSignalLevel(signalStrength, -110, -85);
+ info.voice.relSignalStrength = info.data.relSignalStrength = signalLevel;
+ }
+ }
+ }
+
+ info.rilMessageType = "signalstrengthchange";
+ this._sendNetworkInfoMessage(NETWORK_INFO_SIGNAL, info);
+ },
+
+ /**
+ * Process the network registration flags.
+ *
+ * @return true if the state changed, false otherwise.
+ */
+ _processCREG: function(curState, newState) {
+ let changed = false;
+
+ let regState = this.parseInt(newState[0], NETWORK_CREG_STATE_UNKNOWN);
+ if (curState.regState === undefined || curState.regState !== regState) {
+ changed = true;
+ curState.regState = regState;
+
+ curState.state = NETWORK_CREG_TO_GECKO_MOBILE_CONNECTION_STATE[regState];
+ curState.connected = regState == NETWORK_CREG_STATE_REGISTERED_HOME ||
+ regState == NETWORK_CREG_STATE_REGISTERED_ROAMING;
+ curState.roaming = regState == NETWORK_CREG_STATE_REGISTERED_ROAMING;
+ curState.emergencyCallsOnly = !curState.connected;
+ }
+
+ if (!curState.cell) {
+ curState.cell = {};
+ }
+
+ // From TS 23.003, 0000 and 0xfffe are indicated that no valid LAI exists
+ // in MS. So we still need to report the '0000' as well.
+ let lac = this.parseInt(newState[1], -1, 16);
+ if (curState.cell.gsmLocationAreaCode === undefined ||
+ curState.cell.gsmLocationAreaCode !== lac) {
+ curState.cell.gsmLocationAreaCode = lac;
+ changed = true;
+ }
+
+ let cid = this.parseInt(newState[2], -1, 16);
+ if (curState.cell.gsmCellId === undefined ||
+ curState.cell.gsmCellId !== cid) {
+ curState.cell.gsmCellId = cid;
+ changed = true;
+ }
+
+ let radioTech = (newState[3] === undefined ?
+ NETWORK_CREG_TECH_UNKNOWN :
+ this.parseInt(newState[3], NETWORK_CREG_TECH_UNKNOWN));
+ if (curState.radioTech === undefined || curState.radioTech !== radioTech) {
+ changed = true;
+ curState.radioTech = radioTech;
+ curState.type = GECKO_RADIO_TECH[radioTech] || null;
+ }
+ return changed;
+ },
+
+ _processVoiceRegistrationState: function(state) {
+ let rs = this.voiceRegistrationState;
+ let stateChanged = this._processCREG(rs, state);
+ if (stateChanged && rs.connected) {
+ this.getSmscAddress();
+ }
+
+ let cell = rs.cell;
+ if (this._isCdma) {
+ // Some variables below are not used. Comment them instead of removing to
+ // keep the information about state[x].
+ let cdmaBaseStationId = this.parseInt(state[4], -1);
+ let cdmaBaseStationLatitude = this.parseInt(state[5], -2147483648);
+ let cdmaBaseStationLongitude = this.parseInt(state[6], -2147483648);
+ // let cssIndicator = this.parseInt(state[7]);
+ let cdmaSystemId = this.parseInt(state[8], -1);
+ let cdmaNetworkId = this.parseInt(state[9], -1);
+ // let roamingIndicator = this.parseInt(state[10]);
+ // let systemIsInPRL = this.parseInt(state[11]);
+ // let defaultRoamingIndicator = this.parseInt(state[12]);
+ // let reasonForDenial = this.parseInt(state[13]);
+
+ if (cell.cdmaBaseStationId !== cdmaBaseStationId ||
+ cell.cdmaBaseStationLatitude !== cdmaBaseStationLatitude ||
+ cell.cdmaBaseStationLongitude !== cdmaBaseStationLongitude ||
+ cell.cdmaSystemId !== cdmaSystemId ||
+ cell.cdmaNetworkId !== cdmaNetworkId) {
+ stateChanged = true;
+ cell.cdmaBaseStationId = cdmaBaseStationId;
+ cell.cdmaBaseStationLatitude = cdmaBaseStationLatitude;
+ cell.cdmaBaseStationLongitude = cdmaBaseStationLongitude;
+ cell.cdmaSystemId = cdmaSystemId;
+ cell.cdmaNetworkId = cdmaNetworkId;
+ }
+ }
+
+ if (stateChanged) {
+ rs.rilMessageType = "voiceregistrationstatechange";
+ this._sendNetworkInfoMessage(NETWORK_INFO_VOICE_REGISTRATION_STATE, rs);
+ }
+ },
+
+ _processDataRegistrationState: function(state) {
+ let rs = this.dataRegistrationState;
+ let stateChanged = this._processCREG(rs, state);
+ if (stateChanged) {
+ rs.rilMessageType = "dataregistrationstatechange";
+ this._sendNetworkInfoMessage(NETWORK_INFO_DATA_REGISTRATION_STATE, rs);
+ }
+ },
+
+ _processOperator: function(operatorData) {
+ if (operatorData.length < 3) {
+ if (DEBUG) {
+ this.context.debug("Expected at least 3 strings for operator.");
+ }
+ }
+
+ if (!this.operator) {
+ this.operator = {
+ rilMessageType: "operatorchange",
+ longName: null,
+ shortName: null
+ };
+ }
+
+ let [longName, shortName, networkTuple] = operatorData;
+ let thisTuple = (this.operator.mcc || "") + (this.operator.mnc || "");
+
+ if (this.operator.longName !== longName ||
+ this.operator.shortName !== shortName ||
+ thisTuple !== networkTuple) {
+
+ this.operator.mcc = null;
+ this.operator.mnc = null;
+
+ if (networkTuple) {
+ try {
+ this._processNetworkTuple(networkTuple, this.operator);
+ } catch (e) {
+ if (DEBUG) this.context.debug("Error processing operator tuple: " + e);
+ }
+ } else {
+ // According to ril.h, the operator fields will be NULL when the operator
+ // is not currently registered. We can avoid trying to parse the numeric
+ // tuple in that case.
+ if (DEBUG) {
+ this.context.debug("Operator is currently unregistered");
+ }
+ }
+
+ this.operator.longName = longName;
+ this.operator.shortName = shortName;
+
+ let ICCUtilsHelper = this.context.ICCUtilsHelper;
+ if (ICCUtilsHelper.updateDisplayCondition()) {
+ ICCUtilsHelper.handleICCInfoChange();
+ }
+
+ // NETWORK_INFO_OPERATOR message will be sent out by overrideICCNetworkName
+ // itself if operator name is overridden after checking, or we have to
+ // do it by ourself.
+ if (!this.overrideICCNetworkName()) {
+ this._sendNetworkInfoMessage(NETWORK_INFO_OPERATOR, this.operator);
+ }
+ }
+ },
+
+ _processSuppSvcNotification: function(info) {
+ if (DEBUG) {
+ this.context.debug("handle supp svc notification: " + JSON.stringify(info));
+ }
+
+ if (info.notificationType !== 1) {
+ // We haven't supported MO intermediate result code, i.e.
+ // info.notificationType === 0, which refers to code1 defined in 3GPP
+ // 27.007 7.17. We only support partial MT unsolicited result code,
+ // referring to code2, for now.
+ return;
+ }
+
+ let notification = null;
+
+ switch (info.code) {
+ case SUPP_SVC_NOTIFICATION_CODE2_PUT_ON_HOLD:
+ case SUPP_SVC_NOTIFICATION_CODE2_RETRIEVED:
+ notification = GECKO_SUPP_SVC_NOTIFICATION_FROM_CODE2[info.code];
+ break;
+ default:
+ // Notification type not supported.
+ return;
+ }
+
+ let message = {rilMessageType: "suppSvcNotification",
+ number: info.number, // could be empty.
+ notification: notification};
+ this.sendChromeMessage(message);
+ },
+
+ _cancelEmergencyCbModeTimeout: function() {
+ if (this._exitEmergencyCbModeTimeoutID) {
+ clearTimeout(this._exitEmergencyCbModeTimeoutID);
+ this._exitEmergencyCbModeTimeoutID = null;
+ }
+ },
+
+ _handleChangedEmergencyCbMode: function(active) {
+ this._isInEmergencyCbMode = active;
+
+ // Clear the existed timeout event.
+ this._cancelEmergencyCbModeTimeout();
+
+ // Start a new timeout event when entering the mode.
+ if (active) {
+ this._exitEmergencyCbModeTimeoutID = setTimeout(
+ this.exitEmergencyCbMode.bind(this), EMERGENCY_CB_MODE_TIMEOUT_MS);
+ }
+
+ let message = {rilMessageType: "emergencyCbModeChange",
+ active: active,
+ timeoutMs: EMERGENCY_CB_MODE_TIMEOUT_MS};
+ this.sendChromeMessage(message);
+ },
+
+ _updateNetworkSelectionMode: function(mode) {
+ if (this.networkSelectionMode === mode) {
+ return;
+ }
+
+ let options = {
+ rilMessageType: "networkselectionmodechange",
+ mode: mode
+ };
+ this.networkSelectionMode = mode;
+ this._sendNetworkInfoMessage(NETWORK_INFO_NETWORK_SELECTION_MODE, options);
+ },
+
+ _processNetworks: function() {
+ let strings = this.context.Buf.readStringList();
+ let networks = [];
+
+ for (let i = 0; i < strings.length;
+ i += RILQUIRKS_AVAILABLE_NETWORKS_EXTRA_STRING ? 5 : 4) {
+ let network = {
+ longName: strings[i],
+ shortName: strings[i + 1],
+ mcc: null,
+ mnc: null,
+ state: null
+ };
+
+ let networkTuple = strings[i + 2];
+ try {
+ this._processNetworkTuple(networkTuple, network);
+ } catch (e) {
+ if (DEBUG) this.context.debug("Error processing operator tuple: " + e);
+ }
+
+ let state = strings[i + 3];
+ network.state = RIL_QAN_STATE_TO_GECKO_STATE[state];
+
+ networks.push(network);
+ }
+ return networks;
+ },
+
+ /**
+ * The "numeric" portion of the operator info is a tuple
+ * containing MCC (country code) and MNC (network code).
+ * AFAICT, MCC should always be 3 digits, making the remaining
+ * portion the MNC.
+ */
+ _processNetworkTuple: function(networkTuple, network) {
+ let tupleLen = networkTuple.length;
+
+ if (tupleLen == 5 || tupleLen == 6) {
+ network.mcc = networkTuple.substr(0, 3);
+ network.mnc = networkTuple.substr(3);
+ } else {
+ network.mcc = null;
+ network.mnc = null;
+
+ throw new Error("Invalid network tuple (should be 5 or 6 digits): " + networkTuple);
+ }
+ },
+
+ /**
+ * Check if GSM radio access technology group.
+ */
+ _isGsmTechGroup: function(radioTech) {
+ if (!radioTech) {
+ return true;
+ }
+
+ switch(radioTech) {
+ case NETWORK_CREG_TECH_GPRS:
+ case NETWORK_CREG_TECH_EDGE:
+ case NETWORK_CREG_TECH_UMTS:
+ case NETWORK_CREG_TECH_HSDPA:
+ case NETWORK_CREG_TECH_HSUPA:
+ case NETWORK_CREG_TECH_HSPA:
+ case NETWORK_CREG_TECH_LTE:
+ case NETWORK_CREG_TECH_HSPAP:
+ case NETWORK_CREG_TECH_GSM:
+ case NETWORK_CREG_TECH_DCHSPAP_1:
+ case NETWORK_CREG_TECH_DCHSPAP_2:
+ return true;
+ }
+
+ return false;
+ },
+
+ /**
+ * Process radio technology change.
+ */
+ _processRadioTech: function(radioTech) {
+ let isCdma = !this._isGsmTechGroup(radioTech);
+ this.radioTech = radioTech;
+
+ if (DEBUG) {
+ this.context.debug("Radio tech is set to: " + GECKO_RADIO_TECH[radioTech] +
+ ", it is a " + (isCdma?"cdma":"gsm") + " technology");
+ }
+
+ // We should request SIM information when
+ // 1. Radio state has been changed, so we are waiting for radioTech or
+ // 2. isCdma is different from this._isCdma.
+ if (this._waitingRadioTech || isCdma != this._isCdma) {
+ this._isCdma = isCdma;
+ this._waitingRadioTech = false;
+ this.getICCStatus();
+ }
+ },
+
+ /**
+ * Helper for returning the TOA for the given dial string.
+ */
+ _toaFromString: function(number) {
+ let toa = TOA_UNKNOWN;
+ if (number && number.length > 0 && number[0] == '+') {
+ toa = TOA_INTERNATIONAL;
+ }
+ return toa;
+ },
+
+ /**
+ * @param message A decoded SMS-DELIVER message.
+ *
+ * @see 3GPP TS 31.111 section 7.1.1
+ */
+ dataDownloadViaSMSPP: function(message) {
+ let Buf = this.context.Buf;
+ let GsmPDUHelper = this.context.GsmPDUHelper;
+
+ let options = {
+ pid: message.pid,
+ dcs: message.dcs,
+ encoding: message.encoding,
+ };
+ Buf.newParcel(REQUEST_STK_SEND_ENVELOPE_WITH_STATUS, options);
+
+ Buf.seekIncoming(-1 * (Buf.getCurrentParcelSize() - Buf.getReadAvailable()
+ - 2 * Buf.UINT32_SIZE)); // Skip response_type & request_type.
+ let messageStringLength = Buf.readInt32(); // In semi-octets
+ let smscLength = GsmPDUHelper.readHexOctet(); // In octets, inclusive of TOA
+ let tpduLength = (messageStringLength / 2) - (smscLength + 1); // In octets
+
+ // Device identities: 4 bytes
+ // Address: 0 or (2 + smscLength)
+ // SMS TPDU: (2 or 3) + tpduLength
+ let berLen = 4 +
+ (smscLength ? (2 + smscLength) : 0) +
+ (tpduLength <= 127 ? 2 : 3) + tpduLength; // In octets
+
+ let parcelLength = (berLen <= 127 ? 2 : 3) + berLen; // In octets
+ Buf.writeInt32(parcelLength * 2); // In semi-octets
+
+ // Write a BER-TLV
+ GsmPDUHelper.writeHexOctet(BER_SMS_PP_DOWNLOAD_TAG);
+ if (berLen > 127) {
+ GsmPDUHelper.writeHexOctet(0x81);
+ }
+ GsmPDUHelper.writeHexOctet(berLen);
+
+ // Device Identifies-TLV
+ GsmPDUHelper.writeHexOctet(COMPREHENSIONTLV_TAG_DEVICE_ID |
+ COMPREHENSIONTLV_FLAG_CR);
+ GsmPDUHelper.writeHexOctet(0x02);
+ GsmPDUHelper.writeHexOctet(STK_DEVICE_ID_NETWORK);
+ GsmPDUHelper.writeHexOctet(STK_DEVICE_ID_SIM);
+
+ // Address-TLV
+ if (smscLength) {
+ GsmPDUHelper.writeHexOctet(COMPREHENSIONTLV_TAG_ADDRESS);
+ GsmPDUHelper.writeHexOctet(smscLength);
+ Buf.copyIncomingToOutgoing(Buf.PDU_HEX_OCTET_SIZE * smscLength);
+ }
+
+ // SMS TPDU-TLV
+ GsmPDUHelper.writeHexOctet(COMPREHENSIONTLV_TAG_SMS_TPDU |
+ COMPREHENSIONTLV_FLAG_CR);
+ if (tpduLength > 127) {
+ GsmPDUHelper.writeHexOctet(0x81);
+ }
+ GsmPDUHelper.writeHexOctet(tpduLength);
+ Buf.copyIncomingToOutgoing(Buf.PDU_HEX_OCTET_SIZE * tpduLength);
+
+ // Write 2 string delimitors for the total string length must be even.
+ Buf.writeStringDelimiter(0);
+
+ Buf.sendParcel();
+ },
+
+ /**
+ * @param success A boolean value indicating the result of previous
+ * SMS-DELIVER message handling.
+ * @param responsePduLen ICC IO response PDU length in octets.
+ * @param options An object that contains four attributes: `pid`, `dcs`,
+ * `encoding` and `responsePduLen`.
+ *
+ * @see 3GPP TS 23.040 section 9.2.2.1a
+ */
+ acknowledgeIncomingGsmSmsWithPDU: function(success, responsePduLen, options) {
+ let Buf = this.context.Buf;
+ let GsmPDUHelper = this.context.GsmPDUHelper;
+
+ Buf.newParcel(REQUEST_ACKNOWLEDGE_INCOMING_GSM_SMS_WITH_PDU);
+
+ // Two strings.
+ Buf.writeInt32(2);
+
+ // String 1: Success
+ Buf.writeString(success ? "1" : "0");
+
+ // String 2: RP-ACK/RP-ERROR PDU
+ Buf.writeInt32(2 * (responsePduLen + (success ? 5 : 6))); // In semi-octet
+ // 1. TP-MTI & TP-UDHI
+ GsmPDUHelper.writeHexOctet(PDU_MTI_SMS_DELIVER);
+ if (!success) {
+ // 2. TP-FCS
+ GsmPDUHelper.writeHexOctet(PDU_FCS_USIM_DATA_DOWNLOAD_ERROR);
+ }
+ // 3. TP-PI
+ GsmPDUHelper.writeHexOctet(PDU_PI_USER_DATA_LENGTH |
+ PDU_PI_DATA_CODING_SCHEME |
+ PDU_PI_PROTOCOL_IDENTIFIER);
+ // 4. TP-PID
+ GsmPDUHelper.writeHexOctet(options.pid);
+ // 5. TP-DCS
+ GsmPDUHelper.writeHexOctet(options.dcs);
+ // 6. TP-UDL
+ if (options.encoding == PDU_DCS_MSG_CODING_7BITS_ALPHABET) {
+ GsmPDUHelper.writeHexOctet(Math.floor(responsePduLen * 8 / 7));
+ } else {
+ GsmPDUHelper.writeHexOctet(responsePduLen);
+ }
+ // TP-UD
+ Buf.copyIncomingToOutgoing(Buf.PDU_HEX_OCTET_SIZE * responsePduLen);
+ // Write 2 string delimitors for the total string length must be even.
+ Buf.writeStringDelimiter(0);
+
+ Buf.sendParcel();
+ },
+
+ /**
+ * @param message A decoded SMS-DELIVER message.
+ */
+ writeSmsToSIM: function(message) {
+ let Buf = this.context.Buf;
+ let GsmPDUHelper = this.context.GsmPDUHelper;
+
+ Buf.newParcel(REQUEST_WRITE_SMS_TO_SIM);
+
+ // Write EFsms Status
+ Buf.writeInt32(EFSMS_STATUS_FREE);
+
+ Buf.seekIncoming(-1 * (Buf.getCurrentParcelSize() - Buf.getReadAvailable()
+ - 2 * Buf.UINT32_SIZE)); // Skip response_type & request_type.
+ let messageStringLength = Buf.readInt32(); // In semi-octets
+ let smscLength = GsmPDUHelper.readHexOctet(); // In octets, inclusive of TOA
+ let pduLength = (messageStringLength / 2) - (smscLength + 1); // In octets
+
+ // 1. Write PDU first.
+ if (smscLength > 0) {
+ Buf.seekIncoming(smscLength * Buf.PDU_HEX_OCTET_SIZE);
+ }
+ // Write EFsms PDU string length
+ Buf.writeInt32(2 * pduLength); // In semi-octets
+ if (pduLength) {
+ Buf.copyIncomingToOutgoing(Buf.PDU_HEX_OCTET_SIZE * pduLength);
+ }
+ // Write 2 string delimitors for the total string length must be even.
+ Buf.writeStringDelimiter(0);
+
+ // 2. Write SMSC
+ // Write EFsms SMSC string length
+ Buf.writeInt32(2 * (smscLength + 1)); // Plus smscLength itself, in semi-octets
+ // Write smscLength
+ GsmPDUHelper.writeHexOctet(smscLength);
+ // Write TOA & SMSC Address
+ if (smscLength) {
+ Buf.seekIncoming(-1 * (Buf.getCurrentParcelSize() - Buf.getReadAvailable()
+ - 2 * Buf.UINT32_SIZE // Skip response_type, request_type.
+ - 2 * Buf.PDU_HEX_OCTET_SIZE)); // Skip messageStringLength & smscLength.
+ Buf.copyIncomingToOutgoing(Buf.PDU_HEX_OCTET_SIZE * smscLength);
+ }
+ // Write 2 string delimitors for the total string length must be even.
+ Buf.writeStringDelimiter(0);
+
+ Buf.sendParcel();
+ },
+
+ /**
+ * Helper to delegate the received sms segment to RadioInterface to process.
+ *
+ * @param message
+ * Received sms message.
+ *
+ * @return MOZ_FCS_WAIT_FOR_EXPLICIT_ACK
+ */
+ _processSmsMultipart: function(message) {
+ message.rilMessageType = "sms-received";
+
+ this.sendChromeMessage(message);
+
+ return MOZ_FCS_WAIT_FOR_EXPLICIT_ACK;
+ },
+
+ /**
+ * Helper for processing SMS-STATUS-REPORT PDUs.
+ *
+ * @param length
+ * Length of SMS string in the incoming parcel.
+ *
+ * @return A failure cause defined in 3GPP 23.040 clause 9.2.3.22.
+ */
+ _processSmsStatusReport: function(length) {
+ let [message, result] = this.context.GsmPDUHelper.processReceivedSms(length);
+ if (!message) {
+ if (DEBUG) this.context.debug("invalid SMS-STATUS-REPORT");
+ return PDU_FCS_UNSPECIFIED;
+ }
+
+ let options = this._pendingSentSmsMap[message.messageRef];
+ if (!options) {
+ if (DEBUG) this.context.debug("no pending SMS-SUBMIT message");
+ return PDU_FCS_OK;
+ }
+
+ let status = message.status;
+
+ // 3GPP TS 23.040 9.2.3.15 `The MS shall interpret any reserved values as
+ // "Service Rejected"(01100011) but shall store them exactly as received.`
+ if ((status >= 0x80)
+ || ((status >= PDU_ST_0_RESERVED_BEGIN)
+ && (status < PDU_ST_0_SC_SPECIFIC_BEGIN))
+ || ((status >= PDU_ST_1_RESERVED_BEGIN)
+ && (status < PDU_ST_1_SC_SPECIFIC_BEGIN))
+ || ((status >= PDU_ST_2_RESERVED_BEGIN)
+ && (status < PDU_ST_2_SC_SPECIFIC_BEGIN))
+ || ((status >= PDU_ST_3_RESERVED_BEGIN)
+ && (status < PDU_ST_3_SC_SPECIFIC_BEGIN))
+ ) {
+ status = PDU_ST_3_SERVICE_REJECTED;
+ }
+
+ // Pending. Waiting for next status report.
+ if ((status >>> 5) == 0x01) {
+ if (DEBUG) this.context.debug("SMS-STATUS-REPORT: delivery still pending");
+ return PDU_FCS_OK;
+ }
+
+ delete this._pendingSentSmsMap[message.messageRef];
+
+ let deliveryStatus = ((status >>> 5) === 0x00)
+ ? GECKO_SMS_DELIVERY_STATUS_SUCCESS
+ : GECKO_SMS_DELIVERY_STATUS_ERROR;
+ this.sendChromeMessage({
+ rilMessageType: options.rilMessageType,
+ rilMessageToken: options.rilMessageToken,
+ deliveryStatus: deliveryStatus
+ });
+
+ return PDU_FCS_OK;
+ },
+
+ /**
+ * Helper for processing CDMA SMS Delivery Acknowledgment Message
+ *
+ * @param message
+ * decoded SMS Delivery ACK message from CdmaPDUHelper.
+ *
+ * @return A failure cause defined in 3GPP 23.040 clause 9.2.3.22.
+ */
+ _processCdmaSmsStatusReport: function(message) {
+ let options = this._pendingSentSmsMap[message.msgId];
+ if (!options) {
+ if (DEBUG) this.context.debug("no pending SMS-SUBMIT message");
+ return PDU_FCS_OK;
+ }
+
+ if (message.errorClass === 2) {
+ if (DEBUG) {
+ this.context.debug("SMS-STATUS-REPORT: delivery still pending, " +
+ "msgStatus: " + message.msgStatus);
+ }
+ return PDU_FCS_OK;
+ }
+
+ delete this._pendingSentSmsMap[message.msgId];
+
+ if (message.errorClass === -1 && message.body) {
+ // Process as normal incoming SMS, if errorClass is invalid
+ // but message body is available.
+ return this._processSmsMultipart(message);
+ }
+
+ let deliveryStatus = (message.errorClass === 0)
+ ? GECKO_SMS_DELIVERY_STATUS_SUCCESS
+ : GECKO_SMS_DELIVERY_STATUS_ERROR;
+ this.sendChromeMessage({
+ rilMessageType: options.rilMessageType,
+ rilMessageToken: options.rilMessageToken,
+ deliveryStatus: deliveryStatus
+ });
+
+ return PDU_FCS_OK;
+ },
+
+ /**
+ * Helper for processing CDMA SMS WAP Push Message
+ *
+ * @param message
+ * decoded WAP message from CdmaPDUHelper.
+ *
+ * @return A failure cause defined in 3GPP 23.040 clause 9.2.3.22.
+ */
+ _processCdmaSmsWapPush: function(message) {
+ if (!message.data) {
+ if (DEBUG) this.context.debug("no data inside WAP Push message.");
+ return PDU_FCS_OK;
+ }
+
+ // See 6.5. MAPPING OF WDP TO CDMA SMS in WAP-295-WDP.
+ //
+ // Field | Length (bits)
+ // -----------------------------------------
+ // MSG_TYPE | 8
+ // TOTAL_SEGMENTS | 8
+ // SEGMENT_NUMBER | 8
+ // DATAGRAM | (NUM_FIELDS - 3) * 8
+ let index = 0;
+ if (message.data[index++] !== 0) {
+ if (DEBUG) this.context.debug("Ignore a WAP Message which is not WDP.");
+ return PDU_FCS_OK;
+ }
+
+ // 1. Originator Address in SMS-TL + Message_Id in SMS-TS are used to identify a unique WDP datagram.
+ // 2. TOTAL_SEGMENTS, SEGMENT_NUMBER are used to verify that a complete
+ // datagram has been received and is ready to be passed to a higher layer.
+ message.header = {
+ segmentRef: message.msgId,
+ segmentMaxSeq: message.data[index++],
+ segmentSeq: message.data[index++] + 1 // It's zero-based in CDMA WAP Push.
+ };
+
+ if (message.header.segmentSeq > message.header.segmentMaxSeq) {
+ if (DEBUG) this.context.debug("Wrong WDP segment info.");
+ return PDU_FCS_OK;
+ }
+
+ // Ports are only specified in 1st segment.
+ if (message.header.segmentSeq == 1) {
+ message.header.originatorPort = message.data[index++] << 8;
+ message.header.originatorPort |= message.data[index++];
+ message.header.destinationPort = message.data[index++] << 8;
+ message.header.destinationPort |= message.data[index++];
+ }
+
+ message.data = message.data.subarray(index);
+
+ return this._processSmsMultipart(message);
+ },
+
+ /**
+ * Helper for processing sent multipart SMS.
+ */
+ _processSentSmsSegment: function(options) {
+ // Setup attributes for sending next segment
+ let next = options.segmentSeq;
+ options.body = options.segments[next].body;
+ options.encodedBodyLength = options.segments[next].encodedBodyLength;
+ options.segmentSeq = next + 1;
+
+ this.sendSMS(options);
+ },
+
+ /**
+ * Helper for processing result of send SMS.
+ *
+ * @param length
+ * Length of SMS string in the incoming parcel.
+ * @param options
+ * Sms information.
+ */
+ _processSmsSendResult: function(length, options) {
+ if (options.errorMsg) {
+ if (DEBUG) {
+ this.context.debug("_processSmsSendResult: errorMsg = " +
+ options.errorMsg);
+ }
+
+ this.sendChromeMessage({
+ rilMessageType: options.rilMessageType,
+ rilMessageToken: options.rilMessageToken,
+ errorMsg: options.errorMsg,
+ });
+ return;
+ }
+
+ let Buf = this.context.Buf;
+ options.messageRef = Buf.readInt32();
+ options.ackPDU = Buf.readString();
+ options.errorCode = Buf.readInt32();
+
+ if ((options.segmentMaxSeq > 1)
+ && (options.segmentSeq < options.segmentMaxSeq)) {
+ // Not last segment
+ this._processSentSmsSegment(options);
+ } else {
+ // Last segment sent with success.
+ if (options.requestStatusReport) {
+ if (DEBUG) {
+ this.context.debug("waiting SMS-STATUS-REPORT for messageRef " +
+ options.messageRef);
+ }
+ this._pendingSentSmsMap[options.messageRef] = options;
+ }
+
+ this.sendChromeMessage({
+ rilMessageType: options.rilMessageType,
+ rilMessageToken: options.rilMessageToken,
+ });
+ }
+ },
+
+ _processReceivedSmsCbPage: function(original) {
+ if (original.numPages <= 1) {
+ if (original.body) {
+ original.fullBody = original.body;
+ delete original.body;
+ } else if (original.data) {
+ original.fullData = original.data;
+ delete original.data;
+ }
+ return original;
+ }
+
+ // Hash = <serial>:<mcc>:<mnc>:<lac>:<cid>
+ let hash = original.serial + ":" + this.iccInfo.mcc + ":"
+ + this.iccInfo.mnc + ":";
+ switch (original.geographicalScope) {
+ case CB_GSM_GEOGRAPHICAL_SCOPE_CELL_WIDE_IMMEDIATE:
+ case CB_GSM_GEOGRAPHICAL_SCOPE_CELL_WIDE:
+ hash += this.voiceRegistrationState.cell.gsmLocationAreaCode + ":"
+ + this.voiceRegistrationState.cell.gsmCellId;
+ break;
+ case CB_GSM_GEOGRAPHICAL_SCOPE_LOCATION_AREA_WIDE:
+ hash += this.voiceRegistrationState.cell.gsmLocationAreaCode + ":";
+ break;
+ default:
+ hash += ":";
+ break;
+ }
+
+ let index = original.pageIndex;
+
+ let options = this._receivedSmsCbPagesMap[hash];
+ if (!options) {
+ options = original;
+ this._receivedSmsCbPagesMap[hash] = options;
+
+ options.receivedPages = 0;
+ options.pages = [];
+ } else if (options.pages[index]) {
+ // Duplicated page?
+ if (DEBUG) {
+ this.context.debug("Got duplicated page no." + index +
+ " of a multipage SMSCB: " + JSON.stringify(original));
+ }
+ return null;
+ }
+
+ if (options.encoding == PDU_DCS_MSG_CODING_8BITS_ALPHABET) {
+ options.pages[index] = original.data;
+ delete original.data;
+ } else {
+ options.pages[index] = original.body;
+ delete original.body;
+ }
+ options.receivedPages++;
+ if (options.receivedPages < options.numPages) {
+ if (DEBUG) {
+ this.context.debug("Got page no." + index + " of a multipage SMSCB: " +
+ JSON.stringify(options));
+ }
+ return null;
+ }
+
+ // Remove from map
+ delete this._receivedSmsCbPagesMap[hash];
+
+ // Rebuild full body
+ if (options.encoding == PDU_DCS_MSG_CODING_8BITS_ALPHABET) {
+ // Uint8Array doesn't have `concat`, so we have to merge all pages by hand.
+ let fullDataLen = 0;
+ for (let i = 1; i <= options.numPages; i++) {
+ fullDataLen += options.pages[i].length;
+ }
+
+ options.fullData = new Uint8Array(fullDataLen);
+ for (let d= 0, i = 1; i <= options.numPages; i++) {
+ let data = options.pages[i];
+ for (let j = 0; j < data.length; j++) {
+ options.fullData[d++] = data[j];
+ }
+ }
+ } else {
+ options.fullBody = options.pages.join("");
+ }
+
+ if (DEBUG) {
+ this.context.debug("Got full multipage SMSCB: " + JSON.stringify(options));
+ }
+
+ return options;
+ },
+
+ _mergeCellBroadcastConfigs: function(list, from, to) {
+ if (!list) {
+ return [from, to];
+ }
+
+ for (let i = 0, f1, t1; i < list.length;) {
+ f1 = list[i++];
+ t1 = list[i++];
+ if (to == f1) {
+ // ...[from]...[to|f1]...(t1)
+ list[i - 2] = from;
+ return list;
+ }
+
+ if (to < f1) {
+ // ...[from]...(to)...[f1] or ...[from]...(to)[f1]
+ if (i > 2) {
+ // Not the first range pair, merge three arrays.
+ return list.slice(0, i - 2).concat([from, to]).concat(list.slice(i - 2));
+ } else {
+ return [from, to].concat(list);
+ }
+ }
+
+ if (from > t1) {
+ // ...[f1]...(t1)[from] or ...[f1]...(t1)...[from]
+ continue;
+ }
+
+ // Have overlap or merge-able adjacency with [f1]...(t1). Replace it
+ // with [min(from, f1)]...(max(to, t1)).
+
+ let changed = false;
+ if (from < f1) {
+ // [from]...[f1]...(t1) or [from][f1]...(t1)
+ // Save minimum from value.
+ list[i - 2] = from;
+ changed = true;
+ }
+
+ if (to <= t1) {
+ // [from]...[to](t1) or [from]...(to|t1)
+ // Can't have further merge-able adjacency. Return.
+ return list;
+ }
+
+ // Try merging possible next adjacent range.
+ let j = i;
+ for (let f2, t2; j < list.length;) {
+ f2 = list[j++];
+ t2 = list[j++];
+ if (to > t2) {
+ // [from]...[f2]...[t2]...(to) or [from]...[f2]...[t2](to)
+ // Merge next adjacent range again.
+ continue;
+ }
+
+ if (to < t2) {
+ if (to < f2) {
+ // [from]...(to)[f2] or [from]...(to)...[f2]
+ // Roll back and give up.
+ j -= 2;
+ } else if (to < t2) {
+ // [from]...[to|f2]...(t2), or [from]...[f2]...[to](t2)
+ // Merge to [from]...(t2) and give up.
+ to = t2;
+ }
+ }
+
+ break;
+ }
+
+ // Save maximum to value.
+ list[i - 1] = to;
+
+ if (j != i) {
+ // Remove merged adjacent ranges.
+ let ret = list.slice(0, i);
+ if (j < list.length) {
+ ret = ret.concat(list.slice(j));
+ }
+ return ret;
+ }
+
+ return list;
+ }
+
+ // Append to the end.
+ list.push(from);
+ list.push(to);
+
+ return list;
+ },
+
+ _isCellBroadcastConfigReady: function() {
+ if (!("MMI" in this.cellBroadcastConfigs)) {
+ return false;
+ }
+
+ // CBMI should be ready in GSM.
+ if (!this._isCdma &&
+ (!("CBMI" in this.cellBroadcastConfigs) ||
+ !("CBMID" in this.cellBroadcastConfigs) ||
+ !("CBMIR" in this.cellBroadcastConfigs))) {
+ return false;
+ }
+
+ return true;
+ },
+
+ /**
+ * Merge all members of cellBroadcastConfigs into mergedCellBroadcastConfig.
+ */
+ _mergeAllCellBroadcastConfigs: function() {
+ if (!this._isCellBroadcastConfigReady()) {
+ if (DEBUG) {
+ this.context.debug("cell broadcast configs not ready, waiting ...");
+ }
+ return;
+ }
+
+ // Prepare cell broadcast config. CBMI* are only used in GSM.
+ let usedCellBroadcastConfigs = {MMI: this.cellBroadcastConfigs.MMI};
+ if (!this._isCdma) {
+ usedCellBroadcastConfigs.CBMI = this.cellBroadcastConfigs.CBMI;
+ usedCellBroadcastConfigs.CBMID = this.cellBroadcastConfigs.CBMID;
+ usedCellBroadcastConfigs.CBMIR = this.cellBroadcastConfigs.CBMIR;
+ }
+
+ if (DEBUG) {
+ this.context.debug("Cell Broadcast search lists: " +
+ JSON.stringify(usedCellBroadcastConfigs));
+ }
+
+ let list = null;
+ for (let key in usedCellBroadcastConfigs) {
+ let ll = usedCellBroadcastConfigs[key];
+ if (ll == null) {
+ continue;
+ }
+
+ for (let i = 0; i < ll.length; i += 2) {
+ list = this._mergeCellBroadcastConfigs(list, ll[i], ll[i + 1]);
+ }
+ }
+
+ if (DEBUG) {
+ this.context.debug("Cell Broadcast search lists(merged): " +
+ JSON.stringify(list));
+ }
+ this.mergedCellBroadcastConfig = list;
+ this.updateCellBroadcastConfig();
+ },
+
+ /**
+ * Check whether search list from settings is settable by MMI, that is,
+ * whether the range is bounded in any entries of CB_NON_MMI_SETTABLE_RANGES.
+ */
+ _checkCellBroadcastMMISettable: function(from, to) {
+ if ((to <= from) || (from >= 65536) || (from < 0)) {
+ return false;
+ }
+
+ if (!this._isCdma) {
+ // GSM not settable ranges.
+ for (let i = 0, f, t; i < CB_NON_MMI_SETTABLE_RANGES.length;) {
+ f = CB_NON_MMI_SETTABLE_RANGES[i++];
+ t = CB_NON_MMI_SETTABLE_RANGES[i++];
+ if ((from < t) && (to > f)) {
+ // Have overlap.
+ return false;
+ }
+ }
+ }
+
+ return true;
+ },
+
+ /**
+ * Convert Cell Broadcast settings string into search list.
+ */
+ _convertCellBroadcastSearchList: function(searchListStr) {
+ let parts = searchListStr && searchListStr.split(",");
+ if (!parts) {
+ return null;
+ }
+
+ let list = null;
+ let result, from, to;
+ for (let range of parts) {
+ // Match "12" or "12-34". The result will be ["12", "12", null] or
+ // ["12-34", "12", "34"].
+ result = range.match(/^(\d+)(?:-(\d+))?$/);
+ if (!result) {
+ throw "Invalid format";
+ }
+
+ from = parseInt(result[1], 10);
+ to = (result[2]) ? parseInt(result[2], 10) + 1 : from + 1;
+ if (!this._checkCellBroadcastMMISettable(from, to)) {
+ throw "Invalid range";
+ }
+
+ if (list == null) {
+ list = [];
+ }
+ list.push(from);
+ list.push(to);
+ }
+
+ return list;
+ },
+
+ /**
+ * Handle incoming messages from the main UI thread.
+ *
+ * @param message
+ * Object containing the message. Messages are supposed
+ */
+ handleChromeMessage: function(message) {
+ if (DEBUG) {
+ this.context.debug("Received chrome message " + JSON.stringify(message));
+ }
+ let method = this[message.rilMessageType];
+ if (typeof method != "function") {
+ if (DEBUG) {
+ this.context.debug("Don't know what to do with message " +
+ JSON.stringify(message));
+ }
+ return;
+ }
+ method.call(this, message);
+ },
+
+ /**
+ * Process STK Proactive Command.
+ */
+ processStkProactiveCommand: function() {
+ let Buf = this.context.Buf;
+ let length = Buf.readInt32();
+ let berTlv;
+ try {
+ berTlv = this.context.BerTlvHelper.decode(length / 2);
+ } catch (e) {
+ if (DEBUG) this.context.debug("processStkProactiveCommand : " + e);
+ this.sendStkTerminalResponse({
+ resultCode: STK_RESULT_CMD_DATA_NOT_UNDERSTOOD});
+ return;
+ }
+
+ Buf.readStringDelimiter(length);
+
+ let ctlvs = berTlv.value;
+ let ctlv = this.context.StkProactiveCmdHelper.searchForTag(
+ COMPREHENSIONTLV_TAG_COMMAND_DETAILS, ctlvs);
+ if (!ctlv) {
+ this.sendStkTerminalResponse({
+ resultCode: STK_RESULT_CMD_DATA_NOT_UNDERSTOOD});
+ throw new Error("Can't find COMMAND_DETAILS ComprehensionTlv");
+ }
+
+ let cmdDetails = ctlv.value;
+ if (DEBUG) {
+ this.context.debug("commandNumber = " + cmdDetails.commandNumber +
+ " typeOfCommand = " + cmdDetails.typeOfCommand.toString(16) +
+ " commandQualifier = " + cmdDetails.commandQualifier);
+ }
+
+ // STK_CMD_MORE_TIME need not to propagate event to chrome.
+ if (cmdDetails.typeOfCommand == STK_CMD_MORE_TIME) {
+ this.sendStkTerminalResponse({
+ command: cmdDetails,
+ resultCode: STK_RESULT_OK});
+ return;
+ }
+
+ this.context.StkCommandParamsFactory.createParam(cmdDetails,
+ ctlvs,
+ (aResult) => {
+ cmdDetails.options = aResult;
+ cmdDetails.rilMessageType = "stkcommand";
+ this.sendChromeMessage(cmdDetails);
+ });
+ },
+
+ sendDefaultResponse: function(options) {
+ if (!options.rilMessageType) {
+ return;
+ }
+
+ this.sendChromeMessage(options);
+ },
+
+ /**
+ * Send messages to the main thread.
+ */
+ sendChromeMessage: function(message) {
+ message.rilMessageClientId = this.context.clientId;
+ postMessage(message);
+ },
+
+ /**
+ * Handle incoming requests from the RIL. We find the method that
+ * corresponds to the request type. Incidentally, the request type
+ * _is_ the method name, so that's easy.
+ */
+
+ handleParcel: function(request_type, length, options) {
+ let method = this[request_type];
+ if (typeof method == "function") {
+ if (DEBUG) this.context.debug("Handling parcel as " + method.name);
+ method.call(this, length, options);
+ }
+
+ if (this.telephonyRequestQueue.isValidRequest(request_type)) {
+ this.telephonyRequestQueue.pop(request_type);
+ }
+ }
+};
+
+RilObject.prototype[REQUEST_GET_SIM_STATUS] = function REQUEST_GET_SIM_STATUS(length, options) {
+ if (options.errorMsg) {
+ return;
+ }
+
+ let iccStatus = {};
+ let Buf = this.context.Buf;
+ iccStatus.cardState = Buf.readInt32(); // CARD_STATE_*
+ iccStatus.universalPINState = Buf.readInt32(); // CARD_PINSTATE_*
+ iccStatus.gsmUmtsSubscriptionAppIndex = Buf.readInt32();
+ iccStatus.cdmaSubscriptionAppIndex = Buf.readInt32();
+ iccStatus.imsSubscriptionAppIndex = Buf.readInt32();
+
+ let apps_length = Buf.readInt32();
+ if (apps_length > CARD_MAX_APPS) {
+ apps_length = CARD_MAX_APPS;
+ }
+
+ iccStatus.apps = [];
+ for (let i = 0 ; i < apps_length ; i++) {
+ iccStatus.apps.push({
+ app_type: Buf.readInt32(), // CARD_APPTYPE_*
+ app_state: Buf.readInt32(), // CARD_APPSTATE_*
+ perso_substate: Buf.readInt32(), // CARD_PERSOSUBSTATE_*
+ aid: Buf.readString(),
+ app_label: Buf.readString(),
+ pin1_replaced: Buf.readInt32(),
+ pin1: Buf.readInt32(),
+ pin2: Buf.readInt32()
+ });
+ if (RILQUIRKS_SIM_APP_STATE_EXTRA_FIELDS) {
+ Buf.readInt32();
+ Buf.readInt32();
+ Buf.readInt32();
+ Buf.readInt32();
+ }
+ }
+
+ if (DEBUG) this.context.debug("iccStatus: " + JSON.stringify(iccStatus));
+ this._processICCStatus(iccStatus);
+};
+RilObject.prototype[REQUEST_ENTER_SIM_PIN] = function REQUEST_ENTER_SIM_PIN(length, options) {
+ this._processEnterAndChangeICCResponses(length, options);
+};
+RilObject.prototype[REQUEST_ENTER_SIM_PUK] = function REQUEST_ENTER_SIM_PUK(length, options) {
+ this._processEnterAndChangeICCResponses(length, options);
+};
+RilObject.prototype[REQUEST_ENTER_SIM_PIN2] = function REQUEST_ENTER_SIM_PIN2(length, options) {
+ this._processEnterAndChangeICCResponses(length, options);
+};
+RilObject.prototype[REQUEST_ENTER_SIM_PUK2] = function REQUEST_ENTER_SIM_PUK(length, options) {
+ this._processEnterAndChangeICCResponses(length, options);
+};
+RilObject.prototype[REQUEST_CHANGE_SIM_PIN] = function REQUEST_CHANGE_SIM_PIN(length, options) {
+ this._processEnterAndChangeICCResponses(length, options);
+};
+RilObject.prototype[REQUEST_CHANGE_SIM_PIN2] = function REQUEST_CHANGE_SIM_PIN2(length, options) {
+ this._processEnterAndChangeICCResponses(length, options);
+};
+RilObject.prototype[REQUEST_ENTER_NETWORK_DEPERSONALIZATION_CODE] =
+ function REQUEST_ENTER_NETWORK_DEPERSONALIZATION_CODE(length, options) {
+ this._processEnterAndChangeICCResponses(length, options);
+};
+RilObject.prototype[REQUEST_GET_CURRENT_CALLS] = function REQUEST_GET_CURRENT_CALLS(length, options) {
+ // Retry getCurrentCalls several times when error occurs.
+ if (options.errorMsg) {
+ if (this._getCurrentCallsRetryCount < GET_CURRENT_CALLS_RETRY_MAX) {
+ this._getCurrentCallsRetryCount++;
+ this.getCurrentCalls(options);
+ } else {
+ this.sendDefaultResponse(options);
+ }
+ return;
+ }
+
+ this._getCurrentCallsRetryCount = 0;
+
+ let Buf = this.context.Buf;
+ let calls_length = 0;
+ // The RIL won't even send us the length integer if there are no active calls.
+ // So only read this integer if the parcel actually has it.
+ if (length) {
+ calls_length = Buf.readInt32();
+ }
+
+ let calls = {};
+ for (let i = 0; i < calls_length; i++) {
+ let call = {};
+
+ // Extra uint32 field to get correct callIndex and rest of call data for
+ // call waiting feature.
+ if (RILQUIRKS_EXTRA_UINT32_2ND_CALL && i > 0) {
+ Buf.readInt32();
+ }
+
+ call.state = Buf.readInt32(); // CALL_STATE_*
+ call.callIndex = Buf.readInt32(); // GSM index (1-based)
+ call.toa = Buf.readInt32();
+ call.isMpty = Boolean(Buf.readInt32());
+ call.isMT = Boolean(Buf.readInt32());
+ call.als = Buf.readInt32();
+ call.isVoice = Boolean(Buf.readInt32());
+ call.isVoicePrivacy = Boolean(Buf.readInt32());
+ if (RILQUIRKS_CALLSTATE_EXTRA_UINT32) {
+ Buf.readInt32();
+ }
+ call.number = Buf.readString();
+ call.numberPresentation = Buf.readInt32(); // CALL_PRESENTATION_*
+ call.name = Buf.readString();
+ call.namePresentation = Buf.readInt32();
+
+ call.uusInfo = null;
+ let uusInfoPresent = Buf.readInt32();
+ if (uusInfoPresent == 1) {
+ call.uusInfo = {
+ type: Buf.readInt32(),
+ dcs: Buf.readInt32(),
+ userData: null //XXX TODO byte array?!?
+ };
+ }
+
+ if (call.isVoice) {
+ calls[call.callIndex] = call;
+ }
+ }
+
+ options.calls = calls;
+ options.rilMessageType = options.rilMessageType || "currentCalls";
+ this.sendChromeMessage(options);
+};
+RilObject.prototype[REQUEST_DIAL] = function REQUEST_DIAL(length, options) {
+ this.sendDefaultResponse(options);
+};
+RilObject.prototype[REQUEST_DIAL_EMERGENCY_CALL] = function REQUEST_DIAL_EMERGENCY_CALL(length, options) {
+ RilObject.prototype[REQUEST_DIAL].call(this, length, options);
+};
+RilObject.prototype[REQUEST_GET_IMSI] = function REQUEST_GET_IMSI(length, options) {
+ if (options.errorMsg) {
+ return;
+ }
+
+ this.iccInfoPrivate.imsi = this.context.Buf.readString();
+ if (DEBUG) {
+ this.context.debug("IMSI: " + this.iccInfoPrivate.imsi);
+ }
+
+ options.rilMessageType = "iccimsi";
+ options.imsi = this.iccInfoPrivate.imsi;
+ this.sendChromeMessage(options);
+};
+RilObject.prototype[REQUEST_HANGUP] = function REQUEST_HANGUP(length, options) {
+ this.sendDefaultResponse(options);
+};
+RilObject.prototype[REQUEST_HANGUP_WAITING_OR_BACKGROUND] = function REQUEST_HANGUP_WAITING_OR_BACKGROUND(length, options) {
+ this.sendDefaultResponse(options);
+};
+RilObject.prototype[REQUEST_HANGUP_FOREGROUND_RESUME_BACKGROUND] = function REQUEST_HANGUP_FOREGROUND_RESUME_BACKGROUND(length, options) {
+ this.sendDefaultResponse(options);
+};
+RilObject.prototype[REQUEST_SWITCH_WAITING_OR_HOLDING_AND_ACTIVE] = function REQUEST_SWITCH_WAITING_OR_HOLDING_AND_ACTIVE(length, options) {
+ this.sendDefaultResponse(options);
+};
+RilObject.prototype[REQUEST_CONFERENCE] = function REQUEST_CONFERENCE(length, options) {
+ this.sendDefaultResponse(options);
+};
+RilObject.prototype[REQUEST_UDUB] = function REQUEST_UDUB(length, options) {
+ this.sendDefaultResponse(options);
+};
+RilObject.prototype[REQUEST_LAST_CALL_FAIL_CAUSE] = function REQUEST_LAST_CALL_FAIL_CAUSE(length, options) {
+ // Treat it as CALL_FAIL_ERROR_UNSPECIFIED if the request failed.
+ let failCause = CALL_FAIL_ERROR_UNSPECIFIED;
+
+ if (!options.errorMsg) {
+ let Buf = this.context.Buf;
+ let num = length ? Buf.readInt32() : 0;
+
+ if (num) {
+ let causeNum = Buf.readInt32();
+ failCause = RIL_CALL_FAILCAUSE_TO_GECKO_CALL_ERROR[causeNum] || failCause;
+ }
+ if (DEBUG) this.context.debug("Last call fail cause: " + failCause);
+ }
+
+ options.failCause = failCause;
+ this.sendChromeMessage(options);
+};
+RilObject.prototype[REQUEST_SIGNAL_STRENGTH] = function REQUEST_SIGNAL_STRENGTH(length, options) {
+ this._receivedNetworkInfo(NETWORK_INFO_SIGNAL);
+
+ if (options.errorMsg) {
+ return;
+ }
+
+ let Buf = this.context.Buf;
+ let signal = {};
+
+ signal.gsmSignalStrength = Buf.readInt32();
+ signal.gsmBitErrorRate = Buf.readInt32();
+ if (RILQUIRKS_SIGNAL_EXTRA_INT32) {
+ Buf.readInt32();
+ }
+ signal.cdmaDBM = Buf.readInt32();
+ signal.cdmaECIO = Buf.readInt32();
+ signal.evdoDBM = Buf.readInt32();
+ signal.evdoECIO = Buf.readInt32();
+ signal.evdoSNR = Buf.readInt32();
+
+ signal.lteSignalStrength = Buf.readInt32();
+ signal.lteRSRP = Buf.readInt32();
+ signal.lteRSRQ = Buf.readInt32();
+ signal.lteRSSNR = Buf.readInt32();
+ signal.lteCQI = Buf.readInt32();
+
+ if (DEBUG) this.context.debug("signal strength: " + JSON.stringify(signal));
+
+ this._processSignalStrength(signal);
+};
+RilObject.prototype[REQUEST_VOICE_REGISTRATION_STATE] = function REQUEST_VOICE_REGISTRATION_STATE(length, options) {
+ this._receivedNetworkInfo(NETWORK_INFO_VOICE_REGISTRATION_STATE);
+
+ if (options.errorMsg) {
+ return;
+ }
+
+ let state = this.context.Buf.readStringList();
+ if (DEBUG) this.context.debug("voice registration state: " + state);
+
+ this._processVoiceRegistrationState(state);
+};
+RilObject.prototype[REQUEST_DATA_REGISTRATION_STATE] = function REQUEST_DATA_REGISTRATION_STATE(length, options) {
+ this._receivedNetworkInfo(NETWORK_INFO_DATA_REGISTRATION_STATE);
+
+ if (options.errorMsg) {
+ return;
+ }
+
+ let state = this.context.Buf.readStringList();
+ this._processDataRegistrationState(state);
+};
+RilObject.prototype[REQUEST_OPERATOR] = function REQUEST_OPERATOR(length, options) {
+ this._receivedNetworkInfo(NETWORK_INFO_OPERATOR);
+
+ if (options.errorMsg) {
+ return;
+ }
+
+ let operatorData = this.context.Buf.readStringList();
+ if (DEBUG) this.context.debug("Operator: " + operatorData);
+ this._processOperator(operatorData);
+};
+RilObject.prototype[REQUEST_RADIO_POWER] = function REQUEST_RADIO_POWER(length, options) {
+ this.sendDefaultResponse(options);
+};
+RilObject.prototype[REQUEST_DTMF] = null;
+RilObject.prototype[REQUEST_SEND_SMS] = function REQUEST_SEND_SMS(length, options) {
+ this._processSmsSendResult(length, options);
+};
+RilObject.prototype[REQUEST_SEND_SMS_EXPECT_MORE] = null;
+
+RilObject.prototype[REQUEST_SETUP_DATA_CALL] = function REQUEST_SETUP_DATA_CALL(length, options) {
+ if (options.errorMsg) {
+ this.sendChromeMessage(options);
+ return;
+ }
+
+ let Buf = this.context.Buf;
+ let version = Buf.readInt32();
+ // Skip number of data calls.
+ Buf.readInt32();
+
+ this.readDataCall(options, version);
+ this.sendChromeMessage(options);
+};
+RilObject.prototype[REQUEST_SIM_IO] = function REQUEST_SIM_IO(length, options) {
+ if (options.errorMsg) {
+ if (options.onerror) {
+ options.onerror(options.errorMsg);
+ }
+ return;
+ }
+
+ let Buf = this.context.Buf;
+ options.sw1 = Buf.readInt32();
+ options.sw2 = Buf.readInt32();
+
+ // See 3GPP TS 11.11, clause 9.4.1 for operation success results.
+ if (options.sw1 !== ICC_STATUS_NORMAL_ENDING &&
+ options.sw1 !== ICC_STATUS_NORMAL_ENDING_WITH_EXTRA &&
+ options.sw1 !== ICC_STATUS_WITH_SIM_DATA &&
+ options.sw1 !== ICC_STATUS_WITH_RESPONSE_DATA) {
+ if (DEBUG) {
+ this.context.debug("ICC I/O Error EF id = 0x" + options.fileId.toString(16) +
+ ", command = 0x" + options.command.toString(16) +
+ ", sw1 = 0x" + options.sw1.toString(16) +
+ ", sw2 = 0x" + options.sw2.toString(16));
+ }
+ if (options.onerror) {
+ // We can get fail cause from sw1/sw2 (See TS 11.11 clause 9.4.1 and
+ // ISO 7816-4 clause 6). But currently no one needs this information,
+ // so simply reports "GenericFailure" for now.
+ options.onerror(GECKO_ERROR_GENERIC_FAILURE);
+ }
+ return;
+ }
+ this.context.ICCIOHelper.processICCIO(options);
+};
+RilObject.prototype[REQUEST_SEND_USSD] = function REQUEST_SEND_USSD(length, options) {
+ if (DEBUG) {
+ this.context.debug("REQUEST_SEND_USSD " + JSON.stringify(options));
+ }
+ this.sendChromeMessage(options);
+};
+RilObject.prototype[REQUEST_CANCEL_USSD] = function REQUEST_CANCEL_USSD(length, options) {
+ if (DEBUG) {
+ this.context.debug("REQUEST_CANCEL_USSD" + JSON.stringify(options));
+ }
+ this.sendChromeMessage(options);
+};
+RilObject.prototype[REQUEST_GET_CLIR] = function REQUEST_GET_CLIR(length, options) {
+ if (options.errorMsg) {
+ this.sendChromeMessage(options);
+ return;
+ }
+
+ let Buf = this.context.Buf;
+ let bufLength = Buf.readInt32();
+ if (!bufLength || bufLength < 2) {
+ options.errorMsg = GECKO_ERROR_GENERIC_FAILURE;
+ this.sendChromeMessage(options);
+ return;
+ }
+
+ options.n = Buf.readInt32(); // Will be TS 27.007 +CLIR parameter 'n'.
+ options.m = Buf.readInt32(); // Will be TS 27.007 +CLIR parameter 'm'.
+ this.sendChromeMessage(options);
+};
+RilObject.prototype[REQUEST_SET_CLIR] = function REQUEST_SET_CLIR(length, options) {
+ if (options.rilMessageType == null) {
+ // The request was made by ril_worker itself automatically. Don't report.
+ return;
+ }
+
+ this.sendChromeMessage(options);
+};
+
+RilObject.prototype[REQUEST_QUERY_CALL_FORWARD_STATUS] =
+ function REQUEST_QUERY_CALL_FORWARD_STATUS(length, options) {
+ if (options.errorMsg) {
+ this.sendChromeMessage(options);
+ return;
+ }
+
+ let Buf = this.context.Buf;
+ let rulesLength = 0;
+ if (length) {
+ rulesLength = Buf.readInt32();
+ }
+ if (!rulesLength) {
+ options.errorMsg = GECKO_ERROR_GENERIC_FAILURE;
+ this.sendChromeMessage(options);
+ return;
+ }
+ let rules = new Array(rulesLength);
+ for (let i = 0; i < rulesLength; i++) {
+ let rule = {};
+ rule.active = Buf.readInt32() == 1; // CALL_FORWARD_STATUS_*
+ rule.reason = Buf.readInt32(); // CALL_FORWARD_REASON_*
+ rule.serviceClass = Buf.readInt32();
+ rule.toa = Buf.readInt32();
+ rule.number = Buf.readString();
+ rule.timeSeconds = Buf.readInt32();
+ rules[i] = rule;
+ }
+ options.rules = rules;
+ this.sendChromeMessage(options);
+};
+RilObject.prototype[REQUEST_SET_CALL_FORWARD] =
+ function REQUEST_SET_CALL_FORWARD(length, options) {
+ this.sendChromeMessage(options);
+};
+RilObject.prototype[REQUEST_QUERY_CALL_WAITING] =
+ function REQUEST_QUERY_CALL_WAITING(length, options) {
+ if (options.errorMsg) {
+ this.sendChromeMessage(options);
+ return;
+ }
+
+ let Buf = this.context.Buf;
+ let results = Buf.readInt32List();
+ let enabled = (results[0] === 1);
+ options.serviceClass = enabled ? results[1] : ICC_SERVICE_CLASS_NONE;
+ this.sendChromeMessage(options);
+};
+
+RilObject.prototype[REQUEST_SET_CALL_WAITING] = function REQUEST_SET_CALL_WAITING(length, options) {
+ this.sendChromeMessage(options);
+};
+RilObject.prototype[REQUEST_SMS_ACKNOWLEDGE] = null;
+RilObject.prototype[REQUEST_GET_IMEI] = null;
+RilObject.prototype[REQUEST_GET_IMEISV] = null;
+RilObject.prototype[REQUEST_ANSWER] = function REQUEST_ANSWER(length, options) {
+ this.sendDefaultResponse(options);
+};
+RilObject.prototype[REQUEST_DEACTIVATE_DATA_CALL] = function REQUEST_DEACTIVATE_DATA_CALL(length, options) {
+ this.sendChromeMessage(options);
+};
+RilObject.prototype[REQUEST_QUERY_FACILITY_LOCK] = function REQUEST_QUERY_FACILITY_LOCK(length, options) {
+ if (options.errorMsg) {
+ this.sendChromeMessage(options);
+ return;
+ }
+
+ if (!length) {
+ options.errorMsg = GECKO_ERROR_GENERIC_FAILURE;
+ this.sendChromeMessage(options);
+ return;
+ }
+
+ // Buf.readInt32List()[0] for Call Barring is a bit vector of services.
+ options.serviceClass = this.context.Buf.readInt32List()[0];
+ if (options.queryServiceClass) {
+ options.enabled = (options.serviceClass & options.queryServiceClass) ? true : false;
+ options.serviceClass = options.queryServiceClass;
+ } else {
+ options.enabled = options.serviceClass ? true : false;
+ }
+
+ this.sendChromeMessage(options);
+};
+RilObject.prototype[REQUEST_SET_FACILITY_LOCK] = function REQUEST_SET_FACILITY_LOCK(length, options) {
+ options.retryCount = length ? this.context.Buf.readInt32List()[0] : -1;
+ this.sendChromeMessage(options);
+};
+RilObject.prototype[REQUEST_CHANGE_BARRING_PASSWORD] =
+ function REQUEST_CHANGE_BARRING_PASSWORD(length, options) {
+ this.sendChromeMessage(options);
+};
+RilObject.prototype[REQUEST_QUERY_NETWORK_SELECTION_MODE] = function REQUEST_QUERY_NETWORK_SELECTION_MODE(length, options) {
+ this._receivedNetworkInfo(NETWORK_INFO_NETWORK_SELECTION_MODE);
+
+ if (options.errorMsg) {
+ return;
+ }
+
+ let mode = this.context.Buf.readInt32List();
+ let selectionMode;
+
+ switch (mode[0]) {
+ case NETWORK_SELECTION_MODE_AUTOMATIC:
+ selectionMode = GECKO_NETWORK_SELECTION_AUTOMATIC;
+ break;
+ case NETWORK_SELECTION_MODE_MANUAL:
+ selectionMode = GECKO_NETWORK_SELECTION_MANUAL;
+ break;
+ default:
+ selectionMode = GECKO_NETWORK_SELECTION_UNKNOWN;
+ break;
+ }
+
+ this._updateNetworkSelectionMode(selectionMode);
+};
+RilObject.prototype[REQUEST_SET_NETWORK_SELECTION_AUTOMATIC] = function REQUEST_SET_NETWORK_SELECTION_AUTOMATIC(length, options) {
+ if (!options.errorMsg) {
+ this._updateNetworkSelectionMode(GECKO_NETWORK_SELECTION_AUTOMATIC);
+ }
+ this.sendChromeMessage(options);
+};
+RilObject.prototype[REQUEST_SET_NETWORK_SELECTION_MANUAL] = function REQUEST_SET_NETWORK_SELECTION_MANUAL(length, options) {
+ if (!options.errorMsg) {
+ this._updateNetworkSelectionMode(GECKO_NETWORK_SELECTION_MANUAL);
+ }
+ this.sendChromeMessage(options);
+};
+RilObject.prototype[REQUEST_QUERY_AVAILABLE_NETWORKS] = function REQUEST_QUERY_AVAILABLE_NETWORKS(length, options) {
+ if (!options.errorMsg) {
+ options.networks = this._processNetworks();
+ }
+ this.sendChromeMessage(options);
+};
+RilObject.prototype[REQUEST_DTMF_START] = function REQUEST_DTMF_START(length, options) {
+ this.sendChromeMessage(options);
+};
+RilObject.prototype[REQUEST_DTMF_STOP] = null;
+RilObject.prototype[REQUEST_BASEBAND_VERSION] = function REQUEST_BASEBAND_VERSION(length, options) {
+ if (options.errorMsg) {
+ return;
+ }
+
+ this.basebandVersion = this.context.Buf.readString();
+ if (DEBUG) this.context.debug("Baseband version: " + this.basebandVersion);
+};
+RilObject.prototype[REQUEST_SEPARATE_CONNECTION] = function REQUEST_SEPARATE_CONNECTION(length, options) {
+ this.sendDefaultResponse(options);
+};
+RilObject.prototype[REQUEST_SET_MUTE] = null;
+RilObject.prototype[REQUEST_GET_MUTE] = null;
+RilObject.prototype[REQUEST_QUERY_CLIP] = function REQUEST_QUERY_CLIP(length, options) {
+ if (options.errorMsg) {
+ this.sendChromeMessage(options);
+ return;
+ }
+
+ let Buf = this.context.Buf;
+ let bufLength = Buf.readInt32();
+ if (!bufLength) {
+ options.errorMsg = GECKO_ERROR_GENERIC_FAILURE;
+ this.sendChromeMessage(options);
+ return;
+ }
+
+ options.provisioned = Buf.readInt32();
+ this.sendChromeMessage(options);
+};
+RilObject.prototype[REQUEST_LAST_DATA_CALL_FAIL_CAUSE] = null;
+
+/**
+ * V6:
+ * # addresses - A space-delimited list of addresses with optional "/" prefix
+ * length.
+ * # dnses - A space-delimited list of DNS server addresses.
+ * # gateways - A space-delimited list of default gateway addresses.
+ *
+ * V10:
+ * # pcscf - A space-delimited list of Proxy Call State Control Function
+ * addresses.
+ */
+
+RilObject.prototype.readDataCall = function(options, version) {
+ if (!options) {
+ options = {};
+ }
+ let Buf = this.context.Buf;
+ options.failCause = Buf.readInt32(); // DATACALL_FAIL_*
+ options.suggestedRetryTime = Buf.readInt32();
+ options.cid = Buf.readInt32().toString();
+ options.active = Buf.readInt32(); // DATACALL_ACTIVE_*
+ options.type = Buf.readString();
+ options.ifname = Buf.readString();
+ options.addresses = Buf.readString();
+ options.dnses = Buf.readString();
+ options.gateways = Buf.readString();
+
+ if (version >= 10) {
+ options.pcscf = Buf.readString();
+ }
+
+ if (version >= 11) {
+ let mtu = Buf.readInt32();
+ options.mtu = (mtu > 0) ? mtu : -1 ;
+ }
+
+ return options;
+};
+
+RilObject.prototype[REQUEST_DATA_CALL_LIST] = function REQUEST_DATA_CALL_LIST(length, options) {
+ if (options.errorMsg) {
+ if (options.rilMessageType) {
+ this.sendChromeMessage(options);
+ }
+ return;
+ }
+
+ if (!options.rilMessageType) {
+ // This is an unsolicited data call list changed.
+ options.rilMessageType = "datacalllistchanged";
+ }
+
+ if (!length) {
+ options.datacalls = [];
+ this.sendChromeMessage(options);
+ return;
+ }
+
+ let Buf = this.context.Buf;
+ let version = Buf.readInt32();
+ let num = Buf.readInt32();
+ let datacalls = [];
+ for (let i = 0; i < num; i++) {
+ let datacall;
+ datacall = this.readDataCall({}, version);
+ datacalls.push(datacall);
+ }
+
+ options.datacalls = datacalls;
+ this.sendChromeMessage(options);
+};
+RilObject.prototype[REQUEST_RESET_RADIO] = null;
+RilObject.prototype[REQUEST_OEM_HOOK_RAW] = null;
+RilObject.prototype[REQUEST_OEM_HOOK_STRINGS] = null;
+RilObject.prototype[REQUEST_SCREEN_STATE] = null;
+RilObject.prototype[REQUEST_SET_SUPP_SVC_NOTIFICATION] = null;
+RilObject.prototype[REQUEST_WRITE_SMS_TO_SIM] = function REQUEST_WRITE_SMS_TO_SIM(length, options) {
+ if (options.errorMsg) {
+ // `The MS shall return a "protocol error, unspecified" error message if
+ // the short message cannot be stored in the (U)SIM, and there is other
+ // message storage available at the MS` ~ 3GPP TS 23.038 section 4. Here
+ // we assume we always have indexed db as another storage.
+ this.acknowledgeGsmSms(false, PDU_FCS_PROTOCOL_ERROR);
+ } else {
+ this.acknowledgeGsmSms(true, PDU_FCS_OK);
+ }
+};
+RilObject.prototype[REQUEST_DELETE_SMS_ON_SIM] = null;
+RilObject.prototype[REQUEST_SET_BAND_MODE] = null;
+RilObject.prototype[REQUEST_QUERY_AVAILABLE_BAND_MODE] = null;
+RilObject.prototype[REQUEST_STK_GET_PROFILE] = null;
+RilObject.prototype[REQUEST_STK_SET_PROFILE] = null;
+RilObject.prototype[REQUEST_STK_SEND_ENVELOPE_COMMAND] = null;
+RilObject.prototype[REQUEST_STK_SEND_TERMINAL_RESPONSE] = null;
+RilObject.prototype[REQUEST_STK_HANDLE_CALL_SETUP_REQUESTED_FROM_SIM] = null;
+RilObject.prototype[REQUEST_EXPLICIT_CALL_TRANSFER] = null;
+RilObject.prototype[REQUEST_SET_PREFERRED_NETWORK_TYPE] = function REQUEST_SET_PREFERRED_NETWORK_TYPE(length, options) {
+ this.sendChromeMessage(options);
+};
+RilObject.prototype[REQUEST_GET_PREFERRED_NETWORK_TYPE] = function REQUEST_GET_PREFERRED_NETWORK_TYPE(length, options) {
+ if (options.errorMsg) {
+ this.sendChromeMessage(options);
+ return;
+ }
+
+ options.type = this.context.Buf.readInt32List()[0];
+ this.sendChromeMessage(options);
+};
+RilObject.prototype[REQUEST_GET_NEIGHBORING_CELL_IDS] = function REQUEST_GET_NEIGHBORING_CELL_IDS(length, options) {
+ if (options.errorMsg) {
+ this.sendChromeMessage(options);
+ return;
+ }
+
+ let radioTech = this.voiceRegistrationState.radioTech;
+ if (radioTech == undefined || radioTech == NETWORK_CREG_TECH_UNKNOWN) {
+ options.errorMsg = "RadioTechUnavailable";
+ this.sendChromeMessage(options);
+ return;
+ }
+ if (!this._isGsmTechGroup(radioTech) || radioTech == NETWORK_CREG_TECH_LTE) {
+ options.errorMsg = "UnsupportedRadioTech";
+ this.sendChromeMessage(options);
+ return;
+ }
+
+ let Buf = this.context.Buf;
+ let neighboringCellIds = [];
+ let num = Buf.readInt32();
+
+ for (let i = 0; i < num; i++) {
+ let cellId = {};
+ cellId.networkType = GECKO_RADIO_TECH[radioTech];
+ cellId.signalStrength = Buf.readInt32();
+
+ let cid = Buf.readString();
+ // pad cid string with leading "0"
+ let length = cid.length;
+ if (length > 8) {
+ continue;
+ }
+ if (length < 8) {
+ for (let j = 0; j < (8-length); j++) {
+ cid = "0" + cid;
+ }
+ }
+
+ switch (radioTech) {
+ case NETWORK_CREG_TECH_GPRS:
+ case NETWORK_CREG_TECH_EDGE:
+ case NETWORK_CREG_TECH_GSM:
+ cellId.gsmCellId = this.parseInt(cid.substring(4), -1, 16);
+ cellId.gsmLocationAreaCode = this.parseInt(cid.substring(0, 4), -1, 16);
+ break;
+ case NETWORK_CREG_TECH_UMTS:
+ case NETWORK_CREG_TECH_HSDPA:
+ case NETWORK_CREG_TECH_HSUPA:
+ case NETWORK_CREG_TECH_HSPA:
+ case NETWORK_CREG_TECH_HSPAP:
+ case NETWORK_CREG_TECH_DCHSPAP_1:
+ case NETWORK_CREG_TECH_DCHSPAP_2:
+ cellId.wcdmaPsc = this.parseInt(cid, -1, 16);
+ break;
+ }
+
+ neighboringCellIds.push(cellId);
+ }
+
+ options.result = neighboringCellIds;
+ this.sendChromeMessage(options);
+};
+RilObject.prototype[REQUEST_GET_CELL_INFO_LIST] = function REQUEST_GET_CELL_INFO_LIST(length, options) {
+ if (options.errorMsg) {
+ this.sendChromeMessage(options);
+ return;
+ }
+
+ let Buf = this.context.Buf;
+ let cellInfoList = [];
+ let num = Buf.readInt32();
+ for (let i = 0; i < num; i++) {
+ let cellInfo = {};
+ cellInfo.type = Buf.readInt32();
+ cellInfo.registered = Buf.readInt32() ? true : false;
+ cellInfo.timestampType = Buf.readInt32();
+ cellInfo.timestamp = Buf.readInt64();
+
+ switch(cellInfo.type) {
+ case CELL_INFO_TYPE_GSM:
+ case CELL_INFO_TYPE_WCDMA:
+ cellInfo.mcc = Buf.readInt32();
+ cellInfo.mnc = Buf.readInt32();
+ cellInfo.lac = Buf.readInt32();
+ cellInfo.cid = Buf.readInt32();
+ if (cellInfo.type == CELL_INFO_TYPE_WCDMA) {
+ cellInfo.psc = Buf.readInt32();
+ }
+ cellInfo.signalStrength = Buf.readInt32();
+ cellInfo.bitErrorRate = Buf.readInt32();
+ break;
+ case CELL_INFO_TYPE_CDMA:
+ cellInfo.networkId = Buf.readInt32();
+ cellInfo.systemId = Buf.readInt32();
+ cellInfo.basestationId = Buf.readInt32();
+ cellInfo.longitude = Buf.readInt32();
+ cellInfo.latitude = Buf.readInt32();
+ cellInfo.cdmaDbm = Buf.readInt32();
+ cellInfo.cdmaEcio = Buf.readInt32();
+ cellInfo.evdoDbm = Buf.readInt32();
+ cellInfo.evdoEcio = Buf.readInt32();
+ cellInfo.evdoSnr = Buf.readInt32();
+ break;
+ case CELL_INFO_TYPE_LTE:
+ cellInfo.mcc = Buf.readInt32();
+ cellInfo.mnc = Buf.readInt32();
+ cellInfo.cid = Buf.readInt32();
+ cellInfo.pcid = Buf.readInt32();
+ cellInfo.tac = Buf.readInt32();
+ cellInfo.signalStrength = Buf.readInt32();
+ cellInfo.rsrp = Buf.readInt32();
+ cellInfo.rsrq = Buf.readInt32();
+ cellInfo.rssnr = Buf.readInt32();
+ cellInfo.cqi = Buf.readInt32();
+ break;
+ }
+ cellInfoList.push(cellInfo);
+ }
+ options.result = cellInfoList;
+ this.sendChromeMessage(options);
+};
+RilObject.prototype[REQUEST_SET_LOCATION_UPDATES] = null;
+RilObject.prototype[REQUEST_CDMA_SET_SUBSCRIPTION_SOURCE] = null;
+RilObject.prototype[REQUEST_CDMA_SET_ROAMING_PREFERENCE] = function REQUEST_CDMA_SET_ROAMING_PREFERENCE(length, options) {
+ this.sendChromeMessage(options);
+};
+RilObject.prototype[REQUEST_CDMA_QUERY_ROAMING_PREFERENCE] = function REQUEST_CDMA_QUERY_ROAMING_PREFERENCE(length, options) {
+ if (!options.errorMsg) {
+ options.mode = this.context.Buf.readInt32List()[0];
+ }
+ this.sendChromeMessage(options);
+};
+RilObject.prototype[REQUEST_SET_TTY_MODE] = null;
+RilObject.prototype[REQUEST_QUERY_TTY_MODE] = null;
+RilObject.prototype[REQUEST_CDMA_SET_PREFERRED_VOICE_PRIVACY_MODE] = function REQUEST_CDMA_SET_PREFERRED_VOICE_PRIVACY_MODE(length, options) {
+ this.sendChromeMessage(options);
+};
+RilObject.prototype[REQUEST_CDMA_QUERY_PREFERRED_VOICE_PRIVACY_MODE] = function REQUEST_CDMA_QUERY_PREFERRED_VOICE_PRIVACY_MODE(length, options) {
+ if (options.errorMsg) {
+ this.sendChromeMessage(options);
+ return;
+ }
+
+ let enabled = this.context.Buf.readInt32List();
+ options.enabled = enabled[0] ? true : false;
+ this.sendChromeMessage(options);
+};
+RilObject.prototype[REQUEST_CDMA_FLASH] = function REQUEST_CDMA_FLASH(length, options) {
+ this.sendDefaultResponse(options);
+};
+RilObject.prototype[REQUEST_CDMA_BURST_DTMF] = null;
+RilObject.prototype[REQUEST_CDMA_VALIDATE_AND_WRITE_AKEY] = null;
+RilObject.prototype[REQUEST_CDMA_SEND_SMS] = function REQUEST_CDMA_SEND_SMS(length, options) {
+ this._processSmsSendResult(length, options);
+};
+RilObject.prototype[REQUEST_CDMA_SMS_ACKNOWLEDGE] = null;
+RilObject.prototype[REQUEST_GSM_GET_BROADCAST_SMS_CONFIG] = null;
+RilObject.prototype[REQUEST_GSM_SET_BROADCAST_SMS_CONFIG] = function REQUEST_GSM_SET_BROADCAST_SMS_CONFIG(length, options) {
+ if (options.errorMsg) {
+ return;
+ }
+ this.setSmsBroadcastActivation(true);
+};
+RilObject.prototype[REQUEST_GSM_SMS_BROADCAST_ACTIVATION] = null;
+RilObject.prototype[REQUEST_CDMA_GET_BROADCAST_SMS_CONFIG] = null;
+RilObject.prototype[REQUEST_CDMA_SET_BROADCAST_SMS_CONFIG] = function REQUEST_CDMA_SET_BROADCAST_SMS_CONFIG(length, options) {
+ if (options.errorMsg) {
+ return;
+ }
+ this.setSmsBroadcastActivation(true);
+};
+RilObject.prototype[REQUEST_CDMA_SMS_BROADCAST_ACTIVATION] = null;
+RilObject.prototype[REQUEST_CDMA_SUBSCRIPTION] = function REQUEST_CDMA_SUBSCRIPTION(length, options) {
+ if (options.errorMsg) {
+ return;
+ }
+
+ let result = this.context.Buf.readStringList();
+
+ this.iccInfo.mdn = result[0];
+ // The result[1] is Home SID. (Already be handled in readCDMAHome())
+ // The result[2] is Home NID. (Already be handled in readCDMAHome())
+ // The result[3] is MIN.
+ this.iccInfo.prlVersion = parseInt(result[4], 10);
+
+ this.context.ICCUtilsHelper.handleICCInfoChange();
+};
+RilObject.prototype[REQUEST_CDMA_WRITE_SMS_TO_RUIM] = null;
+RilObject.prototype[REQUEST_CDMA_DELETE_SMS_ON_RUIM] = null;
+RilObject.prototype[REQUEST_DEVICE_IDENTITY] = function REQUEST_DEVICE_IDENTITY(length, options) {
+ if (options.errorMsg) {
+ this.context.debug("Failed to get device identities:" + options.errorMsg);
+ return;
+ }
+
+ let result = this.context.Buf.readStringList();
+ this.deviceIdentities = {
+ imei: result[0] || null,
+ imeisv: result[1] || null,
+ esn: result[2] || null,
+ meid: result[3] || null,
+ };
+
+ this.sendChromeMessage({
+ rilMessageType: "deviceidentitieschange",
+ deviceIdentities: this.deviceIdentities
+ });
+};
+RilObject.prototype[REQUEST_EXIT_EMERGENCY_CALLBACK_MODE] = function REQUEST_EXIT_EMERGENCY_CALLBACK_MODE(length, options) {
+ if (options.internal) {
+ return;
+ }
+
+ this.sendChromeMessage(options);
+};
+RilObject.prototype[REQUEST_GET_SMSC_ADDRESS] = function REQUEST_GET_SMSC_ADDRESS(length, options) {
+ if (!options.rilMessageType || options.rilMessageType !== "getSmscAddress") {
+ return;
+ }
+
+ if (options.errorMsg) {
+ this.sendChromeMessage(options);
+ return;
+ }
+
+ let tosca = TOA_UNKNOWN;
+ let smsc = "";
+ let Buf = this.context.Buf;
+ if (RILQUIRKS_SMSC_ADDRESS_FORMAT === "pdu") {
+ let pduHelper = this.context.GsmPDUHelper;
+ let strlen = Buf.readInt32();
+ let length = pduHelper.readHexOctet();
+
+ // As defined in |8.2.5.2 Destination address element| of 3GPP TS 24.011,
+ // the value of length field can not exceed 11. Since the content might be
+ // filled with 12 'F' when SMSC is cleared, we don't parse the TOA and
+ // address fields if reported length exceeds 11 here. Instead, keep the
+ // default value (TOA_UNKNOWN with an empty address) in this case.
+ const MAX_LENGTH = 11
+ if (length <= MAX_LENGTH) {
+ tosca = pduHelper.readHexOctet();
+
+ // Read and covert the decimal values back to special BCD digits defined in
+ // |Called party BCD number| of 3GPP TS 24.008 (refer the following table).
+ //
+ // +=========+=======+=====+
+ // | value | digit | hex |
+ // +========================
+ // | 1 0 1 0 | * | 0xA |
+ // | 1 0 1 1 | # | 0xB |
+ // | 1 1 0 0 | a | 0xC |
+ // | 1 1 0 1 | b | 0xD |
+ // | 1 1 1 0 | c | 0xE |
+ // +=========+=======+=====+
+ smsc = pduHelper.readSwappedNibbleBcdString(length - 1, true)
+ .replace(/a/ig, "*")
+ .replace(/b/ig, "#")
+ .replace(/c/ig, "a")
+ .replace(/d/ig, "b")
+ .replace(/e/ig, "c");
+
+ Buf.readStringDelimiter(strlen);
+ }
+ } else /* RILQUIRKS_SMSC_ADDRESS_FORMAT === "text" */ {
+ let text = Buf.readString();
+ let segments = text.split(",", 2);
+ // Parse TOA only if it presents since some devices might omit the TOA
+ // segment in the reported SMSC address. If TOA does not present, keep the
+ // default value TOA_UNKNOWN.
+ if (segments.length === 2) {
+ tosca = this.parseInt(segments[1], TOA_UNKNOWN, 10);
+ }
+
+ smsc = segments[0].replace(/\"/g, "");
+ }
+
+ // Convert the NPI value to the corresponding index of CALLED_PARTY_BCD_NPI
+ // array. If the value does not present in the array, use
+ // CALLED_PARTY_BCD_NPI_ISDN.
+ let npi = CALLED_PARTY_BCD_NPI.indexOf(tosca & 0xf);
+ if (npi === -1) {
+ npi = CALLED_PARTY_BCD_NPI.indexOf(CALLED_PARTY_BCD_NPI_ISDN);
+ }
+
+ // Extract TON.
+ let ton = (tosca & 0x70) >> 4;
+
+ // Ensure + sign if TON is international, and vice versa.
+ const TON_INTERNATIONAL = (TOA_INTERNATIONAL & 0x70) >> 4;
+ if (ton === TON_INTERNATIONAL && smsc.charAt(0) !== "+") {
+ smsc = "+" + smsc;
+ } else if (smsc.charAt(0) === "+" && ton !== TON_INTERNATIONAL) {
+ if (DEBUG) {
+ this.context.debug("SMSC address number begins with '+' while the TON is not international. Change TON to international.");
+ }
+ ton = TON_INTERNATIONAL;
+ }
+
+ options.smscAddress = smsc;
+ options.typeOfNumber = ton;
+ options.numberPlanIdentification = npi;
+ this.sendChromeMessage(options);
+};
+RilObject.prototype[REQUEST_SET_SMSC_ADDRESS] = function REQUEST_SET_SMSC_ADDRESS(length, options) {
+ if (!options.rilMessageType || options.rilMessageType !== "setSmscAddress") {
+ return;
+ }
+
+ this.sendChromeMessage(options);
+};
+RilObject.prototype[REQUEST_REPORT_SMS_MEMORY_STATUS] = function REQUEST_REPORT_SMS_MEMORY_STATUS(length, options) {
+ this.pendingToReportSmsMemoryStatus = !!options.errorMsg;
+};
+RilObject.prototype[REQUEST_REPORT_STK_SERVICE_IS_RUNNING] = null;
+RilObject.prototype[REQUEST_CDMA_GET_SUBSCRIPTION_SOURCE] = null;
+RilObject.prototype[REQUEST_ISIM_AUTHENTICATION] = null;
+RilObject.prototype[REQUEST_ACKNOWLEDGE_INCOMING_GSM_SMS_WITH_PDU] = null;
+RilObject.prototype[REQUEST_STK_SEND_ENVELOPE_WITH_STATUS] = function REQUEST_STK_SEND_ENVELOPE_WITH_STATUS(length, options) {
+ if (options.errorMsg) {
+ this.acknowledgeGsmSms(false, PDU_FCS_UNSPECIFIED);
+ return;
+ }
+
+ let Buf = this.context.Buf;
+ let sw1 = Buf.readInt32();
+ let sw2 = Buf.readInt32();
+ if ((sw1 == ICC_STATUS_SAT_BUSY) && (sw2 === 0x00)) {
+ this.acknowledgeGsmSms(false, PDU_FCS_USAT_BUSY);
+ return;
+ }
+
+ let success = ((sw1 == ICC_STATUS_NORMAL_ENDING) && (sw2 === 0x00))
+ || (sw1 == ICC_STATUS_NORMAL_ENDING_WITH_EXTRA);
+
+ let messageStringLength = Buf.readInt32(); // In semi-octets
+ let responsePduLen = messageStringLength / 2; // In octets
+ if (!responsePduLen) {
+ this.acknowledgeGsmSms(success, success ? PDU_FCS_OK
+ : PDU_FCS_USIM_DATA_DOWNLOAD_ERROR);
+ return;
+ }
+
+ this.acknowledgeIncomingGsmSmsWithPDU(success, responsePduLen, options);
+};
+RilObject.prototype[REQUEST_VOICE_RADIO_TECH] = function REQUEST_VOICE_RADIO_TECH(length, options) {
+ if (options.errorMsg) {
+ if (DEBUG) {
+ this.context.debug("Error when getting voice radio tech: " +
+ options.errorMsg);
+ }
+ return;
+ }
+ let radioTech = this.context.Buf.readInt32List();
+ this._processRadioTech(radioTech[0]);
+};
+RilObject.prototype[REQUEST_SET_UNSOL_CELL_INFO_LIST_RATE] = null;
+RilObject.prototype[REQUEST_SET_INITIAL_ATTACH_APN] = null;
+RilObject.prototype[REQUEST_IMS_REGISTRATION_STATE] = null;
+RilObject.prototype[REQUEST_IMS_SEND_SMS] = null;
+RilObject.prototype[REQUEST_SIM_TRANSMIT_APDU_BASIC] = null;
+RilObject.prototype[REQUEST_SIM_OPEN_CHANNEL] = function REQUEST_SIM_OPEN_CHANNEL(length, options) {
+ if (options.errorMsg) {
+ this.sendChromeMessage(options);
+ return;
+ }
+
+ options.channel = this.context.Buf.readInt32List()[0];
+ // onwards may optionally contain the select response for the open channel
+ // command with one byte per integer.
+ this.sendChromeMessage(options);
+};
+RilObject.prototype[REQUEST_SIM_CLOSE_CHANNEL] = function REQUEST_SIM_CLOSE_CHANNEL(length, options) {
+ this.sendDefaultResponse(options);
+};
+RilObject.prototype[REQUEST_SIM_TRANSMIT_APDU_CHANNEL] = function REQUEST_SIM_TRANSMIT_APDU_CHANNEL(length, options) {
+ if (options.errorMsg) {
+ this.sendChromeMessage(options);
+ return;
+ }
+
+ let Buf = this.context.Buf;
+ options.sw1 = Buf.readInt32();
+ options.sw2 = Buf.readInt32();
+ options.simResponse = Buf.readString();
+ if (DEBUG) {
+ this.context.debug("Setting return values for RIL[REQUEST_SIM_TRANSMIT_APDU_CHANNEL]: [" +
+ options.sw1 + "," +
+ options.sw2 + ", " +
+ options.simResponse + "]");
+ }
+ this.sendChromeMessage(options);
+};
+RilObject.prototype[REQUEST_NV_READ_ITEM] = null;
+RilObject.prototype[REQUEST_NV_WRITE_ITEM] = null;
+RilObject.prototype[REQUEST_NV_WRITE_CDMA_PRL] = null;
+RilObject.prototype[REQUEST_NV_RESET_CONFIG] = null;
+RilObject.prototype[REQUEST_SET_UICC_SUBSCRIPTION] = function REQUEST_SET_UICC_SUBSCRIPTION(length, options) {
+ // Resend data subscription after uicc subscription.
+ if (this._attachDataRegistration) {
+ this.setDataRegistration({attach: true});
+ }
+};
+RilObject.prototype[REQUEST_ALLOW_DATA] = null;
+RilObject.prototype[REQUEST_GET_HARDWARE_CONFIG] = null;
+RilObject.prototype[REQUEST_SIM_AUTHENTICATION] = null;
+RilObject.prototype[REQUEST_GET_DC_RT_INFO] = null;
+RilObject.prototype[REQUEST_SET_DC_RT_INFO_RATE] = null;
+RilObject.prototype[REQUEST_SET_DATA_PROFILE] = null;
+RilObject.prototype[REQUEST_SHUTDOWN] = null;
+RilObject.prototype[REQUEST_SET_DATA_SUBSCRIPTION] = function REQUEST_SET_DATA_SUBSCRIPTION(length, options) {
+ if (!options.rilMessageType) {
+ // The request was made by ril_worker itself. Don't report.
+ return;
+ }
+ this.sendChromeMessage(options);
+};
+RilObject.prototype[REQUEST_GET_UNLOCK_RETRY_COUNT] = function REQUEST_GET_UNLOCK_RETRY_COUNT(length, options) {
+ options.retryCount = length ? this.context.Buf.readInt32List()[0] : -1;
+ this.sendChromeMessage(options);
+};
+RilObject.prototype[RIL_REQUEST_GPRS_ATTACH] = function RIL_REQUEST_GPRS_ATTACH(length, options) {
+ if (!options.rilMessageType) {
+ // The request was made by ril_worker itself. Don't report.
+ return;
+ }
+ this.sendChromeMessage(options);
+};
+RilObject.prototype[RIL_REQUEST_GPRS_DETACH] = function RIL_REQUEST_GPRS_DETACH(length, options) {
+ this.sendChromeMessage(options);
+};
+RilObject.prototype[UNSOLICITED_RESPONSE_RADIO_STATE_CHANGED] = function UNSOLICITED_RESPONSE_RADIO_STATE_CHANGED() {
+ let radioState = this.context.Buf.readInt32();
+ let newState;
+ switch (radioState) {
+ case RADIO_STATE_UNAVAILABLE:
+ newState = GECKO_RADIOSTATE_UNKNOWN;
+ break;
+ case RADIO_STATE_OFF:
+ newState = GECKO_RADIOSTATE_DISABLED;
+ break;
+ default:
+ newState = GECKO_RADIOSTATE_ENABLED;
+ }
+
+ if (DEBUG) {
+ this.context.debug("Radio state changed from '" + this.radioState +
+ "' to '" + newState + "'");
+ }
+ if (this.radioState == newState) {
+ return;
+ }
+
+ if (radioState !== RADIO_STATE_UNAVAILABLE) {
+ // Retrieve device identities once radio is available.
+ this.getDeviceIdentity();
+ }
+
+ if (radioState == RADIO_STATE_ON) {
+ // This value is defined in RIL v7, we will retrieve radio tech by another
+ // request. We leave _isCdma untouched, and it will be set once we get the
+ // radio technology.
+ this._waitingRadioTech = true;
+ this.getVoiceRadioTechnology();
+ }
+
+ if ((this.radioState == GECKO_RADIOSTATE_UNKNOWN ||
+ this.radioState == GECKO_RADIOSTATE_DISABLED) &&
+ newState == GECKO_RADIOSTATE_ENABLED) {
+ // The radio became available, let's get its info.
+ this.getBasebandVersion();
+ this.updateCellBroadcastConfig();
+ if ((RILQUIRKS_DATA_REGISTRATION_ON_DEMAND ||
+ RILQUIRKS_SUBSCRIPTION_CONTROL) &&
+ this._attachDataRegistration) {
+ this.setDataRegistration({attach: true});
+ }
+
+ if (this.pendingToReportSmsMemoryStatus) {
+ this._updateSmsMemoryStatus();
+ }
+ }
+
+ this.radioState = newState;
+ this.sendChromeMessage({
+ rilMessageType: "radiostatechange",
+ radioState: newState
+ });
+
+ // If the radio is up and on, so let's query the card state.
+ // On older RILs only if the card is actually ready, though.
+ // If _waitingRadioTech is set, we don't need to get icc status now.
+ if (radioState == RADIO_STATE_UNAVAILABLE ||
+ radioState == RADIO_STATE_OFF ||
+ this._waitingRadioTech) {
+ return;
+ }
+ this.getICCStatus();
+};
+RilObject.prototype[UNSOLICITED_RESPONSE_CALL_STATE_CHANGED] = function UNSOLICITED_RESPONSE_CALL_STATE_CHANGED() {
+ this.getCurrentCalls();
+};
+RilObject.prototype[UNSOLICITED_RESPONSE_VOICE_NETWORK_STATE_CHANGED] = function UNSOLICITED_RESPONSE_VOICE_NETWORK_STATE_CHANGED() {
+ if (DEBUG) {
+ this.context.debug("Network state changed, re-requesting phone state and " +
+ "ICC status");
+ }
+ this.getICCStatus();
+ this.requestNetworkInfo();
+};
+RilObject.prototype[UNSOLICITED_RESPONSE_NEW_SMS] = function UNSOLICITED_RESPONSE_NEW_SMS(length) {
+ let [message, result] = this.context.GsmPDUHelper.processReceivedSms(length);
+
+ if (message) {
+ result = this._processSmsMultipart(message);
+ }
+
+ if (result == PDU_FCS_RESERVED || result == MOZ_FCS_WAIT_FOR_EXPLICIT_ACK) {
+ return;
+ }
+
+ // Not reserved FCS values, send ACK now.
+ this.acknowledgeGsmSms(result == PDU_FCS_OK, result);
+};
+RilObject.prototype[UNSOLICITED_RESPONSE_NEW_SMS_STATUS_REPORT] = function UNSOLICITED_RESPONSE_NEW_SMS_STATUS_REPORT(length) {
+ let result = this._processSmsStatusReport(length);
+ this.acknowledgeGsmSms(result == PDU_FCS_OK, result);
+};
+RilObject.prototype[UNSOLICITED_RESPONSE_NEW_SMS_ON_SIM] = function UNSOLICITED_RESPONSE_NEW_SMS_ON_SIM(length) {
+ let recordNumber = this.context.Buf.readInt32List()[0];
+
+ this.context.SimRecordHelper.readSMS(
+ recordNumber,
+ function onsuccess(message) {
+ if (message && message.simStatus === 3) { //New Unread SMS
+ this._processSmsMultipart(message);
+ }
+ }.bind(this),
+ function onerror(errorMsg) {
+ if (DEBUG) {
+ this.context.debug("Failed to Read NEW SMS on SIM #" + recordNumber +
+ ", errorMsg: " + errorMsg);
+ }
+ });
+};
+RilObject.prototype[UNSOLICITED_ON_USSD] = function UNSOLICITED_ON_USSD() {
+ let [typeCode, message] = this.context.Buf.readStringList();
+ if (DEBUG) {
+ this.context.debug("On USSD. Type Code: " + typeCode + " Message: " + message);
+ }
+
+ this.sendChromeMessage({rilMessageType: "ussdreceived",
+ message: message,
+ // Per ril.h the USSD session is assumed to persist if
+ // the type code is "1", otherwise the current session
+ // (if any) is assumed to have terminated.
+ sessionEnded: typeCode !== "1"});
+};
+RilObject.prototype[UNSOLICITED_ON_USSD_REQUEST] = null;
+RilObject.prototype[UNSOLICITED_NITZ_TIME_RECEIVED] = function UNSOLICITED_NITZ_TIME_RECEIVED() {
+ let dateString = this.context.Buf.readString();
+
+ // The data contained in the NITZ message is
+ // in the form "yy/mm/dd,hh:mm:ss(+/-)tz,dt"
+ // for example: 12/02/16,03:36:08-20,00,310410
+ // See also bug 714352 - Listen for NITZ updates from rild.
+
+ if (DEBUG) this.context.debug("DateTimeZone string " + dateString);
+
+ let now = Date.now();
+
+ let year = parseInt(dateString.substr(0, 2), 10);
+ let month = parseInt(dateString.substr(3, 2), 10);
+ let day = parseInt(dateString.substr(6, 2), 10);
+ let hours = parseInt(dateString.substr(9, 2), 10);
+ let minutes = parseInt(dateString.substr(12, 2), 10);
+ let seconds = parseInt(dateString.substr(15, 2), 10);
+ // Note that |tz| is in 15-min units.
+ let tz = parseInt(dateString.substr(17, 3), 10);
+ // Note that |dst| is in 1-hour units and is already applied in |tz|.
+ let dst = parseInt(dateString.substr(21, 2), 10);
+
+ let timeInMS = Date.UTC(year + PDU_TIMESTAMP_YEAR_OFFSET, month - 1, day,
+ hours, minutes, seconds);
+
+ if (isNaN(timeInMS)) {
+ if (DEBUG) this.context.debug("NITZ failed to convert date");
+ return;
+ }
+
+ this.sendChromeMessage({rilMessageType: "nitzTime",
+ networkTimeInMS: timeInMS,
+ networkTimeZoneInMinutes: -(tz * 15),
+ networkDSTInMinutes: -(dst * 60),
+ receiveTimeInMS: now});
+};
+
+RilObject.prototype[UNSOLICITED_SIGNAL_STRENGTH] = function UNSOLICITED_SIGNAL_STRENGTH(length) {
+ this[REQUEST_SIGNAL_STRENGTH](length, {});
+};
+RilObject.prototype[UNSOLICITED_DATA_CALL_LIST_CHANGED] = function UNSOLICITED_DATA_CALL_LIST_CHANGED(length) {
+ this[REQUEST_DATA_CALL_LIST](length, {});
+};
+RilObject.prototype[UNSOLICITED_SUPP_SVC_NOTIFICATION] = function UNSOLICITED_SUPP_SVC_NOTIFICATION(length) {
+ let Buf = this.context.Buf;
+ let info = {};
+ info.notificationType = Buf.readInt32();
+ info.code = Buf.readInt32();
+ info.index = Buf.readInt32();
+ info.type = Buf.readInt32();
+ info.number = Buf.readString();
+
+ this._processSuppSvcNotification(info);
+};
+
+RilObject.prototype[UNSOLICITED_STK_SESSION_END] = function UNSOLICITED_STK_SESSION_END() {
+ this.sendChromeMessage({rilMessageType: "stksessionend"});
+};
+RilObject.prototype[UNSOLICITED_STK_PROACTIVE_COMMAND] = function UNSOLICITED_STK_PROACTIVE_COMMAND() {
+ this.processStkProactiveCommand();
+};
+RilObject.prototype[UNSOLICITED_STK_EVENT_NOTIFY] = function UNSOLICITED_STK_EVENT_NOTIFY() {
+ this.processStkProactiveCommand();
+};
+RilObject.prototype[UNSOLICITED_STK_CALL_SETUP] = null;
+RilObject.prototype[UNSOLICITED_SIM_SMS_STORAGE_FULL] = null;
+RilObject.prototype[UNSOLICITED_SIM_REFRESH] = null;
+RilObject.prototype[UNSOLICITED_CALL_RING] = function UNSOLICITED_CALL_RING() {
+ let Buf = this.context.Buf;
+ let info = {rilMessageType: "callRing"};
+ let isCDMA = false; //XXX TODO hard-code this for now
+ if (isCDMA) {
+ info.isPresent = Buf.readInt32();
+ info.signalType = Buf.readInt32();
+ info.alertPitch = Buf.readInt32();
+ info.signal = Buf.readInt32();
+ }
+ // At this point we don't know much other than the fact there's an incoming
+ // call, but that's enough to bring up the Phone app already. We'll know
+ // details once we get a call state changed notification and can then
+ // dispatch DOM events etc.
+ this.sendChromeMessage(info);
+};
+RilObject.prototype[UNSOLICITED_RESPONSE_SIM_STATUS_CHANGED] = function UNSOLICITED_RESPONSE_SIM_STATUS_CHANGED() {
+ this.getICCStatus();
+};
+RilObject.prototype[UNSOLICITED_RESPONSE_CDMA_NEW_SMS] = function UNSOLICITED_RESPONSE_CDMA_NEW_SMS(length) {
+ let [message, result] = this.context.CdmaPDUHelper.processReceivedSms(length);
+
+ if (message) {
+ if (message.teleservice === PDU_CDMA_MSG_TELESERIVCIE_ID_WAP) {
+ result = this._processCdmaSmsWapPush(message);
+ } else if (message.subMsgType === PDU_CDMA_MSG_TYPE_DELIVER_ACK) {
+ result = this._processCdmaSmsStatusReport(message);
+ } else {
+ result = this._processSmsMultipart(message);
+ }
+ }
+
+ if (result == PDU_FCS_RESERVED || result == MOZ_FCS_WAIT_FOR_EXPLICIT_ACK) {
+ return;
+ }
+
+ // Not reserved FCS values, send ACK now.
+ this.acknowledgeCdmaSms(result == PDU_FCS_OK, result);
+};
+RilObject.prototype[UNSOLICITED_RESPONSE_NEW_BROADCAST_SMS] = function UNSOLICITED_RESPONSE_NEW_BROADCAST_SMS(length) {
+ let message;
+ try {
+ message =
+ this.context.GsmPDUHelper.readCbMessage(this.context.Buf.readInt32());
+
+ // "Data-Download" message is expected to be handled by the modem.
+ // Ignore it here to prevent any garbage messages to be displayed.
+ // See 9.4.1.2.2 Message Identifier of TS 32.041 for the range of
+ // Message-identifier of the Data-Download CB messages.
+ if (message.messageId >= 0x1000 && message.messageId <= 0x10FF) {
+ if (DEBUG) {
+ this.context.debug("Ignore a Data-Download message, messageId: " +
+ message.messageId);
+ }
+ return;
+ }
+ } catch (e) {
+ if (DEBUG) {
+ this.context.debug("Failed to parse Cell Broadcast message: " + e);
+ }
+ return;
+ }
+
+ message = this._processReceivedSmsCbPage(message);
+ if (!message) {
+ return;
+ }
+
+ // Bug 1235697, failed to deactivate CBS in some modem.
+ // Workaround it according to the settings.
+ // Note: ETWS/CMAS/PWS can be received even disabled.
+ // It will be displayed according to the setting in application layer.
+ if (this.cellBroadcastDisabled && (
+ !(message.messageId >= 0x1100 && message.messageId <= 0x1107) && // ETWS
+ !(message.messageId >= 0x1112 && message.messageId <= 0x112F) && // CMAS
+ !(message.messageId >= 0x1130 && message.messageId <= 0x18FF) // PWS
+ )) {
+ if (DEBUG) {
+ this.context.debug("Ignore a CB message when disabled, messageId: " +
+ message.messageId);
+ }
+ return;
+ }
+
+ message.rilMessageType = "cellbroadcast-received";
+ this.sendChromeMessage(message);
+};
+RilObject.prototype[UNSOLICITED_CDMA_RUIM_SMS_STORAGE_FULL] = null;
+RilObject.prototype[UNSOLICITED_RESTRICTED_STATE_CHANGED] = null;
+RilObject.prototype[UNSOLICITED_ENTER_EMERGENCY_CALLBACK_MODE] = function UNSOLICITED_ENTER_EMERGENCY_CALLBACK_MODE() {
+ this._handleChangedEmergencyCbMode(true);
+};
+RilObject.prototype[UNSOLICITED_CDMA_CALL_WAITING] = function UNSOLICITED_CDMA_CALL_WAITING(length) {
+ let Buf = this.context.Buf;
+ let call = {};
+ call.number = Buf.readString();
+ call.numberPresentation = Buf.readInt32();
+ call.name = Buf.readString();
+ call.namePresentation = Buf.readInt32();
+ call.isPresent = Buf.readInt32();
+ call.signalType = Buf.readInt32();
+ call.alertPitch = Buf.readInt32();
+ call.signal = Buf.readInt32();
+ this.sendChromeMessage({rilMessageType: "cdmaCallWaiting",
+ waitingCall: call});
+};
+RilObject.prototype[UNSOLICITED_CDMA_OTA_PROVISION_STATUS] = function UNSOLICITED_CDMA_OTA_PROVISION_STATUS() {
+ let status =
+ CDMA_OTA_PROVISION_STATUS_TO_GECKO[this.context.Buf.readInt32List()[0]];
+ if (!status) {
+ return;
+ }
+ this.sendChromeMessage({rilMessageType: "otastatuschange",
+ status: status});
+};
+RilObject.prototype[UNSOLICITED_CDMA_INFO_REC] = function UNSOLICITED_CDMA_INFO_REC(length) {
+ this.sendChromeMessage({
+ rilMessageType: "cdma-info-rec-received",
+ records: this.context.CdmaPDUHelper.decodeInformationRecord()
+ });
+};
+RilObject.prototype[UNSOLICITED_OEM_HOOK_RAW] = null;
+RilObject.prototype[UNSOLICITED_RINGBACK_TONE] = null;
+RilObject.prototype[UNSOLICITED_RESEND_INCALL_MUTE] = null;
+RilObject.prototype[UNSOLICITED_CDMA_SUBSCRIPTION_SOURCE_CHANGED] = null;
+RilObject.prototype[UNSOLICITED_CDMA_PRL_CHANGED] = function UNSOLICITED_CDMA_PRL_CHANGED(length) {
+ let version = this.context.Buf.readInt32List()[0];
+ if (version !== this.iccInfo.prlVersion) {
+ this.iccInfo.prlVersion = version;
+ this.context.ICCUtilsHelper.handleICCInfoChange();
+ }
+};
+RilObject.prototype[UNSOLICITED_EXIT_EMERGENCY_CALLBACK_MODE] = function UNSOLICITED_EXIT_EMERGENCY_CALLBACK_MODE() {
+ this._handleChangedEmergencyCbMode(false);
+};
+RilObject.prototype[UNSOLICITED_RIL_CONNECTED] = function UNSOLICITED_RIL_CONNECTED(length) {
+ // Prevent response id collision between UNSOLICITED_RIL_CONNECTED and
+ // UNSOLICITED_VOICE_RADIO_TECH_CHANGED for Akami on gingerbread branch.
+ if (!length) {
+ return;
+ }
+
+ this.version = this.context.Buf.readInt32List()[0];
+ if (DEBUG) {
+ this.context.debug("Detected RIL version " + this.version);
+ }
+
+ this.initRILState();
+ // rild might have restarted, ensure data call list.
+ this.getDataCallList();
+ // Always ensure that we are not in emergency callback mode when init.
+ this.exitEmergencyCbMode();
+ // Reset radio in the case that b2g restart (or crash).
+ this.setRadioEnabled({enabled: false});
+};
+RilObject.prototype[UNSOLICITED_VOICE_RADIO_TECH_CHANGED] = function UNSOLICITED_VOICE_RADIO_TECH_CHANGED(length) {
+ // This unsolicited response will be sent when the technology of a multi-tech
+ // modem is changed, ex. switch between gsm and cdma.
+ // TODO: We may need to do more on updating data when switching between gsm
+ // and cdma mode, e.g. IMEI, ESN, iccInfo, iccType ... etc.
+ // See Bug 866038.
+ this._processRadioTech(this.context.Buf.readInt32List()[0]);
+};
+RilObject.prototype[UNSOLICITED_CELL_INFO_LIST] = null;
+RilObject.prototype[UNSOLICITED_RESPONSE_IMS_NETWORK_STATE_CHANGED] = null;
+RilObject.prototype[UNSOLICITED_UICC_SUBSCRIPTION_STATUS_CHANGED] = null;
+RilObject.prototype[UNSOLICITED_SRVCC_STATE_NOTIFY] = null;
+RilObject.prototype[UNSOLICITED_HARDWARE_CONFIG_CHANGED] = null;
+RilObject.prototype[UNSOLICITED_DC_RT_INFO_CHANGED] = null;
+
+/**
+ * This object exposes the functionality to parse and serialize PDU strings
+ *
+ * A PDU is a string containing a series of hexadecimally encoded octets
+ * or nibble-swapped binary-coded decimals (BCDs). It contains not only the
+ * message text but information about the sender, the SMS service center,
+ * timestamp, etc.
+ */
+function GsmPDUHelperObject(aContext) {
+ this.context = aContext;
+}
+GsmPDUHelperObject.prototype = {
+ context: null,
+
+ /**
+ * Read one character (2 bytes) from a RIL string and decode as hex.
+ *
+ * @return the nibble as a number.
+ */
+ readHexNibble: function() {
+ let nibble = this.context.Buf.readUint16();
+ if (nibble >= 48 && nibble <= 57) {
+ nibble -= 48; // ASCII '0'..'9'
+ } else if (nibble >= 65 && nibble <= 70) {
+ nibble -= 55; // ASCII 'A'..'F'
+ } else if (nibble >= 97 && nibble <= 102) {
+ nibble -= 87; // ASCII 'a'..'f'
+ } else {
+ throw "Found invalid nibble during PDU parsing: " +
+ String.fromCharCode(nibble);
+ }
+ return nibble;
+ },
+
+ /**
+ * Encode a nibble as one hex character in a RIL string (2 bytes).
+ *
+ * @param nibble
+ * The nibble to encode (represented as a number)
+ */
+ writeHexNibble: function(nibble) {
+ nibble &= 0x0f;
+ if (nibble < 10) {
+ nibble += 48; // ASCII '0'
+ } else {
+ nibble += 55; // ASCII 'A'
+ }
+ this.context.Buf.writeUint16(nibble);
+ },
+
+ /**
+ * Read a hex-encoded octet (two nibbles).
+ *
+ * @return the octet as a number.
+ */
+ readHexOctet: function() {
+ return (this.readHexNibble() << 4) | this.readHexNibble();
+ },
+
+ /**
+ * Write an octet as two hex-encoded nibbles.
+ *
+ * @param octet
+ * The octet (represented as a number) to encode.
+ */
+ writeHexOctet: function(octet) {
+ this.writeHexNibble(octet >> 4);
+ this.writeHexNibble(octet);
+ },
+
+ /**
+ * Read an array of hex-encoded octets.
+ */
+ readHexOctetArray: function(length) {
+ let array = new Uint8Array(length);
+ for (let i = 0; i < length; i++) {
+ array[i] = this.readHexOctet();
+ }
+ return array;
+ },
+
+ /**
+ * Helper to write data into a temporary buffer for easier length encoding when
+ * the number of octets for the length encoding is varied.
+ *
+ * @param writeFunction
+ * Function of how the data to be written into temporary buffer.
+ *
+ * @return array of written octets.
+ **/
+ writeWithBuffer: function(writeFunction) {
+ let buf = [];
+ let writeHexOctet = this.writeHexOctet;
+ this.writeHexOctet = function(octet) {
+ buf.push(octet);
+ }
+
+ try {
+ writeFunction();
+ } catch (e) {
+ if (DEBUG) {
+ debug("Error when writeWithBuffer: " + e);
+ }
+ buf = [];
+ } finally {
+ this.writeHexOctet = writeHexOctet;
+ }
+
+ return buf;
+ },
+
+ /**
+ * Convert an octet (number) to a BCD number.
+ *
+ * Any nibbles that are not in the BCD range count as 0.
+ *
+ * @param octet
+ * The octet (a number, as returned by getOctet())
+ *
+ * @return the corresponding BCD number.
+ */
+ octetToBCD: function(octet) {
+ return ((octet & 0xf0) <= 0x90) * ((octet >> 4) & 0x0f) +
+ ((octet & 0x0f) <= 0x09) * (octet & 0x0f) * 10;
+ },
+
+ /**
+ * Convert a BCD number to an octet (number)
+ *
+ * Only take two digits with absolute value.
+ *
+ * @param bcd
+ *
+ * @return the corresponding octet.
+ */
+ BCDToOctet: function(bcd) {
+ bcd = Math.abs(bcd);
+ return ((bcd % 10) << 4) + (Math.floor(bcd / 10) % 10);
+ },
+
+ /**
+ * Convert a semi-octet (number) to a GSM BCD char, or return empty
+ * string if invalid semiOctet and suppressException is set to true.
+ *
+ * @param semiOctet
+ * Nibble to be converted to.
+ * @param suppressException [optional]
+ * Suppress exception if invalid semiOctet and suppressException is set
+ * to true.
+ *
+ * @return GSM BCD char, or empty string.
+ */
+ bcdChars: "0123456789",
+ semiOctetToBcdChar: function(semiOctet, suppressException) {
+ if (semiOctet >= this.bcdChars.length) {
+ if (suppressException) {
+ return "";
+ } else {
+ throw new RangeError();
+ }
+ }
+
+ return this.bcdChars.charAt(semiOctet);
+ },
+
+ /**
+ * Convert a semi-octet (number) to a GSM extended BCD char, or return empty
+ * string if invalid semiOctet and suppressException is set to true.
+ *
+ * @param semiOctet
+ * Nibble to be converted to.
+ * @param suppressException [optional]
+ * Suppress exception if invalid semiOctet and suppressException is set
+ * to true.
+ *
+ * @return GSM extended BCD char, or empty string.
+ */
+ extendedBcdChars: "0123456789*#,;",
+ semiOctetToExtendedBcdChar: function(semiOctet, suppressException) {
+ if (semiOctet >= this.extendedBcdChars.length) {
+ if (suppressException) {
+ return "";
+ } else {
+ throw new RangeError();
+ }
+ }
+
+ return this.extendedBcdChars.charAt(semiOctet);
+ },
+
+ /**
+ * Convert string to a GSM extended BCD string
+ */
+ stringToExtendedBcd: function(string) {
+ return string.replace(/[^0-9*#,]/g, "")
+ .replace(/\*/g, "a")
+ .replace(/\#/g, "b")
+ .replace(/\,/g, "c");
+ },
+
+ /**
+ * Read a *swapped nibble* binary coded decimal (BCD)
+ *
+ * @param pairs
+ * Number of nibble *pairs* to read.
+ *
+ * @return the decimal as a number.
+ */
+ readSwappedNibbleBcdNum: function(pairs) {
+ let number = 0;
+ for (let i = 0; i < pairs; i++) {
+ let octet = this.readHexOctet();
+ // Ignore 'ff' octets as they're often used as filler.
+ if (octet == 0xff) {
+ continue;
+ }
+ // If the first nibble is an "F" , only the second nibble is to be taken
+ // into account.
+ if ((octet & 0xf0) == 0xf0) {
+ number *= 10;
+ number += octet & 0x0f;
+ continue;
+ }
+ number *= 100;
+ number += this.octetToBCD(octet);
+ }
+ return number;
+ },
+
+ /**
+ * Read a *swapped nibble* binary coded decimal (BCD) string
+ *
+ * @param pairs
+ * Number of nibble *pairs* to read.
+ * @param suppressException [optional]
+ * Suppress exception if invalid semiOctet and suppressException is set
+ * to true.
+ *
+ * @return The BCD string.
+ */
+ readSwappedNibbleBcdString: function(pairs, suppressException) {
+ let str = "";
+ for (let i = 0; i < pairs; i++) {
+ let nibbleH = this.readHexNibble();
+ let nibbleL = this.readHexNibble();
+ if (nibbleL == 0x0F) {
+ break;
+ }
+
+ str += this.semiOctetToBcdChar(nibbleL, suppressException);
+ if (nibbleH != 0x0F) {
+ str += this.semiOctetToBcdChar(nibbleH, suppressException);
+ }
+ }
+
+ return str;
+ },
+
+ /**
+ * Read a *swapped nibble* extended binary coded decimal (BCD) string
+ *
+ * @param pairs
+ * Number of nibble *pairs* to read.
+ * @param suppressException [optional]
+ * Suppress exception if invalid semiOctet and suppressException is set
+ * to true.
+ *
+ * @return The BCD string.
+ */
+ readSwappedNibbleExtendedBcdString: function(pairs, suppressException) {
+ let str = "";
+ for (let i = 0; i < pairs; i++) {
+ let nibbleH = this.readHexNibble();
+ let nibbleL = this.readHexNibble();
+ if (nibbleL == 0x0F) {
+ break;
+ }
+
+ str += this.semiOctetToExtendedBcdChar(nibbleL, suppressException);
+ if (nibbleH != 0x0F) {
+ str += this.semiOctetToExtendedBcdChar(nibbleH, suppressException);
+ }
+ }
+
+ return str;
+ },
+
+ /**
+ * Write numerical data as swapped nibble BCD.
+ *
+ * @param data
+ * Data to write (as a string or a number)
+ */
+ writeSwappedNibbleBCD: function(data) {
+ data = data.toString();
+ if (data.length % 2) {
+ data += "F";
+ }
+ let Buf = this.context.Buf;
+ for (let i = 0; i < data.length; i += 2) {
+ Buf.writeUint16(data.charCodeAt(i + 1));
+ Buf.writeUint16(data.charCodeAt(i));
+ }
+ },
+
+ /**
+ * Write numerical data as swapped nibble BCD.
+ * If the number of digit of data is even, add '0' at the beginning.
+ *
+ * @param data
+ * Data to write (as a string or a number)
+ */
+ writeSwappedNibbleBCDNum: function(data) {
+ data = data.toString();
+ if (data.length % 2) {
+ data = "0" + data;
+ }
+ let Buf = this.context.Buf;
+ for (let i = 0; i < data.length; i += 2) {
+ Buf.writeUint16(data.charCodeAt(i + 1));
+ Buf.writeUint16(data.charCodeAt(i));
+ }
+ },
+
+ /**
+ * Read user data, convert to septets, look up relevant characters in a
+ * 7-bit alphabet, and construct string.
+ *
+ * @param length
+ * Number of septets to read (*not* octets)
+ * @param paddingBits
+ * Number of padding bits in the first byte of user data.
+ * @param langIndex
+ * Table index used for normal 7-bit encoded character lookup.
+ * @param langShiftIndex
+ * Table index used for escaped 7-bit encoded character lookup.
+ *
+ * @return a string.
+ */
+ readSeptetsToString: function(length, paddingBits, langIndex, langShiftIndex) {
+ let ret = "";
+ let byteLength = Math.ceil((length * 7 + paddingBits) / 8);
+
+ /**
+ * |<- last byte in header ->|
+ * |<- incompleteBits ->|<- last header septet->|
+ * +===7===|===6===|===5===|===4===|===3===|===2===|===1===|===0===|
+ *
+ * |<- 1st byte in user data ->|
+ * |<- data septet 1 ->|<-paddingBits->|
+ * +===7===|===6===|===5===|===4===|===3===|===2===|===1===|===0===|
+ *
+ * |<- 2nd byte in user data ->|
+ * |<- data spetet 2 ->|<-ds1->|
+ * +===7===|===6===|===5===|===4===|===3===|===2===|===1===|===0===|
+ */
+ let data = 0;
+ let dataBits = 0;
+ if (paddingBits) {
+ data = this.readHexOctet() >> paddingBits;
+ dataBits = 8 - paddingBits;
+ --byteLength;
+ }
+
+ let escapeFound = false;
+ const langTable = PDU_NL_LOCKING_SHIFT_TABLES[langIndex];
+ const langShiftTable = PDU_NL_SINGLE_SHIFT_TABLES[langShiftIndex];
+ do {
+ // Read as much as fits in 32bit word
+ let bytesToRead = Math.min(byteLength, dataBits ? 3 : 4);
+ for (let i = 0; i < bytesToRead; i++) {
+ data |= this.readHexOctet() << dataBits;
+ dataBits += 8;
+ --byteLength;
+ }
+
+ // Consume available full septets
+ for (; dataBits >= 7; dataBits -= 7) {
+ let septet = data & 0x7F;
+ data >>>= 7;
+
+ if (escapeFound) {
+ escapeFound = false;
+ if (septet == PDU_NL_EXTENDED_ESCAPE) {
+ // According to 3GPP TS 23.038, section 6.2.1.1, NOTE 1, "On
+ // receipt of this code, a receiving entity shall display a space
+ // until another extensiion table is defined."
+ ret += " ";
+ } else if (septet == PDU_NL_RESERVED_CONTROL) {
+ // According to 3GPP TS 23.038 B.2, "This code represents a control
+ // character and therefore must not be used for language specific
+ // characters."
+ ret += " ";
+ } else {
+ ret += langShiftTable[septet];
+ }
+ } else if (septet == PDU_NL_EXTENDED_ESCAPE) {
+ escapeFound = true;
+
+ // <escape> is not an effective character
+ --length;
+ } else {
+ ret += langTable[septet];
+ }
+ }
+ } while (byteLength);
+
+ if (ret.length != length) {
+ /**
+ * If num of effective characters does not equal to the length of read
+ * string, cut the tail off. This happens when the last octet of user
+ * data has following layout:
+ *
+ * |<- penultimate octet in user data ->|
+ * |<- data septet N ->|<- dsN-1 ->|
+ * +===7===|===6===|===5===|===4===|===3===|===2===|===1===|===0===|
+ *
+ * |<- last octet in user data ->|
+ * |<- fill bits ->|<-dsN->|
+ * +===7===|===6===|===5===|===4===|===3===|===2===|===1===|===0===|
+ *
+ * The fill bits in the last octet may happen to form a full septet and
+ * be appended at the end of result string.
+ */
+ ret = ret.slice(0, length);
+ }
+ return ret;
+ },
+
+ writeStringAsSeptets: function(message, paddingBits, langIndex, langShiftIndex) {
+ const langTable = PDU_NL_LOCKING_SHIFT_TABLES[langIndex];
+ const langShiftTable = PDU_NL_SINGLE_SHIFT_TABLES[langShiftIndex];
+
+ let dataBits = paddingBits;
+ let data = 0;
+ for (let i = 0; i < message.length; i++) {
+ let c = message.charAt(i);
+ let septet = langTable.indexOf(c);
+ if (septet == PDU_NL_EXTENDED_ESCAPE) {
+ continue;
+ }
+
+ if (septet >= 0) {
+ data |= septet << dataBits;
+ dataBits += 7;
+ } else {
+ septet = langShiftTable.indexOf(c);
+ if (septet == -1) {
+ throw new Error("'" + c + "' is not in 7 bit alphabet "
+ + langIndex + ":" + langShiftIndex + "!");
+ }
+
+ if (septet == PDU_NL_RESERVED_CONTROL) {
+ continue;
+ }
+
+ data |= PDU_NL_EXTENDED_ESCAPE << dataBits;
+ dataBits += 7;
+ data |= septet << dataBits;
+ dataBits += 7;
+ }
+
+ for (; dataBits >= 8; dataBits -= 8) {
+ this.writeHexOctet(data & 0xFF);
+ data >>>= 8;
+ }
+ }
+
+ if (dataBits !== 0) {
+ this.writeHexOctet(data & 0xFF);
+ }
+ },
+
+ writeStringAs8BitUnpacked: function(text) {
+ const langTable = PDU_NL_LOCKING_SHIFT_TABLES[PDU_NL_IDENTIFIER_DEFAULT];
+ const langShiftTable = PDU_NL_SINGLE_SHIFT_TABLES[PDU_NL_IDENTIFIER_DEFAULT];
+
+ let len = text ? text.length : 0;
+ for (let i = 0; i < len; i++) {
+ let c = text.charAt(i);
+ let octet = langTable.indexOf(c);
+
+ if (octet == -1) {
+ octet = langShiftTable.indexOf(c);
+ if (octet == -1) {
+ // Fallback to ASCII space.
+ octet = langTable.indexOf(' ');
+ } else {
+ this.writeHexOctet(PDU_NL_EXTENDED_ESCAPE);
+ }
+ }
+ this.writeHexOctet(octet);
+ }
+ },
+
+ /**
+ * Read user data and decode as a UCS2 string.
+ *
+ * @param numOctets
+ * Number of octets to be read as UCS2 string.
+ *
+ * @return a string.
+ */
+ readUCS2String: function(numOctets) {
+ let str = "";
+ let length = numOctets / 2;
+ for (let i = 0; i < length; ++i) {
+ let code = (this.readHexOctet() << 8) | this.readHexOctet();
+ str += String.fromCharCode(code);
+ }
+
+ if (DEBUG) this.context.debug("Read UCS2 string: " + str);
+
+ return str;
+ },
+
+ /**
+ * Write user data as a UCS2 string.
+ *
+ * @param message
+ * Message string to encode as UCS2 in hex-encoded octets.
+ */
+ writeUCS2String: function(message) {
+ for (let i = 0; i < message.length; ++i) {
+ let code = message.charCodeAt(i);
+ this.writeHexOctet((code >> 8) & 0xFF);
+ this.writeHexOctet(code & 0xFF);
+ }
+ },
+
+ /**
+ * Read 1 + UDHL octets and construct user data header.
+ *
+ * @param msg
+ * message object for output.
+ *
+ * @see 3GPP TS 23.040 9.2.3.24
+ */
+ readUserDataHeader: function(msg) {
+ /**
+ * A header object with properties contained in received message.
+ * The properties set include:
+ *
+ * length: totoal length of the header, default 0.
+ * langIndex: used locking shift table index, default
+ * PDU_NL_IDENTIFIER_DEFAULT.
+ * langShiftIndex: used locking shift table index, default
+ * PDU_NL_IDENTIFIER_DEFAULT.
+ *
+ */
+ let header = {
+ length: 0,
+ langIndex: PDU_NL_IDENTIFIER_DEFAULT,
+ langShiftIndex: PDU_NL_IDENTIFIER_DEFAULT
+ };
+
+ header.length = this.readHexOctet();
+ if (DEBUG) this.context.debug("Read UDH length: " + header.length);
+
+ let dataAvailable = header.length;
+ while (dataAvailable >= 2) {
+ let id = this.readHexOctet();
+ let length = this.readHexOctet();
+ if (DEBUG) this.context.debug("Read UDH id: " + id + ", length: " + length);
+
+ dataAvailable -= 2;
+
+ switch (id) {
+ case PDU_IEI_CONCATENATED_SHORT_MESSAGES_8BIT: {
+ let ref = this.readHexOctet();
+ let max = this.readHexOctet();
+ let seq = this.readHexOctet();
+ dataAvailable -= 3;
+ if (max && seq && (seq <= max)) {
+ header.segmentRef = ref;
+ header.segmentMaxSeq = max;
+ header.segmentSeq = seq;
+ }
+ break;
+ }
+ case PDU_IEI_APPLICATION_PORT_ADDRESSING_SCHEME_8BIT: {
+ let dstp = this.readHexOctet();
+ let orip = this.readHexOctet();
+ dataAvailable -= 2;
+ if ((dstp < PDU_APA_RESERVED_8BIT_PORTS)
+ || (orip < PDU_APA_RESERVED_8BIT_PORTS)) {
+ // 3GPP TS 23.040 clause 9.2.3.24.3: "A receiving entity shall
+ // ignore any information element where the value of the
+ // Information-Element-Data is Reserved or not supported"
+ break;
+ }
+ header.destinationPort = dstp;
+ header.originatorPort = orip;
+ break;
+ }
+ case PDU_IEI_APPLICATION_PORT_ADDRESSING_SCHEME_16BIT: {
+ let dstp = (this.readHexOctet() << 8) | this.readHexOctet();
+ let orip = (this.readHexOctet() << 8) | this.readHexOctet();
+ dataAvailable -= 4;
+ if ((dstp >= PDU_APA_VALID_16BIT_PORTS) ||
+ (orip >= PDU_APA_VALID_16BIT_PORTS)) {
+ // 3GPP TS 23.040 clause 9.2.3.24.4: "A receiving entity shall
+ // ignore any information element where the value of the
+ // Information-Element-Data is Reserved or not supported"
+ // Bug 1130292, some carriers set originatorPort to reserved port
+ // numbers for wap push. We rise this as a warning in debug message
+ // instead of ingoring this IEI to allow user to receive Wap Push
+ // under these carriers.
+ this.context.debug("Warning: Invalid port numbers [dstp, orip]: " +
+ JSON.stringify([dstp, orip]));
+ }
+ header.destinationPort = dstp;
+ header.originatorPort = orip;
+ break;
+ }
+ case PDU_IEI_CONCATENATED_SHORT_MESSAGES_16BIT: {
+ let ref = (this.readHexOctet() << 8) | this.readHexOctet();
+ let max = this.readHexOctet();
+ let seq = this.readHexOctet();
+ dataAvailable -= 4;
+ if (max && seq && (seq <= max)) {
+ header.segmentRef = ref;
+ header.segmentMaxSeq = max;
+ header.segmentSeq = seq;
+ }
+ break;
+ }
+ case PDU_IEI_NATIONAL_LANGUAGE_SINGLE_SHIFT:
+ let langShiftIndex = this.readHexOctet();
+ --dataAvailable;
+ if (langShiftIndex < PDU_NL_SINGLE_SHIFT_TABLES.length) {
+ header.langShiftIndex = langShiftIndex;
+ }
+ break;
+ case PDU_IEI_NATIONAL_LANGUAGE_LOCKING_SHIFT:
+ let langIndex = this.readHexOctet();
+ --dataAvailable;
+ if (langIndex < PDU_NL_LOCKING_SHIFT_TABLES.length) {
+ header.langIndex = langIndex;
+ }
+ break;
+ case PDU_IEI_SPECIAL_SMS_MESSAGE_INDICATION:
+ let msgInd = this.readHexOctet() & 0xFF;
+ let msgCount = this.readHexOctet();
+ dataAvailable -= 2;
+
+
+ /*
+ * TS 23.040 V6.8.1 Sec 9.2.3.24.2
+ * bits 1 0 : basic message indication type
+ * bits 4 3 2 : extended message indication type
+ * bits 6 5 : Profile id
+ * bit 7 : storage type
+ */
+ let storeType = msgInd & PDU_MWI_STORE_TYPE_BIT;
+ let mwi = msg.mwi;
+ if (!mwi) {
+ mwi = msg.mwi = {};
+ }
+
+ if (storeType == PDU_MWI_STORE_TYPE_STORE) {
+ // Store message because TP_UDH indicates so, note this may override
+ // the setting in DCS, but that is expected
+ mwi.discard = false;
+ } else if (mwi.discard === undefined) {
+ // storeType == PDU_MWI_STORE_TYPE_DISCARD
+ // only override mwi.discard here if it hasn't already been set
+ mwi.discard = true;
+ }
+
+ mwi.msgCount = msgCount & 0xFF;
+ mwi.active = mwi.msgCount > 0;
+
+ if (DEBUG) {
+ this.context.debug("MWI in TP_UDH received: " + JSON.stringify(mwi));
+ }
+
+ break;
+ default:
+ if (DEBUG) {
+ this.context.debug("readUserDataHeader: unsupported IEI(" + id +
+ "), " + length + " bytes.");
+ }
+
+ // Read out unsupported data
+ if (length) {
+ let octets;
+ if (DEBUG) octets = new Uint8Array(length);
+
+ for (let i = 0; i < length; i++) {
+ let octet = this.readHexOctet();
+ if (DEBUG) octets[i] = octet;
+ }
+ dataAvailable -= length;
+
+ if (DEBUG) {
+ this.context.debug("readUserDataHeader: " + Array.slice(octets));
+ }
+ }
+ break;
+ }
+ }
+
+ if (dataAvailable !== 0) {
+ throw new Error("Illegal user data header found!");
+ }
+
+ msg.header = header;
+ },
+
+ /**
+ * Write out user data header.
+ *
+ * @param options
+ * Options containing information for user data header write-out. The
+ * `userDataHeaderLength` property must be correctly pre-calculated.
+ */
+ writeUserDataHeader: function(options) {
+ this.writeHexOctet(options.userDataHeaderLength);
+
+ if (options.segmentMaxSeq > 1) {
+ if (options.segmentRef16Bit) {
+ this.writeHexOctet(PDU_IEI_CONCATENATED_SHORT_MESSAGES_16BIT);
+ this.writeHexOctet(4);
+ this.writeHexOctet((options.segmentRef >> 8) & 0xFF);
+ } else {
+ this.writeHexOctet(PDU_IEI_CONCATENATED_SHORT_MESSAGES_8BIT);
+ this.writeHexOctet(3);
+ }
+ this.writeHexOctet(options.segmentRef & 0xFF);
+ this.writeHexOctet(options.segmentMaxSeq & 0xFF);
+ this.writeHexOctet(options.segmentSeq & 0xFF);
+ }
+
+ if (options.dcs == PDU_DCS_MSG_CODING_7BITS_ALPHABET) {
+ if (options.langIndex != PDU_NL_IDENTIFIER_DEFAULT) {
+ this.writeHexOctet(PDU_IEI_NATIONAL_LANGUAGE_LOCKING_SHIFT);
+ this.writeHexOctet(1);
+ this.writeHexOctet(options.langIndex);
+ }
+
+ if (options.langShiftIndex != PDU_NL_IDENTIFIER_DEFAULT) {
+ this.writeHexOctet(PDU_IEI_NATIONAL_LANGUAGE_SINGLE_SHIFT);
+ this.writeHexOctet(1);
+ this.writeHexOctet(options.langShiftIndex);
+ }
+ }
+ },
+
+ /**
+ * Read SM-TL Address.
+ *
+ * @param len
+ * Length of useful semi-octets within the Address-Value field. For
+ * example, the lenth of "12345" should be 5, and 4 for "1234".
+ *
+ * @see 3GPP TS 23.040 9.1.2.5
+ */
+ readAddress: function(len) {
+ // Address Length
+ if (!len || (len < 0)) {
+ if (DEBUG) {
+ this.context.debug("PDU error: invalid sender address length: " + len);
+ }
+ return null;
+ }
+ if (len % 2 == 1) {
+ len += 1;
+ }
+ if (DEBUG) this.context.debug("PDU: Going to read address: " + len);
+
+ // Type-of-Address
+ let toa = this.readHexOctet();
+ let addr = "";
+
+ if ((toa & 0xF0) == PDU_TOA_ALPHANUMERIC) {
+ addr = this.readSeptetsToString(Math.floor(len * 4 / 7), 0,
+ PDU_NL_IDENTIFIER_DEFAULT , PDU_NL_IDENTIFIER_DEFAULT );
+ return addr;
+ }
+ addr = this.readSwappedNibbleExtendedBcdString(len / 2);
+ if (addr.length <= 0) {
+ if (DEBUG) this.context.debug("PDU error: no number provided");
+ return null;
+ }
+ if ((toa & 0xF0) == (PDU_TOA_INTERNATIONAL)) {
+ addr = '+' + addr;
+ }
+
+ return addr;
+ },
+
+ /**
+ * Read TP-Protocol-Indicator(TP-PID).
+ *
+ * @param msg
+ * message object for output.
+ *
+ * @see 3GPP TS 23.040 9.2.3.9
+ */
+ readProtocolIndicator: function(msg) {
+ // `The MS shall interpret reserved, obsolete, or unsupported values as the
+ // value 00000000 but shall store them exactly as received.`
+ msg.pid = this.readHexOctet();
+
+ msg.epid = msg.pid;
+ switch (msg.epid & 0xC0) {
+ case 0x40:
+ // Bit 7..0 = 01xxxxxx
+ switch (msg.epid) {
+ case PDU_PID_SHORT_MESSAGE_TYPE_0:
+ case PDU_PID_ANSI_136_R_DATA:
+ case PDU_PID_USIM_DATA_DOWNLOAD:
+ return;
+ }
+ break;
+ }
+
+ msg.epid = PDU_PID_DEFAULT;
+ },
+
+ /**
+ * Read TP-Data-Coding-Scheme(TP-DCS)
+ *
+ * @param msg
+ * message object for output.
+ *
+ * @see 3GPP TS 23.040 9.2.3.10, 3GPP TS 23.038 4.
+ */
+ readDataCodingScheme: function(msg) {
+ let dcs = this.readHexOctet();
+ if (DEBUG) this.context.debug("PDU: read SMS dcs: " + dcs);
+
+ // No message class by default.
+ let messageClass = PDU_DCS_MSG_CLASS_NORMAL;
+ // 7 bit is the default fallback encoding.
+ let encoding = PDU_DCS_MSG_CODING_7BITS_ALPHABET;
+ switch (dcs & PDU_DCS_CODING_GROUP_BITS) {
+ case 0x40: // bits 7..4 = 01xx
+ case 0x50:
+ case 0x60:
+ case 0x70:
+ // Bit 5..0 are coded exactly the same as Group 00xx
+ case 0x00: // bits 7..4 = 00xx
+ case 0x10:
+ case 0x20:
+ case 0x30:
+ if (dcs & 0x10) {
+ messageClass = dcs & PDU_DCS_MSG_CLASS_BITS;
+ }
+ switch (dcs & 0x0C) {
+ case 0x4:
+ encoding = PDU_DCS_MSG_CODING_8BITS_ALPHABET;
+ break;
+ case 0x8:
+ encoding = PDU_DCS_MSG_CODING_16BITS_ALPHABET;
+ break;
+ }
+ break;
+
+ case 0xE0: // bits 7..4 = 1110
+ encoding = PDU_DCS_MSG_CODING_16BITS_ALPHABET;
+ // Bit 3..0 are coded exactly the same as Message Waiting Indication
+ // Group 1101.
+ // Fall through.
+ case 0xC0: // bits 7..4 = 1100
+ case 0xD0: // bits 7..4 = 1101
+ // Indiciates voicemail indicator set or clear
+ let active = (dcs & PDU_DCS_MWI_ACTIVE_BITS) == PDU_DCS_MWI_ACTIVE_VALUE;
+
+ // If TP-UDH is present, these values will be overwritten
+ switch (dcs & PDU_DCS_MWI_TYPE_BITS) {
+ case PDU_DCS_MWI_TYPE_VOICEMAIL:
+ let mwi = msg.mwi;
+ if (!mwi) {
+ mwi = msg.mwi = {};
+ }
+
+ mwi.active = active;
+ mwi.discard = (dcs & PDU_DCS_CODING_GROUP_BITS) == 0xC0;
+ mwi.msgCount = active ? GECKO_VOICEMAIL_MESSAGE_COUNT_UNKNOWN : 0;
+
+ if (DEBUG) {
+ this.context.debug("MWI in DCS received for voicemail: " +
+ JSON.stringify(mwi));
+ }
+ break;
+ case PDU_DCS_MWI_TYPE_FAX:
+ if (DEBUG) this.context.debug("MWI in DCS received for fax");
+ break;
+ case PDU_DCS_MWI_TYPE_EMAIL:
+ if (DEBUG) this.context.debug("MWI in DCS received for email");
+ break;
+ default:
+ if (DEBUG) this.context.debug("MWI in DCS received for \"other\"");
+ break;
+ }
+ break;
+
+ case 0xF0: // bits 7..4 = 1111
+ if (dcs & 0x04) {
+ encoding = PDU_DCS_MSG_CODING_8BITS_ALPHABET;
+ }
+ messageClass = dcs & PDU_DCS_MSG_CLASS_BITS;
+ break;
+
+ default:
+ // Falling back to default encoding.
+ break;
+ }
+
+ msg.dcs = dcs;
+ msg.encoding = encoding;
+ msg.messageClass = GECKO_SMS_MESSAGE_CLASSES[messageClass];
+
+ if (DEBUG) this.context.debug("PDU: message encoding is " + encoding + " bit.");
+ },
+
+ /**
+ * Read GSM TP-Service-Centre-Time-Stamp(TP-SCTS).
+ *
+ * @see 3GPP TS 23.040 9.2.3.11
+ */
+ readTimestamp: function() {
+ let year = this.readSwappedNibbleBcdNum(1) + PDU_TIMESTAMP_YEAR_OFFSET;
+ let month = this.readSwappedNibbleBcdNum(1) - 1;
+ let day = this.readSwappedNibbleBcdNum(1);
+ let hour = this.readSwappedNibbleBcdNum(1);
+ let minute = this.readSwappedNibbleBcdNum(1);
+ let second = this.readSwappedNibbleBcdNum(1);
+ let timestamp = Date.UTC(year, month, day, hour, minute, second);
+
+ // If the most significant bit of the least significant nibble is 1,
+ // the timezone offset is negative (fourth bit from the right => 0x08):
+ // localtime = UTC + tzOffset
+ // therefore
+ // UTC = localtime - tzOffset
+ let tzOctet = this.readHexOctet();
+ let tzOffset = this.octetToBCD(tzOctet & ~0x08) * 15 * 60 * 1000;
+ tzOffset = (tzOctet & 0x08) ? -tzOffset : tzOffset;
+ timestamp -= tzOffset;
+
+ return timestamp;
+ },
+
+ /**
+ * Write GSM TP-Service-Centre-Time-Stamp(TP-SCTS).
+ *
+ * @see 3GPP TS 23.040 9.2.3.11
+ */
+ writeTimestamp: function(date) {
+ this.writeSwappedNibbleBCDNum(date.getFullYear() - PDU_TIMESTAMP_YEAR_OFFSET);
+
+ // The value returned by getMonth() is an integer between 0 and 11.
+ // 0 is corresponds to January, 1 to February, and so on.
+ this.writeSwappedNibbleBCDNum(date.getMonth() + 1);
+ this.writeSwappedNibbleBCDNum(date.getDate());
+ this.writeSwappedNibbleBCDNum(date.getHours());
+ this.writeSwappedNibbleBCDNum(date.getMinutes());
+ this.writeSwappedNibbleBCDNum(date.getSeconds());
+
+ // the value returned by getTimezoneOffset() is the difference,
+ // in minutes, between UTC and local time.
+ // For example, if your time zone is UTC+10 (Australian Eastern Standard Time),
+ // -600 will be returned.
+ // In TS 23.040 9.2.3.11, the Time Zone field of TP-SCTS indicates
+ // the different between the local time and GMT.
+ // And expressed in quarters of an hours. (so need to divid by 15)
+ let zone = date.getTimezoneOffset() / 15;
+ let octet = this.BCDToOctet(zone);
+
+ // the bit3 of the Time Zone field represents the algebraic sign.
+ // (0: positive, 1: negative).
+ // For example, if the time zone is -0800 GMT,
+ // 480 will be returned by getTimezoneOffset().
+ // In this case, need to mark sign bit as 1. => 0x08
+ if (zone > 0) {
+ octet = octet | 0x08;
+ }
+ this.writeHexOctet(octet);
+ },
+
+ /**
+ * User data can be 7 bit (default alphabet) data, 8 bit data, or 16 bit
+ * (UCS2) data.
+ *
+ * @param msg
+ * message object for output.
+ * @param length
+ * length of user data to read in octets.
+ */
+ readUserData: function(msg, length) {
+ if (DEBUG) {
+ this.context.debug("Reading " + length + " bytes of user data.");
+ }
+
+ let paddingBits = 0;
+ if (msg.udhi) {
+ this.readUserDataHeader(msg);
+
+ if (msg.encoding == PDU_DCS_MSG_CODING_7BITS_ALPHABET) {
+ let headerBits = (msg.header.length + 1) * 8;
+ let headerSeptets = Math.ceil(headerBits / 7);
+
+ length -= headerSeptets;
+ paddingBits = headerSeptets * 7 - headerBits;
+ } else {
+ length -= (msg.header.length + 1);
+ }
+ }
+
+ if (DEBUG) {
+ this.context.debug("After header, " + length + " septets left of user data");
+ }
+
+ msg.body = null;
+ msg.data = null;
+
+ if (length <= 0) {
+ // No data to read.
+ return;
+ }
+
+ switch (msg.encoding) {
+ case PDU_DCS_MSG_CODING_7BITS_ALPHABET:
+ // 7 bit encoding allows 140 octets, which means 160 characters
+ // ((140x8) / 7 = 160 chars)
+ if (length > PDU_MAX_USER_DATA_7BIT) {
+ if (DEBUG) {
+ this.context.debug("PDU error: user data is too long: " + length);
+ }
+ break;
+ }
+
+ let langIndex = msg.udhi ? msg.header.langIndex : PDU_NL_IDENTIFIER_DEFAULT;
+ let langShiftIndex = msg.udhi ? msg.header.langShiftIndex : PDU_NL_IDENTIFIER_DEFAULT;
+ msg.body = this.readSeptetsToString(length, paddingBits, langIndex,
+ langShiftIndex);
+ break;
+ case PDU_DCS_MSG_CODING_8BITS_ALPHABET:
+ msg.data = this.readHexOctetArray(length);
+ break;
+ case PDU_DCS_MSG_CODING_16BITS_ALPHABET:
+ msg.body = this.readUCS2String(length);
+ break;
+ }
+ },
+
+ /**
+ * Read extra parameters if TP-PI is set.
+ *
+ * @param msg
+ * message object for output.
+ */
+ readExtraParams: function(msg) {
+ // Because each PDU octet is converted to two UCS2 char2, we should always
+ // get even messageStringLength in this#_processReceivedSms(). So, we'll
+ // always need two delimitors at the end.
+ if (this.context.Buf.getReadAvailable() <= 4) {
+ return;
+ }
+
+ // TP-Parameter-Indicator
+ let pi;
+ do {
+ // `The most significant bit in octet 1 and any other TP-PI octets which
+ // may be added later is reserved as an extension bit which when set to a
+ // 1 shall indicate that another TP-PI octet follows immediately
+ // afterwards.` ~ 3GPP TS 23.040 9.2.3.27
+ pi = this.readHexOctet();
+ } while (pi & PDU_PI_EXTENSION);
+
+ // `If the TP-UDL bit is set to "1" but the TP-DCS bit is set to "0" then
+ // the receiving entity shall for TP-DCS assume a value of 0x00, i.e. the
+ // 7bit default alphabet.` ~ 3GPP 23.040 9.2.3.27
+ msg.dcs = 0;
+ msg.encoding = PDU_DCS_MSG_CODING_7BITS_ALPHABET;
+
+ // TP-Protocol-Identifier
+ if (pi & PDU_PI_PROTOCOL_IDENTIFIER) {
+ this.readProtocolIndicator(msg);
+ }
+ // TP-Data-Coding-Scheme
+ if (pi & PDU_PI_DATA_CODING_SCHEME) {
+ this.readDataCodingScheme(msg);
+ }
+ // TP-User-Data-Length
+ if (pi & PDU_PI_USER_DATA_LENGTH) {
+ let userDataLength = this.readHexOctet();
+ this.readUserData(msg, userDataLength);
+ }
+ },
+
+ /**
+ * Read and decode a PDU-encoded message from the stream.
+ *
+ * TODO: add some basic sanity checks like:
+ * - do we have the minimum number of chars available
+ */
+ readMessage: function() {
+ // An empty message object. This gets filled below and then returned.
+ let msg = {
+ // D:DELIVER, DR:DELIVER-REPORT, S:SUBMIT, SR:SUBMIT-REPORT,
+ // ST:STATUS-REPORT, C:COMMAND
+ // M:Mandatory, O:Optional, X:Unavailable
+ // D DR S SR ST C
+ SMSC: null, // M M M M M M
+ mti: null, // M M M M M M
+ udhi: null, // M M O M M M
+ sender: null, // M X X X X X
+ recipient: null, // X X M X M M
+ pid: null, // M O M O O M
+ epid: null, // M O M O O M
+ dcs: null, // M O M O O X
+ mwi: null, // O O O O O O
+ replace: false, // O O O O O O
+ header: null, // M M O M M M
+ body: null, // M O M O O O
+ data: null, // M O M O O O
+ sentTimestamp: null, // M X X X X X
+ status: null, // X X X X M X
+ scts: null, // X X X M M X
+ dt: null, // X X X X M X
+ };
+
+ // SMSC info
+ let smscLength = this.readHexOctet();
+ if (smscLength > 0) {
+ let smscTypeOfAddress = this.readHexOctet();
+ // Subtract the type-of-address octet we just read from the length.
+ msg.SMSC = this.readSwappedNibbleExtendedBcdString(smscLength - 1);
+ if ((smscTypeOfAddress >> 4) == (PDU_TOA_INTERNATIONAL >> 4)) {
+ msg.SMSC = '+' + msg.SMSC;
+ }
+ }
+
+ // First octet of this SMS-DELIVER or SMS-SUBMIT message
+ let firstOctet = this.readHexOctet();
+ // Message Type Indicator
+ msg.mti = firstOctet & 0x03;
+ // User data header indicator
+ msg.udhi = firstOctet & PDU_UDHI;
+
+ switch (msg.mti) {
+ case PDU_MTI_SMS_RESERVED:
+ // `If an MS receives a TPDU with a "Reserved" value in the TP-MTI it
+ // shall process the message as if it were an "SMS-DELIVER" but store
+ // the message exactly as received.` ~ 3GPP TS 23.040 9.2.3.1
+ case PDU_MTI_SMS_DELIVER:
+ return this.readDeliverMessage(msg);
+ case PDU_MTI_SMS_STATUS_REPORT:
+ return this.readStatusReportMessage(msg);
+ default:
+ return null;
+ }
+ },
+
+ /**
+ * Helper for processing received SMS parcel data.
+ *
+ * @param length
+ * Length of SMS string in the incoming parcel.
+ *
+ * @return Message parsed or null for invalid message.
+ */
+ processReceivedSms: function(length) {
+ if (!length) {
+ if (DEBUG) this.context.debug("Received empty SMS!");
+ return [null, PDU_FCS_UNSPECIFIED];
+ }
+
+ let Buf = this.context.Buf;
+
+ // An SMS is a string, but we won't read it as such, so let's read the
+ // string length and then defer to PDU parsing helper.
+ let messageStringLength = Buf.readInt32();
+ if (DEBUG) this.context.debug("Got new SMS, length " + messageStringLength);
+ let message = this.readMessage();
+ if (DEBUG) this.context.debug("Got new SMS: " + JSON.stringify(message));
+
+ // Read string delimiters. See Buf.readString().
+ Buf.readStringDelimiter(length);
+
+ // Determine result
+ if (!message) {
+ return [null, PDU_FCS_UNSPECIFIED];
+ }
+
+ if (message.epid == PDU_PID_SHORT_MESSAGE_TYPE_0) {
+ // `A short message type 0 indicates that the ME must acknowledge receipt
+ // of the short message but shall discard its contents.` ~ 3GPP TS 23.040
+ // 9.2.3.9
+ return [null, PDU_FCS_OK];
+ }
+
+ if (message.messageClass == GECKO_SMS_MESSAGE_CLASSES[PDU_DCS_MSG_CLASS_2]) {
+ let RIL = this.context.RIL;
+ switch (message.epid) {
+ case PDU_PID_ANSI_136_R_DATA:
+ case PDU_PID_USIM_DATA_DOWNLOAD:
+ let ICCUtilsHelper = this.context.ICCUtilsHelper;
+ if (ICCUtilsHelper.isICCServiceAvailable("DATA_DOWNLOAD_SMS_PP")) {
+ // `If the service "data download via SMS Point-to-Point" is
+ // allocated and activated in the (U)SIM Service Table, ... then the
+ // ME shall pass the message transparently to the UICC using the
+ // ENVELOPE (SMS-PP DOWNLOAD).` ~ 3GPP TS 31.111 7.1.1.1
+ RIL.dataDownloadViaSMSPP(message);
+
+ // `the ME shall not display the message, or alert the user of a
+ // short message waiting.` ~ 3GPP TS 31.111 7.1.1.1
+ return [null, PDU_FCS_RESERVED];
+ }
+
+ // If the service "data download via SMS-PP" is not available in the
+ // (U)SIM Service Table, ..., then the ME shall store the message in
+ // EFsms in accordance with TS 31.102` ~ 3GPP TS 31.111 7.1.1.1
+
+ // Fall through.
+ default:
+ RIL.writeSmsToSIM(message);
+ break;
+ }
+ }
+
+ // TODO: Bug 739143: B2G SMS: Support SMS Storage Full event
+ if ((message.messageClass != GECKO_SMS_MESSAGE_CLASSES[PDU_DCS_MSG_CLASS_0]) && !true) {
+ // `When a mobile terminated message is class 0..., the MS shall display
+ // the message immediately and send a ACK to the SC ..., irrespective of
+ // whether there is memory available in the (U)SIM or ME.` ~ 3GPP 23.038
+ // clause 4.
+
+ if (message.messageClass == GECKO_SMS_MESSAGE_CLASSES[PDU_DCS_MSG_CLASS_2]) {
+ // `If all the short message storage at the MS is already in use, the
+ // MS shall return "memory capacity exceeded".` ~ 3GPP 23.038 clause 4.
+ return [null, PDU_FCS_MEMORY_CAPACITY_EXCEEDED];
+ }
+
+ return [null, PDU_FCS_UNSPECIFIED];
+ }
+
+ return [message, PDU_FCS_OK];
+ },
+
+ /**
+ * Read and decode a SMS-DELIVER PDU.
+ *
+ * @param msg
+ * message object for output.
+ */
+ readDeliverMessage: function(msg) {
+ // - Sender Address info -
+ let senderAddressLength = this.readHexOctet();
+ msg.sender = this.readAddress(senderAddressLength);
+ // - TP-Protocolo-Identifier -
+ this.readProtocolIndicator(msg);
+ // - TP-Data-Coding-Scheme -
+ this.readDataCodingScheme(msg);
+ // - TP-Service-Center-Time-Stamp -
+ msg.sentTimestamp = this.readTimestamp();
+ // - TP-User-Data-Length -
+ let userDataLength = this.readHexOctet();
+
+ // - TP-User-Data -
+ if (userDataLength > 0) {
+ this.readUserData(msg, userDataLength);
+ }
+
+ return msg;
+ },
+
+ /**
+ * Read and decode a SMS-STATUS-REPORT PDU.
+ *
+ * @param msg
+ * message object for output.
+ */
+ readStatusReportMessage: function(msg) {
+ // TP-Message-Reference
+ msg.messageRef = this.readHexOctet();
+ // TP-Recipient-Address
+ let recipientAddressLength = this.readHexOctet();
+ msg.recipient = this.readAddress(recipientAddressLength);
+ // TP-Service-Centre-Time-Stamp
+ msg.scts = this.readTimestamp();
+ // TP-Discharge-Time
+ msg.dt = this.readTimestamp();
+ // TP-Status
+ msg.status = this.readHexOctet();
+
+ this.readExtraParams(msg);
+
+ return msg;
+ },
+
+ /**
+ * Serialize a SMS-SUBMIT PDU message and write it to the output stream.
+ *
+ * This method expects that a data coding scheme has been chosen already
+ * and that the length of the user data payload in that encoding is known,
+ * too. Both go hand in hand together anyway.
+ *
+ * @param address
+ * String containing the address (number) of the SMS receiver
+ * @param userData
+ * String containing the message to be sent as user data
+ * @param dcs
+ * Data coding scheme. One of the PDU_DCS_MSG_CODING_*BITS_ALPHABET
+ * constants.
+ * @param userDataHeaderLength
+ * Length of embedded user data header, in bytes. The whole header
+ * size will be userDataHeaderLength + 1; 0 for no header.
+ * @param encodedBodyLength
+ * Length of the user data when encoded with the given DCS. For UCS2,
+ * in bytes; for 7-bit, in septets.
+ * @param langIndex
+ * Table index used for normal 7-bit encoded character lookup.
+ * @param langShiftIndex
+ * Table index used for escaped 7-bit encoded character lookup.
+ * @param requestStatusReport
+ * Request status report.
+ */
+ writeMessage: function(options) {
+ if (DEBUG) {
+ this.context.debug("writeMessage: " + JSON.stringify(options));
+ }
+ let Buf = this.context.Buf;
+ let address = options.number;
+ let body = options.body;
+ let dcs = options.dcs;
+ let userDataHeaderLength = options.userDataHeaderLength;
+ let encodedBodyLength = options.encodedBodyLength;
+ let langIndex = options.langIndex;
+ let langShiftIndex = options.langShiftIndex;
+
+ // SMS-SUBMIT Format:
+ //
+ // PDU Type - 1 octet
+ // Message Reference - 1 octet
+ // DA - Destination Address - 2 to 12 octets
+ // PID - Protocol Identifier - 1 octet
+ // DCS - Data Coding Scheme - 1 octet
+ // VP - Validity Period - 0, 1 or 7 octets
+ // UDL - User Data Length - 1 octet
+ // UD - User Data - 140 octets
+
+ let addressFormat = PDU_TOA_ISDN; // 81
+ if (address[0] == '+') {
+ addressFormat = PDU_TOA_INTERNATIONAL | PDU_TOA_ISDN; // 91
+ address = address.substring(1);
+ }
+ //TODO validity is unsupported for now
+ let validity = 0;
+
+ let headerOctets = (userDataHeaderLength ? userDataHeaderLength + 1 : 0);
+ let paddingBits;
+ let userDataLengthInSeptets;
+ let userDataLengthInOctets;
+ if (dcs == PDU_DCS_MSG_CODING_7BITS_ALPHABET) {
+ let headerSeptets = Math.ceil(headerOctets * 8 / 7);
+ userDataLengthInSeptets = headerSeptets + encodedBodyLength;
+ userDataLengthInOctets = Math.ceil(userDataLengthInSeptets * 7 / 8);
+ paddingBits = headerSeptets * 7 - headerOctets * 8;
+ } else {
+ userDataLengthInOctets = headerOctets + encodedBodyLength;
+ paddingBits = 0;
+ }
+
+ let pduOctetLength = 4 + // PDU Type, Message Ref, address length + format
+ Math.ceil(address.length / 2) +
+ 3 + // PID, DCS, UDL
+ userDataLengthInOctets;
+ if (validity) {
+ //TODO: add more to pduOctetLength
+ }
+
+ // Start the string. Since octets are represented in hex, we will need
+ // twice as many characters as octets.
+ Buf.writeInt32(pduOctetLength * 2);
+
+ // - PDU-TYPE-
+
+ // +--------+----------+---------+---------+--------+---------+
+ // | RP (1) | UDHI (1) | SRR (1) | VPF (2) | RD (1) | MTI (2) |
+ // +--------+----------+---------+---------+--------+---------+
+ // RP: 0 Reply path parameter is not set
+ // 1 Reply path parameter is set
+ // UDHI: 0 The UD Field contains only the short message
+ // 1 The beginning of the UD field contains a header in addition
+ // of the short message
+ // SRR: 0 A status report is not requested
+ // 1 A status report is requested
+ // VPF: bit4 bit3
+ // 0 0 VP field is not present
+ // 0 1 Reserved
+ // 1 0 VP field present an integer represented (relative)
+ // 1 1 VP field present a semi-octet represented (absolute)
+ // RD: Instruct the SMSC to accept(0) or reject(1) an SMS-SUBMIT
+ // for a short message still held in the SMSC which has the same
+ // MR and DA as a previously submitted short message from the
+ // same OA
+ // MTI: bit1 bit0 Message Type
+ // 0 0 SMS-DELIVER (SMSC ==> MS)
+ // 0 1 SMS-SUBMIT (MS ==> SMSC)
+
+ // PDU type. MTI is set to SMS-SUBMIT
+ let firstOctet = PDU_MTI_SMS_SUBMIT;
+
+ // Status-Report-Request
+ if (options.requestStatusReport) {
+ firstOctet |= PDU_SRI_SRR;
+ }
+
+ // Validity period
+ if (validity) {
+ //TODO: not supported yet, OR with one of PDU_VPF_*
+ }
+ // User data header indicator
+ if (headerOctets) {
+ firstOctet |= PDU_UDHI;
+ }
+ this.writeHexOctet(firstOctet);
+
+ // Message reference 00
+ this.writeHexOctet(0x00);
+
+ // - Destination Address -
+ this.writeHexOctet(address.length);
+ this.writeHexOctet(addressFormat);
+ this.writeSwappedNibbleBCD(address);
+
+ // - Protocol Identifier -
+ this.writeHexOctet(0x00);
+
+ // - Data coding scheme -
+ // For now it assumes bits 7..4 = 1111 except for the 16 bits use case
+ this.writeHexOctet(dcs);
+
+ // - Validity Period -
+ if (validity) {
+ this.writeHexOctet(validity);
+ }
+
+ // - User Data -
+ if (dcs == PDU_DCS_MSG_CODING_7BITS_ALPHABET) {
+ this.writeHexOctet(userDataLengthInSeptets);
+ } else {
+ this.writeHexOctet(userDataLengthInOctets);
+ }
+
+ if (headerOctets) {
+ this.writeUserDataHeader(options);
+ }
+
+ switch (dcs) {
+ case PDU_DCS_MSG_CODING_7BITS_ALPHABET:
+ this.writeStringAsSeptets(body, paddingBits, langIndex, langShiftIndex);
+ break;
+ case PDU_DCS_MSG_CODING_8BITS_ALPHABET:
+ // Unsupported.
+ break;
+ case PDU_DCS_MSG_CODING_16BITS_ALPHABET:
+ this.writeUCS2String(body);
+ break;
+ }
+
+ // End of the string. The string length is always even by definition, so
+ // we write two \0 delimiters.
+ Buf.writeUint16(0);
+ Buf.writeUint16(0);
+ },
+
+ /**
+ * Read GSM CBS message serial number.
+ *
+ * @param msg
+ * message object for output.
+ *
+ * @see 3GPP TS 23.041 section 9.4.1.2.1
+ */
+ readCbSerialNumber: function(msg) {
+ let Buf = this.context.Buf;
+ msg.serial = Buf.readUint8() << 8 | Buf.readUint8();
+ msg.geographicalScope = (msg.serial >>> 14) & 0x03;
+ msg.messageCode = (msg.serial >>> 4) & 0x03FF;
+ msg.updateNumber = msg.serial & 0x0F;
+ },
+
+ /**
+ * Read GSM CBS message message identifier.
+ *
+ * @param msg
+ * message object for output.
+ *
+ * @see 3GPP TS 23.041 section 9.4.1.2.2
+ */
+ readCbMessageIdentifier: function(msg) {
+ let Buf = this.context.Buf;
+ msg.messageId = Buf.readUint8() << 8 | Buf.readUint8();
+ },
+
+ /**
+ * Read ETWS information from message identifier and serial Number
+ * @param msg
+ * message object for output.
+ *
+ * @see 3GPP TS 23.041 section 9.4.1.2.1 & 9.4.1.2.2
+ */
+ readCbEtwsInfo: function(msg) {
+ if ((msg.format != CB_FORMAT_ETWS)
+ && (msg.messageId >= CB_GSM_MESSAGEID_ETWS_BEGIN)
+ && (msg.messageId <= CB_GSM_MESSAGEID_ETWS_END)) {
+ // `In the case of transmitting CBS message for ETWS, a part of
+ // Message Code can be used to command mobile terminals to activate
+ // emergency user alert and message popup in order to alert the users.`
+ msg.etws = {
+ emergencyUserAlert: msg.messageCode & 0x0200 ? true : false,
+ popup: msg.messageCode & 0x0100 ? true : false
+ };
+
+ let warningType = msg.messageId - CB_GSM_MESSAGEID_ETWS_BEGIN;
+ if (warningType < CB_ETWS_WARNING_TYPE_NAMES.length) {
+ msg.etws.warningType = warningType;
+ }
+ }
+ },
+
+ /**
+ * Read CBS Data Coding Scheme.
+ *
+ * @param msg
+ * message object for output.
+ *
+ * @see 3GPP TS 23.038 section 5.
+ */
+ readCbDataCodingScheme: function(msg) {
+ let dcs = this.context.Buf.readUint8();
+ if (DEBUG) this.context.debug("PDU: read CBS dcs: " + dcs);
+
+ let language = null, hasLanguageIndicator = false;
+ // `Any reserved codings shall be assumed to be the GSM 7bit default
+ // alphabet.`
+ let encoding = PDU_DCS_MSG_CODING_7BITS_ALPHABET;
+ let messageClass = PDU_DCS_MSG_CLASS_NORMAL;
+
+ switch (dcs & PDU_DCS_CODING_GROUP_BITS) {
+ case 0x00: // 0000
+ language = CB_DCS_LANG_GROUP_1[dcs & 0x0F];
+ break;
+
+ case 0x10: // 0001
+ switch (dcs & 0x0F) {
+ case 0x00:
+ hasLanguageIndicator = true;
+ break;
+ case 0x01:
+ encoding = PDU_DCS_MSG_CODING_16BITS_ALPHABET;
+ hasLanguageIndicator = true;
+ break;
+ }
+ break;
+
+ case 0x20: // 0010
+ language = CB_DCS_LANG_GROUP_2[dcs & 0x0F];
+ break;
+
+ case 0x40: // 01xx
+ case 0x50:
+ //case 0x60: Text Compression, not supported
+ //case 0x70: Text Compression, not supported
+ case 0x90: // 1001
+ encoding = (dcs & 0x0C);
+ if (encoding == 0x0C) {
+ encoding = PDU_DCS_MSG_CODING_7BITS_ALPHABET;
+ }
+ messageClass = (dcs & PDU_DCS_MSG_CLASS_BITS);
+ break;
+
+ case 0xF0:
+ encoding = (dcs & 0x04) ? PDU_DCS_MSG_CODING_8BITS_ALPHABET
+ : PDU_DCS_MSG_CODING_7BITS_ALPHABET;
+ switch(dcs & PDU_DCS_MSG_CLASS_BITS) {
+ case 0x01: messageClass = PDU_DCS_MSG_CLASS_USER_1; break;
+ case 0x02: messageClass = PDU_DCS_MSG_CLASS_USER_2; break;
+ case 0x03: messageClass = PDU_DCS_MSG_CLASS_3; break;
+ }
+ break;
+
+ case 0x30: // 0011 (Reserved)
+ case 0x80: // 1000 (Reserved)
+ case 0xA0: // 1010..1100 (Reserved)
+ case 0xB0:
+ case 0xC0:
+ break;
+
+ default:
+ throw new Error("Unsupported CBS data coding scheme: " + dcs);
+ }
+
+ msg.dcs = dcs;
+ msg.encoding = encoding;
+ msg.language = language;
+ msg.messageClass = GECKO_SMS_MESSAGE_CLASSES[messageClass];
+ msg.hasLanguageIndicator = hasLanguageIndicator;
+ },
+
+ /**
+ * Read GSM CBS message page parameter.
+ *
+ * @param msg
+ * message object for output.
+ *
+ * @see 3GPP TS 23.041 section 9.4.1.2.4
+ */
+ readCbPageParameter: function(msg) {
+ let octet = this.context.Buf.readUint8();
+ msg.pageIndex = (octet >>> 4) & 0x0F;
+ msg.numPages = octet & 0x0F;
+ if (!msg.pageIndex || !msg.numPages) {
+ // `If a mobile receives the code 0000 in either the first field or the
+ // second field then it shall treat the CBS message exactly the same as a
+ // CBS message with page parameter 0001 0001 (i.e. a single page message).`
+ msg.pageIndex = msg.numPages = 1;
+ }
+ },
+
+ /**
+ * Read ETWS Primary Notification message warning type.
+ *
+ * @param msg
+ * message object for output.
+ *
+ * @see 3GPP TS 23.041 section 9.3.24
+ */
+ readCbWarningType: function(msg) {
+ let Buf = this.context.Buf;
+ let word = Buf.readUint8() << 8 | Buf.readUint8();
+ msg.etws = {
+ warningType: (word >>> 9) & 0x7F,
+ popup: word & 0x80 ? true : false,
+ emergencyUserAlert: word & 0x100 ? true : false
+ };
+ },
+
+ /**
+ * Read GSM CB Data
+ *
+ * This parameter is a copy of the 'CBS-Message-Information-Page' as sent
+ * from the CBC to the BSC.
+ *
+ * @see 3GPP TS 23.041 section 9.4.1.2.5
+ */
+ readGsmCbData: function(msg, length) {
+ let Buf = this.context.Buf;
+ let bufAdapter = {
+ context: this.context,
+ readHexOctet: function() {
+ return Buf.readUint8();
+ }
+ };
+
+ msg.body = null;
+ msg.data = null;
+ switch (msg.encoding) {
+ case PDU_DCS_MSG_CODING_7BITS_ALPHABET:
+ msg.body = this.readSeptetsToString.call(bufAdapter,
+ Math.floor(length * 8 / 7), 0,
+ PDU_NL_IDENTIFIER_DEFAULT,
+ PDU_NL_IDENTIFIER_DEFAULT);
+ if (msg.hasLanguageIndicator) {
+ msg.language = msg.body.substring(0, 2);
+ msg.body = msg.body.substring(3);
+ }
+ break;
+
+ case PDU_DCS_MSG_CODING_8BITS_ALPHABET:
+ msg.data = Buf.readUint8Array(length);
+ break;
+
+ case PDU_DCS_MSG_CODING_16BITS_ALPHABET:
+ if (msg.hasLanguageIndicator) {
+ msg.language = this.readSeptetsToString.call(bufAdapter, 2, 0,
+ PDU_NL_IDENTIFIER_DEFAULT,
+ PDU_NL_IDENTIFIER_DEFAULT);
+ length -= 2;
+ }
+ msg.body = this.readUCS2String.call(bufAdapter, length);
+ break;
+ }
+
+ if (msg.data || !msg.body) {
+ return;
+ }
+
+ // According to 9.3.19 CBS-Message-Information-Page in TS 23.041:
+ // "
+ // This parameter is of a fixed length of 82 octets and carries up to and
+ // including 82 octets of user information. Where the user information is
+ // less than 82 octets, the remaining octets must be filled with padding.
+ // "
+ // According to 6.2.1.1 GSM 7 bit Default Alphabet and 6.2.3 UCS2 in
+ // TS 23.038, the padding character is <CR>.
+ for (let i = msg.body.length - 1; i >= 0; i--) {
+ if (msg.body.charAt(i) !== '\r') {
+ msg.body = msg.body.substring(0, i + 1);
+ break;
+ }
+ }
+ },
+
+ /**
+ * Read UMTS CB Data
+ *
+ * Octet Number(s) Parameter
+ * 1 Number-of-Pages
+ * 2 - 83 CBS-Message-Information-Page 1
+ * 84 CBS-Message-Information-Length 1
+ * ...
+ * CBS-Message-Information-Page n
+ * CBS-Message-Information-Length n
+ *
+ * @see 3GPP TS 23.041 section 9.4.2.2.5
+ */
+ readUmtsCbData: function(msg) {
+ let Buf = this.context.Buf;
+ let numOfPages = Buf.readUint8();
+ if (numOfPages < 0 || numOfPages > 15) {
+ throw new Error("Invalid numOfPages: " + numOfPages);
+ }
+
+ let bufAdapter = {
+ context: this.context,
+ readHexOctet: function() {
+ return Buf.readUint8();
+ }
+ };
+
+ let removePaddingCharactors = function (text) {
+ for (let i = text.length - 1; i >= 0; i--) {
+ if (text.charAt(i) !== '\r') {
+ return text.substring(0, i + 1);
+ }
+ }
+ return text;
+ };
+
+ let totalLength = 0, length, pageLengths = [];
+ for (let i = 0; i < numOfPages; i++) {
+ Buf.seekIncoming(CB_MSG_PAGE_INFO_SIZE);
+ length = Buf.readUint8();
+ totalLength += length;
+ pageLengths.push(length);
+ }
+
+ // Seek back to beginning of CB Data.
+ Buf.seekIncoming(-numOfPages * (CB_MSG_PAGE_INFO_SIZE + 1));
+
+ switch (msg.encoding) {
+ case PDU_DCS_MSG_CODING_7BITS_ALPHABET: {
+ let body;
+ msg.body = "";
+ for (let i = 0; i < numOfPages; i++) {
+ body = this.readSeptetsToString.call(bufAdapter,
+ Math.floor(pageLengths[i] * 8 / 7),
+ 0,
+ PDU_NL_IDENTIFIER_DEFAULT,
+ PDU_NL_IDENTIFIER_DEFAULT);
+ if (msg.hasLanguageIndicator) {
+ if (!msg.language) {
+ msg.language = body.substring(0, 2);
+ }
+ body = body.substring(3);
+ }
+
+ msg.body += removePaddingCharactors(body);
+
+ // Skip padding octets
+ Buf.seekIncoming(CB_MSG_PAGE_INFO_SIZE - pageLengths[i]);
+ // Read the octet of CBS-Message-Information-Length
+ Buf.readUint8();
+ }
+
+ break;
+ }
+
+ case PDU_DCS_MSG_CODING_8BITS_ALPHABET: {
+ msg.data = new Uint8Array(totalLength);
+ for (let i = 0, j = 0; i < numOfPages; i++) {
+ for (let pageLength = pageLengths[i]; pageLength > 0; pageLength--) {
+ msg.data[j++] = Buf.readUint8();
+ }
+
+ // Skip padding octets
+ Buf.seekIncoming(CB_MSG_PAGE_INFO_SIZE - pageLengths[i]);
+ // Read the octet of CBS-Message-Information-Length
+ Buf.readUint8();
+ }
+
+ break;
+ }
+
+ case PDU_DCS_MSG_CODING_16BITS_ALPHABET: {
+ msg.body = "";
+ for (let i = 0; i < numOfPages; i++) {
+ let pageLength = pageLengths[i];
+ if (msg.hasLanguageIndicator) {
+ if (!msg.language) {
+ msg.language = this.readSeptetsToString.call(bufAdapter,
+ 2,
+ 0,
+ PDU_NL_IDENTIFIER_DEFAULT,
+ PDU_NL_IDENTIFIER_DEFAULT);
+ } else {
+ Buf.readUint16();
+ }
+
+ pageLength -= 2;
+ }
+
+ msg.body += removePaddingCharactors(
+ this.readUCS2String.call(bufAdapter, pageLength));
+
+ // Skip padding octets
+ Buf.seekIncoming(CB_MSG_PAGE_INFO_SIZE - pageLengths[i]);
+ // Read the octet of CBS-Message-Information-Length
+ Buf.readUint8();
+ }
+
+ break;
+ }
+ }
+ },
+
+ /**
+ * Read Cell GSM/ETWS/UMTS Broadcast Message.
+ *
+ * @param pduLength
+ * total length of the incoming PDU in octets.
+ */
+ readCbMessage: function(pduLength) {
+ // Validity GSM ETWS UMTS
+ let msg = {
+ // Internally used in ril_worker:
+ serial: null, // O O O
+ updateNumber: null, // O O O
+ format: null, // O O O
+ dcs: 0x0F, // O X O
+ encoding: PDU_DCS_MSG_CODING_7BITS_ALPHABET, // O X O
+ hasLanguageIndicator: false, // O X O
+ data: null, // O X O
+ body: null, // O X O
+ pageIndex: 1, // O X X
+ numPages: 1, // O X X
+
+ // DOM attributes:
+ geographicalScope: null, // O O O
+ messageCode: null, // O O O
+ messageId: null, // O O O
+ language: null, // O X O
+ fullBody: null, // O X O
+ fullData: null, // O X O
+ messageClass: GECKO_SMS_MESSAGE_CLASSES[PDU_DCS_MSG_CLASS_NORMAL], // O x O
+ etws: null // ? O ?
+ /*{
+ warningType: null, // X O X
+ popup: false, // X O X
+ emergencyUserAlert: false, // X O X
+ }*/
+ };
+
+ if (pduLength <= CB_MESSAGE_SIZE_ETWS) {
+ msg.format = CB_FORMAT_ETWS;
+ return this.readEtwsCbMessage(msg);
+ }
+
+ if (pduLength <= CB_MESSAGE_SIZE_GSM) {
+ msg.format = CB_FORMAT_GSM;
+ return this.readGsmCbMessage(msg, pduLength);
+ }
+
+ if (pduLength >= CB_MESSAGE_SIZE_UMTS_MIN &&
+ pduLength <= CB_MESSAGE_SIZE_UMTS_MAX) {
+ msg.format = CB_FORMAT_UMTS;
+ return this.readUmtsCbMessage(msg);
+ }
+
+ throw new Error("Invalid PDU Length: " + pduLength);
+ },
+
+ /**
+ * Read UMTS CBS Message.
+ *
+ * @param msg
+ * message object for output.
+ *
+ * @see 3GPP TS 23.041 section 9.4.2
+ * @see 3GPP TS 25.324 section 10.2
+ */
+ readUmtsCbMessage: function(msg) {
+ let Buf = this.context.Buf;
+ let type = Buf.readUint8();
+ if (type != CB_UMTS_MESSAGE_TYPE_CBS) {
+ throw new Error("Unsupported UMTS Cell Broadcast message type: " + type);
+ }
+
+ this.readCbMessageIdentifier(msg);
+ this.readCbSerialNumber(msg);
+ this.readCbEtwsInfo(msg);
+ this.readCbDataCodingScheme(msg);
+ this.readUmtsCbData(msg);
+
+ return msg;
+ },
+
+ /**
+ * Read GSM Cell Broadcast Message.
+ *
+ * @param msg
+ * message object for output.
+ * @param pduLength
+ * total length of the incomint PDU in octets.
+ *
+ * @see 3GPP TS 23.041 clause 9.4.1.2
+ */
+ readGsmCbMessage: function(msg, pduLength) {
+ this.readCbSerialNumber(msg);
+ this.readCbMessageIdentifier(msg);
+ this.readCbEtwsInfo(msg);
+ this.readCbDataCodingScheme(msg);
+ this.readCbPageParameter(msg);
+
+ // GSM CB message header takes 6 octets.
+ this.readGsmCbData(msg, pduLength - 6);
+
+ return msg;
+ },
+
+ /**
+ * Read ETWS Primary Notification Message.
+ *
+ * @param msg
+ * message object for output.
+ *
+ * @see 3GPP TS 23.041 clause 9.4.1.3
+ */
+ readEtwsCbMessage: function(msg) {
+ this.readCbSerialNumber(msg);
+ this.readCbMessageIdentifier(msg);
+ this.readCbWarningType(msg);
+
+ // Octet 7..56 is Warning Security Information. However, according to
+ // section 9.4.1.3.6, `The UE shall ignore this parameter.` So we just skip
+ // processing it here.
+
+ return msg;
+ },
+
+ /**
+ * Read network name.
+ *
+ * @param len Length of the information element.
+ * @return
+ * {
+ * networkName: network name.
+ * shouldIncludeCi: Should Country's initials included in text string.
+ * }
+ * @see TS 24.008 clause 10.5.3.5a.
+ */
+ readNetworkName: function(len) {
+ // According to TS 24.008 Sec. 10.5.3.5a, the first octet is:
+ // bit 8: must be 1.
+ // bit 5-7: Text encoding.
+ // 000 - GSM default alphabet.
+ // 001 - UCS2 (16 bit).
+ // else - reserved.
+ // bit 4: MS should add the letters for Country's Initials and a space
+ // to the text string if this bit is true.
+ // bit 1-3: number of spare bits in last octet.
+
+ let codingInfo = this.readHexOctet();
+ if (!(codingInfo & 0x80)) {
+ return null;
+ }
+
+ let textEncoding = (codingInfo & 0x70) >> 4;
+ let shouldIncludeCountryInitials = !!(codingInfo & 0x08);
+ let spareBits = codingInfo & 0x07;
+ let resultString;
+
+ switch (textEncoding) {
+ case 0:
+ // GSM Default alphabet.
+ resultString = this.readSeptetsToString(
+ Math.floor(((len - 1) * 8 - spareBits) / 7), 0,
+ PDU_NL_IDENTIFIER_DEFAULT,
+ PDU_NL_IDENTIFIER_DEFAULT);
+ break;
+ case 1:
+ // UCS2 encoded.
+ resultString = this.context.ICCPDUHelper.readAlphaIdentifier(len - 1);
+ break;
+ default:
+ // Not an available text coding.
+ return null;
+ }
+
+ // TODO - Bug 820286: According to shouldIncludeCountryInitials, add
+ // country initials to the resulting string.
+ return resultString;
+ }
+};
+
+/**
+ * Provide buffer with bitwise read/write function so make encoding/decoding easier.
+ */
+function BitBufferHelperObject(/* unused */aContext) {
+ this.readBuffer = [];
+ this.writeBuffer = [];
+}
+BitBufferHelperObject.prototype = {
+ readCache: 0,
+ readCacheSize: 0,
+ readBuffer: null,
+ readIndex: 0,
+ writeCache: 0,
+ writeCacheSize: 0,
+ writeBuffer: null,
+
+ // Max length is 32 because we use integer as read/write cache.
+ // All read/write functions are implemented based on bitwise operation.
+ readBits: function(length) {
+ if (length <= 0 || length > 32) {
+ return null;
+ }
+
+ if (length > this.readCacheSize) {
+ let bytesToRead = Math.ceil((length - this.readCacheSize) / 8);
+ for(let i = 0; i < bytesToRead; i++) {
+ this.readCache = (this.readCache << 8) | (this.readBuffer[this.readIndex++] & 0xFF);
+ this.readCacheSize += 8;
+ }
+ }
+
+ let bitOffset = (this.readCacheSize - length),
+ resultMask = (1 << length) - 1,
+ result = 0;
+
+ result = (this.readCache >> bitOffset) & resultMask;
+ this.readCacheSize -= length;
+
+ return result;
+ },
+
+ backwardReadPilot: function(length) {
+ if (length <= 0) {
+ return;
+ }
+
+ // Zero-based position.
+ let bitIndexToRead = this.readIndex * 8 - this.readCacheSize - length;
+
+ if (bitIndexToRead < 0) {
+ return;
+ }
+
+ // Update readIndex, readCache, readCacheSize accordingly.
+ let readBits = bitIndexToRead % 8;
+ this.readIndex = Math.floor(bitIndexToRead / 8) + ((readBits) ? 1 : 0);
+ this.readCache = (readBits) ? this.readBuffer[this.readIndex - 1] : 0;
+ this.readCacheSize = (readBits) ? (8 - readBits) : 0;
+ },
+
+ writeBits: function(value, length) {
+ if (length <= 0 || length > 32) {
+ return;
+ }
+
+ let totalLength = length + this.writeCacheSize;
+
+ // 8-byte cache not full
+ if (totalLength < 8) {
+ let valueMask = (1 << length) - 1;
+ this.writeCache = (this.writeCache << length) | (value & valueMask);
+ this.writeCacheSize += length;
+ return;
+ }
+
+ // Deal with unaligned part
+ if (this.writeCacheSize) {
+ let mergeLength = 8 - this.writeCacheSize,
+ valueMask = (1 << mergeLength) - 1;
+
+ this.writeCache = (this.writeCache << mergeLength) | ((value >> (length - mergeLength)) & valueMask);
+ this.writeBuffer.push(this.writeCache & 0xFF);
+ length -= mergeLength;
+ }
+
+ // Aligned part, just copy
+ while (length >= 8) {
+ length -= 8;
+ this.writeBuffer.push((value >> length) & 0xFF);
+ }
+
+ // Rest part is saved into cache
+ this.writeCacheSize = length;
+ this.writeCache = value & ((1 << length) - 1);
+
+ return;
+ },
+
+ // Drop what still in read cache and goto next 8-byte alignment.
+ // There might be a better naming.
+ nextOctetAlign: function() {
+ this.readCache = 0;
+ this.readCacheSize = 0;
+ },
+
+ // Flush current write cache to Buf with padding 0s.
+ // There might be a better naming.
+ flushWithPadding: function() {
+ if (this.writeCacheSize) {
+ this.writeBuffer.push(this.writeCache << (8 - this.writeCacheSize));
+ }
+ this.writeCache = 0;
+ this.writeCacheSize = 0;
+ },
+
+ startWrite: function(dataBuffer) {
+ this.writeBuffer = dataBuffer;
+ this.writeCache = 0;
+ this.writeCacheSize = 0;
+ },
+
+ startRead: function(dataBuffer) {
+ this.readBuffer = dataBuffer;
+ this.readCache = 0;
+ this.readCacheSize = 0;
+ this.readIndex = 0;
+ },
+
+ getWriteBufferSize: function() {
+ return this.writeBuffer.length;
+ },
+
+ overwriteWriteBuffer: function(position, data) {
+ let writeLength = data.length;
+ if (writeLength + position >= this.writeBuffer.length) {
+ writeLength = this.writeBuffer.length - position;
+ }
+ for (let i = 0; i < writeLength; i++) {
+ this.writeBuffer[i + position] = data[i];
+ }
+ }
+};
+
+/**
+ * Helper for CDMA PDU
+ *
+ * Currently, some function are shared with GsmPDUHelper, they should be
+ * moved from GsmPDUHelper to a common object shared among GsmPDUHelper and
+ * CdmaPDUHelper.
+ */
+function CdmaPDUHelperObject(aContext) {
+ this.context = aContext;
+}
+CdmaPDUHelperObject.prototype = {
+ context: null,
+
+ // 1..........C
+ // Only "1234567890*#" is defined in C.S0005-D v2.0
+ dtmfChars: ".1234567890*#...",
+
+ /**
+ * Entry point for SMS encoding, the options object is made compatible
+ * with existing writeMessage() of GsmPDUHelper, but less key is used.
+ *
+ * Current used key in options:
+ * @param number
+ * String containing the address (number) of the SMS receiver
+ * @param body
+ * String containing the message to be sent, segmented part
+ * @param dcs
+ * Data coding scheme. One of the PDU_DCS_MSG_CODING_*BITS_ALPHABET
+ * constants.
+ * @param encodedBodyLength
+ * Length of the user data when encoded with the given DCS. For UCS2,
+ * in bytes; for 7-bit, in septets.
+ * @param requestStatusReport
+ * Request status report.
+ * @param segmentRef
+ * Reference number of concatenated SMS message
+ * @param segmentMaxSeq
+ * Total number of concatenated SMS message
+ * @param segmentSeq
+ * Sequence number of concatenated SMS message
+ */
+ writeMessage: function(options) {
+ if (DEBUG) {
+ this.context.debug("cdma_writeMessage: " + JSON.stringify(options));
+ }
+
+ // Get encoding
+ options.encoding = this.gsmDcsToCdmaEncoding(options.dcs);
+
+ // Common Header
+ if (options.segmentMaxSeq > 1) {
+ this.writeInt(PDU_CDMA_MSG_TELESERIVCIE_ID_WEMT);
+ } else {
+ this.writeInt(PDU_CDMA_MSG_TELESERIVCIE_ID_SMS);
+ }
+
+ this.writeInt(0);
+ this.writeInt(PDU_CDMA_MSG_CATEGORY_UNSPEC);
+
+ // Just fill out address info in byte, rild will encap them for us
+ let addrInfo = this.encodeAddr(options.number);
+ this.writeByte(addrInfo.digitMode);
+ this.writeByte(addrInfo.numberMode);
+ this.writeByte(addrInfo.numberType);
+ this.writeByte(addrInfo.numberPlan);
+ this.writeByte(addrInfo.address.length);
+ for (let i = 0; i < addrInfo.address.length; i++) {
+ this.writeByte(addrInfo.address[i]);
+ }
+
+ // Subaddress, not supported
+ this.writeByte(0); // Subaddress : Type
+ this.writeByte(0); // Subaddress : Odd
+ this.writeByte(0); // Subaddress : length
+
+ // User Data
+ let encodeResult = this.encodeUserData(options);
+ this.writeByte(encodeResult.length);
+ for (let i = 0; i < encodeResult.length; i++) {
+ this.writeByte(encodeResult[i]);
+ }
+
+ encodeResult = null;
+ },
+
+ /**
+ * Data writters
+ */
+ writeInt: function(value) {
+ this.context.Buf.writeInt32(value);
+ },
+
+ writeByte: function(value) {
+ this.context.Buf.writeInt32(value & 0xFF);
+ },
+
+ /**
+ * Transform GSM DCS to CDMA encoding.
+ */
+ gsmDcsToCdmaEncoding: function(encoding) {
+ switch (encoding) {
+ case PDU_DCS_MSG_CODING_7BITS_ALPHABET:
+ return PDU_CDMA_MSG_CODING_7BITS_ASCII;
+ case PDU_DCS_MSG_CODING_8BITS_ALPHABET:
+ return PDU_CDMA_MSG_CODING_OCTET;
+ case PDU_DCS_MSG_CODING_16BITS_ALPHABET:
+ return PDU_CDMA_MSG_CODING_UNICODE;
+ }
+ throw new Error("gsmDcsToCdmaEncoding(): Invalid GSM SMS DCS value: " + encoding);
+ },
+
+ /**
+ * Encode address into CDMA address format, as a byte array.
+ *
+ * @see 3GGP2 C.S0015-B 2.0, 3.4.3.3 Address Parameters
+ *
+ * @param address
+ * String of address to be encoded
+ */
+ encodeAddr: function(address) {
+ let result = {};
+
+ result.numberType = PDU_CDMA_MSG_ADDR_NUMBER_TYPE_UNKNOWN;
+ result.numberPlan = PDU_CDMA_MSG_ADDR_NUMBER_TYPE_UNKNOWN;
+
+ if (address[0] === '+') {
+ address = address.substring(1);
+ }
+
+ // Try encode with DTMF first
+ result.digitMode = PDU_CDMA_MSG_ADDR_DIGIT_MODE_DTMF;
+ result.numberMode = PDU_CDMA_MSG_ADDR_NUMBER_MODE_ANSI;
+
+ result.address = [];
+ for (let i = 0; i < address.length; i++) {
+ let addrDigit = this.dtmfChars.indexOf(address.charAt(i));
+ if (addrDigit < 0) {
+ result.digitMode = PDU_CDMA_MSG_ADDR_DIGIT_MODE_ASCII;
+ result.numberMode = PDU_CDMA_MSG_ADDR_NUMBER_MODE_ASCII;
+ result.address = [];
+ break;
+ }
+ result.address.push(addrDigit);
+ }
+
+ // Address can't be encoded with DTMF, then use 7-bit ASCII
+ if (result.digitMode !== PDU_CDMA_MSG_ADDR_DIGIT_MODE_DTMF) {
+ if (address.indexOf("@") !== -1) {
+ result.numberType = PDU_CDMA_MSG_ADDR_NUMBER_TYPE_NATIONAL;
+ }
+
+ for (let i = 0; i < address.length; i++) {
+ result.address.push(address.charCodeAt(i) & 0x7F);
+ }
+ }
+
+ return result;
+ },
+
+ /**
+ * Encode SMS contents in options into CDMA userData field.
+ * Corresponding and required subparameters will be added automatically.
+ *
+ * @see 3GGP2 C.S0015-B 2.0, 3.4.3.7 Bearer Data
+ * 4.5 Bearer Data Parameters
+ *
+ * Current used key in options:
+ * @param body
+ * String containing the message to be sent, segmented part
+ * @param encoding
+ * Encoding method of CDMA, can be transformed from GSM DCS by function
+ * cdmaPduHelp.gsmDcsToCdmaEncoding()
+ * @param encodedBodyLength
+ * Length of the user data when encoded with the given DCS. For UCS2,
+ * in bytes; for 7-bit, in septets.
+ * @param requestStatusReport
+ * Request status report.
+ * @param segmentRef
+ * Reference number of concatenated SMS message
+ * @param segmentMaxSeq
+ * Total number of concatenated SMS message
+ * @param segmentSeq
+ * Sequence number of concatenated SMS message
+ */
+ encodeUserData: function(options) {
+ let userDataBuffer = [];
+ this.context.BitBufferHelper.startWrite(userDataBuffer);
+
+ // Message Identifier
+ this.encodeUserDataMsgId(options);
+
+ // User Data
+ this.encodeUserDataMsg(options);
+
+ // Reply Option
+ this.encodeUserDataReplyOption(options);
+
+ return userDataBuffer;
+ },
+
+ /**
+ * User data subparameter encoder : Message Identifier
+ *
+ * @see 3GGP2 C.S0015-B 2.0, 4.5.1 Message Identifier
+ */
+ encodeUserDataMsgId: function(options) {
+ let BitBufferHelper = this.context.BitBufferHelper;
+ BitBufferHelper.writeBits(PDU_CDMA_MSG_USERDATA_MSG_ID, 8);
+ BitBufferHelper.writeBits(3, 8);
+ BitBufferHelper.writeBits(PDU_CDMA_MSG_TYPE_SUBMIT, 4);
+ BitBufferHelper.writeBits(1, 16); // TODO: How to get message ID?
+ if (options.segmentMaxSeq > 1) {
+ BitBufferHelper.writeBits(1, 1);
+ } else {
+ BitBufferHelper.writeBits(0, 1);
+ }
+
+ BitBufferHelper.flushWithPadding();
+ },
+
+ /**
+ * User data subparameter encoder : User Data
+ *
+ * @see 3GGP2 C.S0015-B 2.0, 4.5.2 User Data
+ */
+ encodeUserDataMsg: function(options) {
+ let BitBufferHelper = this.context.BitBufferHelper;
+ BitBufferHelper.writeBits(PDU_CDMA_MSG_USERDATA_BODY, 8);
+ // Reserve space for length
+ BitBufferHelper.writeBits(0, 8);
+ let lengthPosition = BitBufferHelper.getWriteBufferSize();
+
+ BitBufferHelper.writeBits(options.encoding, 5);
+
+ // Add user data header for message segement
+ let msgBody = options.body,
+ msgBodySize = (options.encoding === PDU_CDMA_MSG_CODING_7BITS_ASCII ?
+ options.encodedBodyLength :
+ msgBody.length);
+ if (options.segmentMaxSeq > 1) {
+ if (options.encoding === PDU_CDMA_MSG_CODING_7BITS_ASCII) {
+ BitBufferHelper.writeBits(msgBodySize + 7, 8); // Required length for user data header, in septet(7-bit)
+
+ BitBufferHelper.writeBits(5, 8); // total header length 5 bytes
+ BitBufferHelper.writeBits(PDU_IEI_CONCATENATED_SHORT_MESSAGES_8BIT, 8); // header id 0
+ BitBufferHelper.writeBits(3, 8); // length of element for id 0 is 3
+ BitBufferHelper.writeBits(options.segmentRef & 0xFF, 8); // Segement reference
+ BitBufferHelper.writeBits(options.segmentMaxSeq & 0xFF, 8); // Max segment
+ BitBufferHelper.writeBits(options.segmentSeq & 0xFF, 8); // Current segment
+ BitBufferHelper.writeBits(0, 1); // Padding to make header data septet(7-bit) aligned
+ } else {
+ if (options.encoding === PDU_CDMA_MSG_CODING_UNICODE) {
+ BitBufferHelper.writeBits(msgBodySize + 3, 8); // Required length for user data header, in 16-bit
+ } else {
+ BitBufferHelper.writeBits(msgBodySize + 6, 8); // Required length for user data header, in octet(8-bit)
+ }
+
+ BitBufferHelper.writeBits(5, 8); // total header length 5 bytes
+ BitBufferHelper.writeBits(PDU_IEI_CONCATENATED_SHORT_MESSAGES_8BIT, 8); // header id 0
+ BitBufferHelper.writeBits(3, 8); // length of element for id 0 is 3
+ BitBufferHelper.writeBits(options.segmentRef & 0xFF, 8); // Segement reference
+ BitBufferHelper.writeBits(options.segmentMaxSeq & 0xFF, 8); // Max segment
+ BitBufferHelper.writeBits(options.segmentSeq & 0xFF, 8); // Current segment
+ }
+ } else {
+ BitBufferHelper.writeBits(msgBodySize, 8);
+ }
+
+ // Encode message based on encoding method
+ const langTable = PDU_NL_LOCKING_SHIFT_TABLES[PDU_NL_IDENTIFIER_DEFAULT];
+ const langShiftTable = PDU_NL_SINGLE_SHIFT_TABLES[PDU_NL_IDENTIFIER_DEFAULT];
+ for (let i = 0; i < msgBody.length; i++) {
+ switch (options.encoding) {
+ case PDU_CDMA_MSG_CODING_OCTET: {
+ let msgDigit = msgBody.charCodeAt(i);
+ BitBufferHelper.writeBits(msgDigit, 8);
+ break;
+ }
+ case PDU_CDMA_MSG_CODING_7BITS_ASCII: {
+ let msgDigit = msgBody.charCodeAt(i),
+ msgDigitChar = msgBody.charAt(i);
+
+ if (msgDigit >= 32) {
+ BitBufferHelper.writeBits(msgDigit, 7);
+ } else {
+ msgDigit = langTable.indexOf(msgDigitChar);
+
+ if (msgDigit === PDU_NL_EXTENDED_ESCAPE) {
+ break;
+ }
+ if (msgDigit >= 0) {
+ BitBufferHelper.writeBits(msgDigit, 7);
+ } else {
+ msgDigit = langShiftTable.indexOf(msgDigitChar);
+ if (msgDigit == -1) {
+ throw new Error("'" + msgDigitChar + "' is not in 7 bit alphabet "
+ + langIndex + ":" + langShiftIndex + "!");
+ }
+
+ if (msgDigit === PDU_NL_RESERVED_CONTROL) {
+ break;
+ }
+
+ BitBufferHelper.writeBits(PDU_NL_EXTENDED_ESCAPE, 7);
+ BitBufferHelper.writeBits(msgDigit, 7);
+ }
+ }
+ break;
+ }
+ case PDU_CDMA_MSG_CODING_UNICODE: {
+ let msgDigit = msgBody.charCodeAt(i);
+ BitBufferHelper.writeBits(msgDigit, 16);
+ break;
+ }
+ }
+ }
+ BitBufferHelper.flushWithPadding();
+
+ // Fill length
+ let currentPosition = BitBufferHelper.getWriteBufferSize();
+ BitBufferHelper.overwriteWriteBuffer(lengthPosition - 1, [currentPosition - lengthPosition]);
+ },
+
+ /**
+ * User data subparameter encoder : Reply Option
+ *
+ * @see 3GGP2 C.S0015-B 2.0, 4.5.11 Reply Option
+ */
+ encodeUserDataReplyOption: function(options) {
+ if (options.requestStatusReport) {
+ let BitBufferHelper = this.context.BitBufferHelper;
+ BitBufferHelper.writeBits(PDU_CDMA_MSG_USERDATA_REPLY_OPTION, 8);
+ BitBufferHelper.writeBits(1, 8);
+ BitBufferHelper.writeBits(0, 1); // USER_ACK_REQ
+ BitBufferHelper.writeBits(1, 1); // DAK_REQ
+ BitBufferHelper.flushWithPadding();
+ }
+ },
+
+ /**
+ * Entry point for SMS decoding, the returned object is made compatible
+ * with existing readMessage() of GsmPDUHelper
+ */
+ readMessage: function() {
+ let message = {};
+
+ // Teleservice Identifier
+ message.teleservice = this.readInt();
+
+ // Message Type
+ let isServicePresent = this.readByte();
+ if (isServicePresent) {
+ message.messageType = PDU_CDMA_MSG_TYPE_BROADCAST;
+ } else {
+ if (message.teleservice) {
+ message.messageType = PDU_CDMA_MSG_TYPE_P2P;
+ } else {
+ message.messageType = PDU_CDMA_MSG_TYPE_ACK;
+ }
+ }
+
+ // Service Category
+ message.service = this.readInt();
+
+ // Originated Address
+ let addrInfo = {};
+ addrInfo.digitMode = (this.readInt() & 0x01);
+ addrInfo.numberMode = (this.readInt() & 0x01);
+ addrInfo.numberType = (this.readInt() & 0x01);
+ addrInfo.numberPlan = (this.readInt() & 0x01);
+ addrInfo.addrLength = this.readByte();
+ addrInfo.address = [];
+ for (let i = 0; i < addrInfo.addrLength; i++) {
+ addrInfo.address.push(this.readByte());
+ }
+ message.sender = this.decodeAddr(addrInfo);
+
+ // Originated Subaddress
+ addrInfo.Type = (this.readInt() & 0x07);
+ addrInfo.Odd = (this.readByte() & 0x01);
+ addrInfo.addrLength = this.readByte();
+ for (let i = 0; i < addrInfo.addrLength; i++) {
+ let addrDigit = this.readByte();
+ message.sender += String.fromCharCode(addrDigit);
+ }
+
+ // Bearer Data
+ this.decodeUserData(message);
+
+ // Bearer Data Sub-Parameter: User Data
+ let userData = message[PDU_CDMA_MSG_USERDATA_BODY];
+ [message.header, message.body, message.encoding, message.data] =
+ (userData) ? [userData.header, userData.body, userData.encoding, userData.data]
+ : [null, null, null, null];
+
+ // Bearer Data Sub-Parameter: Message Status
+ // Success Delivery (0) if both Message Status and User Data are absent.
+ // Message Status absent (-1) if only User Data is available.
+ let msgStatus = message[PDU_CDMA_MSG_USER_DATA_MSG_STATUS];
+ [message.errorClass, message.msgStatus] =
+ (msgStatus) ? [msgStatus.errorClass, msgStatus.msgStatus]
+ : ((message.body) ? [-1, -1] : [0, 0]);
+
+ // Transform message to GSM msg
+ let msg = {
+ SMSC: "",
+ mti: 0,
+ udhi: 0,
+ sender: message.sender,
+ recipient: null,
+ pid: PDU_PID_DEFAULT,
+ epid: PDU_PID_DEFAULT,
+ dcs: 0,
+ mwi: null,
+ replace: false,
+ header: message.header,
+ body: message.body,
+ data: message.data,
+ sentTimestamp: message[PDU_CDMA_MSG_USERDATA_TIMESTAMP],
+ language: message[PDU_CDMA_LANGUAGE_INDICATOR],
+ status: null,
+ scts: null,
+ dt: null,
+ encoding: message.encoding,
+ messageClass: GECKO_SMS_MESSAGE_CLASSES[PDU_DCS_MSG_CLASS_NORMAL],
+ messageType: message.messageType,
+ serviceCategory: message.service,
+ subMsgType: message[PDU_CDMA_MSG_USERDATA_MSG_ID].msgType,
+ msgId: message[PDU_CDMA_MSG_USERDATA_MSG_ID].msgId,
+ errorClass: message.errorClass,
+ msgStatus: message.msgStatus,
+ teleservice: message.teleservice
+ };
+
+ return msg;
+ },
+
+ /**
+ * Helper for processing received SMS parcel data.
+ *
+ * @param length
+ * Length of SMS string in the incoming parcel.
+ *
+ * @return Message parsed or null for invalid message.
+ */
+ processReceivedSms: function(length) {
+ if (!length) {
+ if (DEBUG) this.context.debug("Received empty SMS!");
+ return [null, PDU_FCS_UNSPECIFIED];
+ }
+
+ let message = this.readMessage();
+ if (DEBUG) this.context.debug("Got new SMS: " + JSON.stringify(message));
+
+ // Determine result
+ if (!message) {
+ return [null, PDU_FCS_UNSPECIFIED];
+ }
+
+ return [message, PDU_FCS_OK];
+ },
+
+ /**
+ * Data readers
+ */
+ readInt: function() {
+ return this.context.Buf.readInt32();
+ },
+
+ readByte: function() {
+ return (this.context.Buf.readInt32() & 0xFF);
+ },
+
+ /**
+ * Decode CDMA address data into address string
+ *
+ * @see 3GGP2 C.S0015-B 2.0, 3.4.3.3 Address Parameters
+ *
+ * Required key in addrInfo
+ * @param addrLength
+ * Length of address
+ * @param digitMode
+ * Address encoding method
+ * @param address
+ * Array of encoded address data
+ */
+ decodeAddr: function(addrInfo) {
+ let result = "";
+ for (let i = 0; i < addrInfo.addrLength; i++) {
+ if (addrInfo.digitMode === PDU_CDMA_MSG_ADDR_DIGIT_MODE_DTMF) {
+ result += this.dtmfChars.charAt(addrInfo.address[i]);
+ } else {
+ result += String.fromCharCode(addrInfo.address[i]);
+ }
+ }
+ return result;
+ },
+
+ /**
+ * Read userData in parcel buffer and decode into message object.
+ * Each subparameter will be stored in corresponding key.
+ *
+ * @see 3GGP2 C.S0015-B 2.0, 3.4.3.7 Bearer Data
+ * 4.5 Bearer Data Parameters
+ */
+ decodeUserData: function(message) {
+ let userDataLength = this.readInt();
+
+ while (userDataLength > 0) {
+ let id = this.readByte(),
+ length = this.readByte(),
+ userDataBuffer = [];
+
+ for (let i = 0; i < length; i++) {
+ userDataBuffer.push(this.readByte());
+ }
+
+ this.context.BitBufferHelper.startRead(userDataBuffer);
+
+ switch (id) {
+ case PDU_CDMA_MSG_USERDATA_MSG_ID:
+ message[id] = this.decodeUserDataMsgId();
+ break;
+ case PDU_CDMA_MSG_USERDATA_BODY:
+ message[id] = this.decodeUserDataMsg(message[PDU_CDMA_MSG_USERDATA_MSG_ID].userHeader);
+ break;
+ case PDU_CDMA_MSG_USERDATA_TIMESTAMP:
+ message[id] = this.decodeUserDataTimestamp();
+ break;
+ case PDU_CDMA_MSG_USERDATA_REPLY_OPTION:
+ message[id] = this.decodeUserDataReplyOption();
+ break;
+ case PDU_CDMA_LANGUAGE_INDICATOR:
+ message[id] = this.decodeLanguageIndicator();
+ break;
+ case PDU_CDMA_MSG_USERDATA_CALLBACK_NUMBER:
+ message[id] = this.decodeUserDataCallbackNumber();
+ break;
+ case PDU_CDMA_MSG_USER_DATA_MSG_STATUS:
+ message[id] = this.decodeUserDataMsgStatus();
+ break;
+ }
+
+ userDataLength -= (length + 2);
+ userDataBuffer = [];
+ }
+ },
+
+ /**
+ * User data subparameter decoder: Message Identifier
+ *
+ * @see 3GGP2 C.S0015-B 2.0, 4.5.1 Message Identifier
+ */
+ decodeUserDataMsgId: function() {
+ let result = {};
+ let BitBufferHelper = this.context.BitBufferHelper;
+ result.msgType = BitBufferHelper.readBits(4);
+ result.msgId = BitBufferHelper.readBits(16);
+ result.userHeader = BitBufferHelper.readBits(1);
+
+ return result;
+ },
+
+ /**
+ * Decode user data header, we only care about segment information
+ * on CDMA.
+ *
+ * This function is mostly copied from gsmPduHelper.readUserDataHeader() but
+ * change the read function, because CDMA user header decoding is't byte-wise
+ * aligned.
+ */
+ decodeUserDataHeader: function(encoding) {
+ let BitBufferHelper = this.context.BitBufferHelper;
+ let header = {},
+ headerSize = BitBufferHelper.readBits(8),
+ userDataHeaderSize = headerSize + 1,
+ headerPaddingBits = 0;
+
+ // Calculate header size
+ if (encoding === PDU_DCS_MSG_CODING_7BITS_ALPHABET) {
+ // Length is in 7-bit
+ header.length = Math.ceil(userDataHeaderSize * 8 / 7);
+ // Calulate padding length
+ headerPaddingBits = (header.length * 7) - (userDataHeaderSize * 8);
+ } else if (encoding === PDU_DCS_MSG_CODING_8BITS_ALPHABET) {
+ header.length = userDataHeaderSize;
+ } else {
+ header.length = userDataHeaderSize / 2;
+ }
+
+ while (headerSize) {
+ let identifier = BitBufferHelper.readBits(8),
+ length = BitBufferHelper.readBits(8);
+
+ headerSize -= (2 + length);
+
+ switch (identifier) {
+ case PDU_IEI_CONCATENATED_SHORT_MESSAGES_8BIT: {
+ let ref = BitBufferHelper.readBits(8),
+ max = BitBufferHelper.readBits(8),
+ seq = BitBufferHelper.readBits(8);
+ if (max && seq && (seq <= max)) {
+ header.segmentRef = ref;
+ header.segmentMaxSeq = max;
+ header.segmentSeq = seq;
+ }
+ break;
+ }
+ case PDU_IEI_APPLICATION_PORT_ADDRESSING_SCHEME_8BIT: {
+ let dstp = BitBufferHelper.readBits(8),
+ orip = BitBufferHelper.readBits(8);
+ if ((dstp < PDU_APA_RESERVED_8BIT_PORTS)
+ || (orip < PDU_APA_RESERVED_8BIT_PORTS)) {
+ // 3GPP TS 23.040 clause 9.2.3.24.3: "A receiving entity shall
+ // ignore any information element where the value of the
+ // Information-Element-Data is Reserved or not supported"
+ break;
+ }
+ header.destinationPort = dstp;
+ header.originatorPort = orip;
+ break;
+ }
+ case PDU_IEI_APPLICATION_PORT_ADDRESSING_SCHEME_16BIT: {
+ let dstp = (BitBufferHelper.readBits(8) << 8) | BitBufferHelper.readBits(8),
+ orip = (BitBufferHelper.readBits(8) << 8) | BitBufferHelper.readBits(8);
+ // 3GPP TS 23.040 clause 9.2.3.24.4: "A receiving entity shall
+ // ignore any information element where the value of the
+ // Information-Element-Data is Reserved or not supported"
+ if ((dstp < PDU_APA_VALID_16BIT_PORTS)
+ && (orip < PDU_APA_VALID_16BIT_PORTS)) {
+ header.destinationPort = dstp;
+ header.originatorPort = orip;
+ }
+ break;
+ }
+ case PDU_IEI_CONCATENATED_SHORT_MESSAGES_16BIT: {
+ let ref = (BitBufferHelper.readBits(8) << 8) | BitBufferHelper.readBits(8),
+ max = BitBufferHelper.readBits(8),
+ seq = BitBufferHelper.readBits(8);
+ if (max && seq && (seq <= max)) {
+ header.segmentRef = ref;
+ header.segmentMaxSeq = max;
+ header.segmentSeq = seq;
+ }
+ break;
+ }
+ case PDU_IEI_NATIONAL_LANGUAGE_SINGLE_SHIFT: {
+ let langShiftIndex = BitBufferHelper.readBits(8);
+ if (langShiftIndex < PDU_NL_SINGLE_SHIFT_TABLES.length) {
+ header.langShiftIndex = langShiftIndex;
+ }
+ break;
+ }
+ case PDU_IEI_NATIONAL_LANGUAGE_LOCKING_SHIFT: {
+ let langIndex = BitBufferHelper.readBits(8);
+ if (langIndex < PDU_NL_LOCKING_SHIFT_TABLES.length) {
+ header.langIndex = langIndex;
+ }
+ break;
+ }
+ case PDU_IEI_SPECIAL_SMS_MESSAGE_INDICATION: {
+ let msgInd = BitBufferHelper.readBits(8) & 0xFF,
+ msgCount = BitBufferHelper.readBits(8);
+ /*
+ * TS 23.040 V6.8.1 Sec 9.2.3.24.2
+ * bits 1 0 : basic message indication type
+ * bits 4 3 2 : extended message indication type
+ * bits 6 5 : Profile id
+ * bit 7 : storage type
+ */
+ let storeType = msgInd & PDU_MWI_STORE_TYPE_BIT;
+ header.mwi = {};
+ mwi = header.mwi;
+
+ if (storeType == PDU_MWI_STORE_TYPE_STORE) {
+ // Store message because TP_UDH indicates so, note this may override
+ // the setting in DCS, but that is expected
+ mwi.discard = false;
+ } else if (mwi.discard === undefined) {
+ // storeType == PDU_MWI_STORE_TYPE_DISCARD
+ // only override mwi.discard here if it hasn't already been set
+ mwi.discard = true;
+ }
+
+ mwi.msgCount = msgCount & 0xFF;
+ mwi.active = mwi.msgCount > 0;
+
+ if (DEBUG) {
+ this.context.debug("MWI in TP_UDH received: " + JSON.stringify(mwi));
+ }
+ break;
+ }
+ default:
+ // Drop unsupported id
+ for (let i = 0; i < length; i++) {
+ BitBufferHelper.readBits(8);
+ }
+ }
+ }
+
+ // Consume padding bits
+ if (headerPaddingBits) {
+ BitBufferHelper.readBits(headerPaddingBits);
+ }
+
+ return header;
+ },
+
+ getCdmaMsgEncoding: function(encoding) {
+ // Determine encoding method
+ switch (encoding) {
+ case PDU_CDMA_MSG_CODING_7BITS_ASCII:
+ case PDU_CDMA_MSG_CODING_IA5:
+ case PDU_CDMA_MSG_CODING_7BITS_GSM:
+ return PDU_DCS_MSG_CODING_7BITS_ALPHABET;
+ case PDU_CDMA_MSG_CODING_OCTET:
+ case PDU_CDMA_MSG_CODING_IS_91:
+ case PDU_CDMA_MSG_CODING_LATIN_HEBREW:
+ case PDU_CDMA_MSG_CODING_LATIN:
+ return PDU_DCS_MSG_CODING_8BITS_ALPHABET;
+ case PDU_CDMA_MSG_CODING_UNICODE:
+ case PDU_CDMA_MSG_CODING_SHIFT_JIS:
+ case PDU_CDMA_MSG_CODING_KOREAN:
+ return PDU_DCS_MSG_CODING_16BITS_ALPHABET;
+ }
+ return null;
+ },
+
+ decodeCdmaPDUMsg: function(encoding, msgType, msgBodySize) {
+ const langTable = PDU_NL_LOCKING_SHIFT_TABLES[PDU_NL_IDENTIFIER_DEFAULT];
+ const langShiftTable = PDU_NL_SINGLE_SHIFT_TABLES[PDU_NL_IDENTIFIER_DEFAULT];
+ let BitBufferHelper = this.context.BitBufferHelper;
+ let result = "";
+ let msgDigit;
+ switch (encoding) {
+ case PDU_CDMA_MSG_CODING_OCTET: // TODO : Require Test
+ while(msgBodySize > 0) {
+ msgDigit = String.fromCharCode(BitBufferHelper.readBits(8));
+ result += msgDigit;
+ msgBodySize--;
+ }
+ break;
+ case PDU_CDMA_MSG_CODING_IS_91: // TODO : Require Test
+ // Referenced from android code
+ switch (msgType) {
+ case PDU_CDMA_MSG_CODING_IS_91_TYPE_SMS:
+ case PDU_CDMA_MSG_CODING_IS_91_TYPE_SMS_FULL:
+ case PDU_CDMA_MSG_CODING_IS_91_TYPE_VOICEMAIL_STATUS:
+ while(msgBodySize > 0) {
+ msgDigit = String.fromCharCode(BitBufferHelper.readBits(6) + 0x20);
+ result += msgDigit;
+ msgBodySize--;
+ }
+ break;
+ case PDU_CDMA_MSG_CODING_IS_91_TYPE_CLI:
+ let addrInfo = {};
+ addrInfo.digitMode = PDU_CDMA_MSG_ADDR_DIGIT_MODE_DTMF;
+ addrInfo.numberMode = PDU_CDMA_MSG_ADDR_NUMBER_MODE_ANSI;
+ addrInfo.numberType = PDU_CDMA_MSG_ADDR_NUMBER_TYPE_UNKNOWN;
+ addrInfo.numberPlan = PDU_CDMA_MSG_ADDR_NUMBER_PLAN_UNKNOWN;
+ addrInfo.addrLength = msgBodySize;
+ addrInfo.address = [];
+ for (let i = 0; i < addrInfo.addrLength; i++) {
+ addrInfo.address.push(BitBufferHelper.readBits(4));
+ }
+ result = this.decodeAddr(addrInfo);
+ break;
+ }
+ // Fall through.
+ case PDU_CDMA_MSG_CODING_7BITS_ASCII:
+ case PDU_CDMA_MSG_CODING_IA5: // TODO : Require Test
+ while(msgBodySize > 0) {
+ msgDigit = BitBufferHelper.readBits(7);
+ if (msgDigit >= 32) {
+ msgDigit = String.fromCharCode(msgDigit);
+ } else {
+ if (msgDigit !== PDU_NL_EXTENDED_ESCAPE) {
+ msgDigit = langTable[msgDigit];
+ } else {
+ msgDigit = BitBufferHelper.readBits(7);
+ msgBodySize--;
+ msgDigit = langShiftTable[msgDigit];
+ }
+ }
+ result += msgDigit;
+ msgBodySize--;
+ }
+ break;
+ case PDU_CDMA_MSG_CODING_UNICODE:
+ while(msgBodySize > 0) {
+ msgDigit = String.fromCharCode(BitBufferHelper.readBits(16));
+ result += msgDigit;
+ msgBodySize--;
+ }
+ break;
+ case PDU_CDMA_MSG_CODING_7BITS_GSM: // TODO : Require Test
+ while(msgBodySize > 0) {
+ msgDigit = BitBufferHelper.readBits(7);
+ if (msgDigit !== PDU_NL_EXTENDED_ESCAPE) {
+ msgDigit = langTable[msgDigit];
+ } else {
+ msgDigit = BitBufferHelper.readBits(7);
+ msgBodySize--;
+ msgDigit = langShiftTable[msgDigit];
+ }
+ result += msgDigit;
+ msgBodySize--;
+ }
+ break;
+ case PDU_CDMA_MSG_CODING_LATIN: // TODO : Require Test
+ // Reference : http://en.wikipedia.org/wiki/ISO/IEC_8859-1
+ while(msgBodySize > 0) {
+ msgDigit = String.fromCharCode(BitBufferHelper.readBits(8));
+ result += msgDigit;
+ msgBodySize--;
+ }
+ break;
+ case PDU_CDMA_MSG_CODING_LATIN_HEBREW: // TODO : Require Test
+ // Reference : http://en.wikipedia.org/wiki/ISO/IEC_8859-8
+ while(msgBodySize > 0) {
+ msgDigit = BitBufferHelper.readBits(8);
+ if (msgDigit === 0xDF) {
+ msgDigit = String.fromCharCode(0x2017);
+ } else if (msgDigit === 0xFD) {
+ msgDigit = String.fromCharCode(0x200E);
+ } else if (msgDigit === 0xFE) {
+ msgDigit = String.fromCharCode(0x200F);
+ } else if (msgDigit >= 0xE0 && msgDigit <= 0xFA) {
+ msgDigit = String.fromCharCode(0x4F0 + msgDigit);
+ } else {
+ msgDigit = String.fromCharCode(msgDigit);
+ }
+ result += msgDigit;
+ msgBodySize--;
+ }
+ break;
+ case PDU_CDMA_MSG_CODING_SHIFT_JIS:
+ // Reference : http://msdn.microsoft.com/en-US/goglobal/cc305152.aspx
+ // http://demo.icu-project.org/icu-bin/convexp?conv=Shift_JIS
+ let shift_jis_message = [];
+
+ while (msgBodySize > 0) {
+ shift_jis_message.push(BitBufferHelper.readBits(8));
+ msgBodySize--;
+ }
+
+ let decoder = new TextDecoder("shift_jis");
+ result = decoder.decode(new Uint8Array(shift_jis_message));
+ break;
+ case PDU_CDMA_MSG_CODING_KOREAN:
+ case PDU_CDMA_MSG_CODING_GSM_DCS:
+ // Fall through.
+ default:
+ break;
+ }
+ return result;
+ },
+
+ /**
+ * User data subparameter decoder : User Data
+ *
+ * @see 3GGP2 C.S0015-B 2.0, 4.5.2 User Data
+ */
+ decodeUserDataMsg: function(hasUserHeader) {
+ let BitBufferHelper = this.context.BitBufferHelper;
+ let result = {},
+ encoding = BitBufferHelper.readBits(5),
+ msgType;
+
+ if (encoding === PDU_CDMA_MSG_CODING_IS_91) {
+ msgType = BitBufferHelper.readBits(8);
+ }
+ result.encoding = this.getCdmaMsgEncoding(encoding);
+
+ let msgBodySize = BitBufferHelper.readBits(8);
+
+ // For segmented SMS, a user header is included before sms content
+ if (hasUserHeader) {
+ result.header = this.decodeUserDataHeader(result.encoding);
+ // header size is included in body size, they are decoded
+ msgBodySize -= result.header.length;
+ }
+
+ // Store original payload if enconding is OCTET for further handling of WAP Push, etc.
+ if (encoding === PDU_CDMA_MSG_CODING_OCTET && msgBodySize > 0) {
+ result.data = new Uint8Array(msgBodySize);
+ for (let i = 0; i < msgBodySize; i++) {
+ result.data[i] = BitBufferHelper.readBits(8);
+ }
+ BitBufferHelper.backwardReadPilot(8 * msgBodySize);
+ }
+
+ // Decode sms content
+ result.body = this.decodeCdmaPDUMsg(encoding, msgType, msgBodySize);
+
+ return result;
+ },
+
+ decodeBcd: function(value) {
+ return ((value >> 4) & 0xF) * 10 + (value & 0x0F);
+ },
+
+ /**
+ * User data subparameter decoder : Time Stamp
+ *
+ * @see 3GGP2 C.S0015-B 2.0, 4.5.4 Message Center Time Stamp
+ */
+ decodeUserDataTimestamp: function() {
+ let BitBufferHelper = this.context.BitBufferHelper;
+ let year = this.decodeBcd(BitBufferHelper.readBits(8)),
+ month = this.decodeBcd(BitBufferHelper.readBits(8)) - 1,
+ date = this.decodeBcd(BitBufferHelper.readBits(8)),
+ hour = this.decodeBcd(BitBufferHelper.readBits(8)),
+ min = this.decodeBcd(BitBufferHelper.readBits(8)),
+ sec = this.decodeBcd(BitBufferHelper.readBits(8));
+
+ if (year >= 96 && year <= 99) {
+ year += 1900;
+ } else {
+ year += 2000;
+ }
+
+ let result = (new Date(year, month, date, hour, min, sec, 0)).valueOf();
+
+ return result;
+ },
+
+ /**
+ * User data subparameter decoder : Reply Option
+ *
+ * @see 3GGP2 C.S0015-B 2.0, 4.5.11 Reply Option
+ */
+ decodeUserDataReplyOption: function() {
+ let replyAction = this.context.BitBufferHelper.readBits(4),
+ result = { userAck: (replyAction & 0x8) ? true : false,
+ deliverAck: (replyAction & 0x4) ? true : false,
+ readAck: (replyAction & 0x2) ? true : false,
+ report: (replyAction & 0x1) ? true : false
+ };
+
+ return result;
+ },
+
+ /**
+ * User data subparameter decoder : Language Indicator
+ *
+ * @see 3GGP2 C.S0015-B 2.0, 4.5.14 Language Indicator
+ */
+ decodeLanguageIndicator: function() {
+ let language = this.context.BitBufferHelper.readBits(8);
+ let result = CB_CDMA_LANG_GROUP[language];
+ return result;
+ },
+
+ /**
+ * User data subparameter decoder : Call-Back Number
+ *
+ * @see 3GGP2 C.S0015-B 2.0, 4.5.15 Call-Back Number
+ */
+ decodeUserDataCallbackNumber: function() {
+ let BitBufferHelper = this.context.BitBufferHelper;
+ let digitMode = BitBufferHelper.readBits(1);
+ if (digitMode) {
+ let numberType = BitBufferHelper.readBits(3),
+ numberPlan = BitBufferHelper.readBits(4);
+ }
+ let numberFields = BitBufferHelper.readBits(8),
+ result = "";
+ for (let i = 0; i < numberFields; i++) {
+ if (digitMode === PDU_CDMA_MSG_ADDR_DIGIT_MODE_DTMF) {
+ let addrDigit = BitBufferHelper.readBits(4);
+ result += this.dtmfChars.charAt(addrDigit);
+ } else {
+ let addrDigit = BitBufferHelper.readBits(8);
+ result += String.fromCharCode(addrDigit);
+ }
+ }
+
+ return result;
+ },
+
+ /**
+ * User data subparameter decoder : Message Status
+ *
+ * @see 3GGP2 C.S0015-B 2.0, 4.5.21 Message Status
+ */
+ decodeUserDataMsgStatus: function() {
+ let BitBufferHelper = this.context.BitBufferHelper;
+ let result = {
+ errorClass: BitBufferHelper.readBits(2),
+ msgStatus: BitBufferHelper.readBits(6)
+ };
+
+ return result;
+ },
+
+ /**
+ * Decode information record parcel.
+ */
+ decodeInformationRecord: function() {
+ let Buf = this.context.Buf;
+ let records = [];
+ let numOfRecords = Buf.readInt32();
+
+ let type;
+ let record;
+ for (let i = 0; i < numOfRecords; i++) {
+ record = {};
+ type = Buf.readInt32();
+
+ switch (type) {
+ /*
+ * Every type is encaped by ril, except extended display
+ */
+ case PDU_CDMA_INFO_REC_TYPE_DISPLAY:
+ case PDU_CDMA_INFO_REC_TYPE_EXTENDED_DISPLAY:
+ record.display = Buf.readString();
+ break;
+ case PDU_CDMA_INFO_REC_TYPE_CALLED_PARTY_NUMBER:
+ record.calledNumber = {};
+ record.calledNumber.number = Buf.readString();
+ record.calledNumber.type = Buf.readInt32();
+ record.calledNumber.plan = Buf.readInt32();
+ record.calledNumber.pi = Buf.readInt32();
+ record.calledNumber.si = Buf.readInt32();
+ break;
+ case PDU_CDMA_INFO_REC_TYPE_CALLING_PARTY_NUMBER:
+ record.callingNumber = {};
+ record.callingNumber.number = Buf.readString();
+ record.callingNumber.type = Buf.readInt32();
+ record.callingNumber.plan = Buf.readInt32();
+ record.callingNumber.pi = Buf.readInt32();
+ record.callingNumber.si = Buf.readInt32();
+ break;
+ case PDU_CDMA_INFO_REC_TYPE_CONNECTED_NUMBER:
+ record.connectedNumber = {};
+ record.connectedNumber.number = Buf.readString();
+ record.connectedNumber.type = Buf.readInt32();
+ record.connectedNumber.plan = Buf.readInt32();
+ record.connectedNumber.pi = Buf.readInt32();
+ record.connectedNumber.si = Buf.readInt32();
+ break;
+ case PDU_CDMA_INFO_REC_TYPE_SIGNAL:
+ record.signal = {};
+ if (!Buf.readInt32()) { // Non-zero if signal is present.
+ Buf.seekIncoming(3 * Buf.UINT32_SIZE);
+ continue;
+ }
+ record.signal.type = Buf.readInt32();
+ record.signal.alertPitch = Buf.readInt32();
+ record.signal.signal = Buf.readInt32();
+ break;
+ case PDU_CDMA_INFO_REC_TYPE_REDIRECTING_NUMBER:
+ record.redirect = {};
+ record.redirect.number = Buf.readString();
+ record.redirect.type = Buf.readInt32();
+ record.redirect.plan = Buf.readInt32();
+ record.redirect.pi = Buf.readInt32();
+ record.redirect.si = Buf.readInt32();
+ record.redirect.reason = Buf.readInt32();
+ break;
+ case PDU_CDMA_INFO_REC_TYPE_LINE_CONTROL:
+ record.lineControl = {};
+ record.lineControl.polarityIncluded = Buf.readInt32();
+ record.lineControl.toggle = Buf.readInt32();
+ record.lineControl.reverse = Buf.readInt32();
+ record.lineControl.powerDenial = Buf.readInt32();
+ break;
+ case PDU_CDMA_INFO_REC_TYPE_T53_CLIR:
+ record.clirCause = Buf.readInt32();
+ break;
+ case PDU_CDMA_INFO_REC_TYPE_T53_AUDIO_CONTROL:
+ record.audioControl = {};
+ record.audioControl.upLink = Buf.readInt32();
+ record.audioControl.downLink = Buf.readInt32();
+ break;
+ case PDU_CDMA_INFO_REC_TYPE_T53_RELEASE:
+ // Fall through
+ default:
+ throw new Error("UNSOLICITED_CDMA_INFO_REC(), Unsupported information record type " + type + "\n");
+ }
+
+ records.push(record);
+ }
+
+ return records;
+ }
+};
+
+/**
+ * Helper for processing ICC PDUs.
+ */
+function ICCPDUHelperObject(aContext) {
+ this.context = aContext;
+}
+ICCPDUHelperObject.prototype = {
+ context: null,
+
+ /**
+ * Read GSM 8-bit unpacked octets,
+ * which are default 7-bit alphabets with bit 8 set to 0.
+ *
+ * @param numOctets
+ * Number of octets to be read.
+ */
+ read8BitUnpackedToString: function(numOctets) {
+ let GsmPDUHelper = this.context.GsmPDUHelper;
+
+ let ret = "";
+ let escapeFound = false;
+ let i;
+ const langTable = PDU_NL_LOCKING_SHIFT_TABLES[PDU_NL_IDENTIFIER_DEFAULT];
+ const langShiftTable = PDU_NL_SINGLE_SHIFT_TABLES[PDU_NL_IDENTIFIER_DEFAULT];
+
+ for(i = 0; i < numOctets; i++) {
+ let octet = GsmPDUHelper.readHexOctet();
+ if (octet == 0xff) {
+ i++;
+ break;
+ }
+
+ if (escapeFound) {
+ escapeFound = false;
+ if (octet == PDU_NL_EXTENDED_ESCAPE) {
+ // According to 3GPP TS 23.038, section 6.2.1.1, NOTE 1, "On
+ // receipt of this code, a receiving entity shall display a space
+ // until another extensiion table is defined."
+ ret += " ";
+ } else if (octet == PDU_NL_RESERVED_CONTROL) {
+ // According to 3GPP TS 23.038 B.2, "This code represents a control
+ // character and therefore must not be used for language specific
+ // characters."
+ ret += " ";
+ } else {
+ ret += langShiftTable[octet];
+ }
+ } else if (octet == PDU_NL_EXTENDED_ESCAPE) {
+ escapeFound = true;
+ } else {
+ ret += langTable[octet];
+ }
+ }
+
+ let Buf = this.context.Buf;
+ Buf.seekIncoming((numOctets - i) * Buf.PDU_HEX_OCTET_SIZE);
+ return ret;
+ },
+
+ /**
+ * Write GSM 8-bit unpacked octets.
+ *
+ * @param numOctets Number of total octets to be writen, including trailing
+ * 0xff.
+ * @param str String to be written. Could be null.
+ *
+ * @return The string has been written into Buf. "" if str is null.
+ */
+ writeStringTo8BitUnpacked: function(numOctets, str) {
+ const langTable = PDU_NL_LOCKING_SHIFT_TABLES[PDU_NL_IDENTIFIER_DEFAULT];
+ const langShiftTable = PDU_NL_SINGLE_SHIFT_TABLES[PDU_NL_IDENTIFIER_DEFAULT];
+
+ let GsmPDUHelper = this.context.GsmPDUHelper;
+
+ // If the character is GSM extended alphabet, two octets will be written.
+ // So we need to keep track of number of octets to be written.
+ let i, j;
+ let len = str ? str.length : 0;
+ for (i = 0, j = 0; i < len && j < numOctets; i++) {
+ let c = str.charAt(i);
+ let octet = langTable.indexOf(c);
+
+ if (octet == -1) {
+ // Make sure we still have enough space to write two octets.
+ if (j + 2 > numOctets) {
+ break;
+ }
+
+ octet = langShiftTable.indexOf(c);
+ if (octet == -1) {
+ // Fallback to ASCII space.
+ octet = langTable.indexOf(' ');
+ } else {
+ GsmPDUHelper.writeHexOctet(PDU_NL_EXTENDED_ESCAPE);
+ j++;
+ }
+ }
+ GsmPDUHelper.writeHexOctet(octet);
+ j++;
+ }
+
+ // trailing 0xff
+ while (j++ < numOctets) {
+ GsmPDUHelper.writeHexOctet(0xff);
+ }
+
+ return (str) ? str.substring(0, i) : "";
+ },
+
+ /**
+ * Write UCS2 String on UICC.
+ * The default choose 0x81 or 0x82 encode, otherwise use 0x80 encode.
+ *
+ * @see TS 102.221, Annex A.
+ * @param numOctets
+ * Total number of octets to be written. This includes the length of
+ * alphaId and the length of trailing unused octets(0xff).
+ * @param str
+ * String to be written.
+ *
+ * @return The string has been written into Buf.
+ */
+ writeICCUCS2String: function(numOctets, str) {
+ let GsmPDUHelper = this.context.GsmPDUHelper;
+ let scheme = 0x80;
+ let basePointer;
+
+ if (str.length > 2) {
+ let min = 0xFFFF;
+ let max = 0;
+ for (let i = 0; i < str.length; i++) {
+ let code = str.charCodeAt(i);
+ // filter out GSM Default Alphabet character
+ if (code & 0xFF80) {
+ if (min > code) {
+ min = code;
+ }
+ if (max < code) {
+ max = code;
+ }
+ }
+ }
+
+ // 0x81 and 0x82 only support 'half-page', i.e., 128 characters.
+ if ((max - min) >= 0 && (max - min) < 128) {
+ // 0x81 base pointer is 0hhh hhhh h000 0000, and bit 16 is set to zero,
+ // therefore it can't compute 0x8000~0xFFFF.
+ // Since 0x81 only support 128 characters,
+ // either XX00~XX7f(bit 8 are 0) or XX80~XXff(bit 8 are 1)
+ if (((min & 0x7f80) == (max & 0x7f80)) &&
+ ((max & 0x8000) == 0)) {
+ scheme = 0x81;
+ basePointer = min & 0x7f80;
+ } else {
+ scheme = 0x82;
+ basePointer = min;
+ }
+ }
+ }
+
+ switch (scheme) {
+ /**
+ * +------+---------+---------+---------+---------+------+------+
+ * | 0x80 | Ch1_msb | Ch1_lsb | Ch2_msb | Ch2_lsb | 0xff | 0xff |
+ * +------+---------+---------+---------+---------+------+------+
+ */
+ case 0x80: {
+ // 0x80 support UCS2 0000~ffff
+ GsmPDUHelper.writeHexOctet(0x80);
+ numOctets--;
+ // Now the str is UCS2 string, each character will take 2 octets.
+ if (str.length * 2 > numOctets) {
+ str = str.substring(0, Math.floor(numOctets / 2));
+ }
+ GsmPDUHelper.writeUCS2String(str);
+
+ // trailing 0xff
+ for (let i = str.length * 2; i < numOctets; i++) {
+ GsmPDUHelper.writeHexOctet(0xff);
+ }
+ return str;
+ }
+ /**
+ * +------+-----+--------------+-----+-----+-----+--------+------+
+ * | 0x81 | len | base_pointer | Ch1 | Ch2 | ... | Ch_len | 0xff |
+ * +------+-----+--------------+-----+-----+-----+--------+------+
+ *
+ * len: The length of characters.
+ * base_pointer: 0hhh hhhh h000 0000
+ * Ch_n: bit 8 = 0
+ * GSM default alphabets
+ * bit 8 = 1
+ * UCS2 character whose char code is (Ch_n - base_pointer) | 0x80
+ *
+ */
+ case 0x81: {
+ GsmPDUHelper.writeHexOctet(0x81);
+
+ if (str.length > (numOctets - 3)) {
+ str = str.substring(0, numOctets - 3);
+ }
+
+ GsmPDUHelper.writeHexOctet(str.length);
+ GsmPDUHelper.writeHexOctet((basePointer >> 7) & 0xff);
+ numOctets -= 3;
+ break;
+ }
+ /* +------+-----+------------------+------------------+-----+-----+-----+--------+
+ * | 0x82 | len | base_pointer_msb | base_pointer_lsb | Ch1 | Ch2 | ... | Ch_len |
+ * +------+-----+------------------+------------------+-----+-----+-----+--------+
+ *
+ * len: The length of characters.
+ * base_pointer_msb, base_pointer_lsn: base_pointer
+ * Ch_n: bit 8 = 0
+ * GSM default alphabets
+ * bit 8 = 1
+ * UCS2 character whose char code is (Ch_n - base_pointer) | 0x80
+ */
+ case 0x82: {
+ GsmPDUHelper.writeHexOctet(0x82);
+
+ if (str.length > (numOctets - 4)) {
+ str = str.substring(0, numOctets - 4);
+ }
+
+ GsmPDUHelper.writeHexOctet(str.length);
+ GsmPDUHelper.writeHexOctet((basePointer >> 8) & 0xff);
+ GsmPDUHelper.writeHexOctet(basePointer & 0xff);
+ numOctets -= 4;
+ break;
+ }
+ }
+
+ if (scheme == 0x81 || scheme == 0x82) {
+ for (let i = 0; i < str.length; i++) {
+ let code = str.charCodeAt(i);
+
+ // bit 8 = 0,
+ // GSM default alphabets
+ if (code >> 8 == 0) {
+ GsmPDUHelper.writeHexOctet(code & 0x7F);
+ } else {
+ // bit 8 = 1,
+ // UCS2 character whose char code is (code - basePointer) | 0x80
+ GsmPDUHelper.writeHexOctet((code - basePointer) | 0x80);
+ }
+ }
+
+ // trailing 0xff
+ for (let i = 0; i < numOctets - str.length; i++) {
+ GsmPDUHelper.writeHexOctet(0xff);
+ }
+ }
+ return str;
+ },
+
+ /**
+ * Read UCS2 String on UICC.
+ *
+ * @see TS 101.221, Annex A.
+ * @param scheme
+ * Coding scheme for UCS2 on UICC. One of 0x80, 0x81 or 0x82.
+ * @param numOctets
+ * Number of octets to be read as UCS2 string.
+ */
+ readICCUCS2String: function(scheme, numOctets) {
+ let Buf = this.context.Buf;
+ let GsmPDUHelper = this.context.GsmPDUHelper;
+
+ let str = "";
+ switch (scheme) {
+ /**
+ * +------+---------+---------+---------+---------+------+------+
+ * | 0x80 | Ch1_msb | Ch1_lsb | Ch2_msb | Ch2_lsb | 0xff | 0xff |
+ * +------+---------+---------+---------+---------+------+------+
+ */
+ case 0x80:
+ let isOdd = numOctets % 2;
+ let i;
+ for (i = 0; i < numOctets - isOdd; i += 2) {
+ let code = (GsmPDUHelper.readHexOctet() << 8) | GsmPDUHelper.readHexOctet();
+ if (code == 0xffff) {
+ i += 2;
+ break;
+ }
+ str += String.fromCharCode(code);
+ }
+
+ // Skip trailing 0xff
+ Buf.seekIncoming((numOctets - i) * Buf.PDU_HEX_OCTET_SIZE);
+ break;
+ case 0x81: // Fall through
+ case 0x82:
+ /**
+ * +------+-----+--------+-----+-----+-----+--------+------+
+ * | 0x81 | len | offset | Ch1 | Ch2 | ... | Ch_len | 0xff |
+ * +------+-----+--------+-----+-----+-----+--------+------+
+ *
+ * len : The length of characters.
+ * offset : 0hhh hhhh h000 0000
+ * Ch_n: bit 8 = 0
+ * GSM default alphabets
+ * bit 8 = 1
+ * UCS2 character whose char code is (Ch_n & 0x7f) + offset
+ *
+ * +------+-----+------------+------------+-----+-----+-----+--------+
+ * | 0x82 | len | offset_msb | offset_lsb | Ch1 | Ch2 | ... | Ch_len |
+ * +------+-----+------------+------------+-----+-----+-----+--------+
+ *
+ * len : The length of characters.
+ * offset_msb, offset_lsn: offset
+ * Ch_n: bit 8 = 0
+ * GSM default alphabets
+ * bit 8 = 1
+ * UCS2 character whose char code is (Ch_n & 0x7f) + offset
+ */
+ let len = GsmPDUHelper.readHexOctet();
+ let offset, headerLen;
+ if (scheme == 0x81) {
+ offset = GsmPDUHelper.readHexOctet() << 7;
+ headerLen = 2;
+ } else {
+ offset = (GsmPDUHelper.readHexOctet() << 8) | GsmPDUHelper.readHexOctet();
+ headerLen = 3;
+ }
+
+ for (let i = 0; i < len; i++) {
+ let ch = GsmPDUHelper.readHexOctet();
+ if (ch & 0x80) {
+ // UCS2
+ str += String.fromCharCode((ch & 0x7f) + offset);
+ } else {
+ // GSM 8bit
+ let count = 0, gotUCS2 = 0;
+ while ((i + count + 1 < len)) {
+ count++;
+ if (GsmPDUHelper.readHexOctet() & 0x80) {
+ gotUCS2 = 1;
+ break;
+ }
+ }
+ // Unread.
+ // +1 for the GSM alphabet indexed at i,
+ Buf.seekIncoming(-1 * (count + 1) * Buf.PDU_HEX_OCTET_SIZE);
+ str += this.read8BitUnpackedToString(count + 1 - gotUCS2);
+ i += count - gotUCS2;
+ }
+ }
+
+ // Skipping trailing 0xff
+ Buf.seekIncoming((numOctets - len - headerLen) * Buf.PDU_HEX_OCTET_SIZE);
+ break;
+ }
+ return str;
+ },
+
+ /**
+ * Read Alpha Id and Dialling number from TS TS 151.011 clause 10.5.1
+ *
+ * @param recordSize The size of linear fixed record.
+ */
+ readAlphaIdDiallingNumber: function(recordSize) {
+ let Buf = this.context.Buf;
+ let length = Buf.readInt32();
+
+ let alphaLen = recordSize - ADN_FOOTER_SIZE_BYTES;
+ let alphaId = this.readAlphaIdentifier(alphaLen);
+
+ let number = this.readNumberWithLength();
+
+ // Skip unused octet, CCP
+ Buf.seekIncoming(Buf.PDU_HEX_OCTET_SIZE);
+
+ let extRecordNumber = this.context.GsmPDUHelper.readHexOctet();
+ Buf.readStringDelimiter(length);
+
+ let contact = null;
+ if (alphaId || number) {
+ contact = {alphaId: alphaId,
+ number: number,
+ extRecordNumber: extRecordNumber};
+ }
+
+ return contact;
+ },
+
+ /**
+ * Write Alpha Identifier and Dialling number from TS 151.011 clause 10.5.1
+ *
+ * @param recordSize The size of linear fixed record.
+ * @param alphaId Alpha Identifier to be written.
+ * @param number Dialling Number to be written.
+ * @param extRecordNumber The record identifier of the EXT.
+ *
+ * @return An object contains the alphaId and number
+ * that have been written into Buf.
+ */
+ writeAlphaIdDiallingNumber: function(recordSize, alphaId, number, extRecordNumber) {
+ let Buf = this.context.Buf;
+ let GsmPDUHelper = this.context.GsmPDUHelper;
+
+ // Write String length
+ let strLen = recordSize * 2;
+ Buf.writeInt32(strLen);
+
+ let alphaLen = recordSize - ADN_FOOTER_SIZE_BYTES;
+ let writtenAlphaId = this.writeAlphaIdentifier(alphaLen, alphaId);
+ let writtenNumber = this.writeNumberWithLength(number);
+
+ // Write unused CCP octet 0xff.
+ GsmPDUHelper.writeHexOctet(0xff);
+ GsmPDUHelper.writeHexOctet((extRecordNumber != null) ? extRecordNumber : 0xff);
+
+ Buf.writeStringDelimiter(strLen);
+
+ return {alphaId: writtenAlphaId,
+ number: writtenNumber};
+ },
+
+ /**
+ * Read Alpha Identifier.
+ *
+ * @see TS 131.102
+ *
+ * @param numOctets
+ * Number of octets to be read.
+ *
+ * It uses either
+ * 1. SMS default 7-bit alphabet with bit 8 set to 0.
+ * 2. UCS2 string.
+ *
+ * Unused bytes should be set to 0xff.
+ */
+ readAlphaIdentifier: function(numOctets) {
+ if (numOctets === 0) {
+ return "";
+ }
+
+ let temp;
+ // Read the 1st octet to determine the encoding.
+ if ((temp = this.context.GsmPDUHelper.readHexOctet()) == 0x80 ||
+ temp == 0x81 ||
+ temp == 0x82) {
+ numOctets--;
+ return this.readICCUCS2String(temp, numOctets);
+ } else {
+ let Buf = this.context.Buf;
+ Buf.seekIncoming(-1 * Buf.PDU_HEX_OCTET_SIZE);
+ return this.read8BitUnpackedToString(numOctets);
+ }
+ },
+
+ /**
+ * Write Alpha Identifier.
+ *
+ * @param numOctets
+ * Total number of octets to be written. This includes the length of
+ * alphaId and the length of trailing unused octets(0xff).
+ * @param alphaId
+ * Alpha Identifier to be written.
+ *
+ * @return The Alpha Identifier has been written into Buf.
+ *
+ * Unused octets will be written as 0xff.
+ */
+ writeAlphaIdentifier: function(numOctets, alphaId) {
+ if (numOctets === 0) {
+ return "";
+ }
+
+ // If alphaId is empty or it's of GSM 8 bit.
+ if (!alphaId || this.context.ICCUtilsHelper.isGsm8BitAlphabet(alphaId)) {
+ return this.writeStringTo8BitUnpacked(numOctets, alphaId);
+ } else {
+ return this.writeICCUCS2String(numOctets, alphaId);
+ }
+ },
+
+ /**
+ * Read Dialling number.
+ *
+ * @see TS 131.102
+ *
+ * @param len
+ * The Length of BCD number.
+ *
+ * From TS 131.102, in EF_ADN, EF_FDN, the field 'Length of BCD number'
+ * means the total bytes should be allocated to store the TON/NPI and
+ * the dialing number.
+ * For example, if the dialing number is 1234567890,
+ * and the TON/NPI is 0x81,
+ * The field 'Length of BCD number' should be 06, which is
+ * 1 byte to store the TON/NPI, 0x81
+ * 5 bytes to store the BCD number 2143658709.
+ *
+ * Here the definition of the length is different from SMS spec,
+ * TS 23.040 9.1.2.5, which the length means
+ * "number of useful semi-octets within the Address-Value field".
+ */
+ readDiallingNumber: function(len) {
+ if (DEBUG) this.context.debug("PDU: Going to read Dialling number: " + len);
+ if (len === 0) {
+ return "";
+ }
+
+ let GsmPDUHelper = this.context.GsmPDUHelper;
+
+ // TOA = TON + NPI
+ let toa = GsmPDUHelper.readHexOctet();
+
+ let number = GsmPDUHelper.readSwappedNibbleExtendedBcdString(len - 1);
+ if (number.length <= 0) {
+ if (DEBUG) this.context.debug("No number provided");
+ return "";
+ }
+ if ((toa >> 4) == (PDU_TOA_INTERNATIONAL >> 4)) {
+ number = '+' + number;
+ }
+ return number;
+ },
+
+ /**
+ * Write Dialling Number.
+ *
+ * @param number The Dialling number
+ */
+ writeDiallingNumber: function(number) {
+ let GsmPDUHelper = this.context.GsmPDUHelper;
+
+ let toa = PDU_TOA_ISDN; // 81
+ if (number[0] == '+') {
+ toa = PDU_TOA_INTERNATIONAL | PDU_TOA_ISDN; // 91
+ number = number.substring(1);
+ }
+ GsmPDUHelper.writeHexOctet(toa);
+ GsmPDUHelper.writeSwappedNibbleBCD(number);
+ },
+
+ readNumberWithLength: function() {
+ let Buf = this.context.Buf;
+ let number = "";
+ let numLen = this.context.GsmPDUHelper.readHexOctet();
+ if (numLen != 0xff) {
+ if (numLen > ADN_MAX_BCD_NUMBER_BYTES) {
+ if (DEBUG) {
+ this.context.debug(
+ "Error: invalid length of BCD number/SSC contents - " + numLen);
+ }
+ Buf.seekIncoming(ADN_MAX_BCD_NUMBER_BYTES * Buf.PDU_HEX_OCTET_SIZE);
+ return number;
+ }
+
+ number = this.readDiallingNumber(numLen);
+ Buf.seekIncoming((ADN_MAX_BCD_NUMBER_BYTES - numLen) * Buf.PDU_HEX_OCTET_SIZE);
+ } else {
+ Buf.seekIncoming(ADN_MAX_BCD_NUMBER_BYTES * Buf.PDU_HEX_OCTET_SIZE);
+ }
+
+ return number;
+ },
+
+ /**
+ * Write Number with Length
+ *
+ * @param number The value to be written.
+ *
+ * @return The number has been written into Buf.
+ */
+ writeNumberWithLength: function(number) {
+ let GsmPDUHelper = this.context.GsmPDUHelper;
+
+ if (number) {
+ let numStart = number[0] == "+" ? 1 : 0;
+ let writtenNumber = number.substring(0, numStart) +
+ number.substring(numStart)
+ .replace(/[^0-9*#,]/g, "");
+ let numDigits = writtenNumber.length - numStart;
+
+ if (numDigits > ADN_MAX_NUMBER_DIGITS) {
+ writtenNumber = writtenNumber.substring(0, ADN_MAX_NUMBER_DIGITS + numStart);
+ numDigits = writtenNumber.length - numStart;
+ }
+
+ // +1 for TON/NPI
+ let numLen = Math.ceil(numDigits / 2) + 1;
+ GsmPDUHelper.writeHexOctet(numLen);
+ this.writeDiallingNumber(writtenNumber.replace(/\*/g, "a")
+ .replace(/\#/g, "b")
+ .replace(/\,/g, "c"));
+ // Write trailing 0xff of Dialling Number.
+ for (let i = 0; i < ADN_MAX_BCD_NUMBER_BYTES - numLen; i++) {
+ GsmPDUHelper.writeHexOctet(0xff);
+ }
+ return writtenNumber;
+ } else {
+ // +1 for numLen
+ for (let i = 0; i < ADN_MAX_BCD_NUMBER_BYTES + 1; i++) {
+ GsmPDUHelper.writeHexOctet(0xff);
+ }
+ return "";
+ }
+ }
+};
+
+function StkCommandParamsFactoryObject(aContext) {
+ this.context = aContext;
+}
+StkCommandParamsFactoryObject.prototype = {
+ context: null,
+
+ createParam: function(cmdDetails, ctlvs, onComplete) {
+ let method = this[cmdDetails.typeOfCommand];
+ if (typeof method != "function") {
+ if (DEBUG) {
+ this.context.debug("Unknown proactive command " +
+ cmdDetails.typeOfCommand.toString(16));
+ }
+ return;
+ }
+ method.call(this, cmdDetails, ctlvs, onComplete);
+ },
+
+ loadIcons: function(iconIdCtlvs, callback) {
+ if (!iconIdCtlvs ||
+ !this.context.ICCUtilsHelper.isICCServiceAvailable("IMG")) {
+ callback(null);
+ return;
+ }
+
+ let onerror = (function() {
+ callback(null);
+ }).bind(this);
+
+ let onsuccess = (function(aIcons) {
+ callback(aIcons);
+ }).bind(this);
+
+ this.context.IconLoader.loadIcons(iconIdCtlvs.map(aCtlv => aCtlv.value.identifier),
+ onsuccess,
+ onerror);
+ },
+
+ appendIconIfNecessary: function(iconIdCtlvs, result, onComplete) {
+ this.loadIcons(iconIdCtlvs, (aIcons) => {
+ if (aIcons) {
+ result.icons = aIcons[0];
+ result.iconSelfExplanatory =
+ iconIdCtlvs[0].value.qualifier == 0 ? true : false;
+ }
+
+ onComplete(result);
+ });
+ },
+
+ /**
+ * Construct a param for Refresh.
+ *
+ * @param cmdDetails
+ * The value object of CommandDetails TLV.
+ * @param ctlvs
+ * The all TLVs in this proactive command.
+ * @param onComplete
+ * Callback to be called when complete.
+ */
+ processRefresh: function(cmdDetails, ctlvs, onComplete) {
+ let refreshType = cmdDetails.commandQualifier;
+ switch (refreshType) {
+ case STK_REFRESH_FILE_CHANGE:
+ case STK_REFRESH_NAA_INIT_AND_FILE_CHANGE:
+ let ctlv = this.context.StkProactiveCmdHelper.searchForTag(
+ COMPREHENSIONTLV_TAG_FILE_LIST, ctlvs);
+ if (ctlv) {
+ let list = ctlv.value.fileList;
+ if (DEBUG) {
+ this.context.debug("Refresh, list = " + list);
+ }
+ this.context.ICCRecordHelper.fetchICCRecords();
+ }
+ break;
+ }
+
+ onComplete(null);
+ },
+
+ /**
+ * Construct a param for Poll Interval.
+ *
+ * @param cmdDetails
+ * The value object of CommandDetails TLV.
+ * @param ctlvs
+ * The all TLVs in this proactive command.
+ * @param onComplete
+ * Callback to be called when complete.
+ */
+ processPollInterval: function(cmdDetails, ctlvs, onComplete) {
+ // Duration is mandatory.
+ let ctlv = this.context.StkProactiveCmdHelper.searchForTag(
+ COMPREHENSIONTLV_TAG_DURATION, ctlvs);
+ if (!ctlv) {
+ this.context.RIL.sendStkTerminalResponse({
+ command: cmdDetails,
+ resultCode: STK_RESULT_REQUIRED_VALUES_MISSING});
+ throw new Error("Stk Poll Interval: Required value missing : Duration");
+ }
+
+ onComplete(ctlv.value);
+ },
+
+ /**
+ * Construct a param for Poll Off.
+ *
+ * @param cmdDetails
+ * The value object of CommandDetails TLV.
+ * @param ctlvs
+ * The all TLVs in this proactive command.
+ * @param onComplete
+ * Callback to be called when complete.
+ */
+ processPollOff: function(cmdDetails, ctlvs, onComplete) {
+ onComplete(null);
+ },
+
+ /**
+ * Construct a param for Set Up Event list.
+ *
+ * @param cmdDetails
+ * The value object of CommandDetails TLV.
+ * @param ctlvs
+ * The all TLVs in this proactive command.
+ * @param onComplete
+ * Callback to be called when complete.
+ */
+ processSetUpEventList: function(cmdDetails, ctlvs, onComplete) {
+ // Event list is mandatory.
+ let ctlv = this.context.StkProactiveCmdHelper.searchForTag(
+ COMPREHENSIONTLV_TAG_EVENT_LIST, ctlvs);
+ if (!ctlv) {
+ this.context.RIL.sendStkTerminalResponse({
+ command: cmdDetails,
+ resultCode: STK_RESULT_REQUIRED_VALUES_MISSING});
+ throw new Error("Stk Event List: Required value missing : Event List");
+ }
+
+ onComplete(ctlv.value || { eventList: null });
+ },
+
+ /**
+ * Construct a param for Setup Menu.
+ *
+ * @param cmdDetails
+ * The value object of CommandDetails TLV.
+ * @param ctlvs
+ * The all TLVs in this proactive command.
+ * @param onComplete
+ * Callback to be called when complete.
+ */
+ processSetupMenu: function(cmdDetails, ctlvs, onComplete) {
+ let StkProactiveCmdHelper = this.context.StkProactiveCmdHelper;
+ let menu = {
+ // Help information available.
+ isHelpAvailable: !!(cmdDetails.commandQualifier & 0x80)
+ };
+
+ let selectedCtlvs = StkProactiveCmdHelper.searchForSelectedTags(ctlvs, [
+ COMPREHENSIONTLV_TAG_ALPHA_ID,
+ COMPREHENSIONTLV_TAG_ITEM,
+ COMPREHENSIONTLV_TAG_ITEM_ID,
+ COMPREHENSIONTLV_TAG_NEXT_ACTION_IND,
+ COMPREHENSIONTLV_TAG_ICON_ID,
+ COMPREHENSIONTLV_TAG_ICON_ID_LIST
+ ]);
+
+ // Alpha identifier is optional.
+ let ctlv = selectedCtlvs.retrieve(COMPREHENSIONTLV_TAG_ALPHA_ID);
+ if (ctlv) {
+ menu.title = ctlv.value.identifier;
+ }
+
+ // Item data object for item 1 is mandatory.
+ let menuCtlvs = selectedCtlvs[COMPREHENSIONTLV_TAG_ITEM];
+ if (!menuCtlvs) {
+ this.context.RIL.sendStkTerminalResponse({
+ command: cmdDetails,
+ resultCode: STK_RESULT_REQUIRED_VALUES_MISSING});
+ throw new Error("Stk Menu: Required value missing : items");
+ }
+ menu.items = menuCtlvs.map(aCtlv => aCtlv.value);
+
+ // Item identifier is optional.
+ ctlv = selectedCtlvs.retrieve(COMPREHENSIONTLV_TAG_ITEM_ID);
+ if (ctlv) {
+ menu.defaultItem = ctlv.value.identifier - 1;
+ }
+
+ // Items next action indicator is optional.
+ ctlv = selectedCtlvs.retrieve(COMPREHENSIONTLV_TAG_NEXT_ACTION_IND);
+ if (ctlv) {
+ menu.nextActionList = ctlv.value;
+ }
+
+ // Icon identifier is optional.
+ let iconIdCtlvs = null;
+ let menuIconCtlv = selectedCtlvs.retrieve(COMPREHENSIONTLV_TAG_ICON_ID);
+ if (menuIconCtlv) {
+ iconIdCtlvs = [menuIconCtlv];
+ }
+
+ // Item icon identifier list is optional.
+ ctlv = selectedCtlvs.retrieve(COMPREHENSIONTLV_TAG_ICON_ID_LIST);
+ if (ctlv) {
+ if (!iconIdCtlvs) {
+ iconIdCtlvs = [];
+ };
+ let iconIdList = ctlv.value;
+ iconIdCtlvs = iconIdCtlvs.concat(iconIdList.identifiers.map((aId) => {
+ return {
+ value: { qualifier: iconIdList.qualifier, identifier: aId }
+ };
+ }));
+ }
+
+ this.loadIcons(iconIdCtlvs, (aIcons) => {
+ if (aIcons) {
+ if (menuIconCtlv) {
+ menu.iconSelfExplanatory =
+ (iconIdCtlvs.shift().value.qualifier == 0) ? true: false;
+ menu.icons = aIcons.shift();
+ }
+
+ for (let i = 0; i < aIcons.length; i++) {
+ menu.items[i].icons = aIcons[i];
+ menu.items[i].iconSelfExplanatory =
+ (iconIdCtlvs[i].value.qualifier == 0) ? true: false;
+ }
+ }
+
+ onComplete(menu);
+ });
+ },
+
+ /**
+ * Construct a param for Select Item.
+ *
+ * @param cmdDetails
+ * The value object of CommandDetails TLV.
+ * @param ctlvs
+ * The all TLVs in this proactive command.
+ * @param onComplete
+ * Callback to be called when complete.
+ */
+ processSelectItem: function(cmdDetails, ctlvs, onComplete) {
+ this.processSetupMenu(cmdDetails, ctlvs, (menu) => {
+ // The 1st bit and 2nd bit determines the presentation type.
+ menu.presentationType = cmdDetails.commandQualifier & 0x03;
+ onComplete(menu);
+ });
+ },
+
+ /**
+ * Construct a param for Display Text.
+ *
+ * @param cmdDetails
+ * The value object of CommandDetails TLV.
+ * @param ctlvs
+ * The all TLVs in this proactive command.
+ * @param onComplete
+ * Callback to be called when complete.
+ */
+ processDisplayText: function(cmdDetails, ctlvs, onComplete) {
+ let StkProactiveCmdHelper = this.context.StkProactiveCmdHelper;
+ let textMsg = {
+ isHighPriority: !!(cmdDetails.commandQualifier & 0x01),
+ userClear: !!(cmdDetails.commandQualifier & 0x80)
+ };
+
+ let selectedCtlvs = StkProactiveCmdHelper.searchForSelectedTags(ctlvs, [
+ COMPREHENSIONTLV_TAG_TEXT_STRING,
+ COMPREHENSIONTLV_TAG_IMMEDIATE_RESPONSE,
+ COMPREHENSIONTLV_TAG_DURATION,
+ COMPREHENSIONTLV_TAG_ICON_ID
+ ]);
+
+ // Text string is mandatory.
+ let ctlv = selectedCtlvs.retrieve(COMPREHENSIONTLV_TAG_TEXT_STRING);
+ if (!ctlv) {
+ this.context.RIL.sendStkTerminalResponse({
+ command: cmdDetails,
+ resultCode: STK_RESULT_REQUIRED_VALUES_MISSING});
+ throw new Error("Stk Display Text: Required value missing : Text String");
+ }
+ textMsg.text = ctlv.value.textString;
+
+ // Immediate response is optional.
+ textMsg.responseNeeded =
+ !!(selectedCtlvs.retrieve(COMPREHENSIONTLV_TAG_IMMEDIATE_RESPONSE));
+
+ // Duration is optional.
+ ctlv = selectedCtlvs.retrieve(COMPREHENSIONTLV_TAG_DURATION);
+ if (ctlv) {
+ textMsg.duration = ctlv.value;
+ }
+
+ // Icon identifier is optional.
+ this.appendIconIfNecessary(selectedCtlvs[COMPREHENSIONTLV_TAG_ICON_ID] || null,
+ textMsg,
+ onComplete);
+ },
+
+ /**
+ * Construct a param for Setup Idle Mode Text.
+ *
+ * @param cmdDetails
+ * The value object of CommandDetails TLV.
+ * @param ctlvs
+ * The all TLVs in this proactive command.
+ * @param onComplete
+ * Callback to be called when complete.
+ */
+ processSetUpIdleModeText: function(cmdDetails, ctlvs, onComplete) {
+ let StkProactiveCmdHelper = this.context.StkProactiveCmdHelper;
+ let textMsg = {};
+
+ let selectedCtlvs = StkProactiveCmdHelper.searchForSelectedTags(ctlvs, [
+ COMPREHENSIONTLV_TAG_TEXT_STRING,
+ COMPREHENSIONTLV_TAG_ICON_ID
+ ]);
+
+ // Text string is mandatory.
+ let ctlv = selectedCtlvs.retrieve(COMPREHENSIONTLV_TAG_TEXT_STRING);
+ if (!ctlv) {
+ this.context.RIL.sendStkTerminalResponse({
+ command: cmdDetails,
+ resultCode: STK_RESULT_REQUIRED_VALUES_MISSING});
+ throw new Error("Stk Set Up Idle Text: Required value missing : Text String");
+ }
+ textMsg.text = ctlv.value.textString;
+
+ // Icon identifier is optional.
+ this.appendIconIfNecessary(selectedCtlvs[COMPREHENSIONTLV_TAG_ICON_ID] || null,
+ textMsg,
+ onComplete);
+ },
+
+ /**
+ * Construct a param for Get Inkey.
+ *
+ * @param cmdDetails
+ * The value object of CommandDetails TLV.
+ * @param ctlvs
+ * The all TLVs in this proactive command.
+ * @param onComplete
+ * Callback to be called when complete.
+ */
+ processGetInkey: function(cmdDetails, ctlvs, onComplete) {
+ let StkProactiveCmdHelper = this.context.StkProactiveCmdHelper;
+ let input = {
+ minLength: 1,
+ maxLength: 1,
+ isAlphabet: !!(cmdDetails.commandQualifier & 0x01),
+ isUCS2: !!(cmdDetails.commandQualifier & 0x02),
+ // Character sets defined in bit 1 and bit 2 are disable and
+ // the YES/NO reponse is required.
+ isYesNoRequested: !!(cmdDetails.commandQualifier & 0x04),
+ // Help information available.
+ isHelpAvailable: !!(cmdDetails.commandQualifier & 0x80)
+ };
+
+ let selectedCtlvs = StkProactiveCmdHelper.searchForSelectedTags(ctlvs, [
+ COMPREHENSIONTLV_TAG_TEXT_STRING,
+ COMPREHENSIONTLV_TAG_DURATION,
+ COMPREHENSIONTLV_TAG_ICON_ID
+ ]);
+
+ // Text string is mandatory.
+ let ctlv = selectedCtlvs.retrieve(COMPREHENSIONTLV_TAG_TEXT_STRING);
+ if (!ctlv) {
+ this.context.RIL.sendStkTerminalResponse({
+ command: cmdDetails,
+ resultCode: STK_RESULT_REQUIRED_VALUES_MISSING});
+ throw new Error("Stk Get InKey: Required value missing : Text String");
+ }
+ input.text = ctlv.value.textString;
+
+ // Duration is optional.
+ ctlv = selectedCtlvs.retrieve(COMPREHENSIONTLV_TAG_DURATION);
+ if (ctlv) {
+ input.duration = ctlv.value;
+ }
+
+ // Icon identifier is optional.
+ this.appendIconIfNecessary(selectedCtlvs[COMPREHENSIONTLV_TAG_ICON_ID] || null,
+ input,
+ onComplete);
+ },
+
+ /**
+ * Construct a param for Get Input.
+ *
+ * @param cmdDetails
+ * The value object of CommandDetails TLV.
+ * @param ctlvs
+ * The all TLVs in this proactive command.
+ * @param onComplete
+ * Callback to be called when complete.
+ */
+ processGetInput: function(cmdDetails, ctlvs, onComplete) {
+ let StkProactiveCmdHelper = this.context.StkProactiveCmdHelper;
+ let input = {
+ isAlphabet: !!(cmdDetails.commandQualifier & 0x01),
+ isUCS2: !!(cmdDetails.commandQualifier & 0x02),
+ // User input shall not be revealed
+ hideInput: !!(cmdDetails.commandQualifier & 0x04),
+ // User input in SMS packed format
+ isPacked: !!(cmdDetails.commandQualifier & 0x08),
+ // Help information available.
+ isHelpAvailable: !!(cmdDetails.commandQualifier & 0x80)
+ };
+
+ let selectedCtlvs = StkProactiveCmdHelper.searchForSelectedTags(ctlvs, [
+ COMPREHENSIONTLV_TAG_TEXT_STRING,
+ COMPREHENSIONTLV_TAG_RESPONSE_LENGTH,
+ COMPREHENSIONTLV_TAG_DEFAULT_TEXT,
+ COMPREHENSIONTLV_TAG_ICON_ID
+ ]);
+
+ // Text string is mandatory.
+ let ctlv = selectedCtlvs.retrieve(COMPREHENSIONTLV_TAG_TEXT_STRING);
+ if (!ctlv) {
+ this.context.RIL.sendStkTerminalResponse({
+ command: cmdDetails,
+ resultCode: STK_RESULT_REQUIRED_VALUES_MISSING});
+ throw new Error("Stk Get Input: Required value missing : Text String");
+ }
+ input.text = ctlv.value.textString;
+
+ // Response length is mandatory.
+ ctlv = selectedCtlvs.retrieve(COMPREHENSIONTLV_TAG_RESPONSE_LENGTH);
+ if (!ctlv) {
+ this.context.RIL.sendStkTerminalResponse({
+ command: cmdDetails,
+ resultCode: STK_RESULT_REQUIRED_VALUES_MISSING});
+ throw new Error("Stk Get Input: Required value missing : Response Length");
+ }
+ input.minLength = ctlv.value.minLength;
+ input.maxLength = ctlv.value.maxLength;
+
+ // Default text is optional.
+ ctlv = selectedCtlvs.retrieve(COMPREHENSIONTLV_TAG_DEFAULT_TEXT);
+ if (ctlv) {
+ input.defaultText = ctlv.value.textString;
+ }
+
+ // Icon identifier is optional.
+ this.appendIconIfNecessary(selectedCtlvs[COMPREHENSIONTLV_TAG_ICON_ID] || null,
+ input,
+ onComplete);
+ },
+
+ /**
+ * Construct a param for SendSS/SMS/USSD/DTMF.
+ *
+ * @param cmdDetails
+ * The value object of CommandDetails TLV.
+ * @param ctlvs
+ * The all TLVs in this proactive command.
+ * @param onComplete
+ * Callback to be called when complete.
+ */
+ processEventNotify: function(cmdDetails, ctlvs, onComplete) {
+ let StkProactiveCmdHelper = this.context.StkProactiveCmdHelper;
+ let textMsg = {};
+
+ let selectedCtlvs = StkProactiveCmdHelper.searchForSelectedTags(ctlvs, [
+ COMPREHENSIONTLV_TAG_ALPHA_ID,
+ COMPREHENSIONTLV_TAG_ICON_ID
+ ]);
+
+ // Alpha identifier is optional.
+ let ctlv = selectedCtlvs.retrieve(COMPREHENSIONTLV_TAG_ALPHA_ID);
+ if (ctlv) {
+ textMsg.text = ctlv.value.identifier;
+ }
+
+ // According to section 6.4.10 of |ETSI TS 102 223|:
+ //
+ // - if the alpha identifier is provided by the UICC and is a null data
+ // object (i.e. length = '00' and no value part), this is an indication
+ // that the terminal should not give any information to the user on the
+ // fact that the terminal is sending a short message;
+ //
+ // - if the alpha identifier is not provided by the UICC, the terminal may
+ // give information to the user concerning what is happening.
+ //
+ // ICCPDUHelper reads alpha id as an empty string if the length is zero,
+ // hence we'll notify the caller when it's not an empty string.
+ if (textMsg.text !== "") {
+ // Icon identifier is optional.
+ this.appendIconIfNecessary(selectedCtlvs[COMPREHENSIONTLV_TAG_ICON_ID] || null,
+ textMsg,
+ onComplete);
+ }
+ },
+
+ /**
+ * Construct a param for Setup Call.
+ *
+ * @param cmdDetails
+ * The value object of CommandDetails TLV.
+ * @param ctlvs
+ * The all TLVs in this proactive command.
+ * @param onComplete
+ * Callback to be called when complete.
+ */
+ processSetupCall: function(cmdDetails, ctlvs, onComplete) {
+ let StkProactiveCmdHelper = this.context.StkProactiveCmdHelper;
+ let call = {};
+ let confirmMessage = {};
+ let callMessage = {};
+
+ let selectedCtlvs = StkProactiveCmdHelper.searchForSelectedTags(ctlvs, [
+ COMPREHENSIONTLV_TAG_ADDRESS,
+ COMPREHENSIONTLV_TAG_ALPHA_ID,
+ COMPREHENSIONTLV_TAG_ICON_ID,
+ COMPREHENSIONTLV_TAG_DURATION
+ ]);
+
+ // Address is mandatory.
+ let ctlv = selectedCtlvs.retrieve(COMPREHENSIONTLV_TAG_ADDRESS);
+ if (!ctlv) {
+ this.context.RIL.sendStkTerminalResponse({
+ command: cmdDetails,
+ resultCode: STK_RESULT_REQUIRED_VALUES_MISSING});
+ throw new Error("Stk Set Up Call: Required value missing : Address");
+ }
+ call.address = ctlv.value.number;
+
+ // Alpha identifier (user confirmation phase) is optional.
+ ctlv = selectedCtlvs.retrieve(COMPREHENSIONTLV_TAG_ALPHA_ID);
+ if (ctlv) {
+ confirmMessage.text = ctlv.value.identifier;
+ call.confirmMessage = confirmMessage;
+ }
+
+ // Alpha identifier (call set up phase) is optional.
+ ctlv = selectedCtlvs.retrieve(COMPREHENSIONTLV_TAG_ALPHA_ID);
+ if (ctlv) {
+ callMessage.text = ctlv.value.identifier;
+ call.callMessage = callMessage;
+ }
+
+ // Duration is optional.
+ ctlv = selectedCtlvs.retrieve(COMPREHENSIONTLV_TAG_DURATION);
+ if (ctlv) {
+ call.duration = ctlv.value;
+ }
+
+ // Icon identifier is optional.
+ let iconIdCtlvs = selectedCtlvs[COMPREHENSIONTLV_TAG_ICON_ID] || null;
+ this.loadIcons(iconIdCtlvs, (aIcons) => {
+ if (aIcons) {
+ confirmMessage.icons = aIcons[0];
+ confirmMessage.iconSelfExplanatory =
+ (iconIdCtlvs[0].value.qualifier == 0) ? true: false;
+ call.confirmMessage = confirmMessage;
+
+ if (aIcons.length > 1) {
+ callMessage.icons = aIcons[1];
+ callMessage.iconSelfExplanatory =
+ (iconIdCtlvs[1].value.qualifier == 0) ? true: false;
+ call.callMessage = callMessage;
+ }
+ }
+
+ onComplete(call);
+ });
+ },
+
+ /**
+ * Construct a param for Launch Browser.
+ *
+ * @param cmdDetails
+ * The value object of CommandDetails TLV.
+ * @param ctlvs
+ * The all TLVs in this proactive command.
+ * @param onComplete
+ * Callback to be called when complete.
+ */
+ processLaunchBrowser: function(cmdDetails, ctlvs, onComplete) {
+ let StkProactiveCmdHelper = this.context.StkProactiveCmdHelper;
+ let browser = {
+ mode: cmdDetails.commandQualifier & 0x03
+ };
+ let confirmMessage = {};
+
+ let selectedCtlvs = StkProactiveCmdHelper.searchForSelectedTags(ctlvs, [
+ COMPREHENSIONTLV_TAG_URL,
+ COMPREHENSIONTLV_TAG_ALPHA_ID,
+ COMPREHENSIONTLV_TAG_ICON_ID
+ ]);
+
+ // URL is mandatory.
+ let ctlv = selectedCtlvs.retrieve(COMPREHENSIONTLV_TAG_URL);
+ if (!ctlv) {
+ this.context.RIL.sendStkTerminalResponse({
+ command: cmdDetails,
+ resultCode: STK_RESULT_REQUIRED_VALUES_MISSING});
+ throw new Error("Stk Launch Browser: Required value missing : URL");
+ }
+ browser.url = ctlv.value.url;
+
+ // Alpha identifier is optional.
+ ctlv = selectedCtlvs.retrieve(COMPREHENSIONTLV_TAG_ALPHA_ID);
+ if (ctlv) {
+ confirmMessage.text = ctlv.value.identifier;
+ browser.confirmMessage = confirmMessage;
+ }
+
+ // Icon identifier is optional.
+ let iconIdCtlvs = selectedCtlvs[COMPREHENSIONTLV_TAG_ICON_ID] || null;
+ this.loadIcons(iconIdCtlvs, (aIcons) => {
+ if (aIcons) {
+ confirmMessage.icons = aIcons[0];
+ confirmMessage.iconSelfExplanatory =
+ (iconIdCtlvs[0].value.qualifier == 0) ? true: false;
+ browser.confirmMessage = confirmMessage;
+ }
+
+ onComplete(browser);
+ });
+ },
+
+ /**
+ * Construct a param for Play Tone.
+ *
+ * @param cmdDetails
+ * The value object of CommandDetails TLV.
+ * @param ctlvs
+ * The all TLVs in this proactive command.
+ * @param onComplete
+ * Callback to be called when complete.
+ */
+ processPlayTone: function(cmdDetails, ctlvs, onComplete) {
+ let StkProactiveCmdHelper = this.context.StkProactiveCmdHelper;
+ let playTone = {
+ // The vibrate is only defined in TS 102.223.
+ isVibrate: !!(cmdDetails.commandQualifier & 0x01)
+ };
+
+ let selectedCtlvs = StkProactiveCmdHelper.searchForSelectedTags(ctlvs, [
+ COMPREHENSIONTLV_TAG_ALPHA_ID,
+ COMPREHENSIONTLV_TAG_TONE,
+ COMPREHENSIONTLV_TAG_DURATION,
+ COMPREHENSIONTLV_TAG_ICON_ID
+ ]);
+
+ // Alpha identifier is optional.
+ let ctlv = selectedCtlvs.retrieve(COMPREHENSIONTLV_TAG_ALPHA_ID);
+ if (ctlv) {
+ playTone.text = ctlv.value.identifier;
+ }
+
+ // Tone is optional.
+ ctlv = selectedCtlvs.retrieve(COMPREHENSIONTLV_TAG_TONE);
+ if (ctlv) {
+ playTone.tone = ctlv.value.tone;
+ }
+
+ // Duration is optional.
+ ctlv = selectedCtlvs.retrieve(COMPREHENSIONTLV_TAG_DURATION);
+ if (ctlv) {
+ playTone.duration = ctlv.value;
+ }
+
+ // Icon identifier is optional.
+ this.appendIconIfNecessary(selectedCtlvs[COMPREHENSIONTLV_TAG_ICON_ID] || null,
+ playTone,
+ onComplete);
+ },
+
+ /**
+ * Construct a param for Provide Local Information.
+ *
+ * @param cmdDetails
+ * The value object of CommandDetails TLV.
+ * @param ctlvs
+ * The all TLVs in this proactive command.
+ * @param onComplete
+ * Callback to be called when complete.
+ */
+ processProvideLocalInfo: function(cmdDetails, ctlvs, onComplete) {
+ let provideLocalInfo = {
+ localInfoType: cmdDetails.commandQualifier
+ };
+
+ onComplete(provideLocalInfo);
+ },
+
+ /**
+ * Construct a param for Timer Management.
+ *
+ * @param cmdDetails
+ * The value object of CommandDetails TLV.
+ * @param ctlvs
+ * The all TLVs in this proactive command.
+ * @param onComplete
+ * Callback to be called when complete.
+ */
+ processTimerManagement: function(cmdDetails, ctlvs, onComplete) {
+ let StkProactiveCmdHelper = this.context.StkProactiveCmdHelper;
+ let timer = {
+ timerAction: cmdDetails.commandQualifier
+ };
+
+ let selectedCtlvs = StkProactiveCmdHelper.searchForSelectedTags(ctlvs, [
+ COMPREHENSIONTLV_TAG_TIMER_IDENTIFIER,
+ COMPREHENSIONTLV_TAG_TIMER_VALUE
+ ]);
+
+ // Timer identifier is mandatory.
+ let ctlv = selectedCtlvs.retrieve(COMPREHENSIONTLV_TAG_TIMER_IDENTIFIER);
+ if (!ctlv) {
+ this.context.RIL.sendStkTerminalResponse({
+ command: cmdDetails,
+ resultCode: STK_RESULT_REQUIRED_VALUES_MISSING});
+ throw new Error("Stk Timer Management: Required value missing : Timer Identifier");
+ }
+ timer.timerId = ctlv.value.timerId;
+
+ // Timer value is conditional.
+ ctlv = selectedCtlvs.retrieve(COMPREHENSIONTLV_TAG_TIMER_VALUE);
+ if (ctlv) {
+ timer.timerValue = ctlv.value.timerValue;
+ }
+
+ onComplete(timer);
+ },
+
+ /**
+ * Construct a param for BIP commands.
+ *
+ * @param cmdDetails
+ * The value object of CommandDetails TLV.
+ * @param ctlvs
+ * The all TLVs in this proactive command.
+ * @param onComplete
+ * Callback to be called when complete.
+ */
+ processBipMessage: function(cmdDetails, ctlvs, onComplete) {
+ let StkProactiveCmdHelper = this.context.StkProactiveCmdHelper;
+ let bipMsg = {};
+
+ let selectedCtlvs = StkProactiveCmdHelper.searchForSelectedTags(ctlvs, [
+ COMPREHENSIONTLV_TAG_ALPHA_ID,
+ COMPREHENSIONTLV_TAG_ICON_ID
+ ]);
+
+ // Alpha identifier is optional.
+ let ctlv = selectedCtlvs.retrieve(COMPREHENSIONTLV_TAG_ALPHA_ID);
+ if (ctlv) {
+ bipMsg.text = ctlv.value.identifier;
+ }
+
+ // Icon identifier is optional.
+ this.appendIconIfNecessary(selectedCtlvs[COMPREHENSIONTLV_TAG_ICON_ID] || null,
+ bipMsg,
+ onComplete);
+ }
+};
+StkCommandParamsFactoryObject.prototype[STK_CMD_REFRESH] = function STK_CMD_REFRESH(cmdDetails, ctlvs, onComplete) {
+ return this.processRefresh(cmdDetails, ctlvs, onComplete);
+};
+StkCommandParamsFactoryObject.prototype[STK_CMD_POLL_INTERVAL] = function STK_CMD_POLL_INTERVAL(cmdDetails, ctlvs, onComplete) {
+ return this.processPollInterval(cmdDetails, ctlvs, onComplete);
+};
+StkCommandParamsFactoryObject.prototype[STK_CMD_POLL_OFF] = function STK_CMD_POLL_OFF(cmdDetails, ctlvs, onComplete) {
+ return this.processPollOff(cmdDetails, ctlvs, onComplete);
+};
+StkCommandParamsFactoryObject.prototype[STK_CMD_PROVIDE_LOCAL_INFO] = function STK_CMD_PROVIDE_LOCAL_INFO(cmdDetails, ctlvs, onComplete) {
+ return this.processProvideLocalInfo(cmdDetails, ctlvs, onComplete);
+};
+StkCommandParamsFactoryObject.prototype[STK_CMD_SET_UP_EVENT_LIST] = function STK_CMD_SET_UP_EVENT_LIST(cmdDetails, ctlvs, onComplete) {
+ return this.processSetUpEventList(cmdDetails, ctlvs, onComplete);
+};
+StkCommandParamsFactoryObject.prototype[STK_CMD_SET_UP_MENU] = function STK_CMD_SET_UP_MENU(cmdDetails, ctlvs, onComplete) {
+ return this.processSetupMenu(cmdDetails, ctlvs, onComplete);
+};
+StkCommandParamsFactoryObject.prototype[STK_CMD_SELECT_ITEM] = function STK_CMD_SELECT_ITEM(cmdDetails, ctlvs, onComplete) {
+ return this.processSelectItem(cmdDetails, ctlvs, onComplete);
+};
+StkCommandParamsFactoryObject.prototype[STK_CMD_DISPLAY_TEXT] = function STK_CMD_DISPLAY_TEXT(cmdDetails, ctlvs, onComplete) {
+ return this.processDisplayText(cmdDetails, ctlvs, onComplete);
+};
+StkCommandParamsFactoryObject.prototype[STK_CMD_SET_UP_IDLE_MODE_TEXT] = function STK_CMD_SET_UP_IDLE_MODE_TEXT(cmdDetails, ctlvs, onComplete) {
+ return this.processSetUpIdleModeText(cmdDetails, ctlvs, onComplete);
+};
+StkCommandParamsFactoryObject.prototype[STK_CMD_GET_INKEY] = function STK_CMD_GET_INKEY(cmdDetails, ctlvs, onComplete) {
+ return this.processGetInkey(cmdDetails, ctlvs, onComplete);
+};
+StkCommandParamsFactoryObject.prototype[STK_CMD_GET_INPUT] = function STK_CMD_GET_INPUT(cmdDetails, ctlvs, onComplete) {
+ return this.processGetInput(cmdDetails, ctlvs, onComplete);
+};
+StkCommandParamsFactoryObject.prototype[STK_CMD_SEND_SS] = function STK_CMD_SEND_SS(cmdDetails, ctlvs, onComplete) {
+ return this.processEventNotify(cmdDetails, ctlvs, onComplete);
+};
+StkCommandParamsFactoryObject.prototype[STK_CMD_SEND_USSD] = function STK_CMD_SEND_USSD(cmdDetails, ctlvs, onComplete) {
+ return this.processEventNotify(cmdDetails, ctlvs, onComplete);
+};
+StkCommandParamsFactoryObject.prototype[STK_CMD_SEND_SMS] = function STK_CMD_SEND_SMS(cmdDetails, ctlvs, onComplete) {
+ return this.processEventNotify(cmdDetails, ctlvs, onComplete);
+};
+StkCommandParamsFactoryObject.prototype[STK_CMD_SEND_DTMF] = function STK_CMD_SEND_DTMF(cmdDetails, ctlvs, onComplete) {
+ return this.processEventNotify(cmdDetails, ctlvs, onComplete);
+};
+StkCommandParamsFactoryObject.prototype[STK_CMD_SET_UP_CALL] = function STK_CMD_SET_UP_CALL(cmdDetails, ctlvs, onComplete) {
+ return this.processSetupCall(cmdDetails, ctlvs, onComplete);
+};
+StkCommandParamsFactoryObject.prototype[STK_CMD_LAUNCH_BROWSER] = function STK_CMD_LAUNCH_BROWSER(cmdDetails, ctlvs, onComplete) {
+ return this.processLaunchBrowser(cmdDetails, ctlvs, onComplete);
+};
+StkCommandParamsFactoryObject.prototype[STK_CMD_PLAY_TONE] = function STK_CMD_PLAY_TONE(cmdDetails, ctlvs, onComplete) {
+ return this.processPlayTone(cmdDetails, ctlvs, onComplete);
+};
+StkCommandParamsFactoryObject.prototype[STK_CMD_TIMER_MANAGEMENT] = function STK_CMD_TIMER_MANAGEMENT(cmdDetails, ctlvs, onComplete) {
+ return this.processTimerManagement(cmdDetails, ctlvs, onComplete);
+};
+StkCommandParamsFactoryObject.prototype[STK_CMD_OPEN_CHANNEL] = function STK_CMD_OPEN_CHANNEL(cmdDetails, ctlvs, onComplete) {
+ return this.processBipMessage(cmdDetails, ctlvs, onComplete);
+};
+StkCommandParamsFactoryObject.prototype[STK_CMD_CLOSE_CHANNEL] = function STK_CMD_CLOSE_CHANNEL(cmdDetails, ctlvs, onComplete) {
+ return this.processBipMessage(cmdDetails, ctlvs, onComplete);
+};
+StkCommandParamsFactoryObject.prototype[STK_CMD_RECEIVE_DATA] = function STK_CMD_RECEIVE_DATA(cmdDetails, ctlvs, onComplete) {
+ return this.processBipMessage(cmdDetails, ctlvs, onComplete);
+};
+StkCommandParamsFactoryObject.prototype[STK_CMD_SEND_DATA] = function STK_CMD_SEND_DATA(cmdDetails, ctlvs, onComplete) {
+ return this.processBipMessage(cmdDetails, ctlvs, onComplete);
+};
+
+function StkProactiveCmdHelperObject(aContext) {
+ this.context = aContext;
+}
+StkProactiveCmdHelperObject.prototype = {
+ context: null,
+
+ retrieve: function(tag, length) {
+ let method = this[tag];
+ if (typeof method != "function") {
+ if (DEBUG) {
+ this.context.debug("Unknown comprehension tag " + tag.toString(16));
+ }
+ let Buf = this.context.Buf;
+ Buf.seekIncoming(length * Buf.PDU_HEX_OCTET_SIZE);
+ return null;
+ }
+ return method.call(this, length);
+ },
+
+ /**
+ * Command Details.
+ *
+ * | Byte | Description | Length |
+ * | 1 | Command details Tag | 1 |
+ * | 2 | Length = 03 | 1 |
+ * | 3 | Command number | 1 |
+ * | 4 | Type of Command | 1 |
+ * | 5 | Command Qualifier | 1 |
+ */
+ retrieveCommandDetails: function(length) {
+ let GsmPDUHelper = this.context.GsmPDUHelper;
+ let cmdDetails = {
+ commandNumber: GsmPDUHelper.readHexOctet(),
+ typeOfCommand: GsmPDUHelper.readHexOctet(),
+ commandQualifier: GsmPDUHelper.readHexOctet()
+ };
+ return cmdDetails;
+ },
+
+ /**
+ * Device Identities.
+ *
+ * | Byte | Description | Length |
+ * | 1 | Device Identity Tag | 1 |
+ * | 2 | Length = 02 | 1 |
+ * | 3 | Source device Identity | 1 |
+ * | 4 | Destination device Id | 1 |
+ */
+ retrieveDeviceId: function(length) {
+ let GsmPDUHelper = this.context.GsmPDUHelper;
+ let deviceId = {
+ sourceId: GsmPDUHelper.readHexOctet(),
+ destinationId: GsmPDUHelper.readHexOctet()
+ };
+ return deviceId;
+ },
+
+ /**
+ * Alpha Identifier.
+ *
+ * | Byte | Description | Length |
+ * | 1 | Alpha Identifier Tag | 1 |
+ * | 2 ~ (Y-1)+2 | Length (X) | Y |
+ * | (Y-1)+3 ~ | Alpha identfier | X |
+ * | (Y-1)+X+2 | | |
+ */
+ retrieveAlphaId: function(length) {
+ let alphaId = {
+ identifier: this.context.ICCPDUHelper.readAlphaIdentifier(length)
+ };
+ return alphaId;
+ },
+
+ /**
+ * Duration.
+ *
+ * | Byte | Description | Length |
+ * | 1 | Response Length Tag | 1 |
+ * | 2 | Lenth = 02 | 1 |
+ * | 3 | Time unit | 1 |
+ * | 4 | Time interval | 1 |
+ */
+ retrieveDuration: function(length) {
+ let GsmPDUHelper = this.context.GsmPDUHelper;
+ let duration = {
+ timeUnit: GsmPDUHelper.readHexOctet(),
+ timeInterval: GsmPDUHelper.readHexOctet(),
+ };
+ return duration;
+ },
+
+ /**
+ * Address.
+ *
+ * | Byte | Description | Length |
+ * | 1 | Alpha Identifier Tag | 1 |
+ * | 2 ~ (Y-1)+2 | Length (X) | Y |
+ * | (Y-1)+3 | TON and NPI | 1 |
+ * | (Y-1)+4 ~ | Dialling number | X |
+ * | (Y-1)+X+2 | | |
+ */
+ retrieveAddress: function(length) {
+ let address = {
+ number : this.context.ICCPDUHelper.readDiallingNumber(length)
+ };
+ return address;
+ },
+
+ /**
+ * Text String.
+ *
+ * | Byte | Description | Length |
+ * | 1 | Text String Tag | 1 |
+ * | 2 ~ (Y-1)+2 | Length (X) | Y |
+ * | (Y-1)+3 | Data coding scheme | 1 |
+ * | (Y-1)+4~ | Text String | X |
+ * | (Y-1)+X+2 | | |
+ */
+ retrieveTextString: function(length) {
+ if (!length) {
+ // null string.
+ return {textString: null};
+ }
+
+ let GsmPDUHelper = this.context.GsmPDUHelper;
+ let text = {
+ codingScheme: GsmPDUHelper.readHexOctet()
+ };
+
+ length--; // -1 for the codingScheme.
+ switch (text.codingScheme & 0x0c) {
+ case STK_TEXT_CODING_GSM_7BIT_PACKED:
+ text.textString =
+ GsmPDUHelper.readSeptetsToString(Math.floor(length * 8 / 7), 0, 0, 0);
+ break;
+ case STK_TEXT_CODING_GSM_8BIT:
+ text.textString =
+ this.context.ICCPDUHelper.read8BitUnpackedToString(length);
+ break;
+ case STK_TEXT_CODING_UCS2:
+ text.textString = GsmPDUHelper.readUCS2String(length);
+ break;
+ }
+ return text;
+ },
+
+ /**
+ * Tone.
+ *
+ * | Byte | Description | Length |
+ * | 1 | Tone Tag | 1 |
+ * | 2 | Lenth = 01 | 1 |
+ * | 3 | Tone | 1 |
+ */
+ retrieveTone: function(length) {
+ let tone = {
+ tone: this.context.GsmPDUHelper.readHexOctet(),
+ };
+ return tone;
+ },
+
+ /**
+ * Item.
+ *
+ * | Byte | Description | Length |
+ * | 1 | Item Tag | 1 |
+ * | 2 ~ (Y-1)+2 | Length (X) | Y |
+ * | (Y-1)+3 | Identifier of item | 1 |
+ * | (Y-1)+4 ~ | Text string of item | X |
+ * | (Y-1)+X+2 | | |
+ */
+ retrieveItem: function(length) {
+ // TS 102.223 ,clause 6.6.7 SET-UP MENU
+ // If the "Item data object for item 1" is a null data object
+ // (i.e. length = '00' and no value part), this is an indication to the ME
+ // to remove the existing menu from the menu system in the ME.
+ if (!length) {
+ return null;
+ }
+ let item = {
+ identifier: this.context.GsmPDUHelper.readHexOctet(),
+ text: this.context.ICCPDUHelper.readAlphaIdentifier(length - 1)
+ };
+ return item;
+ },
+
+ /**
+ * Item Identifier.
+ *
+ * | Byte | Description | Length |
+ * | 1 | Item Identifier Tag | 1 |
+ * | 2 | Lenth = 01 | 1 |
+ * | 3 | Identifier of Item chosen | 1 |
+ */
+ retrieveItemId: function(length) {
+ let itemId = {
+ identifier: this.context.GsmPDUHelper.readHexOctet()
+ };
+ return itemId;
+ },
+
+ /**
+ * Response Length.
+ *
+ * | Byte | Description | Length |
+ * | 1 | Response Length Tag | 1 |
+ * | 2 | Lenth = 02 | 1 |
+ * | 3 | Minimum length of response | 1 |
+ * | 4 | Maximum length of response | 1 |
+ */
+ retrieveResponseLength: function(length) {
+ let GsmPDUHelper = this.context.GsmPDUHelper;
+ let rspLength = {
+ minLength : GsmPDUHelper.readHexOctet(),
+ maxLength : GsmPDUHelper.readHexOctet()
+ };
+ return rspLength;
+ },
+
+ /**
+ * File List.
+ *
+ * | Byte | Description | Length |
+ * | 1 | File List Tag | 1 |
+ * | 2 ~ (Y-1)+2 | Length (X) | Y |
+ * | (Y-1)+3 | Number of files | 1 |
+ * | (Y-1)+4 ~ | Files | X |
+ * | (Y-1)+X+2 | | |
+ */
+ retrieveFileList: function(length) {
+ let num = this.context.GsmPDUHelper.readHexOctet();
+ let fileList = "";
+ length--; // -1 for the num octet.
+ for (let i = 0; i < 2 * length; i++) {
+ // Didn't use readHexOctet here,
+ // otherwise 0x00 will be "0", not "00"
+ fileList += String.fromCharCode(this.context.Buf.readUint16());
+ }
+ return {
+ fileList: fileList
+ };
+ },
+
+ /**
+ * Default Text.
+ *
+ * Same as Text String.
+ */
+ retrieveDefaultText: function(length) {
+ return this.retrieveTextString(length);
+ },
+
+ /**
+ * Event List.
+ */
+ retrieveEventList: function(length) {
+ if (!length) {
+ // null means an indication to ME to remove the existing list of events
+ // in ME.
+ return null;
+ }
+
+ let GsmPDUHelper = this.context.GsmPDUHelper;
+ let eventList = [];
+ for (let i = 0; i < length; i++) {
+ eventList.push(GsmPDUHelper.readHexOctet());
+ }
+ return {
+ eventList: eventList
+ };
+ },
+
+ /**
+ * Icon Id.
+ *
+ * | Byte | Description | Length |
+ * | 1 | Icon Identifier Tag | 1 |
+ * | 2 | Length = 02 | 1 |
+ * | 3 | Icon qualifier | 1 |
+ * | 4 | Icon identifier | 1 |
+ */
+ retrieveIconId: function(length) {
+ if (!length) {
+ return null;
+ }
+
+ let iconId = {
+ qualifier: this.context.GsmPDUHelper.readHexOctet(),
+ identifier: this.context.GsmPDUHelper.readHexOctet()
+ };
+ return iconId;
+ },
+
+ /**
+ * Icon Id List.
+ *
+ * | Byte | Description | Length |
+ * | 1 | Icon Identifier Tag | 1 |
+ * | 2 | Length = X | 1 |
+ * | 3 | Icon qualifier | 1 |
+ * | 4~ | Icon identifier | X-1 |
+ * | 4+X-2 | | |
+ */
+ retrieveIconIdList: function(length) {
+ if (!length) {
+ return null;
+ }
+
+ let iconIdList = {
+ qualifier: this.context.GsmPDUHelper.readHexOctet(),
+ identifiers: []
+ };
+ for (let i = 0; i < length - 1; i++) {
+ iconIdList.identifiers.push(this.context.GsmPDUHelper.readHexOctet());
+ }
+ return iconIdList;
+ },
+
+ /**
+ * Timer Identifier.
+ *
+ * | Byte | Description | Length |
+ * | 1 | Timer Identifier Tag | 1 |
+ * | 2 | Length = 01 | 1 |
+ * | 3 | Timer Identifier | 1 |
+ */
+ retrieveTimerId: function(length) {
+ let id = {
+ timerId: this.context.GsmPDUHelper.readHexOctet()
+ };
+ return id;
+ },
+
+ /**
+ * Timer Value.
+ *
+ * | Byte | Description | Length |
+ * | 1 | Timer Value Tag | 1 |
+ * | 2 | Length = 03 | 1 |
+ * | 3 | Hour | 1 |
+ * | 4 | Minute | 1 |
+ * | 5 | Second | 1 |
+ */
+ retrieveTimerValue: function(length) {
+ let GsmPDUHelper = this.context.GsmPDUHelper;
+ let value = {
+ timerValue: (GsmPDUHelper.readSwappedNibbleBcdNum(1) * 60 * 60) +
+ (GsmPDUHelper.readSwappedNibbleBcdNum(1) * 60) +
+ (GsmPDUHelper.readSwappedNibbleBcdNum(1))
+ };
+ return value;
+ },
+
+ /**
+ * Immediate Response.
+ *
+ * | Byte | Description | Length |
+ * | 1 | Immediate Response Tag | 1 |
+ * | 2 | Length = 00 | 1 |
+ */
+ retrieveImmediaResponse: function(length) {
+ return {};
+ },
+
+ /**
+ * URL
+ *
+ * | Byte | Description | Length |
+ * | 1 | URL Tag | 1 |
+ * | 2 ~ (Y+1) | Length(X) | Y |
+ * | (Y+2) ~ | URL | X |
+ * | (Y+1+X) | | |
+ */
+ retrieveUrl: function(length) {
+ let GsmPDUHelper = this.context.GsmPDUHelper;
+ let s = "";
+ for (let i = 0; i < length; i++) {
+ s += String.fromCharCode(GsmPDUHelper.readHexOctet());
+ }
+ return {url: s};
+ },
+
+ /**
+ * Next Action Indicator List.
+ *
+ * | Byte | Description | Length |
+ * | 1 | Next Action tag | 1 |
+ * | 1 | Length(X) | 1 |
+ * | 3~ | Next Action List | X |
+ * | 3+X-1 | | |
+ */
+ retrieveNextActionList: function(length) {
+ let GsmPDUHelper = this.context.GsmPDUHelper;
+ let nextActionList = [];
+ for (let i = 0; i < length; i++) {
+ nextActionList.push(GsmPDUHelper.readHexOctet());
+ }
+ return nextActionList;
+ },
+
+ searchForTag: function(tag, ctlvs) {
+ for (let ctlv of ctlvs) {
+ if ((ctlv.tag & ~COMPREHENSIONTLV_FLAG_CR) == tag) {
+ return ctlv;
+ }
+ }
+ return null;
+ },
+
+ searchForSelectedTags: function(ctlvs, tags) {
+ let ret = {
+ // Handy utility to de-queue the 1st ctlv of the specified tag.
+ retrieve: function(aTag) {
+ return (this[aTag]) ? this[aTag].shift() : null;
+ }
+ };
+
+ ctlvs.forEach((aCtlv) => {
+ tags.forEach((aTag) => {
+ if ((aCtlv.tag & ~COMPREHENSIONTLV_FLAG_CR) == aTag) {
+ if (!ret[aTag]) {
+ ret[aTag] = [];
+ }
+ ret[aTag].push(aCtlv);
+ }
+ });
+ });
+
+ return ret;
+ },
+};
+StkProactiveCmdHelperObject.prototype[COMPREHENSIONTLV_TAG_COMMAND_DETAILS] = function COMPREHENSIONTLV_TAG_COMMAND_DETAILS(length) {
+ return this.retrieveCommandDetails(length);
+};
+StkProactiveCmdHelperObject.prototype[COMPREHENSIONTLV_TAG_DEVICE_ID] = function COMPREHENSIONTLV_TAG_DEVICE_ID(length) {
+ return this.retrieveDeviceId(length);
+};
+StkProactiveCmdHelperObject.prototype[COMPREHENSIONTLV_TAG_ALPHA_ID] = function COMPREHENSIONTLV_TAG_ALPHA_ID(length) {
+ return this.retrieveAlphaId(length);
+};
+StkProactiveCmdHelperObject.prototype[COMPREHENSIONTLV_TAG_DURATION] = function COMPREHENSIONTLV_TAG_DURATION(length) {
+ return this.retrieveDuration(length);
+};
+StkProactiveCmdHelperObject.prototype[COMPREHENSIONTLV_TAG_ADDRESS] = function COMPREHENSIONTLV_TAG_ADDRESS(length) {
+ return this.retrieveAddress(length);
+};
+StkProactiveCmdHelperObject.prototype[COMPREHENSIONTLV_TAG_TEXT_STRING] = function COMPREHENSIONTLV_TAG_TEXT_STRING(length) {
+ return this.retrieveTextString(length);
+};
+StkProactiveCmdHelperObject.prototype[COMPREHENSIONTLV_TAG_TONE] = function COMPREHENSIONTLV_TAG_TONE(length) {
+ return this.retrieveTone(length);
+};
+StkProactiveCmdHelperObject.prototype[COMPREHENSIONTLV_TAG_ITEM] = function COMPREHENSIONTLV_TAG_ITEM(length) {
+ return this.retrieveItem(length);
+};
+StkProactiveCmdHelperObject.prototype[COMPREHENSIONTLV_TAG_ITEM_ID] = function COMPREHENSIONTLV_TAG_ITEM_ID(length) {
+ return this.retrieveItemId(length);
+};
+StkProactiveCmdHelperObject.prototype[COMPREHENSIONTLV_TAG_RESPONSE_LENGTH] = function COMPREHENSIONTLV_TAG_RESPONSE_LENGTH(length) {
+ return this.retrieveResponseLength(length);
+};
+StkProactiveCmdHelperObject.prototype[COMPREHENSIONTLV_TAG_FILE_LIST] = function COMPREHENSIONTLV_TAG_FILE_LIST(length) {
+ return this.retrieveFileList(length);
+};
+StkProactiveCmdHelperObject.prototype[COMPREHENSIONTLV_TAG_DEFAULT_TEXT] = function COMPREHENSIONTLV_TAG_DEFAULT_TEXT(length) {
+ return this.retrieveDefaultText(length);
+};
+StkProactiveCmdHelperObject.prototype[COMPREHENSIONTLV_TAG_EVENT_LIST] = function COMPREHENSIONTLV_TAG_EVENT_LIST(length) {
+ return this.retrieveEventList(length);
+};
+StkProactiveCmdHelperObject.prototype[COMPREHENSIONTLV_TAG_ICON_ID] = function COMPREHENSIONTLV_TAG_ICON_ID(length) {
+ return this.retrieveIconId(length);
+};
+StkProactiveCmdHelperObject.prototype[COMPREHENSIONTLV_TAG_ICON_ID_LIST] = function COMPREHENSIONTLV_TAG_ICON_ID_LIST(length) {
+ return this.retrieveIconIdList(length);
+};
+StkProactiveCmdHelperObject.prototype[COMPREHENSIONTLV_TAG_TIMER_IDENTIFIER] = function COMPREHENSIONTLV_TAG_TIMER_IDENTIFIER(length) {
+ return this.retrieveTimerId(length);
+};
+StkProactiveCmdHelperObject.prototype[COMPREHENSIONTLV_TAG_TIMER_VALUE] = function COMPREHENSIONTLV_TAG_TIMER_VALUE(length) {
+ return this.retrieveTimerValue(length);
+};
+StkProactiveCmdHelperObject.prototype[COMPREHENSIONTLV_TAG_IMMEDIATE_RESPONSE] = function COMPREHENSIONTLV_TAG_IMMEDIATE_RESPONSE(length) {
+ return this.retrieveImmediaResponse(length);
+};
+StkProactiveCmdHelperObject.prototype[COMPREHENSIONTLV_TAG_URL] = function COMPREHENSIONTLV_TAG_URL(length) {
+ return this.retrieveUrl(length);
+};
+StkProactiveCmdHelperObject.prototype[COMPREHENSIONTLV_TAG_NEXT_ACTION_IND] = function COMPREHENSIONTLV_TAG_NEXT_ACTION_IND(length) {
+ return this.retrieveNextActionList(length);
+};
+
+function ComprehensionTlvHelperObject(aContext) {
+ this.context = aContext;
+}
+ComprehensionTlvHelperObject.prototype = {
+ context: null,
+
+ /**
+ * Decode raw data to a Comprehension-TLV.
+ */
+ decode: function() {
+ let GsmPDUHelper = this.context.GsmPDUHelper;
+
+ let hlen = 0; // For header(tag field + length field) length.
+ let temp = GsmPDUHelper.readHexOctet();
+ hlen++;
+
+ // TS 101.220, clause 7.1.1
+ let tag, cr;
+ switch (temp) {
+ // TS 101.220, clause 7.1.1
+ case 0x0: // Not used.
+ case 0xff: // Not used.
+ case 0x80: // Reserved for future use.
+ throw new Error("Invalid octet when parsing Comprehension TLV :" + temp);
+ case 0x7f: // Tag is three byte format.
+ // TS 101.220 clause 7.1.1.2.
+ // | Byte 1 | Byte 2 | Byte 3 |
+ // | | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | |
+ // | 0x7f |CR | Tag Value |
+ tag = (GsmPDUHelper.readHexOctet() << 8) | GsmPDUHelper.readHexOctet();
+ hlen += 2;
+ cr = (tag & 0x8000) !== 0;
+ tag &= ~0x8000;
+ break;
+ default: // Tag is single byte format.
+ tag = temp;
+ // TS 101.220 clause 7.1.1.1.
+ // | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 |
+ // |CR | Tag Value |
+ cr = (tag & 0x80) !== 0;
+ tag &= ~0x80;
+ }
+
+ // TS 101.220 clause 7.1.2, Length Encoding.
+ // Length | Byte 1 | Byte 2 | Byte 3 | Byte 4 |
+ // 0 - 127 | 00 - 7f | N/A | N/A | N/A |
+ // 128-255 | 81 | 80 - ff| N/A | N/A |
+ // 256-65535| 82 | 0100 - ffff | N/A |
+ // 65536- | 83 | 010000 - ffffff |
+ // 16777215
+ //
+ // Length errors: TS 11.14, clause 6.10.6
+
+ let length; // Data length.
+ temp = GsmPDUHelper.readHexOctet();
+ hlen++;
+ if (temp < 0x80) {
+ length = temp;
+ } else if (temp == 0x81) {
+ length = GsmPDUHelper.readHexOctet();
+ hlen++;
+ if (length < 0x80) {
+ throw new Error("Invalid length in Comprehension TLV :" + length);
+ }
+ } else if (temp == 0x82) {
+ length = (GsmPDUHelper.readHexOctet() << 8) | GsmPDUHelper.readHexOctet();
+ hlen += 2;
+ if (lenth < 0x0100) {
+ throw new Error("Invalid length in 3-byte Comprehension TLV :" + length);
+ }
+ } else if (temp == 0x83) {
+ length = (GsmPDUHelper.readHexOctet() << 16) |
+ (GsmPDUHelper.readHexOctet() << 8) |
+ GsmPDUHelper.readHexOctet();
+ hlen += 3;
+ if (length < 0x010000) {
+ throw new Error("Invalid length in 4-byte Comprehension TLV :" + length);
+ }
+ } else {
+ throw new Error("Invalid octet in Comprehension TLV :" + temp);
+ }
+
+ let ctlv = {
+ tag: tag,
+ length: length,
+ value: this.context.StkProactiveCmdHelper.retrieve(tag, length),
+ cr: cr,
+ hlen: hlen
+ };
+ return ctlv;
+ },
+
+ decodeChunks: function(length) {
+ let chunks = [];
+ let index = 0;
+ while (index < length) {
+ let tlv = this.decode();
+ chunks.push(tlv);
+ index += tlv.length;
+ index += tlv.hlen;
+ }
+ return chunks;
+ },
+
+ /**
+ * Write Location Info Comprehension TLV.
+ *
+ * @param loc location Information.
+ */
+ writeLocationInfoTlv: function(loc) {
+ let GsmPDUHelper = this.context.GsmPDUHelper;
+
+ GsmPDUHelper.writeHexOctet(COMPREHENSIONTLV_TAG_LOCATION_INFO |
+ COMPREHENSIONTLV_FLAG_CR);
+ GsmPDUHelper.writeHexOctet(loc.gsmCellId > 0xffff ? 9 : 7);
+ // From TS 11.14, clause 12.19
+ // "The mobile country code (MCC), the mobile network code (MNC),
+ // the location area code (LAC) and the cell ID are
+ // coded as in TS 04.08."
+ // And from TS 04.08 and TS 24.008,
+ // the format is as follows:
+ //
+ // MCC = MCC_digit_1 + MCC_digit_2 + MCC_digit_3
+ //
+ // 8 7 6 5 4 3 2 1
+ // +-------------+-------------+
+ // | MCC digit 2 | MCC digit 1 | octet 2
+ // | MNC digit 3 | MCC digit 3 | octet 3
+ // | MNC digit 2 | MNC digit 1 | octet 4
+ // +-------------+-------------+
+ //
+ // Also in TS 24.008
+ // "However a network operator may decide to
+ // use only two digits in the MNC in the LAI over the
+ // radio interface. In this case, bits 5 to 8 of octet 3
+ // shall be coded as '1111'".
+
+ // MCC & MNC, 3 octets
+ let mcc = loc.mcc, mnc;
+ if (loc.mnc.length == 2) {
+ mnc = "F" + loc.mnc;
+ } else {
+ mnc = loc.mnc[2] + loc.mnc[0] + loc.mnc[1];
+ }
+ GsmPDUHelper.writeSwappedNibbleBCD(mcc + mnc);
+
+ // LAC, 2 octets
+ GsmPDUHelper.writeHexOctet((loc.gsmLocationAreaCode >> 8) & 0xff);
+ GsmPDUHelper.writeHexOctet(loc.gsmLocationAreaCode & 0xff);
+
+ // Cell Id
+ if (loc.gsmCellId > 0xffff) {
+ // UMTS/WCDMA, gsmCellId is 28 bits.
+ GsmPDUHelper.writeHexOctet((loc.gsmCellId >> 24) & 0xff);
+ GsmPDUHelper.writeHexOctet((loc.gsmCellId >> 16) & 0xff);
+ GsmPDUHelper.writeHexOctet((loc.gsmCellId >> 8) & 0xff);
+ GsmPDUHelper.writeHexOctet(loc.gsmCellId & 0xff);
+ } else {
+ // GSM, gsmCellId is 16 bits.
+ GsmPDUHelper.writeHexOctet((loc.gsmCellId >> 8) & 0xff);
+ GsmPDUHelper.writeHexOctet(loc.gsmCellId & 0xff);
+ }
+ },
+
+ /**
+ * Given a geckoError string, this function translates it into cause value
+ * and write the value into buffer.
+ *
+ * @param geckoError Error string that is passed to gecko.
+ */
+ writeCauseTlv: function(geckoError) {
+ let GsmPDUHelper = this.context.GsmPDUHelper;
+
+ let cause = -1;
+ for (let errorNo in RIL_CALL_FAILCAUSE_TO_GECKO_CALL_ERROR) {
+ if (geckoError == RIL_CALL_FAILCAUSE_TO_GECKO_CALL_ERROR[errorNo]) {
+ cause = errorNo;
+ break;
+ }
+ }
+
+ // Causes specified in 10.5.4.11 of TS 04.08 are less than 128.
+ // we can ignore causes > 127 since Cause TLV is optional in
+ // STK_EVENT_TYPE_CALL_DISCONNECTED.
+ if (cause > 127) {
+ return;
+ }
+
+ cause = (cause == -1) ? ERROR_SUCCESS : cause;
+
+ GsmPDUHelper.writeHexOctet(COMPREHENSIONTLV_TAG_CAUSE |
+ COMPREHENSIONTLV_FLAG_CR);
+ GsmPDUHelper.writeHexOctet(2); // For single cause value.
+
+ // TS 04.08, clause 10.5.4.11:
+ // Code Standard : Standard defined for GSM PLMNS
+ // Location: User
+ GsmPDUHelper.writeHexOctet(0x60);
+
+ // TS 04.08, clause 10.5.4.11: ext bit = 1 + 7 bits for cause.
+ // +-----------------+----------------------------------+
+ // | Ext = 1 (1 bit) | Cause (7 bits) |
+ // +-----------------+----------------------------------+
+ GsmPDUHelper.writeHexOctet(0x80 | cause);
+ },
+
+ writeDateTimeZoneTlv: function(date) {
+ let GsmPDUHelper = this.context.GsmPDUHelper;
+
+ GsmPDUHelper.writeHexOctet(COMPREHENSIONTLV_TAG_DATE_TIME_ZONE);
+ GsmPDUHelper.writeHexOctet(7);
+ GsmPDUHelper.writeTimestamp(date);
+ },
+
+ writeLanguageTlv: function(language) {
+ let GsmPDUHelper = this.context.GsmPDUHelper;
+
+ GsmPDUHelper.writeHexOctet(COMPREHENSIONTLV_TAG_LANGUAGE);
+ GsmPDUHelper.writeHexOctet(2);
+
+ // ISO 639-1, Alpha-2 code
+ // TS 123.038, clause 6.2.1, GSM 7 bit Default Alphabet
+ GsmPDUHelper.writeHexOctet(
+ PDU_NL_LOCKING_SHIFT_TABLES[PDU_NL_IDENTIFIER_DEFAULT].indexOf(language[0]));
+ GsmPDUHelper.writeHexOctet(
+ PDU_NL_LOCKING_SHIFT_TABLES[PDU_NL_IDENTIFIER_DEFAULT].indexOf(language[1]));
+ },
+
+ /**
+ * Write Timer Value Comprehension TLV.
+ *
+ * @param seconds length of time during of the timer.
+ * @param cr Comprehension Required or not
+ */
+ writeTimerValueTlv: function(seconds, cr) {
+ let GsmPDUHelper = this.context.GsmPDUHelper;
+
+ GsmPDUHelper.writeHexOctet(COMPREHENSIONTLV_TAG_TIMER_VALUE |
+ (cr ? COMPREHENSIONTLV_FLAG_CR : 0));
+ GsmPDUHelper.writeHexOctet(3);
+
+ // TS 102.223, clause 8.38
+ // +----------------+------------------+-------------------+
+ // | hours (1 byte) | minutes (1 btye) | seconds (1 byte) |
+ // +----------------+------------------+-------------------+
+ GsmPDUHelper.writeSwappedNibbleBCDNum(Math.floor(seconds / 60 / 60));
+ GsmPDUHelper.writeSwappedNibbleBCDNum(Math.floor(seconds / 60) % 60);
+ GsmPDUHelper.writeSwappedNibbleBCDNum(Math.floor(seconds) % 60);
+ },
+
+ writeTextStringTlv: function(text, coding) {
+ let GsmPDUHelper = this.context.GsmPDUHelper;
+ let buf = GsmPDUHelper.writeWithBuffer(() => {
+ // Write Coding.
+ GsmPDUHelper.writeHexOctet(coding);
+
+ // Write Text String.
+ switch (coding) {
+ case STK_TEXT_CODING_UCS2:
+ GsmPDUHelper.writeUCS2String(text);
+ break;
+ case STK_TEXT_CODING_GSM_7BIT_PACKED:
+ GsmPDUHelper.writeStringAsSeptets(text, 0, 0, 0);
+ break;
+ case STK_TEXT_CODING_GSM_8BIT:
+ GsmPDUHelper.writeStringAs8BitUnpacked(text);
+ break;
+ }
+ });
+
+ let length = buf.length;
+ if (length) {
+ // Write Tag.
+ GsmPDUHelper.writeHexOctet(COMPREHENSIONTLV_TAG_TEXT_STRING |
+ COMPREHENSIONTLV_FLAG_CR);
+ // Write Length.
+ this.writeLength(length);
+ // Write Value.
+ for (let i = 0; i < length; i++) {
+ GsmPDUHelper.writeHexOctet(buf[i]);
+ }
+ }
+ },
+
+ getSizeOfLengthOctets: function(length) {
+ if (length >= 0x10000) {
+ return 4; // 0x83, len_1, len_2, len_3
+ } else if (length >= 0x100) {
+ return 3; // 0x82, len_1, len_2
+ } else if (length >= 0x80) {
+ return 2; // 0x81, len
+ } else {
+ return 1; // len
+ }
+ },
+
+ writeLength: function(length) {
+ let GsmPDUHelper = this.context.GsmPDUHelper;
+
+ // TS 101.220 clause 7.1.2, Length Encoding.
+ // Length | Byte 1 | Byte 2 | Byte 3 | Byte 4 |
+ // 0 - 127 | 00 - 7f | N/A | N/A | N/A |
+ // 128-255 | 81 | 80 - ff| N/A | N/A |
+ // 256-65535| 82 | 0100 - ffff | N/A |
+ // 65536- | 83 | 010000 - ffffff |
+ // 16777215
+ if (length < 0x80) {
+ GsmPDUHelper.writeHexOctet(length);
+ } else if (0x80 <= length && length < 0x100) {
+ GsmPDUHelper.writeHexOctet(0x81);
+ GsmPDUHelper.writeHexOctet(length);
+ } else if (0x100 <= length && length < 0x10000) {
+ GsmPDUHelper.writeHexOctet(0x82);
+ GsmPDUHelper.writeHexOctet((length >> 8) & 0xff);
+ GsmPDUHelper.writeHexOctet(length & 0xff);
+ } else if (0x10000 <= length && length < 0x1000000) {
+ GsmPDUHelper.writeHexOctet(0x83);
+ GsmPDUHelper.writeHexOctet((length >> 16) & 0xff);
+ GsmPDUHelper.writeHexOctet((length >> 8) & 0xff);
+ GsmPDUHelper.writeHexOctet(length & 0xff);
+ } else {
+ throw new Error("Invalid length value :" + length);
+ }
+ },
+};
+
+function BerTlvHelperObject(aContext) {
+ this.context = aContext;
+}
+BerTlvHelperObject.prototype = {
+ context: null,
+
+ /**
+ * Decode Ber TLV.
+ *
+ * @param dataLen
+ * The length of data in bytes.
+ */
+ decode: function(dataLen) {
+ let GsmPDUHelper = this.context.GsmPDUHelper;
+
+ let hlen = 0;
+ let tag = GsmPDUHelper.readHexOctet();
+ hlen++;
+
+ // The length is coded onto 1 or 2 bytes.
+ // Length | Byte 1 | Byte 2
+ // 0 - 127 | length ('00' to '7f') | N/A
+ // 128 - 255 | '81' | length ('80' to 'ff')
+ let length;
+ let temp = GsmPDUHelper.readHexOctet();
+ hlen++;
+ if (temp < 0x80) {
+ length = temp;
+ } else if (temp === 0x81) {
+ length = GsmPDUHelper.readHexOctet();
+ hlen++;
+ if (length < 0x80) {
+ throw new Error("Invalid length " + length);
+ }
+ } else {
+ throw new Error("Invalid length octet " + temp);
+ }
+
+ // Header + body length check.
+ if (dataLen - hlen !== length) {
+ throw new Error("Unexpected BerTlvHelper value length!!");
+ }
+
+ let method = this[tag];
+ if (typeof method != "function") {
+ throw new Error("Unknown Ber tag 0x" + tag.toString(16));
+ }
+
+ let value = method.call(this, length);
+
+ return {
+ tag: tag,
+ length: length,
+ value: value
+ };
+ },
+
+ /**
+ * Process the value part for FCP template TLV.
+ *
+ * @param length
+ * The length of data in bytes.
+ */
+ processFcpTemplate: function(length) {
+ let tlvs = this.decodeChunks(length);
+ return tlvs;
+ },
+
+ /**
+ * Process the value part for proactive command TLV.
+ *
+ * @param length
+ * The length of data in bytes.
+ */
+ processProactiveCommand: function(length) {
+ let ctlvs = this.context.ComprehensionTlvHelper.decodeChunks(length);
+ return ctlvs;
+ },
+
+ /**
+ * Decode raw data to a Ber-TLV.
+ */
+ decodeInnerTlv: function() {
+ let GsmPDUHelper = this.context.GsmPDUHelper;
+ let tag = GsmPDUHelper.readHexOctet();
+ let length = GsmPDUHelper.readHexOctet();
+ return {
+ tag: tag,
+ length: length,
+ value: this.retrieve(tag, length)
+ };
+ },
+
+ decodeChunks: function(length) {
+ let chunks = [];
+ let index = 0;
+ while (index < length) {
+ let tlv = this.decodeInnerTlv();
+ if (tlv.value) {
+ chunks.push(tlv);
+ }
+ index += tlv.length;
+ // tag + length fields consume 2 bytes.
+ index += 2;
+ }
+ return chunks;
+ },
+
+ retrieve: function(tag, length) {
+ let method = this[tag];
+ if (typeof method != "function") {
+ if (DEBUG) {
+ this.context.debug("Unknown Ber tag : 0x" + tag.toString(16));
+ }
+ let Buf = this.context.Buf;
+ Buf.seekIncoming(length * Buf.PDU_HEX_OCTET_SIZE);
+ return null;
+ }
+ return method.call(this, length);
+ },
+
+ /**
+ * File Size Data.
+ *
+ * | Byte | Description | Length |
+ * | 1 | Tag | 1 |
+ * | 2 | Length | 1 |
+ * | 3 to X+24 | Number of allocated data bytes in the file | X |
+ * | | , excluding structural information | |
+ */
+ retrieveFileSizeData: function(length) {
+ let GsmPDUHelper = this.context.GsmPDUHelper;
+ let fileSizeData = 0;
+ for (let i = 0; i < length; i++) {
+ fileSizeData = fileSizeData << 8;
+ fileSizeData += GsmPDUHelper.readHexOctet();
+ }
+
+ return {fileSizeData: fileSizeData};
+ },
+
+ /**
+ * File Descriptor.
+ *
+ * | Byte | Description | Length |
+ * | 1 | Tag | 1 |
+ * | 2 | Length | 1 |
+ * | 3 | File descriptor byte | 1 |
+ * | 4 | Data coding byte | 1 |
+ * | 5 ~ 6 | Record length | 2 |
+ * | 7 | Number of records | 1 |
+ */
+ retrieveFileDescriptor: function(length) {
+ let GsmPDUHelper = this.context.GsmPDUHelper;
+ let fileDescriptorByte = GsmPDUHelper.readHexOctet();
+ let dataCodingByte = GsmPDUHelper.readHexOctet();
+ // See TS 102 221 Table 11.5, we only care the least 3 bits for the
+ // structure of file.
+ let fileStructure = fileDescriptorByte & 0x07;
+
+ let fileDescriptor = {
+ fileStructure: fileStructure
+ };
+ // byte 5 ~ 7 are mandatory for linear fixed and cyclic files, otherwise
+ // they are not applicable.
+ if (fileStructure === UICC_EF_STRUCTURE[EF_STRUCTURE_LINEAR_FIXED] ||
+ fileStructure === UICC_EF_STRUCTURE[EF_STRUCTURE_CYCLIC]) {
+ fileDescriptor.recordLength = (GsmPDUHelper.readHexOctet() << 8) +
+ GsmPDUHelper.readHexOctet();
+ fileDescriptor.numOfRecords = GsmPDUHelper.readHexOctet();
+ }
+
+ return fileDescriptor;
+ },
+
+ /**
+ * File identifier.
+ *
+ * | Byte | Description | Length |
+ * | 1 | Tag | 1 |
+ * | 2 | Length | 1 |
+ * | 3 ~ 4 | File identifier | 2 |
+ */
+ retrieveFileIdentifier: function(length) {
+ let GsmPDUHelper = this.context.GsmPDUHelper;
+ return {fileId : (GsmPDUHelper.readHexOctet() << 8) +
+ GsmPDUHelper.readHexOctet()};
+ },
+
+ searchForNextTag: function(tag, iter) {
+ for (let tlv of iter) {
+ if (tlv.tag === tag) {
+ return tlv;
+ }
+ }
+ return null;
+ }
+};
+BerTlvHelperObject.prototype[BER_FCP_TEMPLATE_TAG] = function BER_FCP_TEMPLATE_TAG(length) {
+ return this.processFcpTemplate(length);
+};
+BerTlvHelperObject.prototype[BER_PROACTIVE_COMMAND_TAG] = function BER_PROACTIVE_COMMAND_TAG(length) {
+ return this.processProactiveCommand(length);
+};
+BerTlvHelperObject.prototype[BER_FCP_FILE_SIZE_DATA_TAG] = function BER_FCP_FILE_SIZE_DATA_TAG(length) {
+ return this.retrieveFileSizeData(length);
+};
+BerTlvHelperObject.prototype[BER_FCP_FILE_DESCRIPTOR_TAG] = function BER_FCP_FILE_DESCRIPTOR_TAG(length) {
+ return this.retrieveFileDescriptor(length);
+};
+BerTlvHelperObject.prototype[BER_FCP_FILE_IDENTIFIER_TAG] = function BER_FCP_FILE_IDENTIFIER_TAG(length) {
+ return this.retrieveFileIdentifier(length);
+};
+
+/**
+ * ICC Helper for getting EF path.
+ */
+function ICCFileHelperObject(aContext) {
+ this.context = aContext;
+}
+ICCFileHelperObject.prototype = {
+ context: null,
+
+ /**
+ * This function handles only EFs that are common to RUIM, SIM, USIM
+ * and other types of ICC cards.
+ */
+ getCommonEFPath: function(fileId) {
+ switch (fileId) {
+ case ICC_EF_ICCID:
+ return EF_PATH_MF_SIM;
+ case ICC_EF_ADN:
+ case ICC_EF_SDN: // Fall through.
+ return EF_PATH_MF_SIM + EF_PATH_DF_TELECOM;
+ case ICC_EF_PBR:
+ return EF_PATH_MF_SIM + EF_PATH_DF_TELECOM + EF_PATH_DF_PHONEBOOK;
+ case ICC_EF_IMG:
+ return EF_PATH_MF_SIM + EF_PATH_DF_TELECOM + EF_PATH_GRAPHICS;
+ }
+ return null;
+ },
+
+ /**
+ * This function handles EFs for SIM.
+ */
+ getSimEFPath: function(fileId) {
+ switch (fileId) {
+ case ICC_EF_FDN:
+ case ICC_EF_MSISDN:
+ case ICC_EF_SMS:
+ case ICC_EF_EXT1:
+ case ICC_EF_EXT2:
+ case ICC_EF_EXT3:
+ return EF_PATH_MF_SIM + EF_PATH_DF_TELECOM;
+ case ICC_EF_AD:
+ case ICC_EF_MBDN:
+ case ICC_EF_MWIS:
+ case ICC_EF_PLMNsel:
+ case ICC_EF_SPN:
+ case ICC_EF_SPDI:
+ case ICC_EF_SST:
+ case ICC_EF_PHASE:
+ case ICC_EF_CBMI:
+ case ICC_EF_CBMID:
+ case ICC_EF_CBMIR:
+ case ICC_EF_OPL:
+ case ICC_EF_PNN:
+ case ICC_EF_GID1:
+ case ICC_EF_CPHS_INFO:
+ case ICC_EF_CPHS_MBN:
+ return EF_PATH_MF_SIM + EF_PATH_DF_GSM;
+ default:
+ return null;
+ }
+ },
+
+ /**
+ * This function handles EFs for USIM.
+ */
+ getUSimEFPath: function(fileId) {
+ switch (fileId) {
+ case ICC_EF_AD:
+ case ICC_EF_FDN:
+ case ICC_EF_MBDN:
+ case ICC_EF_MWIS:
+ case ICC_EF_UST:
+ case ICC_EF_MSISDN:
+ case ICC_EF_SPN:
+ case ICC_EF_SPDI:
+ case ICC_EF_CBMI:
+ case ICC_EF_CBMID:
+ case ICC_EF_CBMIR:
+ case ICC_EF_OPL:
+ case ICC_EF_PNN:
+ case ICC_EF_SMS:
+ case ICC_EF_GID1:
+ // CPHS spec was provided in 1997 based on SIM requirement, there is no
+ // detailed info about how these ICC_EF_CPHS_XXX are allocated in USIM.
+ // What we can do now is to follow what has been done in AOSP to have file
+ // path equal to MF_SIM/DF_GSM.
+ case ICC_EF_CPHS_INFO:
+ case ICC_EF_CPHS_MBN:
+ return EF_PATH_MF_SIM + EF_PATH_ADF_USIM;
+ default:
+ // The file ids in USIM phone book entries are decided by the
+ // card manufacturer. So if we don't match any of the cases
+ // above and if its a USIM return the phone book path.
+ return EF_PATH_MF_SIM + EF_PATH_DF_TELECOM + EF_PATH_DF_PHONEBOOK;
+ }
+ },
+
+ /**
+ * This function handles EFs for RUIM
+ */
+ getRuimEFPath: function(fileId) {
+ switch(fileId) {
+ case ICC_EF_CSIM_IMSI_M:
+ case ICC_EF_CSIM_CDMAHOME:
+ case ICC_EF_CSIM_CST:
+ case ICC_EF_CSIM_SPN:
+ return EF_PATH_MF_SIM + EF_PATH_DF_CDMA;
+ case ICC_EF_FDN:
+ case ICC_EF_EXT1:
+ case ICC_EF_EXT2:
+ case ICC_EF_EXT3:
+ return EF_PATH_MF_SIM + EF_PATH_DF_TELECOM;
+ default:
+ return null;
+ }
+ },
+
+ /**
+ * Helper function for getting the pathId for the specific ICC record
+ * depeding on which type of ICC card we are using.
+ *
+ * @param fileId
+ * File id.
+ * @return The pathId or null in case of an error or invalid input.
+ */
+ getEFPath: function(fileId) {
+ let path = this.getCommonEFPath(fileId);
+ if (path) {
+ return path;
+ }
+
+ switch (this.context.RIL.appType) {
+ case CARD_APPTYPE_SIM:
+ return this.getSimEFPath(fileId);
+ case CARD_APPTYPE_USIM:
+ return this.getUSimEFPath(fileId);
+ case CARD_APPTYPE_RUIM:
+ return this.getRuimEFPath(fileId);
+ default:
+ return null;
+ }
+ }
+};
+
+/**
+ * Helper for ICC IO functionalities.
+ */
+function ICCIOHelperObject(aContext) {
+ this.context = aContext;
+}
+ICCIOHelperObject.prototype = {
+ context: null,
+
+ /**
+ * Load EF with type 'Linear Fixed'.
+ *
+ * @param fileId
+ * The file to operate on, one of the ICC_EF_* constants.
+ * @param recordNumber [optional]
+ * The number of the record shall be loaded.
+ * @param recordSize [optional]
+ * The size of the record.
+ * @param callback [optional]
+ * The callback function shall be called when the record(s) is read.
+ * @param onerror [optional]
+ * The callback function shall be called when failure.
+ */
+ loadLinearFixedEF: function(options) {
+ let cb;
+ let readRecord = (function(options) {
+ options.command = ICC_COMMAND_READ_RECORD;
+ options.p1 = options.recordNumber || 1; // Record number
+ options.p2 = READ_RECORD_ABSOLUTE_MODE;
+ options.p3 = options.recordSize;
+ options.callback = cb || options.callback;
+ this.context.RIL.iccIO(options);
+ }).bind(this);
+
+ options.structure = EF_STRUCTURE_LINEAR_FIXED;
+ options.pathId = this.context.ICCFileHelper.getEFPath(options.fileId);
+ if (options.recordSize) {
+ readRecord(options);
+ return;
+ }
+
+ cb = options.callback;
+ options.callback = readRecord;
+ this.getResponse(options);
+ },
+
+ /**
+ * Load next record from current record Id.
+ */
+ loadNextRecord: function(options) {
+ options.p1++;
+ this.context.RIL.iccIO(options);
+ },
+
+ /**
+ * Update EF with type 'Linear Fixed'.
+ *
+ * @param fileId
+ * The file to operate on, one of the ICC_EF_* constants.
+ * @param recordNumber
+ * The number of the record shall be updated.
+ * @param dataWriter [optional]
+ * The function for writing string parameter for the ICC_COMMAND_UPDATE_RECORD.
+ * @param pin2 [optional]
+ * PIN2 is required when updating ICC_EF_FDN.
+ * @param callback [optional]
+ * The callback function shall be called when the record is updated.
+ * @param onerror [optional]
+ * The callback function shall be called when failure.
+ */
+ updateLinearFixedEF: function(options) {
+ if (!options.fileId || !options.recordNumber) {
+ throw new Error("Unexpected fileId " + options.fileId +
+ " or recordNumber " + options.recordNumber);
+ }
+
+ options.structure = EF_STRUCTURE_LINEAR_FIXED;
+ options.pathId = this.context.ICCFileHelper.getEFPath(options.fileId);
+ let cb = options.callback;
+ options.callback = function callback(options) {
+ options.callback = cb;
+ options.command = ICC_COMMAND_UPDATE_RECORD;
+ options.p1 = options.recordNumber;
+ options.p2 = READ_RECORD_ABSOLUTE_MODE;
+ options.p3 = options.recordSize;
+ this.context.RIL.iccIO(options);
+ }.bind(this);
+ this.getResponse(options);
+ },
+
+ /**
+ * Load EF with type 'Transparent'.
+ *
+ * @param fileId
+ * The file to operate on, one of the ICC_EF_* constants.
+ * @param callback [optional]
+ * The callback function shall be called when the record(s) is read.
+ * @param onerror [optional]
+ * The callback function shall be called when failure.
+ */
+ loadTransparentEF: function(options) {
+ options.structure = EF_STRUCTURE_TRANSPARENT;
+ let cb = options.callback;
+ options.callback = function callback(options) {
+ options.callback = cb;
+ options.command = ICC_COMMAND_READ_BINARY;
+ options.p2 = 0x00;
+ options.p3 = options.fileSize;
+ this.context.RIL.iccIO(options);
+ }.bind(this);
+ this.getResponse(options);
+ },
+
+ /**
+ * Use ICC_COMMAND_GET_RESPONSE to query the EF.
+ *
+ * @param fileId
+ * The file to operate on, one of the ICC_EF_* constants.
+ */
+ getResponse: function(options) {
+ options.command = ICC_COMMAND_GET_RESPONSE;
+ options.pathId = options.pathId ||
+ this.context.ICCFileHelper.getEFPath(options.fileId);
+ if (!options.pathId) {
+ throw new Error("Unknown pathId for " + options.fileId.toString(16));
+ }
+ options.p1 = 0; // For GET_RESPONSE, p1 = 0
+ switch (this.context.RIL.appType) {
+ case CARD_APPTYPE_USIM:
+ options.p2 = GET_RESPONSE_FCP_TEMPLATE;
+ options.p3 = 0x00;
+ break;
+ // For RUIM, CSIM and ISIM, cf bug 955946: keep the old behavior
+ case CARD_APPTYPE_RUIM:
+ case CARD_APPTYPE_CSIM:
+ case CARD_APPTYPE_ISIM:
+ // For SIM, this is what we want
+ case CARD_APPTYPE_SIM:
+ default:
+ options.p2 = 0x00;
+ options.p3 = GET_RESPONSE_EF_SIZE_BYTES;
+ break;
+ }
+ this.context.RIL.iccIO(options);
+ },
+
+ /**
+ * Process ICC I/O response.
+ */
+ processICCIO: function(options) {
+ let func = this[options.command];
+ func.call(this, options);
+ },
+
+ /**
+ * Process a ICC_COMMAND_GET_RESPONSE type command for REQUEST_SIM_IO.
+ */
+ processICCIOGetResponse: function(options) {
+ let Buf = this.context.Buf;
+ let strLen = Buf.readInt32();
+
+ let peek = this.context.GsmPDUHelper.readHexOctet();
+ Buf.seekIncoming(-1 * Buf.PDU_HEX_OCTET_SIZE);
+ if (peek === BER_FCP_TEMPLATE_TAG) {
+ this.processUSimGetResponse(options, strLen / 2);
+ } else {
+ this.processSimGetResponse(options);
+ }
+ Buf.readStringDelimiter(strLen);
+
+ if (options.callback) {
+ options.callback(options);
+ }
+ },
+
+ /**
+ * Helper function for processing USIM get response.
+ */
+ processUSimGetResponse: function(options, octetLen) {
+ let BerTlvHelper = this.context.BerTlvHelper;
+
+ let berTlv = BerTlvHelper.decode(octetLen);
+ // See TS 102 221 Table 11.4 for the content order of getResponse.
+ let iter = berTlv.value.values();
+ let tlv = BerTlvHelper.searchForNextTag(BER_FCP_FILE_DESCRIPTOR_TAG,
+ iter);
+ if (!tlv ||
+ (tlv.value.fileStructure !== UICC_EF_STRUCTURE[options.structure])) {
+ throw new Error("Expected EF structure " +
+ UICC_EF_STRUCTURE[options.structure] +
+ " but read " + tlv.value.fileStructure);
+ }
+
+ if (tlv.value.fileStructure === UICC_EF_STRUCTURE[EF_STRUCTURE_LINEAR_FIXED] ||
+ tlv.value.fileStructure === UICC_EF_STRUCTURE[EF_STRUCTURE_CYCLIC]) {
+ options.recordSize = tlv.value.recordLength;
+ options.totalRecords = tlv.value.numOfRecords;
+ }
+
+ tlv = BerTlvHelper.searchForNextTag(BER_FCP_FILE_IDENTIFIER_TAG, iter);
+ if (!tlv || (tlv.value.fileId !== options.fileId)) {
+ throw new Error("Expected file ID " + options.fileId.toString(16) +
+ " but read " + fileId.toString(16));
+ }
+
+ tlv = BerTlvHelper.searchForNextTag(BER_FCP_FILE_SIZE_DATA_TAG, iter);
+ if (!tlv) {
+ throw new Error("Unexpected file size data");
+ }
+ options.fileSize = tlv.value.fileSizeData;
+ },
+
+ /**
+ * Helper function for processing SIM get response.
+ */
+ processSimGetResponse: function(options) {
+ let Buf = this.context.Buf;
+ let GsmPDUHelper = this.context.GsmPDUHelper;
+
+ // The format is from TS 51.011, clause 9.2.1
+
+ // Skip RFU, data[0] data[1].
+ Buf.seekIncoming(2 * Buf.PDU_HEX_OCTET_SIZE);
+
+ // File size, data[2], data[3]
+ options.fileSize = (GsmPDUHelper.readHexOctet() << 8) |
+ GsmPDUHelper.readHexOctet();
+
+ // 2 bytes File id. data[4], data[5]
+ let fileId = (GsmPDUHelper.readHexOctet() << 8) |
+ GsmPDUHelper.readHexOctet();
+ if (fileId != options.fileId) {
+ throw new Error("Expected file ID " + options.fileId.toString(16) +
+ " but read " + fileId.toString(16));
+ }
+
+ // Type of file, data[6]
+ let fileType = GsmPDUHelper.readHexOctet();
+ if (fileType != TYPE_EF) {
+ throw new Error("Unexpected file type " + fileType);
+ }
+
+ // Skip 1 byte RFU, data[7],
+ // 3 bytes Access conditions, data[8] data[9] data[10],
+ // 1 byte File status, data[11],
+ // 1 byte Length of the following data, data[12].
+ Buf.seekIncoming(((RESPONSE_DATA_STRUCTURE - RESPONSE_DATA_FILE_TYPE - 1) *
+ Buf.PDU_HEX_OCTET_SIZE));
+
+ // Read Structure of EF, data[13]
+ let efStructure = GsmPDUHelper.readHexOctet();
+ if (efStructure != options.structure) {
+ throw new Error("Expected EF structure " + options.structure +
+ " but read " + efStructure);
+ }
+
+ // Length of a record, data[14].
+ // Only available for LINEAR_FIXED and CYCLIC.
+ if (efStructure == EF_STRUCTURE_LINEAR_FIXED ||
+ efStructure == EF_STRUCTURE_CYCLIC) {
+ options.recordSize = GsmPDUHelper.readHexOctet();
+ options.totalRecords = options.fileSize / options.recordSize;
+ } else {
+ Buf.seekIncoming(1 * Buf.PDU_HEX_OCTET_SIZE);
+ }
+ },
+
+ /**
+ * Process a ICC_COMMAND_READ_RECORD type command for REQUEST_SIM_IO.
+ */
+ processICCIOReadRecord: function(options) {
+ if (options.callback) {
+ options.callback(options);
+ }
+ },
+
+ /**
+ * Process a ICC_COMMAND_READ_BINARY type command for REQUEST_SIM_IO.
+ */
+ processICCIOReadBinary: function(options) {
+ if (options.callback) {
+ options.callback(options);
+ }
+ },
+
+ /**
+ * Process a ICC_COMMAND_UPDATE_RECORD type command for REQUEST_SIM_IO.
+ */
+ processICCIOUpdateRecord: function(options) {
+ if (options.callback) {
+ options.callback(options);
+ }
+ },
+};
+ICCIOHelperObject.prototype[ICC_COMMAND_SEEK] = null;
+ICCIOHelperObject.prototype[ICC_COMMAND_READ_BINARY] = function ICC_COMMAND_READ_BINARY(options) {
+ this.processICCIOReadBinary(options);
+};
+ICCIOHelperObject.prototype[ICC_COMMAND_READ_RECORD] = function ICC_COMMAND_READ_RECORD(options) {
+ this.processICCIOReadRecord(options);
+};
+ICCIOHelperObject.prototype[ICC_COMMAND_GET_RESPONSE] = function ICC_COMMAND_GET_RESPONSE(options) {
+ this.processICCIOGetResponse(options);
+};
+ICCIOHelperObject.prototype[ICC_COMMAND_UPDATE_BINARY] = null;
+ICCIOHelperObject.prototype[ICC_COMMAND_UPDATE_RECORD] = function ICC_COMMAND_UPDATE_RECORD(options) {
+ this.processICCIOUpdateRecord(options);
+};
+
+/**
+ * Helper for ICC records.
+ */
+function ICCRecordHelperObject(aContext) {
+ this.context = aContext;
+ // Cache the possible free record id for all files, use fileId as key.
+ this._freeRecordIds = {};
+}
+ICCRecordHelperObject.prototype = {
+ context: null,
+
+ /**
+ * Fetch ICC records.
+ */
+ fetchICCRecords: function() {
+ switch (this.context.RIL.appType) {
+ case CARD_APPTYPE_SIM:
+ case CARD_APPTYPE_USIM:
+ this.context.SimRecordHelper.fetchSimRecords();
+ break;
+ case CARD_APPTYPE_RUIM:
+ this.context.RuimRecordHelper.fetchRuimRecords();
+ break;
+ }
+ },
+
+ /**
+ * Read the ICCID.
+ */
+ readICCID: function() {
+ function callback() {
+ let Buf = this.context.Buf;
+ let RIL = this.context.RIL;
+ let GsmPDUHelper = this.context.GsmPDUHelper;
+
+ let strLen = Buf.readInt32();
+ let octetLen = strLen / 2;
+ RIL.iccInfo.iccid =
+ GsmPDUHelper.readSwappedNibbleBcdString(octetLen, true);
+ // Consumes the remaining buffer if any.
+ let unReadBuffer = this.context.Buf.getReadAvailable() -
+ this.context.Buf.PDU_HEX_OCTET_SIZE;
+ if (unReadBuffer > 0) {
+ this.context.Buf.seekIncoming(unReadBuffer);
+ }
+ Buf.readStringDelimiter(strLen);
+
+ if (DEBUG) this.context.debug("ICCID: " + RIL.iccInfo.iccid);
+ if (RIL.iccInfo.iccid) {
+ this.context.ICCUtilsHelper.handleICCInfoChange();
+ RIL.reportStkServiceIsRunning();
+ }
+ }
+
+ this.context.ICCIOHelper.loadTransparentEF({
+ fileId: ICC_EF_ICCID,
+ callback: callback.bind(this)
+ });
+ },
+
+ /**
+ * Read ICC ADN like EF, i.e. EF_ADN, EF_FDN.
+ *
+ * @param fileId EF id of the ADN, FDN or SDN.
+ * @param extFileId EF id of the EXT.
+ * @param onsuccess Callback to be called when success.
+ * @param onerror Callback to be called when error.
+ */
+ readADNLike: function(fileId, extFileId, onsuccess, onerror) {
+ let ICCIOHelper = this.context.ICCIOHelper;
+
+ function callback(options) {
+ let loadNextContactRecord = () => {
+ if (options.p1 < options.totalRecords) {
+ ICCIOHelper.loadNextRecord(options);
+ return;
+ }
+ if (DEBUG) {
+ for (let i = 0; i < contacts.length; i++) {
+ this.context.debug("contact [" + i + "] " +
+ JSON.stringify(contacts[i]));
+ }
+ }
+ if (onsuccess) {
+ onsuccess(contacts);
+ }
+ };
+
+ let contact =
+ this.context.ICCPDUHelper.readAlphaIdDiallingNumber(options.recordSize);
+ if (contact) {
+ let record = {
+ recordId: options.p1,
+ alphaId: contact.alphaId,
+ number: contact.number
+ };
+ contacts.push(record);
+
+ if (extFileId && contact.extRecordNumber != 0xff) {
+ this.readExtension(extFileId, contact.extRecordNumber, (number) => {
+ if (number) {
+ record.number += number;
+ }
+ loadNextContactRecord();
+ }, () => loadNextContactRecord());
+ return;
+ }
+ }
+ loadNextContactRecord();
+ }
+
+ let contacts = [];
+ ICCIOHelper.loadLinearFixedEF({fileId: fileId,
+ callback: callback.bind(this),
+ onerror: onerror});
+ },
+
+ /**
+ * Update ICC ADN like EFs, like EF_ADN, EF_FDN.
+ *
+ * @param fileId EF id of the ADN or FDN.
+ * @param extRecordNumber The record identifier of the EXT.
+ * @param contact The contact will be updated. (Shall have recordId property)
+ * @param pin2 PIN2 is required when updating ICC_EF_FDN.
+ * @param onsuccess Callback to be called when success.
+ * @param onerror Callback to be called when error.
+ */
+ updateADNLike: function(fileId, extRecordNumber, contact, pin2, onsuccess, onerror) {
+ let updatedContact;
+ function dataWriter(recordSize) {
+ updatedContact = this.context.ICCPDUHelper.writeAlphaIdDiallingNumber(recordSize,
+ contact.alphaId,
+ contact.number,
+ extRecordNumber);
+ }
+
+ function callback(options) {
+ if (onsuccess) {
+ onsuccess(updatedContact);
+ }
+ }
+
+ if (!contact || !contact.recordId) {
+ if (onerror) onerror(GECKO_ERROR_INVALID_PARAMETER);
+ return;
+ }
+
+ this.context.ICCIOHelper.updateLinearFixedEF({
+ fileId: fileId,
+ recordNumber: contact.recordId,
+ dataWriter: dataWriter.bind(this),
+ pin2: pin2,
+ callback: callback.bind(this),
+ onerror: onerror
+ });
+ },
+
+ /**
+ * Read USIM/RUIM Phonebook.
+ *
+ * @param onsuccess Callback to be called when success.
+ * @param onerror Callback to be called when error.
+ */
+ readPBR: function(onsuccess, onerror) {
+ let Buf = this.context.Buf;
+ let GsmPDUHelper = this.context.GsmPDUHelper;
+ let ICCIOHelper = this.context.ICCIOHelper;
+ let ICCUtilsHelper = this.context.ICCUtilsHelper;
+ let RIL = this.context.RIL;
+
+ function callback(options) {
+ let strLen = Buf.readInt32();
+ let octetLen = strLen / 2, readLen = 0;
+
+ let pbrTlvs = [];
+ while (readLen < octetLen) {
+ let tag = GsmPDUHelper.readHexOctet();
+ if (tag == 0xff) {
+ readLen++;
+ Buf.seekIncoming((octetLen - readLen) * Buf.PDU_HEX_OCTET_SIZE);
+ break;
+ }
+
+ let tlvLen = GsmPDUHelper.readHexOctet();
+ let tlvs = ICCUtilsHelper.decodeSimTlvs(tlvLen);
+ pbrTlvs.push({tag: tag,
+ length: tlvLen,
+ value: tlvs});
+
+ readLen += tlvLen + 2; // +2 for tag and tlvLen
+ }
+ Buf.readStringDelimiter(strLen);
+
+ if (pbrTlvs.length > 0) {
+ let pbr = ICCUtilsHelper.parsePbrTlvs(pbrTlvs);
+ // EF_ADN is mandatory if and only if DF_PHONEBOOK is present.
+ if (!pbr.adn) {
+ if (onerror) onerror("Cannot access ADN.");
+ return;
+ }
+ pbrs.push(pbr);
+ }
+
+ if (options.p1 < options.totalRecords) {
+ ICCIOHelper.loadNextRecord(options);
+ } else {
+ if (onsuccess) {
+ RIL.iccInfoPrivate.pbrs = pbrs;
+ onsuccess(pbrs);
+ }
+ }
+ }
+
+ if (RIL.iccInfoPrivate.pbrs) {
+ onsuccess(RIL.iccInfoPrivate.pbrs);
+ return;
+ }
+
+ let pbrs = [];
+ ICCIOHelper.loadLinearFixedEF({fileId : ICC_EF_PBR,
+ callback: callback.bind(this),
+ onerror: onerror});
+ },
+
+ /**
+ * Cache EF_IAP record size.
+ */
+ _iapRecordSize: null,
+
+ /**
+ * Read ICC EF_IAP. (Index Administration Phonebook)
+ *
+ * @see TS 131.102, clause 4.4.2.2
+ *
+ * @param fileId EF id of the IAP.
+ * @param recordNumber The number of the record shall be loaded.
+ * @param onsuccess Callback to be called when success.
+ * @param onerror Callback to be called when error.
+ */
+ readIAP: function(fileId, recordNumber, onsuccess, onerror) {
+ function callback(options) {
+ let Buf = this.context.Buf;
+ let strLen = Buf.readInt32();
+ let octetLen = strLen / 2;
+ this._iapRecordSize = options.recordSize;
+
+ let iap = this.context.GsmPDUHelper.readHexOctetArray(octetLen);
+ Buf.readStringDelimiter(strLen);
+
+ if (onsuccess) {
+ onsuccess(iap);
+ }
+ }
+
+ this.context.ICCIOHelper.loadLinearFixedEF({
+ fileId: fileId,
+ recordNumber: recordNumber,
+ recordSize: this._iapRecordSize,
+ callback: callback.bind(this),
+ onerror: onerror
+ });
+ },
+
+ /**
+ * Update USIM/RUIM Phonebook EF_IAP.
+ *
+ * @see TS 131.102, clause 4.4.2.13
+ *
+ * @param fileId EF id of the IAP.
+ * @param recordNumber The identifier of the record shall be updated.
+ * @param iap The IAP value to be written.
+ * @param onsuccess Callback to be called when success.
+ * @param onerror Callback to be called when error.
+ */
+ updateIAP: function(fileId, recordNumber, iap, onsuccess, onerror) {
+ let dataWriter = function dataWriter(recordSize) {
+ let Buf = this.context.Buf;
+ let GsmPDUHelper = this.context.GsmPDUHelper;
+
+ // Write String length
+ let strLen = recordSize * 2;
+ Buf.writeInt32(strLen);
+
+ for (let i = 0; i < iap.length; i++) {
+ GsmPDUHelper.writeHexOctet(iap[i]);
+ }
+
+ Buf.writeStringDelimiter(strLen);
+ }.bind(this);
+
+ this.context.ICCIOHelper.updateLinearFixedEF({
+ fileId: fileId,
+ recordNumber: recordNumber,
+ dataWriter: dataWriter,
+ callback: onsuccess,
+ onerror: onerror
+ });
+ },
+
+ /**
+ * Cache EF_Email record size.
+ */
+ _emailRecordSize: null,
+
+ /**
+ * Read USIM/RUIM Phonebook EF_EMAIL.
+ *
+ * @see TS 131.102, clause 4.4.2.13
+ *
+ * @param fileId EF id of the EMAIL.
+ * @param fileType The type of the EMAIL, one of the ICC_USIM_TYPE* constants.
+ * @param recordNumber The number of the record shall be loaded.
+ * @param onsuccess Callback to be called when success.
+ * @param onerror Callback to be called when error.
+ */
+ readEmail: function(fileId, fileType, recordNumber, onsuccess, onerror) {
+ function callback(options) {
+ let Buf = this.context.Buf;
+ let ICCPDUHelper = this.context.ICCPDUHelper;
+
+ let strLen = Buf.readInt32();
+ let octetLen = strLen / 2;
+ let email = null;
+ this._emailRecordSize = options.recordSize;
+
+ // Read contact's email
+ //
+ // | Byte | Description | Length | M/O
+ // | 1 ~ X | E-mail Address | X | M
+ // | X+1 | ADN file SFI | 1 | C
+ // | X+2 | ADN file Record Identifier | 1 | C
+ // Note: The fields marked as C above are mandatort if the file
+ // is not type 1 (as specified in EF_PBR)
+ if (fileType == ICC_USIM_TYPE1_TAG) {
+ email = ICCPDUHelper.read8BitUnpackedToString(octetLen);
+ } else {
+ email = ICCPDUHelper.read8BitUnpackedToString(octetLen - 2);
+
+ // Consumes the remaining buffer
+ Buf.seekIncoming(2 * Buf.PDU_HEX_OCTET_SIZE); // For ADN SFI and Record Identifier
+ }
+
+ Buf.readStringDelimiter(strLen);
+
+ if (onsuccess) {
+ onsuccess(email);
+ }
+ }
+
+ this.context.ICCIOHelper.loadLinearFixedEF({
+ fileId: fileId,
+ recordNumber: recordNumber,
+ recordSize: this._emailRecordSize,
+ callback: callback.bind(this),
+ onerror: onerror
+ });
+ },
+
+ /**
+ * Update USIM/RUIM Phonebook EF_EMAIL.
+ *
+ * @see TS 131.102, clause 4.4.2.13
+ *
+ * @param pbr Phonebook Reference File.
+ * @param recordNumber The identifier of the record shall be updated.
+ * @param email The value to be written.
+ * @param adnRecordId The record Id of ADN, only needed if the fileType of Email is TYPE2.
+ * @param onsuccess Callback to be called when success.
+ * @param onerror Callback to be called when error.
+ */
+ updateEmail: function(pbr, recordNumber, email, adnRecordId, onsuccess, onerror) {
+ let fileId = pbr[USIM_PBR_EMAIL].fileId;
+ let fileType = pbr[USIM_PBR_EMAIL].fileType;
+ let writtenEmail;
+ let dataWriter = function dataWriter(recordSize) {
+ let Buf = this.context.Buf;
+ let GsmPDUHelper = this.context.GsmPDUHelper;
+ let ICCPDUHelper = this.context.ICCPDUHelper;
+
+ // Write String length
+ let strLen = recordSize * 2;
+ Buf.writeInt32(strLen);
+
+ if (fileType == ICC_USIM_TYPE1_TAG) {
+ writtenEmail = ICCPDUHelper.writeStringTo8BitUnpacked(recordSize, email);
+ } else {
+ writtenEmail = ICCPDUHelper.writeStringTo8BitUnpacked(recordSize - 2, email);
+ GsmPDUHelper.writeHexOctet(pbr.adn.sfi || 0xff);
+ GsmPDUHelper.writeHexOctet(adnRecordId);
+ }
+
+ Buf.writeStringDelimiter(strLen);
+ }.bind(this);
+
+ let callback = (options) => {
+ if (onsuccess) {
+ onsuccess(writtenEmail);
+ }
+ }
+
+ this.context.ICCIOHelper.updateLinearFixedEF({
+ fileId: fileId,
+ recordNumber: recordNumber,
+ dataWriter: dataWriter,
+ callback: callback,
+ onerror: onerror
+ });
+ },
+
+ /**
+ * Cache EF_ANR record size.
+ */
+ _anrRecordSize: null,
+
+ /**
+ * Read USIM/RUIM Phonebook EF_ANR.
+ *
+ * @see TS 131.102, clause 4.4.2.9
+ *
+ * @param fileId EF id of the ANR.
+ * @param fileType One of the ICC_USIM_TYPE* constants.
+ * @param recordNumber The number of the record shall be loaded.
+ * @param onsuccess Callback to be called when success.
+ * @param onerror Callback to be called when error.
+ */
+ readANR: function(fileId, fileType, recordNumber, onsuccess, onerror) {
+ function callback(options) {
+ let Buf = this.context.Buf;
+ let strLen = Buf.readInt32();
+ let number = null;
+ this._anrRecordSize = options.recordSize;
+
+ // Skip EF_AAS Record ID.
+ Buf.seekIncoming(1 * Buf.PDU_HEX_OCTET_SIZE);
+
+ number = this.context.ICCPDUHelper.readNumberWithLength();
+
+ // Skip 2 unused octets, CCP and EXT1.
+ Buf.seekIncoming(2 * Buf.PDU_HEX_OCTET_SIZE);
+
+ // For Type 2 there are two extra octets.
+ if (fileType == ICC_USIM_TYPE2_TAG) {
+ // Skip 2 unused octets, ADN SFI and Record Identifier.
+ Buf.seekIncoming(2 * Buf.PDU_HEX_OCTET_SIZE);
+ }
+
+ Buf.readStringDelimiter(strLen);
+
+ if (onsuccess) {
+ onsuccess(number);
+ }
+ }
+
+ this.context.ICCIOHelper.loadLinearFixedEF({
+ fileId: fileId,
+ recordNumber: recordNumber,
+ recordSize: this._anrRecordSize,
+ callback: callback.bind(this),
+ onerror: onerror
+ });
+ },
+
+ /**
+ * Update USIM/RUIM Phonebook EF_ANR.
+ *
+ * @see TS 131.102, clause 4.4.2.9
+ *
+ * @param pbr Phonebook Reference File.
+ * @param recordNumber The identifier of the record shall be updated.
+ * @param number The value to be written.
+ * @param adnRecordId The record Id of ADN, only needed if the fileType of Email is TYPE2.
+ * @param onsuccess Callback to be called when success.
+ * @param onerror Callback to be called when error.
+ */
+ updateANR: function(pbr, recordNumber, number, adnRecordId, onsuccess, onerror) {
+ let fileId = pbr[USIM_PBR_ANR0].fileId;
+ let fileType = pbr[USIM_PBR_ANR0].fileType;
+ let writtenNumber;
+ let dataWriter = function dataWriter(recordSize) {
+ let Buf = this.context.Buf;
+ let GsmPDUHelper = this.context.GsmPDUHelper;
+
+ // Write String length
+ let strLen = recordSize * 2;
+ Buf.writeInt32(strLen);
+
+ // EF_AAS record Id. Unused for now.
+ GsmPDUHelper.writeHexOctet(0xff);
+
+ writtenNumber = this.context.ICCPDUHelper.writeNumberWithLength(number);
+
+ // Write unused octets 0xff, CCP and EXT1.
+ GsmPDUHelper.writeHexOctet(0xff);
+ GsmPDUHelper.writeHexOctet(0xff);
+
+ // For Type 2 there are two extra octets.
+ if (fileType == ICC_USIM_TYPE2_TAG) {
+ GsmPDUHelper.writeHexOctet(pbr.adn.sfi || 0xff);
+ GsmPDUHelper.writeHexOctet(adnRecordId);
+ }
+
+ Buf.writeStringDelimiter(strLen);
+ }.bind(this);
+
+ let callback = (options) => {
+ if (onsuccess) {
+ onsuccess(writtenNumber);
+ }
+ }
+
+ this.context.ICCIOHelper.updateLinearFixedEF({
+ fileId: fileId,
+ recordNumber: recordNumber,
+ dataWriter: dataWriter,
+ callback: callback,
+ onerror: onerror
+ });
+ },
+
+ /**
+ * Cache the possible free record id for all files.
+ */
+ _freeRecordIds: null,
+
+ /**
+ * Find free record id.
+ *
+ * @param fileId EF id.
+ * @param onsuccess Callback to be called when success.
+ * @param onerror Callback to be called when error.
+ */
+ findFreeRecordId: function(fileId, onsuccess, onerror) {
+ let ICCIOHelper = this.context.ICCIOHelper;
+
+ function callback(options) {
+ let Buf = this.context.Buf;
+ let GsmPDUHelper = this.context.GsmPDUHelper;
+
+ let strLen = Buf.readInt32();
+ let octetLen = strLen / 2;
+ let readLen = 0;
+
+ while (readLen < octetLen) {
+ let octet = GsmPDUHelper.readHexOctet();
+ readLen++;
+ if (octet != 0xff) {
+ break;
+ }
+ }
+
+ let nextRecord = (options.p1 % options.totalRecords) + 1;
+
+ if (readLen == octetLen) {
+ // Find free record, assume next record is probably free.
+ this._freeRecordIds[fileId] = nextRecord;
+ if (onsuccess) {
+ onsuccess(options.p1);
+ }
+ return;
+ } else {
+ Buf.seekIncoming((octetLen - readLen) * Buf.PDU_HEX_OCTET_SIZE);
+ }
+
+ Buf.readStringDelimiter(strLen);
+
+ if (nextRecord !== recordNumber) {
+ options.p1 = nextRecord;
+ this.context.RIL.iccIO(options);
+ } else {
+ // No free record found.
+ delete this._freeRecordIds[fileId];
+ if (DEBUG) {
+ this.context.debug(CONTACT_ERR_NO_FREE_RECORD_FOUND);
+ }
+ onerror(CONTACT_ERR_NO_FREE_RECORD_FOUND);
+ }
+ }
+
+ // Start searching free records from the possible one.
+ let recordNumber = this._freeRecordIds[fileId] || 1;
+ ICCIOHelper.loadLinearFixedEF({fileId: fileId,
+ recordNumber: recordNumber,
+ callback: callback.bind(this),
+ onerror: onerror});
+ },
+
+ /**
+ * Read Extension Number from TS 151.011 clause 10.5.10, TS 31.102, clause 4.4.2.4
+ *
+ * @param fileId EF Extension id
+ * @param recordNumber The number of the record shall be loaded.
+ * @param onsuccess Callback to be called when success.
+ * @param onerror Callback to be called when error.
+ */
+ readExtension: function(fileId, recordNumber, onsuccess, onerror) {
+ let callback = (options) => {
+ let Buf = this.context.Buf;
+ let length = Buf.readInt32();
+ let recordType = this.context.GsmPDUHelper.readHexOctet();
+ let number = "";
+
+ // TS 31.102, clause 4.4.2.4 EFEXT1
+ // Case 1, Extension1 record is additional data
+ if (recordType & 0x02) {
+ let numLen = this.context.GsmPDUHelper.readHexOctet();
+ if (numLen != 0xff) {
+ if (numLen > EXT_MAX_BCD_NUMBER_BYTES) {
+ if (DEBUG) {
+ this.context.debug(
+ "Error: invalid length of BCD number/SSC contents - " + numLen);
+ }
+ // +1 to skip Identifier
+ Buf.seekIncoming((EXT_MAX_BCD_NUMBER_BYTES + 1) * Buf.PDU_HEX_OCTET_SIZE);
+ Buf.readStringDelimiter(length);
+ onerror();
+ return;
+ }
+
+ number = this.context.GsmPDUHelper.readSwappedNibbleExtendedBcdString(numLen);
+ if (DEBUG) this.context.debug("Contact Extension Number: "+ number);
+ Buf.seekIncoming((EXT_MAX_BCD_NUMBER_BYTES - numLen) * Buf.PDU_HEX_OCTET_SIZE);
+ } else {
+ Buf.seekIncoming(EXT_MAX_BCD_NUMBER_BYTES * Buf.PDU_HEX_OCTET_SIZE);
+ }
+ } else {
+ // Don't support Case 2, Extension1 record is Called Party Subaddress.
+ // +1 skip numLen
+ Buf.seekIncoming((EXT_MAX_BCD_NUMBER_BYTES + 1) * Buf.PDU_HEX_OCTET_SIZE);
+ }
+
+ // Skip Identifier
+ Buf.seekIncoming(Buf.PDU_HEX_OCTET_SIZE);
+ Buf.readStringDelimiter(length);
+ onsuccess(number);
+ }
+
+ this.context.ICCIOHelper.loadLinearFixedEF({
+ fileId: fileId,
+ recordNumber: recordNumber,
+ callback: callback,
+ onerror: onerror
+ });
+ },
+
+ /**
+ * Update Extension.
+ *
+ * @param fileId EF id of the EXT.
+ * @param recordNumber The number of the record shall be updated.
+ * @param number Dialling Number to be written.
+ * @param onsuccess Callback to be called when success.
+ * @param onerror Callback to be called when error.
+ */
+ updateExtension: function(fileId, recordNumber, number, onsuccess, onerror) {
+ let dataWriter = (recordSize) => {
+ let GsmPDUHelper = this.context.GsmPDUHelper;
+ // Write String length
+ let strLen = recordSize * 2;
+ let Buf = this.context.Buf;
+ Buf.writeInt32(strLen);
+
+ // We don't support extension chain.
+ if (number.length > EXT_MAX_NUMBER_DIGITS) {
+ number = number.substring(0, EXT_MAX_NUMBER_DIGITS);
+ }
+
+ let numLen = Math.ceil(number.length / 2);
+ // Write Extension record
+ GsmPDUHelper.writeHexOctet(0x02);
+ GsmPDUHelper.writeHexOctet(numLen);
+ GsmPDUHelper.writeSwappedNibbleBCD(number);
+ // Write trailing 0xff of Extension data.
+ for (let i = 0; i < EXT_MAX_BCD_NUMBER_BYTES - numLen; i++) {
+ GsmPDUHelper.writeHexOctet(0xff);
+ }
+ // Write trailing 0xff for Identifier.
+ GsmPDUHelper.writeHexOctet(0xff);
+ Buf.writeStringDelimiter(strLen);
+ };
+
+ this.context.ICCIOHelper.updateLinearFixedEF({
+ fileId: fileId,
+ recordNumber: recordNumber,
+ dataWriter: dataWriter,
+ callback: onsuccess,
+ onerror: onerror
+ });
+ },
+
+ /**
+ * Clean an EF record.
+ *
+ * @param fileId EF id.
+ * @param recordNumber The number of the record shall be updated.
+ * @param onsuccess Callback to be called when success.
+ * @param onerror Callback to be called when error.
+ */
+ cleanEFRecord: function(fileId, recordNumber, onsuccess, onerror) {
+ let dataWriter = (recordSize) => {
+ let GsmPDUHelper = this.context.GsmPDUHelper;
+ let Buf = this.context.Buf;
+ // Write String length
+ let strLen = recordSize * 2;
+
+ Buf.writeInt32(strLen);
+ // Write record to 0xff
+ for (let i = 0; i < recordSize; i++) {
+ GsmPDUHelper.writeHexOctet(0xff);
+ }
+ Buf.writeStringDelimiter(strLen);
+ }
+
+ this.context.ICCIOHelper.updateLinearFixedEF({
+ fileId: fileId,
+ recordNumber: recordNumber,
+ dataWriter: dataWriter,
+ callback: onsuccess,
+ onerror: onerror
+ });
+ },
+
+ /**
+ * Get ADNLike extension record number.
+ *
+ * @param fileId EF id of the ADN or FDN.
+ * @param recordNumber EF record id of the ADN or FDN.
+ * @param onsuccess Callback to be called when success.
+ * @param onerror Callback to be called when error.
+ */
+ getADNLikeExtensionRecordNumber: function(fileId, recordNumber, onsuccess, onerror) {
+ let callback = (options) => {
+ let Buf = this.context.Buf;
+ let length = Buf.readInt32();
+
+ // Skip alphaLen, numLen, BCD Number, CCP octets.
+ Buf.seekIncoming((options.recordSize -1) * Buf.PDU_HEX_OCTET_SIZE);
+
+ let extRecordNumber = this.context.GsmPDUHelper.readHexOctet();
+ Buf.readStringDelimiter(length);
+
+ onsuccess(extRecordNumber);
+ }
+
+ this.context.ICCIOHelper.loadLinearFixedEF({
+ fileId: fileId,
+ recordNumber: recordNumber,
+ callback: callback,
+ onerror: onerror
+ });
+ },
+};
+
+/**
+ * Helper for (U)SIM Records.
+ */
+function SimRecordHelperObject(aContext) {
+ this.context = aContext;
+}
+SimRecordHelperObject.prototype = {
+ context: null,
+
+ /**
+ * Fetch (U)SIM records.
+ */
+ fetchSimRecords: function() {
+ this.context.RIL.getIMSI();
+ this.readAD();
+ // CPHS was widely introduced in Europe during GSM(2G) era to provide easier
+ // access to carrier's core service like voicemail, call forwarding, manual
+ // PLMN selection, and etc.
+ // Addition EF like EF_CPHS_MBN, EF_CPHS_CPHS_CFF, EF_CPHS_VWI, etc are
+ // introduced to support these feature.
+ // In USIM, the replancement of these EFs are provided. (EF_MBDN, EF_MWIS, ...)
+ // However, some carriers in Europe still rely on these EFs.
+ this.readCphsInfo(() => this.readSST(),
+ (aErrorMsg) => {
+ this.context.debug("Failed to read CPHS_INFO: " + aErrorMsg);
+ this.readSST();
+ });
+ },
+
+ /**
+ * Read EF_phase.
+ * This EF is only available in SIM.
+ */
+ readSimPhase: function() {
+ function callback() {
+ let Buf = this.context.Buf;
+ let strLen = Buf.readInt32();
+
+ let GsmPDUHelper = this.context.GsmPDUHelper;
+ let phase = GsmPDUHelper.readHexOctet();
+ // If EF_phase is coded '03' or greater, an ME supporting STK shall
+ // perform the PROFILE DOWNLOAD procedure.
+ if (RILQUIRKS_SEND_STK_PROFILE_DOWNLOAD &&
+ phase >= ICC_PHASE_2_PROFILE_DOWNLOAD_REQUIRED) {
+ this.context.RIL.sendStkTerminalProfile(STK_SUPPORTED_TERMINAL_PROFILE);
+ }
+
+ Buf.readStringDelimiter(strLen);
+ }
+
+ this.context.ICCIOHelper.loadTransparentEF({
+ fileId: ICC_EF_PHASE,
+ callback: callback.bind(this)
+ });
+ },
+
+ /**
+ * Read the MSISDN from the (U)SIM.
+ */
+ readMSISDN: function() {
+ function callback(options) {
+ let RIL = this.context.RIL;
+
+ let contact =
+ this.context.ICCPDUHelper.readAlphaIdDiallingNumber(options.recordSize);
+ if (!contact ||
+ (RIL.iccInfo.msisdn !== undefined &&
+ RIL.iccInfo.msisdn === contact.number)) {
+ return;
+ }
+ RIL.iccInfo.msisdn = contact.number;
+ if (DEBUG) this.context.debug("MSISDN: " + RIL.iccInfo.msisdn);
+ this.context.ICCUtilsHelper.handleICCInfoChange();
+ }
+
+ this.context.ICCIOHelper.loadLinearFixedEF({
+ fileId: ICC_EF_MSISDN,
+ callback: callback.bind(this)
+ });
+ },
+
+ /**
+ * Read the AD (Administrative Data) from the (U)SIM.
+ */
+ readAD: function() {
+ function callback() {
+ let Buf = this.context.Buf;
+ let strLen = Buf.readInt32();
+ // Each octet is encoded into two chars.
+ let octetLen = strLen / 2;
+ let ad = this.context.GsmPDUHelper.readHexOctetArray(octetLen);
+ Buf.readStringDelimiter(strLen);
+
+ if (DEBUG) {
+ let str = "";
+ for (let i = 0; i < ad.length; i++) {
+ str += ad[i] + ", ";
+ }
+ this.context.debug("AD: " + str);
+ }
+
+ let ICCUtilsHelper = this.context.ICCUtilsHelper;
+ let RIL = this.context.RIL;
+ // TS 31.102, clause 4.2.18 EFAD
+ let mncLength = 0;
+ if (ad && ad[3]) {
+ mncLength = ad[3] & 0x0f;
+ if (mncLength != 0x02 && mncLength != 0x03) {
+ mncLength = 0;
+ }
+ }
+ // The 4th byte of the response is the length of MNC.
+ let mccMnc = ICCUtilsHelper.parseMccMncFromImsi(RIL.iccInfoPrivate.imsi,
+ mncLength);
+ if (mccMnc) {
+ RIL.iccInfo.mcc = mccMnc.mcc;
+ RIL.iccInfo.mnc = mccMnc.mnc;
+ ICCUtilsHelper.handleICCInfoChange();
+ }
+ }
+
+ this.context.ICCIOHelper.loadTransparentEF({
+ fileId: ICC_EF_AD,
+ callback: callback.bind(this)
+ });
+ },
+
+ /**
+ * Read the SPN (Service Provider Name) from the (U)SIM.
+ */
+ readSPN: function() {
+ function callback() {
+ let Buf = this.context.Buf;
+ let strLen = Buf.readInt32();
+ // Each octet is encoded into two chars.
+ let octetLen = strLen / 2;
+ let spnDisplayCondition = this.context.GsmPDUHelper.readHexOctet();
+ // Minus 1 because the first octet is used to store display condition.
+ let spn = this.context.ICCPDUHelper.readAlphaIdentifier(octetLen - 1);
+ Buf.readStringDelimiter(strLen);
+
+ if (DEBUG) {
+ this.context.debug("SPN: spn = " + spn +
+ ", spnDisplayCondition = " + spnDisplayCondition);
+ }
+
+ let RIL = this.context.RIL;
+ RIL.iccInfoPrivate.spnDisplayCondition = spnDisplayCondition;
+ RIL.iccInfo.spn = spn;
+ let ICCUtilsHelper = this.context.ICCUtilsHelper;
+ ICCUtilsHelper.updateDisplayCondition();
+ ICCUtilsHelper.handleICCInfoChange();
+ }
+
+ this.context.ICCIOHelper.loadTransparentEF({
+ fileId: ICC_EF_SPN,
+ callback: callback.bind(this)
+ });
+ },
+
+ readIMG: function(recordNumber, onsuccess, onerror) {
+ function callback(options) {
+ let RIL = this.context.RIL;
+ let Buf = this.context.Buf;
+ let GsmPDUHelper = this.context.GsmPDUHelper;
+ let strLen = Buf.readInt32();
+ // Each octet is encoded into two chars.
+ let octetLen = strLen / 2;
+
+ let numInstances = GsmPDUHelper.readHexOctet();
+
+ // Data length is defined as 9n+1 or 9n+2. See TS 31.102, sub-clause
+ // 4.6.1.1. However, it's likely to have padding appended so we have a
+ // rather loose check.
+ if (octetLen < (9 * numInstances + 1)) {
+ Buf.seekIncoming((octetLen - 1) * Buf.PDU_HEX_OCTET_SIZE);
+ Buf.readStringDelimiter(strLen);
+ if (onerror) {
+ onerror();
+ }
+ return;
+ }
+
+ let imgDescriptors = [];
+ for (let i = 0; i < numInstances; i++) {
+ imgDescriptors[i] = {
+ width: GsmPDUHelper.readHexOctet(),
+ height: GsmPDUHelper.readHexOctet(),
+ codingScheme: GsmPDUHelper.readHexOctet(),
+ fileId: (GsmPDUHelper.readHexOctet() << 8) |
+ GsmPDUHelper.readHexOctet(),
+ offset: (GsmPDUHelper.readHexOctet() << 8) |
+ GsmPDUHelper.readHexOctet(),
+ dataLen: (GsmPDUHelper.readHexOctet() << 8) |
+ GsmPDUHelper.readHexOctet()
+ };
+ }
+ Buf.seekIncoming((octetLen - 9 * numInstances - 1) * Buf.PDU_HEX_OCTET_SIZE);
+ Buf.readStringDelimiter(strLen);
+
+ let instances = [];
+ let currentInstance = 0;
+ let readNextInstance = (function(img) {
+ instances[currentInstance] = img;
+ currentInstance++;
+
+ if (currentInstance < numInstances) {
+ let imgDescriptor = imgDescriptors[currentInstance];
+ this.readIIDF(imgDescriptor.fileId,
+ imgDescriptor.offset,
+ imgDescriptor.dataLen,
+ imgDescriptor.codingScheme,
+ readNextInstance,
+ onerror);
+ } else {
+ if (onsuccess) {
+ onsuccess(instances);
+ }
+ }
+ }).bind(this);
+
+ this.readIIDF(imgDescriptors[0].fileId,
+ imgDescriptors[0].offset,
+ imgDescriptors[0].dataLen,
+ imgDescriptors[0].codingScheme,
+ readNextInstance,
+ onerror);
+ }
+
+ this.context.ICCIOHelper.loadLinearFixedEF({
+ fileId: ICC_EF_IMG,
+ recordNumber: recordNumber,
+ callback: callback.bind(this),
+ onerror: onerror
+ });
+ },
+
+ readIIDF: function(fileId, offset, dataLen, codingScheme, onsuccess, onerror) {
+ // Valid fileId is '4FXX', see TS 31.102, clause 4.6.1.2.
+ if ((fileId >> 8) != 0x4F) {
+ if (onerror) {
+ onerror();
+ }
+ return;
+ }
+
+ function callback() {
+ let Buf = this.context.Buf;
+ let RIL = this.context.RIL;
+ let GsmPDUHelper = this.context.GsmPDUHelper;
+ let strLen = Buf.readInt32();
+ // Each octet is encoded into two chars.
+ let octetLen = strLen / 2;
+
+ if (octetLen < offset + dataLen) {
+ // Data length is not enough. See TS 31.102, clause 4.6.1.1, the
+ // paragraph "Bytes 8 and 9: Length of Image Instance Data."
+ Buf.seekIncoming(octetLen * Buf.PDU_HEX_OCTET_SIZE);
+ Buf.readStringDelimiter(strLen);
+ if (onerror) {
+ onerror();
+ }
+ return;
+ }
+
+ Buf.seekIncoming(offset * Buf.PDU_HEX_OCTET_SIZE);
+
+ let rawData = {
+ width: GsmPDUHelper.readHexOctet(),
+ height: GsmPDUHelper.readHexOctet(),
+ codingScheme: codingScheme
+ };
+
+ switch (codingScheme) {
+ case ICC_IMG_CODING_SCHEME_BASIC:
+ rawData.body = GsmPDUHelper.readHexOctetArray(
+ dataLen - ICC_IMG_HEADER_SIZE_BASIC);
+ Buf.seekIncoming((octetLen - offset - dataLen) * Buf.PDU_HEX_OCTET_SIZE);
+ break;
+
+ case ICC_IMG_CODING_SCHEME_COLOR:
+ case ICC_IMG_CODING_SCHEME_COLOR_TRANSPARENCY:
+ rawData.bitsPerImgPoint = GsmPDUHelper.readHexOctet();
+ let num = GsmPDUHelper.readHexOctet();
+ // The value 0 shall be interpreted as 256. See TS 31.102, Annex B.2.
+ rawData.numOfClutEntries = (num === 0) ? 0x100 : num;
+ rawData.clutOffset = (GsmPDUHelper.readHexOctet() << 8) |
+ GsmPDUHelper.readHexOctet();
+ rawData.body = GsmPDUHelper.readHexOctetArray(
+ dataLen - ICC_IMG_HEADER_SIZE_COLOR);
+
+ Buf.seekIncoming((rawData.clutOffset - offset - dataLen) *
+ Buf.PDU_HEX_OCTET_SIZE);
+ let clut = GsmPDUHelper.readHexOctetArray(rawData.numOfClutEntries *
+ ICC_CLUT_ENTRY_SIZE);
+
+ rawData.clut = clut;
+ }
+
+ Buf.readStringDelimiter(strLen);
+
+ if (onsuccess) {
+ onsuccess(rawData);
+ }
+ }
+
+ this.context.ICCIOHelper.loadTransparentEF({
+ fileId: fileId,
+ pathId: this.context.ICCFileHelper.getEFPath(ICC_EF_IMG),
+ callback: callback.bind(this),
+ onerror: onerror
+ });
+ },
+
+ /**
+ * Read the (U)SIM Service Table from the (U)SIM.
+ */
+ readSST: function() {
+ function callback() {
+ let Buf = this.context.Buf;
+ let RIL = this.context.RIL;
+
+ let strLen = Buf.readInt32();
+ // Each octet is encoded into two chars.
+ let octetLen = strLen / 2;
+ let sst = this.context.GsmPDUHelper.readHexOctetArray(octetLen);
+ Buf.readStringDelimiter(strLen);
+ RIL.iccInfoPrivate.sst = sst;
+ if (DEBUG) {
+ let str = "";
+ for (let i = 0; i < sst.length; i++) {
+ str += sst[i] + ", ";
+ }
+ this.context.debug("SST: " + str);
+ }
+
+ let ICCUtilsHelper = this.context.ICCUtilsHelper;
+ if (ICCUtilsHelper.isICCServiceAvailable("MSISDN")) {
+ if (DEBUG) this.context.debug("MSISDN: MSISDN is available");
+ this.readMSISDN();
+ } else {
+ if (DEBUG) this.context.debug("MSISDN: MSISDN service is not available");
+ }
+
+ // Fetch SPN and PLMN list, if some of them are available.
+ if (ICCUtilsHelper.isICCServiceAvailable("SPN")) {
+ if (DEBUG) this.context.debug("SPN: SPN is available");
+ this.readSPN();
+ } else {
+ if (DEBUG) this.context.debug("SPN: SPN service is not available");
+ }
+
+ if (ICCUtilsHelper.isICCServiceAvailable("MDN")) {
+ if (DEBUG) this.context.debug("MDN: MDN available.");
+ this.readMBDN();
+ } else {
+ if (DEBUG) this.context.debug("MDN: MDN service is not available");
+
+ if (ICCUtilsHelper.isCphsServiceAvailable("MBN")) {
+ // read CPHS_MBN in advance if MBDN is not available.
+ this.readCphsMBN();
+ } else {
+ if (DEBUG) this.context.debug("CPHS_MBN: CPHS_MBN service is not available");
+ }
+ }
+
+ if (ICCUtilsHelper.isICCServiceAvailable("MWIS")) {
+ if (DEBUG) this.context.debug("MWIS: MWIS is available");
+ this.readMWIS();
+ } else {
+ if (DEBUG) this.context.debug("MWIS: MWIS is not available");
+ }
+
+ if (ICCUtilsHelper.isICCServiceAvailable("SPDI")) {
+ if (DEBUG) this.context.debug("SPDI: SPDI available.");
+ this.readSPDI();
+ } else {
+ if (DEBUG) this.context.debug("SPDI: SPDI service is not available");
+ }
+
+ if (ICCUtilsHelper.isICCServiceAvailable("PNN")) {
+ if (DEBUG) this.context.debug("PNN: PNN is available");
+ this.readPNN();
+ } else {
+ if (DEBUG) this.context.debug("PNN: PNN is not available");
+ }
+
+ if (ICCUtilsHelper.isICCServiceAvailable("OPL")) {
+ if (DEBUG) this.context.debug("OPL: OPL is available");
+ this.readOPL();
+ } else {
+ if (DEBUG) this.context.debug("OPL: OPL is not available");
+ }
+
+ if (ICCUtilsHelper.isICCServiceAvailable("GID1")) {
+ if (DEBUG) this.context.debug("GID1: GID1 is available");
+ this.readGID1();
+ } else {
+ if (DEBUG) this.context.debug("GID1: GID1 is not available");
+ }
+
+ if (ICCUtilsHelper.isICCServiceAvailable("CBMI")) {
+ this.readCBMI();
+ } else {
+ RIL.cellBroadcastConfigs.CBMI = null;
+ }
+ if (ICCUtilsHelper.isICCServiceAvailable("DATA_DOWNLOAD_SMS_CB")) {
+ this.readCBMID();
+ } else {
+ RIL.cellBroadcastConfigs.CBMID = null;
+ }
+ if (ICCUtilsHelper.isICCServiceAvailable("CBMIR")) {
+ this.readCBMIR();
+ } else {
+ RIL.cellBroadcastConfigs.CBMIR = null;
+ }
+ RIL._mergeAllCellBroadcastConfigs();
+ }
+
+ // ICC_EF_UST has the same value with ICC_EF_SST.
+ this.context.ICCIOHelper.loadTransparentEF({
+ fileId: ICC_EF_SST,
+ callback: callback.bind(this)
+ });
+ },
+
+ /**
+ * Read (U)SIM MBDN. (Mailbox Dialling Number)
+ *
+ * @see TS 131.102, clause 4.2.60
+ */
+ readMBDN: function() {
+ function callback(options) {
+ let RIL = this.context.RIL;
+ let contact =
+ this.context.ICCPDUHelper.readAlphaIdDiallingNumber(options.recordSize);
+ if ((!contact ||
+ ((!contact.alphaId || contact.alphaId == "") &&
+ (!contact.number || contact.number == ""))) &&
+ this.context.ICCUtilsHelper.isCphsServiceAvailable("MBN")) {
+ // read CPHS_MBN in advance if MBDN is invalid or empty.
+ this.readCphsMBN();
+ return;
+ }
+
+ if (!contact ||
+ (RIL.iccInfoPrivate.mbdn !== undefined &&
+ RIL.iccInfoPrivate.mbdn === contact.number)) {
+ return;
+ }
+ RIL.iccInfoPrivate.mbdn = contact.number;
+ if (DEBUG) {
+ this.context.debug("MBDN, alphaId=" + contact.alphaId +
+ " number=" + contact.number);
+ }
+ contact.rilMessageType = "iccmbdn";
+ RIL.sendChromeMessage(contact);
+ }
+
+ this.context.ICCIOHelper.loadLinearFixedEF({
+ fileId: ICC_EF_MBDN,
+ callback: callback.bind(this)
+ });
+ },
+
+ /**
+ * Read ICC MWIS. (Message Waiting Indication Status)
+ *
+ * @see TS 31.102, clause 4.2.63 for USIM and TS 51.011, clause 10.3.45 for SIM.
+ */
+ readMWIS: function() {
+ function callback(options) {
+ let Buf = this.context.Buf;
+ let RIL = this.context.RIL;
+
+ let strLen = Buf.readInt32();
+ // Each octet is encoded into two chars.
+ let octetLen = strLen / 2;
+ let mwis = this.context.GsmPDUHelper.readHexOctetArray(octetLen);
+ Buf.readStringDelimiter(strLen);
+ if (!mwis) {
+ return;
+ }
+ RIL.iccInfoPrivate.mwis = mwis; //Keep raw MWIS for updateMWIS()
+
+ let mwi = {};
+ // b8 b7 B6 b5 b4 b3 b2 b1 4.2.63, TS 31.102 version 11.6.0
+ // | | | | | | | |__ Voicemail
+ // | | | | | | |_____ Fax
+ // | | | | | |________ Electronic Mail
+ // | | | | |___________ Other
+ // | | | |______________ Videomail
+ // |__|__|_________________ RFU
+ mwi.active = ((mwis[0] & 0x01) != 0);
+
+ if (mwi.active) {
+ // In TS 23.040 msgCount is in the range from 0 to 255.
+ // The value 255 shall be taken to mean 255 or greater.
+ //
+ // However, There is no definition about 0 when MWI is active.
+ //
+ // Normally, when mwi is active, the msgCount must be larger than 0.
+ // Refer to other reference phone,
+ // 0 is usually treated as UNKNOWN for storing 2nd level MWI status (DCS).
+ mwi.msgCount = (mwis[1] === 0) ? GECKO_VOICEMAIL_MESSAGE_COUNT_UNKNOWN
+ : mwis[1];
+ } else {
+ mwi.msgCount = 0;
+ }
+
+ RIL.sendChromeMessage({ rilMessageType: "iccmwis",
+ mwi: mwi });
+ }
+
+ this.context.ICCIOHelper.loadLinearFixedEF({
+ fileId: ICC_EF_MWIS,
+ recordNumber: 1, // Get 1st Subscriber Profile.
+ callback: callback.bind(this)
+ });
+ },
+
+ /**
+ * Update ICC MWIS. (Message Waiting Indication Status)
+ *
+ * @see TS 31.102, clause 4.2.63 for USIM and TS 51.011, clause 10.3.45 for SIM.
+ */
+ updateMWIS: function(mwi) {
+ let RIL = this.context.RIL;
+ if (!RIL.iccInfoPrivate.mwis) {
+ return;
+ }
+
+ function dataWriter(recordSize) {
+ let mwis = RIL.iccInfoPrivate.mwis;
+
+ let msgCount =
+ (mwi.msgCount === GECKO_VOICEMAIL_MESSAGE_COUNT_UNKNOWN) ? 0 : mwi.msgCount;
+
+ [mwis[0], mwis[1]] = (mwi.active) ? [(mwis[0] | 0x01), msgCount]
+ : [(mwis[0] & 0xFE), 0];
+
+ let strLen = recordSize * 2;
+ let Buf = this.context.Buf;
+ Buf.writeInt32(strLen);
+
+ let GsmPDUHelper = this.context.GsmPDUHelper;
+ for (let i = 0; i < mwis.length; i++) {
+ GsmPDUHelper.writeHexOctet(mwis[i]);
+ }
+
+ Buf.writeStringDelimiter(strLen);
+ }
+
+ this.context.ICCIOHelper.updateLinearFixedEF({
+ fileId: ICC_EF_MWIS,
+ recordNumber: 1, // Update 1st Subscriber Profile.
+ dataWriter: dataWriter.bind(this)
+ });
+ },
+
+ /**
+ * Read the SPDI (Service Provider Display Information) from the (U)SIM.
+ *
+ * See TS 131.102 section 4.2.66 for USIM and TS 51.011 section 10.3.50
+ * for SIM.
+ */
+ readSPDI: function() {
+ function callback() {
+ let Buf = this.context.Buf;
+ let strLen = Buf.readInt32();
+ let octetLen = strLen / 2;
+ let readLen = 0;
+ let endLoop = false;
+
+ let RIL = this.context.RIL;
+ RIL.iccInfoPrivate.SPDI = null;
+
+ let GsmPDUHelper = this.context.GsmPDUHelper;
+ while ((readLen < octetLen) && !endLoop) {
+ let tlvTag = GsmPDUHelper.readHexOctet();
+ let tlvLen = GsmPDUHelper.readHexOctet();
+ readLen += 2; // For tag and length fields.
+ switch (tlvTag) {
+ case SPDI_TAG_SPDI:
+ // The value part itself is a TLV.
+ continue;
+ case SPDI_TAG_PLMN_LIST:
+ // This PLMN list is what we want.
+ RIL.iccInfoPrivate.SPDI = this.readPLMNEntries(tlvLen / 3);
+ readLen += tlvLen;
+ endLoop = true;
+ break;
+ default:
+ // We don't care about its content if its tag is not SPDI nor
+ // PLMN_LIST.
+ endLoop = true;
+ break;
+ }
+ }
+
+ // Consume unread octets.
+ Buf.seekIncoming((octetLen - readLen) * Buf.PDU_HEX_OCTET_SIZE);
+ Buf.readStringDelimiter(strLen);
+
+ if (DEBUG) {
+ this.context.debug("SPDI: " + JSON.stringify(RIL.iccInfoPrivate.SPDI));
+ }
+ let ICCUtilsHelper = this.context.ICCUtilsHelper;
+ if (ICCUtilsHelper.updateDisplayCondition()) {
+ ICCUtilsHelper.handleICCInfoChange();
+ }
+ }
+
+ // PLMN List is Servive 51 in USIM, EF_SPDI
+ this.context.ICCIOHelper.loadTransparentEF({
+ fileId: ICC_EF_SPDI,
+ callback: callback.bind(this)
+ });
+ },
+
+ _readCbmiHelper: function(which) {
+ let RIL = this.context.RIL;
+
+ function callback() {
+ let Buf = this.context.Buf;
+ let strLength = Buf.readInt32();
+
+ // Each Message Identifier takes two octets and each octet is encoded
+ // into two chars.
+ let numIds = strLength / 4, list = null;
+ if (numIds) {
+ list = [];
+ let GsmPDUHelper = this.context.GsmPDUHelper;
+ for (let i = 0, id; i < numIds; i++) {
+ id = GsmPDUHelper.readHexOctet() << 8 | GsmPDUHelper.readHexOctet();
+ // `Unused entries shall be set to 'FF FF'.`
+ if (id != 0xFFFF) {
+ list.push(id);
+ list.push(id + 1);
+ }
+ }
+ }
+ if (DEBUG) {
+ this.context.debug(which + ": " + JSON.stringify(list));
+ }
+
+ Buf.readStringDelimiter(strLength);
+
+ RIL.cellBroadcastConfigs[which] = list;
+ RIL._mergeAllCellBroadcastConfigs();
+ }
+
+ function onerror() {
+ RIL.cellBroadcastConfigs[which] = null;
+ RIL._mergeAllCellBroadcastConfigs();
+ }
+
+ let fileId = GLOBAL["ICC_EF_" + which];
+ this.context.ICCIOHelper.loadTransparentEF({
+ fileId: fileId,
+ callback: callback.bind(this),
+ onerror: onerror.bind(this)
+ });
+ },
+
+ /**
+ * Read EFcbmi (Cell Broadcast Message Identifier selection)
+ *
+ * @see 3GPP TS 31.102 v110.02.0 section 4.2.14 EFcbmi
+ * @see 3GPP TS 51.011 v5.0.0 section 10.3.13 EFcbmi
+ */
+ readCBMI: function() {
+ this._readCbmiHelper("CBMI");
+ },
+
+ /**
+ * Read EFcbmid (Cell Broadcast Message Identifier for Data Download)
+ *
+ * @see 3GPP TS 31.102 v110.02.0 section 4.2.20 EFcbmid
+ * @see 3GPP TS 51.011 v5.0.0 section 10.3.26 EFcbmid
+ */
+ readCBMID: function() {
+ this._readCbmiHelper("CBMID");
+ },
+
+ /**
+ * Read EFcbmir (Cell Broadcast Message Identifier Range selection)
+ *
+ * @see 3GPP TS 31.102 v110.02.0 section 4.2.22 EFcbmir
+ * @see 3GPP TS 51.011 v5.0.0 section 10.3.28 EFcbmir
+ */
+ readCBMIR: function() {
+ let RIL = this.context.RIL;
+
+ function callback() {
+ let Buf = this.context.Buf;
+ let strLength = Buf.readInt32();
+
+ // Each Message Identifier range takes four octets and each octet is
+ // encoded into two chars.
+ let numIds = strLength / 8, list = null;
+ if (numIds) {
+ list = [];
+ let GsmPDUHelper = this.context.GsmPDUHelper;
+ for (let i = 0, from, to; i < numIds; i++) {
+ // `Bytes one and two of each range identifier equal the lower value
+ // of a cell broadcast range, bytes three and four equal the upper
+ // value of a cell broadcast range.`
+ from = GsmPDUHelper.readHexOctet() << 8 | GsmPDUHelper.readHexOctet();
+ to = GsmPDUHelper.readHexOctet() << 8 | GsmPDUHelper.readHexOctet();
+ // `Unused entries shall be set to 'FF FF'.`
+ if ((from != 0xFFFF) && (to != 0xFFFF)) {
+ list.push(from);
+ list.push(to + 1);
+ }
+ }
+ }
+ if (DEBUG) {
+ this.context.debug("CBMIR: " + JSON.stringify(list));
+ }
+
+ Buf.readStringDelimiter(strLength);
+
+ RIL.cellBroadcastConfigs.CBMIR = list;
+ RIL._mergeAllCellBroadcastConfigs();
+ }
+
+ function onerror() {
+ RIL.cellBroadcastConfigs.CBMIR = null;
+ RIL._mergeAllCellBroadcastConfigs();
+ }
+
+ this.context.ICCIOHelper.loadTransparentEF({
+ fileId: ICC_EF_CBMIR,
+ callback: callback.bind(this),
+ onerror: onerror.bind(this)
+ });
+ },
+
+ /**
+ * Read OPL (Operator PLMN List) from (U)SIM.
+ *
+ * See 3GPP TS 31.102 Sec. 4.2.59 for USIM
+ * 3GPP TS 51.011 Sec. 10.3.42 for SIM.
+ */
+ readOPL: function() {
+ let ICCIOHelper = this.context.ICCIOHelper;
+ let opl = [];
+ function callback(options) {
+ let Buf = this.context.Buf;
+ let GsmPDUHelper = this.context.GsmPDUHelper;
+
+ let strLen = Buf.readInt32();
+ // The first 7 bytes are LAI (for UMTS) and the format of LAI is defined
+ // in 3GPP TS 23.003, Sec 4.1
+ // +-------------+---------+
+ // | Octet 1 - 3 | MCC/MNC |
+ // +-------------+---------+
+ // | Octet 4 - 7 | LAC |
+ // +-------------+---------+
+ let mccMnc = [GsmPDUHelper.readHexOctet(),
+ GsmPDUHelper.readHexOctet(),
+ GsmPDUHelper.readHexOctet()];
+ if (mccMnc[0] != 0xFF || mccMnc[1] != 0xFF || mccMnc[2] != 0xFF) {
+ let oplElement = {};
+ let semiOctets = [];
+ for (let i = 0; i < mccMnc.length; i++) {
+ semiOctets.push((mccMnc[i] & 0xf0) >> 4);
+ semiOctets.push(mccMnc[i] & 0x0f);
+ }
+ let reformat = [semiOctets[1], semiOctets[0], semiOctets[3],
+ semiOctets[5], semiOctets[4], semiOctets[2]];
+ let buf = "";
+ for (let i = 0; i < reformat.length; i++) {
+ if (reformat[i] != 0xF) {
+ buf += GsmPDUHelper.semiOctetToExtendedBcdChar(reformat[i]);
+ }
+ if (i === 2) {
+ // 0-2: MCC
+ oplElement.mcc = buf;
+ buf = "";
+ } else if (i === 5) {
+ // 3-5: MNC
+ oplElement.mnc = buf;
+ }
+ }
+ // LAC/TAC
+ oplElement.lacTacStart =
+ (GsmPDUHelper.readHexOctet() << 8) | GsmPDUHelper.readHexOctet();
+ oplElement.lacTacEnd =
+ (GsmPDUHelper.readHexOctet() << 8) | GsmPDUHelper.readHexOctet();
+ // PLMN Network Name Record Identifier
+ oplElement.pnnRecordId = GsmPDUHelper.readHexOctet();
+ if (DEBUG) {
+ this.context.debug("OPL: [" + (opl.length + 1) + "]: " +
+ JSON.stringify(oplElement));
+ }
+ opl.push(oplElement);
+ } else {
+ Buf.seekIncoming(5 * Buf.PDU_HEX_OCTET_SIZE);
+ }
+ Buf.readStringDelimiter(strLen);
+
+ let RIL = this.context.RIL;
+ if (options.p1 < options.totalRecords) {
+ ICCIOHelper.loadNextRecord(options);
+ } else {
+ RIL.iccInfoPrivate.OPL = opl;
+ RIL.overrideICCNetworkName();
+ }
+ }
+
+ ICCIOHelper.loadLinearFixedEF({fileId: ICC_EF_OPL,
+ callback: callback.bind(this)});
+ },
+
+ /**
+ * Read PNN (PLMN Network Name) from (U)SIM.
+ *
+ * See 3GPP TS 31.102 Sec. 4.2.58 for USIM
+ * 3GPP TS 51.011 Sec. 10.3.41 for SIM.
+ */
+ readPNN: function() {
+ let ICCIOHelper = this.context.ICCIOHelper;
+ function callback(options) {
+ let pnnElement;
+ let Buf = this.context.Buf;
+ let strLen = Buf.readInt32();
+ let octetLen = strLen / 2;
+ let readLen = 0;
+
+ let GsmPDUHelper = this.context.GsmPDUHelper;
+ while (readLen < octetLen) {
+ let tlvTag = GsmPDUHelper.readHexOctet();
+
+ if (tlvTag == 0xFF) {
+ // Unused byte
+ readLen++;
+ Buf.seekIncoming((octetLen - readLen) * Buf.PDU_HEX_OCTET_SIZE);
+ break;
+ }
+
+ // Needs this check to avoid initializing twice.
+ pnnElement = pnnElement || {};
+
+ let tlvLen = GsmPDUHelper.readHexOctet();
+
+ switch (tlvTag) {
+ case PNN_IEI_FULL_NETWORK_NAME:
+ pnnElement.fullName = GsmPDUHelper.readNetworkName(tlvLen);
+ break;
+ case PNN_IEI_SHORT_NETWORK_NAME:
+ pnnElement.shortName = GsmPDUHelper.readNetworkName(tlvLen);
+ break;
+ default:
+ Buf.seekIncoming(tlvLen * Buf.PDU_HEX_OCTET_SIZE);
+ break;
+ }
+
+ readLen += (tlvLen + 2); // +2 for tlvTag and tlvLen
+ }
+ Buf.readStringDelimiter(strLen);
+
+ pnn.push(pnnElement);
+
+ let RIL = this.context.RIL;
+ if (options.p1 < options.totalRecords) {
+ ICCIOHelper.loadNextRecord(options);
+ } else {
+ if (DEBUG) {
+ for (let i = 0; i < pnn.length; i++) {
+ this.context.debug("PNN: [" + i + "]: " + JSON.stringify(pnn[i]));
+ }
+ }
+ RIL.iccInfoPrivate.PNN = pnn;
+ RIL.overrideICCNetworkName();
+ }
+ }
+
+ let pnn = [];
+ ICCIOHelper.loadLinearFixedEF({fileId: ICC_EF_PNN,
+ callback: callback.bind(this)});
+ },
+
+ /**
+ * Read the list of PLMN (Public Land Mobile Network) entries
+ * We cannot directly rely on readSwappedNibbleBcdToString(),
+ * since it will no correctly handle some corner-cases that are
+ * not a problem in our case (0xFF 0xFF 0xFF).
+ *
+ * @param length The number of PLMN records.
+ * @return An array of string corresponding to the PLMNs.
+ */
+ readPLMNEntries: function(length) {
+ let plmnList = [];
+ // Each PLMN entry has 3 bytes.
+ if (DEBUG) {
+ this.context.debug("PLMN entries length = " + length);
+ }
+ let GsmPDUHelper = this.context.GsmPDUHelper;
+ let index = 0;
+ while (index < length) {
+ // Unused entries will be 0xFFFFFF, according to EF_SPDI
+ // specs (TS 131 102, section 4.2.66)
+ try {
+ let plmn = [GsmPDUHelper.readHexOctet(),
+ GsmPDUHelper.readHexOctet(),
+ GsmPDUHelper.readHexOctet()];
+ if (DEBUG) {
+ this.context.debug("Reading PLMN entry: [" + index + "]: '" + plmn + "'");
+ }
+ if (plmn[0] != 0xFF &&
+ plmn[1] != 0xFF &&
+ plmn[2] != 0xFF) {
+ let semiOctets = [];
+ for (let idx = 0; idx < plmn.length; idx++) {
+ semiOctets.push((plmn[idx] & 0xF0) >> 4);
+ semiOctets.push(plmn[idx] & 0x0F);
+ }
+
+ // According to TS 24.301, 9.9.3.12, the semi octets is arranged
+ // in format:
+ // Byte 1: MCC[2] | MCC[1]
+ // Byte 2: MNC[3] | MCC[3]
+ // Byte 3: MNC[2] | MNC[1]
+ // Therefore, we need to rearrange them.
+ let reformat = [semiOctets[1], semiOctets[0], semiOctets[3],
+ semiOctets[5], semiOctets[4], semiOctets[2]];
+ let buf = "";
+ let plmnEntry = {};
+ for (let i = 0; i < reformat.length; i++) {
+ if (reformat[i] != 0xF) {
+ buf += GsmPDUHelper.semiOctetToExtendedBcdChar(reformat[i]);
+ }
+ if (i === 2) {
+ // 0-2: MCC
+ plmnEntry.mcc = buf;
+ buf = "";
+ } else if (i === 5) {
+ // 3-5: MNC
+ plmnEntry.mnc = buf;
+ }
+ }
+ if (DEBUG) {
+ this.context.debug("PLMN = " + plmnEntry.mcc + ", " + plmnEntry.mnc);
+ }
+ plmnList.push(plmnEntry);
+ }
+ } catch (e) {
+ if (DEBUG) {
+ this.context.debug("PLMN entry " + index + " is invalid.");
+ }
+ break;
+ }
+ index ++;
+ }
+ return plmnList;
+ },
+
+ /**
+ * Read the SMS from the ICC.
+ *
+ * @param recordNumber The number of the record shall be loaded.
+ * @param onsuccess Callback to be called when success.
+ * @param onerror Callback to be called when error.
+ */
+ readSMS: function(recordNumber, onsuccess, onerror) {
+ function callback(options) {
+ let Buf = this.context.Buf;
+ let strLen = Buf.readInt32();
+
+ // TS 51.011, 10.5.3 EF_SMS
+ // b3 b2 b1
+ // 0 0 1 message received by MS from network; message read
+ // 0 1 1 message received by MS from network; message to be read
+ // 1 1 1 MS originating message; message to be sent
+ // 1 0 1 MS originating message; message sent to the network:
+ let GsmPDUHelper = this.context.GsmPDUHelper;
+ let status = GsmPDUHelper.readHexOctet();
+
+ let message = GsmPDUHelper.readMessage();
+ message.simStatus = status;
+
+ // Consumes the remaining buffer
+ Buf.seekIncoming(Buf.getReadAvailable() - Buf.PDU_HEX_OCTET_SIZE);
+
+ Buf.readStringDelimiter(strLen);
+
+ if (message) {
+ onsuccess(message);
+ } else {
+ onerror("Failed to decode SMS on SIM #" + recordNumber);
+ }
+ }
+
+ this.context.ICCIOHelper.loadLinearFixedEF({
+ fileId: ICC_EF_SMS,
+ recordNumber: recordNumber,
+ callback: callback.bind(this),
+ onerror: onerror
+ });
+ },
+
+ readGID1: function() {
+ function callback() {
+ let Buf = this.context.Buf;
+ let RIL = this.context.RIL;
+
+ RIL.iccInfoPrivate.gid1 = Buf.readString();
+ if (DEBUG) {
+ this.context.debug("GID1: " + RIL.iccInfoPrivate.gid1);
+ }
+ }
+
+ this.context.ICCIOHelper.loadTransparentEF({
+ fileId: ICC_EF_GID1,
+ callback: callback.bind(this)
+ });
+ },
+
+ /**
+ * Read CPHS Phase & Service Table from CPHS Info.
+ *
+ * @See B.3.1.1 CPHS Information in CPHS Phase 2.
+ *
+ * @param onsuccess Callback to be called when success.
+ * @param onerror Callback to be called when error.
+ */
+ readCphsInfo: function(onsuccess, onerror) {
+ function callback() {
+ try {
+ let Buf = this.context.Buf;
+ let RIL = this.context.RIL;
+
+ let strLen = Buf.readInt32();
+ // Each octet is encoded into two chars.
+ let octetLen = strLen / 2;
+ let cphsInfo = this.context.GsmPDUHelper.readHexOctetArray(octetLen);
+ Buf.readStringDelimiter(strLen);
+ if (DEBUG) {
+ let str = "";
+ for (let i = 0; i < cphsInfo.length; i++) {
+ str += cphsInfo[i] + ", ";
+ }
+ this.context.debug("CPHS INFO: " + str);
+ }
+
+ /**
+ * CPHS INFORMATION
+ *
+ * Byte 1: CPHS Phase
+ * 01 phase 1
+ * 02 phase 2
+ * etc.
+ *
+ * Byte 2: CPHS Service Table
+ * +----+----+----+----+----+----+----+----+
+ * | b8 | b7 | b6 | b5 | b4 | b3 | b2 | b1 |
+ * +----+----+----+----+----+----+----+----+
+ * | ONSF | MBN | SST | CSP |
+ * | Phase 2 | ALL | Phase 1 | All |
+ * +----+----+----+----+----+----+----+----+
+ *
+ * Byte 3: CPHS Service Table continued
+ * +----+----+----+----+----+----+----+----+
+ * | b8 | b7 | b6 | b5 | b4 | b3 | b2 | b1 |
+ * +----+----+----+----+----+----+----+----+
+ * | RFU | RFU | RFU | INFO_NUM|
+ * | | | | Phase 2 |
+ * +----+----+----+----+----+----+----+----+
+ */
+ let cphsPhase = cphsInfo[0];
+ if (cphsPhase == 1) {
+ // Clear 'Phase 2 only' services.
+ cphsInfo[1] &= 0x3F;
+ // We don't know whether Byte 3 is available in CPHS phase 1 or not.
+ // Add boundary check before accessing it.
+ if (cphsInfo.length > 2) {
+ cphsInfo[2] = 0x00;
+ }
+ } else if (cphsPhase == 2) {
+ // Clear 'Phase 1 only' services.
+ cphsInfo[1] &= 0xF3;
+ } else {
+ throw new Error("Unknown CPHS phase: " + cphsPhase);
+ }
+
+ RIL.iccInfoPrivate.cphsSt = cphsInfo.subarray(1);
+ onsuccess();
+ } catch(e) {
+ onerror(e.toString());
+ }
+ }
+
+ this.context.ICCIOHelper.loadTransparentEF({
+ fileId: ICC_EF_CPHS_INFO,
+ callback: callback.bind(this),
+ onerror: onerror
+ });
+ },
+
+ /**
+ * Read CPHS MBN. (Mailbox Numbers)
+ *
+ * @See B.4.2.2 Voice Message Retrieval and Indicator Clearing
+ */
+ readCphsMBN: function() {
+ function callback(options) {
+ let RIL = this.context.RIL;
+ let contact =
+ this.context.ICCPDUHelper.readAlphaIdDiallingNumber(options.recordSize);
+ if (!contact ||
+ (RIL.iccInfoPrivate.mbdn !== undefined &&
+ RIL.iccInfoPrivate.mbdn === contact.number)) {
+ return;
+ }
+ RIL.iccInfoPrivate.mbdn = contact.number;
+ if (DEBUG) {
+ this.context.debug("CPHS_MDN, alphaId=" + contact.alphaId +
+ " number=" + contact.number);
+ }
+ contact.rilMessageType = "iccmbdn";
+ RIL.sendChromeMessage(contact);
+ }
+
+ this.context.ICCIOHelper.loadLinearFixedEF({
+ fileId: ICC_EF_CPHS_MBN,
+ callback: callback.bind(this)
+ });
+ }
+};
+
+function RuimRecordHelperObject(aContext) {
+ this.context = aContext;
+}
+RuimRecordHelperObject.prototype = {
+ context: null,
+
+ fetchRuimRecords: function() {
+ this.getIMSI_M();
+ this.readCST();
+ this.readCDMAHome();
+ this.context.RIL.getCdmaSubscription();
+ },
+
+ /**
+ * Get IMSI_M from CSIM/RUIM.
+ * See 3GPP2 C.S0065 Sec. 5.2.2
+ */
+ getIMSI_M: function() {
+ function callback() {
+ let Buf = this.context.Buf;
+ let strLen = Buf.readInt32();
+ let encodedImsi = this.context.GsmPDUHelper.readHexOctetArray(strLen / 2);
+ Buf.readStringDelimiter(strLen);
+
+ if ((encodedImsi[CSIM_IMSI_M_PROGRAMMED_BYTE] & 0x80)) { // IMSI_M programmed
+ let RIL = this.context.RIL;
+ RIL.iccInfoPrivate.imsi = this.decodeIMSI(encodedImsi);
+ RIL.sendChromeMessage({rilMessageType: "iccimsi",
+ imsi: RIL.iccInfoPrivate.imsi});
+
+ let ICCUtilsHelper = this.context.ICCUtilsHelper;
+ let mccMnc = ICCUtilsHelper.parseMccMncFromImsi(RIL.iccInfoPrivate.imsi);
+ if (mccMnc) {
+ RIL.iccInfo.mcc = mccMnc.mcc;
+ RIL.iccInfo.mnc = mccMnc.mnc;
+ ICCUtilsHelper.handleICCInfoChange();
+ }
+ }
+ }
+
+ this.context.ICCIOHelper.loadTransparentEF({
+ fileId: ICC_EF_CSIM_IMSI_M,
+ callback: callback.bind(this)
+ });
+ },
+
+ /**
+ * Decode IMSI from IMSI_M
+ * See 3GPP2 C.S0005 Sec. 2.3.1
+ * +---+---------+------------+---+--------+---------+---+---------+--------+
+ * |RFU| MCC | programmed |RFU| MNC | MIN1 |RFU| MIN2 | CLASS |
+ * +---+---------+------------+---+--------+---------+---+---------+--------+
+ * | 6 | 10 bits | 8 bits | 1 | 7 bits | 24 bits | 6 | 10 bits | 8 bits |
+ * +---+---------+------------+---+--------+---------+---+---------+--------+
+ */
+ decodeIMSI: function(encodedImsi) {
+ // MCC: 10 bits, 3 digits
+ let encodedMCC = ((encodedImsi[CSIM_IMSI_M_MCC_BYTE + 1] & 0x03) << 8) +
+ (encodedImsi[CSIM_IMSI_M_MCC_BYTE] & 0xff);
+ let mcc = this.decodeIMSIValue(encodedMCC, 3);
+
+ // MNC: 7 bits, 2 digits
+ let encodedMNC = encodedImsi[CSIM_IMSI_M_MNC_BYTE] & 0x7f;
+ let mnc = this.decodeIMSIValue(encodedMNC, 2);
+
+ // MIN2: 10 bits, 3 digits
+ let encodedMIN2 = ((encodedImsi[CSIM_IMSI_M_MIN2_BYTE + 1] & 0x03) << 8) +
+ (encodedImsi[CSIM_IMSI_M_MIN2_BYTE] & 0xff);
+ let min2 = this.decodeIMSIValue(encodedMIN2, 3);
+
+ // MIN1: 10+4+10 bits, 3+1+3 digits
+ let encodedMIN1First3 = ((encodedImsi[CSIM_IMSI_M_MIN1_BYTE + 2] & 0xff) << 2) +
+ ((encodedImsi[CSIM_IMSI_M_MIN1_BYTE + 1] & 0xc0) >> 6);
+ let min1First3 = this.decodeIMSIValue(encodedMIN1First3, 3);
+
+ let encodedFourthDigit = (encodedImsi[CSIM_IMSI_M_MIN1_BYTE + 1] & 0x3c) >> 2;
+ if (encodedFourthDigit > 9) {
+ encodedFourthDigit = 0;
+ }
+ let fourthDigit = encodedFourthDigit.toString();
+
+ let encodedMIN1Last3 = ((encodedImsi[CSIM_IMSI_M_MIN1_BYTE + 1] & 0x03) << 8) +
+ (encodedImsi[CSIM_IMSI_M_MIN1_BYTE] & 0xff);
+ let min1Last3 = this.decodeIMSIValue(encodedMIN1Last3, 3);
+
+ return mcc + mnc + min2 + min1First3 + fourthDigit + min1Last3;
+ },
+
+ /**
+ * Decode IMSI Helper function
+ * See 3GPP2 C.S0005 section 2.3.1.1
+ */
+ decodeIMSIValue: function(encoded, length) {
+ let offset = length === 3 ? 111 : 11;
+ let value = encoded + offset;
+
+ for (let base = 10, temp = value, i = 0; i < length; i++) {
+ if (temp % 10 === 0) {
+ value -= base;
+ }
+ temp = Math.floor(value / base);
+ base = base * 10;
+ }
+
+ let s = value.toString();
+ while (s.length < length) {
+ s = "0" + s;
+ }
+
+ return s;
+ },
+
+ /**
+ * Read CDMAHOME for CSIM.
+ * See 3GPP2 C.S0023 Sec. 3.4.8.
+ */
+ readCDMAHome: function() {
+ let ICCIOHelper = this.context.ICCIOHelper;
+
+ function callback(options) {
+ let Buf = this.context.Buf;
+ let GsmPDUHelper = this.context.GsmPDUHelper;
+
+ let strLen = Buf.readInt32();
+ let tempOctet = GsmPDUHelper.readHexOctet();
+ cdmaHomeSystemId.push(((GsmPDUHelper.readHexOctet() & 0x7f) << 8) | tempOctet);
+ tempOctet = GsmPDUHelper.readHexOctet();
+ cdmaHomeNetworkId.push(((GsmPDUHelper.readHexOctet() & 0xff) << 8) | tempOctet);
+
+ // Consuming the last octet: band class.
+ Buf.seekIncoming(Buf.PDU_HEX_OCTET_SIZE);
+
+ Buf.readStringDelimiter(strLen);
+ if (options.p1 < options.totalRecords) {
+ ICCIOHelper.loadNextRecord(options);
+ } else {
+ if (DEBUG) {
+ this.context.debug("CDMAHome system id: " +
+ JSON.stringify(cdmaHomeSystemId));
+ this.context.debug("CDMAHome network id: " +
+ JSON.stringify(cdmaHomeNetworkId));
+ }
+ this.context.RIL.cdmaHome = {
+ systemId: cdmaHomeSystemId,
+ networkId: cdmaHomeNetworkId
+ };
+ }
+ }
+
+ let cdmaHomeSystemId = [], cdmaHomeNetworkId = [];
+ ICCIOHelper.loadLinearFixedEF({fileId: ICC_EF_CSIM_CDMAHOME,
+ callback: callback.bind(this)});
+ },
+
+ /**
+ * Read CDMA Service Table.
+ * See 3GPP2 C.S0023 Sec. 3.4.18
+ */
+ readCST: function() {
+ function callback() {
+ let Buf = this.context.Buf;
+ let RIL = this.context.RIL;
+
+ let strLen = Buf.readInt32();
+ // Each octet is encoded into two chars.
+ RIL.iccInfoPrivate.cst =
+ this.context.GsmPDUHelper.readHexOctetArray(strLen / 2);
+ Buf.readStringDelimiter(strLen);
+
+ if (DEBUG) {
+ let str = "";
+ for (let i = 0; i < RIL.iccInfoPrivate.cst.length; i++) {
+ str += RIL.iccInfoPrivate.cst[i] + ", ";
+ }
+ this.context.debug("CST: " + str);
+ }
+
+ if (this.context.ICCUtilsHelper.isICCServiceAvailable("SPN")) {
+ if (DEBUG) this.context.debug("SPN: SPN is available");
+ this.readSPN();
+ }
+ }
+ this.context.ICCIOHelper.loadTransparentEF({
+ fileId: ICC_EF_CSIM_CST,
+ callback: callback.bind(this)
+ });
+ },
+
+ readSPN: function() {
+ function callback() {
+ let Buf = this.context.Buf;
+ let strLen = Buf.readInt32();
+ let octetLen = strLen / 2;
+
+ let GsmPDUHelper = this.context.GsmPDUHelper;
+ let displayCondition = GsmPDUHelper.readHexOctet();
+ let codingScheme = GsmPDUHelper.readHexOctet();
+ // Skip one octet: language indicator.
+ Buf.seekIncoming(Buf.PDU_HEX_OCTET_SIZE);
+ let readLen = 3;
+
+ // SPN String ends up with 0xff.
+ let userDataBuffer = [];
+
+ while (readLen < octetLen) {
+ let octet = GsmPDUHelper.readHexOctet();
+ readLen++;
+ if (octet == 0xff) {
+ break;
+ }
+ userDataBuffer.push(octet);
+ }
+
+ this.context.BitBufferHelper.startRead(userDataBuffer);
+
+ let CdmaPDUHelper = this.context.CdmaPDUHelper;
+ let msgLen;
+ switch (CdmaPDUHelper.getCdmaMsgEncoding(codingScheme)) {
+ case PDU_DCS_MSG_CODING_7BITS_ALPHABET:
+ msgLen = Math.floor(userDataBuffer.length * 8 / 7);
+ break;
+ case PDU_DCS_MSG_CODING_8BITS_ALPHABET:
+ msgLen = userDataBuffer.length;
+ break;
+ case PDU_DCS_MSG_CODING_16BITS_ALPHABET:
+ msgLen = Math.floor(userDataBuffer.length / 2);
+ break;
+ }
+
+ let RIL = this.context.RIL;
+ RIL.iccInfo.spn = CdmaPDUHelper.decodeCdmaPDUMsg(codingScheme, null, msgLen);
+ if (DEBUG) {
+ this.context.debug("CDMA SPN: " + RIL.iccInfo.spn +
+ ", Display condition: " + displayCondition);
+ }
+ RIL.iccInfoPrivate.spnDisplayCondition = displayCondition;
+ Buf.seekIncoming((octetLen - readLen) * Buf.PDU_HEX_OCTET_SIZE);
+ Buf.readStringDelimiter(strLen);
+ }
+
+ this.context.ICCIOHelper.loadTransparentEF({
+ fileId: ICC_EF_CSIM_SPN,
+ callback: callback.bind(this)
+ });
+ }
+};
+
+/**
+ * Helper functions for ICC utilities.
+ */
+function ICCUtilsHelperObject(aContext) {
+ this.context = aContext;
+}
+ICCUtilsHelperObject.prototype = {
+ context: null,
+
+ /**
+ * Get network names by using EF_OPL and EF_PNN
+ *
+ * @See 3GPP TS 31.102 sec. 4.2.58 and sec. 4.2.59 for USIM,
+ * 3GPP TS 51.011 sec. 10.3.41 and sec. 10.3.42 for SIM.
+ *
+ * @param mcc The mobile country code of the network.
+ * @param mnc The mobile network code of the network.
+ * @param lac The location area code of the network.
+ */
+ getNetworkNameFromICC: function(mcc, mnc, lac) {
+ let RIL = this.context.RIL;
+ let iccInfoPriv = RIL.iccInfoPrivate;
+ let iccInfo = RIL.iccInfo;
+ let pnnEntry;
+
+ if (!mcc || !mnc || lac == null || lac < 0) {
+ return null;
+ }
+
+ // We won't get network name if there is no PNN file.
+ if (!iccInfoPriv.PNN) {
+ return null;
+ }
+
+ if (!this.isICCServiceAvailable("OPL")) {
+ // When OPL is not present:
+ // According to 3GPP TS 31.102 Sec. 4.2.58 and 3GPP TS 51.011 Sec. 10.3.41,
+ // If EF_OPL is not present, the first record in this EF is used for the
+ // default network name when registered to the HPLMN.
+ // If we haven't get pnnEntry assigned, we should try to assign default
+ // value to it.
+ if (mcc == iccInfo.mcc && mnc == iccInfo.mnc) {
+ pnnEntry = iccInfoPriv.PNN[0];
+ }
+ } else {
+ let GsmPDUHelper = this.context.GsmPDUHelper;
+ let wildChar = GsmPDUHelper.extendedBcdChars.charAt(0x0d);
+ // According to 3GPP TS 31.102 Sec. 4.2.59 and 3GPP TS 51.011 Sec. 10.3.42,
+ // the ME shall use this EF_OPL in association with the EF_PNN in place
+ // of any network name stored within the ME's internal list and any network
+ // name received when registered to the PLMN.
+ let length = iccInfoPriv.OPL ? iccInfoPriv.OPL.length : 0;
+ for (let i = 0; i < length; i++) {
+ let unmatch = false;
+ let opl = iccInfoPriv.OPL[i];
+ // Try to match the MCC/MNC. Besides, A BCD value of 'D' in any of the
+ // MCC and/or MNC digits shall be used to indicate a "wild" value for
+ // that corresponding MCC/MNC digit.
+ if (opl.mcc.indexOf(wildChar) !== -1) {
+ for (let j = 0; j < opl.mcc.length; j++) {
+ if (opl.mcc[j] !== wildChar && opl.mcc[j] !== mcc[j]) {
+ unmatch = true;
+ break;
+ }
+ }
+ if (unmatch) {
+ continue;
+ }
+ } else {
+ if (mcc !== opl.mcc) {
+ continue;
+ }
+ }
+
+ if (mnc.length !== opl.mnc.length) {
+ continue;
+ }
+
+ if (opl.mnc.indexOf(wildChar) !== -1) {
+ for (let j = 0; j < opl.mnc.length; j++) {
+ if (opl.mnc[j] !== wildChar && opl.mnc[j] !== mnc[j]) {
+ unmatch = true;
+ break;
+ }
+ }
+ if (unmatch) {
+ continue;
+ }
+ } else {
+ if (mnc !== opl.mnc) {
+ continue;
+ }
+ }
+
+ // Try to match the location area code. If current local area code is
+ // covered by lac range that specified in the OPL entry, use the PNN
+ // that specified in the OPL entry.
+ if ((opl.lacTacStart === 0x0 && opl.lacTacEnd == 0xFFFE) ||
+ (opl.lacTacStart <= lac && opl.lacTacEnd >= lac)) {
+ if (opl.pnnRecordId === 0) {
+ // See 3GPP TS 31.102 Sec. 4.2.59 and 3GPP TS 51.011 Sec. 10.3.42,
+ // A value of '00' indicates that the name is to be taken from other
+ // sources.
+ return null;
+ }
+ pnnEntry = iccInfoPriv.PNN[opl.pnnRecordId - 1];
+ break;
+ }
+ }
+ }
+
+ if (!pnnEntry) {
+ return null;
+ }
+
+ // Return a new object to avoid global variable, PNN, be modified by accident.
+ return { fullName: pnnEntry.fullName || "",
+ shortName: pnnEntry.shortName || "" };
+ },
+
+ /**
+ * This will compute the spnDisplay field of the network.
+ * See TS 22.101 Annex A and TS 51.011 10.3.11 for details.
+ *
+ * @return True if some of iccInfo is changed in by this function.
+ */
+ updateDisplayCondition: function() {
+ let RIL = this.context.RIL;
+
+ // If EFspn isn't existed in SIM or it haven't been read yet, we should
+ // just set isDisplayNetworkNameRequired = true and
+ // isDisplaySpnRequired = false
+ let iccInfo = RIL.iccInfo;
+ let iccInfoPriv = RIL.iccInfoPrivate;
+ let displayCondition = iccInfoPriv.spnDisplayCondition;
+ let origIsDisplayNetworkNameRequired = iccInfo.isDisplayNetworkNameRequired;
+ let origIsDisplaySPNRequired = iccInfo.isDisplaySpnRequired;
+
+ if (displayCondition === undefined) {
+ iccInfo.isDisplayNetworkNameRequired = true;
+ iccInfo.isDisplaySpnRequired = false;
+ } else if (RIL._isCdma) {
+ // CDMA family display rule.
+ let cdmaHome = RIL.cdmaHome;
+ let cell = RIL.voiceRegistrationState.cell;
+ let sid = cell && cell.cdmaSystemId;
+ let nid = cell && cell.cdmaNetworkId;
+
+ iccInfo.isDisplayNetworkNameRequired = false;
+
+ // If display condition is 0x0, we don't even need to check network id
+ // or system id.
+ if (displayCondition === 0x0) {
+ iccInfo.isDisplaySpnRequired = false;
+ } else {
+ // CDMA SPN Display condition dosen't specify whenever network name is
+ // reqired.
+ if (!cdmaHome ||
+ !cdmaHome.systemId ||
+ cdmaHome.systemId.length === 0 ||
+ cdmaHome.systemId.length != cdmaHome.networkId.length ||
+ !sid || !nid) {
+ // CDMA Home haven't been ready, or we haven't got the system id and
+ // network id of the network we register to, assuming we are in home
+ // network.
+ iccInfo.isDisplaySpnRequired = true;
+ } else {
+ // Determine if we are registered in the home service area.
+ // System ID and Network ID are described in 3GPP2 C.S0005 Sec. 2.6.5.2.
+ let inHomeArea = false;
+ for (let i = 0; i < cdmaHome.systemId.length; i++) {
+ let homeSid = cdmaHome.systemId[i],
+ homeNid = cdmaHome.networkId[i];
+ if (homeSid === 0 || homeNid === 0 // Reserved system id/network id
+ || homeSid != sid) {
+ continue;
+ }
+ // According to 3GPP2 C.S0005 Sec. 2.6.5.2, NID number 65535 means
+ // all networks in the system should be considered as home.
+ if (homeNid == 65535 || homeNid == nid) {
+ inHomeArea = true;
+ break;
+ }
+ }
+ iccInfo.isDisplaySpnRequired = inHomeArea;
+ }
+ }
+ } else {
+ // GSM family display rule.
+ let operatorMnc = RIL.operator ? RIL.operator.mnc : -1;
+ let operatorMcc = RIL.operator ? RIL.operator.mcc : -1;
+
+ // First detect if we are on HPLMN or one of the PLMN
+ // specified by the SIM card.
+ let isOnMatchingPlmn = false;
+
+ // If the current network is the one defined as mcc/mnc
+ // in SIM card, it's okay.
+ if (iccInfo.mcc == operatorMcc && iccInfo.mnc == operatorMnc) {
+ isOnMatchingPlmn = true;
+ }
+
+ // Test to see if operator's mcc/mnc match mcc/mnc of PLMN.
+ if (!isOnMatchingPlmn && iccInfoPriv.SPDI) {
+ let iccSpdi = iccInfoPriv.SPDI; // PLMN list
+ for (let plmn in iccSpdi) {
+ let plmnMcc = iccSpdi[plmn].mcc;
+ let plmnMnc = iccSpdi[plmn].mnc;
+ isOnMatchingPlmn = (plmnMcc == operatorMcc) && (plmnMnc == operatorMnc);
+ if (isOnMatchingPlmn) {
+ break;
+ }
+ }
+ }
+
+ // See 3GPP TS 22.101 A.4 Service Provider Name indication, and TS 31.102
+ // clause 4.2.12 EF_SPN for detail.
+ if (isOnMatchingPlmn) {
+ // The first bit of display condition tells us if we should display
+ // registered PLMN.
+ if (DEBUG) {
+ this.context.debug("PLMN is HPLMN or PLMN " + "is in PLMN list");
+ }
+
+ // TS 31.102 Sec. 4.2.66 and TS 51.011 Sec. 10.3.50
+ // EF_SPDI contains a list of PLMNs in which the Service Provider Name
+ // shall be displayed.
+ iccInfo.isDisplaySpnRequired = true;
+ iccInfo.isDisplayNetworkNameRequired = (displayCondition & 0x01) !== 0;
+ } else {
+ // The second bit of display condition tells us if we should display
+ // registered PLMN.
+ if (DEBUG) {
+ this.context.debug("PLMN isn't HPLMN and PLMN isn't in PLMN list");
+ }
+
+ iccInfo.isDisplayNetworkNameRequired = true;
+ iccInfo.isDisplaySpnRequired = (displayCondition & 0x02) === 0;
+ }
+ }
+
+ if (DEBUG) {
+ this.context.debug("isDisplayNetworkNameRequired = " +
+ iccInfo.isDisplayNetworkNameRequired);
+ this.context.debug("isDisplaySpnRequired = " + iccInfo.isDisplaySpnRequired);
+ }
+
+ return ((origIsDisplayNetworkNameRequired !== iccInfo.isDisplayNetworkNameRequired) ||
+ (origIsDisplaySPNRequired !== iccInfo.isDisplaySpnRequired));
+ },
+
+ decodeSimTlvs: function(tlvsLen) {
+ let GsmPDUHelper = this.context.GsmPDUHelper;
+
+ let index = 0;
+ let tlvs = [];
+ while (index < tlvsLen) {
+ let simTlv = {
+ tag : GsmPDUHelper.readHexOctet(),
+ length : GsmPDUHelper.readHexOctet(),
+ };
+ simTlv.value = GsmPDUHelper.readHexOctetArray(simTlv.length);
+ tlvs.push(simTlv);
+ index += simTlv.length + 2; // The length of 'tag' and 'length' field.
+ }
+ return tlvs;
+ },
+
+ /**
+ * Parse those TLVs and convert it to an object.
+ */
+ parsePbrTlvs: function(pbrTlvs) {
+ let pbr = {};
+ for (let i = 0; i < pbrTlvs.length; i++) {
+ let pbrTlv = pbrTlvs[i];
+ let anrIndex = 0;
+ for (let j = 0; j < pbrTlv.value.length; j++) {
+ let tlv = pbrTlv.value[j];
+ let tagName = USIM_TAG_NAME[tlv.tag];
+
+ // ANR could have multiple files. We save it as anr0, anr1,...etc.
+ if (tlv.tag == ICC_USIM_EFANR_TAG) {
+ tagName += anrIndex;
+ anrIndex++;
+ }
+ pbr[tagName] = tlv;
+ pbr[tagName].fileType = pbrTlv.tag;
+ pbr[tagName].fileId = (tlv.value[0] << 8) | tlv.value[1];
+ pbr[tagName].sfi = tlv.value[2];
+
+ // For Type 2, the order of files is in the same order in IAP.
+ if (pbrTlv.tag == ICC_USIM_TYPE2_TAG) {
+ pbr[tagName].indexInIAP = j;
+ }
+ }
+ }
+
+ return pbr;
+ },
+
+ /**
+ * Update the ICC information to RadioInterfaceLayer.
+ */
+ handleICCInfoChange: function() {
+ let RIL = this.context.RIL;
+ RIL.iccInfo.rilMessageType = "iccinfochange";
+ RIL.sendChromeMessage(RIL.iccInfo);
+ },
+
+ /**
+ * Get whether specificed (U)SIM service is available.
+ *
+ * @param geckoService
+ * Service name like "ADN", "BDN", etc.
+ *
+ * @return true if the service is enabled, false otherwise.
+ */
+ isICCServiceAvailable: function(geckoService) {
+ let RIL = this.context.RIL;
+ let serviceTable = RIL._isCdma ? RIL.iccInfoPrivate.cst:
+ RIL.iccInfoPrivate.sst;
+ let index, bitmask;
+ if (RIL.appType == CARD_APPTYPE_SIM || RIL.appType == CARD_APPTYPE_RUIM) {
+ /**
+ * Service id is valid in 1..N, and 2 bits are used to code each service.
+ *
+ * +----+-- --+----+----+
+ * | b8 | ... | b2 | b1 |
+ * +----+-- --+----+----+
+ *
+ * b1 = 0, service not allocated.
+ * 1, service allocated.
+ * b2 = 0, service not activated.
+ * 1, service activated.
+ *
+ * @see 3GPP TS 51.011 10.3.7.
+ */
+ let simService;
+ if (RIL.appType == CARD_APPTYPE_SIM) {
+ simService = GECKO_ICC_SERVICES.sim[geckoService];
+ } else {
+ simService = GECKO_ICC_SERVICES.ruim[geckoService];
+ }
+ if (!simService) {
+ return false;
+ }
+ simService -= 1;
+ index = Math.floor(simService / 4);
+ bitmask = 2 << ((simService % 4) << 1);
+ } else if (RIL.appType == CARD_APPTYPE_USIM) {
+ /**
+ * Service id is valid in 1..N, and 1 bit is used to code each service.
+ *
+ * +----+-- --+----+----+
+ * | b8 | ... | b2 | b1 |
+ * +----+-- --+----+----+
+ *
+ * b1 = 0, service not avaiable.
+ * 1, service available.
+ *
+ * @see 3GPP TS 31.102 4.2.8.
+ */
+ let usimService = GECKO_ICC_SERVICES.usim[geckoService];
+ if (!usimService) {
+ return false;
+ }
+ usimService -= 1;
+ index = Math.floor(usimService / 8);
+ bitmask = 1 << ((usimService % 8) << 0);
+ }
+
+ return (serviceTable !== null) &&
+ (index < serviceTable.length) &&
+ ((serviceTable[index] & bitmask) !== 0);
+ },
+
+ /**
+ * Get whether specificed CPHS service is available.
+ *
+ * @param geckoService
+ * Service name like "MDN", etc.
+ *
+ * @return true if the service is enabled, false otherwise.
+ */
+ isCphsServiceAvailable: function(geckoService) {
+ let RIL = this.context.RIL;
+ let serviceTable = RIL.iccInfoPrivate.cphsSt;
+
+ if (!(serviceTable instanceof Uint8Array)) {
+ return false;
+ }
+
+ /**
+ * Service id is valid in 1..N, and 2 bits are used to code each service.
+ *
+ * +----+-- --+----+----+
+ * | b8 | ... | b2 | b1 |
+ * +----+-- --+----+----+
+ *
+ * b1 = 0, service not allocated.
+ * 1, service allocated.
+ * b2 = 0, service not activated.
+ * 1, service activated.
+ *
+ * @See B.3.1.1 CPHS Information in CPHS Phase 2.
+ */
+ let cphsService = GECKO_ICC_SERVICES.cphs[geckoService];
+
+ if (!cphsService) {
+ return false;
+ }
+ cphsService -= 1;
+ let index = Math.floor(cphsService / 4);
+ let bitmask = 2 << ((cphsService % 4) << 1);
+
+ return (index < serviceTable.length) &&
+ ((serviceTable[index] & bitmask) !== 0);
+ },
+
+ /**
+ * Check if the string is of GSM default 7-bit coded alphabets with bit 8
+ * set to 0.
+ *
+ * @param str String to be checked.
+ */
+ isGsm8BitAlphabet: function(str) {
+ if (!str) {
+ return false;
+ }
+
+ const langTable = PDU_NL_LOCKING_SHIFT_TABLES[PDU_NL_IDENTIFIER_DEFAULT];
+ const langShiftTable = PDU_NL_SINGLE_SHIFT_TABLES[PDU_NL_IDENTIFIER_DEFAULT];
+
+ for (let i = 0; i < str.length; i++) {
+ let c = str.charAt(i);
+ let octet = langTable.indexOf(c);
+ if (octet == -1) {
+ octet = langShiftTable.indexOf(c);
+ if (octet == -1) {
+ return false;
+ }
+ }
+ }
+
+ return true;
+ },
+
+ /**
+ * Parse MCC/MNC from IMSI. If there is no available value for the length of
+ * mnc, it will use the data in MCC table to parse.
+ *
+ * @param imsi
+ * The imsi of icc.
+ * @param mncLength [optional]
+ * The length of mnc.
+ * Zero indicates we haven't got a valid mnc length.
+ *
+ * @return An object contains the parsing result of mcc and mnc.
+ * Or null if any error occurred.
+ */
+ parseMccMncFromImsi: function(imsi, mncLength) {
+ if (!imsi) {
+ return null;
+ }
+
+ // MCC is the first 3 digits of IMSI.
+ let mcc = imsi.substr(0,3);
+ if (!mncLength) {
+ // Check the MCC/MNC table for MNC length = 3 first for the case we don't
+ // have the 4th byte data from EF_AD.
+ if (PLMN_HAVING_3DIGITS_MNC[mcc] &&
+ PLMN_HAVING_3DIGITS_MNC[mcc].indexOf(imsi.substr(3, 3)) !== -1) {
+ mncLength = 3;
+ } else {
+ // Check the MCC table to decide the length of MNC.
+ let index = MCC_TABLE_FOR_MNC_LENGTH_IS_3.indexOf(mcc);
+ mncLength = (index !== -1) ? 3 : 2;
+ }
+ }
+ let mnc = imsi.substr(3, mncLength);
+ if (DEBUG) {
+ this.context.debug("IMSI: " + imsi + " MCC: " + mcc + " MNC: " + mnc);
+ }
+
+ return { mcc: mcc, mnc: mnc};
+ },
+};
+
+/**
+ * Helper for ICC Contacts.
+ */
+function ICCContactHelperObject(aContext) {
+ this.context = aContext;
+}
+ICCContactHelperObject.prototype = {
+ context: null,
+
+ /**
+ * Helper function to check DF_PHONEBOOK.
+ */
+ hasDfPhoneBook: function(appType) {
+ switch (appType) {
+ case CARD_APPTYPE_SIM:
+ return false;
+ case CARD_APPTYPE_USIM:
+ return true;
+ case CARD_APPTYPE_RUIM:
+ let ICCUtilsHelper = this.context.ICCUtilsHelper;
+ return ICCUtilsHelper.isICCServiceAvailable("ENHANCED_PHONEBOOK");
+ default:
+ return false;
+ }
+ },
+
+ /**
+ * Helper function to read ICC contacts.
+ *
+ * @param appType One of CARD_APPTYPE_*.
+ * @param contactType One of GECKO_CARDCONTACT_TYPE_*.
+ * @param onsuccess Callback to be called when success.
+ * @param onerror Callback to be called when error.
+ */
+ readICCContacts: function(appType, contactType, onsuccess, onerror) {
+ let ICCRecordHelper = this.context.ICCRecordHelper;
+ let ICCUtilsHelper = this.context.ICCUtilsHelper;
+
+ switch (contactType) {
+ case GECKO_CARDCONTACT_TYPE_ADN:
+ if (!this.hasDfPhoneBook(appType)) {
+ ICCRecordHelper.readADNLike(ICC_EF_ADN,
+ (ICCUtilsHelper.isICCServiceAvailable("EXT1")) ? ICC_EF_EXT1 : null,
+ onsuccess, onerror);
+ } else {
+ this.readUSimContacts(onsuccess, onerror);
+ }
+ break;
+ case GECKO_CARDCONTACT_TYPE_FDN:
+ if (!ICCUtilsHelper.isICCServiceAvailable("FDN")) {
+ onerror(CONTACT_ERR_CONTACT_TYPE_NOT_SUPPORTED);
+ break;
+ }
+ ICCRecordHelper.readADNLike(ICC_EF_FDN,
+ (ICCUtilsHelper.isICCServiceAvailable("EXT2")) ? ICC_EF_EXT2 : null,
+ onsuccess, onerror);
+ break;
+ case GECKO_CARDCONTACT_TYPE_SDN:
+ if (!ICCUtilsHelper.isICCServiceAvailable("SDN")) {
+ onerror(CONTACT_ERR_CONTACT_TYPE_NOT_SUPPORTED);
+ break;
+ }
+
+ ICCRecordHelper.readADNLike(ICC_EF_SDN,
+ (ICCUtilsHelper.isICCServiceAvailable("EXT3")) ? ICC_EF_EXT3 : null,
+ onsuccess, onerror);
+ break;
+ default:
+ if (DEBUG) {
+ this.context.debug("Unsupported contactType :" + contactType);
+ }
+ onerror(CONTACT_ERR_CONTACT_TYPE_NOT_SUPPORTED);
+ break;
+ }
+ },
+
+ /**
+ * Helper function to find free contact record.
+ *
+ * @param appType One of CARD_APPTYPE_*.
+ * @param contactType One of GECKO_CARDCONTACT_TYPE_*.
+ * @param onsuccess Callback to be called when success.
+ * @param onerror Callback to be called when error.
+ */
+ findFreeICCContact: function(appType, contactType, onsuccess, onerror) {
+ let ICCRecordHelper = this.context.ICCRecordHelper;
+
+ switch (contactType) {
+ case GECKO_CARDCONTACT_TYPE_ADN:
+ if (!this.hasDfPhoneBook(appType)) {
+ ICCRecordHelper.findFreeRecordId(ICC_EF_ADN, onsuccess.bind(null, 0), onerror);
+ } else {
+ let gotPbrCb = function gotPbrCb(pbrs) {
+ this.findUSimFreeADNRecordId(pbrs, onsuccess, onerror);
+ }.bind(this);
+
+ ICCRecordHelper.readPBR(gotPbrCb, onerror);
+ }
+ break;
+ case GECKO_CARDCONTACT_TYPE_FDN:
+ ICCRecordHelper.findFreeRecordId(ICC_EF_FDN, onsuccess.bind(null, 0), onerror);
+ break;
+ default:
+ if (DEBUG) {
+ this.context.debug("Unsupported contactType :" + contactType);
+ }
+ onerror(CONTACT_ERR_CONTACT_TYPE_NOT_SUPPORTED);
+ break;
+ }
+ },
+
+ /**
+ * Cache the pbr index of the possible free record.
+ */
+ _freePbrIndex: 0,
+
+ /**
+ * Find free ADN record id in USIM.
+ *
+ * @param pbrs All Phonebook Reference Files read.
+ * @param onsuccess Callback to be called when success.
+ * @param onerror Callback to be called when error.
+ */
+ findUSimFreeADNRecordId: function(pbrs, onsuccess, onerror) {
+ let ICCRecordHelper = this.context.ICCRecordHelper;
+
+ function callback(pbrIndex, recordId) {
+ // Assume other free records are probably in the same phonebook set.
+ this._freePbrIndex = pbrIndex;
+ onsuccess(pbrIndex, recordId);
+ }
+
+ let nextPbrIndex = -1;
+ (function findFreeRecordId(pbrIndex) {
+ if (nextPbrIndex === this._freePbrIndex) {
+ // No free record found, reset the pbr index of free record.
+ this._freePbrIndex = 0;
+ if (DEBUG) {
+ this.context.debug(CONTACT_ERR_NO_FREE_RECORD_FOUND);
+ }
+ onerror(CONTACT_ERR_NO_FREE_RECORD_FOUND);
+ return;
+ }
+
+ let pbr = pbrs[pbrIndex];
+ nextPbrIndex = (pbrIndex + 1) % pbrs.length;
+ ICCRecordHelper.findFreeRecordId(
+ pbr.adn.fileId,
+ callback.bind(this, pbrIndex),
+ findFreeRecordId.bind(this, nextPbrIndex));
+ }).call(this, this._freePbrIndex);
+ },
+
+ /**
+ * Helper function to add a new ICC contact.
+ *
+ * @param appType One of CARD_APPTYPE_*.
+ * @param contactType One of GECKO_CARDCONTACT_TYPE_*.
+ * @param contact The contact will be added.
+ * @param pin2 PIN2 is required for FDN.
+ * @param onsuccess Callback to be called when success.
+ * @param onerror Callback to be called when error.
+ */
+ addICCContact: function(appType, contactType, contact, pin2, onsuccess, onerror) {
+ let foundFreeCb = (function foundFreeCb(pbrIndex, recordId) {
+ contact.pbrIndex = pbrIndex;
+ contact.recordId = recordId;
+ this.updateICCContact(appType, contactType, contact, pin2, onsuccess, onerror);
+ }).bind(this);
+
+ // Find free record first.
+ this.findFreeICCContact(appType, contactType, foundFreeCb, onerror);
+ },
+
+ /**
+ * Helper function to update ICC contact.
+ *
+ * @param appType One of CARD_APPTYPE_*.
+ * @param contactType One of GECKO_CARDCONTACT_TYPE_*.
+ * @param contact The contact will be updated.
+ * @param pin2 PIN2 is required for FDN.
+ * @param onsuccess Callback to be called when success.
+ * @param onerror Callback to be called when error.
+ */
+ updateICCContact: function(appType, contactType, contact, pin2, onsuccess, onerror) {
+ let ICCRecordHelper = this.context.ICCRecordHelper;
+ let ICCUtilsHelper = this.context.ICCUtilsHelper;
+
+ let updateContactCb = (updatedContact) => {
+ updatedContact.pbrIndex = contact.pbrIndex;
+ updatedContact.recordId = contact.recordId;
+ onsuccess(updatedContact);
+ }
+
+ switch (contactType) {
+ case GECKO_CARDCONTACT_TYPE_ADN:
+ if (!this.hasDfPhoneBook(appType)) {
+ if (ICCUtilsHelper.isICCServiceAvailable("EXT1")) {
+ this.updateADNLikeWithExtension(ICC_EF_ADN, ICC_EF_EXT1,
+ contact, null,
+ updateContactCb, onerror);
+ } else {
+ ICCRecordHelper.updateADNLike(ICC_EF_ADN, 0xff,
+ contact, null,
+ updateContactCb, onerror);
+ }
+ } else {
+ this.updateUSimContact(contact, updateContactCb, onerror);
+ }
+ break;
+ case GECKO_CARDCONTACT_TYPE_FDN:
+ if (!pin2) {
+ onerror(GECKO_ERROR_SIM_PIN2);
+ return;
+ }
+ if (!ICCUtilsHelper.isICCServiceAvailable("FDN")) {
+ onerror(CONTACT_ERR_CONTACT_TYPE_NOT_SUPPORTED);
+ break;
+ }
+ if (ICCUtilsHelper.isICCServiceAvailable("EXT2")) {
+ this.updateADNLikeWithExtension(ICC_EF_FDN, ICC_EF_EXT2,
+ contact, pin2,
+ updateContactCb, onerror);
+ } else {
+ ICCRecordHelper.updateADNLike(ICC_EF_FDN,
+ 0xff,
+ contact, pin2,
+ updateContactCb, onerror);
+ }
+ break;
+ default:
+ if (DEBUG) {
+ this.context.debug("Unsupported contactType :" + contactType);
+ }
+ onerror(CONTACT_ERR_CONTACT_TYPE_NOT_SUPPORTED);
+ break;
+ }
+ },
+
+ /**
+ * Read contacts from USIM.
+ *
+ * @param onsuccess Callback to be called when success.
+ * @param onerror Callback to be called when error.
+ */
+ readUSimContacts: function(onsuccess, onerror) {
+ let gotPbrCb = function gotPbrCb(pbrs) {
+ this.readAllPhonebookSets(pbrs, onsuccess, onerror);
+ }.bind(this);
+
+ this.context.ICCRecordHelper.readPBR(gotPbrCb, onerror);
+ },
+
+ /**
+ * Read all Phonebook sets.
+ *
+ * @param pbrs All Phonebook Reference Files read.
+ * @param onsuccess Callback to be called when success.
+ * @param onerror Callback to be called when error.
+ */
+ readAllPhonebookSets: function(pbrs, onsuccess, onerror) {
+ let allContacts = [], pbrIndex = 0;
+ let readPhonebook = function(contacts) {
+ if (contacts) {
+ allContacts = allContacts.concat(contacts);
+ }
+
+ let cLen = contacts ? contacts.length : 0;
+ for (let i = 0; i < cLen; i++) {
+ contacts[i].pbrIndex = pbrIndex;
+ }
+
+ pbrIndex++;
+ if (pbrIndex >= pbrs.length) {
+ if (onsuccess) {
+ onsuccess(allContacts);
+ }
+ return;
+ }
+
+ this.readPhonebookSet(pbrs[pbrIndex], readPhonebook, onerror);
+ }.bind(this);
+
+ this.readPhonebookSet(pbrs[pbrIndex], readPhonebook, onerror);
+ },
+
+ /**
+ * Read from Phonebook Reference File.
+ *
+ * @param pbr Phonebook Reference File to be read.
+ * @param onsuccess Callback to be called when success.
+ * @param onerror Callback to be called when error.
+ */
+ readPhonebookSet: function(pbr, onsuccess, onerror) {
+ let ICCRecordHelper = this.context.ICCRecordHelper;
+ let gotAdnCb = function gotAdnCb(contacts) {
+ this.readSupportedPBRFields(pbr, contacts, onsuccess, onerror);
+ }.bind(this);
+
+ ICCRecordHelper.readADNLike(pbr.adn.fileId,
+ (pbr.ext1) ? pbr.ext1.fileId : null, gotAdnCb, onerror);
+ },
+
+ /**
+ * Read supported Phonebook fields.
+ *
+ * @param pbr Phone Book Reference file.
+ * @param contacts Contacts stored on ICC.
+ * @param onsuccess Callback to be called when success.
+ * @param onerror Callback to be called when error.
+ */
+ readSupportedPBRFields: function(pbr, contacts, onsuccess, onerror) {
+ let fieldIndex = 0;
+ (function readField() {
+ let field = USIM_PBR_FIELDS[fieldIndex];
+ fieldIndex += 1;
+ if (!field) {
+ if (onsuccess) {
+ onsuccess(contacts);
+ }
+ return;
+ }
+
+ this.readPhonebookField(pbr, contacts, field, readField.bind(this), onerror);
+ }).call(this);
+ },
+
+ /**
+ * Read Phonebook field.
+ *
+ * @param pbr The phonebook reference file.
+ * @param contacts Contacts stored on ICC.
+ * @param field Phonebook field to be retrieved.
+ * @param onsuccess Callback to be called when success.
+ * @param onerror Callback to be called when error.
+ */
+ readPhonebookField: function(pbr, contacts, field, onsuccess, onerror) {
+ if (!pbr[field]) {
+ if (onsuccess) {
+ onsuccess(contacts);
+ }
+ return;
+ }
+
+ (function doReadContactField(n) {
+ if (n >= contacts.length) {
+ // All contact's fields are read.
+ if (onsuccess) {
+ onsuccess(contacts);
+ }
+ return;
+ }
+
+ // get n-th contact's field.
+ this.readContactField(pbr, contacts[n], field,
+ doReadContactField.bind(this, n + 1), onerror);
+ }).call(this, 0);
+ },
+
+ /**
+ * Read contact's field from USIM.
+ *
+ * @param pbr The phonebook reference file.
+ * @param contact The contact needs to get field.
+ * @param field Phonebook field to be retrieved.
+ * @param onsuccess Callback to be called when success.
+ * @param onerror Callback to be called when error.
+ */
+ readContactField: function(pbr, contact, field, onsuccess, onerror) {
+ let gotRecordIdCb = function gotRecordIdCb(recordId) {
+ if (recordId == 0xff) {
+ if (onsuccess) {
+ onsuccess();
+ }
+ return;
+ }
+
+ let fileId = pbr[field].fileId;
+ let fileType = pbr[field].fileType;
+ let gotFieldCb = function gotFieldCb(value) {
+ if (value) {
+ // Move anr0 anr1,.. into anr[].
+ if (field.startsWith(USIM_PBR_ANR)) {
+ if (!contact[USIM_PBR_ANR]) {
+ contact[USIM_PBR_ANR] = [];
+ }
+ contact[USIM_PBR_ANR].push(value);
+ } else {
+ contact[field] = value;
+ }
+ }
+
+ if (onsuccess) {
+ onsuccess();
+ }
+ }.bind(this);
+
+ let ICCRecordHelper = this.context.ICCRecordHelper;
+ // Detect EF to be read, for anr, it could have anr0, anr1,...
+ let ef = field.startsWith(USIM_PBR_ANR) ? USIM_PBR_ANR : field;
+ switch (ef) {
+ case USIM_PBR_EMAIL:
+ ICCRecordHelper.readEmail(fileId, fileType, recordId, gotFieldCb, onerror);
+ break;
+ case USIM_PBR_ANR:
+ ICCRecordHelper.readANR(fileId, fileType, recordId, gotFieldCb, onerror);
+ break;
+ default:
+ if (DEBUG) {
+ this.context.debug("Unsupported field :" + field);
+ }
+ onerror(CONTACT_ERR_FIELD_NOT_SUPPORTED);
+ break;
+ }
+ }.bind(this);
+
+ this.getContactFieldRecordId(pbr, contact, field, gotRecordIdCb, onerror);
+ },
+
+ /**
+ * Get the recordId.
+ *
+ * If the fileType of field is ICC_USIM_TYPE1_TAG, use corresponding ADN recordId.
+ * otherwise get the recordId from IAP.
+ *
+ * @see TS 131.102, clause 4.4.2.2
+ *
+ * @param pbr The phonebook reference file.
+ * @param contact The contact will be updated.
+ * @param onsuccess Callback to be called when success.
+ * @param onerror Callback to be called when error.
+ */
+ getContactFieldRecordId: function(pbr, contact, field, onsuccess, onerror) {
+ if (pbr[field].fileType == ICC_USIM_TYPE1_TAG) {
+ // If the file type is ICC_USIM_TYPE1_TAG, use corresponding ADN recordId.
+ if (onsuccess) {
+ onsuccess(contact.recordId);
+ }
+ } else if (pbr[field].fileType == ICC_USIM_TYPE2_TAG) {
+ // If the file type is ICC_USIM_TYPE2_TAG, the recordId shall be got from IAP.
+ let gotIapCb = function gotIapCb(iap) {
+ let indexInIAP = pbr[field].indexInIAP;
+ let recordId = iap[indexInIAP];
+
+ if (onsuccess) {
+ onsuccess(recordId);
+ }
+ }.bind(this);
+
+ this.context.ICCRecordHelper.readIAP(pbr.iap.fileId, contact.recordId,
+ gotIapCb, onerror);
+ } else {
+ if (DEBUG) {
+ this.context.debug("USIM PBR files in Type 3 format are not supported.");
+ }
+ onerror(CONTACT_ERR_REQUEST_NOT_SUPPORTED);
+ }
+ },
+
+ /**
+ * Update USIM contact.
+ *
+ * @param contact The contact will be updated.
+ * @param onsuccess Callback to be called when success.
+ * @param onerror Callback to be called when error.
+ */
+ updateUSimContact: function(contact, onsuccess, onerror) {
+ let gotPbrCb = function gotPbrCb(pbrs) {
+ let pbr = pbrs[contact.pbrIndex];
+ if (!pbr) {
+ if (DEBUG) {
+ this.context.debug(CONTACT_ERR_CANNOT_ACCESS_PHONEBOOK);
+ }
+ onerror(CONTACT_ERR_CANNOT_ACCESS_PHONEBOOK);
+ return;
+ }
+ this.updatePhonebookSet(pbr, contact, onsuccess, onerror);
+ }.bind(this);
+
+ this.context.ICCRecordHelper.readPBR(gotPbrCb, onerror);
+ },
+
+ /**
+ * Update fields in Phonebook Reference File.
+ *
+ * @param pbr Phonebook Reference File to be read.
+ * @param onsuccess Callback to be called when success.
+ * @param onerror Callback to be called when error.
+ */
+ updatePhonebookSet: function(pbr, contact, onsuccess, onerror) {
+ let updateAdnCb = function(updatedContact) {
+ this.updateSupportedPBRFields(pbr, contact, (updatedContactField) => {
+ onsuccess(Object.assign(updatedContact, updatedContactField));
+ }, onerror);
+ }.bind(this);
+
+ if (pbr.ext1) {
+ this.updateADNLikeWithExtension(pbr.adn.fileId, pbr.ext1.fileId,
+ contact, null, updateAdnCb, onerror);
+ } else {
+ this.context.ICCRecordHelper.updateADNLike(pbr.adn.fileId, 0xff, contact,
+ null, updateAdnCb, onerror);
+ }
+ },
+
+ /**
+ * Update supported Phonebook fields.
+ *
+ * @param pbr Phone Book Reference file.
+ * @param contact Contact to be updated.
+ * @param onsuccess Callback to be called when success.
+ * @param onerror Callback to be called when error.
+ */
+ updateSupportedPBRFields: function(pbr, contact, onsuccess, onerror) {
+ let fieldIndex = 0;
+ let contactField = {};
+
+ (function updateField() {
+ let field = USIM_PBR_FIELDS[fieldIndex];
+ fieldIndex += 1;
+
+ if (!field) {
+ if (onsuccess) {
+ onsuccess(contactField);
+ }
+ return;
+ }
+
+ // Check if PBR has this field.
+ if (!pbr[field]) {
+ updateField.call(this);
+ return;
+ }
+
+ this.updateContactField(pbr, contact, field, (fieldEntry) => {
+ contactField = Object.assign(contactField, fieldEntry);
+ updateField.call(this);
+ }, (errorMsg) => {
+ // Bug 1194149, there are some sim cards without sufficient
+ // Type 2 USIM contact fields record. We allow user continue
+ // importing contacts.
+ if (errorMsg === CONTACT_ERR_NO_FREE_RECORD_FOUND) {
+ updateField.call(this);
+ return;
+ }
+ onerror(errorMsg);
+ });
+ }).call(this);
+ },
+
+ /**
+ * Update contact's field from USIM.
+ *
+ * @param pbr The phonebook reference file.
+ * @param contact The contact needs to be updated.
+ * @param field Phonebook field to be updated.
+ * @param onsuccess Callback to be called when success.
+ * @param onerror Callback to be called when error.
+ */
+ updateContactField: function(pbr, contact, field, onsuccess, onerror) {
+ if (pbr[field].fileType === ICC_USIM_TYPE1_TAG) {
+ this.updateContactFieldType1(pbr, contact, field, onsuccess, onerror);
+ } else if (pbr[field].fileType === ICC_USIM_TYPE2_TAG) {
+ this.updateContactFieldType2(pbr, contact, field, onsuccess, onerror);
+ } else {
+ if (DEBUG) {
+ this.context.debug("USIM PBR files in Type 3 format are not supported.");
+ }
+ onerror(CONTACT_ERR_REQUEST_NOT_SUPPORTED);
+ }
+ },
+
+ /**
+ * Update Type 1 USIM contact fields.
+ *
+ * @param pbr The phonebook reference file.
+ * @param contact The contact needs to be updated.
+ * @param field Phonebook field to be updated.
+ * @param onsuccess Callback to be called when success.
+ * @param onerror Callback to be called when error.
+ */
+ updateContactFieldType1: function(pbr, contact, field, onsuccess, onerror) {
+ let ICCRecordHelper = this.context.ICCRecordHelper;
+
+ if (field === USIM_PBR_EMAIL) {
+ ICCRecordHelper.updateEmail(pbr, contact.recordId, contact.email, null,
+ (updatedEmail) => {
+ onsuccess({email: updatedEmail});
+ }, onerror);
+ } else if (field === USIM_PBR_ANR0) {
+ let anr = Array.isArray(contact.anr) ? contact.anr[0] : null;
+ ICCRecordHelper.updateANR(pbr, contact.recordId, anr, null,
+ (updatedANR) => {
+ // ANR could have multiple files. If we support more than one anr,
+ // we will save it as anr0, anr1,...etc.
+ onsuccess((updatedANR) ? {anr: [updatedANR]} : null);
+ }, onerror);
+ } else {
+ if (DEBUG) {
+ this.context.debug("Unsupported field :" + field);
+ }
+ onerror(CONTACT_ERR_FIELD_NOT_SUPPORTED);
+ }
+ },
+
+ /**
+ * Update Type 2 USIM contact fields.
+ *
+ * @param pbr The phonebook reference file.
+ * @param contact The contact needs to be updated.
+ * @param field Phonebook field to be updated.
+ * @param onsuccess Callback to be called when success.
+ * @param onerror Callback to be called when error.
+ */
+ updateContactFieldType2: function(pbr, contact, field, onsuccess, onerror) {
+ let ICCRecordHelper = this.context.ICCRecordHelper;
+
+ // Case 1 : EF_IAP[adnRecordId] doesn't have a value(0xff)
+ // Find a free recordId for EF_field
+ // Update field with that free recordId.
+ // Update IAP.
+ //
+ // Case 2: EF_IAP[adnRecordId] has a value
+ // update EF_field[iap[field.indexInIAP]]
+
+ let gotIapCb = function gotIapCb(iap) {
+ let recordId = iap[pbr[field].indexInIAP];
+ if (recordId === 0xff) {
+ // If the value in IAP[index] is 0xff, which means the contact stored on
+ // the SIM doesn't have the additional attribute (email or anr).
+ // So if the contact to be updated doesn't have the attribute either,
+ // we don't have to update it.
+ if ((field === USIM_PBR_EMAIL && contact.email) ||
+ (field === USIM_PBR_ANR0 &&
+ (Array.isArray(contact.anr) && contact.anr[0]))) {
+ // Case 1.
+ this.addContactFieldType2(pbr, contact, field, onsuccess, onerror);
+ } else {
+ if (onsuccess) {
+ onsuccess();
+ }
+ }
+ return;
+ }
+
+ // Case 2.
+ if (field === USIM_PBR_EMAIL) {
+ ICCRecordHelper.updateEmail(pbr, recordId, contact.email, contact.recordId,
+ (updatedEmail) => {
+ onsuccess({email: updatedEmail});
+ }, onerror);
+ } else if (field === USIM_PBR_ANR0) {
+ let anr = Array.isArray(contact.anr) ? contact.anr[0] : null;
+ ICCRecordHelper.updateANR(pbr, recordId, anr, contact.recordId,
+ (updatedANR) => {
+ // ANR could have multiple files. If we support more than one anr,
+ // we will save it as anr0, anr1,...etc.
+ onsuccess((updatedANR) ? {anr: [updatedANR]} : null);
+ }, onerror);
+ } else {
+ if (DEBUG) {
+ this.context.debug("Unsupported field :" + field);
+ }
+ onerror(CONTACT_ERR_FIELD_NOT_SUPPORTED);
+ }
+
+ }.bind(this);
+
+ ICCRecordHelper.readIAP(pbr.iap.fileId, contact.recordId, gotIapCb, onerror);
+ },
+
+ /**
+ * Add Type 2 USIM contact fields.
+ *
+ * @param pbr The phonebook reference file.
+ * @param contact The contact needs to be updated.
+ * @param field Phonebook field to be updated.
+ * @param onsuccess Callback to be called when success.
+ * @param onerror Callback to be called when error.
+ */
+ addContactFieldType2: function(pbr, contact, field, onsuccess, onerror) {
+ let ICCRecordHelper = this.context.ICCRecordHelper;
+ let successCb = function successCb(recordId) {
+
+ let updateCb = function updateCb(contactField) {
+ this.updateContactFieldIndexInIAP(pbr, contact.recordId, field, recordId, () => {
+ onsuccess(contactField);
+ }, onerror);
+ }.bind(this);
+
+ if (field === USIM_PBR_EMAIL) {
+ ICCRecordHelper.updateEmail(pbr, recordId, contact.email, contact.recordId,
+ (updatedEmail) => {
+ updateCb({email: updatedEmail});
+ }, onerror);
+ } else if (field === USIM_PBR_ANR0) {
+ ICCRecordHelper.updateANR(pbr, recordId, contact.anr[0], contact.recordId,
+ (updatedANR) => {
+ // ANR could have multiple files. If we support more than one anr,
+ // we will save it as anr0, anr1,...etc.
+ updateCb((updatedANR) ? {anr: [updatedANR]} : null);
+ }, onerror);
+ }
+ }.bind(this);
+
+ let errorCb = function errorCb(errorMsg) {
+ if (DEBUG) {
+ this.context.debug(errorMsg + " USIM field " + field);
+ }
+ onerror(errorMsg);
+ }.bind(this);
+
+ ICCRecordHelper.findFreeRecordId(pbr[field].fileId, successCb, errorCb);
+ },
+
+ /**
+ * Update IAP value.
+ *
+ * @param pbr The phonebook reference file.
+ * @param recordNumber The record identifier of EF_IAP.
+ * @param field Phonebook field.
+ * @param value The value of 'field' in IAP.
+ * @param onsuccess Callback to be called when success.
+ * @param onerror Callback to be called when error.
+ *
+ */
+ updateContactFieldIndexInIAP: function(pbr, recordNumber, field, value, onsuccess, onerror) {
+ let ICCRecordHelper = this.context.ICCRecordHelper;
+
+ let gotIAPCb = function gotIAPCb(iap) {
+ iap[pbr[field].indexInIAP] = value;
+ ICCRecordHelper.updateIAP(pbr.iap.fileId, recordNumber, iap, onsuccess, onerror);
+ }.bind(this);
+ ICCRecordHelper.readIAP(pbr.iap.fileId, recordNumber, gotIAPCb, onerror);
+ },
+
+ /**
+ * Update ICC ADN like EFs with Extension, like EF_ADN, EF_FDN.
+ *
+ * @param fileId EF id of the ADN or FDN.
+ * @param extFileId EF id of the EXT.
+ * @param contact The contact will be updated. (Shall have recordId property)
+ * @param pin2 PIN2 is required when updating ICC_EF_FDN.
+ * @param onsuccess Callback to be called when success.
+ * @param onerror Callback to be called when error.
+ */
+ updateADNLikeWithExtension: function(fileId, extFileId, contact, pin2, onsuccess, onerror) {
+ let ICCRecordHelper = this.context.ICCRecordHelper;
+ let extNumber;
+
+ if (contact.number) {
+ let numStart = contact.number[0] == "+" ? 1 : 0;
+ let number = contact.number.substring(0, numStart) +
+ this.context.GsmPDUHelper.stringToExtendedBcd(
+ contact.number.substring(numStart));
+ extNumber = number.substr(numStart + ADN_MAX_NUMBER_DIGITS,
+ EXT_MAX_NUMBER_DIGITS);
+ }
+
+ ICCRecordHelper.getADNLikeExtensionRecordNumber(fileId, contact.recordId,
+ (extRecordNumber) => {
+ let updateADNLike = (extRecordNumber) => {
+ ICCRecordHelper.updateADNLike(fileId, extRecordNumber, contact,
+ pin2, (updatedContact) => {
+ if (extNumber && extRecordNumber != 0xff) {
+ updatedContact.number = updatedContact.number.concat(extNumber);
+ }
+ onsuccess(updatedContact);
+ }, onerror);
+ };
+
+ let updateExtension = (extRecordNumber) => {
+ ICCRecordHelper.updateExtension(extFileId, extRecordNumber, extNumber,
+ () => updateADNLike(extRecordNumber),
+ () => updateADNLike(0xff));
+ };
+
+ if (extNumber) {
+ if (extRecordNumber != 0xff) {
+ updateExtension(extRecordNumber);
+ return;
+ }
+
+ ICCRecordHelper.findFreeRecordId(extFileId,
+ (extRecordNumber) => updateExtension(extRecordNumber),
+ (errorMsg) => {
+ if (DEBUG) {
+ this.context.debug("Couldn't find free extension record Id for " + extFileId + ": " + errorMsg);
+ }
+ updateADNLike(0xff);
+ });
+ return;
+ }
+
+ if (extRecordNumber != 0xff) {
+ ICCRecordHelper.cleanEFRecord(extFileId, extRecordNumber,
+ () => updateADNLike(0xff), onerror);
+ return;
+ }
+
+ updateADNLike(0xff);
+ }, onerror);
+ },
+};
+
+function IconLoaderObject(aContext) {
+ this.context = aContext;
+}
+IconLoaderObject.prototype = {
+ context: null,
+
+ /**
+ * Load icons.
+ *
+ * @param recordNumbers Array of the record identifiers of EF_IMG.
+ * @param onsuccess Callback to be called when success.
+ * @param onerror Callback to be called when error.
+ */
+ loadIcons: function(recordNumbers, onsuccess, onerror) {
+ if (!recordNumbers || !recordNumbers.length) {
+ if (onerror) {
+ onerror();
+ }
+ return;
+ }
+
+ this._start({
+ recordNumbers: recordNumbers,
+ onsuccess: onsuccess,
+ onerror: onerror});
+ },
+
+ _start: function(options) {
+ let callback = (function(icons) {
+ if (!options.icons) {
+ options.icons = [];
+ }
+ for (let i = 0; i < icons.length; i++) {
+ icons[i] = this._parseRawData(icons[i]);
+ }
+ options.icons[options.currentRecordIndex] = icons;
+ options.currentRecordIndex++;
+
+ let recordNumbers = options.recordNumbers;
+ if (options.currentRecordIndex < recordNumbers.length) {
+ let recordNumber = recordNumbers[options.currentRecordIndex];
+ this.context.SimRecordHelper.readIMG(recordNumber,
+ callback,
+ options.onerror);
+ } else {
+ if (options.onsuccess) {
+ options.onsuccess(options.icons);
+ }
+ }
+ }).bind(this);
+
+ options.currentRecordIndex = 0;
+ this.context.SimRecordHelper.readIMG(options.recordNumbers[0],
+ callback,
+ options.onerror);
+ },
+
+ _parseRawData: function(rawData) {
+ let codingScheme = rawData.codingScheme;
+
+ switch (codingScheme) {
+ case ICC_IMG_CODING_SCHEME_BASIC:
+ return this._decodeBasicImage(rawData.width, rawData.height, rawData.body);
+
+ case ICC_IMG_CODING_SCHEME_COLOR:
+ case ICC_IMG_CODING_SCHEME_COLOR_TRANSPARENCY:
+ return this._decodeColorImage(codingScheme,
+ rawData.width, rawData.height,
+ rawData.bitsPerImgPoint,
+ rawData.numOfClutEntries,
+ rawData.clut, rawData.body);
+ }
+
+ return null;
+ },
+
+ _decodeBasicImage: function(width, height, body) {
+ let numOfPixels = width * height;
+ let pixelIndex = 0;
+ let currentByteIndex = 0;
+ let currentByte = 0x00;
+
+ const BLACK = 0x000000FF;
+ const WHITE = 0xFFFFFFFF;
+
+ let pixels = [];
+ while (pixelIndex < numOfPixels) {
+ // Reassign data and index for every byte (8 bits).
+ if (pixelIndex % 8 == 0) {
+ currentByte = body[currentByteIndex++];
+ }
+ let bit = (currentByte >> (7 - (pixelIndex % 8))) & 0x01;
+ pixels[pixelIndex++] = bit ? WHITE : BLACK;
+ }
+
+ return {pixels: pixels,
+ codingScheme: GECKO_IMG_CODING_SCHEME_BASIC,
+ width: width,
+ height: height};
+ },
+
+ _decodeColorImage: function(codingScheme, width, height, bitsPerImgPoint,
+ numOfClutEntries, clut, body) {
+ let mask = 0xff >> (8 - bitsPerImgPoint);
+ let bitsStartOffset = 8 - bitsPerImgPoint;
+ let bitIndex = bitsStartOffset;
+ let numOfPixels = width * height;
+ let pixelIndex = 0;
+ let currentByteIndex = 0;
+ let currentByte = body[currentByteIndex++];
+
+ let pixels = [];
+ while (pixelIndex < numOfPixels) {
+ // Reassign data and index for every byte (8 bits).
+ if (bitIndex < 0) {
+ currentByte = body[currentByteIndex++];
+ bitIndex = bitsStartOffset;
+ }
+ let clutEntry = ((currentByte >> bitIndex) & mask);
+ let clutIndex = clutEntry * ICC_CLUT_ENTRY_SIZE;
+ let alpha = codingScheme == ICC_IMG_CODING_SCHEME_COLOR_TRANSPARENCY &&
+ clutEntry == numOfClutEntries - 1;
+ pixels[pixelIndex++] = alpha ? 0x00
+ : (clut[clutIndex] << 24 |
+ clut[clutIndex + 1] << 16 |
+ clut[clutIndex + 2] << 8 |
+ 0xFF) >>> 0;
+ bitIndex -= bitsPerImgPoint;
+ }
+
+ return {pixels: pixels,
+ codingScheme: ICC_IMG_CODING_SCHEME_TO_GECKO[codingScheme],
+ width: width,
+ height: height};
+ },
+};
+
+/**
+ * Global stuff.
+ */
+
+function Context(aClientId) {
+ this.clientId = aClientId;
+
+ this.Buf = new BufObject(this);
+ this.RIL = new RilObject(this);
+ this.RIL.initRILState();
+}
+Context.prototype = {
+ clientId: null,
+ Buf: null,
+ RIL: null,
+
+ debug: function(aMessage) {
+ GLOBAL.debug("[" + this.clientId + "] " + aMessage);
+ }
+};
+
+(function() {
+ let lazySymbols = [
+ "BerTlvHelper", "BitBufferHelper", "CdmaPDUHelper",
+ "ComprehensionTlvHelper", "GsmPDUHelper", "ICCContactHelper",
+ "ICCFileHelper", "ICCIOHelper", "ICCPDUHelper", "ICCRecordHelper",
+ "ICCUtilsHelper", "RuimRecordHelper", "SimRecordHelper",
+ "StkCommandParamsFactory", "StkProactiveCmdHelper", "IconLoader",
+ ];
+
+ for (let i = 0; i < lazySymbols.length; i++) {
+ let symbol = lazySymbols[i];
+ Object.defineProperty(Context.prototype, symbol, {
+ get: function() {
+ let real = new GLOBAL[symbol + "Object"](this);
+ Object.defineProperty(this, symbol, {
+ value: real,
+ enumerable: true
+ });
+ return real;
+ },
+ configurable: true,
+ enumerable: true
+ });
+ }
+})();
+
+var ContextPool = {
+ _contexts: [],
+
+ handleRilMessage: function(aClientId, aUint8Array) {
+ let context = this._contexts[aClientId];
+ context.Buf.processIncoming(aUint8Array);
+ },
+
+ handleChromeMessage: function(aMessage) {
+ let clientId = aMessage.rilMessageClientId;
+ if (clientId != null) {
+ let context = this._contexts[clientId];
+ context.RIL.handleChromeMessage(aMessage);
+ return;
+ }
+
+ if (DEBUG) debug("Received global chrome message " + JSON.stringify(aMessage));
+ let method = this[aMessage.rilMessageType];
+ if (typeof method != "function") {
+ if (DEBUG) {
+ debug("Don't know what to do");
+ }
+ return;
+ }
+ method.call(this, aMessage);
+ },
+
+ setInitialOptions: function(aOptions) {
+ DEBUG = DEBUG_WORKER || aOptions.debug;
+
+ let quirks = aOptions.quirks;
+ RILQUIRKS_CALLSTATE_EXTRA_UINT32 = quirks.callstateExtraUint32;
+ RILQUIRKS_REQUEST_USE_DIAL_EMERGENCY_CALL = quirks.requestUseDialEmergencyCall;
+ RILQUIRKS_SIM_APP_STATE_EXTRA_FIELDS = quirks.simAppStateExtraFields;
+ RILQUIRKS_EXTRA_UINT32_2ND_CALL = quirks.extraUint2ndCall;
+ RILQUIRKS_HAVE_QUERY_ICC_LOCK_RETRY_COUNT = quirks.haveQueryIccLockRetryCount;
+ RILQUIRKS_SEND_STK_PROFILE_DOWNLOAD = quirks.sendStkProfileDownload;
+ RILQUIRKS_DATA_REGISTRATION_ON_DEMAND = quirks.dataRegistrationOnDemand;
+ RILQUIRKS_SUBSCRIPTION_CONTROL = quirks.subscriptionControl;
+ RILQUIRKS_SIGNAL_EXTRA_INT32 = quirks.signalExtraInt;
+ RILQUIRKS_AVAILABLE_NETWORKS_EXTRA_STRING = quirks.availableNetworkExtraStr;
+ RILQUIRKS_SMSC_ADDRESS_FORMAT = quirks.smscAddressFormat;
+ },
+
+ setDebugFlag: function(aOptions) {
+ DEBUG = DEBUG_WORKER || aOptions.debug;
+ },
+
+ registerClient: function(aOptions) {
+ let clientId = aOptions.clientId;
+ this._contexts[clientId] = new Context(clientId);
+ },
+};
+
+function onRILMessage(aClientId, aUint8Array) {
+ ContextPool.handleRilMessage(aClientId, aUint8Array);
+}
+
+onmessage = function onmessage(event) {
+ ContextPool.handleChromeMessage(event.data);
+};
+
+onerror = function onerror(event) {
+ if (DEBUG) debug("onerror" + event.message + "\n");
+};
diff --git a/dom/system/gonk/ril_worker_buf_object.js b/dom/system/gonk/ril_worker_buf_object.js
new file mode 100644
index 000000000..70018c5b2
--- /dev/null
+++ b/dom/system/gonk/ril_worker_buf_object.js
@@ -0,0 +1,168 @@
+/* global require */
+/* global DEBUG, DEBUG_WORKER */
+/* global RESPONSE_TYPE_SOLICITED */
+/* global RESPONSE_TYPE_UNSOLICITED */
+
+"use strict";
+
+/**
+ * This is a specialized worker buffer for the Parcel protocol.
+ *
+ * NOTE: To prevent including/importing twice, this file should be included
+ * in a file which already includes 'ril_consts.js' and 'require.js'.
+ */
+(function(exports) {
+
+ // Set to true in ril_consts.js to see debug messages
+ let DEBUG = DEBUG_WORKER;
+ // Need to inherit it.
+ let Buf = require("resource://gre/modules/workers/worker_buf.js").Buf;
+
+ let BufObject = function(aContext) {
+ this.context = aContext;
+ // This gets incremented each time we send out a parcel.
+ this.mToken = 1;
+ // Maps tokens we send out with requests to the request type, so that
+ // when we get a response parcel back, we know what request it was for.
+ this.mTokenRequestMap = new Map();
+ // This is because the underlying 'Buf' is still using the 'init' pattern, so
+ // this derived one needs to invoke it.
+ // Using 'apply' style to mark it's a parent method calling explicitly.
+ Buf._init.apply(this);
+
+ // Remapping the request type to different values based on RIL version.
+ // We only have to do this for SUBSCRIPTION right now, so I just make it
+ // simple. A generic logic or structure could be discussed if we have more
+ // use cases, especially the cases from different partners.
+ this._requestMap = {};
+ // RIL version 8.
+ // For the CAF's proprietary parcels. Please see
+ // https://www.codeaurora.org/cgit/quic/la/platform/hardware/ril/tree/include/telephony/ril.h?h=b2g_jb_3.2
+ let map = {};
+ map[REQUEST_SET_UICC_SUBSCRIPTION] = 114;
+ map[REQUEST_SET_DATA_SUBSCRIPTION] = 115;
+ this._requestMap[8] = map;
+ // RIL version 9.
+ // For the CAF's proprietary parcels. Please see
+ // https://www.codeaurora.org/cgit/quic/la/platform/hardware/ril/tree/include/telephony/ril.h?h=b2g_kk_3.5
+ map = {};
+ map[REQUEST_SET_UICC_SUBSCRIPTION] = 115;
+ map[REQUEST_SET_DATA_SUBSCRIPTION] = 116;
+ this._requestMap[9] = map;
+ };
+
+ /**
+ * "inherit" the basic worker buffer.
+ */
+ BufObject.prototype = Object.create(Buf);
+
+ /**
+ * Process one parcel.
+ */
+ BufObject.prototype.processParcel = function() {
+ let responseType = this.readInt32();
+
+ let requestType, options;
+ if (responseType == RESPONSE_TYPE_SOLICITED) {
+ let token = this.readInt32();
+ let error = this.readInt32();
+
+ options = this.mTokenRequestMap.get(token);
+ if (!options) {
+ if (DEBUG) {
+ this.context.debug("Suspicious uninvited request found: " +
+ token + ". Ignored!");
+ }
+ return;
+ }
+
+ this.mTokenRequestMap.delete(token);
+ requestType = options.rilRequestType;
+
+ if (error !== ERROR_SUCCESS) {
+ options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[error] ||
+ GECKO_ERROR_UNSPECIFIED_ERROR;
+ }
+ if (DEBUG) {
+ this.context.debug("Solicited response for request type " + requestType +
+ ", token " + token + ", error " + error);
+ }
+ } else if (responseType == RESPONSE_TYPE_UNSOLICITED) {
+ requestType = this.readInt32();
+ if (DEBUG) {
+ this.context.debug("Unsolicited response for request type " + requestType);
+ }
+ } else {
+ if (DEBUG) {
+ this.context.debug("Unknown response type: " + responseType);
+ }
+ return;
+ }
+
+ this.context.RIL.handleParcel(requestType, this.readAvailable, options);
+ };
+
+ /**
+ * Start a new outgoing parcel.
+ *
+ * @param type
+ * Integer specifying the request type.
+ * @param options [optional]
+ * Object containing information about the request, e.g. the
+ * original main thread message object that led to the RIL request.
+ */
+ BufObject.prototype.newParcel = function(type, options) {
+ if (DEBUG) {
+ this.context.debug("New outgoing parcel of type " + type);
+ }
+
+ // We're going to leave room for the parcel size at the beginning.
+ this.outgoingIndex = this.PARCEL_SIZE_SIZE;
+ this.writeInt32(this._reMapRequestType(type));
+ this.writeInt32(this.mToken);
+
+ if (!options) {
+ options = {};
+ }
+ options.rilRequestType = type;
+ this.mTokenRequestMap.set(this.mToken, options);
+ this.mToken++;
+ return this.mToken;
+ };
+
+ BufObject.prototype.simpleRequest = function(type, options) {
+ this.newParcel(type, options);
+ this.sendParcel();
+ };
+
+ BufObject.prototype.onSendParcel = function(parcel) {
+ self.postRILMessage(this.context.clientId, parcel);
+ };
+
+ /**
+ * Remapping the request type to different values based on RIL version.
+ * We only have to do this for SUBSCRIPTION right now, so I just make it
+ * simple. A generic logic or structure could be discussed if we have more
+ * use cases, especially the cases from different partners.
+ */
+ BufObject.prototype._reMapRequestType = function(type) {
+ for (let version in this._requestMap) {
+ if (this.context.RIL.version <= version) {
+ let newType = this._requestMap[version][type];
+ if (newType) {
+ if (DEBUG) {
+ this.context.debug("Remap request type to " + newType);
+ }
+ return newType;
+ }
+ }
+ }
+ return type;
+ };
+
+ // Before we make sure to form it as a module would not add extra
+ // overhead of module loading, we need to define it in this way
+ // rather than 'module.exports' it as a module component.
+ exports.BufObject = BufObject;
+})(self); // in worker self is the global
+
diff --git a/dom/system/gonk/ril_worker_telephony_request_queue.js b/dom/system/gonk/ril_worker_telephony_request_queue.js
new file mode 100644
index 000000000..4dba7a42f
--- /dev/null
+++ b/dom/system/gonk/ril_worker_telephony_request_queue.js
@@ -0,0 +1,157 @@
+/* global DEBUG, DEBUG_WORKER */
+/* global REQUEST_GET_CURRENT_CALLS */
+/* global REQUEST_ANSWER, REQUEST_CONFERENCE, REQUEST_DIAL */
+/* global REQUEST_DIAL_EMERGENCY_CALL, REQUEST_HANGUP */
+/* global REQUEST_HANGUP_FOREGROUND_RESUME_BACKGROUND */
+/* global REQUEST_HANGUP_WAITING_OR_BACKGROUND */
+/* global REQUEST_SEPARATE_CONNECTION */
+/* global REQUEST_SWITCH_WAITING_OR_HOLDING_AND_ACTIVE, REQUEST_UDUB */
+
+"use strict";
+
+(function(exports) {
+
+ const TELEPHONY_REQUESTS = [
+ REQUEST_GET_CURRENT_CALLS,
+ REQUEST_ANSWER,
+ REQUEST_CONFERENCE,
+ REQUEST_DIAL,
+ REQUEST_DIAL_EMERGENCY_CALL,
+ REQUEST_HANGUP,
+ REQUEST_HANGUP_FOREGROUND_RESUME_BACKGROUND,
+ REQUEST_HANGUP_WAITING_OR_BACKGROUND,
+ REQUEST_SEPARATE_CONNECTION,
+ REQUEST_SWITCH_WAITING_OR_HOLDING_AND_ACTIVE,
+ REQUEST_UDUB
+ ];
+
+ // Set to true in ril_consts.js to see debug messages
+ let DEBUG = DEBUG_WORKER;
+
+ /**
+ * Queue entry; only used in the queue.
+ */
+ let TelephonyRequestEntry = function(request, callback) {
+ this.request = request;
+ this.callback = callback;
+ };
+
+ let TelephonyRequestQueue = function(ril) {
+ this.ril = ril;
+ this.currentQueue = null; // Point to the current running queue.
+
+ this.queryQueue = [];
+ this.controlQueue = [];
+ };
+
+ TelephonyRequestQueue.prototype._getQueue = function(request) {
+ return (request === REQUEST_GET_CURRENT_CALLS) ? this.queryQueue
+ : this.controlQueue;
+ };
+
+ TelephonyRequestQueue.prototype._getAnotherQueue = function(queue) {
+ return (this.queryQueue === queue) ? this.controlQueue : this.queryQueue;
+ };
+
+ TelephonyRequestQueue.prototype._find = function(queue, request) {
+ for (let i = 0; i < queue.length; ++i) {
+ if (queue[i].request === request) {
+ return i;
+ }
+ }
+ return -1;
+ };
+
+ TelephonyRequestQueue.prototype._startQueue = function(queue) {
+ if (queue.length === 0) {
+ return;
+ }
+
+ // We only need to keep one entry for queryQueue.
+ if (queue === this.queryQueue) {
+ queue.splice(1, queue.length - 1);
+ }
+
+ this.currentQueue = queue;
+ for (let entry of queue) {
+ this._executeEntry(entry);
+ }
+ };
+
+ TelephonyRequestQueue.prototype._executeEntry = function(entry) {
+ if (DEBUG) {
+ this.debug("execute " + this._getRequestName(entry.request));
+ }
+ entry.callback();
+ };
+
+ TelephonyRequestQueue.prototype._getRequestName = function(request) {
+ let method = this.ril[request];
+ return (typeof method === 'function') ? method.name : "";
+ };
+
+ TelephonyRequestQueue.prototype.debug = function(msg) {
+ this.ril.context.debug("[TeleQ] " + msg);
+ };
+
+ TelephonyRequestQueue.prototype.isValidRequest = function(request) {
+ return TELEPHONY_REQUESTS.indexOf(request) !== -1;
+ };
+
+ TelephonyRequestQueue.prototype.push = function(request, callback) {
+ if (!this.isValidRequest(request)) {
+ if (DEBUG) {
+ this.debug("Error: " + this._getRequestName(request) +
+ " is not a telephony request");
+ }
+ return;
+ }
+
+ if (DEBUG) {
+ this.debug("push " + this._getRequestName(request));
+ }
+ let entry = new TelephonyRequestEntry(request, callback);
+ let queue = this._getQueue(request);
+ queue.push(entry);
+
+ // Try to run the request.
+ if (this.currentQueue === queue) {
+ this._executeEntry(entry);
+ } else if (!this.currentQueue) {
+ this._startQueue(queue);
+ }
+ };
+
+ TelephonyRequestQueue.prototype.pop = function(request) {
+ if (!this.isValidRequest(request)) {
+ if (DEBUG) {
+ this.debug("Error: " + this._getRequestName(request) +
+ " is not a telephony request");
+ }
+ return;
+ }
+
+ if (DEBUG) {
+ this.debug("pop " + this._getRequestName(request));
+ }
+ let queue = this._getQueue(request);
+ let index = this._find(queue, request);
+ if (index === -1) {
+ throw new Error("Cannot find the request in telephonyRequestQueue.");
+ } else {
+ queue.splice(index, 1);
+ }
+
+ if (queue.length === 0) {
+ this.currentQueue = null;
+ this._startQueue(this._getAnotherQueue(queue));
+ }
+ };
+
+
+ // Before we make sure to form it as a module would not add extra
+ // overhead of module loading, we need to define it in this way
+ // rather than 'module.exports' it as a module component.
+ exports.TelephonyRequestQueue = TelephonyRequestQueue;
+})(self); // in worker self is the global
+
diff --git a/dom/system/gonk/systemlibs.js b/dom/system/gonk/systemlibs.js
new file mode 100644
index 000000000..a27b30e20
--- /dev/null
+++ b/dom/system/gonk/systemlibs.js
@@ -0,0 +1,201 @@
+/* Copyright 2012 Mozilla Foundation and Mozilla contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+if (!this.ctypes) {
+ // We're likely being loaded as a JSM.
+ this.EXPORTED_SYMBOLS = [ "libcutils", "netHelpers" ];
+ Components.utils.import("resource://gre/modules/ctypes.jsm");
+}
+
+const SYSTEM_PROPERTY_KEY_MAX = 32;
+const SYSTEM_PROPERTY_VALUE_MAX = 92;
+
+// We leave this as 'undefined' instead of setting it to 'false'. That
+// way a file that includes us can have it defined already without us
+// overriding the value here.
+var DEBUG;
+
+/**
+ * Expose some system-level functions.
+ */
+this.libcutils = (function() {
+ let lib;
+ try {
+ lib = ctypes.open("libcutils.so");
+ } catch(ex) {
+ // Return a fallback option in case libcutils.so isn't present (e.g.
+ // when building Firefox with MOZ_B2G_RIL.
+ if (DEBUG) {
+ dump("Could not load libcutils.so. Using fake propdb.\n");
+ }
+ let fake_propdb = Object.create(null);
+ return {
+ property_get: function(key, defaultValue) {
+ if (key in fake_propdb) {
+ return fake_propdb[key];
+ }
+ return defaultValue === undefined ? null : defaultValue;
+ },
+ property_set: function(key, value) {
+ fake_propdb[key] = value;
+ }
+ };
+ }
+
+ let c_property_get = lib.declare("property_get", ctypes.default_abi,
+ ctypes.int, // return value: length
+ ctypes.char.ptr, // key
+ ctypes.char.ptr, // value
+ ctypes.char.ptr); // default
+ let c_property_set = lib.declare("property_set", ctypes.default_abi,
+ ctypes.int, // return value: success
+ ctypes.char.ptr, // key
+ ctypes.char.ptr); // value
+ let c_value_buf = ctypes.char.array(SYSTEM_PROPERTY_VALUE_MAX)();
+
+ return {
+
+ /**
+ * Get a system property.
+ *
+ * @param key
+ * Name of the property
+ * @param defaultValue [optional]
+ * Default value to return if the property isn't set (default: null)
+ */
+ property_get: function(key, defaultValue) {
+ if (defaultValue === undefined) {
+ defaultValue = null;
+ }
+ c_property_get(key, c_value_buf, defaultValue);
+ return c_value_buf.readString();
+ },
+
+ /**
+ * Set a system property
+ *
+ * @param key
+ * Name of the property
+ * @param value
+ * Value to set the property to.
+ */
+ property_set: function(key, value) {
+ let rv = c_property_set(key, value);
+ if (rv) {
+ throw Error('libcutils.property_set("' + key + '", "' + value +
+ '") failed with error ' + rv);
+ }
+ }
+
+ };
+})();
+
+/**
+ * Helpers for conversions.
+ */
+this.netHelpers = {
+
+ /**
+ * Swap byte orders for 32-bit value
+ */
+ swap32: function(n) {
+ return (((n >> 24) & 0xFF) << 0) |
+ (((n >> 16) & 0xFF) << 8) |
+ (((n >> 8) & 0xFF) << 16) |
+ (((n >> 0) & 0xFF) << 24);
+ },
+
+ /**
+ * Convert network byte order to host byte order
+ * Note: Assume that the system is little endian
+ */
+ ntohl: function(n) {
+ return this.swap32(n);
+ },
+
+ /**
+ * Convert host byte order to network byte order
+ * Note: Assume that the system is little endian
+ */
+ htonl: function(n) {
+ return this.swap32(n);
+ },
+
+ /**
+ * Convert integer representation of an IP address to the string
+ * representation.
+ *
+ * @param ip
+ * IP address in number format.
+ */
+ ipToString: function(ip) {
+ return ((ip >> 0) & 0xFF) + "." +
+ ((ip >> 8) & 0xFF) + "." +
+ ((ip >> 16) & 0xFF) + "." +
+ ((ip >> 24) & 0xFF);
+ },
+
+ /**
+ * Convert string representation of an IP address to the integer
+ * representation (network byte order).
+ *
+ * @param string
+ * String containing the IP address.
+ */
+ stringToIP: function(string) {
+ if (!string) {
+ return null;
+ }
+ let ip = 0;
+ let start, end = -1;
+ for (let i = 0; i < 4; i++) {
+ start = end + 1;
+ end = string.indexOf(".", start);
+ if (end == -1) {
+ end = string.length;
+ }
+ let num = parseInt(string.slice(start, end), 10);
+ if (isNaN(num)) {
+ return null;
+ }
+ ip |= num << (i * 8);
+ }
+ return ip;
+ },
+
+ /**
+ * Make a subnet mask.
+ */
+ makeMask: function(len) {
+ let mask = 0;
+ for (let i = 0; i < len; ++i) {
+ mask |= (0x80000000 >> i);
+ }
+ return this.ntohl(mask);
+ },
+
+ /**
+ * Get Mask length from given mask address
+ */
+ getMaskLength: function(mask) {
+ let len = 0;
+ let netmask = this.ntohl(mask);
+ while (netmask & 0x80000000) {
+ len++;
+ netmask = netmask << 1;
+ }
+ return len;
+ }
+};
diff --git a/dom/system/gonk/tests/header_helpers.js b/dom/system/gonk/tests/header_helpers.js
new file mode 100644
index 000000000..8d1144f75
--- /dev/null
+++ b/dom/system/gonk/tests/header_helpers.js
@@ -0,0 +1,217 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+var {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
+
+
+var subscriptLoader = Cc["@mozilla.org/moz/jssubscript-loader;1"]
+ .getService(Ci.mozIJSSubScriptLoader);
+
+/**
+ * Start a new RIL worker.
+ *
+ * @param custom_ns
+ * Namespace with symbols to be injected into the new worker
+ * namespace.
+ *
+ * @return an object that represents the worker's namespace.
+ *
+ * @note that this does not start an actual worker thread. The worker
+ * is executed on the main thread, within a separate namespace object.
+ */
+function newWorker(custom_ns) {
+ let worker_ns = {
+ importScripts: function() {
+ Array.slice(arguments).forEach(function(script) {
+ if (!script.startsWith("resource:")) {
+ script = "resource://gre/modules/" + script;
+ }
+ subscriptLoader.loadSubScript(script, this);
+ }, this);
+ },
+
+ postRILMessage: function(message) {
+ },
+
+ postMessage: function(message) {
+ },
+
+ // Define these variables inside the worker scope so ES5 strict mode
+ // doesn't flip out.
+ onmessage: undefined,
+ onerror: undefined,
+
+ DEBUG: true
+ };
+ // The 'self' variable in a worker points to the worker's own namespace.
+ worker_ns.self = worker_ns;
+
+ // Copy the custom definitions over.
+ for (let key in custom_ns) {
+ worker_ns[key] = custom_ns[key];
+ }
+
+ // fake require() for toolkit/components/workerloader/require.js
+ let require = (function() {
+ return function require(script) {
+ worker_ns.module = {};
+ worker_ns.importScripts(script);
+ return worker_ns;
+ }
+ })();
+
+ Object.freeze(require);
+ Object.defineProperty(worker_ns, "require", {
+ value: require,
+ enumerable: true,
+ configurable: false
+ });
+
+ // Load the RIL worker itself.
+ worker_ns.importScripts("ril_worker.js");
+
+ // Register at least one client.
+ worker_ns.ContextPool.registerClient({ clientId: 0 });
+
+ return worker_ns;
+}
+
+/**
+ * Create a buffered RIL worker.
+ *
+ * @return A worker object that stores sending octets in a internal buffer.
+ */
+function newUint8Worker() {
+ let worker = newWorker();
+ let index = 0; // index for read
+ let buf = [];
+
+ let context = worker.ContextPool._contexts[0];
+ context.Buf.writeUint8 = function(value) {
+ buf.push(value);
+ };
+
+ context.Buf.readUint8 = function() {
+ return buf[index++];
+ };
+
+ context.Buf.seekIncoming = function(offset) {
+ index += offset;
+ };
+
+ context.Buf.getReadAvailable = function() {
+ return buf.length - index;
+ };
+
+ worker.debug = do_print;
+
+ return worker;
+}
+
+/**
+ * Create a worker that keeps posted chrome message.
+ */
+function newInterceptWorker() {
+ let postedMessage;
+ let worker = newWorker({
+ postRILMessage: function(data) {
+ },
+ postMessage: function(message) {
+ postedMessage = message;
+ }
+ });
+ return {
+ get postedMessage() {
+ return postedMessage;
+ },
+ get worker() {
+ return worker;
+ }
+ };
+}
+
+/**
+ * Create a parcel suitable for postRILMessage().
+ *
+ * @param fakeParcelSize
+ * Value to be written to parcel size field for testing
+ * incorrect/incomplete parcel reading. Replaced with correct
+ * one determined length of data if negative.
+ * @param response
+ * Response code of the incoming parcel.
+ * @param request
+ * Request code of the incoming parcel.
+ * @param data
+ * Extra data to be appended.
+ *
+ * @return an Uint8Array carrying all parcel data.
+ */
+function newIncomingParcel(fakeParcelSize, response, request, data) {
+ const UINT32_SIZE = 4;
+ const PARCEL_SIZE_SIZE = 4;
+
+ let realParcelSize = data.length + 2 * UINT32_SIZE;
+ let buffer = new ArrayBuffer(realParcelSize + PARCEL_SIZE_SIZE);
+ let bytes = new Uint8Array(buffer);
+
+ let writeIndex = 0;
+ function writeUint8(value) {
+ bytes[writeIndex] = value;
+ ++writeIndex;
+ }
+
+ function writeInt32(value) {
+ writeUint8(value & 0xff);
+ writeUint8((value >> 8) & 0xff);
+ writeUint8((value >> 16) & 0xff);
+ writeUint8((value >> 24) & 0xff);
+ }
+
+ function writeParcelSize(value) {
+ writeUint8((value >> 24) & 0xff);
+ writeUint8((value >> 16) & 0xff);
+ writeUint8((value >> 8) & 0xff);
+ writeUint8(value & 0xff);
+ }
+
+ if (fakeParcelSize < 0) {
+ fakeParcelSize = realParcelSize;
+ }
+ writeParcelSize(fakeParcelSize);
+
+ writeInt32(response);
+ writeInt32(request);
+
+ // write parcel data
+ for (let ii = 0; ii < data.length; ++ii) {
+ writeUint8(data[ii]);
+ }
+
+ return bytes;
+}
+
+/**
+ * Create a parcel buffer which represents the hex string.
+ *
+ * @param hexString
+ * The HEX string to be converted.
+ *
+ * @return an Uint8Array carrying all parcel data.
+ */
+function hexStringToParcelByteArrayData(hexString) {
+ let length = Math.ceil((hexString.length / 2));
+ let bytes = new Uint8Array(4 + length);
+
+ bytes[0] = length & 0xFF;
+ bytes[1] = (length >> 8) & 0xFF;
+ bytes[2] = (length >> 16) & 0xFF;
+ bytes[3] = (length >> 24) & 0xFF;
+
+ for (let i = 0; i < length; i ++) {
+ bytes[i + 4] = Number.parseInt(hexString.substr(i * 2, 2), 16);
+ }
+
+ return bytes;
+}
diff --git a/dom/system/gonk/tests/marionette/head.js b/dom/system/gonk/tests/marionette/head.js
new file mode 100644
index 000000000..5a6ee1272
--- /dev/null
+++ b/dom/system/gonk/tests/marionette/head.js
@@ -0,0 +1,345 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+MARIONETTE_CONTEXT = "chrome";
+
+const SETTINGS_KEY_DATA_ENABLED = "ril.data.enabled";
+const SETTINGS_KEY_DATA_APN_SETTINGS = "ril.data.apnSettings";
+const SETTINGS_KEY_WIFI_ENABLED = "wifi.enabled";
+
+const TOPIC_CONNECTION_STATE_CHANGED = "network-connection-state-changed";
+const TOPIC_NETWORK_ACTIVE_CHANGED = "network-active-changed";
+
+const NETWORK_STATE_UNKNOWN = Ci.nsINetworkInfo.NETWORK_STATE_UNKNOWN;
+const NETWORK_STATE_CONNECTING = Ci.nsINetworkInfo.NETWORK_STATE_CONNECTING;
+const NETWORK_STATE_CONNECTED = Ci.nsINetworkInfo.NETWORK_STATE_CONNECTED;
+const NETWORK_STATE_DISCONNECTING = Ci.nsINetworkInfo.NETWORK_STATE_DISCONNECTING;
+const NETWORK_STATE_DISCONNECTED = Ci.nsINetworkInfo.NETWORK_STATE_DISCONNECTED;
+
+const NETWORK_TYPE_MOBILE = Ci.nsINetworkInfo.NETWORK_TYPE_MOBILE;
+const NETWORK_TYPE_MOBILE_MMS = Ci.nsINetworkInfo.NETWORK_TYPE_MOBILE_MMS;
+const NETWORK_TYPE_MOBILE_SUPL = Ci.nsINetworkInfo.NETWORK_TYPE_MOBILE_SUPL;
+const NETWORK_TYPE_MOBILE_IMS = Ci.nsINetworkInfo.NETWORK_TYPE_MOBILE_IMS;
+const NETWORK_TYPE_MOBILE_DUN = Ci.nsINetworkInfo.NETWORK_TYPE_MOBILE_DUN;
+const NETWORK_TYPE_MOBILE_FOTA = Ci.nsINetworkInfo.NETWORK_TYPE_MOBILE_FOTA;
+
+const networkTypes = [
+ NETWORK_TYPE_MOBILE,
+ NETWORK_TYPE_MOBILE_MMS,
+ NETWORK_TYPE_MOBILE_SUPL,
+ NETWORK_TYPE_MOBILE_IMS,
+ NETWORK_TYPE_MOBILE_DUN,
+ NETWORK_TYPE_MOBILE_FOTA
+];
+
+var Promise = Cu.import("resource://gre/modules/Promise.jsm").Promise;
+
+var ril = Cc["@mozilla.org/ril;1"].getService(Ci.nsIRadioInterfaceLayer);
+ok(ril, "ril.constructor is " + ril.constructor);
+
+var radioInterface = ril.getRadioInterface(0);
+ok(radioInterface, "radioInterface.constructor is " + radioInterface.constrctor);
+
+var _pendingEmulatorShellCmdCount = 0;
+var _pendingEmulatorCmdCount = 0;
+
+/**
+ * Send emulator shell command with safe guard.
+ *
+ * We should only call |finish()| after all emulator shell command transactions
+ * end, so here comes with the pending counter. Resolve when the emulator
+ * shell gives response. Never reject.
+ *
+ * Fulfill params:
+ * result -- an array of emulator shell response lines.
+ *
+ * @param aCommands
+ * A string array commands to be passed to emulator through adb shell.
+ *
+ * @return A deferred promise.
+ */
+function runEmulatorShellCmdSafe(aCommands) {
+ return new Promise(function(aResolve, aReject) {
+ ++_pendingEmulatorShellCmdCount;
+ runEmulatorShell(aCommands, function(aResult) {
+ --_pendingEmulatorShellCmdCount;
+
+ log("Emulator shell response: " + JSON.stringify(aResult));
+ aResolve(aResult);
+ });
+ });
+}
+
+/**
+ * Send emulator command with safe guard.
+ *
+ * We should only call |finish()| after all emulator command transactions
+ * end, so here comes with the pending counter. Resolve when the emulator
+ * gives positive response, and reject otherwise.
+ *
+ * Fulfill params:
+ * result -- an array of emulator response lines.
+ * Reject params:
+ * result -- an array of emulator response lines.
+ *
+ * @param aCommand
+ * A string command to be passed to emulator through its telnet console.
+ *
+ * @return A deferred promise.
+ */
+function runEmulatorCmdSafe(aCommand) {
+ log(aCommand);
+ return new Promise(function(aResolve, aReject) {
+ ++_pendingEmulatorCmdCount;
+ runEmulatorCmd(aCommand, function(aResult) {
+ --_pendingEmulatorCmdCount;
+
+ log("Emulator console response: " + JSON.stringify(aResult));
+ if (Array.isArray(aResult) &&
+ aResult[aResult.length - 1] === "OK") {
+ aResolve(aResult);
+ } else {
+ aReject(aResult);
+ }
+ });
+ });
+}
+
+/**
+ * Get mozSettings value specified by @aKey.
+ *
+ * Resolve if that mozSettings value is retrieved successfully, reject
+ * otherwise.
+ *
+ * Fulfill params: The corresponding mozSettings value of the key.
+ * Reject params: (none)
+ *
+ * @param aKey
+ * A string.
+ * @param aAllowError [optional]
+ * A boolean value. If set to true, an error response won't be treated
+ * as test failure. Default: false.
+ *
+ * @return A deferred promise.
+ */
+function getSettings(aKey, aAllowError) {
+ let request = window.navigator.mozSettings.createLock().get(aKey);
+ return request.then(function resolve(aValue) {
+ log("getSettings(" + aKey + ") - success");
+ return aValue[aKey];
+ }, function reject(aError) {
+ ok(aAllowError, "getSettings(" + aKey + ") - error");
+ });
+}
+
+/**
+ * Set mozSettings values.
+ *
+ * Resolve if that mozSettings value is set successfully, reject otherwise.
+ *
+ * Fulfill params: (none)
+ * Reject params: (none)
+ *
+ * @param aKey
+ * A string key.
+ * @param aValue
+ * An object value.
+ * @param aAllowError [optional]
+ * A boolean value. If set to true, an error response won't be treated
+ * as test failure. Default: false.
+ *
+ * @return A deferred promise.
+ */
+function setSettings(aKey, aValue, aAllowError) {
+ let settings = {};
+ settings[aKey] = aValue;
+ let lock = window.navigator.mozSettings.createLock();
+ let request = lock.set(settings);
+ let deferred = Promise.defer();
+ lock.onsettingstransactionsuccess = function () {
+ log("setSettings(" + JSON.stringify(settings) + ") - success");
+ deferred.resolve();
+ };
+ lock.onsettingstransactionfailure = function () {
+ ok(aAllowError, "setSettings(" + JSON.stringify(settings) + ") - error");
+ // We resolve even though we've thrown an error, since the ok()
+ // will do that.
+ deferred.resolve();
+ };
+ return deferred.promise;
+}
+
+/**
+ * Wait for observer event.
+ *
+ * Resolve if that topic event occurs. Never reject.
+ *
+ * Fulfill params: the subject passed.
+ *
+ * @param aTopic
+ * A string topic name.
+ *
+ * @return A deferred promise.
+ */
+function waitForObserverEvent(aTopic) {
+ let obs = Cc["@mozilla.org/observer-service;1"].getService(Ci.nsIObserverService);
+ let deferred = Promise.defer();
+
+ obs.addObserver(function observer(subject, topic, data) {
+ if (topic === aTopic) {
+ obs.removeObserver(observer, aTopic);
+ deferred.resolve(subject);
+ }
+ }, aTopic, false);
+
+ return deferred.promise;
+}
+
+/**
+ * Wait for one named event.
+ *
+ * Resolve if that named event occurs. Never reject.
+ *
+ * Fulfill params: the DOMEvent passed.
+ *
+ * @param aEventTarget
+ * An EventTarget object.
+ * @param aEventName
+ * A string event name.
+ * @param aMatchFun [optional]
+ * A matching function returns true or false to filter the event.
+ *
+ * @return A deferred promise.
+ */
+function waitForTargetEvent(aEventTarget, aEventName, aMatchFun) {
+ return new Promise(function(aResolve, aReject) {
+ aEventTarget.addEventListener(aEventName, function onevent(aEvent) {
+ if (!aMatchFun || aMatchFun(aEvent)) {
+ aEventTarget.removeEventListener(aEventName, onevent);
+ ok(true, "Event '" + aEventName + "' got.");
+ aResolve(aEvent);
+ }
+ });
+ });
+}
+
+/**
+ * Set the default data connection enabling state, wait for
+ * "network-connection-state-changed" event and verify state.
+ *
+ * Fulfill params: instance of nsIRilNetworkInfo of the network connected.
+ *
+ * @param aEnabled
+ * A boolean state.
+ *
+ * @return A deferred promise.
+ */
+function setDataEnabledAndWait(aEnabled) {
+ let promises = [];
+ promises.push(waitForObserverEvent(TOPIC_CONNECTION_STATE_CHANGED)
+ .then(function(aSubject) {
+ ok(aSubject instanceof Ci.nsIRilNetworkInfo,
+ "subject should be an instance of nsIRilNetworkInfo");
+ is(aSubject.type, NETWORK_TYPE_MOBILE,
+ "subject.type should be " + NETWORK_TYPE_MOBILE);
+ is(aSubject.state,
+ aEnabled ? Ci.nsINetworkInfo.NETWORK_STATE_CONNECTED
+ : Ci.nsINetworkInfo.NETWORK_STATE_DISCONNECTED,
+ "subject.state should be " + aEnabled ? "CONNECTED" : "DISCONNECTED");
+
+ return aSubject;
+ }));
+ promises.push(setSettings(SETTINGS_KEY_DATA_ENABLED, aEnabled));
+
+ return Promise.all(promises).then(aValues => aValues[0]);
+}
+
+/**
+ * Setup a certain type of data connection, wait for
+ * "network-connection-state-changed" event and verify state.
+ *
+ * Fulfill params: instance of nsIRilNetworkInfo of the network connected.
+ *
+ * @param aNetworkType
+ * The mobile network type to setup.
+ *
+ * @return A deferred promise.
+ */
+function setupDataCallAndWait(aNetworkType) {
+ log("setupDataCallAndWait: " + aNetworkType);
+
+ let promises = [];
+ promises.push(waitForObserverEvent(TOPIC_CONNECTION_STATE_CHANGED)
+ .then(function(aSubject) {
+ ok(aSubject instanceof Ci.nsIRilNetworkInfo,
+ "subject should be an instance of nsIRilNetworkInfo");
+ is(aSubject.type, aNetworkType,
+ "subject.type should be " + aNetworkType);
+ is(aSubject.state, Ci.nsINetworkInfo.NETWORK_STATE_CONNECTED,
+ "subject.state should be CONNECTED");
+
+ return aSubject;
+ }));
+ promises.push(radioInterface.setupDataCallByType(aNetworkType));
+
+ return Promise.all(promises).then(aValues => aValues[0]);
+}
+
+/**
+ * Deactivate a certain type of data connection, wait for
+ * "network-connection-state-changed" event and verify state.
+ *
+ * Fulfill params: (none)
+ *
+ * @param aNetworkType
+ * The mobile network type to deactivate.
+ *
+ * @return A deferred promise.
+ */
+function deactivateDataCallAndWait(aNetworkType) {
+ log("deactivateDataCallAndWait: " + aNetworkType);
+
+ let promises = [];
+ promises.push(waitForObserverEvent(TOPIC_CONNECTION_STATE_CHANGED)
+ .then(function(aSubject) {
+ ok(aSubject instanceof Ci.nsIRilNetworkInfo,
+ "subject should be an instance of nsIRilNetworkInfo");
+ is(aSubject.type, aNetworkType,
+ "subject.type should be " + aNetworkType);
+ is(aSubject.state, Ci.nsINetworkInfo.NETWORK_STATE_DISCONNECTED,
+ "subject.state should be DISCONNECTED");
+ }));
+ promises.push(radioInterface.deactivateDataCallByType(aNetworkType));
+
+ return Promise.all(promises);
+}
+
+/**
+ * Wait for pending emulator transactions and call |finish()|.
+ */
+function cleanUp() {
+ // Use ok here so that we have at least one test run.
+ ok(true, ":: CLEANING UP ::");
+
+ waitFor(finish, function() {
+ return _pendingEmulatorShellCmdCount === 0 &&
+ _pendingEmulatorCmdCount === 0;
+ });
+}
+
+/**
+ * Basic test routine helper.
+ *
+ * This helper does nothing but clean-ups.
+ *
+ * @param aTestCaseMain
+ * A function that takes no parameter.
+ */
+function startTestBase(aTestCaseMain) {
+ Promise.resolve()
+ .then(aTestCaseMain)
+ .then(cleanUp, function(aException) {
+ ok(false, "promise rejects during test: " + aException);
+ cleanUp();
+ });
+}
diff --git a/dom/system/gonk/tests/marionette/manifest.ini b/dom/system/gonk/tests/marionette/manifest.ini
new file mode 100644
index 000000000..528fe3baf
--- /dev/null
+++ b/dom/system/gonk/tests/marionette/manifest.ini
@@ -0,0 +1,19 @@
+[DEFAULT]
+run-if = buildapp == 'b2g'
+
+[test_geolocation.js]
+skip-if = true # Bug 808783
+[test_fakevolume.js]
+[test_ril_code_quality.py]
+[test_screen_state.js]
+[test_dsds_numRadioInterfaces.js]
+[test_data_connection.js]
+[test_network_active_changed.js]
+[test_multiple_data_connection.js]
+[test_data_connection_proxy.js]
+[test_network_interface_list_service.js]
+[test_all_network_info.js]
+[test_network_interface_mtu.js]
+skip-if = android_version < '19'
+[test_timezone_changes.js]
+skip-if = android_version < '19'
diff --git a/dom/system/gonk/tests/marionette/ril_jshint/README.md b/dom/system/gonk/tests/marionette/ril_jshint/README.md
new file mode 100644
index 000000000..a63967d63
--- /dev/null
+++ b/dom/system/gonk/tests/marionette/ril_jshint/README.md
@@ -0,0 +1,9 @@
+Test RIL Code Quality
+=====================
+
+For more information, please refer to
+
+* Bug 880643 - B2G RIL: Add a code quality test on try server for RIL javascript code in gecko
+* Slide: https://speakerdeck.com/aknow/improve-code-quality-of-ril-code-by-jshint
+* Document: https://hackpad.com/Code-Quality-Test-For-RIL-Javascript-Code-In-Gecko-cz5j7YIGiw8
+
diff --git a/dom/system/gonk/tests/marionette/ril_jshint/jshint.js b/dom/system/gonk/tests/marionette/ril_jshint/jshint.js
new file mode 100644
index 000000000..ec5263a5b
--- /dev/null
+++ b/dom/system/gonk/tests/marionette/ril_jshint/jshint.js
@@ -0,0 +1,11096 @@
+//2.1.3
+var JSHINT;
+(function () {
+var require;
+require=(function(e,t,n){function i(n,s){if(!t[n]){if(!e[n]){var o=typeof require=="function"&&require;if(!s&&o)return o(n,!0);if(r)return r(n,!0);throw new Error("Cannot find module '"+n+"'")}var u=t[n]={exports:{}};e[n][0].call(u.exports,function(t){var r=e[n][1][t];return i(r?r:t)},u,u.exports)}return t[n].exports}var r=typeof require=="function"&&require;for(var s=0;s<n.length;s++)i(n[s]);return i})({1:[function(require,module,exports){
+// shim for using process in browser
+
+var process = module.exports = {};
+
+process.nextTick = (function () {
+ var canSetImmediate = typeof window !== 'undefined'
+ && window.setImmediate;
+ var canPost = typeof window !== 'undefined'
+ && window.postMessage && window.addEventListener
+ ;
+
+ if (canSetImmediate) {
+ return function (f) { return window.setImmediate(f) };
+ }
+
+ if (canPost) {
+ var queue = [];
+ window.addEventListener('message', function (ev) {
+ if (ev.source === window && ev.data === 'process-tick') {
+ ev.stopPropagation();
+ if (queue.length > 0) {
+ var fn = queue.shift();
+ fn();
+ }
+ }
+ }, true);
+
+ return function nextTick(fn) {
+ queue.push(fn);
+ window.postMessage('process-tick', '*');
+ };
+ }
+
+ return function nextTick(fn) {
+ setTimeout(fn, 0);
+ };
+})();
+
+process.title = 'browser';
+process.browser = true;
+process.env = {};
+process.argv = [];
+
+process.binding = function (name) {
+ throw new Error('process.binding is not supported');
+}
+
+// TODO(shtylman)
+process.cwd = function () { return '/' };
+process.chdir = function (dir) {
+ throw new Error('process.chdir is not supported');
+};
+
+},{}],2:[function(require,module,exports){
+(function(process){if (!process.EventEmitter) process.EventEmitter = function () {};
+
+var EventEmitter = exports.EventEmitter = process.EventEmitter;
+var isArray = typeof Array.isArray === 'function'
+ ? Array.isArray
+ : function (xs) {
+ return Object.prototype.toString.call(xs) === '[object Array]'
+ }
+;
+function indexOf (xs, x) {
+ if (xs.indexOf) return xs.indexOf(x);
+ for (var i = 0; i < xs.length; i++) {
+ if (x === xs[i]) return i;
+ }
+ return -1;
+}
+
+// By default EventEmitters will print a warning if more than
+// 10 listeners are added to it. This is a useful default which
+// helps finding memory leaks.
+//
+// Obviously not all Emitters should be limited to 10. This function allows
+// that to be increased. Set to zero for unlimited.
+var defaultMaxListeners = 10;
+EventEmitter.prototype.setMaxListeners = function(n) {
+ if (!this._events) this._events = {};
+ this._events.maxListeners = n;
+};
+
+
+EventEmitter.prototype.emit = function(type) {
+ // If there is no 'error' event listener then throw.
+ if (type === 'error') {
+ if (!this._events || !this._events.error ||
+ (isArray(this._events.error) && !this._events.error.length))
+ {
+ if (arguments[1] instanceof Error) {
+ throw arguments[1]; // Unhandled 'error' event
+ } else {
+ throw new Error("Uncaught, unspecified 'error' event.");
+ }
+ return false;
+ }
+ }
+
+ if (!this._events) return false;
+ var handler = this._events[type];
+ if (!handler) return false;
+
+ if (typeof handler == 'function') {
+ switch (arguments.length) {
+ // fast cases
+ case 1:
+ handler.call(this);
+ break;
+ case 2:
+ handler.call(this, arguments[1]);
+ break;
+ case 3:
+ handler.call(this, arguments[1], arguments[2]);
+ break;
+ // slower
+ default:
+ var args = Array.prototype.slice.call(arguments, 1);
+ handler.apply(this, args);
+ }
+ return true;
+
+ } else if (isArray(handler)) {
+ var args = Array.prototype.slice.call(arguments, 1);
+
+ var listeners = handler.slice();
+ for (var i = 0, l = listeners.length; i < l; i++) {
+ listeners[i].apply(this, args);
+ }
+ return true;
+
+ } else {
+ return false;
+ }
+};
+
+// EventEmitter is defined in src/node_events.cc
+// EventEmitter.prototype.emit() is also defined there.
+EventEmitter.prototype.addListener = function(type, listener) {
+ if ('function' !== typeof listener) {
+ throw new Error('addListener only takes instances of Function');
+ }
+
+ if (!this._events) this._events = {};
+
+ // To avoid recursion in the case that type == "newListeners"! Before
+ // adding it to the listeners, first emit "newListeners".
+ this.emit('newListener', type, listener);
+
+ if (!this._events[type]) {
+ // Optimize the case of one listener. Don't need the extra array object.
+ this._events[type] = listener;
+ } else if (isArray(this._events[type])) {
+
+ // Check for listener leak
+ if (!this._events[type].warned) {
+ var m;
+ if (this._events.maxListeners !== undefined) {
+ m = this._events.maxListeners;
+ } else {
+ m = defaultMaxListeners;
+ }
+
+ if (m && m > 0 && this._events[type].length > m) {
+ this._events[type].warned = true;
+ console.error('(node) warning: possible EventEmitter memory ' +
+ 'leak detected. %d listeners added. ' +
+ 'Use emitter.setMaxListeners() to increase limit.',
+ this._events[type].length);
+ console.trace();
+ }
+ }
+
+ // If we've already got an array, just append.
+ this._events[type].push(listener);
+ } else {
+ // Adding the second element, need to change to array.
+ this._events[type] = [this._events[type], listener];
+ }
+
+ return this;
+};
+
+EventEmitter.prototype.on = EventEmitter.prototype.addListener;
+
+EventEmitter.prototype.once = function(type, listener) {
+ var self = this;
+ self.on(type, function g() {
+ self.removeListener(type, g);
+ listener.apply(this, arguments);
+ });
+
+ return this;
+};
+
+EventEmitter.prototype.removeListener = function(type, listener) {
+ if ('function' !== typeof listener) {
+ throw new Error('removeListener only takes instances of Function');
+ }
+
+ // does not use listeners(), so no side effect of creating _events[type]
+ if (!this._events || !this._events[type]) return this;
+
+ var list = this._events[type];
+
+ if (isArray(list)) {
+ var i = indexOf(list, listener);
+ if (i < 0) return this;
+ list.splice(i, 1);
+ if (list.length == 0)
+ delete this._events[type];
+ } else if (this._events[type] === listener) {
+ delete this._events[type];
+ }
+
+ return this;
+};
+
+EventEmitter.prototype.removeAllListeners = function(type) {
+ if (arguments.length === 0) {
+ this._events = {};
+ return this;
+ }
+
+ // does not use listeners(), so no side effect of creating _events[type]
+ if (type && this._events && this._events[type]) this._events[type] = null;
+ return this;
+};
+
+EventEmitter.prototype.listeners = function(type) {
+ if (!this._events) this._events = {};
+ if (!this._events[type]) this._events[type] = [];
+ if (!isArray(this._events[type])) {
+ this._events[type] = [this._events[type]];
+ }
+ return this._events[type];
+};
+
+})(require("__browserify_process"))
+},{"__browserify_process":1}],3:[function(require,module,exports){
+(function(){// jshint -W001
+
+"use strict";
+
+// Identifiers provided by the ECMAScript standard.
+
+exports.reservedVars = {
+ arguments : false,
+ NaN : false
+};
+
+exports.ecmaIdentifiers = {
+ Array : false,
+ Boolean : false,
+ Date : false,
+ decodeURI : false,
+ decodeURIComponent : false,
+ encodeURI : false,
+ encodeURIComponent : false,
+ Error : false,
+ "eval" : false,
+ EvalError : false,
+ Function : false,
+ hasOwnProperty : false,
+ isFinite : false,
+ isNaN : false,
+ JSON : false,
+ Math : false,
+ Map : false,
+ Number : false,
+ Object : false,
+ parseInt : false,
+ parseFloat : false,
+ RangeError : false,
+ ReferenceError : false,
+ RegExp : false,
+ Set : false,
+ String : false,
+ SyntaxError : false,
+ TypeError : false,
+ URIError : false,
+ WeakMap : false
+};
+
+// Global variables commonly provided by a web browser environment.
+
+exports.browser = {
+ ArrayBuffer : false,
+ ArrayBufferView : false,
+ Audio : false,
+ Blob : false,
+ addEventListener : false,
+ applicationCache : false,
+ atob : false,
+ blur : false,
+ btoa : false,
+ clearInterval : false,
+ clearTimeout : false,
+ close : false,
+ closed : false,
+ DataView : false,
+ DOMParser : false,
+ defaultStatus : false,
+ document : false,
+ Element : false,
+ ElementTimeControl : false,
+ event : false,
+ FileReader : false,
+ Float32Array : false,
+ Float64Array : false,
+ FormData : false,
+ focus : false,
+ frames : false,
+ getComputedStyle : false,
+ HTMLElement : false,
+ HTMLAnchorElement : false,
+ HTMLBaseElement : false,
+ HTMLBlockquoteElement: false,
+ HTMLBodyElement : false,
+ HTMLBRElement : false,
+ HTMLButtonElement : false,
+ HTMLCanvasElement : false,
+ HTMLDirectoryElement : false,
+ HTMLDivElement : false,
+ HTMLDListElement : false,
+ HTMLFieldSetElement : false,
+ HTMLFontElement : false,
+ HTMLFormElement : false,
+ HTMLFrameElement : false,
+ HTMLFrameSetElement : false,
+ HTMLHeadElement : false,
+ HTMLHeadingElement : false,
+ HTMLHRElement : false,
+ HTMLHtmlElement : false,
+ HTMLIFrameElement : false,
+ HTMLImageElement : false,
+ HTMLInputElement : false,
+ HTMLIsIndexElement : false,
+ HTMLLabelElement : false,
+ HTMLLayerElement : false,
+ HTMLLegendElement : false,
+ HTMLLIElement : false,
+ HTMLLinkElement : false,
+ HTMLMapElement : false,
+ HTMLMenuElement : false,
+ HTMLMetaElement : false,
+ HTMLModElement : false,
+ HTMLObjectElement : false,
+ HTMLOListElement : false,
+ HTMLOptGroupElement : false,
+ HTMLOptionElement : false,
+ HTMLParagraphElement : false,
+ HTMLParamElement : false,
+ HTMLPreElement : false,
+ HTMLQuoteElement : false,
+ HTMLScriptElement : false,
+ HTMLSelectElement : false,
+ HTMLStyleElement : false,
+ HTMLTableCaptionElement: false,
+ HTMLTableCellElement : false,
+ HTMLTableColElement : false,
+ HTMLTableElement : false,
+ HTMLTableRowElement : false,
+ HTMLTableSectionElement: false,
+ HTMLTextAreaElement : false,
+ HTMLTitleElement : false,
+ HTMLUListElement : false,
+ HTMLVideoElement : false,
+ history : false,
+ Int16Array : false,
+ Int32Array : false,
+ Int8Array : false,
+ Image : false,
+ length : false,
+ localStorage : false,
+ location : false,
+ MessageChannel : false,
+ MessageEvent : false,
+ MessagePort : false,
+ moveBy : false,
+ moveTo : false,
+ MutationObserver : false,
+ name : false,
+ Node : false,
+ NodeFilter : false,
+ navigator : false,
+ onbeforeunload : true,
+ onblur : true,
+ onerror : true,
+ onfocus : true,
+ onload : true,
+ onresize : true,
+ onunload : true,
+ open : false,
+ openDatabase : false,
+ opener : false,
+ Option : false,
+ parent : false,
+ print : false,
+ removeEventListener : false,
+ resizeBy : false,
+ resizeTo : false,
+ screen : false,
+ scroll : false,
+ scrollBy : false,
+ scrollTo : false,
+ sessionStorage : false,
+ setInterval : false,
+ setTimeout : false,
+ SharedWorker : false,
+ status : false,
+ SVGAElement : false,
+ SVGAltGlyphDefElement: false,
+ SVGAltGlyphElement : false,
+ SVGAltGlyphItemElement: false,
+ SVGAngle : false,
+ SVGAnimateColorElement: false,
+ SVGAnimateElement : false,
+ SVGAnimateMotionElement: false,
+ SVGAnimateTransformElement: false,
+ SVGAnimatedAngle : false,
+ SVGAnimatedBoolean : false,
+ SVGAnimatedEnumeration: false,
+ SVGAnimatedInteger : false,
+ SVGAnimatedLength : false,
+ SVGAnimatedLengthList: false,
+ SVGAnimatedNumber : false,
+ SVGAnimatedNumberList: false,
+ SVGAnimatedPathData : false,
+ SVGAnimatedPoints : false,
+ SVGAnimatedPreserveAspectRatio: false,
+ SVGAnimatedRect : false,
+ SVGAnimatedString : false,
+ SVGAnimatedTransformList: false,
+ SVGAnimationElement : false,
+ SVGCSSRule : false,
+ SVGCircleElement : false,
+ SVGClipPathElement : false,
+ SVGColor : false,
+ SVGColorProfileElement: false,
+ SVGColorProfileRule : false,
+ SVGComponentTransferFunctionElement: false,
+ SVGCursorElement : false,
+ SVGDefsElement : false,
+ SVGDescElement : false,
+ SVGDocument : false,
+ SVGElement : false,
+ SVGElementInstance : false,
+ SVGElementInstanceList: false,
+ SVGEllipseElement : false,
+ SVGExternalResourcesRequired: false,
+ SVGFEBlendElement : false,
+ SVGFEColorMatrixElement: false,
+ SVGFEComponentTransferElement: false,
+ SVGFECompositeElement: false,
+ SVGFEConvolveMatrixElement: false,
+ SVGFEDiffuseLightingElement: false,
+ SVGFEDisplacementMapElement: false,
+ SVGFEDistantLightElement: false,
+ SVGFEDropShadowElement: false,
+ SVGFEFloodElement : false,
+ SVGFEFuncAElement : false,
+ SVGFEFuncBElement : false,
+ SVGFEFuncGElement : false,
+ SVGFEFuncRElement : false,
+ SVGFEGaussianBlurElement: false,
+ SVGFEImageElement : false,
+ SVGFEMergeElement : false,
+ SVGFEMergeNodeElement: false,
+ SVGFEMorphologyElement: false,
+ SVGFEOffsetElement : false,
+ SVGFEPointLightElement: false,
+ SVGFESpecularLightingElement: false,
+ SVGFESpotLightElement: false,
+ SVGFETileElement : false,
+ SVGFETurbulenceElement: false,
+ SVGFilterElement : false,
+ SVGFilterPrimitiveStandardAttributes: false,
+ SVGFitToViewBox : false,
+ SVGFontElement : false,
+ SVGFontFaceElement : false,
+ SVGFontFaceFormatElement: false,
+ SVGFontFaceNameElement: false,
+ SVGFontFaceSrcElement: false,
+ SVGFontFaceUriElement: false,
+ SVGForeignObjectElement: false,
+ SVGGElement : false,
+ SVGGlyphElement : false,
+ SVGGlyphRefElement : false,
+ SVGGradientElement : false,
+ SVGHKernElement : false,
+ SVGICCColor : false,
+ SVGImageElement : false,
+ SVGLangSpace : false,
+ SVGLength : false,
+ SVGLengthList : false,
+ SVGLineElement : false,
+ SVGLinearGradientElement: false,
+ SVGLocatable : false,
+ SVGMPathElement : false,
+ SVGMarkerElement : false,
+ SVGMaskElement : false,
+ SVGMatrix : false,
+ SVGMetadataElement : false,
+ SVGMissingGlyphElement: false,
+ SVGNumber : false,
+ SVGNumberList : false,
+ SVGPaint : false,
+ SVGPathElement : false,
+ SVGPathSeg : false,
+ SVGPathSegArcAbs : false,
+ SVGPathSegArcRel : false,
+ SVGPathSegClosePath : false,
+ SVGPathSegCurvetoCubicAbs: false,
+ SVGPathSegCurvetoCubicRel: false,
+ SVGPathSegCurvetoCubicSmoothAbs: false,
+ SVGPathSegCurvetoCubicSmoothRel: false,
+ SVGPathSegCurvetoQuadraticAbs: false,
+ SVGPathSegCurvetoQuadraticRel: false,
+ SVGPathSegCurvetoQuadraticSmoothAbs: false,
+ SVGPathSegCurvetoQuadraticSmoothRel: false,
+ SVGPathSegLinetoAbs : false,
+ SVGPathSegLinetoHorizontalAbs: false,
+ SVGPathSegLinetoHorizontalRel: false,
+ SVGPathSegLinetoRel : false,
+ SVGPathSegLinetoVerticalAbs: false,
+ SVGPathSegLinetoVerticalRel: false,
+ SVGPathSegList : false,
+ SVGPathSegMovetoAbs : false,
+ SVGPathSegMovetoRel : false,
+ SVGPatternElement : false,
+ SVGPoint : false,
+ SVGPointList : false,
+ SVGPolygonElement : false,
+ SVGPolylineElement : false,
+ SVGPreserveAspectRatio: false,
+ SVGRadialGradientElement: false,
+ SVGRect : false,
+ SVGRectElement : false,
+ SVGRenderingIntent : false,
+ SVGSVGElement : false,
+ SVGScriptElement : false,
+ SVGSetElement : false,
+ SVGStopElement : false,
+ SVGStringList : false,
+ SVGStylable : false,
+ SVGStyleElement : false,
+ SVGSwitchElement : false,
+ SVGSymbolElement : false,
+ SVGTRefElement : false,
+ SVGTSpanElement : false,
+ SVGTests : false,
+ SVGTextContentElement: false,
+ SVGTextElement : false,
+ SVGTextPathElement : false,
+ SVGTextPositioningElement: false,
+ SVGTitleElement : false,
+ SVGTransform : false,
+ SVGTransformList : false,
+ SVGTransformable : false,
+ SVGURIReference : false,
+ SVGUnitTypes : false,
+ SVGUseElement : false,
+ SVGVKernElement : false,
+ SVGViewElement : false,
+ SVGViewSpec : false,
+ SVGZoomAndPan : false,
+ TimeEvent : false,
+ top : false,
+ Uint16Array : false,
+ Uint32Array : false,
+ Uint8Array : false,
+ Uint8ClampedArray : false,
+ WebSocket : false,
+ window : false,
+ Worker : false,
+ XMLHttpRequest : false,
+ XMLSerializer : false,
+ XPathEvaluator : false,
+ XPathException : false,
+ XPathExpression : false,
+ XPathNSResolver : false,
+ XPathResult : false
+};
+
+exports.devel = {
+ alert : false,
+ confirm: false,
+ console: false,
+ Debug : false,
+ opera : false,
+ prompt : false
+};
+
+exports.worker = {
+ importScripts: true,
+ postMessage : true,
+ self : true
+};
+
+// Widely adopted global names that are not part of ECMAScript standard
+exports.nonstandard = {
+ escape : false,
+ unescape: false
+};
+
+// Globals provided by popular JavaScript environments.
+
+exports.couch = {
+ "require" : false,
+ respond : false,
+ getRow : false,
+ emit : false,
+ send : false,
+ start : false,
+ sum : false,
+ log : false,
+ exports : false,
+ module : false,
+ provides : false
+};
+
+exports.node = {
+ __filename : false,
+ __dirname : false,
+ Buffer : false,
+ DataView : false,
+ console : false,
+ exports : true, // In Node it is ok to exports = module.exports = foo();
+ GLOBAL : false,
+ global : false,
+ module : false,
+ process : false,
+ require : false,
+ setTimeout : false,
+ clearTimeout : false,
+ setInterval : false,
+ clearInterval : false,
+ setImmediate : false, // v0.9.1+
+ clearImmediate: false // v0.9.1+
+};
+
+exports.phantom = {
+ phantom : true,
+ require : true,
+ WebPage : true
+};
+
+exports.rhino = {
+ defineClass : false,
+ deserialize : false,
+ gc : false,
+ help : false,
+ importPackage: false,
+ "java" : false,
+ load : false,
+ loadClass : false,
+ print : false,
+ quit : false,
+ readFile : false,
+ readUrl : false,
+ runCommand : false,
+ seal : false,
+ serialize : false,
+ spawn : false,
+ sync : false,
+ toint32 : false,
+ version : false
+};
+
+exports.wsh = {
+ ActiveXObject : true,
+ Enumerator : true,
+ GetObject : true,
+ ScriptEngine : true,
+ ScriptEngineBuildVersion : true,
+ ScriptEngineMajorVersion : true,
+ ScriptEngineMinorVersion : true,
+ VBArray : true,
+ WSH : true,
+ WScript : true,
+ XDomainRequest : true
+};
+
+// Globals provided by popular JavaScript libraries.
+
+exports.dojo = {
+ dojo : false,
+ dijit : false,
+ dojox : false,
+ define : false,
+ "require": false
+};
+
+exports.jquery = {
+ "$" : false,
+ jQuery : false
+};
+
+exports.mootools = {
+ "$" : false,
+ "$$" : false,
+ Asset : false,
+ Browser : false,
+ Chain : false,
+ Class : false,
+ Color : false,
+ Cookie : false,
+ Core : false,
+ Document : false,
+ DomReady : false,
+ DOMEvent : false,
+ DOMReady : false,
+ Drag : false,
+ Element : false,
+ Elements : false,
+ Event : false,
+ Events : false,
+ Fx : false,
+ Group : false,
+ Hash : false,
+ HtmlTable : false,
+ Iframe : false,
+ IframeShim : false,
+ InputValidator: false,
+ instanceOf : false,
+ Keyboard : false,
+ Locale : false,
+ Mask : false,
+ MooTools : false,
+ Native : false,
+ Options : false,
+ OverText : false,
+ Request : false,
+ Scroller : false,
+ Slick : false,
+ Slider : false,
+ Sortables : false,
+ Spinner : false,
+ Swiff : false,
+ Tips : false,
+ Type : false,
+ typeOf : false,
+ URI : false,
+ Window : false
+};
+
+exports.prototypejs = {
+ "$" : false,
+ "$$" : false,
+ "$A" : false,
+ "$F" : false,
+ "$H" : false,
+ "$R" : false,
+ "$break" : false,
+ "$continue" : false,
+ "$w" : false,
+ Abstract : false,
+ Ajax : false,
+ Class : false,
+ Enumerable : false,
+ Element : false,
+ Event : false,
+ Field : false,
+ Form : false,
+ Hash : false,
+ Insertion : false,
+ ObjectRange : false,
+ PeriodicalExecuter: false,
+ Position : false,
+ Prototype : false,
+ Selector : false,
+ Template : false,
+ Toggle : false,
+ Try : false,
+ Autocompleter : false,
+ Builder : false,
+ Control : false,
+ Draggable : false,
+ Draggables : false,
+ Droppables : false,
+ Effect : false,
+ Sortable : false,
+ SortableObserver : false,
+ Sound : false,
+ Scriptaculous : false
+};
+
+exports.yui = {
+ YUI : false,
+ Y : false,
+ YUI_config: false
+};
+
+
+})()
+},{}],4:[function(require,module,exports){
+"use strict";
+
+var state = {
+ syntax: {},
+
+ reset: function () {
+ this.tokens = {
+ prev: null,
+ next: null,
+ curr: null
+ };
+
+ this.option = {};
+ this.ignored = {};
+ this.directive = {};
+ this.jsonMode = false;
+ this.jsonWarnings = [];
+ this.lines = [];
+ this.tab = "";
+ this.cache = {}; // Node.JS doesn't have Map. Sniff.
+ }
+};
+
+exports.state = state;
+
+},{}],5:[function(require,module,exports){
+(function(){"use strict";
+
+exports.register = function (linter) {
+ // Check for properties named __proto__. This special property was
+ // deprecated and then re-introduced for ES6.
+
+ linter.on("Identifier", function style_scanProto(data) {
+ if (linter.getOption("proto")) {
+ return;
+ }
+
+ if (data.name === "__proto__") {
+ linter.warn("W103", {
+ line: data.line,
+ char: data.char,
+ data: [ data.name ]
+ });
+ }
+ });
+
+ // Check for properties named __iterator__. This is a special property
+ // available only in browsers with JavaScript 1.7 implementation.
+
+ linter.on("Identifier", function style_scanIterator(data) {
+ if (linter.getOption("iterator")) {
+ return;
+ }
+
+ if (data.name === "__iterator__") {
+ linter.warn("W104", {
+ line: data.line,
+ char: data.char,
+ data: [ data.name ]
+ });
+ }
+ });
+
+ // Check for dangling underscores.
+
+ linter.on("Identifier", function style_scanDangling(data) {
+ if (!linter.getOption("nomen")) {
+ return;
+ }
+
+ // Underscore.js
+ if (data.name === "_") {
+ return;
+ }
+
+ // In Node, __dirname and __filename should be ignored.
+ if (linter.getOption("node")) {
+ if (/^(__dirname|__filename)$/.test(data.name) && !data.isProperty) {
+ return;
+ }
+ }
+
+ if (/^(_+.*|.*_+)$/.test(data.name)) {
+ linter.warn("W105", {
+ line: data.line,
+ char: data.from,
+ data: [ "dangling '_'", data.name ]
+ });
+ }
+ });
+
+ // Check that all identifiers are using camelCase notation.
+ // Exceptions: names like MY_VAR and _myVar.
+
+ linter.on("Identifier", function style_scanCamelCase(data) {
+ if (!linter.getOption("camelcase")) {
+ return;
+ }
+
+ if (data.name.replace(/^_+/, "").indexOf("_") > -1 && !data.name.match(/^[A-Z0-9_]*$/)) {
+ linter.warn("W106", {
+ line: data.line,
+ char: data.from,
+ data: [ data.name ]
+ });
+ }
+ });
+
+ // Enforce consistency in style of quoting.
+
+ linter.on("String", function style_scanQuotes(data) {
+ var quotmark = linter.getOption("quotmark");
+ var code;
+
+ if (!quotmark) {
+ return;
+ }
+
+ // If quotmark is set to 'single' warn about all double-quotes.
+
+ if (quotmark === "single" && data.quote !== "'") {
+ code = "W109";
+ }
+
+ // If quotmark is set to 'double' warn about all single-quotes.
+
+ if (quotmark === "double" && data.quote !== "\"") {
+ code = "W108";
+ }
+
+ // If quotmark is set to true, remember the first quotation style
+ // and then warn about all others.
+
+ if (quotmark === true) {
+ if (!linter.getCache("quotmark")) {
+ linter.setCache("quotmark", data.quote);
+ }
+
+ if (linter.getCache("quotmark") !== data.quote) {
+ code = "W110";
+ }
+ }
+
+ if (code) {
+ linter.warn(code, {
+ line: data.line,
+ char: data.char,
+ });
+ }
+ });
+
+ linter.on("Number", function style_scanNumbers(data) {
+ if (data.value.charAt(0) === ".") {
+ // Warn about a leading decimal point.
+ linter.warn("W008", {
+ line: data.line,
+ char: data.char,
+ data: [ data.value ]
+ });
+ }
+
+ if (data.value.substr(data.value.length - 1) === ".") {
+ // Warn about a trailing decimal point.
+ linter.warn("W047", {
+ line: data.line,
+ char: data.char,
+ data: [ data.value ]
+ });
+ }
+
+ if (/^00+/.test(data.value)) {
+ // Multiple leading zeroes.
+ linter.warn("W046", {
+ line: data.line,
+ char: data.char,
+ data: [ data.value ]
+ });
+ }
+ });
+
+ // Warn about script URLs.
+
+ linter.on("String", function style_scanJavaScriptURLs(data) {
+ var re = /^(?:javascript|jscript|ecmascript|vbscript|mocha|livescript)\s*:/i;
+
+ if (linter.getOption("scripturl")) {
+ return;
+ }
+
+ if (re.test(data.value)) {
+ linter.warn("W107", {
+ line: data.line,
+ char: data.char
+ });
+ }
+ });
+};
+})()
+},{}],6:[function(require,module,exports){
+/*
+ * Regular expressions. Some of these are stupidly long.
+ */
+
+/*jshint maxlen:1000 */
+
+"use string";
+
+// Unsafe comment or string (ax)
+exports.unsafeString =
+ /@cc|<\/?|script|\]\s*\]|<\s*!|&lt/i;
+
+// Unsafe characters that are silently deleted by one or more browsers (cx)
+exports.unsafeChars =
+ /[\u0000-\u001f\u007f-\u009f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/;
+
+// Characters in strings that need escaping (nx and nxg)
+exports.needEsc =
+ /[\u0000-\u001f&<"\/\\\u007f-\u009f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/;
+
+exports.needEscGlobal =
+ /[\u0000-\u001f&<"\/\\\u007f-\u009f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g;
+
+// Star slash (lx)
+exports.starSlash = /\*\//;
+
+// Identifier (ix)
+exports.identifier = /^([a-zA-Z_$][a-zA-Z0-9_$]*)$/;
+
+// JavaScript URL (jx)
+exports.javascriptURL = /^(?:javascript|jscript|ecmascript|vbscript|mocha|livescript)\s*:/i;
+
+// Catches /* falls through */ comments (ft)
+//exports.fallsThrough = /^\s*\/\*\s*falls?\sthrough\s*\*\/\s*$/;
+exports.fallsThrough = /^\s*\/\/\s*Falls?\sthrough.*\s*$/;
+
+},{}],7:[function(require,module,exports){
+(function(global){/*global window, global*/
+var util = require("util")
+var assert = require("assert")
+
+var slice = Array.prototype.slice
+var console
+var times = {}
+
+if (typeof global !== "undefined" && global.console) {
+ console = global.console
+} else if (typeof window !== "undefined" && window.console) {
+ console = window.console
+} else {
+ console = window.console = {}
+}
+
+var functions = [
+ [log, "log"]
+ , [info, "info"]
+ , [warn, "warn"]
+ , [error, "error"]
+ , [time, "time"]
+ , [timeEnd, "timeEnd"]
+ , [trace, "trace"]
+ , [dir, "dir"]
+ , [assert, "assert"]
+]
+
+for (var i = 0; i < functions.length; i++) {
+ var tuple = functions[i]
+ var f = tuple[0]
+ var name = tuple[1]
+
+ if (!console[name]) {
+ console[name] = f
+ }
+}
+
+module.exports = console
+
+function log() {}
+
+function info() {
+ console.log.apply(console, arguments)
+}
+
+function warn() {
+ console.log.apply(console, arguments)
+}
+
+function error() {
+ console.warn.apply(console, arguments)
+}
+
+function time(label) {
+ times[label] = Date.now()
+}
+
+function timeEnd(label) {
+ var time = times[label]
+ if (!time) {
+ throw new Error("No such label: " + label)
+ }
+
+ var duration = Date.now() - time
+ console.log(label + ": " + duration + "ms")
+}
+
+function trace() {
+ var err = new Error()
+ err.name = "Trace"
+ err.message = util.format.apply(null, arguments)
+ console.error(err.stack)
+}
+
+function dir(object) {
+ console.log(util.inspect(object) + "\n")
+}
+
+function assert(expression) {
+ if (!expression) {
+ var arr = slice.call(arguments, 1)
+ assert.ok(false, util.format.apply(null, arr))
+ }
+}
+
+})(window)
+},{"util":8,"assert":9}],10:[function(require,module,exports){
+(function(){/*
+ * Lexical analysis and token construction.
+ */
+
+"use strict";
+
+var _ = require("underscore");
+var events = require("events");
+var reg = require("./reg.js");
+var state = require("./state.js").state;
+
+// Some of these token types are from JavaScript Parser API
+// while others are specific to JSHint parser.
+// JS Parser API: https://developer.mozilla.org/en-US/docs/SpiderMonkey/Parser_API
+
+var Token = {
+ Identifier: 1,
+ Punctuator: 2,
+ NumericLiteral: 3,
+ StringLiteral: 4,
+ Comment: 5,
+ Keyword: 6,
+ NullLiteral: 7,
+ BooleanLiteral: 8,
+ RegExp: 9
+};
+
+// This is auto generated from the unicode tables.
+// The tables are at:
+// http://www.fileformat.info/info/unicode/category/Lu/list.htm
+// http://www.fileformat.info/info/unicode/category/Ll/list.htm
+// http://www.fileformat.info/info/unicode/category/Lt/list.htm
+// http://www.fileformat.info/info/unicode/category/Lm/list.htm
+// http://www.fileformat.info/info/unicode/category/Lo/list.htm
+// http://www.fileformat.info/info/unicode/category/Nl/list.htm
+
+var unicodeLetterTable = [
+ 170, 170, 181, 181, 186, 186, 192, 214,
+ 216, 246, 248, 705, 710, 721, 736, 740, 748, 748, 750, 750,
+ 880, 884, 886, 887, 890, 893, 902, 902, 904, 906, 908, 908,
+ 910, 929, 931, 1013, 1015, 1153, 1162, 1319, 1329, 1366,
+ 1369, 1369, 1377, 1415, 1488, 1514, 1520, 1522, 1568, 1610,
+ 1646, 1647, 1649, 1747, 1749, 1749, 1765, 1766, 1774, 1775,
+ 1786, 1788, 1791, 1791, 1808, 1808, 1810, 1839, 1869, 1957,
+ 1969, 1969, 1994, 2026, 2036, 2037, 2042, 2042, 2048, 2069,
+ 2074, 2074, 2084, 2084, 2088, 2088, 2112, 2136, 2308, 2361,
+ 2365, 2365, 2384, 2384, 2392, 2401, 2417, 2423, 2425, 2431,
+ 2437, 2444, 2447, 2448, 2451, 2472, 2474, 2480, 2482, 2482,
+ 2486, 2489, 2493, 2493, 2510, 2510, 2524, 2525, 2527, 2529,
+ 2544, 2545, 2565, 2570, 2575, 2576, 2579, 2600, 2602, 2608,
+ 2610, 2611, 2613, 2614, 2616, 2617, 2649, 2652, 2654, 2654,
+ 2674, 2676, 2693, 2701, 2703, 2705, 2707, 2728, 2730, 2736,
+ 2738, 2739, 2741, 2745, 2749, 2749, 2768, 2768, 2784, 2785,
+ 2821, 2828, 2831, 2832, 2835, 2856, 2858, 2864, 2866, 2867,
+ 2869, 2873, 2877, 2877, 2908, 2909, 2911, 2913, 2929, 2929,
+ 2947, 2947, 2949, 2954, 2958, 2960, 2962, 2965, 2969, 2970,
+ 2972, 2972, 2974, 2975, 2979, 2980, 2984, 2986, 2990, 3001,
+ 3024, 3024, 3077, 3084, 3086, 3088, 3090, 3112, 3114, 3123,
+ 3125, 3129, 3133, 3133, 3160, 3161, 3168, 3169, 3205, 3212,
+ 3214, 3216, 3218, 3240, 3242, 3251, 3253, 3257, 3261, 3261,
+ 3294, 3294, 3296, 3297, 3313, 3314, 3333, 3340, 3342, 3344,
+ 3346, 3386, 3389, 3389, 3406, 3406, 3424, 3425, 3450, 3455,
+ 3461, 3478, 3482, 3505, 3507, 3515, 3517, 3517, 3520, 3526,
+ 3585, 3632, 3634, 3635, 3648, 3654, 3713, 3714, 3716, 3716,
+ 3719, 3720, 3722, 3722, 3725, 3725, 3732, 3735, 3737, 3743,
+ 3745, 3747, 3749, 3749, 3751, 3751, 3754, 3755, 3757, 3760,
+ 3762, 3763, 3773, 3773, 3776, 3780, 3782, 3782, 3804, 3805,
+ 3840, 3840, 3904, 3911, 3913, 3948, 3976, 3980, 4096, 4138,
+ 4159, 4159, 4176, 4181, 4186, 4189, 4193, 4193, 4197, 4198,
+ 4206, 4208, 4213, 4225, 4238, 4238, 4256, 4293, 4304, 4346,
+ 4348, 4348, 4352, 4680, 4682, 4685, 4688, 4694, 4696, 4696,
+ 4698, 4701, 4704, 4744, 4746, 4749, 4752, 4784, 4786, 4789,
+ 4792, 4798, 4800, 4800, 4802, 4805, 4808, 4822, 4824, 4880,
+ 4882, 4885, 4888, 4954, 4992, 5007, 5024, 5108, 5121, 5740,
+ 5743, 5759, 5761, 5786, 5792, 5866, 5870, 5872, 5888, 5900,
+ 5902, 5905, 5920, 5937, 5952, 5969, 5984, 5996, 5998, 6000,
+ 6016, 6067, 6103, 6103, 6108, 6108, 6176, 6263, 6272, 6312,
+ 6314, 6314, 6320, 6389, 6400, 6428, 6480, 6509, 6512, 6516,
+ 6528, 6571, 6593, 6599, 6656, 6678, 6688, 6740, 6823, 6823,
+ 6917, 6963, 6981, 6987, 7043, 7072, 7086, 7087, 7104, 7141,
+ 7168, 7203, 7245, 7247, 7258, 7293, 7401, 7404, 7406, 7409,
+ 7424, 7615, 7680, 7957, 7960, 7965, 7968, 8005, 8008, 8013,
+ 8016, 8023, 8025, 8025, 8027, 8027, 8029, 8029, 8031, 8061,
+ 8064, 8116, 8118, 8124, 8126, 8126, 8130, 8132, 8134, 8140,
+ 8144, 8147, 8150, 8155, 8160, 8172, 8178, 8180, 8182, 8188,
+ 8305, 8305, 8319, 8319, 8336, 8348, 8450, 8450, 8455, 8455,
+ 8458, 8467, 8469, 8469, 8473, 8477, 8484, 8484, 8486, 8486,
+ 8488, 8488, 8490, 8493, 8495, 8505, 8508, 8511, 8517, 8521,
+ 8526, 8526, 8544, 8584, 11264, 11310, 11312, 11358,
+ 11360, 11492, 11499, 11502, 11520, 11557, 11568, 11621,
+ 11631, 11631, 11648, 11670, 11680, 11686, 11688, 11694,
+ 11696, 11702, 11704, 11710, 11712, 11718, 11720, 11726,
+ 11728, 11734, 11736, 11742, 11823, 11823, 12293, 12295,
+ 12321, 12329, 12337, 12341, 12344, 12348, 12353, 12438,
+ 12445, 12447, 12449, 12538, 12540, 12543, 12549, 12589,
+ 12593, 12686, 12704, 12730, 12784, 12799, 13312, 13312,
+ 19893, 19893, 19968, 19968, 40907, 40907, 40960, 42124,
+ 42192, 42237, 42240, 42508, 42512, 42527, 42538, 42539,
+ 42560, 42606, 42623, 42647, 42656, 42735, 42775, 42783,
+ 42786, 42888, 42891, 42894, 42896, 42897, 42912, 42921,
+ 43002, 43009, 43011, 43013, 43015, 43018, 43020, 43042,
+ 43072, 43123, 43138, 43187, 43250, 43255, 43259, 43259,
+ 43274, 43301, 43312, 43334, 43360, 43388, 43396, 43442,
+ 43471, 43471, 43520, 43560, 43584, 43586, 43588, 43595,
+ 43616, 43638, 43642, 43642, 43648, 43695, 43697, 43697,
+ 43701, 43702, 43705, 43709, 43712, 43712, 43714, 43714,
+ 43739, 43741, 43777, 43782, 43785, 43790, 43793, 43798,
+ 43808, 43814, 43816, 43822, 43968, 44002, 44032, 44032,
+ 55203, 55203, 55216, 55238, 55243, 55291, 63744, 64045,
+ 64048, 64109, 64112, 64217, 64256, 64262, 64275, 64279,
+ 64285, 64285, 64287, 64296, 64298, 64310, 64312, 64316,
+ 64318, 64318, 64320, 64321, 64323, 64324, 64326, 64433,
+ 64467, 64829, 64848, 64911, 64914, 64967, 65008, 65019,
+ 65136, 65140, 65142, 65276, 65313, 65338, 65345, 65370,
+ 65382, 65470, 65474, 65479, 65482, 65487, 65490, 65495,
+ 65498, 65500, 65536, 65547, 65549, 65574, 65576, 65594,
+ 65596, 65597, 65599, 65613, 65616, 65629, 65664, 65786,
+ 65856, 65908, 66176, 66204, 66208, 66256, 66304, 66334,
+ 66352, 66378, 66432, 66461, 66464, 66499, 66504, 66511,
+ 66513, 66517, 66560, 66717, 67584, 67589, 67592, 67592,
+ 67594, 67637, 67639, 67640, 67644, 67644, 67647, 67669,
+ 67840, 67861, 67872, 67897, 68096, 68096, 68112, 68115,
+ 68117, 68119, 68121, 68147, 68192, 68220, 68352, 68405,
+ 68416, 68437, 68448, 68466, 68608, 68680, 69635, 69687,
+ 69763, 69807, 73728, 74606, 74752, 74850, 77824, 78894,
+ 92160, 92728, 110592, 110593, 119808, 119892, 119894, 119964,
+ 119966, 119967, 119970, 119970, 119973, 119974, 119977, 119980,
+ 119982, 119993, 119995, 119995, 119997, 120003, 120005, 120069,
+ 120071, 120074, 120077, 120084, 120086, 120092, 120094, 120121,
+ 120123, 120126, 120128, 120132, 120134, 120134, 120138, 120144,
+ 120146, 120485, 120488, 120512, 120514, 120538, 120540, 120570,
+ 120572, 120596, 120598, 120628, 120630, 120654, 120656, 120686,
+ 120688, 120712, 120714, 120744, 120746, 120770, 120772, 120779,
+ 131072, 131072, 173782, 173782, 173824, 173824, 177972, 177972,
+ 177984, 177984, 178205, 178205, 194560, 195101
+];
+
+var identifierStartTable = [];
+
+for (var i = 0; i < 128; i++) {
+ identifierStartTable[i] =
+ i === 36 || // $
+ i >= 65 && i <= 90 || // A-Z
+ i === 95 || // _
+ i >= 97 && i <= 122; // a-z
+}
+
+var identifierPartTable = [];
+
+for (var i = 0; i < 128; i++) {
+ identifierPartTable[i] =
+ identifierStartTable[i] || // $, _, A-Z, a-z
+ i >= 48 && i <= 57; // 0-9
+}
+
+// Object that handles postponed lexing verifications that checks the parsed
+// environment state.
+
+function asyncTrigger() {
+ var _checks = [];
+
+ return {
+ push: function (fn) {
+ _checks.push(fn);
+ },
+
+ check: function () {
+ for (var check in _checks) {
+ _checks[check]();
+ }
+
+ _checks.splice(0, _checks.length);
+ }
+ };
+}
+
+/*
+ * Lexer for JSHint.
+ *
+ * This object does a char-by-char scan of the provided source code
+ * and produces a sequence of tokens.
+ *
+ * var lex = new Lexer("var i = 0;");
+ * lex.start();
+ * lex.token(); // returns the next token
+ *
+ * You have to use the token() method to move the lexer forward
+ * but you don't have to use its return value to get tokens. In addition
+ * to token() method returning the next token, the Lexer object also
+ * emits events.
+ *
+ * lex.on("Identifier", function (data) {
+ * if (data.name.indexOf("_") >= 0) {
+ * // Produce a warning.
+ * }
+ * });
+ *
+ * Note that the token() method returns tokens in a JSLint-compatible
+ * format while the event emitter uses a slightly modified version of
+ * Mozilla's JavaScript Parser API. Eventually, we will move away from
+ * JSLint format.
+ */
+function Lexer(source) {
+ var lines = source;
+
+ if (typeof lines === "string") {
+ lines = lines
+ .replace(/\r\n/g, "\n")
+ .replace(/\r/g, "\n")
+ .split("\n");
+ }
+
+ // If the first line is a shebang (#!), make it a blank and move on.
+ // Shebangs are used by Node scripts.
+
+ if (lines[0] && lines[0].substr(0, 2) === "#!") {
+ lines[0] = "";
+ }
+
+ this.emitter = new events.EventEmitter();
+ this.source = source;
+ this.lines = lines;
+ this.prereg = true;
+
+ this.line = 0;
+ this.char = 1;
+ this.from = 1;
+ this.input = "";
+
+ for (var i = 0; i < state.option.indent; i += 1) {
+ state.tab += " ";
+ }
+}
+
+Lexer.prototype = {
+ _lines: [],
+
+ get lines() {
+ this._lines = state.lines;
+ return this._lines;
+ },
+
+ set lines(val) {
+ this._lines = val;
+ state.lines = this._lines;
+ },
+
+ /*
+ * Return the next i character without actually moving the
+ * char pointer.
+ */
+ peek: function (i) {
+ return this.input.charAt(i || 0);
+ },
+
+ /*
+ * Move the char pointer forward i times.
+ */
+ skip: function (i) {
+ i = i || 1;
+ this.char += i;
+ this.input = this.input.slice(i);
+ },
+
+ /*
+ * Subscribe to a token event. The API for this method is similar
+ * Underscore.js i.e. you can subscribe to multiple events with
+ * one call:
+ *
+ * lex.on("Identifier Number", function (data) {
+ * // ...
+ * });
+ */
+ on: function (names, listener) {
+ names.split(" ").forEach(function (name) {
+ this.emitter.on(name, listener);
+ }.bind(this));
+ },
+
+ /*
+ * Trigger a token event. All arguments will be passed to each
+ * listener.
+ */
+ trigger: function () {
+ this.emitter.emit.apply(this.emitter, Array.prototype.slice.call(arguments));
+ },
+
+ /*
+ * Postpone a token event. the checking condition is set as
+ * last parameter, and the trigger function is called in a
+ * stored callback. To be later called using the check() function
+ * by the parser. This avoids parser's peek() to give the lexer
+ * a false context.
+ */
+ triggerAsync: function (type, args, checks, fn) {
+ checks.push(function () {
+ if (fn()) {
+ this.trigger(type, args);
+ }
+ }.bind(this));
+ },
+
+ /*
+ * Extract a punctuator out of the next sequence of characters
+ * or return 'null' if its not possible.
+ *
+ * This method's implementation was heavily influenced by the
+ * scanPunctuator function in the Esprima parser's source code.
+ */
+ scanPunctuator: function () {
+ var ch1 = this.peek();
+ var ch2, ch3, ch4;
+
+ switch (ch1) {
+ // Most common single-character punctuators
+ case ".":
+ if ((/^[0-9]$/).test(this.peek(1))) {
+ return null;
+ }
+ if (this.peek(1) === "." && this.peek(2) === ".") {
+ return {
+ type: Token.Punctuator,
+ value: "..."
+ };
+ }
+ /* falls through */
+ case "(":
+ case ")":
+ case ";":
+ case ",":
+ case "{":
+ case "}":
+ case "[":
+ case "]":
+ case ":":
+ case "~":
+ case "?":
+ return {
+ type: Token.Punctuator,
+ value: ch1
+ };
+
+ // A pound sign (for Node shebangs)
+ case "#":
+ return {
+ type: Token.Punctuator,
+ value: ch1
+ };
+
+ // We're at the end of input
+ case "":
+ return null;
+ }
+
+ // Peek more characters
+
+ ch2 = this.peek(1);
+ ch3 = this.peek(2);
+ ch4 = this.peek(3);
+
+ // 4-character punctuator: >>>=
+
+ if (ch1 === ">" && ch2 === ">" && ch3 === ">" && ch4 === "=") {
+ return {
+ type: Token.Punctuator,
+ value: ">>>="
+ };
+ }
+
+ // 3-character punctuators: === !== >>> <<= >>=
+
+ if (ch1 === "=" && ch2 === "=" && ch3 === "=") {
+ return {
+ type: Token.Punctuator,
+ value: "==="
+ };
+ }
+
+ if (ch1 === "!" && ch2 === "=" && ch3 === "=") {
+ return {
+ type: Token.Punctuator,
+ value: "!=="
+ };
+ }
+
+ if (ch1 === ">" && ch2 === ">" && ch3 === ">") {
+ return {
+ type: Token.Punctuator,
+ value: ">>>"
+ };
+ }
+
+ if (ch1 === "<" && ch2 === "<" && ch3 === "=") {
+ return {
+ type: Token.Punctuator,
+ value: "<<="
+ };
+ }
+
+ if (ch1 === ">" && ch2 === ">" && ch3 === "=") {
+ return {
+ type: Token.Punctuator,
+ value: ">>="
+ };
+ }
+
+ // Fat arrow punctuator
+ if (ch1 === "=" && ch2 === ">") {
+ return {
+ type: Token.Punctuator,
+ value: ch1 + ch2
+ };
+ }
+
+ // 2-character punctuators: <= >= == != ++ -- << >> && ||
+ // += -= *= %= &= |= ^= (but not /=, see below)
+ if (ch1 === ch2 && ("+-<>&|".indexOf(ch1) >= 0)) {
+ return {
+ type: Token.Punctuator,
+ value: ch1 + ch2
+ };
+ }
+
+ if ("<>=!+-*%&|^".indexOf(ch1) >= 0) {
+ if (ch2 === "=") {
+ return {
+ type: Token.Punctuator,
+ value: ch1 + ch2
+ };
+ }
+
+ return {
+ type: Token.Punctuator,
+ value: ch1
+ };
+ }
+
+ // Special case: /=. We need to make sure that this is an
+ // operator and not a regular expression.
+
+ if (ch1 === "/") {
+ if (ch2 === "=" && /\/=(?!(\S*\/[gim]?))/.test(this.input)) {
+ // /= is not a part of a regular expression, return it as a
+ // punctuator.
+ return {
+ type: Token.Punctuator,
+ value: "/="
+ };
+ }
+
+ return {
+ type: Token.Punctuator,
+ value: "/"
+ };
+ }
+
+ return null;
+ },
+
+ /*
+ * Extract a comment out of the next sequence of characters and/or
+ * lines or return 'null' if its not possible. Since comments can
+ * span across multiple lines this method has to move the char
+ * pointer.
+ *
+ * In addition to normal JavaScript comments (// and /*) this method
+ * also recognizes JSHint- and JSLint-specific comments such as
+ * /*jshint, /*jslint, /*globals and so on.
+ */
+ scanComments: function () {
+ var ch1 = this.peek();
+ var ch2 = this.peek(1);
+ var rest = this.input.substr(2);
+ var startLine = this.line;
+ var startChar = this.char;
+
+ // Create a comment token object and make sure it
+ // has all the data JSHint needs to work with special
+ // comments.
+
+ function commentToken(label, body, opt) {
+ var special = ["jshint", "jslint", "members", "member", "globals", "global", "exported"];
+ var isSpecial = false;
+ var value = label + body;
+ var commentType = "plain";
+ opt = opt || {};
+
+ if (opt.isMultiline) {
+ value += "*/";
+ }
+
+ special.forEach(function (str) {
+ if (isSpecial) {
+ return;
+ }
+
+ // Don't recognize any special comments other than jshint for single-line
+ // comments. This introduced many problems with legit comments.
+ if (label === "//" && str !== "jshint") {
+ return;
+ }
+
+ if (body.substr(0, str.length) === str) {
+ isSpecial = true;
+ label = label + str;
+ body = body.substr(str.length);
+ }
+
+ if (!isSpecial && body.charAt(0) === " " && body.substr(1, str.length) === str) {
+ isSpecial = true;
+ label = label + " " + str;
+ body = body.substr(str.length + 1);
+ }
+
+ if (!isSpecial) {
+ return;
+ }
+
+ switch (str) {
+ case "member":
+ commentType = "members";
+ break;
+ case "global":
+ commentType = "globals";
+ break;
+ default:
+ commentType = str;
+ }
+ });
+
+ return {
+ type: Token.Comment,
+ commentType: commentType,
+ value: value,
+ body: body,
+ isSpecial: isSpecial,
+ isMultiline: opt.isMultiline || false,
+ isMalformed: opt.isMalformed || false
+ };
+ }
+
+ // End of unbegun comment. Raise an error and skip that input.
+ if (ch1 === "*" && ch2 === "/") {
+ this.trigger("error", {
+ code: "E018",
+ line: startLine,
+ character: startChar
+ });
+
+ this.skip(2);
+ return null;
+ }
+
+ // Comments must start either with // or /*
+ if (ch1 !== "/" || (ch2 !== "*" && ch2 !== "/")) {
+ return null;
+ }
+
+ // One-line comment
+ if (ch2 === "/") {
+ this.skip(this.input.length); // Skip to the EOL.
+ return commentToken("//", rest);
+ }
+
+ var body = "";
+
+ /* Multi-line comment */
+ if (ch2 === "*") {
+ this.skip(2);
+
+ while (this.peek() !== "*" || this.peek(1) !== "/") {
+ if (this.peek() === "") { // End of Line
+ body += "\n";
+
+ // If we hit EOF and our comment is still unclosed,
+ // trigger an error and end the comment implicitly.
+ if (!this.nextLine()) {
+ this.trigger("error", {
+ code: "E017",
+ line: startLine,
+ character: startChar
+ });
+
+ return commentToken("/*", body, {
+ isMultiline: true,
+ isMalformed: true
+ });
+ }
+ } else {
+ body += this.peek();
+ this.skip();
+ }
+ }
+
+ this.skip(2);
+ return commentToken("/*", body, { isMultiline: true });
+ }
+ },
+
+ /*
+ * Extract a keyword out of the next sequence of characters or
+ * return 'null' if its not possible.
+ */
+ scanKeyword: function () {
+ var result = /^[a-zA-Z_$][a-zA-Z0-9_$]*/.exec(this.input);
+ var keywords = [
+ "if", "in", "do", "var", "for", "new",
+ "try", "let", "this", "else", "case",
+ "void", "with", "enum", "while", "break",
+ "catch", "throw", "const", "yield", "class",
+ "super", "return", "typeof", "delete",
+ "switch", "export", "import", "default",
+ "finally", "extends", "function", "continue",
+ "debugger", "instanceof"
+ ];
+
+ if (result && keywords.indexOf(result[0]) >= 0) {
+ return {
+ type: Token.Keyword,
+ value: result[0]
+ };
+ }
+
+ return null;
+ },
+
+ /*
+ * Extract a JavaScript identifier out of the next sequence of
+ * characters or return 'null' if its not possible. In addition,
+ * to Identifier this method can also produce BooleanLiteral
+ * (true/false) and NullLiteral (null).
+ */
+ scanIdentifier: function () {
+ var id = "";
+ var index = 0;
+ var type, char;
+
+ // Detects any character in the Unicode categories "Uppercase
+ // letter (Lu)", "Lowercase letter (Ll)", "Titlecase letter
+ // (Lt)", "Modifier letter (Lm)", "Other letter (Lo)", or
+ // "Letter number (Nl)".
+ //
+ // Both approach and unicodeLetterTable were borrowed from
+ // Google's Traceur.
+
+ function isUnicodeLetter(code) {
+ for (var i = 0; i < unicodeLetterTable.length;) {
+ if (code < unicodeLetterTable[i++]) {
+ return false;
+ }
+
+ if (code <= unicodeLetterTable[i++]) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ function isHexDigit(str) {
+ return (/^[0-9a-fA-F]$/).test(str);
+ }
+
+ var readUnicodeEscapeSequence = function () {
+ /*jshint validthis:true */
+ index += 1;
+
+ if (this.peek(index) !== "u") {
+ return null;
+ }
+
+ var ch1 = this.peek(index + 1);
+ var ch2 = this.peek(index + 2);
+ var ch3 = this.peek(index + 3);
+ var ch4 = this.peek(index + 4);
+ var code;
+
+ if (isHexDigit(ch1) && isHexDigit(ch2) && isHexDigit(ch3) && isHexDigit(ch4)) {
+ code = parseInt(ch1 + ch2 + ch3 + ch4, 16);
+
+ if (isUnicodeLetter(code)) {
+ index += 5;
+ return "\\u" + ch1 + ch2 + ch3 + ch4;
+ }
+
+ return null;
+ }
+
+ return null;
+ }.bind(this);
+
+ var getIdentifierStart = function () {
+ /*jshint validthis:true */
+ var chr = this.peek(index);
+ var code = chr.charCodeAt(0);
+
+ if (code === 92) {
+ return readUnicodeEscapeSequence();
+ }
+
+ if (code < 128) {
+ if (identifierStartTable[code]) {
+ index += 1;
+ return chr;
+ }
+
+ return null;
+ }
+
+ if (isUnicodeLetter(code)) {
+ index += 1;
+ return chr;
+ }
+
+ return null;
+ }.bind(this);
+
+ var getIdentifierPart = function () {
+ /*jshint validthis:true */
+ var chr = this.peek(index);
+ var code = chr.charCodeAt(0);
+
+ if (code === 92) {
+ return readUnicodeEscapeSequence();
+ }
+
+ if (code < 128) {
+ if (identifierPartTable[code]) {
+ index += 1;
+ return chr;
+ }
+
+ return null;
+ }
+
+ if (isUnicodeLetter(code)) {
+ index += 1;
+ return chr;
+ }
+
+ return null;
+ }.bind(this);
+
+ char = getIdentifierStart();
+ if (char === null) {
+ return null;
+ }
+
+ id = char;
+ for (;;) {
+ char = getIdentifierPart();
+
+ if (char === null) {
+ break;
+ }
+
+ id += char;
+ }
+
+ switch (id) {
+ case "true":
+ case "false":
+ type = Token.BooleanLiteral;
+ break;
+ case "null":
+ type = Token.NullLiteral;
+ break;
+ default:
+ type = Token.Identifier;
+ }
+
+ return {
+ type: type,
+ value: id
+ };
+ },
+
+ /*
+ * Extract a numeric literal out of the next sequence of
+ * characters or return 'null' if its not possible. This method
+ * supports all numeric literals described in section 7.8.3
+ * of the EcmaScript 5 specification.
+ *
+ * This method's implementation was heavily influenced by the
+ * scanNumericLiteral function in the Esprima parser's source code.
+ */
+ scanNumericLiteral: function () {
+ var index = 0;
+ var value = "";
+ var length = this.input.length;
+ var char = this.peek(index);
+ var bad;
+
+ function isDecimalDigit(str) {
+ return (/^[0-9]$/).test(str);
+ }
+
+ function isOctalDigit(str) {
+ return (/^[0-7]$/).test(str);
+ }
+
+ function isHexDigit(str) {
+ return (/^[0-9a-fA-F]$/).test(str);
+ }
+
+ function isIdentifierStart(ch) {
+ return (ch === "$") || (ch === "_") || (ch === "\\") ||
+ (ch >= "a" && ch <= "z") || (ch >= "A" && ch <= "Z");
+ }
+
+ // Numbers must start either with a decimal digit or a point.
+
+ if (char !== "." && !isDecimalDigit(char)) {
+ return null;
+ }
+
+ if (char !== ".") {
+ value = this.peek(index);
+ index += 1;
+ char = this.peek(index);
+
+ if (value === "0") {
+ // Base-16 numbers.
+ if (char === "x" || char === "X") {
+ index += 1;
+ value += char;
+
+ while (index < length) {
+ char = this.peek(index);
+ if (!isHexDigit(char)) {
+ break;
+ }
+ value += char;
+ index += 1;
+ }
+
+ if (value.length <= 2) { // 0x
+ return {
+ type: Token.NumericLiteral,
+ value: value,
+ isMalformed: true
+ };
+ }
+
+ if (index < length) {
+ char = this.peek(index);
+ if (isIdentifierStart(char)) {
+ return null;
+ }
+ }
+
+ return {
+ type: Token.NumericLiteral,
+ value: value,
+ base: 16,
+ isMalformed: false
+ };
+ }
+
+ // Base-8 numbers.
+ if (isOctalDigit(char)) {
+ index += 1;
+ value += char;
+ bad = false;
+
+ while (index < length) {
+ char = this.peek(index);
+
+ // Numbers like '019' (note the 9) are not valid octals
+ // but we still parse them and mark as malformed.
+
+ if (isDecimalDigit(char)) {
+ bad = true;
+ } else if (!isOctalDigit(char)) {
+ break;
+ }
+ value += char;
+ index += 1;
+ }
+
+ if (index < length) {
+ char = this.peek(index);
+ if (isIdentifierStart(char)) {
+ return null;
+ }
+ }
+
+ return {
+ type: Token.NumericLiteral,
+ value: value,
+ base: 8,
+ isMalformed: false
+ };
+ }
+
+ // Decimal numbers that start with '0' such as '09' are illegal
+ // but we still parse them and return as malformed.
+
+ if (isDecimalDigit(char)) {
+ index += 1;
+ value += char;
+ }
+ }
+
+ while (index < length) {
+ char = this.peek(index);
+ if (!isDecimalDigit(char)) {
+ break;
+ }
+ value += char;
+ index += 1;
+ }
+ }
+
+ // Decimal digits.
+
+ if (char === ".") {
+ value += char;
+ index += 1;
+
+ while (index < length) {
+ char = this.peek(index);
+ if (!isDecimalDigit(char)) {
+ break;
+ }
+ value += char;
+ index += 1;
+ }
+ }
+
+ // Exponent part.
+
+ if (char === "e" || char === "E") {
+ value += char;
+ index += 1;
+ char = this.peek(index);
+
+ if (char === "+" || char === "-") {
+ value += this.peek(index);
+ index += 1;
+ }
+
+ char = this.peek(index);
+ if (isDecimalDigit(char)) {
+ value += char;
+ index += 1;
+
+ while (index < length) {
+ char = this.peek(index);
+ if (!isDecimalDigit(char)) {
+ break;
+ }
+ value += char;
+ index += 1;
+ }
+ } else {
+ return null;
+ }
+ }
+
+ if (index < length) {
+ char = this.peek(index);
+ if (isIdentifierStart(char)) {
+ return null;
+ }
+ }
+
+ return {
+ type: Token.NumericLiteral,
+ value: value,
+ base: 10,
+ isMalformed: !isFinite(value)
+ };
+ },
+
+ /*
+ * Extract a string out of the next sequence of characters and/or
+ * lines or return 'null' if its not possible. Since strings can
+ * span across multiple lines this method has to move the char
+ * pointer.
+ *
+ * This method recognizes pseudo-multiline JavaScript strings:
+ *
+ * var str = "hello\
+ * world";
+ */
+ scanStringLiteral: function (checks) {
+ /*jshint loopfunc:true */
+ var quote = this.peek();
+
+ // String must start with a quote.
+ if (quote !== "\"" && quote !== "'") {
+ return null;
+ }
+
+ // In JSON strings must always use double quotes.
+ this.triggerAsync("warning", {
+ code: "W108",
+ line: this.line,
+ character: this.char // +1?
+ }, checks, function () { return state.jsonMode && quote !== "\""; });
+
+ var value = "";
+ var startLine = this.line;
+ var startChar = this.char;
+ var allowNewLine = false;
+
+ this.skip();
+
+ while (this.peek() !== quote) {
+ while (this.peek() === "") { // End Of Line
+
+ // If an EOL is not preceded by a backslash, show a warning
+ // and proceed like it was a legit multi-line string where
+ // author simply forgot to escape the newline symbol.
+ //
+ // Another approach is to implicitly close a string on EOL
+ // but it generates too many false positives.
+
+ if (!allowNewLine) {
+ this.trigger("warning", {
+ code: "W112",
+ line: this.line,
+ character: this.char
+ });
+ } else {
+ allowNewLine = false;
+
+ // Otherwise show a warning if multistr option was not set.
+ // For JSON, show warning no matter what.
+
+ this.triggerAsync("warning", {
+ code: "W043",
+ line: this.line,
+ character: this.char
+ }, checks, function () { return !state.option.multistr; });
+
+ this.triggerAsync("warning", {
+ code: "W042",
+ line: this.line,
+ character: this.char
+ }, checks, function () { return state.jsonMode && state.option.multistr; });
+ }
+
+ // If we get an EOF inside of an unclosed string, show an
+ // error and implicitly close it at the EOF point.
+
+ if (!this.nextLine()) {
+ this.trigger("error", {
+ code: "E029",
+ line: startLine,
+ character: startChar
+ });
+
+ return {
+ type: Token.StringLiteral,
+ value: value,
+ isUnclosed: true,
+ quote: quote
+ };
+ }
+ }
+
+ allowNewLine = false;
+ var char = this.peek();
+ var jump = 1; // A length of a jump, after we're done
+ // parsing this character.
+
+ if (char < " ") {
+ // Warn about a control character in a string.
+ this.trigger("warning", {
+ code: "W113",
+ line: this.line,
+ character: this.char,
+ data: [ "<non-printable>" ]
+ });
+ }
+
+ // Special treatment for some escaped characters.
+
+ if (char === "\\") {
+ this.skip();
+ char = this.peek();
+
+ switch (char) {
+ case "'":
+ this.triggerAsync("warning", {
+ code: "W114",
+ line: this.line,
+ character: this.char,
+ data: [ "\\'" ]
+ }, checks, function () {return state.jsonMode; });
+ break;
+ case "b":
+ char = "\b";
+ break;
+ case "f":
+ char = "\f";
+ break;
+ case "n":
+ char = "\n";
+ break;
+ case "r":
+ char = "\r";
+ break;
+ case "t":
+ char = "\t";
+ break;
+ case "0":
+ char = "\0";
+
+ // Octal literals fail in strict mode.
+ // Check if the number is between 00 and 07.
+ var n = parseInt(this.peek(1), 10);
+ this.triggerAsync("warning", {
+ code: "W115",
+ line: this.line,
+ character: this.char
+ }, checks,
+ function () { return n >= 0 && n <= 7 && state.directive["use strict"]; });
+ break;
+ case "u":
+ char = String.fromCharCode(parseInt(this.input.substr(1, 4), 16));
+ jump = 5;
+ break;
+ case "v":
+ this.triggerAsync("warning", {
+ code: "W114",
+ line: this.line,
+ character: this.char,
+ data: [ "\\v" ]
+ }, checks, function () { return state.jsonMode; });
+
+ char = "\v";
+ break;
+ case "x":
+ var x = parseInt(this.input.substr(1, 2), 16);
+
+ this.triggerAsync("warning", {
+ code: "W114",
+ line: this.line,
+ character: this.char,
+ data: [ "\\x-" ]
+ }, checks, function () { return state.jsonMode; });
+
+ char = String.fromCharCode(x);
+ jump = 3;
+ break;
+ case "\\":
+ case "\"":
+ case "/":
+ break;
+ case "":
+ allowNewLine = true;
+ char = "";
+ break;
+ case "!":
+ if (value.slice(value.length - 2) === "<") {
+ break;
+ }
+
+ /*falls through */
+ default:
+ // Weird escaping.
+ this.trigger("warning", {
+ code: "W044",
+ line: this.line,
+ character: this.char
+ });
+ }
+ }
+
+ value += char;
+ this.skip(jump);
+ }
+
+ this.skip();
+ return {
+ type: Token.StringLiteral,
+ value: value,
+ isUnclosed: false,
+ quote: quote
+ };
+ },
+
+ /*
+ * Extract a regular expression out of the next sequence of
+ * characters and/or lines or return 'null' if its not possible.
+ *
+ * This method is platform dependent: it accepts almost any
+ * regular expression values but then tries to compile and run
+ * them using system's RegExp object. This means that there are
+ * rare edge cases where one JavaScript engine complains about
+ * your regular expression while others don't.
+ */
+ scanRegExp: function () {
+ var index = 0;
+ var length = this.input.length;
+ var char = this.peek();
+ var value = char;
+ var body = "";
+ var flags = [];
+ var malformed = false;
+ var isCharSet = false;
+ var terminated;
+
+ var scanUnexpectedChars = function () {
+ // Unexpected control character
+ if (char < " ") {
+ malformed = true;
+ this.trigger("warning", {
+ code: "W048",
+ line: this.line,
+ character: this.char
+ });
+ }
+
+ // Unexpected escaped character
+ if (char === "<") {
+ malformed = true;
+ this.trigger("warning", {
+ code: "W049",
+ line: this.line,
+ character: this.char,
+ data: [ char ]
+ });
+ }
+ }.bind(this);
+
+ // Regular expressions must start with '/'
+ if (!this.prereg || char !== "/") {
+ return null;
+ }
+
+ index += 1;
+ terminated = false;
+
+ // Try to get everything in between slashes. A couple of
+ // cases aside (see scanUnexpectedChars) we don't really
+ // care whether the resulting expression is valid or not.
+ // We will check that later using the RegExp object.
+
+ while (index < length) {
+ char = this.peek(index);
+ value += char;
+ body += char;
+
+ if (isCharSet) {
+ if (char === "]") {
+ if (this.peek(index - 1) !== "\\" || this.peek(index - 2) === "\\") {
+ isCharSet = false;
+ }
+ }
+
+ if (char === "\\") {
+ index += 1;
+ char = this.peek(index);
+ body += char;
+ value += char;
+
+ scanUnexpectedChars();
+ }
+
+ index += 1;
+ continue;
+ }
+
+ if (char === "\\") {
+ index += 1;
+ char = this.peek(index);
+ body += char;
+ value += char;
+
+ scanUnexpectedChars();
+
+ if (char === "/") {
+ index += 1;
+ continue;
+ }
+
+ if (char === "[") {
+ index += 1;
+ continue;
+ }
+ }
+
+ if (char === "[") {
+ isCharSet = true;
+ index += 1;
+ continue;
+ }
+
+ if (char === "/") {
+ body = body.substr(0, body.length - 1);
+ terminated = true;
+ index += 1;
+ break;
+ }
+
+ index += 1;
+ }
+
+ // A regular expression that was never closed is an
+ // error from which we cannot recover.
+
+ if (!terminated) {
+ this.trigger("error", {
+ code: "E015",
+ line: this.line,
+ character: this.from
+ });
+
+ return void this.trigger("fatal", {
+ line: this.line,
+ from: this.from
+ });
+ }
+
+ // Parse flags (if any).
+
+ while (index < length) {
+ char = this.peek(index);
+ if (!/[gim]/.test(char)) {
+ break;
+ }
+ flags.push(char);
+ value += char;
+ index += 1;
+ }
+
+ // Check regular expression for correctness.
+
+ try {
+ new RegExp(body, flags.join(""));
+ } catch (err) {
+ malformed = true;
+ this.trigger("error", {
+ code: "E016",
+ line: this.line,
+ character: this.char,
+ data: [ err.message ] // Platform dependent!
+ });
+ }
+
+ return {
+ type: Token.RegExp,
+ value: value,
+ flags: flags,
+ isMalformed: malformed
+ };
+ },
+
+ /*
+ * Scan for any occurence of mixed tabs and spaces. If smarttabs option
+ * is on, ignore tabs followed by spaces.
+ *
+ * Tabs followed by one space followed by a block comment are allowed.
+ */
+ scanMixedSpacesAndTabs: function () {
+ var at, match;
+
+ if (state.option.smarttabs) {
+ // Negative look-behind for "//"
+ match = this.input.match(/(\/\/|^\s?\*)? \t/);
+ at = match && !match[1] ? 0 : -1;
+ } else {
+ at = this.input.search(/ \t|\t [^\*]/);
+ }
+
+ return at;
+ },
+
+ /*
+ * Scan for characters that get silently deleted by one or more browsers.
+ */
+ scanUnsafeChars: function () {
+ return this.input.search(reg.unsafeChars);
+ },
+
+ /*
+ * Produce the next raw token or return 'null' if no tokens can be matched.
+ * This method skips over all space characters.
+ */
+ next: function (checks) {
+ this.from = this.char;
+
+ // Move to the next non-space character.
+ var start;
+ if (/\s/.test(this.peek())) {
+ start = this.char;
+
+ while (/\s/.test(this.peek())) {
+ this.from += 1;
+ this.skip();
+ }
+
+ if (this.peek() === "") { // EOL
+ if (!/^\s*$/.test(this.lines[this.line - 1]) && state.option.trailing) {
+ this.trigger("warning", { code: "W102", line: this.line, character: start });
+ }
+ }
+ }
+
+ // Methods that work with multi-line structures and move the
+ // character pointer.
+
+ var match = this.scanComments() ||
+ this.scanStringLiteral(checks);
+
+ if (match) {
+ return match;
+ }
+
+ // Methods that don't move the character pointer.
+
+ match =
+ this.scanRegExp() ||
+ this.scanPunctuator() ||
+ this.scanKeyword() ||
+ this.scanIdentifier() ||
+ this.scanNumericLiteral();
+
+ if (match) {
+ this.skip(match.value.length);
+ return match;
+ }
+
+ // No token could be matched, give up.
+
+ return null;
+ },
+
+ /*
+ * Switch to the next line and reset all char pointers. Once
+ * switched, this method also checks for mixed spaces and tabs
+ * and other minor warnings.
+ */
+ nextLine: function () {
+ var char;
+
+ if (this.line >= this.lines.length) {
+ return false;
+ }
+
+ this.input = this.lines[this.line];
+ this.line += 1;
+ this.char = 1;
+ this.from = 1;
+
+ char = this.scanMixedSpacesAndTabs();
+ if (char >= 0) {
+ this.trigger("warning", { code: "W099", line: this.line, character: char + 1 });
+ }
+
+ this.input = this.input.replace(/\t/g, state.tab);
+ char = this.scanUnsafeChars();
+
+ if (char >= 0) {
+ this.trigger("warning", { code: "W100", line: this.line, character: char });
+ }
+
+ // If there is a limit on line length, warn when lines get too
+ // long.
+
+ if (state.option.maxlen && state.option.maxlen < this.input.length) {
+ this.trigger("warning", { code: "W101", line: this.line, character: this.input.length });
+ }
+
+ return true;
+ },
+
+ /*
+ * This is simply a synonym for nextLine() method with a friendlier
+ * public name.
+ */
+ start: function () {
+ this.nextLine();
+ },
+
+ /*
+ * Produce the next token. This function is called by advance() to get
+ * the next token. It retuns a token in a JSLint-compatible format.
+ */
+ token: function () {
+ /*jshint loopfunc:true */
+ var checks = asyncTrigger();
+ var token;
+
+
+ function isReserved(token, isProperty) {
+ if (!token.reserved) {
+ return false;
+ }
+
+ if (token.meta && token.meta.isFutureReservedWord) {
+ // ES3 FutureReservedWord in an ES5 environment.
+ if (state.option.inES5(true) && !token.meta.es5) {
+ return false;
+ }
+
+ // Some ES5 FutureReservedWord identifiers are active only
+ // within a strict mode environment.
+ if (token.meta.strictOnly) {
+ if (!state.option.strict && !state.directive["use strict"]) {
+ return false;
+ }
+ }
+
+ if (isProperty) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ // Produce a token object.
+ var create = function (type, value, isProperty) {
+ /*jshint validthis:true */
+ var obj;
+
+ if (type !== "(endline)" && type !== "(end)") {
+ this.prereg = false;
+ }
+
+ if (type === "(punctuator)") {
+ switch (value) {
+ case ".":
+ case ")":
+ case "~":
+ case "#":
+ case "]":
+ this.prereg = false;
+ break;
+ default:
+ this.prereg = true;
+ }
+
+ obj = Object.create(state.syntax[value] || state.syntax["(error)"]);
+ }
+
+ if (type === "(identifier)") {
+ if (value === "return" || value === "case" || value === "typeof") {
+ this.prereg = true;
+ }
+
+ if (_.has(state.syntax, value)) {
+ obj = Object.create(state.syntax[value] || state.syntax["(error)"]);
+
+ // If this can't be a reserved keyword, reset the object.
+ if (!isReserved(obj, isProperty && type === "(identifier)")) {
+ obj = null;
+ }
+ }
+ }
+
+ if (!obj) {
+ obj = Object.create(state.syntax[type]);
+ }
+
+ obj.identifier = (type === "(identifier)");
+ obj.type = obj.type || type;
+ obj.value = value;
+ obj.line = this.line;
+ obj.character = this.char;
+ obj.from = this.from;
+
+ if (isProperty && obj.identifier) {
+ obj.isProperty = isProperty;
+ }
+
+ obj.check = checks.check;
+
+ return obj;
+ }.bind(this);
+
+ for (;;) {
+ if (!this.input.length) {
+ return create(this.nextLine() ? "(endline)" : "(end)", "");
+ }
+
+ token = this.next(checks);
+
+ if (!token) {
+ if (this.input.length) {
+ // Unexpected character.
+ this.trigger("error", {
+ code: "E024",
+ line: this.line,
+ character: this.char,
+ data: [ this.peek() ]
+ });
+
+ this.input = "";
+ }
+
+ continue;
+ }
+
+ switch (token.type) {
+ case Token.StringLiteral:
+ this.triggerAsync("String", {
+ line: this.line,
+ char: this.char,
+ from: this.from,
+ value: token.value,
+ quote: token.quote
+ }, checks, function () { return true; });
+
+ return create("(string)", token.value);
+ case Token.Identifier:
+ this.trigger("Identifier", {
+ line: this.line,
+ char: this.char,
+ from: this.form,
+ name: token.value,
+ isProperty: state.tokens.curr.id === "."
+ });
+
+ /* falls through */
+ case Token.Keyword:
+ case Token.NullLiteral:
+ case Token.BooleanLiteral:
+ return create("(identifier)", token.value, state.tokens.curr.id === ".");
+
+ case Token.NumericLiteral:
+ if (token.isMalformed) {
+ this.trigger("warning", {
+ code: "W045",
+ line: this.line,
+ character: this.char,
+ data: [ token.value ]
+ });
+ }
+
+ this.triggerAsync("warning", {
+ code: "W114",
+ line: this.line,
+ character: this.char,
+ data: [ "0x-" ]
+ }, checks, function () { return token.base === 16 && state.jsonMode; });
+
+ this.triggerAsync("warning", {
+ code: "W115",
+ line: this.line,
+ character: this.char
+ }, checks, function () {
+ return state.directive["use strict"] && token.base === 8;
+ });
+
+ this.trigger("Number", {
+ line: this.line,
+ char: this.char,
+ from: this.from,
+ value: token.value,
+ base: token.base,
+ isMalformed: token.malformed
+ });
+
+ return create("(number)", token.value);
+
+ case Token.RegExp:
+ return create("(regexp)", token.value);
+
+ case Token.Comment:
+ state.tokens.curr.comment = true;
+
+ if (token.isSpecial) {
+ return {
+ value: token.value,
+ body: token.body,
+ type: token.commentType,
+ isSpecial: token.isSpecial,
+ line: this.line,
+ character: this.char,
+ from: this.from
+ };
+ }
+
+ break;
+
+ case "":
+ break;
+
+ default:
+ return create("(punctuator)", token.value);
+ }
+ }
+ }
+};
+
+exports.Lexer = Lexer;
+
+})()
+},{"events":2,"./reg.js":6,"./state.js":4,"underscore":11}],"jshint":[function(require,module,exports){
+module.exports=require('E/GbHF');
+},{}],"E/GbHF":[function(require,module,exports){
+(function(){/*!
+ * JSHint, by JSHint Community.
+ *
+ * This file (and this file only) is licensed under the same slightly modified
+ * MIT license that JSLint is. It stops evil-doers everywhere:
+ *
+ * Copyright (c) 2002 Douglas Crockford (www.JSLint.com)
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom
+ * the Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * The Software shall be used for Good, not Evil.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ *
+ */
+
+/*jshint quotmark:double */
+/*global console:true */
+/*exported console */
+
+var _ = require("underscore");
+var events = require("events");
+var vars = require("../shared/vars.js");
+var messages = require("../shared/messages.js");
+var Lexer = require("./lex.js").Lexer;
+var reg = require("./reg.js");
+var state = require("./state.js").state;
+var style = require("./style.js");
+
+// We need this module here because environments such as IE and Rhino
+// don't necessarilly expose the 'console' API and browserify uses
+// it to log things. It's a sad state of affair, really.
+var console = require("console-browserify");
+
+// We build the application inside a function so that we produce only a singleton
+// variable. That function will be invoked immediately, and its return value is
+// the JSHINT function itself.
+
+var JSHINT = (function () {
+ "use strict";
+
+ var anonname, // The guessed name for anonymous functions.
+ api, // Extension API
+
+ // These are operators that should not be used with the ! operator.
+ bang = {
+ "<" : true,
+ "<=" : true,
+ "==" : true,
+ "===": true,
+ "!==": true,
+ "!=" : true,
+ ">" : true,
+ ">=" : true,
+ "+" : true,
+ "-" : true,
+ "*" : true,
+ "/" : true,
+ "%" : true
+ },
+
+ // These are the JSHint boolean options.
+ boolOptions = {
+ asi : true, // if automatic semicolon insertion should be tolerated
+ bitwise : true, // if bitwise operators should not be allowed
+ boss : true, // if advanced usage of assignments should be allowed
+ browser : true, // if the standard browser globals should be predefined
+ camelcase : true, // if identifiers should be required in camel case
+ couch : true, // if CouchDB globals should be predefined
+ curly : true, // if curly braces around all blocks should be required
+ debug : true, // if debugger statements should be allowed
+ devel : true, // if logging globals should be predefined (console, alert, etc.)
+ dojo : true, // if Dojo Toolkit globals should be predefined
+ eqeqeq : true, // if === should be required
+ eqnull : true, // if == null comparisons should be tolerated
+ es3 : true, // if ES3 syntax should be allowed
+ es5 : true, // if ES5 syntax should be allowed (is now set per default)
+ esnext : true, // if es.next specific syntax should be allowed
+ moz : true, // if mozilla specific syntax should be allowed
+ evil : true, // if eval should be allowed
+ expr : true, // if ExpressionStatement should be allowed as Programs
+ forin : true, // if for in statements must filter
+ funcscope : true, // if only function scope should be used for scope tests
+ gcl : true, // if JSHint should be compatible with Google Closure Linter
+ globalstrict: true, // if global "use strict"; should be allowed (also enables 'strict')
+ immed : true, // if immediate invocations must be wrapped in parens
+ iterator : true, // if the `__iterator__` property should be allowed
+ jquery : true, // if jQuery globals should be predefined
+ lastsemic : true, // if semicolons may be ommitted for the trailing
+ // statements inside of a one-line blocks.
+ laxbreak : true, // if line breaks should not be checked
+ laxcomma : true, // if line breaks should not be checked around commas
+ loopfunc : true, // if functions should be allowed to be defined within
+ // loops
+ mootools : true, // if MooTools globals should be predefined
+ multistr : true, // allow multiline strings
+ newcap : true, // if constructor names must be capitalized
+ noarg : true, // if arguments.caller and arguments.callee should be
+ // disallowed
+ node : true, // if the Node.js environment globals should be
+ // predefined
+ noempty : true, // if empty blocks should be disallowed
+ nonew : true, // if using `new` for side-effects should be disallowed
+ nonstandard : true, // if non-standard (but widely adopted) globals should
+ // be predefined
+ nomen : true, // if names should be checked
+ onevar : true, // if only one var statement per function should be
+ // allowed
+ passfail : true, // if the scan should stop on first error
+ phantom : true, // if PhantomJS symbols should be allowed
+ plusplus : true, // if increment/decrement should not be allowed
+ proto : true, // if the `__proto__` property should be allowed
+ prototypejs : true, // if Prototype and Scriptaculous globals should be
+ // predefined
+ rhino : true, // if the Rhino environment globals should be predefined
+ undef : true, // if variables should be declared before used
+ scripturl : true, // if script-targeted URLs should be tolerated
+ shadow : true, // if variable shadowing should be tolerated
+ smarttabs : true, // if smarttabs should be tolerated
+ // (http://www.emacswiki.org/emacs/SmartTabs)
+ strict : true, // require the "use strict"; pragma
+ sub : true, // if all forms of subscript notation are tolerated
+ supernew : true, // if `new function () { ... };` and `new Object;`
+ // should be tolerated
+ trailing : true, // if trailing whitespace rules apply
+ validthis : true, // if 'this' inside a non-constructor function is valid.
+ // This is a function scoped option only.
+ withstmt : true, // if with statements should be allowed
+ white : true, // if strict whitespace rules apply
+ worker : true, // if Web Worker script symbols should be allowed
+ wsh : true, // if the Windows Scripting Host environment globals
+ // should be predefined
+ yui : true, // YUI variables should be predefined
+
+ // Obsolete options
+ onecase : true, // if one case switch statements should be allowed
+ regexp : true, // if the . should not be allowed in regexp literals
+ regexdash : true // if unescaped first/last dash (-) inside brackets
+ // should be tolerated
+ },
+
+ // These are the JSHint options that can take any value
+ // (we use this object to detect invalid options)
+ valOptions = {
+ maxlen : false,
+ indent : false,
+ maxerr : false,
+ predef : false,
+ quotmark : false, //'single'|'double'|true
+ scope : false,
+ maxstatements: false, // {int} max statements per function
+ maxdepth : false, // {int} max nested block depth per function
+ maxparams : false, // {int} max params per function
+ maxcomplexity: false, // {int} max cyclomatic complexity per function
+ unused : true, // warn if variables are unused. Available options:
+ // false - don't check for unused variables
+ // true - "vars" + check last function param
+ // "vars" - skip checking unused function params
+ // "strict" - "vars" + check all function params
+ latedef : false // warn if the variable is used before its definition
+ // false - don't emit any warnings
+ // true - warn if any variable is used before its definition
+ // "nofunc" - warn for any variable but function declarations
+ },
+
+ // These are JSHint boolean options which are shared with JSLint
+ // where the definition in JSHint is opposite JSLint
+ invertedOptions = {
+ bitwise : true,
+ forin : true,
+ newcap : true,
+ nomen : true,
+ plusplus: true,
+ regexp : true,
+ undef : true,
+ white : true,
+
+ // Inverted and renamed, use JSHint name here
+ eqeqeq : true,
+ onevar : true,
+ strict : true
+ },
+
+ // These are JSHint boolean options which are shared with JSLint
+ // where the name has been changed but the effect is unchanged
+ renamedOptions = {
+ eqeq : "eqeqeq",
+ vars : "onevar",
+ windows: "wsh",
+ sloppy : "strict"
+ },
+
+ declared, // Globals that were declared using /*global ... */ syntax.
+ exported, // Variables that are used outside of the current file.
+
+ functionicity = [
+ "closure", "exception", "global", "label",
+ "outer", "unused", "var"
+ ],
+
+ funct, // The current function
+ functions, // All of the functions
+
+ global, // The global scope
+ implied, // Implied globals
+ inblock,
+ indent,
+ lookahead,
+ lex,
+ member,
+ membersOnly,
+ noreach,
+ predefined, // Global variables defined by option
+
+ scope, // The current scope
+ stack,
+ unuseds,
+ urls,
+ warnings,
+
+ extraModules = [],
+ emitter = new events.EventEmitter();
+
+ function checkOption(name, t) {
+ name = name.trim();
+
+ if (/^[+-]W\d{3}$/g.test(name)) {
+ return true;
+ }
+
+ if (valOptions[name] === undefined && boolOptions[name] === undefined) {
+ if (t.type !== "jslint") {
+ error("E001", t, name);
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ function isString(obj) {
+ return Object.prototype.toString.call(obj) === "[object String]";
+ }
+
+ function isIdentifier(tkn, value) {
+ if (!tkn)
+ return false;
+
+ if (!tkn.identifier || tkn.value !== value)
+ return false;
+
+ return true;
+ }
+
+ function isReserved(token) {
+ if (!token.reserved) {
+ return false;
+ }
+
+ if (token.meta && token.meta.isFutureReservedWord) {
+ // ES3 FutureReservedWord in an ES5 environment.
+ if (state.option.inES5(true) && !token.meta.es5) {
+ return false;
+ }
+
+ // Some ES5 FutureReservedWord identifiers are active only
+ // within a strict mode environment.
+ if (token.meta.strictOnly) {
+ if (!state.option.strict && !state.directive["use strict"]) {
+ return false;
+ }
+ }
+
+ if (token.isProperty) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ function supplant(str, data) {
+ return str.replace(/\{([^{}]*)\}/g, function (a, b) {
+ var r = data[b];
+ return typeof r === "string" || typeof r === "number" ? r : a;
+ });
+ }
+
+ function combine(t, o) {
+ var n;
+ for (n in o) {
+ if (_.has(o, n) && !_.has(JSHINT.blacklist, n)) {
+ t[n] = o[n];
+ }
+ }
+ }
+
+ function updatePredefined() {
+ Object.keys(JSHINT.blacklist).forEach(function (key) {
+ delete predefined[key];
+ });
+ }
+
+ function assume() {
+ if (state.option.es5) {
+ warning("I003");
+ }
+ if (state.option.couch) {
+ combine(predefined, vars.couch);
+ }
+
+ if (state.option.rhino) {
+ combine(predefined, vars.rhino);
+ }
+
+ if (state.option.phantom) {
+ combine(predefined, vars.phantom);
+ }
+
+ if (state.option.prototypejs) {
+ combine(predefined, vars.prototypejs);
+ }
+
+ if (state.option.node) {
+ combine(predefined, vars.node);
+ }
+
+ if (state.option.devel) {
+ combine(predefined, vars.devel);
+ }
+
+ if (state.option.dojo) {
+ combine(predefined, vars.dojo);
+ }
+
+ if (state.option.browser) {
+ combine(predefined, vars.browser);
+ }
+
+ if (state.option.nonstandard) {
+ combine(predefined, vars.nonstandard);
+ }
+
+ if (state.option.jquery) {
+ combine(predefined, vars.jquery);
+ }
+
+ if (state.option.mootools) {
+ combine(predefined, vars.mootools);
+ }
+
+ if (state.option.worker) {
+ combine(predefined, vars.worker);
+ }
+
+ if (state.option.wsh) {
+ combine(predefined, vars.wsh);
+ }
+
+ if (state.option.globalstrict && state.option.strict !== false) {
+ state.option.strict = true;
+ }
+
+ if (state.option.yui) {
+ combine(predefined, vars.yui);
+ }
+
+ // Let's assume that chronologically ES3 < ES5 < ES6/ESNext < Moz
+
+ state.option.inMoz = function (strict) {
+ if (strict) {
+ return state.option.moz && !state.option.esnext;
+ }
+ return state.option.moz;
+ };
+
+ state.option.inESNext = function (strict) {
+ if (strict) {
+ return !state.option.moz && state.option.esnext;
+ }
+ return state.option.moz || state.option.esnext;
+ };
+
+ state.option.inES5 = function (/* strict */) {
+ return !state.option.es3;
+ };
+
+ state.option.inES3 = function (strict) {
+ if (strict) {
+ return !state.option.moz && !state.option.esnext && state.option.es3;
+ }
+ return state.option.es3;
+ };
+ }
+
+ // Produce an error warning.
+ function quit(code, line, chr) {
+ var percentage = Math.floor((line / state.lines.length) * 100);
+ var message = messages.errors[code].desc;
+
+ throw {
+ name: "JSHintError",
+ line: line,
+ character: chr,
+ message: message + " (" + percentage + "% scanned).",
+ raw: message
+ };
+ }
+
+ function isundef(scope, code, token, a) {
+ return JSHINT.undefs.push([scope, code, token, a]);
+ }
+
+ function warning(code, t, a, b, c, d) {
+ var ch, l, w, msg;
+
+ if (/^W\d{3}$/.test(code)) {
+ if (state.ignored[code])
+ return;
+
+ msg = messages.warnings[code];
+ } else if (/E\d{3}/.test(code)) {
+ msg = messages.errors[code];
+ } else if (/I\d{3}/.test(code)) {
+ msg = messages.info[code];
+ }
+
+ t = t || state.tokens.next;
+ if (t.id === "(end)") { // `~
+ t = state.tokens.curr;
+ }
+
+ l = t.line || 0;
+ ch = t.from || 0;
+
+ w = {
+ id: "(error)",
+ raw: msg.desc,
+ code: msg.code,
+ evidence: state.lines[l - 1] || "",
+ line: l,
+ character: ch,
+ scope: JSHINT.scope,
+ a: a,
+ b: b,
+ c: c,
+ d: d
+ };
+
+ w.reason = supplant(msg.desc, w);
+ JSHINT.errors.push(w);
+
+ if (state.option.passfail) {
+ quit("E042", l, ch);
+ }
+
+ warnings += 1;
+ if (warnings >= state.option.maxerr) {
+ quit("E043", l, ch);
+ }
+
+ return w;
+ }
+
+ function warningAt(m, l, ch, a, b, c, d) {
+ return warning(m, {
+ line: l,
+ from: ch
+ }, a, b, c, d);
+ }
+
+ function error(m, t, a, b, c, d) {
+ warning(m, t, a, b, c, d);
+ }
+
+ function errorAt(m, l, ch, a, b, c, d) {
+ return error(m, {
+ line: l,
+ from: ch
+ }, a, b, c, d);
+ }
+
+ // Tracking of "internal" scripts, like eval containing a static string
+ function addInternalSrc(elem, src) {
+ var i;
+ i = {
+ id: "(internal)",
+ elem: elem,
+ value: src
+ };
+ JSHINT.internals.push(i);
+ return i;
+ }
+
+ function addlabel(t, type, tkn, islet) {
+ // Define t in the current function in the current scope.
+ if (type === "exception") {
+ if (_.has(funct["(context)"], t)) {
+ if (funct[t] !== true && !state.option.node) {
+ warning("W002", state.tokens.next, t);
+ }
+ }
+ }
+
+ if (_.has(funct, t) && !funct["(global)"]) {
+ if (funct[t] === true) {
+ if (state.option.latedef) {
+ if ((state.option.latedef === true && _.contains([funct[t], type], "unction")) ||
+ !_.contains([funct[t], type], "unction")) {
+ warning("W003", state.tokens.next, t);
+ }
+ }
+ } else {
+ if (!state.option.shadow && type !== "exception" ||
+ (funct["(blockscope)"].getlabel(t))) {
+ warning("W004", state.tokens.next, t);
+ }
+ }
+ }
+
+ // a double definition of a let variable in same block throws a TypeError
+ //if (funct["(blockscope)"] && funct["(blockscope)"].current.has(t)) {
+ // error("E044", state.tokens.next, t);
+ //}
+
+ // if the identifier is from a let, adds it only to the current blockscope
+ if (islet) {
+ funct["(blockscope)"].current.add(t, type, state.tokens.curr);
+ } else {
+
+ funct[t] = type;
+
+ if (tkn) {
+ funct["(tokens)"][t] = tkn;
+ }
+
+ if (funct["(global)"]) {
+ global[t] = funct;
+ if (_.has(implied, t)) {
+ if (state.option.latedef) {
+ if ((state.option.latedef === true && _.contains([funct[t], type], "unction")) ||
+ !_.contains([funct[t], type], "unction")) {
+ warning("W003", state.tokens.next, t);
+ }
+ }
+
+ delete implied[t];
+ }
+ } else {
+ scope[t] = funct;
+ }
+ }
+ }
+
+ function doOption() {
+ var nt = state.tokens.next;
+ var body = nt.body.split(",").map(function (s) { return s.trim(); });
+ var predef = {};
+
+ if (nt.type === "globals") {
+ body.forEach(function (g) {
+ g = g.split(":");
+ var key = g[0];
+ var val = g[1];
+
+ if (key.charAt(0) === "-") {
+ key = key.slice(1);
+ val = false;
+
+ JSHINT.blacklist[key] = key;
+ updatePredefined();
+ } else {
+ predef[key] = (val === "true");
+ }
+ });
+
+ combine(predefined, predef);
+
+ for (var key in predef) {
+ if (_.has(predef, key)) {
+ declared[key] = nt;
+ }
+ }
+ }
+
+ if (nt.type === "exported") {
+ body.forEach(function (e) {
+ exported[e] = true;
+ });
+ }
+
+ if (nt.type === "members") {
+ membersOnly = membersOnly || {};
+
+ body.forEach(function (m) {
+ var ch1 = m.charAt(0);
+ var ch2 = m.charAt(m.length - 1);
+
+ if (ch1 === ch2 && (ch1 === "\"" || ch1 === "'")) {
+ m = m
+ .substr(1, m.length - 2)
+ .replace("\\b", "\b")
+ .replace("\\t", "\t")
+ .replace("\\n", "\n")
+ .replace("\\v", "\v")
+ .replace("\\f", "\f")
+ .replace("\\r", "\r")
+ .replace("\\\\", "\\")
+ .replace("\\\"", "\"");
+ }
+
+ membersOnly[m] = false;
+ });
+ }
+
+ var numvals = [
+ "maxstatements",
+ "maxparams",
+ "maxdepth",
+ "maxcomplexity",
+ "maxerr",
+ "maxlen",
+ "indent"
+ ];
+
+ if (nt.type === "jshint" || nt.type === "jslint") {
+ body.forEach(function (g) {
+ g = g.split(":");
+ var key = (g[0] || "").trim();
+ var val = (g[1] || "").trim();
+
+ if (!checkOption(key, nt)) {
+ return;
+ }
+
+ if (numvals.indexOf(key) >= 0) {
+
+ // GH988 - numeric options can be disabled by setting them to `false`
+ if (val !== "false") {
+ val = +val;
+
+ if (typeof val !== "number" || !isFinite(val) || val <= 0 || Math.floor(val) !== val) {
+ error("E032", nt, g[1].trim());
+ return;
+ }
+
+ if (key === "indent") {
+ state.option["(explicitIndent)"] = true;
+ }
+ state.option[key] = val;
+ } else {
+ if (key === "indent") {
+ state.option["(explicitIndent)"] = false;
+ } else {
+ state.option[key] = false;
+ }
+ }
+
+ return;
+ }
+
+ if (key === "validthis") {
+ // `validthis` is valid only within a function scope.
+ if (funct["(global)"]) {
+ error("E009");
+ } else {
+ if (val === "true" || val === "false") {
+ state.option.validthis = (val === "true");
+ } else {
+ error("E002", nt);
+ }
+ }
+ return;
+ }
+
+ if (key === "quotmark") {
+ switch (val) {
+ case "true":
+ case "false":
+ state.option.quotmark = (val === "true");
+ break;
+ case "double":
+ case "single":
+ state.option.quotmark = val;
+ break;
+ default:
+ error("E002", nt);
+ }
+ return;
+ }
+
+ if (key === "unused") {
+ switch (val) {
+ case "true":
+ state.option.unused = true;
+ break;
+ case "false":
+ state.option.unused = false;
+ break;
+ case "vars":
+ case "strict":
+ state.option.unused = val;
+ break;
+ default:
+ error("E002", nt);
+ }
+ return;
+ }
+
+ if (key === "latedef") {
+ switch (val) {
+ case "true":
+ state.option.latedef = true;
+ break;
+ case "false":
+ state.option.latedef = false;
+ break;
+ case "nofunc":
+ state.option.latedef = "nofunc";
+ break;
+ default:
+ error("E002", nt);
+ }
+ return;
+ }
+
+ var match = /^([+-])(W\d{3})$/g.exec(key);
+ if (match) {
+ // ignore for -W..., unignore for +W...
+ state.ignored[match[2]] = (match[1] === "-");
+ return;
+ }
+
+ var tn;
+ if (val === "true" || val === "false") {
+ if (nt.type === "jslint") {
+ tn = renamedOptions[key] || key;
+ state.option[tn] = (val === "true");
+
+ if (invertedOptions[tn] !== undefined) {
+ state.option[tn] = !state.option[tn];
+ }
+ } else {
+ state.option[key] = (val === "true");
+ }
+
+ if (key === "newcap") {
+ state.option["(explicitNewcap)"] = true;
+ }
+ return;
+ }
+
+ error("E002", nt);
+ });
+
+ assume();
+ }
+ }
+
+ // We need a peek function. If it has an argument, it peeks that much farther
+ // ahead. It is used to distinguish
+ // for ( var i in ...
+ // from
+ // for ( var i = ...
+
+ function peek(p) {
+ var i = p || 0, j = 0, t;
+
+ while (j <= i) {
+ t = lookahead[j];
+ if (!t) {
+ t = lookahead[j] = lex.token();
+ }
+ j += 1;
+ }
+ return t;
+ }
+
+ // Produce the next token. It looks for programming errors.
+
+ function advance(id, t) {
+ switch (state.tokens.curr.id) {
+ case "(number)":
+ if (state.tokens.next.id === ".") {
+ warning("W005", state.tokens.curr);
+ }
+ break;
+ case "-":
+ if (state.tokens.next.id === "-" || state.tokens.next.id === "--") {
+ warning("W006");
+ }
+ break;
+ case "+":
+ if (state.tokens.next.id === "+" || state.tokens.next.id === "++") {
+ warning("W007");
+ }
+ break;
+ }
+
+ if (state.tokens.curr.type === "(string)" || state.tokens.curr.identifier) {
+ anonname = state.tokens.curr.value;
+ }
+
+ if (id && state.tokens.next.id !== id) {
+ if (t) {
+ if (state.tokens.next.id === "(end)") {
+ error("E019", t, t.id);
+ } else {
+ error("E020", state.tokens.next, id, t.id, t.line, state.tokens.next.value);
+ }
+ } else if (state.tokens.next.type !== "(identifier)" || state.tokens.next.value !== id) {
+ warning("W116", state.tokens.next, id, state.tokens.next.value);
+ }
+ }
+
+ state.tokens.prev = state.tokens.curr;
+ state.tokens.curr = state.tokens.next;
+ for (;;) {
+ state.tokens.next = lookahead.shift() || lex.token();
+
+ if (!state.tokens.next) { // No more tokens left, give up
+ quit("E041", state.tokens.curr.line);
+ }
+
+ if (state.tokens.next.id === "(end)" || state.tokens.next.id === "(error)") {
+ return;
+ }
+
+ if (state.tokens.next.check) {
+ state.tokens.next.check();
+ }
+
+ if (state.tokens.next.isSpecial) {
+ doOption();
+ } else {
+ if (state.tokens.next.id !== "(endline)") {
+ break;
+ }
+ }
+ }
+ }
+
+ // This is the heart of JSHINT, the Pratt parser. In addition to parsing, it
+ // is looking for ad hoc lint patterns. We add .fud to Pratt's model, which is
+ // like .nud except that it is only used on the first token of a statement.
+ // Having .fud makes it much easier to define statement-oriented languages like
+ // JavaScript. I retained Pratt's nomenclature.
+
+ // .nud Null denotation
+ // .fud First null denotation
+ // .led Left denotation
+ // lbp Left binding power
+ // rbp Right binding power
+
+ // They are elements of the parsing method called Top Down Operator Precedence.
+
+ function expression(rbp, initial) {
+ var left, isArray = false, isObject = false, isLetExpr = false;
+
+ // if current expression is a let expression
+ if (!initial && state.tokens.next.value === "let" && peek(0).value === "(") {
+ if (!state.option.inMoz(true)) {
+ warning("W118", state.tokens.next, "let expressions");
+ }
+ isLetExpr = true;
+ // create a new block scope we use only for the current expression
+ funct["(blockscope)"].stack();
+ advance("let");
+ advance("(");
+ state.syntax["let"].fud.call(state.syntax["let"].fud, false);
+ advance(")");
+ }
+
+ if (state.tokens.next.id === "(end)")
+ error("E006", state.tokens.curr);
+
+ advance();
+
+ if (initial) {
+ anonname = "anonymous";
+ funct["(verb)"] = state.tokens.curr.value;
+ }
+
+ if (initial === true && state.tokens.curr.fud) {
+ left = state.tokens.curr.fud();
+ } else {
+ if (state.tokens.curr.nud) {
+ left = state.tokens.curr.nud();
+ } else {
+ error("E030", state.tokens.curr, state.tokens.curr.id);
+ }
+
+ var end_of_expr = state.tokens.next.identifier &&
+ !state.tokens.curr.led &&
+ state.tokens.curr.line !== state.tokens.next.line;
+ while (rbp < state.tokens.next.lbp && !end_of_expr) {
+ isArray = state.tokens.curr.value === "Array";
+ isObject = state.tokens.curr.value === "Object";
+
+ // #527, new Foo.Array(), Foo.Array(), new Foo.Object(), Foo.Object()
+ // Line breaks in IfStatement heads exist to satisfy the checkJSHint
+ // "Line too long." error.
+ if (left && (left.value || (left.first && left.first.value))) {
+ // If the left.value is not "new", or the left.first.value is a "."
+ // then safely assume that this is not "new Array()" and possibly
+ // not "new Object()"...
+ if (left.value !== "new" ||
+ (left.first && left.first.value && left.first.value === ".")) {
+ isArray = false;
+ // ...In the case of Object, if the left.value and state.tokens.curr.value
+ // are not equal, then safely assume that this not "new Object()"
+ if (left.value !== state.tokens.curr.value) {
+ isObject = false;
+ }
+ }
+ }
+
+ advance();
+
+ if (isArray && state.tokens.curr.id === "(" && state.tokens.next.id === ")") {
+ warning("W009", state.tokens.curr);
+ }
+
+ if (isObject && state.tokens.curr.id === "(" && state.tokens.next.id === ")") {
+ warning("W010", state.tokens.curr);
+ }
+
+ if (left && state.tokens.curr.led) {
+ left = state.tokens.curr.led(left);
+ } else {
+ error("E033", state.tokens.curr, state.tokens.curr.id);
+ }
+ }
+ }
+ if (isLetExpr) {
+ funct["(blockscope)"].unstack();
+ }
+ return left;
+ }
+
+
+// Functions for conformance of style.
+
+ function adjacent(left, right) {
+ left = left || state.tokens.curr;
+ right = right || state.tokens.next;
+ if (state.option.white) {
+ if (left.character !== right.from && left.line === right.line) {
+ left.from += (left.character - left.from);
+ warning("W011", left, left.value);
+ }
+ }
+ }
+
+ function nobreak(left, right) {
+ left = left || state.tokens.curr;
+ right = right || state.tokens.next;
+ if (state.option.white && (left.character !== right.from || left.line !== right.line)) {
+ warning("W012", right, right.value);
+ }
+ }
+
+ function nospace(left, right) {
+ left = left || state.tokens.curr;
+ right = right || state.tokens.next;
+ if (state.option.white && !left.comment) {
+ if (left.line === right.line) {
+ adjacent(left, right);
+ }
+ }
+ }
+
+ function nonadjacent(left, right) {
+ if (state.option.white) {
+ left = left || state.tokens.curr;
+ right = right || state.tokens.next;
+
+ if (left.value === ";" && right.value === ";") {
+ return;
+ }
+
+ if (left.line === right.line && left.character === right.from) {
+ left.from += (left.character - left.from);
+ warning("W013", left, left.value);
+ }
+ }
+ }
+
+ function nobreaknonadjacent(left, right) {
+ left = left || state.tokens.curr;
+ right = right || state.tokens.next;
+ if (!state.option.laxbreak && left.line !== right.line) {
+ warning("W014", right, right.id);
+ } else if (state.option.white) {
+ left = left || state.tokens.curr;
+ right = right || state.tokens.next;
+ if (left.character === right.from) {
+ left.from += (left.character - left.from);
+ warning("W013", left, left.value);
+ }
+ }
+ }
+
+ function indentation(bias) {
+ if (!state.option.white && !state.option["(explicitIndent)"]) {
+ return;
+ }
+
+ if (state.tokens.next.id === "(end)") {
+ return;
+ }
+
+ var i = indent + (bias || 0);
+ if (state.tokens.next.from !== i) {
+ warning("W015", state.tokens.next, state.tokens.next.value, i, state.tokens.next.from);
+ }
+ }
+
+ function nolinebreak(t) {
+ t = t || state.tokens.curr;
+ if (t.line !== state.tokens.next.line) {
+ warning("E022", t, t.value);
+ }
+ }
+
+
+ function comma(opts) {
+ opts = opts || {};
+
+ if (!opts.peek) {
+ if (state.tokens.curr.line !== state.tokens.next.line) {
+ if (!state.option.laxcomma) {
+ if (comma.first) {
+ warning("I001");
+ comma.first = false;
+ }
+ warning("W014", state.tokens.curr, state.tokens.next.value);
+ }
+ } else if (!state.tokens.curr.comment &&
+ state.tokens.curr.character !== state.tokens.next.from && state.option.white) {
+ state.tokens.curr.from += (state.tokens.curr.character - state.tokens.curr.from);
+ warning("W011", state.tokens.curr, state.tokens.curr.value);
+ }
+
+ advance(",");
+ }
+
+ // TODO: This is a temporary solution to fight against false-positives in
+ // arrays and objects with trailing commas (see GH-363). The best solution
+ // would be to extract all whitespace rules out of parser.
+
+ if (state.tokens.next.value !== "]" && state.tokens.next.value !== "}") {
+ nonadjacent(state.tokens.curr, state.tokens.next);
+ }
+
+ if (state.tokens.next.identifier && !(opts.property && state.option.inES5())) {
+ // Keywords that cannot follow a comma operator.
+ switch (state.tokens.next.value) {
+ case "break":
+ case "case":
+ case "catch":
+ case "continue":
+ case "default":
+ case "do":
+ case "else":
+ case "finally":
+ case "for":
+ case "if":
+ case "in":
+ case "instanceof":
+ case "return":
+ case "yield":
+ case "switch":
+ case "throw":
+ case "try":
+ case "var":
+ case "let":
+ case "while":
+ case "with":
+ error("E024", state.tokens.next, state.tokens.next.value);
+ return false;
+ }
+ }
+
+ if (state.tokens.next.type === "(punctuator)") {
+ switch (state.tokens.next.value) {
+ case "}":
+ case "]":
+ case ",":
+ if (opts.allowTrailing) {
+ return true;
+ }
+
+ /* falls through */
+ case ")":
+ error("E024", state.tokens.next, state.tokens.next.value);
+ return false;
+ }
+ }
+ return true;
+ }
+
+ // Functional constructors for making the symbols that will be inherited by
+ // tokens.
+
+ function symbol(s, p) {
+ var x = state.syntax[s];
+ if (!x || typeof x !== "object") {
+ state.syntax[s] = x = {
+ id: s,
+ lbp: p,
+ value: s
+ };
+ }
+ return x;
+ }
+
+ function delim(s) {
+ return symbol(s, 0);
+ }
+
+ function stmt(s, f) {
+ var x = delim(s);
+ x.identifier = x.reserved = true;
+ x.fud = f;
+ return x;
+ }
+
+ function blockstmt(s, f) {
+ var x = stmt(s, f);
+ x.block = true;
+ return x;
+ }
+
+ function reserveName(x) {
+ var c = x.id.charAt(0);
+ if ((c >= "a" && c <= "z") || (c >= "A" && c <= "Z")) {
+ x.identifier = x.reserved = true;
+ }
+ return x;
+ }
+
+ function prefix(s, f) {
+ var x = symbol(s, 150);
+ reserveName(x);
+ x.nud = (typeof f === "function") ? f : function () {
+ this.right = expression(150);
+ this.arity = "unary";
+ if (this.id === "++" || this.id === "--") {
+ if (state.option.plusplus) {
+ warning("W016", this, this.id);
+ } else if ((!this.right.identifier || isReserved(this.right)) &&
+ this.right.id !== "." && this.right.id !== "[") {
+ warning("W017", this);
+ }
+ }
+ return this;
+ };
+ return x;
+ }
+
+ function type(s, f) {
+ var x = delim(s);
+ x.type = s;
+ x.nud = f;
+ return x;
+ }
+
+ function reserve(name, func) {
+ var x = type(name, func);
+ x.identifier = true;
+ x.reserved = true;
+ return x;
+ }
+
+ function FutureReservedWord(name, meta) {
+ var x = type(name, (meta && meta.nud) || function () {
+ return this;
+ });
+
+ meta = meta || {};
+ meta.isFutureReservedWord = true;
+
+ x.value = name;
+ x.identifier = true;
+ x.reserved = true;
+ x.meta = meta;
+
+ return x;
+ }
+
+ function reservevar(s, v) {
+ return reserve(s, function () {
+ if (typeof v === "function") {
+ v(this);
+ }
+ return this;
+ });
+ }
+
+ function infix(s, f, p, w) {
+ var x = symbol(s, p);
+ reserveName(x);
+ x.led = function (left) {
+ if (!w) {
+ nobreaknonadjacent(state.tokens.prev, state.tokens.curr);
+ nonadjacent(state.tokens.curr, state.tokens.next);
+ }
+ if (s === "in" && left.id === "!") {
+ warning("W018", left, "!");
+ }
+ if (typeof f === "function") {
+ return f(left, this);
+ } else {
+ this.left = left;
+ this.right = expression(p);
+ return this;
+ }
+ };
+ return x;
+ }
+
+
+ function application(s) {
+ var x = symbol(s, 42);
+
+ x.led = function (left) {
+ if (!state.option.inESNext()) {
+ warning("W104", state.tokens.curr, "arrow function syntax (=>)");
+ }
+
+ nobreaknonadjacent(state.tokens.prev, state.tokens.curr);
+ nonadjacent(state.tokens.curr, state.tokens.next);
+
+ this.left = left;
+ this.right = doFunction(undefined, undefined, false, left);
+ return this;
+ };
+ return x;
+ }
+
+ function relation(s, f) {
+ var x = symbol(s, 100);
+
+ x.led = function (left) {
+ nobreaknonadjacent(state.tokens.prev, state.tokens.curr);
+ nonadjacent(state.tokens.curr, state.tokens.next);
+ var right = expression(100);
+
+ if (isIdentifier(left, "NaN") || isIdentifier(right, "NaN")) {
+ warning("W019", this);
+ } else if (f) {
+ f.apply(this, [left, right]);
+ }
+
+ if (!left || !right) {
+ quit("E041", state.tokens.curr.line);
+ }
+
+ if (left.id === "!") {
+ warning("W018", left, "!");
+ }
+
+ if (right.id === "!") {
+ warning("W018", right, "!");
+ }
+
+ this.left = left;
+ this.right = right;
+ return this;
+ };
+ return x;
+ }
+
+ function isPoorRelation(node) {
+ return node &&
+ ((node.type === "(number)" && +node.value === 0) ||
+ (node.type === "(string)" && node.value === "") ||
+ (node.type === "null" && !state.option.eqnull) ||
+ node.type === "true" ||
+ node.type === "false" ||
+ node.type === "undefined");
+ }
+
+ function assignop(s) {
+ symbol(s, 20).exps = true;
+
+ return infix(s, function (left, that) {
+ that.left = left;
+
+ if (left) {
+ if (predefined[left.value] === false &&
+ scope[left.value]["(global)"] === true) {
+ warning("W020", left);
+ } else if (left["function"]) {
+ warning("W021", left, left.value);
+ }
+
+ if (funct[left.value] === "const") {
+ error("E013", left, left.value);
+ }
+
+ if (left.id === ".") {
+ if (!left.left) {
+ warning("E031", that);
+ } else if (left.left.value === "arguments" && !state.directive["use strict"]) {
+ warning("E031", that);
+ }
+
+ that.right = expression(19);
+ return that;
+ } else if (left.id === "[") {
+ if (state.tokens.curr.left.first) {
+ state.tokens.curr.left.first.forEach(function (t) {
+ if (funct[t.value] === "const") {
+ error("E013", t, t.value);
+ }
+ });
+ } else if (!left.left) {
+ warning("E031", that);
+ } else if (left.left.value === "arguments" && !state.directive["use strict"]) {
+ warning("E031", that);
+ }
+ that.right = expression(19);
+ return that;
+ } else if (left.identifier && !isReserved(left)) {
+ if (funct[left.value] === "exception") {
+ warning("W022", left);
+ }
+ that.right = expression(19);
+ return that;
+ }
+
+ if (left === state.syntax["function"]) {
+ warning("W023", state.tokens.curr);
+ }
+ }
+
+ error("E031", that);
+ }, 20);
+ }
+
+
+ function bitwise(s, f, p) {
+ var x = symbol(s, p);
+ reserveName(x);
+ x.led = (typeof f === "function") ? f : function (left) {
+ if (state.option.bitwise) {
+ warning("W016", this, this.id);
+ }
+ this.left = left;
+ this.right = expression(p);
+ return this;
+ };
+ return x;
+ }
+
+
+ function bitwiseassignop(s) {
+ symbol(s, 20).exps = true;
+ return infix(s, function (left, that) {
+ if (state.option.bitwise) {
+ warning("W016", that, that.id);
+ }
+ nonadjacent(state.tokens.prev, state.tokens.curr);
+ nonadjacent(state.tokens.curr, state.tokens.next);
+ if (left) {
+ if (left.id === "." || left.id === "[" ||
+ (left.identifier && !isReserved(left))) {
+ expression(19);
+ return that;
+ }
+ if (left === state.syntax["function"]) {
+ warning("W023", state.tokens.curr);
+ }
+ return that;
+ }
+ error("E031", that);
+ }, 20);
+ }
+
+
+ function suffix(s) {
+ var x = symbol(s, 150);
+
+ x.led = function (left) {
+ if (state.option.plusplus) {
+ warning("W016", this, this.id);
+ } else if ((!left.identifier || isReserved(left)) && left.id !== "." && left.id !== "[") {
+ warning("W017", this);
+ }
+
+ this.left = left;
+ return this;
+ };
+ return x;
+ }
+
+ // fnparam means that this identifier is being defined as a function
+ // argument (see identifier())
+ // prop means that this identifier is that of an object property
+
+ function optionalidentifier(fnparam, prop) {
+ if (!state.tokens.next.identifier) {
+ return;
+ }
+
+ advance();
+
+ var curr = state.tokens.curr;
+ var meta = curr.meta || {};
+ var val = state.tokens.curr.value;
+
+ if (!isReserved(curr)) {
+ return val;
+ }
+
+ if (prop) {
+ if (state.option.inES5() || meta.isFutureReservedWord) {
+ return val;
+ }
+ }
+
+ if (fnparam && val === "undefined") {
+ return val;
+ }
+
+ // Display an info message about reserved words as properties
+ // and ES5 but do it only once.
+ if (prop && !api.getCache("displayed:I002")) {
+ api.setCache("displayed:I002", true);
+ warning("I002");
+ }
+
+ warning("W024", state.tokens.curr, state.tokens.curr.id);
+ return val;
+ }
+
+ // fnparam means that this identifier is being defined as a function
+ // argument
+ // prop means that this identifier is that of an object property
+ function identifier(fnparam, prop) {
+ var i = optionalidentifier(fnparam, prop);
+ if (i) {
+ return i;
+ }
+ if (state.tokens.curr.id === "function" && state.tokens.next.id === "(") {
+ warning("W025");
+ } else {
+ error("E030", state.tokens.next, state.tokens.next.value);
+ }
+ }
+
+
+ function reachable(s) {
+ var i = 0, t;
+ if (state.tokens.next.id !== ";" || noreach) {
+ return;
+ }
+ for (;;) {
+ t = peek(i);
+ if (t.reach) {
+ return;
+ }
+ if (t.id !== "(endline)") {
+ if (t.id === "function") {
+ if (!state.option.latedef) {
+ break;
+ }
+
+ warning("W026", t);
+ break;
+ }
+
+ warning("W027", t, t.value, s);
+ break;
+ }
+ i += 1;
+ }
+ }
+
+
+ function statement(noindent) {
+ var values;
+ var i = indent, r, s = scope, t = state.tokens.next;
+
+ if (t.id === ";") {
+ advance(";");
+ return;
+ }
+
+ // Is this a labelled statement?
+ var res = isReserved(t);
+
+ // We're being more tolerant here: if someone uses
+ // a FutureReservedWord as a label, we warn but proceed
+ // anyway.
+
+ if (res && t.meta && t.meta.isFutureReservedWord && peek().id === ":") {
+ warning("W024", t, t.id);
+ res = false;
+ }
+
+ // detect a destructuring assignment
+ if (_.has(["[", "{"], t.value)) {
+ if (lookupBlockType().isDestAssign) {
+ if (!state.option.inESNext()) {
+ warning("W104", state.tokens.curr, "destructuring expression");
+ }
+ values = destructuringExpression();
+ values.forEach(function (tok) {
+ isundef(funct, "W117", tok.token, tok.id);
+ });
+ advance("=");
+ destructuringExpressionMatch(values, expression(5, true));
+ advance(";");
+ return;
+ }
+ }
+ if (t.identifier && !res && peek().id === ":") {
+ advance();
+ advance(":");
+ scope = Object.create(s);
+ addlabel(t.value, "label");
+
+ if (!state.tokens.next.labelled && state.tokens.next.value !== "{") {
+ warning("W028", state.tokens.next, t.value, state.tokens.next.value);
+ }
+
+ state.tokens.next.label = t.value;
+ t = state.tokens.next;
+ }
+
+ // Is it a lonely block?
+
+ if (t.id === "{") {
+ // Is it a switch case block?
+ //
+ // switch (foo) {
+ // case bar: { <= here.
+ // ...
+ // }
+ // }
+ var iscase = (funct["(verb)"] === "case" && state.tokens.curr.value === ":");
+ block(true, true, false, false, iscase);
+ return;
+ }
+
+ // Parse the statement.
+
+ if (!noindent) {
+ indentation();
+ }
+ r = expression(0, true);
+
+ // Look for the final semicolon.
+
+ if (!t.block) {
+ if (!state.option.expr && (!r || !r.exps)) {
+ warning("W030", state.tokens.curr);
+ } else if (state.option.nonew && r && r.left && r.id === "(" && r.left.id === "new") {
+ warning("W031", t);
+ }
+
+ if (state.tokens.next.id !== ";") {
+ if (!state.option.asi) {
+ // If this is the last statement in a block that ends on
+ // the same line *and* option lastsemic is on, ignore the warning.
+ // Otherwise, complain about missing semicolon.
+ if (!state.option.lastsemic || state.tokens.next.id !== "}" ||
+ state.tokens.next.line !== state.tokens.curr.line) {
+ warningAt("W033", state.tokens.curr.line, state.tokens.curr.character);
+ }
+ }
+ } else {
+ adjacent(state.tokens.curr, state.tokens.next);
+ advance(";");
+ nonadjacent(state.tokens.curr, state.tokens.next);
+ }
+ }
+
+ // Restore the indentation.
+
+ indent = i;
+ scope = s;
+ return r;
+ }
+
+
+ function statements(startLine) {
+ var a = [], p;
+
+ while (!state.tokens.next.reach && state.tokens.next.id !== "(end)") {
+ if (state.tokens.next.id === ";") {
+ p = peek();
+
+ if (!p || (p.id !== "(" && p.id !== "[")) {
+ warning("W032");
+ }
+
+ advance(";");
+ } else {
+ a.push(statement(startLine === state.tokens.next.line));
+ }
+ }
+ return a;
+ }
+
+
+ /*
+ * read all directives
+ * recognizes a simple form of asi, but always
+ * warns, if it is used
+ */
+ function directives() {
+ var i, p, pn;
+
+ for (;;) {
+ if (state.tokens.next.id === "(string)") {
+ p = peek(0);
+ if (p.id === "(endline)") {
+ i = 1;
+ do {
+ pn = peek(i);
+ i = i + 1;
+ } while (pn.id === "(endline)");
+
+ if (pn.id !== ";") {
+ if (pn.id !== "(string)" && pn.id !== "(number)" &&
+ pn.id !== "(regexp)" && pn.identifier !== true &&
+ pn.id !== "}") {
+ break;
+ }
+ warning("W033", state.tokens.next);
+ } else {
+ p = pn;
+ }
+ } else if (p.id === "}") {
+ // Directive with no other statements, warn about missing semicolon
+ warning("W033", p);
+ } else if (p.id !== ";") {
+ break;
+ }
+
+ indentation();
+ advance();
+ if (state.directive[state.tokens.curr.value]) {
+ warning("W034", state.tokens.curr, state.tokens.curr.value);
+ }
+
+ if (state.tokens.curr.value === "use strict") {
+ if (!state.option["(explicitNewcap)"])
+ state.option.newcap = true;
+ state.option.undef = true;
+ }
+
+ // there's no directive negation, so always set to true
+ state.directive[state.tokens.curr.value] = true;
+
+ if (p.id === ";") {
+ advance(";");
+ }
+ continue;
+ }
+ break;
+ }
+ }
+
+
+ /*
+ * Parses a single block. A block is a sequence of statements wrapped in
+ * braces.
+ *
+ * ordinary - true for everything but function bodies and try blocks.
+ * stmt - true if block can be a single statement (e.g. in if/for/while).
+ * isfunc - true if block is a function body
+ * isfatarrow -
+ * iscase - true if block is a switch case block
+ */
+ function block(ordinary, stmt, isfunc, isfatarrow, iscase) {
+ var a,
+ b = inblock,
+ old_indent = indent,
+ m,
+ s = scope,
+ t,
+ line,
+ d;
+
+ inblock = ordinary;
+
+ if (!ordinary || !state.option.funcscope)
+ scope = Object.create(scope);
+
+ nonadjacent(state.tokens.curr, state.tokens.next);
+ t = state.tokens.next;
+
+ var metrics = funct["(metrics)"];
+ metrics.nestedBlockDepth += 1;
+ metrics.verifyMaxNestedBlockDepthPerFunction();
+
+ if (state.tokens.next.id === "{") {
+ advance("{");
+
+ // create a new block scope
+ funct["(blockscope)"].stack();
+
+ line = state.tokens.curr.line;
+ if (state.tokens.next.id !== "}") {
+ indent += state.option.indent;
+ while (!ordinary && state.tokens.next.from > indent) {
+ indent += state.option.indent;
+ }
+
+ if (isfunc) {
+ m = {};
+ for (d in state.directive) {
+ if (_.has(state.directive, d)) {
+ m[d] = state.directive[d];
+ }
+ }
+ directives();
+
+ if (state.option.strict && funct["(context)"]["(global)"]) {
+ if (!m["use strict"] && !state.directive["use strict"]) {
+ warning("E007");
+ }
+ }
+ }
+
+ a = statements(line);
+
+ metrics.statementCount += a.length;
+
+ if (isfunc) {
+ state.directive = m;
+ }
+
+ indent -= state.option.indent;
+ if (line !== state.tokens.next.line) {
+ indentation();
+ }
+ } else if (line !== state.tokens.next.line) {
+ indentation();
+ }
+ advance("}", t);
+
+ funct["(blockscope)"].unstack();
+
+ indent = old_indent;
+ } else if (!ordinary) {
+ if (isfunc) {
+ m = {};
+ if (stmt && !isfatarrow && !state.option.inMoz(true)) {
+ error("W118", state.tokens.curr, "function closure expressions");
+ }
+
+ if (!stmt) {
+ for (d in state.directive) {
+ if (_.has(state.directive, d)) {
+ m[d] = state.directive[d];
+ }
+ }
+ }
+ expression(5);
+
+ if (state.option.strict && funct["(context)"]["(global)"]) {
+ if (!m["use strict"] && !state.directive["use strict"]) {
+ warning("E007");
+ }
+ }
+ } else {
+ error("E021", state.tokens.next, "{", state.tokens.next.value);
+ }
+ } else {
+
+ // check to avoid let declaration not within a block
+ funct["(nolet)"] = true;
+
+ if (!stmt || state.option.curly) {
+ warning("W116", state.tokens.next, "{", state.tokens.next.value);
+ }
+
+ noreach = true;
+ indent += state.option.indent;
+ // test indentation only if statement is in new line
+ a = [statement(state.tokens.next.line === state.tokens.curr.line)];
+ indent -= state.option.indent;
+ noreach = false;
+
+ delete funct["(nolet)"];
+ }
+ // If it is a "break" in switch case, don't clear and let it propagate out.
+ if (!(iscase && funct["(verb)"] === "break")) funct["(verb)"] = null;
+
+ if (!ordinary || !state.option.funcscope) scope = s;
+ inblock = b;
+ if (ordinary && state.option.noempty && (!a || a.length === 0)) {
+ warning("W035");
+ }
+ metrics.nestedBlockDepth -= 1;
+ return a;
+ }
+
+
+ function countMember(m) {
+ if (membersOnly && typeof membersOnly[m] !== "boolean") {
+ warning("W036", state.tokens.curr, m);
+ }
+ if (typeof member[m] === "number") {
+ member[m] += 1;
+ } else {
+ member[m] = 1;
+ }
+ }
+
+
+ function note_implied(tkn) {
+ var name = tkn.value, line = tkn.line, a = implied[name];
+ if (typeof a === "function") {
+ a = false;
+ }
+
+ if (!a) {
+ a = [line];
+ implied[name] = a;
+ } else if (a[a.length - 1] !== line) {
+ a.push(line);
+ }
+ }
+
+
+ // Build the syntax table by declaring the syntactic elements of the language.
+
+ type("(number)", function () {
+ return this;
+ });
+
+ type("(string)", function () {
+ return this;
+ });
+
+ state.syntax["(identifier)"] = {
+ type: "(identifier)",
+ lbp: 0,
+ identifier: true,
+ nud: function () {
+ var v = this.value,
+ s = scope[v],
+ f;
+
+ if (typeof s === "function") {
+ // Protection against accidental inheritance.
+ s = undefined;
+ } else if (typeof s === "boolean") {
+ f = funct;
+ funct = functions[0];
+ addlabel(v, "var");
+ s = funct;
+ funct = f;
+ }
+ var block;
+ if (_.has(funct, "(blockscope)")) {
+ block = funct["(blockscope)"].getlabel(v);
+ }
+
+ // The name is in scope and defined in the current function.
+ if (funct === s || block) {
+ // Change 'unused' to 'var', and reject labels.
+ // the name is in a block scope
+ switch (block ? block[v]["(type)"] : funct[v]) {
+ case "unused":
+ if (block) block[v]["(type)"] = "var";
+ else funct[v] = "var";
+ break;
+ case "unction":
+ if (block) block[v]["(type)"] = "function";
+ else funct[v] = "function";
+ this["function"] = true;
+ break;
+ case "function":
+ this["function"] = true;
+ break;
+ case "label":
+ warning("W037", state.tokens.curr, v);
+ break;
+ }
+ } else if (funct["(global)"]) {
+ // The name is not defined in the function. If we are in the global
+ // scope, then we have an undefined variable.
+ //
+ // Operators typeof and delete do not raise runtime errors even if
+ // the base object of a reference is null so no need to display warning
+ // if we're inside of typeof or delete.
+
+ if (typeof predefined[v] !== "boolean") {
+ // Attempting to subscript a null reference will throw an
+ // error, even within the typeof and delete operators
+ if (!(anonname === "typeof" || anonname === "delete") ||
+ (state.tokens.next && (state.tokens.next.value === "." ||
+ state.tokens.next.value === "["))) {
+
+ // if we're in a list comprehension, variables are declared
+ // locally and used before being defined. So we check
+ // the presence of the given variable in the comp array
+ // before declaring it undefined.
+
+ if (!funct["(comparray)"].check(v)) {
+ isundef(funct, "W117", state.tokens.curr, v);
+ }
+ }
+ }
+
+ note_implied(state.tokens.curr);
+ } else {
+ // If the name is already defined in the current
+ // function, but not as outer, then there is a scope error.
+
+ switch (funct[v]) {
+ case "closure":
+ case "function":
+ case "var":
+ case "unused":
+ warning("W038", state.tokens.curr, v);
+ break;
+ case "label":
+ warning("W037", state.tokens.curr, v);
+ break;
+ case "outer":
+ case "global":
+ break;
+ default:
+ // If the name is defined in an outer function, make an outer entry,
+ // and if it was unused, make it var.
+ if (s === true) {
+ funct[v] = true;
+ } else if (s === null) {
+ warning("W039", state.tokens.curr, v);
+ note_implied(state.tokens.curr);
+ } else if (typeof s !== "object") {
+ // Operators typeof and delete do not raise runtime errors even
+ // if the base object of a reference is null so no need to
+ //
+ // display warning if we're inside of typeof or delete.
+ // Attempting to subscript a null reference will throw an
+ // error, even within the typeof and delete operators
+ if (!(anonname === "typeof" || anonname === "delete") ||
+ (state.tokens.next &&
+ (state.tokens.next.value === "." || state.tokens.next.value === "["))) {
+
+ isundef(funct, "W117", state.tokens.curr, v);
+ }
+ funct[v] = true;
+ note_implied(state.tokens.curr);
+ } else {
+ switch (s[v]) {
+ case "function":
+ case "unction":
+ this["function"] = true;
+ s[v] = "closure";
+ funct[v] = s["(global)"] ? "global" : "outer";
+ break;
+ case "var":
+ case "unused":
+ s[v] = "closure";
+ funct[v] = s["(global)"] ? "global" : "outer";
+ break;
+ case "closure":
+ funct[v] = s["(global)"] ? "global" : "outer";
+ break;
+ case "label":
+ warning("W037", state.tokens.curr, v);
+ }
+ }
+ }
+ }
+ return this;
+ },
+ led: function () {
+ error("E033", state.tokens.next, state.tokens.next.value);
+ }
+ };
+
+ type("(regexp)", function () {
+ return this;
+ });
+
+ // ECMAScript parser
+
+ delim("(endline)");
+ delim("(begin)");
+ delim("(end)").reach = true;
+ delim("(error)").reach = true;
+ delim("}").reach = true;
+ delim(")");
+ delim("]");
+ delim("\"").reach = true;
+ delim("'").reach = true;
+ delim(";");
+ delim(":").reach = true;
+ delim("#");
+
+ reserve("else");
+ reserve("case").reach = true;
+ reserve("catch");
+ reserve("default").reach = true;
+ reserve("finally");
+ reservevar("arguments", function (x) {
+ if (state.directive["use strict"] && funct["(global)"]) {
+ warning("E008", x);
+ }
+ });
+ reservevar("eval");
+ reservevar("false");
+ reservevar("Infinity");
+ reservevar("null");
+ reservevar("this", function (x) {
+ if (state.directive["use strict"] && !state.option.validthis && ((funct["(statement)"] &&
+ funct["(name)"].charAt(0) > "Z") || funct["(global)"])) {
+ warning("W040", x);
+ }
+ });
+ reservevar("true");
+ reservevar("undefined");
+
+ assignop("=", "assign", 20);
+ assignop("+=", "assignadd", 20);
+ assignop("-=", "assignsub", 20);
+ assignop("*=", "assignmult", 20);
+ assignop("/=", "assigndiv", 20).nud = function () {
+ error("E014");
+ };
+ assignop("%=", "assignmod", 20);
+
+ bitwiseassignop("&=", "assignbitand", 20);
+ bitwiseassignop("|=", "assignbitor", 20);
+ bitwiseassignop("^=", "assignbitxor", 20);
+ bitwiseassignop("<<=", "assignshiftleft", 20);
+ bitwiseassignop(">>=", "assignshiftright", 20);
+ bitwiseassignop(">>>=", "assignshiftrightunsigned", 20);
+ infix(",", function (left, that) {
+ var expr;
+ that.exprs = [left];
+ if (!comma({peek: true})) {
+ return that;
+ }
+ while (true) {
+ if (!(expr = expression(5))) {
+ break;
+ }
+ that.exprs.push(expr);
+ if (state.tokens.next.value !== "," || !comma()) {
+ break;
+ }
+ }
+ return that;
+ }, 5, true);
+ infix("?", function (left, that) {
+ that.left = left;
+ that.right = expression(10);
+ advance(":");
+ that["else"] = expression(10);
+ return that;
+ }, 30);
+
+ infix("||", "or", 40);
+ infix("&&", "and", 50);
+ bitwise("|", "bitor", 70);
+ bitwise("^", "bitxor", 80);
+ bitwise("&", "bitand", 90);
+ relation("==", function (left, right) {
+ var eqnull = state.option.eqnull && (left.value === "null" || right.value === "null");
+
+ if (!eqnull && state.option.eqeqeq)
+ warning("W116", this, "===", "==");
+ else if (isPoorRelation(left))
+ warning("W041", this, "===", left.value);
+ else if (isPoorRelation(right))
+ warning("W041", this, "===", right.value);
+
+ return this;
+ });
+ relation("===");
+ relation("!=", function (left, right) {
+ var eqnull = state.option.eqnull &&
+ (left.value === "null" || right.value === "null");
+
+ if (!eqnull && state.option.eqeqeq) {
+ warning("W116", this, "!==", "!=");
+ } else if (isPoorRelation(left)) {
+ warning("W041", this, "!==", left.value);
+ } else if (isPoorRelation(right)) {
+ warning("W041", this, "!==", right.value);
+ }
+ return this;
+ });
+ relation("!==");
+ relation("<");
+ relation(">");
+ relation("<=");
+ relation(">=");
+ bitwise("<<", "shiftleft", 120);
+ bitwise(">>", "shiftright", 120);
+ bitwise(">>>", "shiftrightunsigned", 120);
+ infix("in", "in", 120);
+ infix("instanceof", "instanceof", 120);
+ infix("+", function (left, that) {
+ var right = expression(130);
+ if (left && right && left.id === "(string)" && right.id === "(string)") {
+ left.value += right.value;
+ left.character = right.character;
+ if (!state.option.scripturl && reg.javascriptURL.test(left.value)) {
+ warning("W050", left);
+ }
+ return left;
+ }
+ that.left = left;
+ that.right = right;
+ return that;
+ }, 130);
+ prefix("+", "num");
+ prefix("+++", function () {
+ warning("W007");
+ this.right = expression(150);
+ this.arity = "unary";
+ return this;
+ });
+ infix("+++", function (left) {
+ warning("W007");
+ this.left = left;
+ this.right = expression(130);
+ return this;
+ }, 130);
+ infix("-", "sub", 130);
+ prefix("-", "neg");
+ prefix("---", function () {
+ warning("W006");
+ this.right = expression(150);
+ this.arity = "unary";
+ return this;
+ });
+ infix("---", function (left) {
+ warning("W006");
+ this.left = left;
+ this.right = expression(130);
+ return this;
+ }, 130);
+ infix("*", "mult", 140);
+ infix("/", "div", 140);
+ infix("%", "mod", 140);
+
+ suffix("++", "postinc");
+ prefix("++", "preinc");
+ state.syntax["++"].exps = true;
+
+ suffix("--", "postdec");
+ prefix("--", "predec");
+ state.syntax["--"].exps = true;
+ prefix("delete", function () {
+ var p = expression(5);
+ if (!p || (p.id !== "." && p.id !== "[")) {
+ warning("W051");
+ }
+ this.first = p;
+ return this;
+ }).exps = true;
+
+ prefix("~", function () {
+ if (state.option.bitwise) {
+ warning("W052", this, "~");
+ }
+ expression(150);
+ return this;
+ });
+
+ prefix("...", function () {
+ if (!state.option.inESNext()) {
+ warning("W104", this, "spread/rest operator");
+ }
+ if (!state.tokens.next.identifier) {
+ error("E030", state.tokens.next, state.tokens.next.value);
+ }
+ expression(150);
+ return this;
+ });
+
+ prefix("!", function () {
+ this.right = expression(150);
+ this.arity = "unary";
+
+ if (!this.right) { // '!' followed by nothing? Give up.
+ quit("E041", this.line || 0);
+ }
+
+ if (bang[this.right.id] === true) {
+ warning("W018", this, "!");
+ }
+ return this;
+ });
+
+ prefix("typeof", "typeof");
+ prefix("new", function () {
+ var c = expression(155), i;
+ if (c && c.id !== "function") {
+ if (c.identifier) {
+ c["new"] = true;
+ switch (c.value) {
+ case "Number":
+ case "String":
+ case "Boolean":
+ case "Math":
+ case "JSON":
+ warning("W053", state.tokens.prev, c.value);
+ break;
+ case "Function":
+ if (!state.option.evil) {
+ warning("W054");
+ }
+ break;
+ case "Date":
+ case "RegExp":
+ break;
+ default:
+ if (c.id !== "function") {
+ i = c.value.substr(0, 1);
+ if (state.option.newcap && (i < "A" || i > "Z") && !_.has(global, c.value)) {
+ warning("W055", state.tokens.curr);
+ }
+ }
+ }
+ } else {
+ if (c.id !== "." && c.id !== "[" && c.id !== "(") {
+ warning("W056", state.tokens.curr);
+ }
+ }
+ } else {
+ if (!state.option.supernew)
+ warning("W057", this);
+ }
+ adjacent(state.tokens.curr, state.tokens.next);
+ if (state.tokens.next.id !== "(" && !state.option.supernew) {
+ warning("W058", state.tokens.curr, state.tokens.curr.value);
+ }
+ this.first = c;
+ return this;
+ });
+ state.syntax["new"].exps = true;
+
+ prefix("void").exps = true;
+
+ infix(".", function (left, that) {
+ adjacent(state.tokens.prev, state.tokens.curr);
+ nobreak();
+ var m = identifier(false, true);
+
+ if (typeof m === "string") {
+ countMember(m);
+ }
+
+ that.left = left;
+ that.right = m;
+
+ if (m && m === "hasOwnProperty" && state.tokens.next.value === "=") {
+ warning("W001");
+ }
+
+ if (left && left.value === "arguments" && (m === "callee" || m === "caller")) {
+ if (state.option.noarg)
+ warning("W059", left, m);
+ else if (state.directive["use strict"])
+ error("E008");
+ } else if (!state.option.evil && left && left.value === "document" &&
+ (m === "write" || m === "writeln")) {
+ warning("W060", left);
+ }
+
+ if (!state.option.evil && (m === "eval" || m === "execScript")) {
+ warning("W061");
+ }
+
+ return that;
+ }, 160, true);
+
+ infix("(", function (left, that) {
+ if (state.tokens.prev.id !== "}" && state.tokens.prev.id !== ")") {
+ nobreak(state.tokens.prev, state.tokens.curr);
+ }
+
+ nospace();
+ if (state.option.immed && left && !left.immed && left.id === "function") {
+ warning("W062");
+ }
+
+ var n = 0;
+ var p = [];
+
+ if (left) {
+ if (left.type === "(identifier)") {
+ if (left.value.match(/^[A-Z]([A-Z0-9_$]*[a-z][A-Za-z0-9_$]*)?$/)) {
+ if ("Number String Boolean Date Object".indexOf(left.value) === -1) {
+ if (left.value === "Math") {
+ warning("W063", left);
+ } else if (state.option.newcap) {
+ warning("W064", left);
+ }
+ }
+ }
+ }
+ }
+
+ if (state.tokens.next.id !== ")") {
+ for (;;) {
+ p[p.length] = expression(10);
+ n += 1;
+ if (state.tokens.next.id !== ",") {
+ break;
+ }
+ comma();
+ }
+ }
+
+ advance(")");
+ nospace(state.tokens.prev, state.tokens.curr);
+
+ if (typeof left === "object") {
+ if (left.value === "parseInt" && n === 1) {
+ warning("W065", state.tokens.curr);
+ }
+ if (!state.option.evil) {
+ if (left.value === "eval" || left.value === "Function" ||
+ left.value === "execScript") {
+ warning("W061", left);
+
+ if (p[0] && [0].id === "(string)") {
+ addInternalSrc(left, p[0].value);
+ }
+ } else if (p[0] && p[0].id === "(string)" &&
+ (left.value === "setTimeout" ||
+ left.value === "setInterval")) {
+ warning("W066", left);
+ addInternalSrc(left, p[0].value);
+
+ // window.setTimeout/setInterval
+ } else if (p[0] && p[0].id === "(string)" &&
+ left.value === "." &&
+ left.left.value === "window" &&
+ (left.right === "setTimeout" ||
+ left.right === "setInterval")) {
+ warning("W066", left);
+ addInternalSrc(left, p[0].value);
+ }
+ }
+ if (!left.identifier && left.id !== "." && left.id !== "[" &&
+ left.id !== "(" && left.id !== "&&" && left.id !== "||" &&
+ left.id !== "?") {
+ warning("W067", left);
+ }
+ }
+
+ that.left = left;
+ return that;
+ }, 155, true).exps = true;
+
+ prefix("(", function () {
+ nospace();
+ var bracket, brackets = [];
+ var pn, pn1, i = 0;
+
+ do {
+ pn = peek(i);
+ i += 1;
+ pn1 = peek(i);
+ i += 1;
+ } while (pn.value !== ")" && pn1.value !== "=>" && pn1.value !== ";" && pn1.type !== "(end)");
+
+ if (state.tokens.next.id === "function") {
+ state.tokens.next.immed = true;
+ }
+
+ var exprs = [];
+
+ if (state.tokens.next.id !== ")") {
+ for (;;) {
+ if (pn1.value === "=>" && state.tokens.next.value === "{") {
+ bracket = state.tokens.next;
+ bracket.left = destructuringExpression();
+ brackets.push(bracket);
+ for (var t in bracket.left) {
+ exprs.push(bracket.left[t].token);
+ }
+ } else {
+ exprs.push(expression(5));
+ }
+ if (state.tokens.next.id !== ",") {
+ break;
+ }
+ comma();
+ }
+ }
+
+ advance(")", this);
+ nospace(state.tokens.prev, state.tokens.curr);
+ if (state.option.immed && exprs[0] && exprs[0].id === "function") {
+ if (state.tokens.next.id !== "(" &&
+ (state.tokens.next.id !== "." || (peek().value !== "call" && peek().value !== "apply"))) {
+ warning("W068", this);
+ }
+ }
+
+ if (state.tokens.next.value === "=>") {
+ return exprs;
+ }
+ if (!exprs.length) {
+ return;
+ }
+ exprs[exprs.length - 1].paren = true;
+ if (exprs.length > 1) {
+ return Object.create(state.syntax[","], { exprs: { value: exprs } });
+ }
+ return exprs[0];
+ });
+
+ application("=>");
+
+ infix("[", function (left, that) {
+ nobreak(state.tokens.prev, state.tokens.curr);
+ nospace();
+ var e = expression(5), s;
+ if (e && e.type === "(string)") {
+ if (!state.option.evil && (e.value === "eval" || e.value === "execScript")) {
+ warning("W061", that);
+ }
+
+ countMember(e.value);
+ if (!state.option.sub && reg.identifier.test(e.value)) {
+ s = state.syntax[e.value];
+ if (!s || !isReserved(s)) {
+ warning("W069", state.tokens.prev, e.value);
+ }
+ }
+ }
+ advance("]", that);
+
+ if (e && e.value === "hasOwnProperty" && state.tokens.next.value === "=") {
+ warning("W001");
+ }
+
+ nospace(state.tokens.prev, state.tokens.curr);
+ that.left = left;
+ that.right = e;
+ return that;
+ }, 160, true);
+
+ function comprehensiveArrayExpression() {
+ var res = {};
+ res.exps = true;
+ funct["(comparray)"].stack();
+
+ res.right = expression(5);
+ advance("for");
+ if (state.tokens.next.value === "each") {
+ advance("each");
+ if (!state.option.inMoz(true)) {
+ warning("W118", state.tokens.curr, "for each");
+ }
+ }
+ advance("(");
+ funct["(comparray)"].setState("define");
+ res.left = expression(5);
+ advance(")");
+ if (state.tokens.next.value === "if") {
+ advance("if");
+ advance("(");
+ funct["(comparray)"].setState("filter");
+ res.filter = expression(5);
+ advance(")");
+ }
+ advance("]");
+ funct["(comparray)"].unstack();
+ return res;
+ }
+
+ prefix("[", function () {
+ var blocktype = lookupBlockType(true);
+ if (blocktype.isCompArray) {
+ if (!state.option.inMoz(true)) {
+ warning("W118", state.tokens.curr, "array comprehension");
+ }
+ return comprehensiveArrayExpression();
+ } else if (blocktype.isDestAssign && !state.option.inESNext()) {
+ warning("W104", state.tokens.curr, "destructuring assignment");
+ }
+ var b = state.tokens.curr.line !== state.tokens.next.line;
+ this.first = [];
+ if (b) {
+ indent += state.option.indent;
+ if (state.tokens.next.from === indent + state.option.indent) {
+ indent += state.option.indent;
+ }
+ }
+ while (state.tokens.next.id !== "(end)") {
+ while (state.tokens.next.id === ",") {
+ if (!state.option.inES5())
+ warning("W070");
+ advance(",");
+ }
+ if (state.tokens.next.id === "]") {
+ break;
+ }
+ if (b && state.tokens.curr.line !== state.tokens.next.line) {
+ indentation();
+ }
+ this.first.push(expression(10));
+ if (state.tokens.next.id === ",") {
+ comma({ allowTrailing: true });
+ if (state.tokens.next.id === "]" && !state.option.inES5(true)) {
+ warning("W070", state.tokens.curr);
+ break;
+ }
+ } else {
+ break;
+ }
+ }
+ if (b) {
+ indent -= state.option.indent;
+ indentation();
+ }
+ advance("]", this);
+ return this;
+ }, 160);
+
+
+ function property_name() {
+ var id = optionalidentifier(false, true);
+
+ if (!id) {
+ if (state.tokens.next.id === "(string)") {
+ id = state.tokens.next.value;
+ advance();
+ } else if (state.tokens.next.id === "(number)") {
+ id = state.tokens.next.value.toString();
+ advance();
+ }
+ }
+
+ if (id === "hasOwnProperty") {
+ warning("W001");
+ }
+
+ return id;
+ }
+
+
+ function functionparams(parsed) {
+ var curr, next;
+ var params = [];
+ var ident;
+ var tokens = [];
+ var t;
+
+ if (parsed) {
+ if (parsed instanceof Array) {
+ for (var i in parsed) {
+ curr = parsed[i];
+ if (_.contains(["{", "["], curr.id)) {
+ for (t in curr.left) {
+ t = tokens[t];
+ if (t.id) {
+ params.push(t.id);
+ addlabel(t.id, "unused", t.token);
+ }
+ }
+ } else if (curr.value === "...") {
+ if (!state.option.inESNext()) {
+ warning("W104", curr, "spread/rest operator");
+ }
+ continue;
+ } else {
+ addlabel(curr.value, "unused", curr);
+ }
+ }
+ return params;
+ } else {
+ if (parsed.identifier === true) {
+ addlabel(parsed.value, "unused", parsed);
+ return [parsed];
+ }
+ }
+ }
+
+ next = state.tokens.next;
+
+ advance("(");
+ nospace();
+
+ if (state.tokens.next.id === ")") {
+ advance(")");
+ return;
+ }
+
+ for (;;) {
+ if (_.contains(["{", "["], state.tokens.next.id)) {
+ tokens = destructuringExpression();
+ for (t in tokens) {
+ t = tokens[t];
+ if (t.id) {
+ params.push(t.id);
+ addlabel(t.id, "unused", t.token);
+ }
+ }
+ } else if (state.tokens.next.value === "...") {
+ if (!state.option.inESNext()) {
+ warning("W104", state.tokens.next, "spread/rest operator");
+ }
+ advance("...");
+ nospace();
+ ident = identifier(true);
+ params.push(ident);
+ addlabel(ident, "unused", state.tokens.curr);
+ } else {
+ ident = identifier(true);
+ params.push(ident);
+ addlabel(ident, "unused", state.tokens.curr);
+ }
+ if (state.tokens.next.id === ",") {
+ comma();
+ } else {
+ advance(")", next);
+ nospace(state.tokens.prev, state.tokens.curr);
+ return params;
+ }
+ }
+ }
+
+
+ function doFunction(name, statement, generator, fatarrowparams) {
+ var f;
+ var oldOption = state.option;
+ var oldIgnored = state.ignored;
+ var oldScope = scope;
+
+ state.option = Object.create(state.option);
+ state.ignored = Object.create(state.ignored);
+ scope = Object.create(scope);
+
+ funct = {
+ "(name)" : name || "\"" + anonname + "\"",
+ "(line)" : state.tokens.next.line,
+ "(character)" : state.tokens.next.character,
+ "(context)" : funct,
+ "(breakage)" : 0,
+ "(loopage)" : 0,
+ "(metrics)" : createMetrics(state.tokens.next),
+ "(scope)" : scope,
+ "(statement)" : statement,
+ "(tokens)" : {},
+ "(blockscope)": funct["(blockscope)"],
+ "(comparray)" : funct["(comparray)"]
+ };
+
+ if (generator) {
+ funct["(generator)"] = true;
+ }
+
+ f = funct;
+ state.tokens.curr.funct = funct;
+
+ functions.push(funct);
+
+ if (name) {
+ addlabel(name, "function");
+ }
+
+ funct["(params)"] = functionparams(fatarrowparams);
+
+ funct["(metrics)"].verifyMaxParametersPerFunction(funct["(params)"]);
+
+ block(false, true, true, fatarrowparams ? true:false);
+
+ if (generator && funct["(generator)"] !== "yielded") {
+ error("E047", state.tokens.curr);
+ }
+
+ funct["(metrics)"].verifyMaxStatementsPerFunction();
+ funct["(metrics)"].verifyMaxComplexityPerFunction();
+ funct["(unusedOption)"] = state.option.unused;
+
+ scope = oldScope;
+ state.option = oldOption;
+ state.ignored = oldIgnored;
+ funct["(last)"] = state.tokens.curr.line;
+ funct["(lastcharacter)"] = state.tokens.curr.character;
+ funct = funct["(context)"];
+
+ return f;
+ }
+
+ function createMetrics(functionStartToken) {
+ return {
+ statementCount: 0,
+ nestedBlockDepth: -1,
+ ComplexityCount: 1,
+ verifyMaxStatementsPerFunction: function () {
+ if (state.option.maxstatements &&
+ this.statementCount > state.option.maxstatements) {
+ warning("W071", functionStartToken, this.statementCount);
+ }
+ },
+
+ verifyMaxParametersPerFunction: function (params) {
+ params = params || [];
+
+ if (state.option.maxparams && params.length > state.option.maxparams) {
+ warning("W072", functionStartToken, params.length);
+ }
+ },
+
+ verifyMaxNestedBlockDepthPerFunction: function () {
+ if (state.option.maxdepth &&
+ this.nestedBlockDepth > 0 &&
+ this.nestedBlockDepth === state.option.maxdepth + 1) {
+ warning("W073", null, this.nestedBlockDepth);
+ }
+ },
+
+ verifyMaxComplexityPerFunction: function () {
+ var max = state.option.maxcomplexity;
+ var cc = this.ComplexityCount;
+ if (max && cc > max) {
+ warning("W074", functionStartToken, cc);
+ }
+ }
+ };
+ }
+
+ function increaseComplexityCount() {
+ funct["(metrics)"].ComplexityCount += 1;
+ }
+
+ // Parse assignments that were found instead of conditionals.
+ // For example: if (a = 1) { ... }
+
+ function checkCondAssignment(expr) {
+ var id = expr.id;
+ if (id === ",") {
+ expr = expr.exprs[expr.exprs.length - 1];
+ id = expr.id;
+ }
+ switch (id) {
+ case "=":
+ case "+=":
+ case "-=":
+ case "*=":
+ case "%=":
+ case "&=":
+ case "|=":
+ case "^=":
+ case "/=":
+ if (!expr.paren && !state.option.boss) {
+ warning("W084");
+ }
+ }
+ }
+
+
+ (function (x) {
+ x.nud = function (isclassdef) {
+ var b, f, i, p, t, g;
+ var props = {}; // All properties, including accessors
+ var tag = "";
+
+ function saveProperty(name, tkn) {
+ if (props[name] && _.has(props, name))
+ warning("W075", state.tokens.next, i);
+ else
+ props[name] = {};
+
+ props[name].basic = true;
+ props[name].basictkn = tkn;
+ }
+
+ function saveSetter(name, tkn) {
+ if (props[name] && _.has(props, name)) {
+ if (props[name].basic || props[name].setter)
+ warning("W075", state.tokens.next, i);
+ } else {
+ props[name] = {};
+ }
+
+ props[name].setter = true;
+ props[name].setterToken = tkn;
+ }
+
+ function saveGetter(name) {
+ if (props[name] && _.has(props, name)) {
+ if (props[name].basic || props[name].getter)
+ warning("W075", state.tokens.next, i);
+ } else {
+ props[name] = {};
+ }
+
+ props[name].getter = true;
+ props[name].getterToken = state.tokens.curr;
+ }
+
+ b = state.tokens.curr.line !== state.tokens.next.line;
+ if (b) {
+ indent += state.option.indent;
+ if (state.tokens.next.from === indent + state.option.indent) {
+ indent += state.option.indent;
+ }
+ }
+
+ for (;;) {
+ if (state.tokens.next.id === "}") {
+ break;
+ }
+
+ if (b) {
+ indentation();
+ }
+
+ if (isclassdef && state.tokens.next.value === "static") {
+ advance("static");
+ tag = "static ";
+ }
+
+ if (state.tokens.next.value === "get" && peek().id !== ":") {
+ advance("get");
+
+ if (!state.option.inES5(!isclassdef)) {
+ error("E034");
+ }
+
+ i = property_name();
+ if (!i) {
+ error("E035");
+ }
+
+ // It is a Syntax Error if PropName of MethodDefinition is
+ // "constructor" and SpecialMethod of MethodDefinition is true.
+ if (isclassdef && i === "constructor") {
+ error("E049", state.tokens.next, "class getter method", i);
+ }
+
+ saveGetter(tag + i);
+ t = state.tokens.next;
+ adjacent(state.tokens.curr, state.tokens.next);
+ f = doFunction();
+ p = f["(params)"];
+
+ if (p) {
+ warning("W076", t, p[0], i);
+ }
+
+ adjacent(state.tokens.curr, state.tokens.next);
+ } else if (state.tokens.next.value === "set" && peek().id !== ":") {
+ advance("set");
+
+ if (!state.option.inES5(!isclassdef)) {
+ error("E034");
+ }
+
+ i = property_name();
+ if (!i) {
+ error("E035");
+ }
+
+ // It is a Syntax Error if PropName of MethodDefinition is
+ // "constructor" and SpecialMethod of MethodDefinition is true.
+ if (isclassdef && i === "constructor") {
+ error("E049", state.tokens.next, "class setter method", i);
+ }
+
+ saveSetter(tag + i, state.tokens.next);
+ t = state.tokens.next;
+ adjacent(state.tokens.curr, state.tokens.next);
+ f = doFunction();
+ p = f["(params)"];
+
+ if (!p || p.length !== 1) {
+ warning("W077", t, i);
+ }
+ } else {
+ g = false;
+ if (state.tokens.next.value === "*" && state.tokens.next.type === "(punctuator)") {
+ if (!state.option.inESNext()) {
+ warning("W104", state.tokens.next, "generator functions");
+ }
+ advance("*");
+ g = true;
+ }
+ i = property_name();
+ saveProperty(tag + i, state.tokens.next);
+
+ if (typeof i !== "string") {
+ break;
+ }
+
+ if (state.tokens.next.value === "(") {
+ if (!state.option.inESNext()) {
+ warning("W104", state.tokens.curr, "concise methods");
+ }
+ doFunction(i, undefined, g);
+ } else if (!isclassdef) {
+ advance(":");
+ nonadjacent(state.tokens.curr, state.tokens.next);
+ expression(10);
+ }
+ }
+ // It is a Syntax Error if PropName of MethodDefinition is "prototype".
+ if (isclassdef && i === "prototype") {
+ error("E049", state.tokens.next, "class method", i);
+ }
+
+ countMember(i);
+ if (isclassdef) {
+ tag = "";
+ continue;
+ }
+ if (state.tokens.next.id === ",") {
+ comma({ allowTrailing: true, property: true });
+ if (state.tokens.next.id === ",") {
+ warning("W070", state.tokens.curr);
+ } else if (state.tokens.next.id === "}" && !state.option.inES5(true)) {
+ warning("W070", state.tokens.curr);
+ }
+ } else {
+ break;
+ }
+ }
+ if (b) {
+ indent -= state.option.indent;
+ indentation();
+ }
+ advance("}", this);
+
+ // Check for lonely setters if in the ES5 mode.
+ if (state.option.inES5()) {
+ for (var name in props) {
+ if (_.has(props, name) && props[name].setter && !props[name].getter) {
+ warning("W078", props[name].setterToken);
+ }
+ }
+ }
+ return this;
+ };
+ x.fud = function () {
+ error("E036", state.tokens.curr);
+ };
+ }(delim("{")));
+
+ function destructuringExpression() {
+ var id, ids;
+ var identifiers = [];
+ if (!state.option.inESNext()) {
+ warning("W104", state.tokens.curr, "destructuring expression");
+ }
+ var nextInnerDE = function () {
+ var ident;
+ if (_.contains(["[", "{"], state.tokens.next.value)) {
+ ids = destructuringExpression();
+ for (var id in ids) {
+ id = ids[id];
+ identifiers.push({ id: id.id, token: id.token });
+ }
+ } else if (state.tokens.next.value === ",") {
+ identifiers.push({ id: null, token: state.tokens.curr });
+ } else {
+ ident = identifier();
+ if (ident)
+ identifiers.push({ id: ident, token: state.tokens.curr });
+ }
+ };
+ if (state.tokens.next.value === "[") {
+ advance("[");
+ nextInnerDE();
+ while (state.tokens.next.value !== "]") {
+ advance(",");
+ nextInnerDE();
+ }
+ advance("]");
+ } else if (state.tokens.next.value === "{") {
+ advance("{");
+ id = identifier();
+ if (state.tokens.next.value === ":") {
+ advance(":");
+ nextInnerDE();
+ } else {
+ identifiers.push({ id: id, token: state.tokens.curr });
+ }
+ while (state.tokens.next.value !== "}") {
+ advance(",");
+ id = identifier();
+ if (state.tokens.next.value === ":") {
+ advance(":");
+ nextInnerDE();
+ } else {
+ identifiers.push({ id: id, token: state.tokens.curr });
+ }
+ }
+ advance("}");
+ }
+ return identifiers;
+ }
+ function destructuringExpressionMatch(tokens, value) {
+ if (value.first) {
+ _.zip(tokens, value.first).forEach(function (val) {
+ var token = val[0];
+ var value = val[1];
+ if (token && value) {
+ token.first = value;
+ } else if (token && token.first && !value) {
+ warning("W080", token.first, token.first.value);
+ } /* else {
+ XXX value is discarded: wouldn't it need a warning ?
+ } */
+ });
+ }
+ }
+
+ var conststatement = stmt("const", function (prefix) {
+ var tokens, value;
+ // state variable to know if it is a lone identifier, or a destructuring statement.
+ var lone;
+
+ if (!state.option.inESNext()) {
+ warning("W104", state.tokens.curr, "const");
+ }
+
+ this.first = [];
+ for (;;) {
+ var names = [];
+ nonadjacent(state.tokens.curr, state.tokens.next);
+ if (_.contains(["{", "["], state.tokens.next.value)) {
+ tokens = destructuringExpression();
+ lone = false;
+ } else {
+ tokens = [ { id: identifier(), token: state.tokens.curr } ];
+ lone = true;
+ }
+ for (var t in tokens) {
+ t = tokens[t];
+ if (funct[t.id] === "const") {
+ warning("E011", null, t.id);
+ }
+ if (funct["(global)"] && predefined[t.id] === false) {
+ warning("W079", t.token, t.id);
+ }
+ if (t.id) {
+ addlabel(t.id, "const");
+ names.push(t.token);
+ }
+ }
+ if (prefix) {
+ break;
+ }
+
+ this.first = this.first.concat(names);
+
+ if (state.tokens.next.id !== "=") {
+ warning("E012", state.tokens.curr, state.tokens.curr.value);
+ }
+
+ if (state.tokens.next.id === "=") {
+ nonadjacent(state.tokens.curr, state.tokens.next);
+ advance("=");
+ nonadjacent(state.tokens.curr, state.tokens.next);
+ if (state.tokens.next.id === "undefined") {
+ warning("W080", state.tokens.prev, state.tokens.prev.value);
+ }
+ if (peek(0).id === "=" && state.tokens.next.identifier) {
+ error("E037", state.tokens.next, state.tokens.next.value);
+ }
+ value = expression(5);
+ if (lone) {
+ tokens[0].first = value;
+ } else {
+ destructuringExpressionMatch(names, value);
+ }
+ }
+
+ if (state.tokens.next.id !== ",") {
+ break;
+ }
+ comma();
+ }
+ return this;
+ });
+ conststatement.exps = true;
+ var varstatement = stmt("var", function (prefix) {
+ // JavaScript does not have block scope. It only has function scope. So,
+ // declaring a variable in a block can have unexpected consequences.
+ var tokens, lone, value;
+
+ if (funct["(onevar)"] && state.option.onevar) {
+ warning("W081");
+ } else if (!funct["(global)"]) {
+ funct["(onevar)"] = true;
+ }
+
+ this.first = [];
+ for (;;) {
+ var names = [];
+ nonadjacent(state.tokens.curr, state.tokens.next);
+ if (_.contains(["{", "["], state.tokens.next.value)) {
+ tokens = destructuringExpression();
+ lone = false;
+ } else {
+ tokens = [ { id: identifier(), token: state.tokens.curr } ];
+ lone = true;
+ }
+ for (var t in tokens) {
+ t = tokens[t];
+ if (state.option.inESNext() && funct[t.id] === "const") {
+ warning("E011", null, t.id);
+ }
+ if (funct["(global)"] && predefined[t.id] === false) {
+ warning("W079", t.token, t.id);
+ }
+ if (t.id) {
+ addlabel(t.id, "unused", t.token);
+ names.push(t.token);
+ }
+ }
+ if (prefix) {
+ break;
+ }
+
+ this.first = this.first.concat(names);
+
+ if (state.tokens.next.id === "=") {
+ nonadjacent(state.tokens.curr, state.tokens.next);
+ advance("=");
+ nonadjacent(state.tokens.curr, state.tokens.next);
+ if (state.tokens.next.id === "undefined") {
+ warning("W080", state.tokens.prev, state.tokens.prev.value);
+ }
+ if (peek(0).id === "=" && state.tokens.next.identifier) {
+ error("E038", state.tokens.next, state.tokens.next.value);
+ }
+ value = expression(5);
+ if (lone) {
+ tokens[0].first = value;
+ } else {
+ destructuringExpressionMatch(names, value);
+ }
+ }
+
+ if (state.tokens.next.id !== ",") {
+ break;
+ }
+ comma();
+ }
+ return this;
+ });
+ varstatement.exps = true;
+ var letstatement = stmt("let", function (prefix) {
+ var tokens, lone, value, letblock;
+
+ if (!state.option.inESNext()) {
+ warning("W104", state.tokens.curr, "let");
+ }
+
+ if (state.tokens.next.value === "(") {
+ if (!state.option.inMoz(true)) {
+ warning("W118", state.tokens.next, "let block");
+ }
+ advance("(");
+ funct["(blockscope)"].stack();
+ letblock = true;
+ } else if (funct["(nolet)"]) {
+ error("E048", state.tokens.curr);
+ }
+
+ if (funct["(onevar)"] && state.option.onevar) {
+ warning("W081");
+ } else if (!funct["(global)"]) {
+ funct["(onevar)"] = true;
+ }
+
+ this.first = [];
+ for (;;) {
+ var names = [];
+ nonadjacent(state.tokens.curr, state.tokens.next);
+ if (_.contains(["{", "["], state.tokens.next.value)) {
+ tokens = destructuringExpression();
+ lone = false;
+ } else {
+ tokens = [ { id: identifier(), token: state.tokens.curr.value } ];
+ lone = true;
+ }
+ for (var t in tokens) {
+ t = tokens[t];
+ if (state.option.inESNext() && funct[t.id] === "const") {
+ warning("E011", null, t.id);
+ }
+ if (funct["(global)"] && predefined[t.id] === false) {
+ warning("W079", t.token, t.id);
+ }
+ if (t.id && !funct["(nolet)"]) {
+ addlabel(t.id, "unused", t.token, true);
+ names.push(t.token);
+ }
+ }
+ if (prefix) {
+ break;
+ }
+
+ this.first = this.first.concat(names);
+
+ if (state.tokens.next.id === "=") {
+ nonadjacent(state.tokens.curr, state.tokens.next);
+ advance("=");
+ nonadjacent(state.tokens.curr, state.tokens.next);
+ if (state.tokens.next.id === "undefined") {
+ warning("W080", state.tokens.prev, state.tokens.prev.value);
+ }
+ if (peek(0).id === "=" && state.tokens.next.identifier) {
+ error("E037", state.tokens.next, state.tokens.next.value);
+ }
+ value = expression(5);
+ if (lone) {
+ tokens[0].first = value;
+ } else {
+ destructuringExpressionMatch(names, value);
+ }
+ }
+
+ if (state.tokens.next.id !== ",") {
+ break;
+ }
+ comma();
+ }
+ if (letblock) {
+ advance(")");
+ block(true, true);
+ this.block = true;
+ funct["(blockscope)"].unstack();
+ }
+
+ return this;
+ });
+ letstatement.exps = true;
+
+ blockstmt("class", function () {
+ return classdef.call(this, true);
+ });
+
+ function classdef(stmt) {
+ /*jshint validthis:true */
+ if (!state.option.inESNext()) {
+ warning("W104", state.tokens.curr, "class");
+ }
+ if (stmt) {
+ // BindingIdentifier
+ this.name = identifier();
+ addlabel(this.name, "unused", state.tokens.curr);
+ } else if (state.tokens.next.identifier && state.tokens.next.value !== "extends") {
+ // BindingIdentifier(opt)
+ this.name = identifier();
+ }
+ classtail(this);
+ return this;
+ }
+
+ function classtail(c) {
+ var strictness = state.directive["use strict"];
+
+ // ClassHeritage(opt)
+ if (state.tokens.next.value === "extends") {
+ advance("extends");
+ c.heritage = expression(10);
+ }
+
+ // A ClassBody is always strict code.
+ state.directive["use strict"] = true;
+ advance("{");
+ // ClassBody(opt)
+ c.body = state.syntax["{"].nud(true);
+ state.directive["use strict"] = strictness;
+ }
+
+ blockstmt("function", function () {
+ var generator = false;
+ if (state.tokens.next.value === "*") {
+ advance("*");
+ if (state.option.inESNext(true)) {
+ generator = true;
+ } else {
+ warning("W119", state.tokens.curr, "function*");
+ }
+ }
+ if (inblock) {
+ warning("W082", state.tokens.curr);
+
+ }
+ var i = identifier();
+ if (funct[i] === "const") {
+ warning("E011", null, i);
+ }
+ adjacent(state.tokens.curr, state.tokens.next);
+ addlabel(i, "unction", state.tokens.curr);
+
+ doFunction(i, { statement: true }, generator);
+ if (state.tokens.next.id === "(" && state.tokens.next.line === state.tokens.curr.line) {
+ error("E039");
+ }
+ return this;
+ });
+
+ prefix("function", function () {
+ var generator = false;
+ if (state.tokens.next.value === "*") {
+ if (!state.option.inESNext()) {
+ warning("W119", state.tokens.curr, "function*");
+ }
+ advance("*");
+ generator = true;
+ }
+ var i = optionalidentifier();
+ if (i || state.option.gcl) {
+ adjacent(state.tokens.curr, state.tokens.next);
+ } else {
+ nonadjacent(state.tokens.curr, state.tokens.next);
+ }
+ doFunction(i, undefined, generator);
+ if (!state.option.loopfunc && funct["(loopage)"]) {
+ warning("W083");
+ }
+ return this;
+ });
+
+ blockstmt("if", function () {
+ var t = state.tokens.next;
+ increaseComplexityCount();
+ state.condition = true;
+ advance("(");
+ nonadjacent(this, t);
+ nospace();
+ checkCondAssignment(expression(0));
+ advance(")", t);
+ state.condition = false;
+ nospace(state.tokens.prev, state.tokens.curr);
+ block(true, true);
+ if (state.tokens.next.id === "else") {
+ nonadjacent(state.tokens.curr, state.tokens.next);
+ advance("else");
+ if (state.tokens.next.id === "if" || state.tokens.next.id === "switch") {
+ statement(true);
+ } else {
+ block(true, true);
+ }
+ }
+ return this;
+ });
+
+ blockstmt("try", function () {
+ var b;
+
+ function doCatch() {
+ var oldScope = scope;
+ var e;
+
+ advance("catch");
+ nonadjacent(state.tokens.curr, state.tokens.next);
+ advance("(");
+
+ scope = Object.create(oldScope);
+
+ e = state.tokens.next.value;
+ if (state.tokens.next.type !== "(identifier)") {
+ e = null;
+ warning("E030", state.tokens.next, e);
+ }
+
+ advance();
+
+ funct = {
+ "(name)" : "(catch)",
+ "(line)" : state.tokens.next.line,
+ "(character)": state.tokens.next.character,
+ "(context)" : funct,
+ "(breakage)" : funct["(breakage)"],
+ "(loopage)" : funct["(loopage)"],
+ "(scope)" : scope,
+ "(statement)": false,
+ "(metrics)" : createMetrics(state.tokens.next),
+ "(catch)" : true,
+ "(tokens)" : {},
+ "(blockscope)": funct["(blockscope)"],
+ "(comparray)": funct["(comparray)"]
+ };
+
+ if (e) {
+ addlabel(e, "exception");
+ }
+
+ if (state.tokens.next.value === "if") {
+ if (!state.option.inMoz(true)) {
+ warning("W118", state.tokens.curr, "catch filter");
+ }
+ advance("if");
+ expression(0);
+ }
+
+ advance(")");
+
+ state.tokens.curr.funct = funct;
+ functions.push(funct);
+
+ block(false);
+
+ scope = oldScope;
+
+ funct["(last)"] = state.tokens.curr.line;
+ funct["(lastcharacter)"] = state.tokens.curr.character;
+ funct = funct["(context)"];
+ }
+
+ block(false);
+
+ while (state.tokens.next.id === "catch") {
+ increaseComplexityCount();
+ if (b && (!state.option.inMoz(true))) {
+ warning("W118", state.tokens.next, "multiple catch blocks");
+ }
+ doCatch();
+ b = true;
+ }
+
+ if (state.tokens.next.id === "finally") {
+ advance("finally");
+ block(false);
+ return;
+ }
+
+ if (!b) {
+ error("E021", state.tokens.next, "catch", state.tokens.next.value);
+ }
+
+ return this;
+ });
+
+ blockstmt("while", function () {
+ var t = state.tokens.next;
+ funct["(breakage)"] += 1;
+ funct["(loopage)"] += 1;
+ increaseComplexityCount();
+ advance("(");
+ nonadjacent(this, t);
+ nospace();
+ checkCondAssignment(expression(0));
+ advance(")", t);
+ nospace(state.tokens.prev, state.tokens.curr);
+ block(true, true);
+ funct["(breakage)"] -= 1;
+ funct["(loopage)"] -= 1;
+ return this;
+ }).labelled = true;
+
+ blockstmt("with", function () {
+ var t = state.tokens.next;
+ if (state.directive["use strict"]) {
+ error("E010", state.tokens.curr);
+ } else if (!state.option.withstmt) {
+ warning("W085", state.tokens.curr);
+ }
+
+ advance("(");
+ nonadjacent(this, t);
+ nospace();
+ expression(0);
+ advance(")", t);
+ nospace(state.tokens.prev, state.tokens.curr);
+ block(true, true);
+
+ return this;
+ });
+
+ blockstmt("switch", function () {
+ var t = state.tokens.next,
+ g = false;
+ funct["(breakage)"] += 1;
+ advance("(");
+ nonadjacent(this, t);
+ nospace();
+ checkCondAssignment(expression(0));
+ advance(")", t);
+ nospace(state.tokens.prev, state.tokens.curr);
+ nonadjacent(state.tokens.curr, state.tokens.next);
+ t = state.tokens.next;
+ advance("{");
+ nonadjacent(state.tokens.curr, state.tokens.next);
+ indent += state.option.indent;
+ this.cases = [];
+
+ for (;;) {
+ switch (state.tokens.next.id) {
+ case "case":
+ switch (funct["(verb)"]) {
+ case "yield":
+ case "break":
+ case "case":
+ case "continue":
+ case "return":
+ case "switch":
+ case "throw":
+ break;
+ default:
+ // You can tell JSHint that you don't use break intentionally by
+ // adding a comment /* falls through */ on a line just before
+ // the next `case`.
+ if (!reg.fallsThrough.test(state.lines[state.tokens.next.line - 2])) {
+ warning("W086", state.tokens.curr, "case");
+ }
+ }
+ indentation(-state.option.indent);
+ advance("case");
+ this.cases.push(expression(20));
+ increaseComplexityCount();
+ g = true;
+ advance(":");
+ funct["(verb)"] = "case";
+ break;
+ case "default":
+ switch (funct["(verb)"]) {
+ case "yield":
+ case "break":
+ case "continue":
+ case "return":
+ case "throw":
+ break;
+ default:
+ // Do not display a warning if 'default' is the first statement or if
+ // there is a special /* falls through */ comment.
+ if (this.cases.length) {
+ if (!reg.fallsThrough.test(state.lines[state.tokens.next.line - 2])) {
+ warning("W086", state.tokens.curr, "default");
+ }
+ }
+ }
+ indentation(-state.option.indent);
+ advance("default");
+ g = true;
+ advance(":");
+ break;
+ case "}":
+ indent -= state.option.indent;
+ indentation();
+ advance("}", t);
+ funct["(breakage)"] -= 1;
+ funct["(verb)"] = undefined;
+ return;
+ case "(end)":
+ error("E023", state.tokens.next, "}");
+ return;
+ default:
+ if (g) {
+ switch (state.tokens.curr.id) {
+ case ",":
+ error("E040");
+ return;
+ case ":":
+ g = false;
+ statements();
+ break;
+ default:
+ error("E025", state.tokens.curr);
+ return;
+ }
+ } else {
+ if (state.tokens.curr.id === ":") {
+ advance(":");
+ error("E024", state.tokens.curr, ":");
+ statements();
+ } else {
+ error("E021", state.tokens.next, "case", state.tokens.next.value);
+ return;
+ }
+ }
+ }
+ }
+ }).labelled = true;
+
+ stmt("debugger", function () {
+ if (!state.option.debug) {
+ warning("W087");
+ }
+ return this;
+ }).exps = true;
+
+ (function () {
+ var x = stmt("do", function () {
+ funct["(breakage)"] += 1;
+ funct["(loopage)"] += 1;
+ increaseComplexityCount();
+
+ this.first = block(true, true);
+ advance("while");
+ var t = state.tokens.next;
+ nonadjacent(state.tokens.curr, t);
+ advance("(");
+ nospace();
+ checkCondAssignment(expression(0));
+ advance(")", t);
+ nospace(state.tokens.prev, state.tokens.curr);
+ funct["(breakage)"] -= 1;
+ funct["(loopage)"] -= 1;
+ return this;
+ });
+ x.labelled = true;
+ x.exps = true;
+ }());
+
+ blockstmt("for", function () {
+ var s, t = state.tokens.next;
+ var letscope = false;
+ var foreachtok = null;
+
+ if (t.value === "each") {
+ foreachtok = t;
+ advance("each");
+ if (!state.option.inMoz(true)) {
+ warning("W118", state.tokens.curr, "for each");
+ }
+ }
+
+ funct["(breakage)"] += 1;
+ funct["(loopage)"] += 1;
+ increaseComplexityCount();
+ advance("(");
+ nonadjacent(this, t);
+ nospace();
+
+ // what kind of for(…) statement it is? for(…of…)? for(…in…)? for(…;…;…)?
+ var nextop; // contains the token of the "in" or "of" operator
+ var i = 0;
+ var inof = ["in", "of"];
+ do {
+ nextop = peek(i);
+ ++i;
+ } while (!_.contains(inof, nextop.value) && nextop.value !== ";" &&
+ nextop.type !== "(end)");
+
+ // if we're in a for (… in|of …) statement
+ if (_.contains(inof, nextop.value)) {
+ if (!state.option.inESNext() && nextop.value === "of") {
+ error("W104", nextop, "for of");
+ }
+ if (state.tokens.next.id === "var") {
+ advance("var");
+ state.syntax["var"].fud.call(state.syntax["var"].fud, true);
+ } else if (state.tokens.next.id === "let") {
+ advance("let");
+ // create a new block scope
+ letscope = true;
+ funct["(blockscope)"].stack();
+ state.syntax["let"].fud.call(state.syntax["let"].fud, true);
+ } else {
+ switch (funct[state.tokens.next.value]) {
+ case "unused":
+ funct[state.tokens.next.value] = "var";
+ break;
+ case "var":
+ break;
+ default:
+ if (!funct["(blockscope)"].getlabel(state.tokens.next.value))
+ warning("W088", state.tokens.next, state.tokens.next.value);
+ }
+ advance();
+ }
+ advance(nextop.value);
+ expression(20);
+ advance(")", t);
+ s = block(true, true);
+ if (state.option.forin && s && (s.length > 1 || typeof s[0] !== "object" ||
+ s[0].value !== "if")) {
+ warning("W089", this);
+ }
+ funct["(breakage)"] -= 1;
+ funct["(loopage)"] -= 1;
+ } else {
+ if (foreachtok) {
+ error("E045", foreachtok);
+ }
+ if (state.tokens.next.id !== ";") {
+ if (state.tokens.next.id === "var") {
+ advance("var");
+ state.syntax["var"].fud.call(state.syntax["var"].fud);
+ } else if (state.tokens.next.id === "let") {
+ advance("let");
+ // create a new block scope
+ letscope = true;
+ funct["(blockscope)"].stack();
+ state.syntax["let"].fud.call(state.syntax["let"].fud);
+ } else {
+ for (;;) {
+ expression(0, "for");
+ if (state.tokens.next.id !== ",") {
+ break;
+ }
+ comma();
+ }
+ }
+ }
+ nolinebreak(state.tokens.curr);
+ advance(";");
+ if (state.tokens.next.id !== ";") {
+ checkCondAssignment(expression(0));
+ }
+ nolinebreak(state.tokens.curr);
+ advance(";");
+ if (state.tokens.next.id === ";") {
+ error("E021", state.tokens.next, ")", ";");
+ }
+ if (state.tokens.next.id !== ")") {
+ for (;;) {
+ expression(0, "for");
+ if (state.tokens.next.id !== ",") {
+ break;
+ }
+ comma();
+ }
+ }
+ advance(")", t);
+ nospace(state.tokens.prev, state.tokens.curr);
+ block(true, true);
+ funct["(breakage)"] -= 1;
+ funct["(loopage)"] -= 1;
+
+ }
+ // unstack loop blockscope
+ if (letscope) {
+ funct["(blockscope)"].unstack();
+ }
+ return this;
+ }).labelled = true;
+
+
+ stmt("break", function () {
+ var v = state.tokens.next.value;
+
+ if (funct["(breakage)"] === 0)
+ warning("W052", state.tokens.next, this.value);
+
+ if (!state.option.asi)
+ nolinebreak(this);
+
+ if (state.tokens.next.id !== ";") {
+ if (state.tokens.curr.line === state.tokens.next.line) {
+ if (funct[v] !== "label") {
+ warning("W090", state.tokens.next, v);
+ } else if (scope[v] !== funct) {
+ warning("W091", state.tokens.next, v);
+ }
+ this.first = state.tokens.next;
+ advance();
+ }
+ }
+ reachable("break");
+ return this;
+ }).exps = true;
+
+
+ stmt("continue", function () {
+ var v = state.tokens.next.value;
+
+ if (funct["(breakage)"] === 0)
+ warning("W052", state.tokens.next, this.value);
+
+ if (!state.option.asi)
+ nolinebreak(this);
+
+ if (state.tokens.next.id !== ";") {
+ if (state.tokens.curr.line === state.tokens.next.line) {
+ if (funct[v] !== "label") {
+ warning("W090", state.tokens.next, v);
+ } else if (scope[v] !== funct) {
+ warning("W091", state.tokens.next, v);
+ }
+ this.first = state.tokens.next;
+ advance();
+ }
+ } else if (!funct["(loopage)"]) {
+ warning("W052", state.tokens.next, this.value);
+ }
+ reachable("continue");
+ return this;
+ }).exps = true;
+
+
+ stmt("return", function () {
+ if (this.line === state.tokens.next.line) {
+ if (state.tokens.next.id === "(regexp)")
+ warning("W092");
+
+ if (state.tokens.next.id !== ";" && !state.tokens.next.reach) {
+ nonadjacent(state.tokens.curr, state.tokens.next);
+ this.first = expression(0);
+
+ if (this.first &&
+ this.first.type === "(punctuator)" && this.first.value === "=" && !state.option.boss) {
+ warningAt("W093", this.first.line, this.first.character);
+ }
+ }
+ } else {
+ if (state.tokens.next.type === "(punctuator)" &&
+ ["[", "{", "+", "-"].indexOf(state.tokens.next.value) > -1) {
+ nolinebreak(this); // always warn (Line breaking error)
+ }
+ }
+ reachable("return");
+ return this;
+ }).exps = true;
+
+ stmt("yield", function () {
+ if (state.option.inESNext(true) && funct["(generator)"] !== true) {
+ error("E046", state.tokens.curr, "yield");
+ } else if (!state.option.inESNext()) {
+ warning("W104", state.tokens.curr, "yield");
+ }
+ funct["(generator)"] = "yielded";
+ if (this.line === state.tokens.next.line) {
+ if (state.tokens.next.id === "(regexp)")
+ warning("W092");
+
+ if (state.tokens.next.id !== ";" && !state.tokens.next.reach) {
+ nonadjacent(state.tokens.curr, state.tokens.next);
+ this.first = expression(0);
+
+ if (this.first.type === "(punctuator)" && this.first.value === "=" && !state.option.boss) {
+ warningAt("W093", this.first.line, this.first.character);
+ }
+ }
+ } else if (!state.option.asi) {
+ nolinebreak(this); // always warn (Line breaking error)
+ }
+ return this;
+ }).exps = true;
+
+
+ stmt("throw", function () {
+ nolinebreak(this);
+ nonadjacent(state.tokens.curr, state.tokens.next);
+ this.first = expression(20);
+ reachable("throw");
+ return this;
+ }).exps = true;
+
+ // Future Reserved Words
+
+ FutureReservedWord("abstract");
+ FutureReservedWord("boolean");
+ FutureReservedWord("byte");
+ FutureReservedWord("char");
+ FutureReservedWord("class", { es5: true, nud: classdef });
+ FutureReservedWord("double");
+ FutureReservedWord("enum", { es5: true });
+ FutureReservedWord("export", { es5: true });
+ FutureReservedWord("extends", { es5: true });
+ FutureReservedWord("final");
+ FutureReservedWord("float");
+ FutureReservedWord("goto");
+ FutureReservedWord("implements", { es5: true, strictOnly: true });
+ FutureReservedWord("import", { es5: true });
+ FutureReservedWord("int");
+ FutureReservedWord("interface", { es5: true, strictOnly: true });
+ FutureReservedWord("long");
+ FutureReservedWord("native");
+ FutureReservedWord("package", { es5: true, strictOnly: true });
+ FutureReservedWord("private", { es5: true, strictOnly: true });
+ FutureReservedWord("protected", { es5: true, strictOnly: true });
+ FutureReservedWord("public", { es5: true, strictOnly: true });
+ FutureReservedWord("short");
+ FutureReservedWord("static", { es5: true, strictOnly: true });
+ FutureReservedWord("super", { es5: true });
+ FutureReservedWord("synchronized");
+ FutureReservedWord("throws");
+ FutureReservedWord("transient");
+ FutureReservedWord("volatile");
+
+ // this function is used to determine wether a squarebracket or a curlybracket
+ // expression is a comprehension array, destructuring assignment or a json value.
+
+ var lookupBlockType = function () {
+ var pn, pn1;
+ var i = 0;
+ var bracketStack = 0;
+ var ret = {};
+ if (_.contains(["[", "{"], state.tokens.curr.value))
+ bracketStack += 1;
+ if (_.contains(["[", "{"], state.tokens.next.value))
+ bracketStack += 1;
+ if (_.contains(["]", "}"], state.tokens.next.value))
+ bracketStack -= 1;
+ do {
+ pn = peek(i);
+ pn1 = peek(i + 1);
+ i = i + 1;
+ if (_.contains(["[", "{"], pn.value)) {
+ bracketStack += 1;
+ } else if (_.contains(["]", "}"], pn.value)) {
+ bracketStack -= 1;
+ }
+ if (pn.identifier && pn.value === "for" && bracketStack === 1) {
+ ret.isCompArray = true;
+ ret.notJson = true;
+ break;
+ }
+ if (_.contains(["}", "]"], pn.value) && pn1.value === "=") {
+ ret.isDestAssign = true;
+ ret.notJson = true;
+ break;
+ }
+ if (pn.value === ";") {
+ ret.isBlock = true;
+ ret.notJson = true;
+ }
+ } while (bracketStack > 0 && pn.id !== "(end)" && i < 15);
+ return ret;
+ };
+
+ // Check whether this function has been reached for a destructuring assign with undeclared values
+ function destructuringAssignOrJsonValue() {
+ // lookup for the assignment (esnext only)
+ // if it has semicolons, it is a block, so go parse it as a block
+ // or it's not a block, but there are assignments, check for undeclared variables
+
+ var block = lookupBlockType();
+ if (block.notJson) {
+ if (!state.option.inESNext() && block.isDestAssign) {
+ warning("W104", state.tokens.curr, "destructuring assignment");
+ }
+ statements();
+ // otherwise parse json value
+ } else {
+ state.option.laxbreak = true;
+ state.jsonMode = true;
+ jsonValue();
+ }
+ }
+
+ // array comprehension parsing function
+ // parses and defines the three states of the list comprehension in order
+ // to avoid defining global variables, but keeping them to the list comprehension scope
+ // only. The order of the states are as follows:
+ // * "use" which will be the returned iterative part of the list comprehension
+ // * "define" which will define the variables local to the list comprehension
+ // * "filter" which will help filter out values
+
+ var arrayComprehension = function () {
+ var CompArray = function () {
+ this.mode = "use";
+ this.variables = [];
+ };
+ var _carrays = [];
+ var _current;
+ function declare(v) {
+ var l = _current.variables.filter(function (elt) {
+ // if it has, change its undef state
+ if (elt.value === v) {
+ elt.undef = false;
+ return v;
+ }
+ }).length;
+ return l !== 0;
+ }
+ function use(v) {
+ var l = _current.variables.filter(function (elt) {
+ // and if it has been defined
+ if (elt.value === v && !elt.undef) {
+ if (elt.unused === true) {
+ elt.unused = false;
+ }
+ return v;
+ }
+ }).length;
+ // otherwise we warn about it
+ return (l === 0);
+ }
+ return {stack: function () {
+ _current = new CompArray();
+ _carrays.push(_current);
+ },
+ unstack: function () {
+ _current.variables.filter(function (v) {
+ if (v.unused)
+ warning("W098", v.token, v.value);
+ if (v.undef)
+ isundef(v.funct, "W117", v.token, v.value);
+ });
+ _carrays.splice(_carrays[_carrays.length - 1], 1);
+ _current = _carrays[_carrays.length - 1];
+ },
+ setState: function (s) {
+ if (_.contains(["use", "define", "filter"], s))
+ _current.mode = s;
+ },
+ check: function (v) {
+ // When we are in "use" state of the list comp, we enqueue that var
+ if (_current && _current.mode === "use") {
+ _current.variables.push({funct: funct,
+ token: state.tokens.curr,
+ value: v,
+ undef: true,
+ unused: false});
+ return true;
+ // When we are in "define" state of the list comp,
+ } else if (_current && _current.mode === "define") {
+ // check if the variable has been used previously
+ if (!declare(v)) {
+ _current.variables.push({funct: funct,
+ token: state.tokens.curr,
+ value: v,
+ undef: false,
+ unused: true});
+ }
+ return true;
+ // When we are in "filter" state,
+ } else if (_current && _current.mode === "filter") {
+ // we check whether current variable has been declared
+ if (use(v)) {
+ // if not we warn about it
+ isundef(funct, "W117", state.tokens.curr, v);
+ }
+ return true;
+ }
+ return false;
+ }
+ };
+ };
+
+
+ // Parse JSON
+
+ function jsonValue() {
+
+ function jsonObject() {
+ var o = {}, t = state.tokens.next;
+ advance("{");
+ if (state.tokens.next.id !== "}") {
+ for (;;) {
+ if (state.tokens.next.id === "(end)") {
+ error("E026", state.tokens.next, t.line);
+ } else if (state.tokens.next.id === "}") {
+ warning("W094", state.tokens.curr);
+ break;
+ } else if (state.tokens.next.id === ",") {
+ error("E028", state.tokens.next);
+ } else if (state.tokens.next.id !== "(string)") {
+ warning("W095", state.tokens.next, state.tokens.next.value);
+ }
+ if (o[state.tokens.next.value] === true) {
+ warning("W075", state.tokens.next, state.tokens.next.value);
+ } else if ((state.tokens.next.value === "__proto__" &&
+ !state.option.proto) || (state.tokens.next.value === "__iterator__" &&
+ !state.option.iterator)) {
+ warning("W096", state.tokens.next, state.tokens.next.value);
+ } else {
+ o[state.tokens.next.value] = true;
+ }
+ advance();
+ advance(":");
+ jsonValue();
+ if (state.tokens.next.id !== ",") {
+ break;
+ }
+ advance(",");
+ }
+ }
+ advance("}");
+ }
+
+ function jsonArray() {
+ var t = state.tokens.next;
+ advance("[");
+ if (state.tokens.next.id !== "]") {
+ for (;;) {
+ if (state.tokens.next.id === "(end)") {
+ error("E027", state.tokens.next, t.line);
+ } else if (state.tokens.next.id === "]") {
+ warning("W094", state.tokens.curr);
+ break;
+ } else if (state.tokens.next.id === ",") {
+ error("E028", state.tokens.next);
+ }
+ jsonValue();
+ if (state.tokens.next.id !== ",") {
+ break;
+ }
+ advance(",");
+ }
+ }
+ advance("]");
+ }
+
+ switch (state.tokens.next.id) {
+ case "{":
+ jsonObject();
+ break;
+ case "[":
+ jsonArray();
+ break;
+ case "true":
+ case "false":
+ case "null":
+ case "(number)":
+ case "(string)":
+ advance();
+ break;
+ case "-":
+ advance("-");
+ if (state.tokens.curr.character !== state.tokens.next.from) {
+ warning("W011", state.tokens.curr);
+ }
+ adjacent(state.tokens.curr, state.tokens.next);
+ advance("(number)");
+ break;
+ default:
+ error("E003", state.tokens.next);
+ }
+ }
+
+ var blockScope = function () {
+ var _current = {};
+ var _variables = [_current];
+
+ function _checkBlockLabels() {
+ for (var t in _current) {
+ if (_current[t]["(type)"] === "unused") {
+ if (state.option.unused) {
+ var tkn = _current[t]["(token)"];
+ var line = tkn.line;
+ var chr = tkn.character;
+ warningAt("W098", line, chr, t);
+ }
+ }
+ }
+ }
+
+ return {
+ stack: function () {
+ _current = {};
+ _variables.push(_current);
+ },
+
+ unstack: function () {
+ _checkBlockLabels();
+ _variables.splice(_variables.length - 1, 1);
+ _current = _.last(_variables);
+ },
+
+ getlabel: function (l) {
+ for (var i = _variables.length - 1 ; i >= 0; --i) {
+ if (_.has(_variables[i], l)) {
+ return _variables[i];
+ }
+ }
+ },
+
+ current: {
+ has: function (t) {
+ return _.has(_current, t);
+ },
+ add: function (t, type, tok) {
+ _current[t] = { "(type)" : type,
+ "(token)": tok };
+ }
+ }
+ };
+ };
+
+ // The actual JSHINT function itself.
+ var itself = function (s, o, g) {
+ var a, i, k, x;
+ var optionKeys;
+ var newOptionObj = {};
+ var newIgnoredObj = {};
+
+ state.reset();
+
+ if (o && o.scope) {
+ JSHINT.scope = o.scope;
+ } else {
+ JSHINT.errors = [];
+ JSHINT.undefs = [];
+ JSHINT.internals = [];
+ JSHINT.blacklist = {};
+ JSHINT.scope = "(main)";
+ }
+
+ predefined = Object.create(null);
+ combine(predefined, vars.ecmaIdentifiers);
+ combine(predefined, vars.reservedVars);
+
+ combine(predefined, g || {});
+
+ declared = Object.create(null);
+ exported = Object.create(null);
+
+ if (o) {
+ a = o.predef;
+ if (a) {
+ if (!Array.isArray(a) && typeof a === "object") {
+ a = Object.keys(a);
+ }
+
+ a.forEach(function (item) {
+ var slice, prop;
+
+ if (item[0] === "-") {
+ slice = item.slice(1);
+ JSHINT.blacklist[slice] = slice;
+ } else {
+ prop = Object.getOwnPropertyDescriptor(o.predef, item);
+ predefined[item] = prop ? prop.value : false;
+ }
+ });
+ }
+
+ optionKeys = Object.keys(o);
+ for (x = 0; x < optionKeys.length; x++) {
+ if (/^-W\d{3}$/g.test(optionKeys[x])) {
+ newIgnoredObj[optionKeys[x].slice(1)] = true;
+ } else {
+ newOptionObj[optionKeys[x]] = o[optionKeys[x]];
+
+ if (optionKeys[x] === "newcap" && o[optionKeys[x]] === false)
+ newOptionObj["(explicitNewcap)"] = true;
+
+ if (optionKeys[x] === "indent")
+ newOptionObj["(explicitIndent)"] = o[optionKeys[x]] === false ? false : true;
+ }
+ }
+ }
+
+ state.option = newOptionObj;
+ state.ignored = newIgnoredObj;
+
+ state.option.indent = state.option.indent || 4;
+ state.option.maxerr = state.option.maxerr || 50;
+
+ indent = 1;
+ global = Object.create(predefined);
+ scope = global;
+ funct = {
+ "(global)": true,
+ "(name)": "(global)",
+ "(scope)": scope,
+ "(breakage)": 0,
+ "(loopage)": 0,
+ "(tokens)": {},
+ "(metrics)": createMetrics(state.tokens.next),
+ "(blockscope)": blockScope(),
+ "(comparray)": arrayComprehension()
+ };
+ functions = [funct];
+ urls = [];
+ stack = null;
+ member = {};
+ membersOnly = null;
+ implied = {};
+ inblock = false;
+ lookahead = [];
+ warnings = 0;
+ unuseds = [];
+
+ if (!isString(s) && !Array.isArray(s)) {
+ errorAt("E004", 0);
+ return false;
+ }
+
+ api = {
+ get isJSON() {
+ return state.jsonMode;
+ },
+
+ getOption: function (name) {
+ return state.option[name] || null;
+ },
+
+ getCache: function (name) {
+ return state.cache[name];
+ },
+
+ setCache: function (name, value) {
+ state.cache[name] = value;
+ },
+
+ warn: function (code, data) {
+ warningAt.apply(null, [ code, data.line, data.char ].concat(data.data));
+ },
+
+ on: function (names, listener) {
+ names.split(" ").forEach(function (name) {
+ emitter.on(name, listener);
+ }.bind(this));
+ }
+ };
+
+ emitter.removeAllListeners();
+ (extraModules || []).forEach(function (func) {
+ func(api);
+ });
+
+ state.tokens.prev = state.tokens.curr = state.tokens.next = state.syntax["(begin)"];
+
+ lex = new Lexer(s);
+
+ lex.on("warning", function (ev) {
+ warningAt.apply(null, [ ev.code, ev.line, ev.character].concat(ev.data));
+ });
+
+ lex.on("error", function (ev) {
+ errorAt.apply(null, [ ev.code, ev.line, ev.character ].concat(ev.data));
+ });
+
+ lex.on("fatal", function (ev) {
+ quit("E041", ev.line, ev.from);
+ });
+
+ lex.on("Identifier", function (ev) {
+ emitter.emit("Identifier", ev);
+ });
+
+ lex.on("String", function (ev) {
+ emitter.emit("String", ev);
+ });
+
+ lex.on("Number", function (ev) {
+ emitter.emit("Number", ev);
+ });
+
+ lex.start();
+
+ // Check options
+ for (var name in o) {
+ if (_.has(o, name)) {
+ checkOption(name, state.tokens.curr);
+ }
+ }
+
+ assume();
+
+ // combine the passed globals after we've assumed all our options
+ combine(predefined, g || {});
+
+ //reset values
+ comma.first = true;
+
+ try {
+ advance();
+ switch (state.tokens.next.id) {
+ case "{":
+ case "[":
+ destructuringAssignOrJsonValue();
+ break;
+ default:
+ directives();
+
+ if (state.directive["use strict"]) {
+ if (!state.option.globalstrict && !state.option.node) {
+ warning("W097", state.tokens.prev);
+ }
+ }
+
+ statements();
+ }
+ advance((state.tokens.next && state.tokens.next.value !== ".") ? "(end)" : undefined);
+ funct["(blockscope)"].unstack();
+
+ var markDefined = function (name, context) {
+ do {
+ if (typeof context[name] === "string") {
+ // JSHINT marks unused variables as 'unused' and
+ // unused function declaration as 'unction'. This
+ // code changes such instances back 'var' and
+ // 'closure' so that the code in JSHINT.data()
+ // doesn't think they're unused.
+
+ if (context[name] === "unused")
+ context[name] = "var";
+ else if (context[name] === "unction")
+ context[name] = "closure";
+
+ return true;
+ }
+
+ context = context["(context)"];
+ } while (context);
+
+ return false;
+ };
+
+ var clearImplied = function (name, line) {
+ if (!implied[name])
+ return;
+
+ var newImplied = [];
+ for (var i = 0; i < implied[name].length; i += 1) {
+ if (implied[name][i] !== line)
+ newImplied.push(implied[name][i]);
+ }
+
+ if (newImplied.length === 0)
+ delete implied[name];
+ else
+ implied[name] = newImplied;
+ };
+
+ var warnUnused = function (name, tkn, type, unused_opt) {
+ var line = tkn.line;
+ var chr = tkn.character;
+
+ if (unused_opt === undefined) {
+ unused_opt = state.option.unused;
+ }
+
+ if (unused_opt === true) {
+ unused_opt = "last-param";
+ }
+
+ var warnable_types = {
+ "vars": ["var"],
+ "last-param": ["var", "param"],
+ "strict": ["var", "param", "last-param"]
+ };
+
+ if (unused_opt) {
+ if (warnable_types[unused_opt] && warnable_types[unused_opt].indexOf(type) !== -1) {
+ warningAt("W098", line, chr, name);
+ }
+ }
+
+ unuseds.push({
+ name: name,
+ line: line,
+ character: chr
+ });
+ };
+
+ var checkUnused = function (func, key) {
+ var type = func[key];
+ var tkn = func["(tokens)"][key];
+
+ if (key.charAt(0) === "(")
+ return;
+
+ if (type !== "unused" && type !== "unction")
+ return;
+
+ // Params are checked separately from other variables.
+ if (func["(params)"] && func["(params)"].indexOf(key) !== -1)
+ return;
+
+ // Variable is in global scope and defined as exported.
+ if (func["(global)"] && _.has(exported, key)) {
+ return;
+ }
+
+ warnUnused(key, tkn, "var");
+ };
+
+ // Check queued 'x is not defined' instances to see if they're still undefined.
+ for (i = 0; i < JSHINT.undefs.length; i += 1) {
+ k = JSHINT.undefs[i].slice(0);
+
+ if (markDefined(k[2].value, k[0])) {
+ clearImplied(k[2].value, k[2].line);
+ } else if (state.option.undef) {
+ warning.apply(warning, k.slice(1));
+ }
+ }
+
+ functions.forEach(function (func) {
+ if (func["(unusedOption)"] === false) {
+ return;
+ }
+
+ for (var key in func) {
+ if (_.has(func, key)) {
+ checkUnused(func, key);
+ }
+ }
+
+ if (!func["(params)"])
+ return;
+
+ var params = func["(params)"].slice();
+ var param = params.pop();
+ var type, unused_opt;
+
+ while (param) {
+ type = func[param];
+ unused_opt = func["(unusedOption)"] || state.option.unused;
+ unused_opt = unused_opt === true ? "last-param" : unused_opt;
+
+ // 'undefined' is a special case for (function (window, undefined) { ... })();
+ // patterns.
+
+ if (param === "undefined")
+ return;
+
+ if (type === "unused" || type === "unction") {
+ warnUnused(param, func["(tokens)"][param], "param", func["(unusedOption)"]);
+ } else if (unused_opt === "last-param") {
+ return;
+ }
+
+ param = params.pop();
+ }
+ });
+
+ for (var key in declared) {
+ if (_.has(declared, key) && !_.has(global, key)) {
+ warnUnused(key, declared[key], "var");
+ }
+ }
+
+ } catch (err) {
+ if (err && err.name === "JSHintError") {
+ var nt = state.tokens.next || {};
+ JSHINT.errors.push({
+ scope : "(main)",
+ raw : err.raw,
+ reason : err.message,
+ line : err.line || nt.line,
+ character : err.character || nt.from
+ }, null);
+ } else {
+ throw err;
+ }
+ }
+
+ // Loop over the listed "internals", and check them as well.
+
+ if (JSHINT.scope === "(main)") {
+ o = o || {};
+
+ for (i = 0; i < JSHINT.internals.length; i += 1) {
+ k = JSHINT.internals[i];
+ o.scope = k.elem;
+ itself(k.value, o, g);
+ }
+ }
+
+ return JSHINT.errors.length === 0;
+ };
+
+ // Modules.
+ itself.addModule = function (func) {
+ extraModules.push(func);
+ };
+
+ itself.addModule(style.register);
+
+ // Data summary.
+ itself.data = function () {
+ var data = {
+ functions: [],
+ options: state.option
+ };
+ var implieds = [];
+ var members = [];
+ var fu, f, i, j, n, globals;
+
+ if (itself.errors.length) {
+ data.errors = itself.errors;
+ }
+
+ if (state.jsonMode) {
+ data.json = true;
+ }
+
+ for (n in implied) {
+ if (_.has(implied, n)) {
+ implieds.push({
+ name: n,
+ line: implied[n]
+ });
+ }
+ }
+
+ if (implieds.length > 0) {
+ data.implieds = implieds;
+ }
+
+ if (urls.length > 0) {
+ data.urls = urls;
+ }
+
+ globals = Object.keys(scope);
+ if (globals.length > 0) {
+ data.globals = globals;
+ }
+
+ for (i = 1; i < functions.length; i += 1) {
+ f = functions[i];
+ fu = {};
+
+ for (j = 0; j < functionicity.length; j += 1) {
+ fu[functionicity[j]] = [];
+ }
+
+ for (j = 0; j < functionicity.length; j += 1) {
+ if (fu[functionicity[j]].length === 0) {
+ delete fu[functionicity[j]];
+ }
+ }
+
+ fu.name = f["(name)"];
+ fu.param = f["(params)"];
+ fu.line = f["(line)"];
+ fu.character = f["(character)"];
+ fu.last = f["(last)"];
+ fu.lastcharacter = f["(lastcharacter)"];
+ data.functions.push(fu);
+ }
+
+ if (unuseds.length > 0) {
+ data.unused = unuseds;
+ }
+
+ members = [];
+ for (n in member) {
+ if (typeof member[n] === "number") {
+ data.member = member;
+ break;
+ }
+ }
+
+ return data;
+ };
+
+ itself.jshint = itself;
+
+ return itself;
+}());
+
+// Make JSHINT a Node module, if possible.
+if (typeof exports === "object" && exports) {
+ exports.JSHINT = JSHINT;
+}
+
+})()
+},{"events":2,"../shared/vars.js":3,"./lex.js":10,"./reg.js":6,"./state.js":4,"../shared/messages.js":12,"./style.js":5,"console-browserify":7,"underscore":11}],12:[function(require,module,exports){
+(function(){"use strict";
+
+var _ = require("underscore");
+
+var errors = {
+ // JSHint options
+ E001: "Bad option: '{a}'.",
+ E002: "Bad option value.",
+
+ // JSHint input
+ E003: "Expected a JSON value.",
+ E004: "Input is neither a string nor an array of strings.",
+ E005: "Input is empty.",
+ E006: "Unexpected early end of program.",
+
+ // Strict mode
+ E007: "Missing \"use strict\" statement.",
+ E008: "Strict violation.",
+ E009: "Option 'validthis' can't be used in a global scope.",
+ E010: "'with' is not allowed in strict mode.",
+
+ // Constants
+ E011: "const '{a}' has already been declared.",
+ E012: "const '{a}' is initialized to 'undefined'.",
+ E013: "Attempting to override '{a}' which is a constant.",
+
+ // Regular expressions
+ E014: "A regular expression literal can be confused with '/='.",
+ E015: "Unclosed regular expression.",
+ E016: "Invalid regular expression.",
+
+ // Tokens
+ E017: "Unclosed comment.",
+ E018: "Unbegun comment.",
+ E019: "Unmatched '{a}'.",
+ E020: "Expected '{a}' to match '{b}' from line {c} and instead saw '{d}'.",
+ E021: "Expected '{a}' and instead saw '{b}'.",
+ E022: "Line breaking error '{a}'.",
+ E023: "Missing '{a}'.",
+ E024: "Unexpected '{a}'.",
+ E025: "Missing ':' on a case clause.",
+ E026: "Missing '}' to match '{' from line {a}.",
+ E027: "Missing ']' to match '[' form line {a}.",
+ E028: "Illegal comma.",
+ E029: "Unclosed string.",
+
+ // Everything else
+ E030: "Expected an identifier and instead saw '{a}'.",
+ E031: "Bad assignment.", // FIXME: Rephrase
+ E032: "Expected a small integer or 'false' and instead saw '{a}'.",
+ E033: "Expected an operator and instead saw '{a}'.",
+ E034: "get/set are ES5 features.",
+ E035: "Missing property name.",
+ E036: "Expected to see a statement and instead saw a block.",
+ E037: "Constant {a} was not declared correctly.",
+ E038: "Variable {a} was not declared correctly.",
+ E039: "Function declarations are not invocable. Wrap the whole function invocation in parens.",
+ E040: "Each value should have its own case label.",
+ E041: "Unrecoverable syntax error.",
+ E042: "Stopping.",
+ E043: "Too many errors.",
+ E044: "'{a}' is already defined and can't be redefined.",
+ E045: "Invalid for each loop.",
+ E046: "A yield statement shall be within a generator function (with syntax: `function*`)",
+ E047: "A generator function shall contain a yield statement.",
+ E048: "Let declaration not directly within block.",
+ E049: "A {a} cannot be named '{b}'."
+};
+
+var warnings = {
+ W001: "'hasOwnProperty' is a really bad name.",
+ W002: "Value of '{a}' may be overwritten in IE.",
+ W003: "'{a}' was used before it was defined.",
+ W004: "'{a}' is already defined.",
+ W005: "A dot following a number can be confused with a decimal point.",
+ W006: "Confusing minuses.",
+ W007: "Confusing pluses.",
+ W008: "A leading decimal point can be confused with a dot: '{a}'.",
+ W009: "The array literal notation [] is preferrable.",
+ W010: "The object literal notation {} is preferrable.",
+ W011: "Unexpected space after '{a}'.",
+ W012: "Unexpected space before '{a}'.",
+ W013: "Missing space after '{a}'.",
+ W014: "Bad line breaking before '{a}'.",
+ W015: "Expected '{a}' to have an indentation at {b} instead at {c}.",
+ W016: "Unexpected use of '{a}'.",
+ W017: "Bad operand.",
+ W018: "Confusing use of '{a}'.",
+ W019: "Use the isNaN function to compare with NaN.",
+ W020: "Read only.",
+ W021: "'{a}' is a function.",
+ W022: "Do not assign to the exception parameter.",
+ W023: "Expected an identifier in an assignment and instead saw a function invocation.",
+ W024: "Expected an identifier and instead saw '{a}' (a reserved word).",
+ W025: "Missing name in function declaration.",
+ W026: "Inner functions should be listed at the top of the outer function.",
+ W027: "Unreachable '{a}' after '{b}'.",
+ W028: "Label '{a}' on {b} statement.",
+ W030: "Expected an assignment or function call and instead saw an expression.",
+ W031: "Do not use 'new' for side effects.",
+ W032: "Unnecessary semicolon.",
+ W033: "Missing semicolon.",
+ W034: "Unnecessary directive \"{a}\".",
+ W035: "Empty block.",
+ W036: "Unexpected /*member '{a}'.",
+ W037: "'{a}' is a statement label.",
+ W038: "'{a}' used out of scope.",
+ W039: "'{a}' is not allowed.",
+ W040: "Possible strict violation.",
+ W041: "Use '{a}' to compare with '{b}'.",
+ W042: "Avoid EOL escaping.",
+ W043: "Bad escaping of EOL. Use option multistr if needed.",
+ W044: "Bad or unnecessary escaping.",
+ W045: "Bad number '{a}'.",
+ W046: "Don't use extra leading zeros '{a}'.",
+ W047: "A trailing decimal point can be confused with a dot: '{a}'.",
+ W048: "Unexpected control character in regular expression.",
+ W049: "Unexpected escaped character '{a}' in regular expression.",
+ W050: "JavaScript URL.",
+ W051: "Variables should not be deleted.",
+ W052: "Unexpected '{a}'.",
+ W053: "Do not use {a} as a constructor.",
+ W054: "The Function constructor is a form of eval.",
+ W055: "A constructor name should start with an uppercase letter.",
+ W056: "Bad constructor.",
+ W057: "Weird construction. Is 'new' unnecessary?",
+ W058: "Missing '()' invoking a constructor.",
+ W059: "Avoid arguments.{a}.",
+ W060: "document.write can be a form of eval.",
+ W061: "eval can be harmful.",
+ W062: "Wrap an immediate function invocation in parens " +
+ "to assist the reader in understanding that the expression " +
+ "is the result of a function, and not the function itself.",
+ W063: "Math is not a function.",
+ W064: "Missing 'new' prefix when invoking a constructor.",
+ W065: "Missing radix parameter.",
+ W066: "Implied eval. Consider passing a function instead of a string.",
+ W067: "Bad invocation.",
+ W068: "Wrapping non-IIFE function literals in parens is unnecessary.",
+ W069: "['{a}'] is better written in dot notation.",
+ W070: "Extra comma. (it breaks older versions of IE)",
+ W071: "This function has too many statements. ({a})",
+ W072: "This function has too many parameters. ({a})",
+ W073: "Blocks are nested too deeply. ({a})",
+ W074: "This function's cyclomatic complexity is too high. ({a})",
+ W075: "Duplicate key '{a}'.",
+ W076: "Unexpected parameter '{a}' in get {b} function.",
+ W077: "Expected a single parameter in set {a} function.",
+ W078: "Setter is defined without getter.",
+ W079: "Redefinition of '{a}'.",
+ W080: "It's not necessary to initialize '{a}' to 'undefined'.",
+ W081: "Too many var statements.",
+ W082: "Function declarations should not be placed in blocks. " +
+ "Use a function expression or move the statement to the top of " +
+ "the outer function.",
+ W083: "Don't make functions within a loop.",
+ W084: "Expected a conditional expression and instead saw an assignment.",
+ W085: "Don't use 'with'.",
+ W086: "Expected a 'break' statement before '{a}'.",
+ W087: "Forgotten 'debugger' statement?",
+ W088: "Creating global 'for' variable. Should be 'for (var {a} ...'.",
+ W089: "The body of a for in should be wrapped in an if statement to filter " +
+ "unwanted properties from the prototype.",
+ W090: "'{a}' is not a statement label.",
+ W091: "'{a}' is out of scope.",
+ W092: "Wrap the /regexp/ literal in parens to disambiguate the slash operator.",
+ W093: "Did you mean to return a conditional instead of an assignment?",
+ W094: "Unexpected comma.",
+ W095: "Expected a string and instead saw {a}.",
+ W096: "The '{a}' key may produce unexpected results.",
+ W097: "Use the function form of \"use strict\".",
+ W098: "'{a}' is defined but never used.",
+ W099: "Mixed spaces and tabs.",
+ W100: "This character may get silently deleted by one or more browsers.",
+ W101: "Line is too long.",
+ W102: "Trailing whitespace.",
+ W103: "The '{a}' property is deprecated.",
+ W104: "'{a}' is only available in JavaScript 1.7.",
+ W105: "Unexpected {a} in '{b}'.",
+ W106: "Identifier '{a}' is not in camel case.",
+ W107: "Script URL.",
+ W108: "Strings must use doublequote.",
+ W109: "Strings must use singlequote.",
+ W110: "Mixed double and single quotes.",
+ W112: "Unclosed string.",
+ W113: "Control character in string: {a}.",
+ W114: "Avoid {a}.",
+ W115: "Octal literals are not allowed in strict mode.",
+ W116: "Expected '{a}' and instead saw '{b}'.",
+ W117: "'{a}' is not defined.",
+ W118: "'{a}' is only available in Mozilla JavaScript extensions (use moz option).",
+ W119: "'{a}' is only available in ES6 (use esnext option)."
+};
+
+var info = {
+ I001: "Comma warnings can be turned off with 'laxcomma'.",
+ I002: "Reserved words as properties can be used under the 'es5' option.",
+ I003: "ES5 option is now set per default"
+};
+
+exports.errors = {};
+exports.warnings = {};
+exports.info = {};
+
+_.each(errors, function (desc, code) {
+ exports.errors[code] = { code: code, desc: desc };
+});
+
+_.each(warnings, function (desc, code) {
+ exports.warnings[code] = { code: code, desc: desc };
+});
+
+_.each(info, function (desc, code) {
+ exports.info[code] = { code: code, desc: desc };
+});
+
+})()
+},{"underscore":11}],8:[function(require,module,exports){
+var events = require('events');
+
+exports.isArray = isArray;
+exports.isDate = function(obj){return Object.prototype.toString.call(obj) === '[object Date]'};
+exports.isRegExp = function(obj){return Object.prototype.toString.call(obj) === '[object RegExp]'};
+
+
+exports.print = function () {};
+exports.puts = function () {};
+exports.debug = function() {};
+
+exports.inspect = function(obj, showHidden, depth, colors) {
+ var seen = [];
+
+ var stylize = function(str, styleType) {
+ // http://en.wikipedia.org/wiki/ANSI_escape_code#graphics
+ var styles =
+ { 'bold' : [1, 22],
+ 'italic' : [3, 23],
+ 'underline' : [4, 24],
+ 'inverse' : [7, 27],
+ 'white' : [37, 39],
+ 'grey' : [90, 39],
+ 'black' : [30, 39],
+ 'blue' : [34, 39],
+ 'cyan' : [36, 39],
+ 'green' : [32, 39],
+ 'magenta' : [35, 39],
+ 'red' : [31, 39],
+ 'yellow' : [33, 39] };
+
+ var style =
+ { 'special': 'cyan',
+ 'number': 'blue',
+ 'boolean': 'yellow',
+ 'undefined': 'grey',
+ 'null': 'bold',
+ 'string': 'green',
+ 'date': 'magenta',
+ // "name": intentionally not styling
+ 'regexp': 'red' }[styleType];
+
+ if (style) {
+ return '\033[' + styles[style][0] + 'm' + str +
+ '\033[' + styles[style][1] + 'm';
+ } else {
+ return str;
+ }
+ };
+ if (! colors) {
+ stylize = function(str, styleType) { return str; };
+ }
+
+ function format(value, recurseTimes) {
+ // Provide a hook for user-specified inspect functions.
+ // Check that value is an object with an inspect function on it
+ if (value && typeof value.inspect === 'function' &&
+ // Filter out the util module, it's inspect function is special
+ value !== exports &&
+ // Also filter out any prototype objects using the circular check.
+ !(value.constructor && value.constructor.prototype === value)) {
+ return value.inspect(recurseTimes);
+ }
+
+ // Primitive types cannot have properties
+ switch (typeof value) {
+ case 'undefined':
+ return stylize('undefined', 'undefined');
+
+ case 'string':
+ var simple = '\'' + JSON.stringify(value).replace(/^"|"$/g, '')
+ .replace(/'/g, "\\'")
+ .replace(/\\"/g, '"') + '\'';
+ return stylize(simple, 'string');
+
+ case 'number':
+ return stylize('' + value, 'number');
+
+ case 'boolean':
+ return stylize('' + value, 'boolean');
+ }
+ // For some reason typeof null is "object", so special case here.
+ if (value === null) {
+ return stylize('null', 'null');
+ }
+
+ // Look up the keys of the object.
+ var visible_keys = Object_keys(value);
+ var keys = showHidden ? Object_getOwnPropertyNames(value) : visible_keys;
+
+ // Functions without properties can be shortcutted.
+ if (typeof value === 'function' && keys.length === 0) {
+ if (isRegExp(value)) {
+ return stylize('' + value, 'regexp');
+ } else {
+ var name = value.name ? ': ' + value.name : '';
+ return stylize('[Function' + name + ']', 'special');
+ }
+ }
+
+ // Dates without properties can be shortcutted
+ if (isDate(value) && keys.length === 0) {
+ return stylize(value.toUTCString(), 'date');
+ }
+
+ var base, type, braces;
+ // Determine the object type
+ if (isArray(value)) {
+ type = 'Array';
+ braces = ['[', ']'];
+ } else {
+ type = 'Object';
+ braces = ['{', '}'];
+ }
+
+ // Make functions say that they are functions
+ if (typeof value === 'function') {
+ var n = value.name ? ': ' + value.name : '';
+ base = (isRegExp(value)) ? ' ' + value : ' [Function' + n + ']';
+ } else {
+ base = '';
+ }
+
+ // Make dates with properties first say the date
+ if (isDate(value)) {
+ base = ' ' + value.toUTCString();
+ }
+
+ if (keys.length === 0) {
+ return braces[0] + base + braces[1];
+ }
+
+ if (recurseTimes < 0) {
+ if (isRegExp(value)) {
+ return stylize('' + value, 'regexp');
+ } else {
+ return stylize('[Object]', 'special');
+ }
+ }
+
+ seen.push(value);
+
+ var output = keys.map(function(key) {
+ var name, str;
+ if (value.__lookupGetter__) {
+ if (value.__lookupGetter__(key)) {
+ if (value.__lookupSetter__(key)) {
+ str = stylize('[Getter/Setter]', 'special');
+ } else {
+ str = stylize('[Getter]', 'special');
+ }
+ } else {
+ if (value.__lookupSetter__(key)) {
+ str = stylize('[Setter]', 'special');
+ }
+ }
+ }
+ if (visible_keys.indexOf(key) < 0) {
+ name = '[' + key + ']';
+ }
+ if (!str) {
+ if (seen.indexOf(value[key]) < 0) {
+ if (recurseTimes === null) {
+ str = format(value[key]);
+ } else {
+ str = format(value[key], recurseTimes - 1);
+ }
+ if (str.indexOf('\n') > -1) {
+ if (isArray(value)) {
+ str = str.split('\n').map(function(line) {
+ return ' ' + line;
+ }).join('\n').substr(2);
+ } else {
+ str = '\n' + str.split('\n').map(function(line) {
+ return ' ' + line;
+ }).join('\n');
+ }
+ }
+ } else {
+ str = stylize('[Circular]', 'special');
+ }
+ }
+ if (typeof name === 'undefined') {
+ if (type === 'Array' && key.match(/^\d+$/)) {
+ return str;
+ }
+ name = JSON.stringify('' + key);
+ if (name.match(/^"([a-zA-Z_][a-zA-Z_0-9]*)"$/)) {
+ name = name.substr(1, name.length - 2);
+ name = stylize(name, 'name');
+ } else {
+ name = name.replace(/'/g, "\\'")
+ .replace(/\\"/g, '"')
+ .replace(/(^"|"$)/g, "'");
+ name = stylize(name, 'string');
+ }
+ }
+
+ return name + ': ' + str;
+ });
+
+ seen.pop();
+
+ var numLinesEst = 0;
+ var length = output.reduce(function(prev, cur) {
+ numLinesEst++;
+ if (cur.indexOf('\n') >= 0) numLinesEst++;
+ return prev + cur.length + 1;
+ }, 0);
+
+ if (length > 50) {
+ output = braces[0] +
+ (base === '' ? '' : base + '\n ') +
+ ' ' +
+ output.join(',\n ') +
+ ' ' +
+ braces[1];
+
+ } else {
+ output = braces[0] + base + ' ' + output.join(', ') + ' ' + braces[1];
+ }
+
+ return output;
+ }
+ return format(obj, (typeof depth === 'undefined' ? 2 : depth));
+};
+
+
+function isArray(ar) {
+ return ar instanceof Array ||
+ Array.isArray(ar) ||
+ (ar && ar !== Object.prototype && isArray(ar.__proto__));
+}
+
+
+function isRegExp(re) {
+ return re instanceof RegExp ||
+ (typeof re === 'object' && Object.prototype.toString.call(re) === '[object RegExp]');
+}
+
+
+function isDate(d) {
+ if (d instanceof Date) return true;
+ if (typeof d !== 'object') return false;
+ var properties = Date.prototype && Object_getOwnPropertyNames(Date.prototype);
+ var proto = d.__proto__ && Object_getOwnPropertyNames(d.__proto__);
+ return JSON.stringify(proto) === JSON.stringify(properties);
+}
+
+function pad(n) {
+ return n < 10 ? '0' + n.toString(10) : n.toString(10);
+}
+
+var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep',
+ 'Oct', 'Nov', 'Dec'];
+
+// 26 Feb 16:19:34
+function timestamp() {
+ var d = new Date();
+ var time = [pad(d.getHours()),
+ pad(d.getMinutes()),
+ pad(d.getSeconds())].join(':');
+ return [d.getDate(), months[d.getMonth()], time].join(' ');
+}
+
+exports.log = function (msg) {};
+
+exports.pump = null;
+
+var Object_keys = Object.keys || function (obj) {
+ var res = [];
+ for (var key in obj) res.push(key);
+ return res;
+};
+
+var Object_getOwnPropertyNames = Object.getOwnPropertyNames || function (obj) {
+ var res = [];
+ for (var key in obj) {
+ if (Object.hasOwnProperty.call(obj, key)) res.push(key);
+ }
+ return res;
+};
+
+var Object_create = Object.create || function (prototype, properties) {
+ // from es5-shim
+ var object;
+ if (prototype === null) {
+ object = { '__proto__' : null };
+ }
+ else {
+ if (typeof prototype !== 'object') {
+ throw new TypeError(
+ 'typeof prototype[' + (typeof prototype) + '] != \'object\''
+ );
+ }
+ var Type = function () {};
+ Type.prototype = prototype;
+ object = new Type();
+ object.__proto__ = prototype;
+ }
+ if (typeof properties !== 'undefined' && Object.defineProperties) {
+ Object.defineProperties(object, properties);
+ }
+ return object;
+};
+
+exports.inherits = function(ctor, superCtor) {
+ ctor.super_ = superCtor;
+ ctor.prototype = Object_create(superCtor.prototype, {
+ constructor: {
+ value: ctor,
+ enumerable: false,
+ writable: true,
+ configurable: true
+ }
+ });
+};
+
+var formatRegExp = /%[sdj%]/g;
+exports.format = function(f) {
+ if (typeof f !== 'string') {
+ var objects = [];
+ for (var i = 0; i < arguments.length; i++) {
+ objects.push(exports.inspect(arguments[i]));
+ }
+ return objects.join(' ');
+ }
+
+ var i = 1;
+ var args = arguments;
+ var len = args.length;
+ var str = String(f).replace(formatRegExp, function(x) {
+ if (x === '%%') return '%';
+ if (i >= len) return x;
+ switch (x) {
+ case '%s': return String(args[i++]);
+ case '%d': return Number(args[i++]);
+ case '%j': return JSON.stringify(args[i++]);
+ default:
+ return x;
+ }
+ });
+ for(var x = args[i]; i < len; x = args[++i]){
+ if (x === null || typeof x !== 'object') {
+ str += ' ' + x;
+ } else {
+ str += ' ' + exports.inspect(x);
+ }
+ }
+ return str;
+};
+
+},{"events":2}],9:[function(require,module,exports){
+(function(){// UTILITY
+var util = require('util');
+var Buffer = require("buffer").Buffer;
+var pSlice = Array.prototype.slice;
+
+function objectKeys(object) {
+ if (Object.keys) return Object.keys(object);
+ var result = [];
+ for (var name in object) {
+ if (Object.prototype.hasOwnProperty.call(object, name)) {
+ result.push(name);
+ }
+ }
+ return result;
+}
+
+// 1. The assert module provides functions that throw
+// AssertionError's when particular conditions are not met. The
+// assert module must conform to the following interface.
+
+var assert = module.exports = ok;
+
+// 2. The AssertionError is defined in assert.
+// new assert.AssertionError({ message: message,
+// actual: actual,
+// expected: expected })
+
+assert.AssertionError = function AssertionError(options) {
+ this.name = 'AssertionError';
+ this.message = options.message;
+ this.actual = options.actual;
+ this.expected = options.expected;
+ this.operator = options.operator;
+ var stackStartFunction = options.stackStartFunction || fail;
+
+ if (Error.captureStackTrace) {
+ Error.captureStackTrace(this, stackStartFunction);
+ }
+};
+util.inherits(assert.AssertionError, Error);
+
+function replacer(key, value) {
+ if (value === undefined) {
+ return '' + value;
+ }
+ if (typeof value === 'number' && (isNaN(value) || !isFinite(value))) {
+ return value.toString();
+ }
+ if (typeof value === 'function' || value instanceof RegExp) {
+ return value.toString();
+ }
+ return value;
+}
+
+function truncate(s, n) {
+ if (typeof s == 'string') {
+ return s.length < n ? s : s.slice(0, n);
+ } else {
+ return s;
+ }
+}
+
+assert.AssertionError.prototype.toString = function() {
+ if (this.message) {
+ return [this.name + ':', this.message].join(' ');
+ } else {
+ return [
+ this.name + ':',
+ truncate(JSON.stringify(this.actual, replacer), 128),
+ this.operator,
+ truncate(JSON.stringify(this.expected, replacer), 128)
+ ].join(' ');
+ }
+};
+
+// assert.AssertionError instanceof Error
+
+assert.AssertionError.__proto__ = Error.prototype;
+
+// At present only the three keys mentioned above are used and
+// understood by the spec. Implementations or sub modules can pass
+// other keys to the AssertionError's constructor - they will be
+// ignored.
+
+// 3. All of the following functions must throw an AssertionError
+// when a corresponding condition is not met, with a message that
+// may be undefined if not provided. All assertion methods provide
+// both the actual and expected values to the assertion error for
+// display purposes.
+
+function fail(actual, expected, message, operator, stackStartFunction) {
+ throw new assert.AssertionError({
+ message: message,
+ actual: actual,
+ expected: expected,
+ operator: operator,
+ stackStartFunction: stackStartFunction
+ });
+}
+
+// EXTENSION! allows for well behaved errors defined elsewhere.
+assert.fail = fail;
+
+// 4. Pure assertion tests whether a value is truthy, as determined
+// by !!guard.
+// assert.ok(guard, message_opt);
+// This statement is equivalent to assert.equal(true, guard,
+// message_opt);. To test strictly for the value true, use
+// assert.strictEqual(true, guard, message_opt);.
+
+function ok(value, message) {
+ if (!!!value) fail(value, true, message, '==', assert.ok);
+}
+assert.ok = ok;
+
+// 5. The equality assertion tests shallow, coercive equality with
+// ==.
+// assert.equal(actual, expected, message_opt);
+
+assert.equal = function equal(actual, expected, message) {
+ if (actual != expected) fail(actual, expected, message, '==', assert.equal);
+};
+
+// 6. The non-equality assertion tests for whether two objects are not equal
+// with != assert.notEqual(actual, expected, message_opt);
+
+assert.notEqual = function notEqual(actual, expected, message) {
+ if (actual == expected) {
+ fail(actual, expected, message, '!=', assert.notEqual);
+ }
+};
+
+// 7. The equivalence assertion tests a deep equality relation.
+// assert.deepEqual(actual, expected, message_opt);
+
+assert.deepEqual = function deepEqual(actual, expected, message) {
+ if (!_deepEqual(actual, expected)) {
+ fail(actual, expected, message, 'deepEqual', assert.deepEqual);
+ }
+};
+
+function _deepEqual(actual, expected) {
+ // 7.1. All identical values are equivalent, as determined by ===.
+ if (actual === expected) {
+ return true;
+
+ } else if (Buffer.isBuffer(actual) && Buffer.isBuffer(expected)) {
+ if (actual.length != expected.length) return false;
+
+ for (var i = 0; i < actual.length; i++) {
+ if (actual[i] !== expected[i]) return false;
+ }
+
+ return true;
+
+ // 7.2. If the expected value is a Date object, the actual value is
+ // equivalent if it is also a Date object that refers to the same time.
+ } else if (actual instanceof Date && expected instanceof Date) {
+ return actual.getTime() === expected.getTime();
+
+ // 7.3. Other pairs that do not both pass typeof value == 'object',
+ // equivalence is determined by ==.
+ } else if (typeof actual != 'object' && typeof expected != 'object') {
+ return actual == expected;
+
+ // 7.4. For all other Object pairs, including Array objects, equivalence is
+ // determined by having the same number of owned properties (as verified
+ // with Object.prototype.hasOwnProperty.call), the same set of keys
+ // (although not necessarily the same order), equivalent values for every
+ // corresponding key, and an identical 'prototype' property. Note: this
+ // accounts for both named and indexed properties on Arrays.
+ } else {
+ return objEquiv(actual, expected);
+ }
+}
+
+function isUndefinedOrNull(value) {
+ return value === null || value === undefined;
+}
+
+function isArguments(object) {
+ return Object.prototype.toString.call(object) == '[object Arguments]';
+}
+
+function objEquiv(a, b) {
+ if (isUndefinedOrNull(a) || isUndefinedOrNull(b))
+ return false;
+ // an identical 'prototype' property.
+ if (a.prototype !== b.prototype) return false;
+ //~~~I've managed to break Object.keys through screwy arguments passing.
+ // Converting to array solves the problem.
+ if (isArguments(a)) {
+ if (!isArguments(b)) {
+ return false;
+ }
+ a = pSlice.call(a);
+ b = pSlice.call(b);
+ return _deepEqual(a, b);
+ }
+ try {
+ var ka = objectKeys(a),
+ kb = objectKeys(b),
+ key, i;
+ } catch (e) {//happens when one is a string literal and the other isn't
+ return false;
+ }
+ // having the same number of owned properties (keys incorporates
+ // hasOwnProperty)
+ if (ka.length != kb.length)
+ return false;
+ //the same set of keys (although not necessarily the same order),
+ ka.sort();
+ kb.sort();
+ //~~~cheap key test
+ for (i = ka.length - 1; i >= 0; i--) {
+ if (ka[i] != kb[i])
+ return false;
+ }
+ //equivalent values for every corresponding key, and
+ //~~~possibly expensive deep test
+ for (i = ka.length - 1; i >= 0; i--) {
+ key = ka[i];
+ if (!_deepEqual(a[key], b[key])) return false;
+ }
+ return true;
+}
+
+// 8. The non-equivalence assertion tests for any deep inequality.
+// assert.notDeepEqual(actual, expected, message_opt);
+
+assert.notDeepEqual = function notDeepEqual(actual, expected, message) {
+ if (_deepEqual(actual, expected)) {
+ fail(actual, expected, message, 'notDeepEqual', assert.notDeepEqual);
+ }
+};
+
+// 9. The strict equality assertion tests strict equality, as determined by ===.
+// assert.strictEqual(actual, expected, message_opt);
+
+assert.strictEqual = function strictEqual(actual, expected, message) {
+ if (actual !== expected) {
+ fail(actual, expected, message, '===', assert.strictEqual);
+ }
+};
+
+// 10. The strict non-equality assertion tests for strict inequality, as
+// determined by !==. assert.notStrictEqual(actual, expected, message_opt);
+
+assert.notStrictEqual = function notStrictEqual(actual, expected, message) {
+ if (actual === expected) {
+ fail(actual, expected, message, '!==', assert.notStrictEqual);
+ }
+};
+
+function expectedException(actual, expected) {
+ if (!actual || !expected) {
+ return false;
+ }
+
+ if (expected instanceof RegExp) {
+ return expected.test(actual);
+ } else if (actual instanceof expected) {
+ return true;
+ } else if (expected.call({}, actual) === true) {
+ return true;
+ }
+
+ return false;
+}
+
+function _throws(shouldThrow, block, expected, message) {
+ var actual;
+
+ if (typeof expected === 'string') {
+ message = expected;
+ expected = null;
+ }
+
+ try {
+ block();
+ } catch (e) {
+ actual = e;
+ }
+
+ message = (expected && expected.name ? ' (' + expected.name + ').' : '.') +
+ (message ? ' ' + message : '.');
+
+ if (shouldThrow && !actual) {
+ fail('Missing expected exception' + message);
+ }
+
+ if (!shouldThrow && expectedException(actual, expected)) {
+ fail('Got unwanted exception' + message);
+ }
+
+ if ((shouldThrow && actual && expected &&
+ !expectedException(actual, expected)) || (!shouldThrow && actual)) {
+ throw actual;
+ }
+}
+
+// 11. Expected to throw an error:
+// assert.throws(block, Error_opt, message_opt);
+
+assert.throws = function(block, /*optional*/error, /*optional*/message) {
+ _throws.apply(this, [true].concat(pSlice.call(arguments)));
+};
+
+// EXTENSION! This is annoying to write outside this module.
+assert.doesNotThrow = function(block, /*optional*/error, /*optional*/message) {
+ _throws.apply(this, [false].concat(pSlice.call(arguments)));
+};
+
+assert.ifError = function(err) { if (err) {throw err;}};
+
+})()
+},{"util":8,"buffer":13}],11:[function(require,module,exports){
+(function(){// Underscore.js 1.4.4
+// http://underscorejs.org
+// (c) 2009-2013 Jeremy Ashkenas, DocumentCloud Inc.
+// Underscore may be freely distributed under the MIT license.
+
+(function() {
+
+ // Baseline setup
+ // --------------
+
+ // Establish the root object, `window` in the browser, or `global` on the server.
+ var root = this;
+
+ // Save the previous value of the `_` variable.
+ var previousUnderscore = root._;
+
+ // Establish the object that gets returned to break out of a loop iteration.
+ var breaker = {};
+
+ // Save bytes in the minified (but not gzipped) version:
+ var ArrayProto = Array.prototype, ObjProto = Object.prototype, FuncProto = Function.prototype;
+
+ // Create quick reference variables for speed access to core prototypes.
+ var push = ArrayProto.push,
+ slice = ArrayProto.slice,
+ concat = ArrayProto.concat,
+ toString = ObjProto.toString,
+ hasOwnProperty = ObjProto.hasOwnProperty;
+
+ // All **ECMAScript 5** native function implementations that we hope to use
+ // are declared here.
+ var
+ nativeForEach = ArrayProto.forEach,
+ nativeMap = ArrayProto.map,
+ nativeReduce = ArrayProto.reduce,
+ nativeReduceRight = ArrayProto.reduceRight,
+ nativeFilter = ArrayProto.filter,
+ nativeEvery = ArrayProto.every,
+ nativeSome = ArrayProto.some,
+ nativeIndexOf = ArrayProto.indexOf,
+ nativeLastIndexOf = ArrayProto.lastIndexOf,
+ nativeIsArray = Array.isArray,
+ nativeKeys = Object.keys,
+ nativeBind = FuncProto.bind;
+
+ // Create a safe reference to the Underscore object for use below.
+ var _ = function(obj) {
+ if (obj instanceof _) return obj;
+ if (!(this instanceof _)) return new _(obj);
+ this._wrapped = obj;
+ };
+
+ // Export the Underscore object for **Node.js**, with
+ // backwards-compatibility for the old `require()` API. If we're in
+ // the browser, add `_` as a global object via a string identifier,
+ // for Closure Compiler "advanced" mode.
+ if (typeof exports !== 'undefined') {
+ if (typeof module !== 'undefined' && module.exports) {
+ exports = module.exports = _;
+ }
+ exports._ = _;
+ } else {
+ root._ = _;
+ }
+
+ // Current version.
+ _.VERSION = '1.4.4';
+
+ // Collection Functions
+ // --------------------
+
+ // The cornerstone, an `each` implementation, aka `forEach`.
+ // Handles objects with the built-in `forEach`, arrays, and raw objects.
+ // Delegates to **ECMAScript 5**'s native `forEach` if available.
+ var each = _.each = _.forEach = function(obj, iterator, context) {
+ if (obj == null) return;
+ if (nativeForEach && obj.forEach === nativeForEach) {
+ obj.forEach(iterator, context);
+ } else if (obj.length === +obj.length) {
+ for (var i = 0, l = obj.length; i < l; i++) {
+ if (iterator.call(context, obj[i], i, obj) === breaker) return;
+ }
+ } else {
+ for (var key in obj) {
+ if (_.has(obj, key)) {
+ if (iterator.call(context, obj[key], key, obj) === breaker) return;
+ }
+ }
+ }
+ };
+
+ // Return the results of applying the iterator to each element.
+ // Delegates to **ECMAScript 5**'s native `map` if available.
+ _.map = _.collect = function(obj, iterator, context) {
+ var results = [];
+ if (obj == null) return results;
+ if (nativeMap && obj.map === nativeMap) return obj.map(iterator, context);
+ each(obj, function(value, index, list) {
+ results[results.length] = iterator.call(context, value, index, list);
+ });
+ return results;
+ };
+
+ var reduceError = 'Reduce of empty array with no initial value';
+
+ // **Reduce** builds up a single result from a list of values, aka `inject`,
+ // or `foldl`. Delegates to **ECMAScript 5**'s native `reduce` if available.
+ _.reduce = _.foldl = _.inject = function(obj, iterator, memo, context) {
+ var initial = arguments.length > 2;
+ if (obj == null) obj = [];
+ if (nativeReduce && obj.reduce === nativeReduce) {
+ if (context) iterator = _.bind(iterator, context);
+ return initial ? obj.reduce(iterator, memo) : obj.reduce(iterator);
+ }
+ each(obj, function(value, index, list) {
+ if (!initial) {
+ memo = value;
+ initial = true;
+ } else {
+ memo = iterator.call(context, memo, value, index, list);
+ }
+ });
+ if (!initial) throw new TypeError(reduceError);
+ return memo;
+ };
+
+ // The right-associative version of reduce, also known as `foldr`.
+ // Delegates to **ECMAScript 5**'s native `reduceRight` if available.
+ _.reduceRight = _.foldr = function(obj, iterator, memo, context) {
+ var initial = arguments.length > 2;
+ if (obj == null) obj = [];
+ if (nativeReduceRight && obj.reduceRight === nativeReduceRight) {
+ if (context) iterator = _.bind(iterator, context);
+ return initial ? obj.reduceRight(iterator, memo) : obj.reduceRight(iterator);
+ }
+ var length = obj.length;
+ if (length !== +length) {
+ var keys = _.keys(obj);
+ length = keys.length;
+ }
+ each(obj, function(value, index, list) {
+ index = keys ? keys[--length] : --length;
+ if (!initial) {
+ memo = obj[index];
+ initial = true;
+ } else {
+ memo = iterator.call(context, memo, obj[index], index, list);
+ }
+ });
+ if (!initial) throw new TypeError(reduceError);
+ return memo;
+ };
+
+ // Return the first value which passes a truth test. Aliased as `detect`.
+ _.find = _.detect = function(obj, iterator, context) {
+ var result;
+ any(obj, function(value, index, list) {
+ if (iterator.call(context, value, index, list)) {
+ result = value;
+ return true;
+ }
+ });
+ return result;
+ };
+
+ // Return all the elements that pass a truth test.
+ // Delegates to **ECMAScript 5**'s native `filter` if available.
+ // Aliased as `select`.
+ _.filter = _.select = function(obj, iterator, context) {
+ var results = [];
+ if (obj == null) return results;
+ if (nativeFilter && obj.filter === nativeFilter) return obj.filter(iterator, context);
+ each(obj, function(value, index, list) {
+ if (iterator.call(context, value, index, list)) results[results.length] = value;
+ });
+ return results;
+ };
+
+ // Return all the elements for which a truth test fails.
+ _.reject = function(obj, iterator, context) {
+ return _.filter(obj, function(value, index, list) {
+ return !iterator.call(context, value, index, list);
+ }, context);
+ };
+
+ // Determine whether all of the elements match a truth test.
+ // Delegates to **ECMAScript 5**'s native `every` if available.
+ // Aliased as `all`.
+ _.every = _.all = function(obj, iterator, context) {
+ iterator || (iterator = _.identity);
+ var result = true;
+ if (obj == null) return result;
+ if (nativeEvery && obj.every === nativeEvery) return obj.every(iterator, context);
+ each(obj, function(value, index, list) {
+ if (!(result = result && iterator.call(context, value, index, list))) return breaker;
+ });
+ return !!result;
+ };
+
+ // Determine if at least one element in the object matches a truth test.
+ // Delegates to **ECMAScript 5**'s native `some` if available.
+ // Aliased as `any`.
+ var any = _.some = _.any = function(obj, iterator, context) {
+ iterator || (iterator = _.identity);
+ var result = false;
+ if (obj == null) return result;
+ if (nativeSome && obj.some === nativeSome) return obj.some(iterator, context);
+ each(obj, function(value, index, list) {
+ if (result || (result = iterator.call(context, value, index, list))) return breaker;
+ });
+ return !!result;
+ };
+
+ // Determine if the array or object contains a given value (using `===`).
+ // Aliased as `include`.
+ _.contains = _.include = function(obj, target) {
+ if (obj == null) return false;
+ if (nativeIndexOf && obj.indexOf === nativeIndexOf) return obj.indexOf(target) != -1;
+ return any(obj, function(value) {
+ return value === target;
+ });
+ };
+
+ // Invoke a method (with arguments) on every item in a collection.
+ _.invoke = function(obj, method) {
+ var args = slice.call(arguments, 2);
+ var isFunc = _.isFunction(method);
+ return _.map(obj, function(value) {
+ return (isFunc ? method : value[method]).apply(value, args);
+ });
+ };
+
+ // Convenience version of a common use case of `map`: fetching a property.
+ _.pluck = function(obj, key) {
+ return _.map(obj, function(value){ return value[key]; });
+ };
+
+ // Convenience version of a common use case of `filter`: selecting only objects
+ // containing specific `key:value` pairs.
+ _.where = function(obj, attrs, first) {
+ if (_.isEmpty(attrs)) return first ? null : [];
+ return _[first ? 'find' : 'filter'](obj, function(value) {
+ for (var key in attrs) {
+ if (attrs[key] !== value[key]) return false;
+ }
+ return true;
+ });
+ };
+
+ // Convenience version of a common use case of `find`: getting the first object
+ // containing specific `key:value` pairs.
+ _.findWhere = function(obj, attrs) {
+ return _.where(obj, attrs, true);
+ };
+
+ // Return the maximum element or (element-based computation).
+ // Can't optimize arrays of integers longer than 65,535 elements.
+ // See: https://bugs.webkit.org/show_bug.cgi?id=80797
+ _.max = function(obj, iterator, context) {
+ if (!iterator && _.isArray(obj) && obj[0] === +obj[0] && obj.length < 65535) {
+ return Math.max.apply(Math, obj);
+ }
+ if (!iterator && _.isEmpty(obj)) return -Infinity;
+ var result = {computed : -Infinity, value: -Infinity};
+ each(obj, function(value, index, list) {
+ var computed = iterator ? iterator.call(context, value, index, list) : value;
+ computed >= result.computed && (result = {value : value, computed : computed});
+ });
+ return result.value;
+ };
+
+ // Return the minimum element (or element-based computation).
+ _.min = function(obj, iterator, context) {
+ if (!iterator && _.isArray(obj) && obj[0] === +obj[0] && obj.length < 65535) {
+ return Math.min.apply(Math, obj);
+ }
+ if (!iterator && _.isEmpty(obj)) return Infinity;
+ var result = {computed : Infinity, value: Infinity};
+ each(obj, function(value, index, list) {
+ var computed = iterator ? iterator.call(context, value, index, list) : value;
+ computed < result.computed && (result = {value : value, computed : computed});
+ });
+ return result.value;
+ };
+
+ // Shuffle an array.
+ _.shuffle = function(obj) {
+ var rand;
+ var index = 0;
+ var shuffled = [];
+ each(obj, function(value) {
+ rand = _.random(index++);
+ shuffled[index - 1] = shuffled[rand];
+ shuffled[rand] = value;
+ });
+ return shuffled;
+ };
+
+ // An internal function to generate lookup iterators.
+ var lookupIterator = function(value) {
+ return _.isFunction(value) ? value : function(obj){ return obj[value]; };
+ };
+
+ // Sort the object's values by a criterion produced by an iterator.
+ _.sortBy = function(obj, value, context) {
+ var iterator = lookupIterator(value);
+ return _.pluck(_.map(obj, function(value, index, list) {
+ return {
+ value : value,
+ index : index,
+ criteria : iterator.call(context, value, index, list)
+ };
+ }).sort(function(left, right) {
+ var a = left.criteria;
+ var b = right.criteria;
+ if (a !== b) {
+ if (a > b || a === void 0) return 1;
+ if (a < b || b === void 0) return -1;
+ }
+ return left.index < right.index ? -1 : 1;
+ }), 'value');
+ };
+
+ // An internal function used for aggregate "group by" operations.
+ var group = function(obj, value, context, behavior) {
+ var result = {};
+ var iterator = lookupIterator(value || _.identity);
+ each(obj, function(value, index) {
+ var key = iterator.call(context, value, index, obj);
+ behavior(result, key, value);
+ });
+ return result;
+ };
+
+ // Groups the object's values by a criterion. Pass either a string attribute
+ // to group by, or a function that returns the criterion.
+ _.groupBy = function(obj, value, context) {
+ return group(obj, value, context, function(result, key, value) {
+ (_.has(result, key) ? result[key] : (result[key] = [])).push(value);
+ });
+ };
+
+ // Counts instances of an object that group by a certain criterion. Pass
+ // either a string attribute to count by, or a function that returns the
+ // criterion.
+ _.countBy = function(obj, value, context) {
+ return group(obj, value, context, function(result, key) {
+ if (!_.has(result, key)) result[key] = 0;
+ result[key]++;
+ });
+ };
+
+ // Use a comparator function to figure out the smallest index at which
+ // an object should be inserted so as to maintain order. Uses binary search.
+ _.sortedIndex = function(array, obj, iterator, context) {
+ iterator = iterator == null ? _.identity : lookupIterator(iterator);
+ var value = iterator.call(context, obj);
+ var low = 0, high = array.length;
+ while (low < high) {
+ var mid = (low + high) >>> 1;
+ iterator.call(context, array[mid]) < value ? low = mid + 1 : high = mid;
+ }
+ return low;
+ };
+
+ // Safely convert anything iterable into a real, live array.
+ _.toArray = function(obj) {
+ if (!obj) return [];
+ if (_.isArray(obj)) return slice.call(obj);
+ if (obj.length === +obj.length) return _.map(obj, _.identity);
+ return _.values(obj);
+ };
+
+ // Return the number of elements in an object.
+ _.size = function(obj) {
+ if (obj == null) return 0;
+ return (obj.length === +obj.length) ? obj.length : _.keys(obj).length;
+ };
+
+ // Array Functions
+ // ---------------
+
+ // Get the first element of an array. Passing **n** will return the first N
+ // values in the array. Aliased as `head` and `take`. The **guard** check
+ // allows it to work with `_.map`.
+ _.first = _.head = _.take = function(array, n, guard) {
+ if (array == null) return void 0;
+ return (n != null) && !guard ? slice.call(array, 0, n) : array[0];
+ };
+
+ // Returns everything but the last entry of the array. Especially useful on
+ // the arguments object. Passing **n** will return all the values in
+ // the array, excluding the last N. The **guard** check allows it to work with
+ // `_.map`.
+ _.initial = function(array, n, guard) {
+ return slice.call(array, 0, array.length - ((n == null) || guard ? 1 : n));
+ };
+
+ // Get the last element of an array. Passing **n** will return the last N
+ // values in the array. The **guard** check allows it to work with `_.map`.
+ _.last = function(array, n, guard) {
+ if (array == null) return void 0;
+ if ((n != null) && !guard) {
+ return slice.call(array, Math.max(array.length - n, 0));
+ } else {
+ return array[array.length - 1];
+ }
+ };
+
+ // Returns everything but the first entry of the array. Aliased as `tail` and `drop`.
+ // Especially useful on the arguments object. Passing an **n** will return
+ // the rest N values in the array. The **guard**
+ // check allows it to work with `_.map`.
+ _.rest = _.tail = _.drop = function(array, n, guard) {
+ return slice.call(array, (n == null) || guard ? 1 : n);
+ };
+
+ // Trim out all falsy values from an array.
+ _.compact = function(array) {
+ return _.filter(array, _.identity);
+ };
+
+ // Internal implementation of a recursive `flatten` function.
+ var flatten = function(input, shallow, output) {
+ each(input, function(value) {
+ if (_.isArray(value)) {
+ shallow ? push.apply(output, value) : flatten(value, shallow, output);
+ } else {
+ output.push(value);
+ }
+ });
+ return output;
+ };
+
+ // Return a completely flattened version of an array.
+ _.flatten = function(array, shallow) {
+ return flatten(array, shallow, []);
+ };
+
+ // Return a version of the array that does not contain the specified value(s).
+ _.without = function(array) {
+ return _.difference(array, slice.call(arguments, 1));
+ };
+
+ // Produce a duplicate-free version of the array. If the array has already
+ // been sorted, you have the option of using a faster algorithm.
+ // Aliased as `unique`.
+ _.uniq = _.unique = function(array, isSorted, iterator, context) {
+ if (_.isFunction(isSorted)) {
+ context = iterator;
+ iterator = isSorted;
+ isSorted = false;
+ }
+ var initial = iterator ? _.map(array, iterator, context) : array;
+ var results = [];
+ var seen = [];
+ each(initial, function(value, index) {
+ if (isSorted ? (!index || seen[seen.length - 1] !== value) : !_.contains(seen, value)) {
+ seen.push(value);
+ results.push(array[index]);
+ }
+ });
+ return results;
+ };
+
+ // Produce an array that contains the union: each distinct element from all of
+ // the passed-in arrays.
+ _.union = function() {
+ return _.uniq(concat.apply(ArrayProto, arguments));
+ };
+
+ // Produce an array that contains every item shared between all the
+ // passed-in arrays.
+ _.intersection = function(array) {
+ var rest = slice.call(arguments, 1);
+ return _.filter(_.uniq(array), function(item) {
+ return _.every(rest, function(other) {
+ return _.indexOf(other, item) >= 0;
+ });
+ });
+ };
+
+ // Take the difference between one array and a number of other arrays.
+ // Only the elements present in just the first array will remain.
+ _.difference = function(array) {
+ var rest = concat.apply(ArrayProto, slice.call(arguments, 1));
+ return _.filter(array, function(value){ return !_.contains(rest, value); });
+ };
+
+ // Zip together multiple lists into a single array -- elements that share
+ // an index go together.
+ _.zip = function() {
+ var args = slice.call(arguments);
+ var length = _.max(_.pluck(args, 'length'));
+ var results = new Array(length);
+ for (var i = 0; i < length; i++) {
+ results[i] = _.pluck(args, "" + i);
+ }
+ return results;
+ };
+
+ // Converts lists into objects. Pass either a single array of `[key, value]`
+ // pairs, or two parallel arrays of the same length -- one of keys, and one of
+ // the corresponding values.
+ _.object = function(list, values) {
+ if (list == null) return {};
+ var result = {};
+ for (var i = 0, l = list.length; i < l; i++) {
+ if (values) {
+ result[list[i]] = values[i];
+ } else {
+ result[list[i][0]] = list[i][1];
+ }
+ }
+ return result;
+ };
+
+ // If the browser doesn't supply us with indexOf (I'm looking at you, **MSIE**),
+ // we need this function. Return the position of the first occurrence of an
+ // item in an array, or -1 if the item is not included in the array.
+ // Delegates to **ECMAScript 5**'s native `indexOf` if available.
+ // If the array is large and already in sort order, pass `true`
+ // for **isSorted** to use binary search.
+ _.indexOf = function(array, item, isSorted) {
+ if (array == null) return -1;
+ var i = 0, l = array.length;
+ if (isSorted) {
+ if (typeof isSorted == 'number') {
+ i = (isSorted < 0 ? Math.max(0, l + isSorted) : isSorted);
+ } else {
+ i = _.sortedIndex(array, item);
+ return array[i] === item ? i : -1;
+ }
+ }
+ if (nativeIndexOf && array.indexOf === nativeIndexOf) return array.indexOf(item, isSorted);
+ for (; i < l; i++) if (array[i] === item) return i;
+ return -1;
+ };
+
+ // Delegates to **ECMAScript 5**'s native `lastIndexOf` if available.
+ _.lastIndexOf = function(array, item, from) {
+ if (array == null) return -1;
+ var hasIndex = from != null;
+ if (nativeLastIndexOf && array.lastIndexOf === nativeLastIndexOf) {
+ return hasIndex ? array.lastIndexOf(item, from) : array.lastIndexOf(item);
+ }
+ var i = (hasIndex ? from : array.length);
+ while (i--) if (array[i] === item) return i;
+ return -1;
+ };
+
+ // Generate an integer Array containing an arithmetic progression. A port of
+ // the native Python `range()` function. See
+ // [the Python documentation](http://docs.python.org/library/functions.html#range).
+ _.range = function(start, stop, step) {
+ if (arguments.length <= 1) {
+ stop = start || 0;
+ start = 0;
+ }
+ step = arguments[2] || 1;
+
+ var len = Math.max(Math.ceil((stop - start) / step), 0);
+ var idx = 0;
+ var range = new Array(len);
+
+ while(idx < len) {
+ range[idx++] = start;
+ start += step;
+ }
+
+ return range;
+ };
+
+ // Function (ahem) Functions
+ // ------------------
+
+ // Create a function bound to a given object (assigning `this`, and arguments,
+ // optionally). Delegates to **ECMAScript 5**'s native `Function.bind` if
+ // available.
+ _.bind = function(func, context) {
+ if (func.bind === nativeBind && nativeBind) return nativeBind.apply(func, slice.call(arguments, 1));
+ var args = slice.call(arguments, 2);
+ return function() {
+ return func.apply(context, args.concat(slice.call(arguments)));
+ };
+ };
+
+ // Partially apply a function by creating a version that has had some of its
+ // arguments pre-filled, without changing its dynamic `this` context.
+ _.partial = function(func) {
+ var args = slice.call(arguments, 1);
+ return function() {
+ return func.apply(this, args.concat(slice.call(arguments)));
+ };
+ };
+
+ // Bind all of an object's methods to that object. Useful for ensuring that
+ // all callbacks defined on an object belong to it.
+ _.bindAll = function(obj) {
+ var funcs = slice.call(arguments, 1);
+ if (funcs.length === 0) funcs = _.functions(obj);
+ each(funcs, function(f) { obj[f] = _.bind(obj[f], obj); });
+ return obj;
+ };
+
+ // Memoize an expensive function by storing its results.
+ _.memoize = function(func, hasher) {
+ var memo = {};
+ hasher || (hasher = _.identity);
+ return function() {
+ var key = hasher.apply(this, arguments);
+ return _.has(memo, key) ? memo[key] : (memo[key] = func.apply(this, arguments));
+ };
+ };
+
+ // Delays a function for the given number of milliseconds, and then calls
+ // it with the arguments supplied.
+ _.delay = function(func, wait) {
+ var args = slice.call(arguments, 2);
+ return setTimeout(function(){ return func.apply(null, args); }, wait);
+ };
+
+ // Defers a function, scheduling it to run after the current call stack has
+ // cleared.
+ _.defer = function(func) {
+ return _.delay.apply(_, [func, 1].concat(slice.call(arguments, 1)));
+ };
+
+ // Returns a function, that, when invoked, will only be triggered at most once
+ // during a given window of time.
+ _.throttle = function(func, wait) {
+ var context, args, timeout, result;
+ var previous = 0;
+ var later = function() {
+ previous = new Date;
+ timeout = null;
+ result = func.apply(context, args);
+ };
+ return function() {
+ var now = new Date;
+ var remaining = wait - (now - previous);
+ context = this;
+ args = arguments;
+ if (remaining <= 0) {
+ clearTimeout(timeout);
+ timeout = null;
+ previous = now;
+ result = func.apply(context, args);
+ } else if (!timeout) {
+ timeout = setTimeout(later, remaining);
+ }
+ return result;
+ };
+ };
+
+ // Returns a function, that, as long as it continues to be invoked, will not
+ // be triggered. The function will be called after it stops being called for
+ // N milliseconds. If `immediate` is passed, trigger the function on the
+ // leading edge, instead of the trailing.
+ _.debounce = function(func, wait, immediate) {
+ var timeout, result;
+ return function() {
+ var context = this, args = arguments;
+ var later = function() {
+ timeout = null;
+ if (!immediate) result = func.apply(context, args);
+ };
+ var callNow = immediate && !timeout;
+ clearTimeout(timeout);
+ timeout = setTimeout(later, wait);
+ if (callNow) result = func.apply(context, args);
+ return result;
+ };
+ };
+
+ // Returns a function that will be executed at most one time, no matter how
+ // often you call it. Useful for lazy initialization.
+ _.once = function(func) {
+ var ran = false, memo;
+ return function() {
+ if (ran) return memo;
+ ran = true;
+ memo = func.apply(this, arguments);
+ func = null;
+ return memo;
+ };
+ };
+
+ // Returns the first function passed as an argument to the second,
+ // allowing you to adjust arguments, run code before and after, and
+ // conditionally execute the original function.
+ _.wrap = function(func, wrapper) {
+ return function() {
+ var args = [func];
+ push.apply(args, arguments);
+ return wrapper.apply(this, args);
+ };
+ };
+
+ // Returns a function that is the composition of a list of functions, each
+ // consuming the return value of the function that follows.
+ _.compose = function() {
+ var funcs = arguments;
+ return function() {
+ var args = arguments;
+ for (var i = funcs.length - 1; i >= 0; i--) {
+ args = [funcs[i].apply(this, args)];
+ }
+ return args[0];
+ };
+ };
+
+ // Returns a function that will only be executed after being called N times.
+ _.after = function(times, func) {
+ if (times <= 0) return func();
+ return function() {
+ if (--times < 1) {
+ return func.apply(this, arguments);
+ }
+ };
+ };
+
+ // Object Functions
+ // ----------------
+
+ // Retrieve the names of an object's properties.
+ // Delegates to **ECMAScript 5**'s native `Object.keys`
+ _.keys = nativeKeys || function(obj) {
+ if (obj !== Object(obj)) throw new TypeError('Invalid object');
+ var keys = [];
+ for (var key in obj) if (_.has(obj, key)) keys[keys.length] = key;
+ return keys;
+ };
+
+ // Retrieve the values of an object's properties.
+ _.values = function(obj) {
+ var values = [];
+ for (var key in obj) if (_.has(obj, key)) values.push(obj[key]);
+ return values;
+ };
+
+ // Convert an object into a list of `[key, value]` pairs.
+ _.pairs = function(obj) {
+ var pairs = [];
+ for (var key in obj) if (_.has(obj, key)) pairs.push([key, obj[key]]);
+ return pairs;
+ };
+
+ // Invert the keys and values of an object. The values must be serializable.
+ _.invert = function(obj) {
+ var result = {};
+ for (var key in obj) if (_.has(obj, key)) result[obj[key]] = key;
+ return result;
+ };
+
+ // Return a sorted list of the function names available on the object.
+ // Aliased as `methods`
+ _.functions = _.methods = function(obj) {
+ var names = [];
+ for (var key in obj) {
+ if (_.isFunction(obj[key])) names.push(key);
+ }
+ return names.sort();
+ };
+
+ // Extend a given object with all the properties in passed-in object(s).
+ _.extend = function(obj) {
+ each(slice.call(arguments, 1), function(source) {
+ if (source) {
+ for (var prop in source) {
+ obj[prop] = source[prop];
+ }
+ }
+ });
+ return obj;
+ };
+
+ // Return a copy of the object only containing the whitelisted properties.
+ _.pick = function(obj) {
+ var copy = {};
+ var keys = concat.apply(ArrayProto, slice.call(arguments, 1));
+ each(keys, function(key) {
+ if (key in obj) copy[key] = obj[key];
+ });
+ return copy;
+ };
+
+ // Return a copy of the object without the blacklisted properties.
+ _.omit = function(obj) {
+ var copy = {};
+ var keys = concat.apply(ArrayProto, slice.call(arguments, 1));
+ for (var key in obj) {
+ if (!_.contains(keys, key)) copy[key] = obj[key];
+ }
+ return copy;
+ };
+
+ // Fill in a given object with default properties.
+ _.defaults = function(obj) {
+ each(slice.call(arguments, 1), function(source) {
+ if (source) {
+ for (var prop in source) {
+ if (obj[prop] == null) obj[prop] = source[prop];
+ }
+ }
+ });
+ return obj;
+ };
+
+ // Create a (shallow-cloned) duplicate of an object.
+ _.clone = function(obj) {
+ if (!_.isObject(obj)) return obj;
+ return _.isArray(obj) ? obj.slice() : _.extend({}, obj);
+ };
+
+ // Invokes interceptor with the obj, and then returns obj.
+ // The primary purpose of this method is to "tap into" a method chain, in
+ // order to perform operations on intermediate results within the chain.
+ _.tap = function(obj, interceptor) {
+ interceptor(obj);
+ return obj;
+ };
+
+ // Internal recursive comparison function for `isEqual`.
+ var eq = function(a, b, aStack, bStack) {
+ // Identical objects are equal. `0 === -0`, but they aren't identical.
+ // See the Harmony `egal` proposal: http://wiki.ecmascript.org/doku.php?id=harmony:egal.
+ if (a === b) return a !== 0 || 1 / a == 1 / b;
+ // A strict comparison is necessary because `null == undefined`.
+ if (a == null || b == null) return a === b;
+ // Unwrap any wrapped objects.
+ if (a instanceof _) a = a._wrapped;
+ if (b instanceof _) b = b._wrapped;
+ // Compare `[[Class]]` names.
+ var className = toString.call(a);
+ if (className != toString.call(b)) return false;
+ switch (className) {
+ // Strings, numbers, dates, and booleans are compared by value.
+ case '[object String]':
+ // Primitives and their corresponding object wrappers are equivalent; thus, `"5"` is
+ // equivalent to `new String("5")`.
+ return a == String(b);
+ case '[object Number]':
+ // `NaN`s are equivalent, but non-reflexive. An `egal` comparison is performed for
+ // other numeric values.
+ return a != +a ? b != +b : (a == 0 ? 1 / a == 1 / b : a == +b);
+ case '[object Date]':
+ case '[object Boolean]':
+ // Coerce dates and booleans to numeric primitive values. Dates are compared by their
+ // millisecond representations. Note that invalid dates with millisecond representations
+ // of `NaN` are not equivalent.
+ return +a == +b;
+ // RegExps are compared by their source patterns and flags.
+ case '[object RegExp]':
+ return a.source == b.source &&
+ a.global == b.global &&
+ a.multiline == b.multiline &&
+ a.ignoreCase == b.ignoreCase;
+ }
+ if (typeof a != 'object' || typeof b != 'object') return false;
+ // Assume equality for cyclic structures. The algorithm for detecting cyclic
+ // structures is adapted from ES 5.1 section 15.12.3, abstract operation `JO`.
+ var length = aStack.length;
+ while (length--) {
+ // Linear search. Performance is inversely proportional to the number of
+ // unique nested structures.
+ if (aStack[length] == a) return bStack[length] == b;
+ }
+ // Add the first object to the stack of traversed objects.
+ aStack.push(a);
+ bStack.push(b);
+ var size = 0, result = true;
+ // Recursively compare objects and arrays.
+ if (className == '[object Array]') {
+ // Compare array lengths to determine if a deep comparison is necessary.
+ size = a.length;
+ result = size == b.length;
+ if (result) {
+ // Deep compare the contents, ignoring non-numeric properties.
+ while (size--) {
+ if (!(result = eq(a[size], b[size], aStack, bStack))) break;
+ }
+ }
+ } else {
+ // Objects with different constructors are not equivalent, but `Object`s
+ // from different frames are.
+ var aCtor = a.constructor, bCtor = b.constructor;
+ if (aCtor !== bCtor && !(_.isFunction(aCtor) && (aCtor instanceof aCtor) &&
+ _.isFunction(bCtor) && (bCtor instanceof bCtor))) {
+ return false;
+ }
+ // Deep compare objects.
+ for (var key in a) {
+ if (_.has(a, key)) {
+ // Count the expected number of properties.
+ size++;
+ // Deep compare each member.
+ if (!(result = _.has(b, key) && eq(a[key], b[key], aStack, bStack))) break;
+ }
+ }
+ // Ensure that both objects contain the same number of properties.
+ if (result) {
+ for (key in b) {
+ if (_.has(b, key) && !(size--)) break;
+ }
+ result = !size;
+ }
+ }
+ // Remove the first object from the stack of traversed objects.
+ aStack.pop();
+ bStack.pop();
+ return result;
+ };
+
+ // Perform a deep comparison to check if two objects are equal.
+ _.isEqual = function(a, b) {
+ return eq(a, b, [], []);
+ };
+
+ // Is a given array, string, or object empty?
+ // An "empty" object has no enumerable own-properties.
+ _.isEmpty = function(obj) {
+ if (obj == null) return true;
+ if (_.isArray(obj) || _.isString(obj)) return obj.length === 0;
+ for (var key in obj) if (_.has(obj, key)) return false;
+ return true;
+ };
+
+ // Is a given value a DOM element?
+ _.isElement = function(obj) {
+ return !!(obj && obj.nodeType === 1);
+ };
+
+ // Is a given value an array?
+ // Delegates to ECMA5's native Array.isArray
+ _.isArray = nativeIsArray || function(obj) {
+ return toString.call(obj) == '[object Array]';
+ };
+
+ // Is a given variable an object?
+ _.isObject = function(obj) {
+ return obj === Object(obj);
+ };
+
+ // Add some isType methods: isArguments, isFunction, isString, isNumber, isDate, isRegExp.
+ each(['Arguments', 'Function', 'String', 'Number', 'Date', 'RegExp'], function(name) {
+ _['is' + name] = function(obj) {
+ return toString.call(obj) == '[object ' + name + ']';
+ };
+ });
+
+ // Define a fallback version of the method in browsers (ahem, IE), where
+ // there isn't any inspectable "Arguments" type.
+ if (!_.isArguments(arguments)) {
+ _.isArguments = function(obj) {
+ return !!(obj && _.has(obj, 'callee'));
+ };
+ }
+
+ // Optimize `isFunction` if appropriate.
+ if (typeof (/./) !== 'function') {
+ _.isFunction = function(obj) {
+ return typeof obj === 'function';
+ };
+ }
+
+ // Is a given object a finite number?
+ _.isFinite = function(obj) {
+ return isFinite(obj) && !isNaN(parseFloat(obj));
+ };
+
+ // Is the given value `NaN`? (NaN is the only number which does not equal itself).
+ _.isNaN = function(obj) {
+ return _.isNumber(obj) && obj != +obj;
+ };
+
+ // Is a given value a boolean?
+ _.isBoolean = function(obj) {
+ return obj === true || obj === false || toString.call(obj) == '[object Boolean]';
+ };
+
+ // Is a given value equal to null?
+ _.isNull = function(obj) {
+ return obj === null;
+ };
+
+ // Is a given variable undefined?
+ _.isUndefined = function(obj) {
+ return obj === void 0;
+ };
+
+ // Shortcut function for checking if an object has a given property directly
+ // on itself (in other words, not on a prototype).
+ _.has = function(obj, key) {
+ return hasOwnProperty.call(obj, key);
+ };
+
+ // Utility Functions
+ // -----------------
+
+ // Run Underscore.js in *noConflict* mode, returning the `_` variable to its
+ // previous owner. Returns a reference to the Underscore object.
+ _.noConflict = function() {
+ root._ = previousUnderscore;
+ return this;
+ };
+
+ // Keep the identity function around for default iterators.
+ _.identity = function(value) {
+ return value;
+ };
+
+ // Run a function **n** times.
+ _.times = function(n, iterator, context) {
+ var accum = Array(n);
+ for (var i = 0; i < n; i++) accum[i] = iterator.call(context, i);
+ return accum;
+ };
+
+ // Return a random integer between min and max (inclusive).
+ _.random = function(min, max) {
+ if (max == null) {
+ max = min;
+ min = 0;
+ }
+ return min + Math.floor(Math.random() * (max - min + 1));
+ };
+
+ // List of HTML entities for escaping.
+ var entityMap = {
+ escape: {
+ '&': '&amp;',
+ '<': '&lt;',
+ '>': '&gt;',
+ '"': '&quot;',
+ "'": '&#x27;',
+ '/': '&#x2F;'
+ }
+ };
+ entityMap.unescape = _.invert(entityMap.escape);
+
+ // Regexes containing the keys and values listed immediately above.
+ var entityRegexes = {
+ escape: new RegExp('[' + _.keys(entityMap.escape).join('') + ']', 'g'),
+ unescape: new RegExp('(' + _.keys(entityMap.unescape).join('|') + ')', 'g')
+ };
+
+ // Functions for escaping and unescaping strings to/from HTML interpolation.
+ _.each(['escape', 'unescape'], function(method) {
+ _[method] = function(string) {
+ if (string == null) return '';
+ return ('' + string).replace(entityRegexes[method], function(match) {
+ return entityMap[method][match];
+ });
+ };
+ });
+
+ // If the value of the named property is a function then invoke it;
+ // otherwise, return it.
+ _.result = function(object, property) {
+ if (object == null) return null;
+ var value = object[property];
+ return _.isFunction(value) ? value.call(object) : value;
+ };
+
+ // Add your own custom functions to the Underscore object.
+ _.mixin = function(obj) {
+ each(_.functions(obj), function(name){
+ var func = _[name] = obj[name];
+ _.prototype[name] = function() {
+ var args = [this._wrapped];
+ push.apply(args, arguments);
+ return result.call(this, func.apply(_, args));
+ };
+ });
+ };
+
+ // Generate a unique integer id (unique within the entire client session).
+ // Useful for temporary DOM ids.
+ var idCounter = 0;
+ _.uniqueId = function(prefix) {
+ var id = ++idCounter + '';
+ return prefix ? prefix + id : id;
+ };
+
+ // By default, Underscore uses ERB-style template delimiters, change the
+ // following template settings to use alternative delimiters.
+ _.templateSettings = {
+ evaluate : /<%([\s\S]+?)%>/g,
+ interpolate : /<%=([\s\S]+?)%>/g,
+ escape : /<%-([\s\S]+?)%>/g
+ };
+
+ // When customizing `templateSettings`, if you don't want to define an
+ // interpolation, evaluation or escaping regex, we need one that is
+ // guaranteed not to match.
+ var noMatch = /(.)^/;
+
+ // Certain characters need to be escaped so that they can be put into a
+ // string literal.
+ var escapes = {
+ "'": "'",
+ '\\': '\\',
+ '\r': 'r',
+ '\n': 'n',
+ '\t': 't',
+ '\u2028': 'u2028',
+ '\u2029': 'u2029'
+ };
+
+ var escaper = /\\|'|\r|\n|\t|\u2028|\u2029/g;
+
+ // JavaScript micro-templating, similar to John Resig's implementation.
+ // Underscore templating handles arbitrary delimiters, preserves whitespace,
+ // and correctly escapes quotes within interpolated code.
+ _.template = function(text, data, settings) {
+ var render;
+ settings = _.defaults({}, settings, _.templateSettings);
+
+ // Combine delimiters into one regular expression via alternation.
+ var matcher = new RegExp([
+ (settings.escape || noMatch).source,
+ (settings.interpolate || noMatch).source,
+ (settings.evaluate || noMatch).source
+ ].join('|') + '|$', 'g');
+
+ // Compile the template source, escaping string literals appropriately.
+ var index = 0;
+ var source = "__p+='";
+ text.replace(matcher, function(match, escape, interpolate, evaluate, offset) {
+ source += text.slice(index, offset)
+ .replace(escaper, function(match) { return '\\' + escapes[match]; });
+
+ if (escape) {
+ source += "'+\n((__t=(" + escape + "))==null?'':_.escape(__t))+\n'";
+ }
+ if (interpolate) {
+ source += "'+\n((__t=(" + interpolate + "))==null?'':__t)+\n'";
+ }
+ if (evaluate) {
+ source += "';\n" + evaluate + "\n__p+='";
+ }
+ index = offset + match.length;
+ return match;
+ });
+ source += "';\n";
+
+ // If a variable is not specified, place data values in local scope.
+ if (!settings.variable) source = 'with(obj||{}){\n' + source + '}\n';
+
+ source = "var __t,__p='',__j=Array.prototype.join," +
+ "print=function(){__p+=__j.call(arguments,'');};\n" +
+ source + "return __p;\n";
+
+ try {
+ render = new Function(settings.variable || 'obj', '_', source);
+ } catch (e) {
+ e.source = source;
+ throw e;
+ }
+
+ if (data) return render(data, _);
+ var template = function(data) {
+ return render.call(this, data, _);
+ };
+
+ // Provide the compiled function source as a convenience for precompilation.
+ template.source = 'function(' + (settings.variable || 'obj') + '){\n' + source + '}';
+
+ return template;
+ };
+
+ // Add a "chain" function, which will delegate to the wrapper.
+ _.chain = function(obj) {
+ return _(obj).chain();
+ };
+
+ // OOP
+ // ---------------
+ // If Underscore is called as a function, it returns a wrapped object that
+ // can be used OO-style. This wrapper holds altered versions of all the
+ // underscore functions. Wrapped objects may be chained.
+
+ // Helper function to continue chaining intermediate results.
+ var result = function(obj) {
+ return this._chain ? _(obj).chain() : obj;
+ };
+
+ // Add all of the Underscore functions to the wrapper object.
+ _.mixin(_);
+
+ // Add all mutator Array functions to the wrapper.
+ each(['pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift'], function(name) {
+ var method = ArrayProto[name];
+ _.prototype[name] = function() {
+ var obj = this._wrapped;
+ method.apply(obj, arguments);
+ if ((name == 'shift' || name == 'splice') && obj.length === 0) delete obj[0];
+ return result.call(this, obj);
+ };
+ });
+
+ // Add all accessor Array functions to the wrapper.
+ each(['concat', 'join', 'slice'], function(name) {
+ var method = ArrayProto[name];
+ _.prototype[name] = function() {
+ return result.call(this, method.apply(this._wrapped, arguments));
+ };
+ });
+
+ _.extend(_.prototype, {
+
+ // Start chaining a wrapped Underscore object.
+ chain: function() {
+ this._chain = true;
+ return this;
+ },
+
+ // Extracts the result from a wrapped and chained object.
+ value: function() {
+ return this._wrapped;
+ }
+
+ });
+
+}).call(this);
+
+})()
+},{}],14:[function(require,module,exports){
+exports.readIEEE754 = function(buffer, offset, isBE, mLen, nBytes) {
+ var e, m,
+ eLen = nBytes * 8 - mLen - 1,
+ eMax = (1 << eLen) - 1,
+ eBias = eMax >> 1,
+ nBits = -7,
+ i = isBE ? 0 : (nBytes - 1),
+ d = isBE ? 1 : -1,
+ s = buffer[offset + i];
+
+ i += d;
+
+ e = s & ((1 << (-nBits)) - 1);
+ s >>= (-nBits);
+ nBits += eLen;
+ for (; nBits > 0; e = e * 256 + buffer[offset + i], i += d, nBits -= 8);
+
+ m = e & ((1 << (-nBits)) - 1);
+ e >>= (-nBits);
+ nBits += mLen;
+ for (; nBits > 0; m = m * 256 + buffer[offset + i], i += d, nBits -= 8);
+
+ if (e === 0) {
+ e = 1 - eBias;
+ } else if (e === eMax) {
+ return m ? NaN : ((s ? -1 : 1) * Infinity);
+ } else {
+ m = m + Math.pow(2, mLen);
+ e = e - eBias;
+ }
+ return (s ? -1 : 1) * m * Math.pow(2, e - mLen);
+};
+
+exports.writeIEEE754 = function(buffer, value, offset, isBE, mLen, nBytes) {
+ var e, m, c,
+ eLen = nBytes * 8 - mLen - 1,
+ eMax = (1 << eLen) - 1,
+ eBias = eMax >> 1,
+ rt = (mLen === 23 ? Math.pow(2, -24) - Math.pow(2, -77) : 0),
+ i = isBE ? (nBytes - 1) : 0,
+ d = isBE ? -1 : 1,
+ s = value < 0 || (value === 0 && 1 / value < 0) ? 1 : 0;
+
+ value = Math.abs(value);
+
+ if (isNaN(value) || value === Infinity) {
+ m = isNaN(value) ? 1 : 0;
+ e = eMax;
+ } else {
+ e = Math.floor(Math.log(value) / Math.LN2);
+ if (value * (c = Math.pow(2, -e)) < 1) {
+ e--;
+ c *= 2;
+ }
+ if (e + eBias >= 1) {
+ value += rt / c;
+ } else {
+ value += rt * Math.pow(2, 1 - eBias);
+ }
+ if (value * c >= 2) {
+ e++;
+ c /= 2;
+ }
+
+ if (e + eBias >= eMax) {
+ m = 0;
+ e = eMax;
+ } else if (e + eBias >= 1) {
+ m = (value * c - 1) * Math.pow(2, mLen);
+ e = e + eBias;
+ } else {
+ m = value * Math.pow(2, eBias - 1) * Math.pow(2, mLen);
+ e = 0;
+ }
+ }
+
+ for (; mLen >= 8; buffer[offset + i] = m & 0xff, i += d, m /= 256, mLen -= 8);
+
+ e = (e << mLen) | m;
+ eLen += mLen;
+ for (; eLen > 0; buffer[offset + i] = e & 0xff, i += d, e /= 256, eLen -= 8);
+
+ buffer[offset + i - d] |= s * 128;
+};
+
+},{}],13:[function(require,module,exports){
+(function(){function SlowBuffer (size) {
+ this.length = size;
+};
+
+var assert = require('assert');
+
+exports.INSPECT_MAX_BYTES = 50;
+
+
+function toHex(n) {
+ if (n < 16) return '0' + n.toString(16);
+ return n.toString(16);
+}
+
+function utf8ToBytes(str) {
+ var byteArray = [];
+ for (var i = 0; i < str.length; i++)
+ if (str.charCodeAt(i) <= 0x7F)
+ byteArray.push(str.charCodeAt(i));
+ else {
+ var h = encodeURIComponent(str.charAt(i)).substr(1).split('%');
+ for (var j = 0; j < h.length; j++)
+ byteArray.push(parseInt(h[j], 16));
+ }
+
+ return byteArray;
+}
+
+function asciiToBytes(str) {
+ var byteArray = []
+ for (var i = 0; i < str.length; i++ )
+ // Node's code seems to be doing this and not & 0x7F..
+ byteArray.push( str.charCodeAt(i) & 0xFF );
+
+ return byteArray;
+}
+
+function base64ToBytes(str) {
+ return require("base64-js").toByteArray(str);
+}
+
+SlowBuffer.byteLength = function (str, encoding) {
+ switch (encoding || "utf8") {
+ case 'hex':
+ return str.length / 2;
+
+ case 'utf8':
+ case 'utf-8':
+ return utf8ToBytes(str).length;
+
+ case 'ascii':
+ case 'binary':
+ return str.length;
+
+ case 'base64':
+ return base64ToBytes(str).length;
+
+ default:
+ throw new Error('Unknown encoding');
+ }
+};
+
+function blitBuffer(src, dst, offset, length) {
+ var pos, i = 0;
+ while (i < length) {
+ if ((i+offset >= dst.length) || (i >= src.length))
+ break;
+
+ dst[i + offset] = src[i];
+ i++;
+ }
+ return i;
+}
+
+SlowBuffer.prototype.utf8Write = function (string, offset, length) {
+ var bytes, pos;
+ return SlowBuffer._charsWritten = blitBuffer(utf8ToBytes(string), this, offset, length);
+};
+
+SlowBuffer.prototype.asciiWrite = function (string, offset, length) {
+ var bytes, pos;
+ return SlowBuffer._charsWritten = blitBuffer(asciiToBytes(string), this, offset, length);
+};
+
+SlowBuffer.prototype.binaryWrite = SlowBuffer.prototype.asciiWrite;
+
+SlowBuffer.prototype.base64Write = function (string, offset, length) {
+ var bytes, pos;
+ return SlowBuffer._charsWritten = blitBuffer(base64ToBytes(string), this, offset, length);
+};
+
+SlowBuffer.prototype.base64Slice = function (start, end) {
+ var bytes = Array.prototype.slice.apply(this, arguments)
+ return require("base64-js").fromByteArray(bytes);
+}
+
+function decodeUtf8Char(str) {
+ try {
+ return decodeURIComponent(str);
+ } catch (err) {
+ return String.fromCharCode(0xFFFD); // UTF 8 invalid char
+ }
+}
+
+SlowBuffer.prototype.utf8Slice = function () {
+ var bytes = Array.prototype.slice.apply(this, arguments);
+ var res = "";
+ var tmp = "";
+ var i = 0;
+ while (i < bytes.length) {
+ if (bytes[i] <= 0x7F) {
+ res += decodeUtf8Char(tmp) + String.fromCharCode(bytes[i]);
+ tmp = "";
+ } else
+ tmp += "%" + bytes[i].toString(16);
+
+ i++;
+ }
+
+ return res + decodeUtf8Char(tmp);
+}
+
+SlowBuffer.prototype.asciiSlice = function () {
+ var bytes = Array.prototype.slice.apply(this, arguments);
+ var ret = "";
+ for (var i = 0; i < bytes.length; i++)
+ ret += String.fromCharCode(bytes[i]);
+ return ret;
+}
+
+SlowBuffer.prototype.binarySlice = SlowBuffer.prototype.asciiSlice;
+
+SlowBuffer.prototype.inspect = function() {
+ var out = [],
+ len = this.length;
+ for (var i = 0; i < len; i++) {
+ out[i] = toHex(this[i]);
+ if (i == exports.INSPECT_MAX_BYTES) {
+ out[i + 1] = '...';
+ break;
+ }
+ }
+ return '<SlowBuffer ' + out.join(' ') + '>';
+};
+
+
+SlowBuffer.prototype.hexSlice = function(start, end) {
+ var len = this.length;
+
+ if (!start || start < 0) start = 0;
+ if (!end || end < 0 || end > len) end = len;
+
+ var out = '';
+ for (var i = start; i < end; i++) {
+ out += toHex(this[i]);
+ }
+ return out;
+};
+
+
+SlowBuffer.prototype.toString = function(encoding, start, end) {
+ encoding = String(encoding || 'utf8').toLowerCase();
+ start = +start || 0;
+ if (typeof end == 'undefined') end = this.length;
+
+ // Fastpath empty strings
+ if (+end == start) {
+ return '';
+ }
+
+ switch (encoding) {
+ case 'hex':
+ return this.hexSlice(start, end);
+
+ case 'utf8':
+ case 'utf-8':
+ return this.utf8Slice(start, end);
+
+ case 'ascii':
+ return this.asciiSlice(start, end);
+
+ case 'binary':
+ return this.binarySlice(start, end);
+
+ case 'base64':
+ return this.base64Slice(start, end);
+
+ case 'ucs2':
+ case 'ucs-2':
+ return this.ucs2Slice(start, end);
+
+ default:
+ throw new Error('Unknown encoding');
+ }
+};
+
+
+SlowBuffer.prototype.hexWrite = function(string, offset, length) {
+ offset = +offset || 0;
+ var remaining = this.length - offset;
+ if (!length) {
+ length = remaining;
+ } else {
+ length = +length;
+ if (length > remaining) {
+ length = remaining;
+ }
+ }
+
+ // must be an even number of digits
+ var strLen = string.length;
+ if (strLen % 2) {
+ throw new Error('Invalid hex string');
+ }
+ if (length > strLen / 2) {
+ length = strLen / 2;
+ }
+ for (var i = 0; i < length; i++) {
+ var byte = parseInt(string.substr(i * 2, 2), 16);
+ if (isNaN(byte)) throw new Error('Invalid hex string');
+ this[offset + i] = byte;
+ }
+ SlowBuffer._charsWritten = i * 2;
+ return i;
+};
+
+
+SlowBuffer.prototype.write = function(string, offset, length, encoding) {
+ // Support both (string, offset, length, encoding)
+ // and the legacy (string, encoding, offset, length)
+ if (isFinite(offset)) {
+ if (!isFinite(length)) {
+ encoding = length;
+ length = undefined;
+ }
+ } else { // legacy
+ var swap = encoding;
+ encoding = offset;
+ offset = length;
+ length = swap;
+ }
+
+ offset = +offset || 0;
+ var remaining = this.length - offset;
+ if (!length) {
+ length = remaining;
+ } else {
+ length = +length;
+ if (length > remaining) {
+ length = remaining;
+ }
+ }
+ encoding = String(encoding || 'utf8').toLowerCase();
+
+ switch (encoding) {
+ case 'hex':
+ return this.hexWrite(string, offset, length);
+
+ case 'utf8':
+ case 'utf-8':
+ return this.utf8Write(string, offset, length);
+
+ case 'ascii':
+ return this.asciiWrite(string, offset, length);
+
+ case 'binary':
+ return this.binaryWrite(string, offset, length);
+
+ case 'base64':
+ return this.base64Write(string, offset, length);
+
+ case 'ucs2':
+ case 'ucs-2':
+ return this.ucs2Write(string, offset, length);
+
+ default:
+ throw new Error('Unknown encoding');
+ }
+};
+
+
+// slice(start, end)
+SlowBuffer.prototype.slice = function(start, end) {
+ if (end === undefined) end = this.length;
+
+ if (end > this.length) {
+ throw new Error('oob');
+ }
+ if (start > end) {
+ throw new Error('oob');
+ }
+
+ return new Buffer(this, end - start, +start);
+};
+
+SlowBuffer.prototype.copy = function(target, targetstart, sourcestart, sourceend) {
+ var temp = [];
+ for (var i=sourcestart; i<sourceend; i++) {
+ assert.ok(typeof this[i] !== 'undefined', "copying undefined buffer bytes!");
+ temp.push(this[i]);
+ }
+
+ for (var i=targetstart; i<targetstart+temp.length; i++) {
+ target[i] = temp[i-targetstart];
+ }
+};
+
+SlowBuffer.prototype.fill = function(value, start, end) {
+ if (end > this.length) {
+ throw new Error('oob');
+ }
+ if (start > end) {
+ throw new Error('oob');
+ }
+
+ for (var i = start; i < end; i++) {
+ this[i] = value;
+ }
+}
+
+function coerce(length) {
+ // Coerce length to a number (possibly NaN), round up
+ // in case it's fractional (e.g. 123.456) then do a
+ // double negate to coerce a NaN to 0. Easy, right?
+ length = ~~Math.ceil(+length);
+ return length < 0 ? 0 : length;
+}
+
+
+// Buffer
+
+function Buffer(subject, encoding, offset) {
+ if (!(this instanceof Buffer)) {
+ return new Buffer(subject, encoding, offset);
+ }
+
+ var type;
+
+ // Are we slicing?
+ if (typeof offset === 'number') {
+ this.length = coerce(encoding);
+ this.parent = subject;
+ this.offset = offset;
+ } else {
+ // Find the length
+ switch (type = typeof subject) {
+ case 'number':
+ this.length = coerce(subject);
+ break;
+
+ case 'string':
+ this.length = Buffer.byteLength(subject, encoding);
+ break;
+
+ case 'object': // Assume object is an array
+ this.length = coerce(subject.length);
+ break;
+
+ default:
+ throw new Error('First argument needs to be a number, ' +
+ 'array or string.');
+ }
+
+ if (this.length > Buffer.poolSize) {
+ // Big buffer, just alloc one.
+ this.parent = new SlowBuffer(this.length);
+ this.offset = 0;
+
+ } else {
+ // Small buffer.
+ if (!pool || pool.length - pool.used < this.length) allocPool();
+ this.parent = pool;
+ this.offset = pool.used;
+ pool.used += this.length;
+ }
+
+ // Treat array-ish objects as a byte array.
+ if (isArrayIsh(subject)) {
+ for (var i = 0; i < this.length; i++) {
+ if (subject instanceof Buffer) {
+ this.parent[i + this.offset] = subject.readUInt8(i);
+ }
+ else {
+ this.parent[i + this.offset] = subject[i];
+ }
+ }
+ } else if (type == 'string') {
+ // We are a string
+ this.length = this.write(subject, 0, encoding);
+ }
+ }
+
+}
+
+function isArrayIsh(subject) {
+ return Array.isArray(subject) || Buffer.isBuffer(subject) ||
+ subject && typeof subject === 'object' &&
+ typeof subject.length === 'number';
+}
+
+exports.SlowBuffer = SlowBuffer;
+exports.Buffer = Buffer;
+
+Buffer.poolSize = 8 * 1024;
+var pool;
+
+function allocPool() {
+ pool = new SlowBuffer(Buffer.poolSize);
+ pool.used = 0;
+}
+
+
+// Static methods
+Buffer.isBuffer = function isBuffer(b) {
+ return b instanceof Buffer || b instanceof SlowBuffer;
+};
+
+Buffer.concat = function (list, totalLength) {
+ if (!Array.isArray(list)) {
+ throw new Error("Usage: Buffer.concat(list, [totalLength])\n \
+ list should be an Array.");
+ }
+
+ if (list.length === 0) {
+ return new Buffer(0);
+ } else if (list.length === 1) {
+ return list[0];
+ }
+
+ if (typeof totalLength !== 'number') {
+ totalLength = 0;
+ for (var i = 0; i < list.length; i++) {
+ var buf = list[i];
+ totalLength += buf.length;
+ }
+ }
+
+ var buffer = new Buffer(totalLength);
+ var pos = 0;
+ for (var i = 0; i < list.length; i++) {
+ var buf = list[i];
+ buf.copy(buffer, pos);
+ pos += buf.length;
+ }
+ return buffer;
+};
+
+// Inspect
+Buffer.prototype.inspect = function inspect() {
+ var out = [],
+ len = this.length;
+
+ for (var i = 0; i < len; i++) {
+ out[i] = toHex(this.parent[i + this.offset]);
+ if (i == exports.INSPECT_MAX_BYTES) {
+ out[i + 1] = '...';
+ break;
+ }
+ }
+
+ return '<Buffer ' + out.join(' ') + '>';
+};
+
+
+Buffer.prototype.get = function get(i) {
+ if (i < 0 || i >= this.length) throw new Error('oob');
+ return this.parent[this.offset + i];
+};
+
+
+Buffer.prototype.set = function set(i, v) {
+ if (i < 0 || i >= this.length) throw new Error('oob');
+ return this.parent[this.offset + i] = v;
+};
+
+
+// write(string, offset = 0, length = buffer.length-offset, encoding = 'utf8')
+Buffer.prototype.write = function(string, offset, length, encoding) {
+ // Support both (string, offset, length, encoding)
+ // and the legacy (string, encoding, offset, length)
+ if (isFinite(offset)) {
+ if (!isFinite(length)) {
+ encoding = length;
+ length = undefined;
+ }
+ } else { // legacy
+ var swap = encoding;
+ encoding = offset;
+ offset = length;
+ length = swap;
+ }
+
+ offset = +offset || 0;
+ var remaining = this.length - offset;
+ if (!length) {
+ length = remaining;
+ } else {
+ length = +length;
+ if (length > remaining) {
+ length = remaining;
+ }
+ }
+ encoding = String(encoding || 'utf8').toLowerCase();
+
+ var ret;
+ switch (encoding) {
+ case 'hex':
+ ret = this.parent.hexWrite(string, this.offset + offset, length);
+ break;
+
+ case 'utf8':
+ case 'utf-8':
+ ret = this.parent.utf8Write(string, this.offset + offset, length);
+ break;
+
+ case 'ascii':
+ ret = this.parent.asciiWrite(string, this.offset + offset, length);
+ break;
+
+ case 'binary':
+ ret = this.parent.binaryWrite(string, this.offset + offset, length);
+ break;
+
+ case 'base64':
+ // Warning: maxLength not taken into account in base64Write
+ ret = this.parent.base64Write(string, this.offset + offset, length);
+ break;
+
+ case 'ucs2':
+ case 'ucs-2':
+ ret = this.parent.ucs2Write(string, this.offset + offset, length);
+ break;
+
+ default:
+ throw new Error('Unknown encoding');
+ }
+
+ Buffer._charsWritten = SlowBuffer._charsWritten;
+
+ return ret;
+};
+
+
+// toString(encoding, start=0, end=buffer.length)
+Buffer.prototype.toString = function(encoding, start, end) {
+ encoding = String(encoding || 'utf8').toLowerCase();
+
+ if (typeof start == 'undefined' || start < 0) {
+ start = 0;
+ } else if (start > this.length) {
+ start = this.length;
+ }
+
+ if (typeof end == 'undefined' || end > this.length) {
+ end = this.length;
+ } else if (end < 0) {
+ end = 0;
+ }
+
+ start = start + this.offset;
+ end = end + this.offset;
+
+ switch (encoding) {
+ case 'hex':
+ return this.parent.hexSlice(start, end);
+
+ case 'utf8':
+ case 'utf-8':
+ return this.parent.utf8Slice(start, end);
+
+ case 'ascii':
+ return this.parent.asciiSlice(start, end);
+
+ case 'binary':
+ return this.parent.binarySlice(start, end);
+
+ case 'base64':
+ return this.parent.base64Slice(start, end);
+
+ case 'ucs2':
+ case 'ucs-2':
+ return this.parent.ucs2Slice(start, end);
+
+ default:
+ throw new Error('Unknown encoding');
+ }
+};
+
+
+// byteLength
+Buffer.byteLength = SlowBuffer.byteLength;
+
+
+// fill(value, start=0, end=buffer.length)
+Buffer.prototype.fill = function fill(value, start, end) {
+ value || (value = 0);
+ start || (start = 0);
+ end || (end = this.length);
+
+ if (typeof value === 'string') {
+ value = value.charCodeAt(0);
+ }
+ if (!(typeof value === 'number') || isNaN(value)) {
+ throw new Error('value is not a number');
+ }
+
+ if (end < start) throw new Error('end < start');
+
+ // Fill 0 bytes; we're done
+ if (end === start) return 0;
+ if (this.length == 0) return 0;
+
+ if (start < 0 || start >= this.length) {
+ throw new Error('start out of bounds');
+ }
+
+ if (end < 0 || end > this.length) {
+ throw new Error('end out of bounds');
+ }
+
+ return this.parent.fill(value,
+ start + this.offset,
+ end + this.offset);
+};
+
+
+// copy(targetBuffer, targetStart=0, sourceStart=0, sourceEnd=buffer.length)
+Buffer.prototype.copy = function(target, target_start, start, end) {
+ var source = this;
+ start || (start = 0);
+ end || (end = this.length);
+ target_start || (target_start = 0);
+
+ if (end < start) throw new Error('sourceEnd < sourceStart');
+
+ // Copy 0 bytes; we're done
+ if (end === start) return 0;
+ if (target.length == 0 || source.length == 0) return 0;
+
+ if (target_start < 0 || target_start >= target.length) {
+ throw new Error('targetStart out of bounds');
+ }
+
+ if (start < 0 || start >= source.length) {
+ throw new Error('sourceStart out of bounds');
+ }
+
+ if (end < 0 || end > source.length) {
+ throw new Error('sourceEnd out of bounds');
+ }
+
+ // Are we oob?
+ if (end > this.length) {
+ end = this.length;
+ }
+
+ if (target.length - target_start < end - start) {
+ end = target.length - target_start + start;
+ }
+
+ return this.parent.copy(target.parent,
+ target_start + target.offset,
+ start + this.offset,
+ end + this.offset);
+};
+
+
+// slice(start, end)
+Buffer.prototype.slice = function(start, end) {
+ if (end === undefined) end = this.length;
+ if (end > this.length) throw new Error('oob');
+ if (start > end) throw new Error('oob');
+
+ return new Buffer(this.parent, end - start, +start + this.offset);
+};
+
+
+// Legacy methods for backwards compatibility.
+
+Buffer.prototype.utf8Slice = function(start, end) {
+ return this.toString('utf8', start, end);
+};
+
+Buffer.prototype.binarySlice = function(start, end) {
+ return this.toString('binary', start, end);
+};
+
+Buffer.prototype.asciiSlice = function(start, end) {
+ return this.toString('ascii', start, end);
+};
+
+Buffer.prototype.utf8Write = function(string, offset) {
+ return this.write(string, offset, 'utf8');
+};
+
+Buffer.prototype.binaryWrite = function(string, offset) {
+ return this.write(string, offset, 'binary');
+};
+
+Buffer.prototype.asciiWrite = function(string, offset) {
+ return this.write(string, offset, 'ascii');
+};
+
+Buffer.prototype.readUInt8 = function(offset, noAssert) {
+ var buffer = this;
+
+ if (!noAssert) {
+ assert.ok(offset !== undefined && offset !== null,
+ 'missing offset');
+
+ assert.ok(offset < buffer.length,
+ 'Trying to read beyond buffer length');
+ }
+
+ if (offset >= buffer.length) return;
+
+ return buffer.parent[buffer.offset + offset];
+};
+
+function readUInt16(buffer, offset, isBigEndian, noAssert) {
+ var val = 0;
+
+
+ if (!noAssert) {
+ assert.ok(typeof (isBigEndian) === 'boolean',
+ 'missing or invalid endian');
+
+ assert.ok(offset !== undefined && offset !== null,
+ 'missing offset');
+
+ assert.ok(offset + 1 < buffer.length,
+ 'Trying to read beyond buffer length');
+ }
+
+ if (offset >= buffer.length) return 0;
+
+ if (isBigEndian) {
+ val = buffer.parent[buffer.offset + offset] << 8;
+ if (offset + 1 < buffer.length) {
+ val |= buffer.parent[buffer.offset + offset + 1];
+ }
+ } else {
+ val = buffer.parent[buffer.offset + offset];
+ if (offset + 1 < buffer.length) {
+ val |= buffer.parent[buffer.offset + offset + 1] << 8;
+ }
+ }
+
+ return val;
+}
+
+Buffer.prototype.readUInt16LE = function(offset, noAssert) {
+ return readUInt16(this, offset, false, noAssert);
+};
+
+Buffer.prototype.readUInt16BE = function(offset, noAssert) {
+ return readUInt16(this, offset, true, noAssert);
+};
+
+function readUInt32(buffer, offset, isBigEndian, noAssert) {
+ var val = 0;
+
+ if (!noAssert) {
+ assert.ok(typeof (isBigEndian) === 'boolean',
+ 'missing or invalid endian');
+
+ assert.ok(offset !== undefined && offset !== null,
+ 'missing offset');
+
+ assert.ok(offset + 3 < buffer.length,
+ 'Trying to read beyond buffer length');
+ }
+
+ if (offset >= buffer.length) return 0;
+
+ if (isBigEndian) {
+ if (offset + 1 < buffer.length)
+ val = buffer.parent[buffer.offset + offset + 1] << 16;
+ if (offset + 2 < buffer.length)
+ val |= buffer.parent[buffer.offset + offset + 2] << 8;
+ if (offset + 3 < buffer.length)
+ val |= buffer.parent[buffer.offset + offset + 3];
+ val = val + (buffer.parent[buffer.offset + offset] << 24 >>> 0);
+ } else {
+ if (offset + 2 < buffer.length)
+ val = buffer.parent[buffer.offset + offset + 2] << 16;
+ if (offset + 1 < buffer.length)
+ val |= buffer.parent[buffer.offset + offset + 1] << 8;
+ val |= buffer.parent[buffer.offset + offset];
+ if (offset + 3 < buffer.length)
+ val = val + (buffer.parent[buffer.offset + offset + 3] << 24 >>> 0);
+ }
+
+ return val;
+}
+
+Buffer.prototype.readUInt32LE = function(offset, noAssert) {
+ return readUInt32(this, offset, false, noAssert);
+};
+
+Buffer.prototype.readUInt32BE = function(offset, noAssert) {
+ return readUInt32(this, offset, true, noAssert);
+};
+
+
+/*
+ * Signed integer types, yay team! A reminder on how two's complement actually
+ * works. The first bit is the signed bit, i.e. tells us whether or not the
+ * number should be positive or negative. If the two's complement value is
+ * positive, then we're done, as it's equivalent to the unsigned representation.
+ *
+ * Now if the number is positive, you're pretty much done, you can just leverage
+ * the unsigned translations and return those. Unfortunately, negative numbers
+ * aren't quite that straightforward.
+ *
+ * At first glance, one might be inclined to use the traditional formula to
+ * translate binary numbers between the positive and negative values in two's
+ * complement. (Though it doesn't quite work for the most negative value)
+ * Mainly:
+ * - invert all the bits
+ * - add one to the result
+ *
+ * Of course, this doesn't quite work in Javascript. Take for example the value
+ * of -128. This could be represented in 16 bits (big-endian) as 0xff80. But of
+ * course, Javascript will do the following:
+ *
+ * > ~0xff80
+ * -65409
+ *
+ * Whoh there, Javascript, that's not quite right. But wait, according to
+ * Javascript that's perfectly correct. When Javascript ends up seeing the
+ * constant 0xff80, it has no notion that it is actually a signed number. It
+ * assumes that we've input the unsigned value 0xff80. Thus, when it does the
+ * binary negation, it casts it into a signed value, (positive 0xff80). Then
+ * when you perform binary negation on that, it turns it into a negative number.
+ *
+ * Instead, we're going to have to use the following general formula, that works
+ * in a rather Javascript friendly way. I'm glad we don't support this kind of
+ * weird numbering scheme in the kernel.
+ *
+ * (BIT-MAX - (unsigned)val + 1) * -1
+ *
+ * The astute observer, may think that this doesn't make sense for 8-bit numbers
+ * (really it isn't necessary for them). However, when you get 16-bit numbers,
+ * you do. Let's go back to our prior example and see how this will look:
+ *
+ * (0xffff - 0xff80 + 1) * -1
+ * (0x007f + 1) * -1
+ * (0x0080) * -1
+ */
+Buffer.prototype.readInt8 = function(offset, noAssert) {
+ var buffer = this;
+ var neg;
+
+ if (!noAssert) {
+ assert.ok(offset !== undefined && offset !== null,
+ 'missing offset');
+
+ assert.ok(offset < buffer.length,
+ 'Trying to read beyond buffer length');
+ }
+
+ if (offset >= buffer.length) return;
+
+ neg = buffer.parent[buffer.offset + offset] & 0x80;
+ if (!neg) {
+ return (buffer.parent[buffer.offset + offset]);
+ }
+
+ return ((0xff - buffer.parent[buffer.offset + offset] + 1) * -1);
+};
+
+function readInt16(buffer, offset, isBigEndian, noAssert) {
+ var neg, val;
+
+ if (!noAssert) {
+ assert.ok(typeof (isBigEndian) === 'boolean',
+ 'missing or invalid endian');
+
+ assert.ok(offset !== undefined && offset !== null,
+ 'missing offset');
+
+ assert.ok(offset + 1 < buffer.length,
+ 'Trying to read beyond buffer length');
+ }
+
+ val = readUInt16(buffer, offset, isBigEndian, noAssert);
+ neg = val & 0x8000;
+ if (!neg) {
+ return val;
+ }
+
+ return (0xffff - val + 1) * -1;
+}
+
+Buffer.prototype.readInt16LE = function(offset, noAssert) {
+ return readInt16(this, offset, false, noAssert);
+};
+
+Buffer.prototype.readInt16BE = function(offset, noAssert) {
+ return readInt16(this, offset, true, noAssert);
+};
+
+function readInt32(buffer, offset, isBigEndian, noAssert) {
+ var neg, val;
+
+ if (!noAssert) {
+ assert.ok(typeof (isBigEndian) === 'boolean',
+ 'missing or invalid endian');
+
+ assert.ok(offset !== undefined && offset !== null,
+ 'missing offset');
+
+ assert.ok(offset + 3 < buffer.length,
+ 'Trying to read beyond buffer length');
+ }
+
+ val = readUInt32(buffer, offset, isBigEndian, noAssert);
+ neg = val & 0x80000000;
+ if (!neg) {
+ return (val);
+ }
+
+ return (0xffffffff - val + 1) * -1;
+}
+
+Buffer.prototype.readInt32LE = function(offset, noAssert) {
+ return readInt32(this, offset, false, noAssert);
+};
+
+Buffer.prototype.readInt32BE = function(offset, noAssert) {
+ return readInt32(this, offset, true, noAssert);
+};
+
+function readFloat(buffer, offset, isBigEndian, noAssert) {
+ if (!noAssert) {
+ assert.ok(typeof (isBigEndian) === 'boolean',
+ 'missing or invalid endian');
+
+ assert.ok(offset + 3 < buffer.length,
+ 'Trying to read beyond buffer length');
+ }
+
+ return require('./buffer_ieee754').readIEEE754(buffer, offset, isBigEndian,
+ 23, 4);
+}
+
+Buffer.prototype.readFloatLE = function(offset, noAssert) {
+ return readFloat(this, offset, false, noAssert);
+};
+
+Buffer.prototype.readFloatBE = function(offset, noAssert) {
+ return readFloat(this, offset, true, noAssert);
+};
+
+function readDouble(buffer, offset, isBigEndian, noAssert) {
+ if (!noAssert) {
+ assert.ok(typeof (isBigEndian) === 'boolean',
+ 'missing or invalid endian');
+
+ assert.ok(offset + 7 < buffer.length,
+ 'Trying to read beyond buffer length');
+ }
+
+ return require('./buffer_ieee754').readIEEE754(buffer, offset, isBigEndian,
+ 52, 8);
+}
+
+Buffer.prototype.readDoubleLE = function(offset, noAssert) {
+ return readDouble(this, offset, false, noAssert);
+};
+
+Buffer.prototype.readDoubleBE = function(offset, noAssert) {
+ return readDouble(this, offset, true, noAssert);
+};
+
+
+/*
+ * We have to make sure that the value is a valid integer. This means that it is
+ * non-negative. It has no fractional component and that it does not exceed the
+ * maximum allowed value.
+ *
+ * value The number to check for validity
+ *
+ * max The maximum value
+ */
+function verifuint(value, max) {
+ assert.ok(typeof (value) == 'number',
+ 'cannot write a non-number as a number');
+
+ assert.ok(value >= 0,
+ 'specified a negative value for writing an unsigned value');
+
+ assert.ok(value <= max, 'value is larger than maximum value for type');
+
+ assert.ok(Math.floor(value) === value, 'value has a fractional component');
+}
+
+Buffer.prototype.writeUInt8 = function(value, offset, noAssert) {
+ var buffer = this;
+
+ if (!noAssert) {
+ assert.ok(value !== undefined && value !== null,
+ 'missing value');
+
+ assert.ok(offset !== undefined && offset !== null,
+ 'missing offset');
+
+ assert.ok(offset < buffer.length,
+ 'trying to write beyond buffer length');
+
+ verifuint(value, 0xff);
+ }
+
+ if (offset < buffer.length) {
+ buffer.parent[buffer.offset + offset] = value;
+ }
+};
+
+function writeUInt16(buffer, value, offset, isBigEndian, noAssert) {
+ if (!noAssert) {
+ assert.ok(value !== undefined && value !== null,
+ 'missing value');
+
+ assert.ok(typeof (isBigEndian) === 'boolean',
+ 'missing or invalid endian');
+
+ assert.ok(offset !== undefined && offset !== null,
+ 'missing offset');
+
+ assert.ok(offset + 1 < buffer.length,
+ 'trying to write beyond buffer length');
+
+ verifuint(value, 0xffff);
+ }
+
+ for (var i = 0; i < Math.min(buffer.length - offset, 2); i++) {
+ buffer.parent[buffer.offset + offset + i] =
+ (value & (0xff << (8 * (isBigEndian ? 1 - i : i)))) >>>
+ (isBigEndian ? 1 - i : i) * 8;
+ }
+
+}
+
+Buffer.prototype.writeUInt16LE = function(value, offset, noAssert) {
+ writeUInt16(this, value, offset, false, noAssert);
+};
+
+Buffer.prototype.writeUInt16BE = function(value, offset, noAssert) {
+ writeUInt16(this, value, offset, true, noAssert);
+};
+
+function writeUInt32(buffer, value, offset, isBigEndian, noAssert) {
+ if (!noAssert) {
+ assert.ok(value !== undefined && value !== null,
+ 'missing value');
+
+ assert.ok(typeof (isBigEndian) === 'boolean',
+ 'missing or invalid endian');
+
+ assert.ok(offset !== undefined && offset !== null,
+ 'missing offset');
+
+ assert.ok(offset + 3 < buffer.length,
+ 'trying to write beyond buffer length');
+
+ verifuint(value, 0xffffffff);
+ }
+
+ for (var i = 0; i < Math.min(buffer.length - offset, 4); i++) {
+ buffer.parent[buffer.offset + offset + i] =
+ (value >>> (isBigEndian ? 3 - i : i) * 8) & 0xff;
+ }
+}
+
+Buffer.prototype.writeUInt32LE = function(value, offset, noAssert) {
+ writeUInt32(this, value, offset, false, noAssert);
+};
+
+Buffer.prototype.writeUInt32BE = function(value, offset, noAssert) {
+ writeUInt32(this, value, offset, true, noAssert);
+};
+
+
+/*
+ * We now move onto our friends in the signed number category. Unlike unsigned
+ * numbers, we're going to have to worry a bit more about how we put values into
+ * arrays. Since we are only worrying about signed 32-bit values, we're in
+ * slightly better shape. Unfortunately, we really can't do our favorite binary
+ * & in this system. It really seems to do the wrong thing. For example:
+ *
+ * > -32 & 0xff
+ * 224
+ *
+ * What's happening above is really: 0xe0 & 0xff = 0xe0. However, the results of
+ * this aren't treated as a signed number. Ultimately a bad thing.
+ *
+ * What we're going to want to do is basically create the unsigned equivalent of
+ * our representation and pass that off to the wuint* functions. To do that
+ * we're going to do the following:
+ *
+ * - if the value is positive
+ * we can pass it directly off to the equivalent wuint
+ * - if the value is negative
+ * we do the following computation:
+ * mb + val + 1, where
+ * mb is the maximum unsigned value in that byte size
+ * val is the Javascript negative integer
+ *
+ *
+ * As a concrete value, take -128. In signed 16 bits this would be 0xff80. If
+ * you do out the computations:
+ *
+ * 0xffff - 128 + 1
+ * 0xffff - 127
+ * 0xff80
+ *
+ * You can then encode this value as the signed version. This is really rather
+ * hacky, but it should work and get the job done which is our goal here.
+ */
+
+/*
+ * A series of checks to make sure we actually have a signed 32-bit number
+ */
+function verifsint(value, max, min) {
+ assert.ok(typeof (value) == 'number',
+ 'cannot write a non-number as a number');
+
+ assert.ok(value <= max, 'value larger than maximum allowed value');
+
+ assert.ok(value >= min, 'value smaller than minimum allowed value');
+
+ assert.ok(Math.floor(value) === value, 'value has a fractional component');
+}
+
+function verifIEEE754(value, max, min) {
+ assert.ok(typeof (value) == 'number',
+ 'cannot write a non-number as a number');
+
+ assert.ok(value <= max, 'value larger than maximum allowed value');
+
+ assert.ok(value >= min, 'value smaller than minimum allowed value');
+}
+
+Buffer.prototype.writeInt8 = function(value, offset, noAssert) {
+ var buffer = this;
+
+ if (!noAssert) {
+ assert.ok(value !== undefined && value !== null,
+ 'missing value');
+
+ assert.ok(offset !== undefined && offset !== null,
+ 'missing offset');
+
+ assert.ok(offset < buffer.length,
+ 'Trying to write beyond buffer length');
+
+ verifsint(value, 0x7f, -0x80);
+ }
+
+ if (value >= 0) {
+ buffer.writeUInt8(value, offset, noAssert);
+ } else {
+ buffer.writeUInt8(0xff + value + 1, offset, noAssert);
+ }
+};
+
+function writeInt16(buffer, value, offset, isBigEndian, noAssert) {
+ if (!noAssert) {
+ assert.ok(value !== undefined && value !== null,
+ 'missing value');
+
+ assert.ok(typeof (isBigEndian) === 'boolean',
+ 'missing or invalid endian');
+
+ assert.ok(offset !== undefined && offset !== null,
+ 'missing offset');
+
+ assert.ok(offset + 1 < buffer.length,
+ 'Trying to write beyond buffer length');
+
+ verifsint(value, 0x7fff, -0x8000);
+ }
+
+ if (value >= 0) {
+ writeUInt16(buffer, value, offset, isBigEndian, noAssert);
+ } else {
+ writeUInt16(buffer, 0xffff + value + 1, offset, isBigEndian, noAssert);
+ }
+}
+
+Buffer.prototype.writeInt16LE = function(value, offset, noAssert) {
+ writeInt16(this, value, offset, false, noAssert);
+};
+
+Buffer.prototype.writeInt16BE = function(value, offset, noAssert) {
+ writeInt16(this, value, offset, true, noAssert);
+};
+
+function writeInt32(buffer, value, offset, isBigEndian, noAssert) {
+ if (!noAssert) {
+ assert.ok(value !== undefined && value !== null,
+ 'missing value');
+
+ assert.ok(typeof (isBigEndian) === 'boolean',
+ 'missing or invalid endian');
+
+ assert.ok(offset !== undefined && offset !== null,
+ 'missing offset');
+
+ assert.ok(offset + 3 < buffer.length,
+ 'Trying to write beyond buffer length');
+
+ verifsint(value, 0x7fffffff, -0x80000000);
+ }
+
+ if (value >= 0) {
+ writeUInt32(buffer, value, offset, isBigEndian, noAssert);
+ } else {
+ writeUInt32(buffer, 0xffffffff + value + 1, offset, isBigEndian, noAssert);
+ }
+}
+
+Buffer.prototype.writeInt32LE = function(value, offset, noAssert) {
+ writeInt32(this, value, offset, false, noAssert);
+};
+
+Buffer.prototype.writeInt32BE = function(value, offset, noAssert) {
+ writeInt32(this, value, offset, true, noAssert);
+};
+
+function writeFloat(buffer, value, offset, isBigEndian, noAssert) {
+ if (!noAssert) {
+ assert.ok(value !== undefined && value !== null,
+ 'missing value');
+
+ assert.ok(typeof (isBigEndian) === 'boolean',
+ 'missing or invalid endian');
+
+ assert.ok(offset !== undefined && offset !== null,
+ 'missing offset');
+
+ assert.ok(offset + 3 < buffer.length,
+ 'Trying to write beyond buffer length');
+
+ verifIEEE754(value, 3.4028234663852886e+38, -3.4028234663852886e+38);
+ }
+
+ require('./buffer_ieee754').writeIEEE754(buffer, value, offset, isBigEndian,
+ 23, 4);
+}
+
+Buffer.prototype.writeFloatLE = function(value, offset, noAssert) {
+ writeFloat(this, value, offset, false, noAssert);
+};
+
+Buffer.prototype.writeFloatBE = function(value, offset, noAssert) {
+ writeFloat(this, value, offset, true, noAssert);
+};
+
+function writeDouble(buffer, value, offset, isBigEndian, noAssert) {
+ if (!noAssert) {
+ assert.ok(value !== undefined && value !== null,
+ 'missing value');
+
+ assert.ok(typeof (isBigEndian) === 'boolean',
+ 'missing or invalid endian');
+
+ assert.ok(offset !== undefined && offset !== null,
+ 'missing offset');
+
+ assert.ok(offset + 7 < buffer.length,
+ 'Trying to write beyond buffer length');
+
+ verifIEEE754(value, 1.7976931348623157E+308, -1.7976931348623157E+308);
+ }
+
+ require('./buffer_ieee754').writeIEEE754(buffer, value, offset, isBigEndian,
+ 52, 8);
+}
+
+Buffer.prototype.writeDoubleLE = function(value, offset, noAssert) {
+ writeDouble(this, value, offset, false, noAssert);
+};
+
+Buffer.prototype.writeDoubleBE = function(value, offset, noAssert) {
+ writeDouble(this, value, offset, true, noAssert);
+};
+
+SlowBuffer.prototype.readUInt8 = Buffer.prototype.readUInt8;
+SlowBuffer.prototype.readUInt16LE = Buffer.prototype.readUInt16LE;
+SlowBuffer.prototype.readUInt16BE = Buffer.prototype.readUInt16BE;
+SlowBuffer.prototype.readUInt32LE = Buffer.prototype.readUInt32LE;
+SlowBuffer.prototype.readUInt32BE = Buffer.prototype.readUInt32BE;
+SlowBuffer.prototype.readInt8 = Buffer.prototype.readInt8;
+SlowBuffer.prototype.readInt16LE = Buffer.prototype.readInt16LE;
+SlowBuffer.prototype.readInt16BE = Buffer.prototype.readInt16BE;
+SlowBuffer.prototype.readInt32LE = Buffer.prototype.readInt32LE;
+SlowBuffer.prototype.readInt32BE = Buffer.prototype.readInt32BE;
+SlowBuffer.prototype.readFloatLE = Buffer.prototype.readFloatLE;
+SlowBuffer.prototype.readFloatBE = Buffer.prototype.readFloatBE;
+SlowBuffer.prototype.readDoubleLE = Buffer.prototype.readDoubleLE;
+SlowBuffer.prototype.readDoubleBE = Buffer.prototype.readDoubleBE;
+SlowBuffer.prototype.writeUInt8 = Buffer.prototype.writeUInt8;
+SlowBuffer.prototype.writeUInt16LE = Buffer.prototype.writeUInt16LE;
+SlowBuffer.prototype.writeUInt16BE = Buffer.prototype.writeUInt16BE;
+SlowBuffer.prototype.writeUInt32LE = Buffer.prototype.writeUInt32LE;
+SlowBuffer.prototype.writeUInt32BE = Buffer.prototype.writeUInt32BE;
+SlowBuffer.prototype.writeInt8 = Buffer.prototype.writeInt8;
+SlowBuffer.prototype.writeInt16LE = Buffer.prototype.writeInt16LE;
+SlowBuffer.prototype.writeInt16BE = Buffer.prototype.writeInt16BE;
+SlowBuffer.prototype.writeInt32LE = Buffer.prototype.writeInt32LE;
+SlowBuffer.prototype.writeInt32BE = Buffer.prototype.writeInt32BE;
+SlowBuffer.prototype.writeFloatLE = Buffer.prototype.writeFloatLE;
+SlowBuffer.prototype.writeFloatBE = Buffer.prototype.writeFloatBE;
+SlowBuffer.prototype.writeDoubleLE = Buffer.prototype.writeDoubleLE;
+SlowBuffer.prototype.writeDoubleBE = Buffer.prototype.writeDoubleBE;
+
+})()
+},{"assert":9,"./buffer_ieee754":14,"base64-js":15}],15:[function(require,module,exports){
+(function (exports) {
+ 'use strict';
+
+ var lookup = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
+
+ function b64ToByteArray(b64) {
+ var i, j, l, tmp, placeHolders, arr;
+
+ if (b64.length % 4 > 0) {
+ throw 'Invalid string. Length must be a multiple of 4';
+ }
+
+ // the number of equal signs (place holders)
+ // if there are two placeholders, than the two characters before it
+ // represent one byte
+ // if there is only one, then the three characters before it represent 2 bytes
+ // this is just a cheap hack to not do indexOf twice
+ placeHolders = b64.indexOf('=');
+ placeHolders = placeHolders > 0 ? b64.length - placeHolders : 0;
+
+ // base64 is 4/3 + up to two characters of the original data
+ arr = [];//new Uint8Array(b64.length * 3 / 4 - placeHolders);
+
+ // if there are placeholders, only get up to the last complete 4 chars
+ l = placeHolders > 0 ? b64.length - 4 : b64.length;
+
+ for (i = 0, j = 0; i < l; i += 4, j += 3) {
+ tmp = (lookup.indexOf(b64[i]) << 18) | (lookup.indexOf(b64[i + 1]) << 12) | (lookup.indexOf(b64[i + 2]) << 6) | lookup.indexOf(b64[i + 3]);
+ arr.push((tmp & 0xFF0000) >> 16);
+ arr.push((tmp & 0xFF00) >> 8);
+ arr.push(tmp & 0xFF);
+ }
+
+ if (placeHolders === 2) {
+ tmp = (lookup.indexOf(b64[i]) << 2) | (lookup.indexOf(b64[i + 1]) >> 4);
+ arr.push(tmp & 0xFF);
+ } else if (placeHolders === 1) {
+ tmp = (lookup.indexOf(b64[i]) << 10) | (lookup.indexOf(b64[i + 1]) << 4) | (lookup.indexOf(b64[i + 2]) >> 2);
+ arr.push((tmp >> 8) & 0xFF);
+ arr.push(tmp & 0xFF);
+ }
+
+ return arr;
+ }
+
+ function uint8ToBase64(uint8) {
+ var i,
+ extraBytes = uint8.length % 3, // if we have 1 byte left, pad 2 bytes
+ output = "",
+ temp, length;
+
+ function tripletToBase64 (num) {
+ return lookup[num >> 18 & 0x3F] + lookup[num >> 12 & 0x3F] + lookup[num >> 6 & 0x3F] + lookup[num & 0x3F];
+ };
+
+ // go through the array every three bytes, we'll deal with trailing stuff later
+ for (i = 0, length = uint8.length - extraBytes; i < length; i += 3) {
+ temp = (uint8[i] << 16) + (uint8[i + 1] << 8) + (uint8[i + 2]);
+ output += tripletToBase64(temp);
+ }
+
+ // pad the end with zeros, but make sure to not forget the extra bytes
+ switch (extraBytes) {
+ case 1:
+ temp = uint8[uint8.length - 1];
+ output += lookup[temp >> 2];
+ output += lookup[(temp << 4) & 0x3F];
+ output += '==';
+ break;
+ case 2:
+ temp = (uint8[uint8.length - 2] << 8) + (uint8[uint8.length - 1]);
+ output += lookup[temp >> 10];
+ output += lookup[(temp >> 4) & 0x3F];
+ output += lookup[(temp << 2) & 0x3F];
+ output += '=';
+ break;
+ }
+
+ return output;
+ }
+
+ module.exports.toByteArray = b64ToByteArray;
+ module.exports.fromByteArray = uint8ToBase64;
+}());
+
+},{}]},{},["E/GbHF"])
+;
+JSHINT = require('jshint').JSHINT;
+}());
diff --git a/dom/system/gonk/tests/marionette/ril_jshint/jshintrc b/dom/system/gonk/tests/marionette/ril_jshint/jshintrc
new file mode 100644
index 000000000..437fe1a6f
--- /dev/null
+++ b/dom/system/gonk/tests/marionette/ril_jshint/jshintrc
@@ -0,0 +1,118 @@
+{
+ // JSHint Default Configuration File (as on JSHint website)
+ // See http://jshint.com/docs/ for more details
+
+ // Modify for RIL usage.
+
+ "maxerr" : 10000, // {int} Maximum error before stopping
+
+ // Enforcing
+ "bitwise" : false, // true: Prohibit bitwise operators (&, |, ^, etc.)
+ "camelcase" : false, // true: Identifiers must be in camelCase
+ "curly" : false, // true: Require {} for every new block or scope
+ "eqeqeq" : false, // true: Require triple equals (===) for comparison
+ "forin" : false, // true: Require filtering for..in loops with obj.hasOwnProperty()
+ "immed" : false, // true: Require immediate invocations to be wrapped in parens e.g. `(function () { } ());`
+ //"indent" : 2, // {int} Number of spaces to use for indentation
+ "latedef" : false, // true: Require variables/functions to be defined before being used
+ "newcap" : false, // true: Require capitalization of all constructor functions e.g. `new F()`
+ "noarg" : true, // true: Prohibit use of `arguments.caller` and `arguments.callee`
+ "noempty" : false, // true: Prohibit use of empty blocks
+ "nonew" : false, // true: Prohibit use of constructors for side-effects (without assignment)
+ "plusplus" : false, // true: Prohibit use of `++` & `--`
+ "quotmark" : false, // Quotation mark consistency:
+ // false : do nothing (default)
+ // true : ensure whatever is used is consistent
+ // "single" : require single quotes
+ // "double" : require double quotes
+ "undef" : false, // true: Require all non-global variables to be declared (prevents global leaks)
+ "unused" : false, // true: Require all defined variables be used
+ "strict" : false, // true: Requires all functions run in ES5 Strict Mode
+ "trailing" : false, // true: Prohibit trailing whitespaces
+ "maxparams" : false, // {int} Max number of formal params allowed per function
+ "maxdepth" : false, // {int} Max depth of nested blocks (within functions)
+ "maxstatements" : false, // {int} Max number statements per function
+ "maxcomplexity" : false, // {int} Max cyclomatic complexity per function
+ "maxlen" : false, // {int} Max number of characters per line
+
+ // Relaxing
+ "asi" : false, // true: Tolerate Automatic Semicolon Insertion (no semicolons)
+ "boss" : false, // true: Tolerate assignments where comparisons would be expected
+ "debug" : false, // true: Allow debugger statements e.g. browser breakpoints.
+ "eqnull" : true, // true: Tolerate use of `== null`
+ "es5" : false, // true: Allow ES5 syntax (ex: getters and setters)
+ "esnext" : false, // true: Allow ES.next (ES6) syntax (ex: `const`)
+ "moz" : true, // true: Allow Mozilla specific syntax (extends and overrides esnext features)
+ // (ex: `for each`, multiple try/catch, function expression…)
+ "evil" : false, // true: Tolerate use of `eval` and `new Function()`
+ "expr" : false, // true: Tolerate `ExpressionStatement` as Programs
+ "funcscope" : false, // true: Tolerate defining variables inside control statements"
+ "globalstrict" : true, // true: Allow global "use strict" (also enables 'strict')
+ "iterator" : false, // true: Tolerate using the `__iterator__` property
+ "lastsemic" : false, // true: Tolerate omitting a semicolon for the last statement of a 1-line block
+ "laxbreak" : true, // true: Tolerate possibly unsafe line breakings
+ "laxcomma" : false, // true: Tolerate comma-first style coding
+ "loopfunc" : false, // true: Tolerate functions being defined in loops
+ "multistr" : false, // true: Tolerate multi-line strings
+ "proto" : true, // true: Tolerate using the `__proto__` property
+ "scripturl" : false, // true: Tolerate script-targeted URLs
+ "smarttabs" : false, // true: Tolerate mixed tabs/spaces when used for alignment
+ "shadow" : false, // true: Allows re-define variables later in code e.g. `var x=1; x=2;`
+ "sub" : false, // true: Tolerate using `[]` notation when it can still be expressed in dot notation
+ "supernew" : false, // true: Tolerate `new function () { ... };` and `new Object;`
+ "validthis" : true, // true: Tolerate using this in a non-constructor function
+
+ // Environments
+ "browser" : false, // Web Browser (window, document, etc)
+ "couch" : false, // CouchDB
+ "devel" : true, // Development/debugging (alert, confirm, etc)
+ "dojo" : false, // Dojo Toolkit
+ "jquery" : false, // jQuery
+ "mootools" : false, // MooTools
+ "node" : false, // Node.js
+ "nonstandard" : false, // Widely adopted globals (escape, unescape, etc)
+ "prototypejs" : false, // Prototype and Scriptaculous
+ "rhino" : false, // Rhino
+ "worker" : true, // Web Workers
+ "wsh" : false, // Windows Scripting Host
+ "yui" : false, // Yahoo User Interface
+
+ // Legacy
+ "nomen" : false, // true: Prohibit dangling `_` in variables
+ "onevar" : false, // true: Allow only one `var` statement per function
+ "passfail" : false, // true: Stop on first error
+ "white" : false, // true: Check against strict whitespace and indentation rules
+
+ // Custom Globals
+ "predef" : [ ], // additional predefined global variables
+
+ "globals": {
+ "ChromeWorker": false,
+ "Components": false,
+ "DOMRequestIpcHelper": false,
+ "ObjectWrapper": false,
+ "PhoneNumberUtils": false,
+ "RILNetworkInterface": false,
+ "Services": false,
+ "Uint8Array": false,
+ "WAP": false,
+ "XPCOMUtils": false,
+ "cpmm": false,
+ "dump": false,
+ "gAudioManager": false,
+ "gMessageManager": false,
+ "gMobileMessageDatabaseService": false,
+ "gMobileMessageService": false,
+ "gNetworkManager": false,
+ "gPowerManagerService": false,
+ "gSettingsService": false,
+ "gSmsService": false,
+ "gSystemMessenger": false,
+ "gSystemWorkerManager": false,
+ "gTimeService": false,
+ "gUUIDGenerator": false,
+ "ppmm": true,
+
+ "__end_guardian_for_easy_sorting__": false
+ }
+}
diff --git a/dom/system/gonk/tests/marionette/test_all_network_info.js b/dom/system/gonk/tests/marionette/test_all_network_info.js
new file mode 100644
index 000000000..5225ab6d6
--- /dev/null
+++ b/dom/system/gonk/tests/marionette/test_all_network_info.js
@@ -0,0 +1,106 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+MARIONETTE_TIMEOUT = 60000;
+MARIONETTE_HEAD_JS = "head.js";
+
+var networkManager =
+ Cc["@mozilla.org/network/manager;1"].getService(Ci.nsINetworkManager);
+ok(networkManager,
+ "networkManager.constructor is " + networkManager.constructor);
+
+var wifiManager = window.navigator.mozWifiManager;
+ok(wifiManager, "wifiManager.constructor is " + wifiManager.constructor);
+
+function setEmulatorAPN() {
+ let apn = [
+ [{"carrier":"T-Mobile US",
+ "apn":"epc.tmobile.com",
+ "mmsc":"http://mms.msg.eng.t-mobile.com/mms/wapenc",
+ "types":["default","supl","mms","ims","dun", "fota"]}]
+ ];
+
+ return setSettings(SETTINGS_KEY_DATA_APN_SETTINGS, apn);
+}
+
+function ensureWifiEnabled(aEnabled) {
+ if (wifiManager.enabled === aEnabled) {
+ log('Already ' + (aEnabled ? 'enabled' : 'disabled'));
+ return Promise.resolve();
+ }
+ return requestWifiEnabled(aEnabled);
+}
+
+function requestWifiEnabled(aEnabled) {
+ let promises = [];
+
+ promises.push(waitForTargetEvent(wifiManager, aEnabled ? 'enabled' : 'disabled',
+ function() {
+ return wifiManager.enabled === aEnabled ? true : false;
+ }));
+ promises.push(setSettings(SETTINGS_KEY_WIFI_ENABLED, aEnabled));
+
+ return Promise.all(promises);
+}
+
+// Test initial State
+function verifyInitialState() {
+ log("= verifyInitialState =");
+
+ // Data and wifi should be off before starting any test.
+ return getSettings(SETTINGS_KEY_DATA_ENABLED)
+ .then(value => {
+ is(value, false, "Data must be off");
+ })
+ .then(() => ensureWifiEnabled(false));
+}
+
+function testAllNetworkInfo(aAnyConnected) {
+ log("= testAllNetworkInfo = " + aAnyConnected);
+
+ let allNetworkInfo = networkManager.allNetworkInfo;
+ ok(allNetworkInfo, "NetworkManager.allNetworkInfo");
+
+ let count = Object.keys(allNetworkInfo).length;
+ ok(count > 0, "NetworkManager.allNetworkInfo count");
+
+ let connected = false;
+ for (let networkId in allNetworkInfo) {
+ if (allNetworkInfo.hasOwnProperty(networkId)) {
+ let networkInfo = allNetworkInfo[networkId];
+ if (networkInfo.state == Ci.nsINetworkInfo.NETWORK_STATE_CONNECTED) {
+ connected = true;
+ break;
+ }
+ }
+ }
+
+ is(aAnyConnected, connected, "NetworkManager.allNetworkInfo any connected");
+}
+
+// Start test
+startTestBase(function() {
+
+ let origApnSettings, origWifiEnabled;
+ return Promise.resolve()
+ .then(() => {
+ origWifiEnabled = wifiManager.enabled;
+ })
+ .then(() => verifyInitialState())
+ .then(() => getSettings(SETTINGS_KEY_DATA_APN_SETTINGS))
+ .then(value => {
+ origApnSettings = value;
+ })
+ .then(() => setEmulatorAPN())
+ .then(() => setDataEnabledAndWait(true))
+ .then(() => testAllNetworkInfo(true))
+ .then(() => setDataEnabledAndWait(false))
+ .then(() => testAllNetworkInfo(false))
+ // Restore original apn settings and wifi state.
+ .then(() => {
+ if (origApnSettings) {
+ return setSettings(SETTINGS_KEY_DATA_APN_SETTINGS, origApnSettings);
+ }
+ })
+ .then(() => ensureWifiEnabled(origWifiEnabled));
+});
diff --git a/dom/system/gonk/tests/marionette/test_data_connection.js b/dom/system/gonk/tests/marionette/test_data_connection.js
new file mode 100644
index 000000000..5a53b1e5f
--- /dev/null
+++ b/dom/system/gonk/tests/marionette/test_data_connection.js
@@ -0,0 +1,70 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+MARIONETTE_TIMEOUT = 60000;
+MARIONETTE_HEAD_JS = "head.js";
+
+function setEmulatorAPN() {
+ let apn = [
+ [{"carrier":"T-Mobile US",
+ "apn":"epc.tmobile.com",
+ "mmsc":"http://mms.msg.eng.t-mobile.com/mms/wapenc",
+ "types":["default","supl","mms","ims","dun", "fota"]}]
+ ];
+
+ return setSettings(SETTINGS_KEY_DATA_APN_SETTINGS, apn);
+}
+
+// Test initial State
+function testInitialState() {
+ log("= testInitialState =");
+
+ // Data should be off before starting any test.
+ return getSettings(SETTINGS_KEY_DATA_ENABLED)
+ .then(value => {
+ is(value, false, "Data must be off");
+ });
+}
+
+// Test default data Connection
+function testDefaultDataConnection() {
+ log("= testDefaultDataConnection =");
+
+ // Enable default data
+ return setDataEnabledAndWait(true)
+ // Disable default data
+ .then(() => setDataEnabledAndWait(false));
+}
+
+// Test non default data connection
+function testNonDefaultDataConnection() {
+ log("= testNonDefaultDataConnection =");
+
+ function doTestNonDefaultDataConnection(type) {
+ log("doTestNonDefaultDataConnection: " + type);
+
+ return setupDataCallAndWait(type)
+ .then(() => deactivateDataCallAndWait(type));
+ }
+
+ let currentApn;
+ return getSettings(SETTINGS_KEY_DATA_APN_SETTINGS)
+ .then(value => {
+ currentApn = value;
+ })
+ .then(setEmulatorAPN)
+ .then(() => doTestNonDefaultDataConnection(NETWORK_TYPE_MOBILE_MMS))
+ .then(() => doTestNonDefaultDataConnection(NETWORK_TYPE_MOBILE_SUPL))
+ .then(() => doTestNonDefaultDataConnection(NETWORK_TYPE_MOBILE_IMS))
+ .then(() => doTestNonDefaultDataConnection(NETWORK_TYPE_MOBILE_DUN))
+ .then(() => doTestNonDefaultDataConnection(NETWORK_TYPE_MOBILE_FOTA))
+ // Restore APN settings
+ .then(() => setSettings(SETTINGS_KEY_DATA_APN_SETTINGS, currentApn));
+}
+
+// Start test
+startTestBase(function() {
+ return testInitialState()
+ .then(() => testDefaultDataConnection())
+ .then(() => testNonDefaultDataConnection());
+});
diff --git a/dom/system/gonk/tests/marionette/test_data_connection_proxy.js b/dom/system/gonk/tests/marionette/test_data_connection_proxy.js
new file mode 100644
index 000000000..a99187538
--- /dev/null
+++ b/dom/system/gonk/tests/marionette/test_data_connection_proxy.js
@@ -0,0 +1,99 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+MARIONETTE_TIMEOUT = 60000;
+MARIONETTE_HEAD_JS = "head.js";
+
+const HTTP_PROXY = "10.0.2.200";
+const HTTP_PROXY_PORT = "8080";
+const MANUAL_PROXY_CONFIGURATION = 1;
+
+// Test initial State
+function verifyInitialState() {
+ log("= verifyInitialState =");
+
+ // Data should be off before starting any test.
+ return getSettings(SETTINGS_KEY_DATA_ENABLED)
+ .then(value => {
+ is(value, false, "Data must be off");
+ });
+}
+
+function setTestApn() {
+ let apn = [
+ [ {"carrier": "T-Mobile US",
+ "apn": "epc.tmobile.com",
+ "proxy": HTTP_PROXY,
+ "port": HTTP_PROXY_PORT,
+ "mmsc": "http://mms.msg.eng.t-mobile.com/mms/wapenc",
+ "types": ["default","supl","mms"]} ]
+ ];
+
+ return setSettings(SETTINGS_KEY_DATA_APN_SETTINGS, apn);
+}
+
+function waitForHttpProxyVerified(aShouldBeSet) {
+ let TIME_OUT_VALUE = 20000;
+
+ return new Promise(function(aResolve, aReject) {
+ try {
+ waitFor(aResolve, () => {
+ let proxyType = SpecialPowers.getIntPref("network.proxy.type");
+ let httpProxy = SpecialPowers.getCharPref("network.proxy.http");
+ let sslProxy = SpecialPowers.getCharPref("network.proxy.ssl");
+ let httpProxyPort = SpecialPowers.getIntPref("network.proxy.http_port");
+ let sslProxyPort = SpecialPowers.getIntPref("network.proxy.ssl_port");
+
+ if ((aShouldBeSet &&
+ proxyType == MANUAL_PROXY_CONFIGURATION &&
+ httpProxy == HTTP_PROXY &&
+ sslProxy == HTTP_PROXY &&
+ httpProxyPort == HTTP_PROXY_PORT &&
+ sslProxyPort == HTTP_PROXY_PORT) ||
+ (!aShouldBeSet && proxyType != MANUAL_PROXY_CONFIGURATION &&
+ !httpProxy && !sslProxy && !httpProxyPort && !sslProxyPort)) {
+ return true;
+ }
+
+ return false;
+ }, TIME_OUT_VALUE);
+ } catch(aError) {
+ // Timed out.
+ aReject(aError);
+ }
+ });
+}
+
+function testDefaultDataHttpProxy() {
+ log("= testDefaultDataHttpProxy =");
+
+ return setDataEnabledAndWait(true)
+ .then(() => waitForHttpProxyVerified(true))
+ .then(() => setDataEnabledAndWait(false))
+ .then(() => waitForHttpProxyVerified(false));
+}
+
+function testNonDefaultDataHttpProxy(aType) {
+ log("= testNonDefaultDataHttpProxy - " + aType + " =");
+
+ return setupDataCallAndWait(aType)
+ // Http proxy should not be set for non-default data connections.
+ .then(() => waitForHttpProxyVerified(false))
+ .then(() => deactivateDataCallAndWait(aType));
+}
+
+// Start test
+startTestBase(function() {
+ let origApnSettings;
+ return verifyInitialState()
+ .then(() => getSettings(SETTINGS_KEY_DATA_APN_SETTINGS))
+ .then(value => {
+ origApnSettings = value;
+ })
+ .then(() => setTestApn())
+ .then(() => testDefaultDataHttpProxy())
+ .then(() => testNonDefaultDataHttpProxy(NETWORK_TYPE_MOBILE_MMS))
+ .then(() => testNonDefaultDataHttpProxy(NETWORK_TYPE_MOBILE_SUPL))
+ // Restore APN settings
+ .then(() => setSettings(SETTINGS_KEY_DATA_APN_SETTINGS, origApnSettings));
+});
diff --git a/dom/system/gonk/tests/marionette/test_dsds_numRadioInterfaces.js b/dom/system/gonk/tests/marionette/test_dsds_numRadioInterfaces.js
new file mode 100644
index 000000000..e178b8b65
--- /dev/null
+++ b/dom/system/gonk/tests/marionette/test_dsds_numRadioInterfaces.js
@@ -0,0 +1,43 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+MARIONETTE_TIMEOUT = 60000;
+MARIONETTE_CONTEXT = "chrome";
+
+Cu.import("resource://gre/modules/Promise.jsm");
+Cu.import("resource://gre/modules/systemlibs.js");
+
+const NS_RIL_CONTRACTID = "@mozilla.org/ril;1";
+
+const PROP_RO_MOZ_RIL_NUMCLIENTS = "ro.moz.ril.numclients";
+
+const PREF_RIL_NUM_RADIO_INTERFACES = "ril.numRadioInterfaces";
+
+ok(libcutils, "libcutils is available");
+
+var propNum = (function() {
+ try {
+ let numString = libcutils.property_get(PROP_RO_MOZ_RIL_NUMCLIENTS, "1");
+ let num = parseInt(numString, 10);
+ if (num >= 0) {
+ return num;
+ }
+ } catch (e) {}
+})();
+
+log("Retrieved '" + PROP_RO_MOZ_RIL_NUMCLIENTS + "' = " + propNum);
+ok(propNum, PROP_RO_MOZ_RIL_NUMCLIENTS);
+
+var prefNum = Services.prefs.getIntPref(PREF_RIL_NUM_RADIO_INTERFACES);
+log("Retrieved '" + PREF_RIL_NUM_RADIO_INTERFACES + "' = " + prefNum);
+
+var ril = Cc[NS_RIL_CONTRACTID].getService(Ci.nsIRadioInterfaceLayer);
+ok(ril, "ril.constructor is " + ril.constructor);
+
+var ifaceNum = ril.numRadioInterfaces;
+log("Retrieved 'nsIRadioInterfaceLayer.numRadioInterfaces' = " + ifaceNum);
+
+is(propNum, prefNum);
+is(propNum, ifaceNum);
+
+finish();
diff --git a/dom/system/gonk/tests/marionette/test_fakevolume.js b/dom/system/gonk/tests/marionette/test_fakevolume.js
new file mode 100644
index 000000000..173f9ac11
--- /dev/null
+++ b/dom/system/gonk/tests/marionette/test_fakevolume.js
@@ -0,0 +1,25 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+MARIONETTE_TIMEOUT = 10000;
+
+var Cc = SpecialPowers.Cc;
+var Ci = SpecialPowers.Ci;
+
+var volumeService = Cc["@mozilla.org/telephony/volume-service;1"].getService(Ci.nsIVolumeService);
+ok(volumeService, "Should have volume service");
+
+var volName = "fake";
+var mountPoint = "/data/fake/storage";
+volumeService.createFakeVolume(volName, mountPoint);
+
+var vol = volumeService.getVolumeByName(volName);
+ok(vol, "volume shouldn't be null");
+
+is(volName, vol.name, "name");
+is(mountPoint, vol.mountPoint, "moutnPoint");
+is(Ci.nsIVolume.STATE_MOUNTED, vol.state, "state");
+
+ok(vol.mountGeneration > 0, "mount generation should not be zero");
+
+finish();
diff --git a/dom/system/gonk/tests/marionette/test_geolocation.js b/dom/system/gonk/tests/marionette/test_geolocation.js
new file mode 100644
index 000000000..201c8b3e3
--- /dev/null
+++ b/dom/system/gonk/tests/marionette/test_geolocation.js
@@ -0,0 +1,117 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+MARIONETTE_TIMEOUT = 10000;
+
+var geolocation = window.navigator.geolocation;
+ok(geolocation);
+
+var sample = [];
+var result = [];
+var wpid;
+
+/**
+ * Grant special power to get the geolocation
+ */
+SpecialPowers.addPermission("geolocation", true, document);
+
+/**
+ * Disable wifi geolocation provider
+ */
+wifiUri = SpecialPowers.getCharPref("geo.wifi.uri");
+SpecialPowers.setCharPref("geo.wifi.uri", "http://mochi.test:8888/tests/dom/tests/mochitest/geolocation/network_geolocation.sjs?action=stop-responding");
+
+/**
+ * Helper that compares the geolocation against the web API.
+ */
+function verifyLocation() {
+
+ log("Sample:" + sample.join(','));
+ log("Result:" + result.join(','));
+
+ for (i in sample) {
+ is(sample.pop(), result.pop());
+ }
+
+ window.setTimeout(cleanup, 0);
+}
+
+/**
+ * Test story begins here.
+ */
+function setup() {
+ log("Providing initial setup: set geographic position watcher.");
+
+
+ wpid = geolocation.watchPosition(function(position) {
+ log("Position changes: (" + position.coords.latitude + "/" + position.coords.longitude + ")");
+ result.push(""+position.coords.latitude + "/" + position.coords.longitude);
+ });
+
+ lat = 0;
+ lon = 0;
+
+ cmd = "geo fix " + lon + " " + lat;
+ sample.push(lat+"/"+lon);
+
+ runEmulatorCmd(cmd, function(result) {
+ window.setTimeout(movePosition_1, 0);
+ });
+}
+
+function movePosition_1() {
+ log("Geolocation changes. Move to Position 1.");
+
+ lat = 25;
+ lon = 121.56499833333334;
+
+ cmd = "geo fix " + lon + " " + lat;
+ sample.push(lat+"/"+lon);
+
+ runEmulatorCmd(cmd, function(result) {
+ window.setTimeout(movePosition_2, 0);
+ });
+}
+
+function movePosition_2() {
+ log("Geolocation changes to a negative longitude. Move to Position 2.");
+
+ lat = 37.393;
+ lon = -122.08199833333335;
+
+ cmd = "geo fix " + lon + " " + lat;
+ sample.push(lat+"/"+lon);
+
+ runEmulatorCmd(cmd, function(result) {
+ window.setTimeout(movePosition_3, 0);
+ });
+}
+
+function movePosition_3() {
+ log("Geolocation changes with WatchPosition. Move to Position 3.");
+
+ lat = -22;
+ lon = -43;
+
+ cmd = "geo fix " + lon + " " + lat;
+ sample.push(lat+"/"+lon);
+
+ geolocation.getCurrentPosition(function(position) {
+ log("getCurrentPosition: Expected location: ("+lat+"/"+lon+"); Current location: (" + position.coords.latitude + "/" + position.coords.longitude + ")");
+ is(lat, position.coords.latitude);
+ is(lon, position.coords.longitude);
+ });
+
+ runEmulatorCmd(cmd, function(result) {
+ window.setTimeout(verifyLocation, 0);
+ });
+}
+
+function cleanup() {
+ geolocation.clearWatch(wpid);
+ SpecialPowers.removePermission("geolocation", document);
+ SpecialPowers.setCharPref("geo.wifi.uri", wifiUri);
+ finish();
+}
+
+setup();
diff --git a/dom/system/gonk/tests/marionette/test_multiple_data_connection.js b/dom/system/gonk/tests/marionette/test_multiple_data_connection.js
new file mode 100644
index 000000000..24abd4451
--- /dev/null
+++ b/dom/system/gonk/tests/marionette/test_multiple_data_connection.js
@@ -0,0 +1,89 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+MARIONETTE_TIMEOUT = 60000;
+MARIONETTE_HEAD_JS = "head.js";
+
+// Must sync with hardware/ril/reference-ril/reference-ril.c
+const MAX_DATA_CONTEXTS = 4;
+
+function setEmulatorAPN() {
+ // Use different apn for each network type.
+ let apn = [[ { "carrier":"T-Mobile US",
+ "apn":"epc1.tmobile.com",
+ "types":["default"] },
+ { "carrier":"T-Mobile US",
+ "apn":"epc2.tmobile.com",
+ "mmsc":"http://mms.msg.eng.t-mobile.com/mms/wapenc",
+ "types":["mms"] },
+ { "carrier":"T-Mobile US",
+ "apn":"epc3.tmobile.com",
+ "types":["supl"] },
+ { "carrier":"T-Mobile US",
+ "apn":"epc4.tmobile.com",
+ "types":["ims"] },
+ { "carrier":"T-Mobile US",
+ "apn":"epc5.tmobile.com",
+ "types":["dun"] },
+ { "carrier":"T-Mobile US",
+ "apn":"epc6.tmobile.com",
+ "types":["fota"] }]];
+
+ return setSettings(SETTINGS_KEY_DATA_APN_SETTINGS, apn);
+}
+
+// Test initial State
+function testInitialState() {
+ log("= testInitialState =");
+
+ // Data should be off before starting any test.
+ return getSettings(SETTINGS_KEY_DATA_ENABLED)
+ .then(value => {
+ is(value, false, "Data must be off");
+ });
+}
+
+function testSetupConcurrentDataCalls() {
+ log("= testSetupConcurrentDataCalls =");
+
+ let promise = Promise.resolve();
+ // Skip default mobile type.
+ for (let i = 1; i < MAX_DATA_CONTEXTS; i++) {
+ let type = networkTypes[i];
+ promise = promise.then(() => setupDataCallAndWait(type));
+ }
+ return promise;
+}
+
+function testDeactivateConcurrentDataCalls() {
+ log("= testDeactivateConcurrentDataCalls =");
+
+ let promise = Promise.resolve();
+ // Skip default mobile type.
+ for (let i = 1; i < MAX_DATA_CONTEXTS; i++) {
+ let type = networkTypes[i];
+ promise = promise.then(() => deactivateDataCallAndWait(type));
+ }
+ return promise;
+}
+
+// Start test
+startTestBase(function() {
+
+ let origApnSettings;
+ return testInitialState()
+ .then(() => getSettings(SETTINGS_KEY_DATA_APN_SETTINGS))
+ .then(value => {
+ origApnSettings = value;
+ })
+ .then(() => setEmulatorAPN())
+ .then(() => setDataEnabledAndWait(true))
+ .then(() => testSetupConcurrentDataCalls())
+ .then(() => testDeactivateConcurrentDataCalls())
+ .then(() => setDataEnabledAndWait(false))
+ .then(() => {
+ if (origApnSettings) {
+ return setSettings(SETTINGS_KEY_DATA_APN_SETTINGS, origApnSettings);
+ }
+ });
+});
diff --git a/dom/system/gonk/tests/marionette/test_network_active_changed.js b/dom/system/gonk/tests/marionette/test_network_active_changed.js
new file mode 100644
index 000000000..5886f37ed
--- /dev/null
+++ b/dom/system/gonk/tests/marionette/test_network_active_changed.js
@@ -0,0 +1,52 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+MARIONETTE_TIMEOUT = 60000;
+MARIONETTE_HEAD_JS = "head.js";
+
+var networkManager =
+ Cc["@mozilla.org/network/manager;1"].getService(Ci.nsINetworkManager);
+ok(networkManager,
+ "networkManager.constructor is " + networkManager.constructor);
+
+function testInitialState() {
+ return getSettings(SETTINGS_KEY_DATA_ENABLED)
+ .then((enabled) => {
+ is(enabled, false, "data should be off by default");
+ is(networkManager.activeNetworkInfo, null,
+ "networkManager.activeNetworkInfo should be null by default");
+ });
+}
+
+function testActiveNetworkChangedBySwitchingDataCall(aDataCallEnabled) {
+ log("Test active network by switching dataCallEnabled to " + aDataCallEnabled);
+
+ let promises = [];
+ promises.push(waitForObserverEvent(TOPIC_NETWORK_ACTIVE_CHANGED));
+ promises.push(setSettings(SETTINGS_KEY_DATA_ENABLED, aDataCallEnabled));
+
+ return Promise.all(promises).then(function(results) {
+ let subject = results[0];
+
+ if (aDataCallEnabled) {
+ ok(subject instanceof Ci.nsINetworkInfo,
+ "subject should be an instance of nsINetworkInfo");
+ ok(subject instanceof Ci.nsIRilNetworkInfo,
+ "subject should be an instance of nsIRilNetworkInfo");
+ is(subject.type, NETWORK_TYPE_MOBILE,
+ "subject.type should be NETWORK_TYPE_MOBILE");
+ }
+
+ is(subject, networkManager.activeNetworkInfo,
+ "subject should be equal with networkManager.activeNetworkInfo");
+ });
+}
+
+// Start test
+startTestBase(function() {
+ return testInitialState()
+ // Test active network changed by enabling data call.
+ .then(() => testActiveNetworkChangedBySwitchingDataCall(true))
+ // Test active network changed by disabling data call.
+ .then(() => testActiveNetworkChangedBySwitchingDataCall(false));
+});
diff --git a/dom/system/gonk/tests/marionette/test_network_interface_list_service.js b/dom/system/gonk/tests/marionette/test_network_interface_list_service.js
new file mode 100644
index 000000000..549940fa5
--- /dev/null
+++ b/dom/system/gonk/tests/marionette/test_network_interface_list_service.js
@@ -0,0 +1,95 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+MARIONETTE_TIMEOUT = 60000;
+MARIONETTE_HEAD_JS = "head.js";
+
+function getNetworkInfo(aType) {
+ let networkListService =
+ Cc["@mozilla.org/network/interface-list-service;1"].
+ getService(Ci.nsINetworkInterfaceListService);
+ // Get all available interfaces
+ let networkList = networkListService.getDataInterfaceList(0);
+
+ // Try to get nsINetworkInterface for aType.
+ let numberOfInterface = networkList.getNumberOfInterface();
+ for (let i = 0; i < numberOfInterface; i++) {
+ let info = networkList.getInterfaceInfo(i);
+ if (info.type === aType) {
+ return info;
+ }
+ }
+
+ return null;
+}
+
+// Test getDataInterfaceList by enabling/disabling mobile data.
+function testGetDataInterfaceList(aMobileDataEnabled) {
+ log("Test getDataInterfaceList with mobile data " +
+ aMobileDataEnabled ? "enabled" : "disabled");
+
+ return setDataEnabledAndWait(aMobileDataEnabled)
+ .then(() => getNetworkInfo(NETWORK_TYPE_MOBILE))
+ .then((networkInfo) => {
+ if (!networkInfo) {
+ ok(false, "Should get an valid nsINetworkInfo for mobile");
+ return;
+ }
+
+ ok(networkInfo instanceof Ci.nsINetworkInfo,
+ "networkInfo should be an instance of nsINetworkInfo");
+
+ let ipAddresses = {};
+ let prefixs = {};
+ let numOfGateways = {};
+ let numOfDnses = {};
+ let numOfIpAddresses = networkInfo.getAddresses(ipAddresses, prefixs);
+ let gateways = networkInfo.getGateways(numOfGateways);
+ let dnses = networkInfo.getDnses(numOfDnses);
+
+ if (aMobileDataEnabled) {
+ // Mobile data is enabled.
+ is(networkInfo.state, NETWORK_STATE_CONNECTED, "check state");
+ ok(numOfIpAddresses > 0, "check number of ipAddresses");
+ ok(ipAddresses.value.length > 0, "check ipAddresses.length");
+ ok(prefixs.value.length > 0, "check prefixs.length");
+ ok(numOfGateways.value > 0, "check number of gateways");
+ ok(prefixs.value.length > 0, "check prefixs.length");
+ ok(gateways.length > 0, "check gateways.length");
+ ok(numOfDnses.value > 0, "check number of dnses");
+ ok(dnses.length > 0, "check dnses.length");
+ } else {
+ // Mobile data is disabled.
+ is(networkInfo.state, NETWORK_STATE_DISCONNECTED, "check state");
+ is(numOfIpAddresses, 0, "check number of ipAddresses");
+ is(ipAddresses.value.length, 0, "check ipAddresses.length");
+ is(prefixs.value.length, 0, "check prefixs.length");
+ is(numOfGateways.value, 0, "check number of gateways");
+ is(prefixs.value.length, 0, "check prefixs.length");
+ is(gateways.length, 0, "check gateways.length");
+ is(numOfDnses.value, 0, "check number of dnses");
+ is(dnses.length, 0, "check dnses.length");
+ }
+ });
+}
+
+// Start test
+startTestBase(function() {
+ return Promise.resolve()
+ // Test initial State
+ .then(() => {
+ log("Test initial state");
+
+ // Data should be off before starting any test.
+ return getSettings(SETTINGS_KEY_DATA_ENABLED)
+ .then(value => {
+ is(value, false, "Mobile data must be off");
+ });
+ })
+
+ // Test getDataInterfaceList with mobile data enabled.
+ .then(() => testGetDataInterfaceList(true))
+
+ // Test getDataInterfaceList with mobile data disabled.
+ .then(() => testGetDataInterfaceList(false));
+});
diff --git a/dom/system/gonk/tests/marionette/test_network_interface_mtu.js b/dom/system/gonk/tests/marionette/test_network_interface_mtu.js
new file mode 100644
index 000000000..679efe2ed
--- /dev/null
+++ b/dom/system/gonk/tests/marionette/test_network_interface_mtu.js
@@ -0,0 +1,100 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+MARIONETTE_TIMEOUT = 60000;
+MARIONETTE_HEAD_JS = "head.js";
+
+const TEST_MTU1 = "1410";
+const TEST_MTU2 = "1440";
+
+function setEmulatorAPN() {
+ let apn = [
+ [ { "carrier":"T-Mobile US",
+ "apn":"epc1.tmobile.com",
+ "types":["default"],
+ "mtu": TEST_MTU1 },
+ { "carrier":"T-Mobile US",
+ "apn":"epc2.tmobile.com",
+ "mmsc":"http://mms.msg.eng.t-mobile.com/mms/wapenc",
+ "types":["supl","mms","ims","dun", "fota"],
+ "mtu": TEST_MTU2 } ]
+ ];
+
+ return setSettings(SETTINGS_KEY_DATA_APN_SETTINGS, apn);
+}
+
+function verifyInitialState() {
+ // Data should be off before starting any test.
+ return getSettings(SETTINGS_KEY_DATA_ENABLED)
+ .then(value => {
+ is(value, false, "Data must be off");
+ });
+}
+
+function verifyMtu(aInterfaceName, aMtu) {
+ return runEmulatorShellCmdSafe(['ip', 'link', 'show', 'dev', aInterfaceName])
+ .then(aLines => {
+ // Sample output:
+ //
+ // 4: rmnet0: <BROADCAST,MULTICAST> mtu 1410 qdisc pfifo_fast state DOWN mode DEFAULT qlen 1000
+ // link/ether 52:54:00:12:34:58 brd ff:ff:ff:ff:ff:ff
+ //
+ let mtu;
+ aLines.some(function (aLine) {
+ let tokens = aLine.trim().split(/\s+/);
+ let mtuIndex = tokens.indexOf('mtu');
+ if (mtuIndex < 0 || mtuIndex + 1 >= tokens.length) {
+ return false;
+ }
+
+ mtu = tokens[mtuIndex + 1];
+ return true;
+ });
+
+ is(mtu, aMtu, aInterfaceName + "'s mtu.");
+ });
+}
+
+function testDefaultDataCallMtu() {
+ log("= testDefaultDataCallMtu =");
+
+ return setDataEnabledAndWait(true)
+ .then(aNetworkInfo => verifyMtu(aNetworkInfo.name, TEST_MTU1))
+ .then(() => setDataEnabledAndWait(false));
+}
+
+function testNonDefaultDataCallMtu() {
+ log("= testNonDefaultDataCallMtu =");
+
+ function doTestNonDefaultDataCallMtu(aType) {
+ log("doTestNonDefaultDataCallMtu: " + aType);
+
+ return setupDataCallAndWait(aType)
+ .then(aNetworkInfo => verifyMtu(aNetworkInfo.name, TEST_MTU2))
+ .then(() => deactivateDataCallAndWait(aType));
+ }
+
+ return doTestNonDefaultDataCallMtu(NETWORK_TYPE_MOBILE_MMS)
+ .then(() => doTestNonDefaultDataCallMtu(NETWORK_TYPE_MOBILE_SUPL))
+ .then(() => doTestNonDefaultDataCallMtu(NETWORK_TYPE_MOBILE_IMS))
+ .then(() => doTestNonDefaultDataCallMtu(NETWORK_TYPE_MOBILE_DUN))
+ .then(() => doTestNonDefaultDataCallMtu(NETWORK_TYPE_MOBILE_FOTA));
+}
+
+// Start test
+startTestBase(function() {
+ let origApnSettings;
+ return verifyInitialState()
+ .then(() => getSettings(SETTINGS_KEY_DATA_APN_SETTINGS))
+ .then(value => {
+ origApnSettings = value;
+ })
+ .then(() => setEmulatorAPN())
+ .then(() => testDefaultDataCallMtu())
+ .then(() => testNonDefaultDataCallMtu())
+ .then(() => {
+ if (origApnSettings) {
+ return setSettings(SETTINGS_KEY_DATA_APN_SETTINGS, origApnSettings);
+ }
+ });
+});
diff --git a/dom/system/gonk/tests/marionette/test_ril_code_quality.py b/dom/system/gonk/tests/marionette/test_ril_code_quality.py
new file mode 100644
index 000000000..d741d8a2e
--- /dev/null
+++ b/dom/system/gonk/tests/marionette/test_ril_code_quality.py
@@ -0,0 +1,371 @@
+"""
+The test performs the static code analysis check by JSHint.
+
+Target js files:
+- RadioInterfaceLayer.js
+- ril_worker.js
+- ril_consts.js
+
+If the js file contains the line of 'importScript()' (Ex: ril_worker.js), the
+test will perform a special merge step before excuting JSHint.
+
+Ex: Script A
+--------------------------------
+importScripts('Script B')
+...
+--------------------------------
+
+We merge these two scripts into one by the following way.
+
+--------------------------------
+[Script B (ex: ril_consts.js)]
+(function(){ [Script A (ex: ril_worker.js)]
+})();
+--------------------------------
+
+Script A (ril_worker.js) runs global strict mode.
+Script B (ril_consts.js) not.
+
+The above merge way ensures the correct scope of 'strict mode.'
+"""
+
+import bisect
+import inspect
+import os
+import os.path
+import re
+import unicodedata
+
+from marionette_harness import MarionetteTestCase
+
+
+class StringUtility:
+
+ """A collection of some string utilities."""
+
+ @staticmethod
+ def find_match_lines(lines, pattern):
+ """Return a list of lines that contains given pattern."""
+ return [line for line in lines if pattern in line]
+
+ @staticmethod
+ def remove_non_ascii(data):
+ """Remove non ascii characters in data and return it as new string."""
+ if type(data).__name__ == 'unicode':
+ data = unicodedata.normalize(
+ 'NFKD', data).encode('ascii', 'ignore')
+ return data
+
+ @staticmethod
+ def auto_close(lines):
+ """Ensure every line ends with '\n'."""
+ if lines and not lines[-1].endswith('\n'):
+ lines[-1] += '\n'
+ return lines
+
+ @staticmethod
+ def auto_wrap_strict_mode(lines):
+ """Wrap by function scope if lines contain 'use strict'."""
+ if StringUtility.find_match_lines(lines, 'use strict'):
+ lines[0] = '(function(){' + lines[0]
+ lines.append('})();\n')
+ return lines
+
+ @staticmethod
+ def get_imported_list(lines):
+ """Get a list of imported items."""
+ return [item
+ for line in StringUtility.find_match_lines(lines, 'importScripts')
+ for item in StringUtility._get_imported_list_from_line(line)]
+
+ @staticmethod
+ def _get_imported_list_from_line(line):
+ """Extract all items from 'importScripts(...)'.
+
+ importScripts("ril_consts.js", "systemlibs.js")
+ => ['ril_consts', 'systemlibs.js']
+
+ """
+ pattern = re.compile(r'\s*importScripts\((.*)\)')
+ m = pattern.match(line)
+ if not m:
+ raise Exception('Parse importScripts error.')
+ return [name.translate(None, '\' "') for name in m.group(1).split(',')]
+
+
+class ResourceUriFileReader:
+
+ """Handle the process of reading the source code from system."""
+
+ URI_PREFIX = 'resource://gre/'
+ URI_PATH = {
+ 'RadioInterfaceLayer.js': 'components/RadioInterfaceLayer.js',
+ 'ril_worker.js': 'modules/ril_worker.js',
+ 'ril_consts.js': 'modules/ril_consts.js',
+ 'systemlibs.js': 'modules/systemlibs.js',
+ 'worker_buf.js': 'modules/workers/worker_buf.js',
+ }
+
+ CODE_OPEN_CHANNEL_BY_URI = '''
+ var Cc = Components.classes;
+ var Ci = Components.interfaces;
+ var ios = Cc["@mozilla.org/network/io-service;1"].getService(Ci.nsIIOService);
+ var secMan = Cc["@mozilla.org/scriptsecuritymanager;1"].getService(Ci.nsIScriptSecurityManager);
+ global.uri = '%(uri)s';
+ global.channel = ios.newChannel2(global.uri,
+ null,
+ null,
+ null, // aLoadingNode
+ secMan.getSystemPrincipal(),
+ null, // aTriggeringPrincipal
+ Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL,
+ Ci.nsIContentPolicy.TYPE_OTHER);
+ '''
+
+ CODE_GET_SPEC = '''
+ return global.channel.URI.spec;
+ '''
+
+ CODE_READ_CONTENT = '''
+ var Cc = Components.classes;
+ var Ci = Components.interfaces;
+
+ var zipReader = Cc["@mozilla.org/libjar/zip-reader;1"].createInstance(Ci.nsIZipReader);
+ var inputStream = Cc["@mozilla.org/scriptableinputstream;1"].createInstance(Ci.nsIScriptableInputStream);
+
+ var jaruri = global.channel.URI.QueryInterface(Ci.nsIJARURI);
+ var file = jaruri.JARFile.QueryInterface(Ci.nsIFileURL).file;
+ var entry = jaruri.JAREntry;
+ zipReader.open(file);
+ inputStream.init(zipReader.getInputStream(entry));
+ var content = inputStream.read(inputStream.available());
+ inputStream.close();
+ zipReader.close();
+ return content;
+ '''
+
+ @classmethod
+ def get_uri(cls, filename):
+ """Convert filename to URI in system."""
+ if filename.startswith(cls.URI_PREFIX):
+ return filename
+ else:
+ return cls.URI_PREFIX + cls.URI_PATH[filename]
+
+ def __init__(self, marionette):
+ self.runjs = lambda x: marionette.execute_script(x,
+ new_sandbox=False,
+ sandbox='system')
+
+ def read_file(self, filename):
+ """Read file and return the contents as string."""
+ content = self._read_uri(self.get_uri(filename))
+ content = content.replace('"use strict";', '')
+ return StringUtility.remove_non_ascii(content)
+
+ def _read_uri(self, uri):
+ """Read URI in system and return the contents as string."""
+ # Open the uri as a channel.
+ self.runjs(self.CODE_OPEN_CHANNEL_BY_URI % {'uri': uri})
+
+ # Make sure spec is a jar uri, and not recursive.
+ # Ex: 'jar:file:///system/b2g/omni.ja!/modules/ril_worker.js'
+ #
+ # For simplicity, we don't handle other special cases in this test.
+ # If B2G build system changes in the future, such as put the jar in
+ # another jar, the test case will fail.
+ spec = self.runjs(self.CODE_GET_SPEC)
+ if (not spec.startswith('jar:file://')) or (spec.count('jar:') != 1):
+ raise Exception('URI resolve error')
+
+ # Read the content from channel.
+ content = self.runjs(self.CODE_READ_CONTENT)
+ return content
+
+
+class JSHintEngine:
+
+ """Invoke jshint script on system."""
+
+ CODE_INIT_JSHINT = '''
+ %(script)s;
+ global.JSHINT = JSHINT;
+ global.options = JSON.parse(%(config_string)s);
+ global.globals = global.options.globals;
+ delete global.options.globals;
+ '''
+
+ CODE_RUN_JSHINT = '''
+ global.script = %(code)s;
+ return global.JSHINT(global.script, global.options, global.globals);
+ '''
+
+ CODE_GET_JSHINT_ERROR = '''
+ return global.JSHINT.errors;
+ '''
+
+ def __init__(self, marionette, script, config):
+ # Remove single line comment in config.
+ config = '\n'.join([line.partition('//')[0]
+ for line in config.splitlines()])
+
+ # Set global (JSHINT, options, global) in js environment.
+ self.runjs = lambda x: marionette.execute_script(x,
+ new_sandbox=False,
+ sandbox='system')
+ self.runjs(self.CODE_INIT_JSHINT %
+ {'script': script, 'config_string': repr(config)})
+
+ def run(self, code, filename=''):
+ """Excute JShint check for the given code."""
+ check_pass = self.runjs(self.CODE_RUN_JSHINT % {'code': repr(code)})
+ errors = self.runjs(self.CODE_GET_JSHINT_ERROR)
+ return check_pass, self._get_error_messages(errors, filename)
+
+ def _get_error_messages(self, errors, filename=''):
+ """
+ Convert an error object to a list of readable string.
+
+ [{"a": null, "c": null, "code": "W033", "d": null, "character": 6,
+ "evidence": "var a", "raw": "Missing semicolon.",
+ "reason": "Missing semicolon.", "b": null, "scope": "(main)", "line": 1,
+ "id": "(error)"}]
+ => line 1, col 6, Missing semicolon.
+
+ """
+ LINE, COL, REASON = u'line', u'character', u'reason'
+ return ["%s: line %s, col %s, %s" %
+ (filename, error[LINE], error[COL], error[REASON])
+ for error in errors if error]
+
+
+class Linter:
+
+ """Handle the linting related process."""
+
+ def __init__(self, code_reader, jshint, reporter=None):
+ """Set the linter with code_reader, jshint engine, and reporter.
+
+ Should have following functionality.
+ - code_reader.read_file(filename)
+ - jshint.run(code, filename)
+ - reporter([...])
+
+ """
+ self.code_reader = code_reader
+ self.jshint = jshint
+ if reporter is None:
+ self.reporter = lambda x: '\n'.join(x)
+ else:
+ self.reporter = reporter
+
+ def lint_file(self, filename):
+ """Lint the file and return (pass, error_message)."""
+ # Get code contents.
+ code = self.code_reader.read_file(filename)
+ lines = code.splitlines()
+ import_list = StringUtility.get_imported_list(lines)
+ if not import_list:
+ check_pass, error_message = self.jshint.run(code, filename)
+ else:
+ newlines, info = self._merge_multiple_codes(filename, import_list)
+ # Each line of |newlines| contains '\n'.
+ check_pass, error_message = self.jshint.run(''.join(newlines))
+ error_message = self._convert_merged_result(error_message, info)
+ # Only keep errors for this file.
+ error_message = [line for line in error_message
+ if line.startswith(filename)]
+ check_pass = (len(error_message) == 0)
+ return check_pass, self.reporter(error_message)
+
+ def _merge_multiple_codes(self, filename, import_list):
+ """Merge multiple codes from filename and import_list."""
+ dirname, filename = os.path.split(filename)
+ dst_line = 1
+ dst_results = []
+ info = []
+
+ # Put the imported script first, and then the original script.
+ for f in import_list + [filename]:
+ filepath = os.path.join(dirname, f)
+
+ # Maintain a mapping table.
+ # New line number after merge => original file and line number.
+ info.append((dst_line, filepath, 1))
+ try:
+ code = self.code_reader.read_file(filepath)
+ lines = code.splitlines(True) # Keep '\n'.
+ src_results = StringUtility.auto_wrap_strict_mode(
+ StringUtility.auto_close(lines))
+ dst_results.extend(src_results)
+ dst_line += len(src_results)
+ except:
+ info.pop()
+ return dst_results, info
+
+ def _convert_merged_result(self, error_lines, line_info):
+ pattern = re.compile(r'(.*): line (\d+),(.*)')
+ start_line = [info[0] for info in line_info]
+ new_result_lines = []
+ for line in error_lines:
+ m = pattern.match(line)
+ if not m:
+ continue
+
+ line_number, remain = int(m.group(2)), m.group(3)
+
+ # [1, 2, 7, 8]
+ # ^ for 7, pos = 3
+ # ^ for 6, pos = 2
+ pos = bisect.bisect_right(start_line, line_number)
+ dst_line, name, src_line = line_info[pos - 1]
+ real_line_number = line_number - dst_line + src_line
+ new_result_lines.append(
+ "%s: line %s,%s" % (name, real_line_number, remain))
+ return new_result_lines
+
+
+class TestRILCodeQuality(MarionetteTestCase):
+
+ JSHINT_PATH = 'ril_jshint/jshint.js'
+ JSHINTRC_PATH = 'ril_jshint/jshintrc'
+
+ def _read_local_file(self, filepath):
+ """Read file content from local (folder of this test case)."""
+ test_dir = os.path.dirname(inspect.getfile(TestRILCodeQuality))
+ return open(os.path.join(test_dir, filepath)).read()
+
+ def _get_extended_error_message(self, error_message):
+ return '\n'.join(['See errors below and more information in Bug 880643',
+ '\n'.join(error_message),
+ 'See errors above and more information in Bug 880643'])
+
+ def _check(self, filename):
+ check_pass, error_message = self.linter.lint_file(filename)
+ self.assertTrue(check_pass, error_message)
+
+ def setUp(self):
+ MarionetteTestCase.setUp(self)
+ self.linter = Linter(
+ ResourceUriFileReader(self.marionette),
+ JSHintEngine(self.marionette,
+ self._read_local_file(self.JSHINT_PATH),
+ self._read_local_file(self.JSHINTRC_PATH)),
+ self._get_extended_error_message)
+
+ def tearDown(self):
+ MarionetteTestCase.tearDown(self)
+
+ def test_RadioInterfaceLayer(self):
+ self._check('RadioInterfaceLayer.js')
+
+ # Bug 936504. Disable the test for 'ril_worker.js'. It sometimes runs very
+ # slow and causes the timeout fail on try server.
+ #def test_ril_worker(self):
+ # self._check('ril_worker.js')
+
+ def test_ril_consts(self):
+ self._check('ril_consts.js')
+
+ def test_worker_buf(self):
+ self._check('worker_buf.js')
diff --git a/dom/system/gonk/tests/marionette/test_screen_state.js b/dom/system/gonk/tests/marionette/test_screen_state.js
new file mode 100644
index 000000000..2281412d5
--- /dev/null
+++ b/dom/system/gonk/tests/marionette/test_screen_state.js
@@ -0,0 +1,47 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+MARIONETTE_TIMEOUT = 10000;
+
+var Services = SpecialPowers.Services;
+
+function testScreenState(on, expected, msg) {
+ // send event to RadioInterface
+ Services.obs.notifyObservers(null, 'screen-state-changed', on);
+ // maybe rild/qemu needs some time to process the event
+ window.setTimeout(function() {
+ runEmulatorCmd('gsm report creg', function(result) {
+ is(result.pop(), 'OK', '\'gsm report creg\' successful');
+ ok(result.indexOf(expected) !== -1, msg);
+ runNextTest();
+ })}, 1000);
+}
+
+function testScreenStateDisabled() {
+ testScreenState('off', '+CREG: 1', 'screen is disabled');
+}
+
+function testScreenStateEnabled() {
+ testScreenState('on', '+CREG: 2', 'screen is enabled');
+}
+
+var tests = [
+ testScreenStateDisabled,
+ testScreenStateEnabled
+];
+
+function runNextTest() {
+ let test = tests.shift();
+ if (!test) {
+ cleanUp();
+ return;
+ }
+
+ test();
+}
+
+function cleanUp() {
+ finish();
+}
+
+runNextTest();
diff --git a/dom/system/gonk/tests/marionette/test_timezone_changes.js b/dom/system/gonk/tests/marionette/test_timezone_changes.js
new file mode 100644
index 000000000..11dbaec5a
--- /dev/null
+++ b/dom/system/gonk/tests/marionette/test_timezone_changes.js
@@ -0,0 +1,135 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+MARIONETTE_TIMEOUT = 60000;
+MARIONETTE_HEAD_JS = 'head.js';
+
+function init() {
+ let promises = [];
+
+ /*
+ * The initial timezone of the emulator could be anywhere, depends the host
+ * machine. Ensure resetting it to UTC before testing.
+ */
+ promises.push(runEmulatorCmdSafe('gsm timezone 0'));
+ promises.push(new Promise((aResolve, aReject) => {
+ waitFor(aResolve, () => {
+ return new Date().getTimezoneOffset() === 0;
+ });
+ }));
+
+ return Promise.all(promises);
+}
+
+function paddingZeros(aNumber, aLength) {
+ let str = '' + aNumber;
+ while (str.length < aLength) {
+ str = '0' + str;
+ }
+
+ return str;
+}
+
+function verifyDate(aTestDate, aUTCOffsetDate) {
+ // Verify basic properties.
+ is(aUTCOffsetDate.getUTCFullYear(), aTestDate.getFullYear(), 'year');
+ is(aUTCOffsetDate.getUTCMonth(), aTestDate.getMonth(), 'month');
+ is(aUTCOffsetDate.getUTCDate(), aTestDate.getDate(), 'date');
+ is(aUTCOffsetDate.getUTCHours(), aTestDate.getHours(), 'hours');
+ is(aUTCOffsetDate.getUTCMinutes(), aTestDate.getMinutes(), 'minutes');
+ is(aUTCOffsetDate.getUTCMilliseconds(), aTestDate.getMilliseconds(), 'milliseconds');
+
+ // Ensure toLocaleString also uses correct timezone.
+ // It uses ICU's timezone instead of the offset calculated from gecko prtime.
+ let expectedDateString =
+ paddingZeros(aUTCOffsetDate.getUTCMonth() + 1, 2) + '/' +
+ paddingZeros(aUTCOffsetDate.getUTCDate(), 2);
+ let dateString = aTestDate.toLocaleString('en-US', {
+ month: '2-digit',
+ day: '2-digit',
+ });
+ let expectedTimeString =
+ paddingZeros(aUTCOffsetDate.getUTCHours(), 2) + ':' +
+ paddingZeros(aUTCOffsetDate.getUTCMinutes(), 2);
+ let timeString = aTestDate.toLocaleString('en-US', {
+ hour12: false,
+ hour: '2-digit',
+ minute: '2-digit'
+ });
+
+ is(expectedDateString, dateString, 'dateString');
+ is(expectedTimeString, timeString, 'timeString');
+}
+
+function waitForTimezoneUpdate(aTzOffset,
+ aTestDateInMillis = 86400000, // Use 'UTC 00:00:00, 2nd of Jan, 1970' by default.
+ aTransTzOffset, aTransTestDateInMillis) {
+ return new Promise(function(aResolve, aReject) {
+ window.addEventListener('moztimechange', function onevent(aEvent) {
+ // Since there could be multiple duplicate moztimechange event, wait until
+ // timezone is actually changed to expected value before removing the
+ // listener.
+ let testDate = new Date(aTestDateInMillis);
+ if (testDate.getTimezoneOffset() === aTzOffset) {
+ window.removeEventListener('moztimechange', onevent);
+
+ // The UTC time of offsetDate is the same as the expected local time of
+ // testDate. We'll use it to verify the values.
+ let offsetDate = new Date(aTestDateInMillis - aTzOffset * 60 * 1000);
+ verifyDate(testDate, offsetDate);
+
+ // Verify transition time if given.
+ if (aTransTzOffset !== undefined) {
+ testDate = new Date(aTransTestDateInMillis);
+ is(testDate.getTimezoneOffset(), aTransTzOffset);
+
+ // Verify transition date.
+ offsetDate = new Date(aTransTestDateInMillis - aTransTzOffset * 60 * 1000);
+ verifyDate(testDate, offsetDate);
+ }
+
+ aResolve(aEvent);
+ }
+ });
+ });
+}
+
+function testChangeNitzTimezone(aTzDiff) {
+ let promises = [];
+
+ // aTzOffset should be the expected value for getTimezoneOffset().
+ // Note that getTimezoneOffset() is not so straightforward,
+ // it values (UTC - localtime), so UTC+08:00 returns -480.
+ promises.push(waitForTimezoneUpdate(-aTzDiff * 15));
+ promises.push(runEmulatorCmdSafe('gsm timezone ' + aTzDiff));
+
+ return Promise.all(promises);
+}
+
+function testChangeOlsonTimezone(aOlsonTz, aTzOffset, aTestDateInMillis,
+ aTransTzOffset, aTransTestDateInMillis) {
+ let promises = [];
+
+ promises.push(waitForTimezoneUpdate(aTzOffset, aTestDateInMillis,
+ aTransTzOffset, aTransTestDateInMillis));
+ promises.push(setSettings('time.timezone', aOlsonTz));
+
+ return Promise.all(promises);
+}
+
+// Start test
+startTestBase(function() {
+ return init()
+ .then(() => testChangeNitzTimezone(36)) // UTC+09:00
+ .then(() => testChangeOlsonTimezone('America/New_York',
+ 300, 1446357600000, // 2015/11/01 02:00 UTC-04:00 => 01:00 UTC-05:00 (EST)
+ 240, 1425798000000)) // 2015/03/08 02:00 UTC-05:00 => 03:00 UTC-04:00 (EDT)
+ .then(() => testChangeNitzTimezone(-22)) // UTC-05:30
+ .then(() => testChangeNitzTimezone(51)) // UTC+12:45
+ .then(() => testChangeOlsonTimezone('Australia/Adelaide',
+ -570, 1428165000000, // 2015/04/05 03:00 UTC+10:30 => 02:00 UTC+09:30 (ACST)
+ -630, 1443889800000)) // 2015/10/04 02:00 UTC+09:30 => 03:00 UTC+10:30 (ACDT)
+ .then(() => testChangeNitzTimezone(-38)) // UTC-09:30
+ .then(() => testChangeNitzTimezone(0)) // UTC
+ .then(() => runEmulatorCmdSafe('gsm timezone auto'));
+});
diff --git a/dom/system/gonk/tests/test_ril_system_messenger.js b/dom/system/gonk/tests/test_ril_system_messenger.js
new file mode 100644
index 000000000..a588d0ddb
--- /dev/null
+++ b/dom/system/gonk/tests/test_ril_system_messenger.js
@@ -0,0 +1,1187 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+var {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
+var RIL = {};
+Cu.import("resource://gre/modules/ril_consts.js", RIL);
+
+XPCOMUtils.defineLazyServiceGetter(this, "gStkCmdFactory",
+ "@mozilla.org/icc/stkcmdfactory;1",
+ "nsIStkCmdFactory");
+
+/**
+ * Name space for RILSystemMessenger.jsm. Only initialized after first call to
+ * newRILSystemMessenger.
+ */
+var RSM;
+
+var gReceivedMsgType = null;
+var gReceivedMessage = null;
+
+/**
+ * Create a new RILSystemMessenger instance.
+ *
+ * @return a RILSystemMessenger instance.
+ */
+function newRILSystemMessenger() {
+ if (!RSM) {
+ RSM = Cu.import("resource://gre/modules/RILSystemMessenger.jsm", {});
+ equal(typeof RSM.RILSystemMessenger, "function", "RSM.RILSystemMessenger");
+ }
+
+ let rsm = new RSM.RILSystemMessenger();
+ rsm.broadcastMessage = (aType, aMessage) => {
+ gReceivedMsgType = aType;
+ gReceivedMessage = aMessage;
+ };
+
+ rsm.createCommandMessage = (aStkProactiveCmd) => {
+ return gStkCmdFactory.createCommandMessage(aStkProactiveCmd);
+ };
+
+ return rsm;
+}
+
+function equal_received_system_message(aType, aMessage) {
+ equal(aType, gReceivedMsgType);
+ deepEqual(aMessage, gReceivedMessage);
+ gReceivedMsgType = null;
+ gReceivedMessage = null;
+}
+
+/**
+ * Verify that each nsIXxxMessenger could be retrieved.
+ */
+function run_test() {
+ let telephonyMessenger = Cc["@mozilla.org/ril/system-messenger-helper;1"]
+ .getService(Ci.nsITelephonyMessenger);
+
+ let smsMessenger = Cc["@mozilla.org/ril/system-messenger-helper;1"]
+ .getService(Ci.nsISmsMessenger);
+
+ let cellbroadcastMessenger = Cc["@mozilla.org/ril/system-messenger-helper;1"]
+ .getService(Ci.nsICellbroadcastMessenger);
+
+ let mobileConnectionMessenger = Cc["@mozilla.org/ril/system-messenger-helper;1"]
+ .getService(Ci.nsIMobileConnectionMessenger);
+
+ let iccMessenger = Cc["@mozilla.org/ril/system-messenger-helper;1"]
+ .getService(Ci.nsIIccMessenger);
+
+ ok(telephonyMessenger !== null, "Get TelephonyMessenger.");
+ ok(smsMessenger != null, "Get SmsMessenger.");
+ ok(cellbroadcastMessenger != null, "Get CellbroadcastMessenger.");
+ ok(mobileConnectionMessenger != null, "Get MobileConnectionMessenger.");
+ ok(iccMessenger != null, "Get IccMessenger.");
+
+ run_next_test();
+}
+
+/**
+ * Verify RILSystemMessenger.notifyNewCall()
+ */
+add_test(function test_telephony_messenger_notify_new_call() {
+ let messenger = newRILSystemMessenger();
+
+ messenger.notifyNewCall();
+ equal_received_system_message("telephony-new-call", {});
+
+ run_next_test();
+});
+
+/**
+ * Verify RILSystemMessenger.notifyCallEnded()
+ */
+add_test(function test_telephony_messenger_notify_call_ended() {
+ let messenger = newRILSystemMessenger();
+
+ messenger.notifyCallEnded(1,
+ "+0987654321",
+ null,
+ true,
+ 500,
+ false,
+ true);
+
+ equal_received_system_message("telephony-call-ended", {
+ serviceId: 1,
+ number: "+0987654321",
+ emergency: true,
+ duration: 500,
+ direction: "incoming",
+ hangUpLocal: true
+ });
+
+ // Verify 'optional' parameter of secondNumber.
+ messenger.notifyCallEnded(1,
+ "+0987654321",
+ "+1234567890",
+ true,
+ 500,
+ true,
+ false);
+
+ equal_received_system_message("telephony-call-ended", {
+ serviceId: 1,
+ number: "+0987654321",
+ emergency: true,
+ duration: 500,
+ direction: "outgoing",
+ hangUpLocal: false,
+ secondNumber: "+1234567890"
+ });
+
+ run_next_test();
+});
+
+/**
+ * Verify RILSystemMessenger.notifySms()
+ */
+add_test(function test_sms_messenger_notify_sms() {
+ let messenger = newRILSystemMessenger();
+ let timestamp = Date.now();
+ let sentTimestamp = timestamp + 100;
+ let deliveryTimestamp = sentTimestamp + 100;
+
+ // Verify 'sms-received' system message.
+ messenger.notifySms(Ci.nsISmsMessenger.NOTIFICATION_TYPE_RECEIVED,
+ 1,
+ 2,
+ "99887766554433221100",
+ Ci.nsISmsService.DELIVERY_TYPE_RECEIVED,
+ Ci.nsISmsService.DELIVERY_STATUS_TYPE_SUCCESS,
+ "+0987654321",
+ null,
+ "Incoming message",
+ Ci.nsISmsService.MESSAGE_CLASS_TYPE_CLASS_2,
+ timestamp,
+ sentTimestamp,
+ 0,
+ false);
+
+ equal_received_system_message("sms-received", {
+ iccId: "99887766554433221100",
+ type: "sms",
+ id: 1,
+ threadId: 2,
+ delivery: "received",
+ deliveryStatus: "success",
+ sender: "+0987654321",
+ receiver: null,
+ body: "Incoming message",
+ messageClass: "class-2",
+ timestamp: timestamp,
+ sentTimestamp: sentTimestamp,
+ deliveryTimestamp: 0,
+ read: false
+ });
+
+ // Verify 'sms-sent' system message.
+ messenger.notifySms(Ci.nsISmsMessenger.NOTIFICATION_TYPE_SENT,
+ 3,
+ 4,
+ "99887766554433221100",
+ Ci.nsISmsService.DELIVERY_TYPE_SENT,
+ Ci.nsISmsService.DELIVERY_STATUS_TYPE_PENDING,
+ null,
+ "+0987654321",
+ "Outgoing message",
+ Ci.nsISmsService.MESSAGE_CLASS_TYPE_NORMAL,
+ timestamp,
+ 0,
+ 0,
+ true);
+
+ equal_received_system_message("sms-sent", {
+ iccId: "99887766554433221100",
+ type: "sms",
+ id: 3,
+ threadId: 4,
+ delivery: "sent",
+ deliveryStatus: "pending",
+ sender: null,
+ receiver: "+0987654321",
+ body: "Outgoing message",
+ messageClass: "normal",
+ timestamp: timestamp,
+ sentTimestamp: 0,
+ deliveryTimestamp: 0,
+ read: true
+ });
+
+ // Verify 'sms-delivery-success' system message.
+ messenger.notifySms(Ci.nsISmsMessenger.NOTIFICATION_TYPE_DELIVERY_SUCCESS,
+ 5,
+ 6,
+ "99887766554433221100",
+ Ci.nsISmsService.DELIVERY_TYPE_SENT,
+ Ci.nsISmsService.DELIVERY_STATUS_TYPE_SUCCESS,
+ null,
+ "+0987654321",
+ "Outgoing message",
+ Ci.nsISmsService.MESSAGE_CLASS_TYPE_NORMAL,
+ timestamp,
+ 0,
+ deliveryTimestamp,
+ true);
+
+ equal_received_system_message("sms-delivery-success", {
+ iccId: "99887766554433221100",
+ type: "sms",
+ id: 5,
+ threadId: 6,
+ delivery: "sent",
+ deliveryStatus: "success",
+ sender: null,
+ receiver: "+0987654321",
+ body: "Outgoing message",
+ messageClass: "normal",
+ timestamp: timestamp,
+ sentTimestamp: 0,
+ deliveryTimestamp: deliveryTimestamp,
+ read: true
+ });
+
+ // Verify 'sms-failed' system message.
+ messenger.notifySms(Ci.nsISmsMessenger.NOTIFICATION_TYPE_SENT_FAILED,
+ 7,
+ 8,
+ "99887766554433221100",
+ Ci.nsISmsService.DELIVERY_TYPE_ERROR,
+ Ci.nsISmsService.DELIVERY_STATUS_TYPE_ERROR,
+ null,
+ "+0987654321",
+ "Outgoing message",
+ Ci.nsISmsService.MESSAGE_CLASS_TYPE_NORMAL,
+ timestamp,
+ 0,
+ 0,
+ true);
+
+ equal_received_system_message("sms-failed", {
+ iccId: "99887766554433221100",
+ type: "sms",
+ id: 7,
+ threadId: 8,
+ delivery: "error",
+ deliveryStatus: "error",
+ sender: null,
+ receiver: "+0987654321",
+ body: "Outgoing message",
+ messageClass: "normal",
+ timestamp: timestamp,
+ sentTimestamp: 0,
+ deliveryTimestamp: 0,
+ read: true
+ });
+
+ // Verify 'sms-delivery-error' system message.
+ messenger.notifySms(Ci.nsISmsMessenger.NOTIFICATION_TYPE_DELIVERY_ERROR,
+ 9,
+ 10,
+ "99887766554433221100",
+ Ci.nsISmsService.DELIVERY_TYPE_SENT,
+ Ci.nsISmsService.DELIVERY_STATUS_TYPE_ERROR,
+ null,
+ "+0987654321",
+ "Outgoing message",
+ Ci.nsISmsService.MESSAGE_CLASS_TYPE_NORMAL,
+ timestamp,
+ 0,
+ 0,
+ true);
+
+ equal_received_system_message("sms-delivery-error", {
+ iccId: "99887766554433221100",
+ type: "sms",
+ id: 9,
+ threadId: 10,
+ delivery: "sent",
+ deliveryStatus: "error",
+ sender: null,
+ receiver: "+0987654321",
+ body: "Outgoing message",
+ messageClass: "normal",
+ timestamp: timestamp,
+ sentTimestamp: 0,
+ deliveryTimestamp: 0,
+ read: true
+ });
+
+ // Verify the protection of invalid nsISmsMessenger.NOTIFICATION_TYPEs.
+ try {
+ messenger.notifySms(5,
+ 1,
+ 2,
+ "99887766554433221100",
+ Ci.nsISmsService.DELIVERY_TYPE_RECEIVED,
+ Ci.nsISmsService.DELIVERY_STATUS_TYPE_SUCCESS,
+ "+0987654321",
+ null,
+ "Incoming message",
+ Ci.nsISmsService.MESSAGE_CLASS_TYPE_NORMAL,
+ timestamp,
+ sentTimestamp,
+ 0,
+ false);
+ ok(false, "Failed to verify the protection of invalid nsISmsMessenger.NOTIFICATION_TYPE!");
+ } catch (e) {}
+
+ run_next_test();
+});
+
+/**
+ * Verify RILSystemMessenger.notifyCbMessageReceived()
+ */
+add_test(function test_cellbroadcast_messenger_notify_cb_message_received() {
+ let messenger = newRILSystemMessenger();
+ let timestamp = Date.now();
+
+ // Verify ETWS
+ messenger.notifyCbMessageReceived(0,
+ Ci.nsICellBroadcastService.GSM_GEOGRAPHICAL_SCOPE_CELL_IMMEDIATE,
+ 256,
+ 4352,
+ null,
+ null,
+ Ci.nsICellBroadcastService.GSM_MESSAGE_CLASS_NORMAL,
+ timestamp,
+ Ci.nsICellBroadcastService.CDMA_SERVICE_CATEGORY_INVALID,
+ true,
+ Ci.nsICellBroadcastService.GSM_ETWS_WARNING_EARTHQUAKE,
+ false,
+ true);
+ equal_received_system_message("cellbroadcast-received", {
+ serviceId: 0,
+ gsmGeographicalScope: "cell-immediate",
+ messageCode: 256,
+ messageId: 4352,
+ language: null,
+ body: null,
+ messageClass: "normal",
+ timestamp: timestamp,
+ cdmaServiceCategory: null,
+ etws: {
+ warningType: "earthquake",
+ emergencyUserAlert: false,
+ popup: true
+ }
+ });
+
+ // Verify Normal CB Message
+ messenger.notifyCbMessageReceived(1,
+ Ci.nsICellBroadcastService.GSM_GEOGRAPHICAL_SCOPE_PLMN,
+ 0,
+ 50,
+ "en",
+ "The quick brown fox jumps over the lazy dog",
+ Ci.nsICellBroadcastService.GSM_MESSAGE_CLASS_NORMAL,
+ timestamp,
+ Ci.nsICellBroadcastService.CDMA_SERVICE_CATEGORY_INVALID,
+ false,
+ Ci.nsICellBroadcastService.GSM_ETWS_WARNING_INVALID,
+ false,
+ false);
+ equal_received_system_message("cellbroadcast-received", {
+ serviceId: 1,
+ gsmGeographicalScope: "plmn",
+ messageCode: 0,
+ messageId: 50,
+ language: "en",
+ body: "The quick brown fox jumps over the lazy dog",
+ messageClass: "normal",
+ timestamp: timestamp,
+ cdmaServiceCategory: null,
+ etws: null
+ });
+
+ // Verify CB Message with ETWS Info
+ messenger.notifyCbMessageReceived(0,
+ Ci.nsICellBroadcastService.GSM_GEOGRAPHICAL_SCOPE_LOCATION_AREA,
+ 0,
+ 4354,
+ "en",
+ "Earthquake & Tsunami Warning!",
+ Ci.nsICellBroadcastService.GSM_MESSAGE_CLASS_0,
+ timestamp,
+ Ci.nsICellBroadcastService.CDMA_SERVICE_CATEGORY_INVALID,
+ true,
+ Ci.nsICellBroadcastService.GSM_ETWS_WARNING_EARTHQUAKE_TSUNAMI,
+ true,
+ false);
+ equal_received_system_message("cellbroadcast-received", {
+ serviceId: 0,
+ gsmGeographicalScope: "location-area",
+ messageCode: 0,
+ messageId: 4354,
+ language: "en",
+ body: "Earthquake & Tsunami Warning!",
+ messageClass: "class-0",
+ timestamp: timestamp,
+ cdmaServiceCategory: null,
+ etws: {
+ warningType: "earthquake-tsunami",
+ emergencyUserAlert: true,
+ popup: false
+ }
+ });
+
+ // Verify CDMA CB Message
+ messenger.notifyCbMessageReceived(0,
+ Ci.nsICellBroadcastService.GSM_GEOGRAPHICAL_SCOPE_INVALID,
+ 0,
+ 0,
+ null,
+ "CDMA CB Message",
+ Ci.nsICellBroadcastService.GSM_MESSAGE_CLASS_NORMAL,
+ timestamp,
+ 512,
+ false,
+ Ci.nsICellBroadcastService.GSM_ETWS_WARNING_INVALID,
+ false,
+ false);
+ equal_received_system_message("cellbroadcast-received", {
+ serviceId: 0,
+ gsmGeographicalScope: null,
+ messageCode: 0,
+ messageId: 0,
+ language: null,
+ body: "CDMA CB Message",
+ messageClass: "normal",
+ timestamp: timestamp,
+ cdmaServiceCategory: 512,
+ etws: null
+ });
+
+ run_next_test();
+});
+
+/**
+ * Verify RILSystemMessenger.notifyUssdReceived()
+ */
+add_test(function test_mobileconnection_notify_ussd_received() {
+ let messenger = newRILSystemMessenger();
+
+ messenger.notifyUssdReceived(0, "USSD Message", false);
+
+ equal_received_system_message("ussd-received", {
+ serviceId: 0,
+ message: "USSD Message",
+ sessionEnded: false
+ });
+
+ messenger.notifyUssdReceived(1, "USSD Message", true);
+
+ equal_received_system_message("ussd-received", {
+ serviceId: 1,
+ message: "USSD Message",
+ sessionEnded: true
+ });
+
+ run_next_test();
+});
+
+/**
+ * Verify RILSystemMessenger.notifyCdmaInfoRecXXX()
+ */
+add_test(function test_mobileconnection_notify_cdma_info() {
+ let messenger = newRILSystemMessenger();
+
+ messenger.notifyCdmaInfoRecDisplay(0, "CDMA Display Info");
+
+ equal_received_system_message("cdma-info-rec-received", {
+ clientId: 0,
+ display: "CDMA Display Info"
+ });
+
+ messenger.notifyCdmaInfoRecCalledPartyNumber(1, 1, 2, "+0987654321", 3, 4);
+
+ equal_received_system_message("cdma-info-rec-received", {
+ clientId: 1,
+ calledNumber: {
+ type: 1,
+ plan: 2,
+ number: "+0987654321",
+ pi: 3,
+ si: 4
+ }
+ });
+
+ messenger.notifyCdmaInfoRecCallingPartyNumber(0, 5, 6, "+1234567890", 7, 8);
+
+ equal_received_system_message("cdma-info-rec-received", {
+ clientId: 0,
+ callingNumber: {
+ type: 5,
+ plan: 6,
+ number: "+1234567890",
+ pi: 7,
+ si: 8
+ }
+ });
+
+ messenger.notifyCdmaInfoRecConnectedPartyNumber(1, 4, 3, "+56473839201", 2, 1);
+
+ equal_received_system_message("cdma-info-rec-received", {
+ clientId: 1,
+ connectedNumber: {
+ type: 4,
+ plan: 3,
+ number: "+56473839201",
+ pi: 2,
+ si: 1
+ }
+ });
+
+ messenger.notifyCdmaInfoRecSignal(0, 1, 2, 3);
+
+ equal_received_system_message("cdma-info-rec-received", {
+ clientId: 0,
+ signal: {
+ type: 1,
+ alertPitch: 2,
+ signal: 3
+ }
+ });
+
+ messenger.notifyCdmaInfoRecRedirectingNumber(1, 8, 7, "+1029384756", 6, 5, 4);
+
+ equal_received_system_message("cdma-info-rec-received", {
+ clientId: 1,
+ redirect: {
+ type: 8,
+ plan: 7,
+ number: "+1029384756",
+ pi: 6,
+ si: 5,
+ reason: 4
+ }
+ });
+
+ messenger.notifyCdmaInfoRecLineControl(0, 1, 0, 1, 255);
+
+ equal_received_system_message("cdma-info-rec-received", {
+ clientId: 0,
+ lineControl: {
+ polarityIncluded: 1,
+ toggle: 0,
+ reverse: 1,
+ powerDenial: 255
+ }
+ });
+
+ messenger.notifyCdmaInfoRecClir(1, 256);
+
+ equal_received_system_message("cdma-info-rec-received", {
+ clientId: 1,
+ clirCause: 256
+ });
+
+ messenger.notifyCdmaInfoRecAudioControl(0, 255, -1);
+
+ equal_received_system_message("cdma-info-rec-received", {
+ clientId: 0,
+ audioControl: {
+ upLink: 255,
+ downLink: -1
+ }
+ });
+
+ run_next_test();
+});
+
+/**
+ * Verify Error Handling of StkProactiveCmdFactory.createCommand()
+ */
+add_test(function test_icc_stk_cmd_factory_create_command_error() {
+ let messenger = newRILSystemMessenger();
+
+ // Verify the protection of invalid typeOfCommand.
+ try {
+ gStkCmdFactory.createCommand({
+ commandNumber: 0,
+ typeOfCommand: RIL.STK_CMD_MORE_TIME, // Invalid TypeOfCommand
+ commandQualifier: 0x00
+ });
+
+ ok(false, "Failed to verify the protection of createCommand()!");
+ } catch (e) {
+ ok(e.message.indexOf("Unknown Command Type") !== -1,
+ "Invalid typeOfCommand!");
+ }
+
+ run_next_test();
+});
+
+/**
+ * Verify Error Handling of StkProactiveCmdFactory.createCommandMessage()
+ */
+add_test(function test_icc_stk_cmd_factory_create_system_msg_invalid_cmd_type() {
+ let messenger = newRILSystemMessenger();
+ let iccId = "99887766554433221100";
+
+ // Verify the protection of invalid typeOfCommand.
+ try {
+ gStkCmdFactory.createCommandMessage({
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIStkProactiveCmd]),
+
+ // nsIStkProactiveCmd
+ commandNumber: 0,
+ typeOfCommand: RIL.STK_CMD_MORE_TIME, // Invalid TypeOfCommand
+ commandQualifier: 0
+ });
+
+ ok(false, "Failed to identify invalid typeOfCommand!");
+ } catch (e) {
+ ok(e.message.indexOf("Unknown Command Type") !== -1,
+ "Invalid typeOfCommand!");
+ }
+
+ run_next_test();
+});
+
+/**
+ * Verify Error Handling of StkProactiveCmdFactory.createCommandMessage()
+ */
+add_test(function test_icc_stk_cmd_factory_create_system_msg_incorrect_cmd_type() {
+ let messenger = newRILSystemMessenger();
+ let iccId = "99887766554433221100";
+
+ // Verify the protection of invalid typeOfCommand.
+ try {
+ gStkCmdFactory.createCommandMessage({
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIStkProactiveCmd,
+ Ci.nsIStkProvideLocalInfoCmd]),
+
+ // nsIStkProactiveCmd
+ commandNumber: 0,
+ typeOfCommand: RIL.STK_CMD_POLL_INTERVAL, // Incorrect typeOfCommand
+ commandQualifier: 0,
+ // nsIStkProvideLocalInfoCmd
+ localInfoType: 0x00,
+ });
+
+ ok(false, "Failed to identify incorrect typeOfCommand!");
+ } catch (e) {
+ ok(e.message.indexOf("Failed to convert command into concrete class: ") !== -1);
+ }
+
+ run_next_test();
+});
+
+/**
+ * Verify RILSystemMessenger.notifyStkProactiveCommand()
+ */
+add_test(function test_icc_notify_stk_proactive_command() {
+ let messenger = newRILSystemMessenger();
+ let iccId = "99887766554433221100";
+ let WHT = 0xFFFFFFFF;
+ let BLK = 0x000000FF;
+ let RED = 0xFF0000FF;
+ let GRN = 0x00FF00FF;
+ let BLU = 0x0000FFFF;
+ let TSP = 0;
+ // Basic Image, see Anex B.1 in TS 31.102.
+ let basicIcon = {
+ width: 8,
+ height: 8,
+ codingScheme: "basic",
+ pixels: [WHT, WHT, WHT, WHT, WHT, WHT, WHT, WHT,
+ BLK, BLK, BLK, BLK, BLK, BLK, WHT, WHT,
+ WHT, BLK, WHT, BLK, BLK, WHT, BLK, WHT,
+ WHT, BLK, BLK, WHT, WHT, BLK, BLK, WHT,
+ WHT, BLK, BLK, WHT, WHT, BLK, BLK, WHT,
+ WHT, BLK, WHT, BLK, BLK, WHT, BLK, WHT,
+ WHT, WHT, BLK, BLK, BLK, BLK, WHT, WHT,
+ WHT, WHT, WHT, WHT, WHT, WHT, WHT, WHT]
+ };
+ // Color Image, see Anex B.2 in TS 31.102.
+ let colorIcon = {
+ width: 8,
+ height: 8,
+ codingScheme: "color",
+ pixels: [BLU, BLU, BLU, BLU, BLU, BLU, BLU, BLU,
+ BLU, RED, RED, RED, RED, RED, RED, BLU,
+ BLU, RED, GRN, GRN, GRN, RED, RED, BLU,
+ BLU, RED, RED, GRN, GRN, RED, RED, BLU,
+ BLU, RED, RED, GRN, GRN, RED, RED, BLU,
+ BLU, RED, RED, GRN, GRN, GRN, RED, BLU,
+ BLU, RED, RED, RED, RED, RED, RED, BLU,
+ BLU, BLU, BLU, BLU, BLU, BLU, BLU, BLU]
+ };
+ // Color Image with Transparency, see Anex B.2 in TS 31.102.
+ let colorTransparencyIcon = {
+ width: 8,
+ height: 8,
+ codingScheme: "color-transparency",
+ pixels: [TSP, TSP, TSP, TSP, TSP, TSP, TSP, TSP,
+ TSP, RED, RED, RED, RED, RED, RED, TSP,
+ TSP, RED, GRN, GRN, GRN, RED, RED, TSP,
+ TSP, RED, RED, GRN, GRN, RED, RED, TSP,
+ TSP, RED, RED, GRN, GRN, RED, RED, TSP,
+ TSP, RED, RED, GRN, GRN, GRN, RED, TSP,
+ TSP, RED, RED, RED, RED, RED, RED, TSP,
+ TSP, TSP, TSP, TSP, TSP, TSP, TSP, TSP]
+ };
+
+ let cmdCount = 0;
+
+ // Test Messages:
+ let messages = [
+ // STK_CMD_REFRESH
+ {
+ commandNumber: ++cmdCount,
+ typeOfCommand: RIL.STK_CMD_REFRESH,
+ commandQualifier: 0x04 // UICC Reset
+ },
+ // STK_CMD_POLL_INTERVAL
+ {
+ commandNumber: ++cmdCount,
+ typeOfCommand: RIL.STK_CMD_POLL_INTERVAL,
+ commandQualifier: 0x00, // RFU
+ options: {
+ timeUnit: RIL.STK_TIME_UNIT_TENTH_SECOND,
+ timeInterval: 0x05
+ }
+ },
+ // STK_CMD_POLL_OFF
+ {
+ commandNumber: ++cmdCount,
+ typeOfCommand: RIL.STK_CMD_POLL_OFF,
+ commandQualifier: 0x00, // RFU
+ },
+ // STK_CMD_PROVIDE_LOCAL_INFO
+ {
+ commandNumber: ++cmdCount,
+ typeOfCommand: RIL.STK_CMD_PROVIDE_LOCAL_INFO,
+ commandQualifier: 0x01, // IMEI of the terminal
+ options: {
+ localInfoType: 0x01 // IMEI of the terminal
+ }
+ },
+ // STK_CMD_SET_UP_EVENT_LIST with eventList
+ {
+ commandNumber: ++cmdCount,
+ typeOfCommand: RIL.STK_CMD_SET_UP_EVENT_LIST,
+ commandQualifier: 0x00, // RFU
+ options: {
+ eventList: [ 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+ 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F,
+ 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
+ 0x18, 0x19, 0x1A, 0x1B, 0x1C ]
+ }
+ },
+ // STK_CMD_SET_UP_EVENT_LIST without eventList
+ {
+ commandNumber: ++cmdCount,
+ typeOfCommand: RIL.STK_CMD_SET_UP_EVENT_LIST,
+ commandQualifier: 0x00, // RFU
+ options: {
+ eventList: null
+ }
+ },
+ // STK_CMD_SET_UP_MENU with mandatory properties.
+ {
+ commandNumber: ++cmdCount,
+ typeOfCommand: RIL.STK_CMD_SET_UP_MENU,
+ commandQualifier: 0x80, // bit 8: 1 = help information available
+ options: {
+ title: "Toolkit Menu 1",
+ items: [
+ { identifier: 0x01, text: "Menu Item 1" },
+ { identifier: 0x02, text: "Menu Item 2" },
+ { identifier: 0x03, text: "Menu Item 3" }
+ ],
+ isHelpAvailable: true
+ }
+ },
+ // STK_CMD_SET_UP_MENU with optional properties including:
+ // iconInfo for this menu, iconInfo for each item and nextActionList.
+ {
+ commandNumber: ++cmdCount,
+ typeOfCommand: RIL.STK_CMD_SET_UP_MENU,
+ commandQualifier: 0x00, // bit 8: 0 = help information is not available
+ options: {
+ title: "Toolkit Menu 2",
+ items: [
+ { identifier: 0x01,
+ text: "Menu Item 1",
+ iconSelfExplanatory: true,
+ icons: [basicIcon]
+ },
+ { identifier: 0x02,
+ text: "Menu Item 2",
+ iconSelfExplanatory: false,
+ icons: [basicIcon, colorIcon]
+ },
+ { identifier: 0x03,
+ text: "Menu Item 3",
+ iconSelfExplanatory: true,
+ icons: [basicIcon, colorIcon, colorTransparencyIcon]
+ },
+ ],
+ nextActionList: [
+ RIL.STK_NEXT_ACTION_END_PROACTIVE_SESSION,
+ RIL.STK_NEXT_ACTION_NULL,
+ RIL.STK_NEXT_ACTION_NULL,
+ RIL.STK_NEXT_ACTION_NULL
+ ],
+ iconSelfExplanatory: false,
+ icons: [basicIcon, colorIcon, colorTransparencyIcon],
+ isHelpAvailable: false
+ }
+ },
+ // STK_CMD_SELECT_ITEM with mandatory properties.
+ {
+ commandNumber: ++cmdCount,
+ typeOfCommand: RIL.STK_CMD_SELECT_ITEM,
+ commandQualifier: RIL.STK_PRESENTATION_TYPE_NOT_SPECIFIED,
+ options: {
+ title: null,
+ items: [
+ { identifier: 0x01, text: "Menu Item 1" },
+ { identifier: 0x02, text: "Menu Item 2" },
+ { identifier: 0x03, text: "Menu Item 3" }
+ ],
+ presentationType: RIL.STK_PRESENTATION_TYPE_NOT_SPECIFIED,
+ isHelpAvailable: false
+ }
+ },
+ // STK_CMD_SELECT_ITEM with optional properties including:
+ // title, iconInfo for this menu, iconInfo for each item and nextActionList.
+ {
+ commandNumber: ++cmdCount,
+ typeOfCommand: RIL.STK_CMD_SELECT_ITEM,
+ commandQualifier: RIL.STK_PRESENTATION_TYPE_NAVIGATION_OPTIONS,
+ options: {
+ title: "Selected Toolkit Menu",
+ items: [
+ { identifier: 0x01,
+ text: "Menu Item 1",
+ iconSelfExplanatory: true,
+ icons: [basicIcon]
+ },
+ { identifier: 0x02,
+ text: "Menu Item 2",
+ iconSelfExplanatory: false,
+ icons: [basicIcon, colorIcon]
+ },
+ { identifier: 0x03,
+ text: "Menu Item 3",
+ iconSelfExplanatory: true,
+ icons: [basicIcon, colorIcon, colorTransparencyIcon]
+ },
+ ],
+ nextActionList: [
+ RIL.STK_NEXT_ACTION_END_PROACTIVE_SESSION,
+ RIL.STK_NEXT_ACTION_NULL,
+ RIL.STK_NEXT_ACTION_NULL,
+ RIL.STK_NEXT_ACTION_NULL
+ ],
+ defaultItem: 0x02,
+ iconSelfExplanatory: false,
+ icons: [basicIcon, colorIcon, colorTransparencyIcon],
+ presentationType: RIL.STK_PRESENTATION_TYPE_NAVIGATION_OPTIONS,
+ isHelpAvailable: false
+ }
+ },
+ // STK_CMD_DISPLAY_TEXT with mandatory properties.
+ {
+ commandNumber: ++cmdCount,
+ typeOfCommand: RIL.STK_CMD_DISPLAY_TEXT,
+ commandQualifier: 0x01, // bit 1: High Priority
+ options: {
+ text: "Display Text 1",
+ isHighPriority: true,
+ userClear: false,
+ responseNeeded: false
+ }
+ },
+ // STK_CMD_DISPLAY_TEXT with optional properties.
+ {
+ commandNumber: ++cmdCount,
+ typeOfCommand: RIL.STK_CMD_DISPLAY_TEXT,
+ commandQualifier: 0x80, // bit 8: User Clear
+ options: {
+ text: "Display Text 2",
+ isHighPriority: false,
+ userClear: true,
+ responseNeeded: true,
+ duration: {
+ timeUnit: RIL.STK_TIME_UNIT_TENTH_SECOND,
+ timeInterval: 0x05
+ },
+ iconSelfExplanatory: true,
+ icons: [basicIcon]
+ }
+ },
+ // STK_CMD_SET_UP_IDLE_MODE_TEXT
+ {
+ commandNumber: ++cmdCount,
+ typeOfCommand: RIL.STK_CMD_SET_UP_IDLE_MODE_TEXT,
+ commandQualifier: 0x00, // RFU
+ options: {
+ text: "Setup Idle Mode Text"
+ }
+ },
+ // STK_CMD_SEND_SS
+ {
+ commandNumber: ++cmdCount,
+ typeOfCommand: RIL.STK_CMD_SEND_SS,
+ commandQualifier: 0x00, // RFU
+ options: {
+ text: "Send SS",
+ iconSelfExplanatory: true,
+ icons: [colorIcon]
+ }
+ },
+ // STK_CMD_SEND_USSD
+ {
+ commandNumber: ++cmdCount,
+ typeOfCommand: RIL.STK_CMD_SEND_USSD,
+ commandQualifier: 0x00, // RFU
+ options: {
+ text: "Send USSD"
+ }
+ },
+ // STK_CMD_SEND_SMS
+ {
+ commandNumber: ++cmdCount,
+ typeOfCommand: RIL.STK_CMD_SEND_SMS,
+ commandQualifier: 0x00, // RFU
+ options: {
+ text: "Send SMS",
+ iconSelfExplanatory: false,
+ icons: [colorTransparencyIcon]
+ }
+ },
+ // STK_CMD_SEND_DTMF
+ {
+ commandNumber: ++cmdCount,
+ typeOfCommand: RIL.STK_CMD_SEND_DTMF,
+ commandQualifier: 0x00, // RFU
+ options: {
+ text: "Send DTMF",
+ iconSelfExplanatory: true,
+ icons: [basicIcon]
+ }
+ },
+ // STK_CMD_GET_INKEY
+ {
+ commandNumber: ++cmdCount,
+ typeOfCommand: RIL.STK_CMD_GET_INKEY,
+ commandQualifier: 0x84, // bit 3: isYesNoRequested, bit 8: isHelpAvailable
+ options: {
+ text: "Get Input Key",
+ minLength: 1,
+ maxLength: 1,
+ duration: {
+ timeUnit: RIL.STK_TIME_UNIT_SECOND,
+ timeInterval: 0x0A
+ },
+ isAlphabet: false,
+ isUCS2: false,
+ isYesNoRequested: true,
+ isHelpAvailable: true,
+ defaultText: null,
+ iconSelfExplanatory: false,
+ icons: [colorIcon]
+ }
+ },
+ // STK_CMD_GET_INPUT
+ {
+ commandNumber: ++cmdCount,
+ typeOfCommand: RIL.STK_CMD_GET_INPUT,
+ commandQualifier: 0x0F, // bit 1-4: isAlphabet, isUCS2, hideInput, isPacked
+ options: {
+ text: "Get Input Text",
+ minLength: 1,
+ maxLength: 255,
+ defaultText: "Default Input Text",
+ isAlphabet: true,
+ isUCS2: true,
+ hideInput: true,
+ isPacked: true,
+ isHelpAvailable: false,
+ defaultText: null,
+ iconSelfExplanatory: true,
+ icons: [basicIcon]
+ }
+ },
+ // STK_CMD_SET_UP_CALL with mandatory properties.
+ {
+ commandNumber: ++cmdCount,
+ typeOfCommand: RIL.STK_CMD_SET_UP_CALL,
+ commandQualifier: 0x00, // RFU
+ options: {
+ address: "+0987654321"
+ }
+ },
+ // STK_CMD_SET_UP_CALL with optional properties.
+ {
+ commandNumber: ++cmdCount,
+ typeOfCommand: RIL.STK_CMD_SET_UP_CALL,
+ commandQualifier: 0x00, // RFU
+ options: {
+ address: "+0987654321",
+ confirmMessage: {
+ text: "Confirm Message",
+ iconSelfExplanatory: false,
+ icons: [colorIcon]
+ },
+ callMessage: {
+ text: "Call Message",
+ iconSelfExplanatory: true,
+ icons: [basicIcon]
+ },
+ duration: {
+ timeUnit: RIL.STK_TIME_UNIT_SECOND,
+ timeInterval: 0x0A
+ }
+ }
+ },
+ // STK_CMD_LAUNCH_BROWSER with mandatory properties.
+ {
+ commandNumber: ++cmdCount,
+ typeOfCommand: RIL.STK_CMD_LAUNCH_BROWSER,
+ commandQualifier: RIL.STK_BROWSER_MODE_USING_NEW_BROWSER,
+ options: {
+ url: "http://www.mozilla.org",
+ mode: RIL.STK_BROWSER_MODE_USING_NEW_BROWSER
+ }
+ },
+ // STK_CMD_LAUNCH_BROWSER with optional properties.
+ {
+ commandNumber: ++cmdCount,
+ typeOfCommand: RIL.STK_CMD_LAUNCH_BROWSER,
+ commandQualifier: RIL.STK_BROWSER_MODE_USING_NEW_BROWSER,
+ options: {
+ url: "http://www.mozilla.org",
+ mode: RIL.STK_BROWSER_MODE_USING_NEW_BROWSER,
+ confirmMessage: {
+ text: "Confirm Message for Launch Browser",
+ iconSelfExplanatory: false,
+ icons: [colorTransparencyIcon]
+ }
+ }
+ },
+ // STK_CMD_PLAY_TONE with mandatory properties.
+ {
+ commandNumber: ++cmdCount,
+ typeOfCommand: RIL.STK_CMD_PLAY_TONE,
+ commandQualifier: 0x01, // isVibrate
+ options: {
+ text: null,
+ isVibrate: true
+ }
+ },
+ // STK_CMD_PLAY_TONE with optional properties.
+ {
+ commandNumber: ++cmdCount,
+ typeOfCommand: RIL.STK_CMD_PLAY_TONE,
+ commandQualifier: 0x00, // isVibrate = false
+ options: {
+ text: "Play Tone",
+ tone: RIL.STK_TONE_TYPE_CONGESTION,
+ isVibrate: false,
+ duration: {
+ timeUnit: RIL.STK_TIME_UNIT_SECOND,
+ timeInterval: 0x0A
+ },
+ iconSelfExplanatory: true,
+ icons: [basicIcon]
+ }
+ },
+ // STK_CMD_TIMER_MANAGEMENT with mandatory properties.
+ {
+ commandNumber: ++cmdCount,
+ typeOfCommand: RIL.STK_CMD_TIMER_MANAGEMENT,
+ commandQualifier: RIL.STK_TIMER_DEACTIVATE,
+ options: {
+ timerId: 0x08,
+ timerAction: RIL.STK_TIMER_DEACTIVATE
+ }
+ },
+ // STK_CMD_TIMER_MANAGEMENT with optional properties.
+ {
+ commandNumber: ++cmdCount,
+ typeOfCommand: RIL.STK_CMD_TIMER_MANAGEMENT,
+ commandQualifier: RIL.STK_TIMER_START,
+ options: {
+ timerId: 0x01,
+ timerValue: (12 * 60 * 60) + (30 * 60) + (30), // 12:30:30
+ timerAction: RIL.STK_TIMER_START
+ }
+ },
+ // STK_CMD_OPEN_CHANNEL with mandatory properties.
+ {
+ commandNumber: ++cmdCount,
+ typeOfCommand: RIL.STK_CMD_OPEN_CHANNEL,
+ commandQualifier: 0x00, //RFU
+ options: {
+ text: null,
+ }
+ },
+ // STK_CMD_OPEN_CHANNEL with optional properties.
+ {
+ commandNumber: ++cmdCount,
+ typeOfCommand: RIL.STK_CMD_OPEN_CHANNEL,
+ commandQualifier: 0x00, //RFU
+ options: {
+ text: "Open Channel",
+ iconSelfExplanatory: false,
+ icons: [colorIcon]
+ }
+ },
+ // STK_CMD_CLOSE_CHANNEL with optional properties.
+ {
+ commandNumber: ++cmdCount,
+ typeOfCommand: RIL.STK_CMD_CLOSE_CHANNEL,
+ commandQualifier: 0x00, //RFU
+ options: {
+ text: "Close Channel",
+ iconSelfExplanatory: true,
+ icons: [colorTransparencyIcon]
+ }
+ },
+ // STK_CMD_SEND_DATA with optional properties.
+ {
+ commandNumber: ++cmdCount,
+ typeOfCommand: RIL.STK_CMD_SEND_DATA,
+ commandQualifier: 0x00, //RFU
+ options: {
+ text: null,
+ iconSelfExplanatory: false,
+ icons: [basicIcon]
+ }
+ },
+ // STK_CMD_RECEIVE_DATA with optional properties.
+ {
+ commandNumber: ++cmdCount,
+ typeOfCommand: RIL.STK_CMD_RECEIVE_DATA,
+ commandQualifier: 0x00, //RFU
+ options: {
+ text: "Receive Data"
+ }
+ },
+ null // Termination condition to run_next_test()
+ ];
+
+ messages.forEach(function(aMessage) {
+ if (!aMessage) {
+ run_next_test();
+ return;
+ }
+
+ messenger.notifyStkProactiveCommand(iccId,
+ gStkCmdFactory.createCommand(aMessage));
+
+ equal_received_system_message("icc-stkcommand", {
+ iccId: iccId,
+ command: aMessage
+ });
+ });
+});
diff --git a/dom/system/gonk/tests/test_ril_worker_barring_password.js b/dom/system/gonk/tests/test_ril_worker_barring_password.js
new file mode 100644
index 000000000..fcd3e4405
--- /dev/null
+++ b/dom/system/gonk/tests/test_ril_worker_barring_password.js
@@ -0,0 +1,61 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+subscriptLoader.loadSubScript("resource://gre/modules/ril_consts.js", this);
+
+function run_test() {
+ run_next_test();
+}
+
+const PIN = "0000";
+const NEW_PIN = "1234";
+
+add_test(function test_change_call_barring_password() {
+ let worker = newUint8Worker();
+ let context = worker.ContextPool._contexts[0];
+ let buf = context.Buf;
+
+ function do_test(facility, pin, newPin) {
+ buf.sendParcel = function fakeSendParcel () {
+ // Request Type.
+ equal(this.readInt32(), REQUEST_CHANGE_BARRING_PASSWORD);
+
+ // Token : we don't care.
+ this.readInt32();
+
+ let parcel = this.readStringList();
+ equal(parcel.length, 3);
+ equal(parcel[0], facility);
+ equal(parcel[1], pin);
+ equal(parcel[2], newPin);
+ };
+
+ let options = {facility: facility, pin: pin, newPin: newPin};
+ context.RIL.changeCallBarringPassword(options);
+ }
+
+ do_test(ICC_CB_FACILITY_BA_ALL, PIN, NEW_PIN);
+
+ run_next_test();
+});
+
+add_test(function test_check_change_call_barring_password_result() {
+ let barringPasswordOptions;
+ let workerHelper = newInterceptWorker();
+ let worker = workerHelper.worker;
+
+ let context = worker.ContextPool._contexts[0];
+ context.RIL.changeCallBarringPassword =
+ function fakeChangeCallBarringPassword(options) {
+ barringPasswordOptions = options;
+ context.RIL[REQUEST_CHANGE_BARRING_PASSWORD](0, {});
+ };
+
+ context.RIL.changeCallBarringPassword({pin: PIN, newPin: NEW_PIN});
+
+ let postedMessage = workerHelper.postedMessage;
+ equal(barringPasswordOptions.pin, PIN);
+ equal(barringPasswordOptions.newPin, NEW_PIN);
+
+ run_next_test();
+});
diff --git a/dom/system/gonk/tests/test_ril_worker_buf.js b/dom/system/gonk/tests/test_ril_worker_buf.js
new file mode 100644
index 000000000..30054a881
--- /dev/null
+++ b/dom/system/gonk/tests/test_ril_worker_buf.js
@@ -0,0 +1,187 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+function run_test() {
+ run_next_test();
+}
+
+/**
+ * Add test function with specified parcel and request handler.
+ *
+ * @param parcel
+ * Incoming parcel to be tested.
+ * @param handler
+ * Handler to be invoked as RIL request handler.
+ */
+function add_test_incoming_parcel(parcel, handler) {
+ add_test(function test_incoming_parcel() {
+ let worker = newWorker({
+ postRILMessage: function(data) {
+ // do nothing
+ },
+ postMessage: function(message) {
+ // do nothing
+ }
+ });
+
+ if (!parcel) {
+ parcel = newIncomingParcel(-1,
+ worker.RESPONSE_TYPE_UNSOLICITED,
+ worker.REQUEST_VOICE_REGISTRATION_STATE,
+ [0, 0, 0, 0]);
+ }
+
+ let context = worker.ContextPool._contexts[0];
+ // supports only requests less or equal than UINT8_MAX(255).
+ let buf = context.Buf;
+ let request = parcel[buf.PARCEL_SIZE_SIZE + buf.UINT32_SIZE];
+ context.RIL[request] = function ril_request_handler() {
+ handler.apply(this, arguments);
+ };
+
+ worker.onRILMessage(0, parcel);
+
+ // end of incoming parcel's trip, let's do next test.
+ run_next_test();
+ });
+}
+
+// Test normal parcel handling.
+add_test_incoming_parcel(null,
+ function test_normal_parcel_handling() {
+ let self = this;
+ try {
+ // reads exactly the same size, should not throw anything.
+ self.context.Buf.readInt32();
+ } catch (e) {
+ ok(false, "Got exception: " + e);
+ }
+ }
+);
+
+// Test parcel under read.
+add_test_incoming_parcel(null,
+ function test_parcel_under_read() {
+ let self = this;
+ try {
+ // reads less than parcel size, should not throw.
+ self.context.Buf.readUint16();
+ } catch (e) {
+ ok(false, "Got exception: " + e);
+ }
+ }
+);
+
+// Test parcel over read.
+add_test_incoming_parcel(null,
+ function test_parcel_over_read() {
+ let buf = this.context.Buf;
+
+ // read all data available
+ while (buf.readAvailable > 0) {
+ buf.readUint8();
+ }
+
+ throws(function over_read_handler() {
+ // reads more than parcel size, should throw an error.
+ buf.readUint8();
+ },"Trying to read data beyond the parcel end!");
+ }
+);
+
+// Test Bug 814761: buffer overwritten
+add_test(function test_incoming_parcel_buffer_overwritten() {
+ let worker = newWorker({
+ postRILMessage: function(data) {
+ // do nothing
+ },
+ postMessage: function(message) {
+ // do nothing
+ }
+ });
+
+ let context = worker.ContextPool._contexts[0];
+ // A convenient alias.
+ let buf = context.Buf;
+
+ // Allocate an array of specified size and set each of its elements to value.
+ function calloc(length, value) {
+ let array = new Array(length);
+ for (let i = 0; i < length; i++) {
+ array[i] = value;
+ }
+ return array;
+ }
+
+ // Do nothing in handleParcel().
+ let request = worker.REQUEST_VOICE_REGISTRATION_STATE;
+ context.RIL[request] = null;
+
+ // Prepare two parcels, whose sizes are both smaller than the incoming buffer
+ // size but larger when combined, to trigger the bug.
+ let pA_dataLength = buf.incomingBufferLength / 2;
+ let pA = newIncomingParcel(-1,
+ worker.RESPONSE_TYPE_UNSOLICITED,
+ request,
+ calloc(pA_dataLength, 1));
+ let pA_parcelSize = pA.length - buf.PARCEL_SIZE_SIZE;
+
+ let pB_dataLength = buf.incomingBufferLength * 3 / 4;
+ let pB = newIncomingParcel(-1,
+ worker.RESPONSE_TYPE_UNSOLICITED,
+ request,
+ calloc(pB_dataLength, 1));
+ let pB_parcelSize = pB.length - buf.PARCEL_SIZE_SIZE;
+
+ // First, send an incomplete pA and verifies related data pointer:
+ let p1 = pA.subarray(0, pA.length - 1);
+ worker.onRILMessage(0, p1);
+ // The parcel should not have been processed.
+ equal(buf.readAvailable, 0);
+ // buf.currentParcelSize should have been set because incoming data has more
+ // than 4 octets.
+ equal(buf.currentParcelSize, pA_parcelSize);
+ // buf.readIncoming should contains remaining unconsumed octets count.
+ equal(buf.readIncoming, p1.length - buf.PARCEL_SIZE_SIZE);
+ // buf.incomingWriteIndex should be ready to accept the last octet.
+ equal(buf.incomingWriteIndex, p1.length);
+
+ // Second, send the last octet of pA and whole pB. The Buf should now expand
+ // to cover both pA & pB.
+ let p2 = new Uint8Array(1 + pB.length);
+ p2.set(pA.subarray(pA.length - 1), 0);
+ p2.set(pB, 1);
+ worker.onRILMessage(0, p2);
+ // The parcels should have been both consumed.
+ equal(buf.readAvailable, 0);
+ // No parcel data remains.
+ equal(buf.currentParcelSize, 0);
+ // No parcel data remains.
+ equal(buf.readIncoming, 0);
+ // The Buf should now expand to cover both pA & pB.
+ equal(buf.incomingWriteIndex, pA.length + pB.length);
+
+ // end of incoming parcel's trip, let's do next test.
+ run_next_test();
+});
+
+// Test Buf.readUint8Array.
+add_test_incoming_parcel(null,
+ function test_buf_readUint8Array() {
+ let buf = this.context.Buf;
+
+ let u8array = buf.readUint8Array(1);
+ equal(u8array instanceof Uint8Array, true);
+ equal(u8array.length, 1);
+ equal(buf.readAvailable, 3);
+
+ u8array = buf.readUint8Array(2);
+ equal(u8array.length, 2);
+ equal(buf.readAvailable, 1);
+
+ throws(function over_read_handler() {
+ // reads more than parcel size, should throw an error.
+ u8array = buf.readUint8Array(2);
+ }, "Trying to read data beyond the parcel end!");
+ }
+);
diff --git a/dom/system/gonk/tests/test_ril_worker_cdma_info_rec.js b/dom/system/gonk/tests/test_ril_worker_cdma_info_rec.js
new file mode 100644
index 000000000..335c0c403
--- /dev/null
+++ b/dom/system/gonk/tests/test_ril_worker_cdma_info_rec.js
@@ -0,0 +1,234 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+subscriptLoader.loadSubScript("resource://gre/modules/ril_consts.js", this);
+
+function run_test() {
+ run_next_test();
+}
+
+/**
+ * Helper function.
+ */
+function newWorkerWithParcel(parcelBuf) {
+ let worker = newWorker({
+ postRILMessage: function(data) {
+ // Do nothing
+ },
+ postMessage: function(message) {
+ // Do nothing
+ }
+ });
+
+ let index = 0; // index for read
+ let buf = parcelBuf;
+
+ let context = worker.ContextPool._contexts[0];
+ context.Buf.readUint8 = function() {
+ return buf[index++];
+ };
+
+ context.Buf.readUint16 = function() {
+ return buf[index++];
+ };
+
+ context.Buf.readInt32 = function() {
+ return buf[index++];
+ };
+
+ context.Buf.seekIncoming = function(offset) {
+ index += offset / context.Buf.UINT32_SIZE;
+ };
+
+ return worker;
+}
+
+// Test CDMA information record decoder.
+
+/**
+ * Verify decoder for type DISPLAY
+ */
+add_test(function test_display() {
+ let worker = newWorkerWithParcel([
+ 0x01, // one inforemation record
+ 0x00, // type: display
+ 0x09, // length: 9
+ 0x54, 0x65, 0x73, 0x74, 0x20, 0x49, 0x6E, 0x66,
+ 0x6F, 0x00]);
+ let context = worker.ContextPool._contexts[0];
+ let helper = context.CdmaPDUHelper;
+ let records = helper.decodeInformationRecord();
+
+ equal(records[0].display, "Test Info");
+
+ run_next_test();
+});
+
+/**
+ * Verify decoder for type EXTENDED DISPLAY
+ */
+add_test(function test_extended_display() {
+ let worker = newWorkerWithParcel([
+ 0x01, // one inforemation record
+ 0x07, // type: extended display
+ 0x12, // length: 18
+ 0x54, 0x65, 0x73, 0x74, 0x20, 0x45, 0x78, 0x74,
+ 0x65, 0x6E, 0x64, 0x65, 0x64, 0x20, 0x49, 0x6E,
+ 0x66, 0x6F, 0x00, 0x00]);
+ let context = worker.ContextPool._contexts[0];
+ let helper = context.CdmaPDUHelper;
+ let records = helper.decodeInformationRecord();
+
+ equal(records[0].display, "Test Extended Info");
+
+ run_next_test();
+});
+
+/**
+ * Verify decoder for mixed type
+ */
+add_test(function test_mixed() {
+ let worker = newWorkerWithParcel([
+ 0x02, // two inforemation record
+ 0x00, // type: display
+ 0x0B, // length: 11
+ 0x54, 0x65, 0x73, 0x74, 0x20, 0x49, 0x6E, 0x66,
+ 0x6F, 0x20, 0x31, 0x00,
+ 0x07, // type: extended display
+ 0x0B, // length: 11
+ 0x54, 0x65, 0x73, 0x74, 0x20, 0x49, 0x6E, 0x66,
+ 0x6F, 0x20, 0x32, 0x00]);
+ let context = worker.ContextPool._contexts[0];
+ let helper = context.CdmaPDUHelper;
+ let records = helper.decodeInformationRecord();
+
+ equal(records[0].display, "Test Info 1");
+ equal(records[1].display, "Test Info 2");
+
+ run_next_test();
+});
+
+/**
+ * Verify decoder for multiple types
+ */
+add_test(function test_multiple() {
+ let worker = newWorkerWithParcel([
+ 0x02, // two inforemation record
+ 0x00, // type: display
+ 0x0B, // length: 11
+ 0x54, 0x65, 0x73, 0x74, 0x20, 0x49, 0x6E, 0x66,
+ 0x6F, 0x20, 0x31, 0x00,
+ 0x00, // type: display
+ 0x0B, // length: 11
+ 0x54, 0x65, 0x73, 0x74, 0x20, 0x49, 0x6E, 0x66,
+ 0x6F, 0x20, 0x32, 0x00]);
+ let context = worker.ContextPool._contexts[0];
+ let helper = context.CdmaPDUHelper;
+ let records = helper.decodeInformationRecord();
+
+ equal(records[0].display, "Test Info 1");
+ equal(records[1].display, "Test Info 2");
+
+ run_next_test();
+});
+
+/**
+ * Verify decoder for Signal Type
+ */
+add_test(function test_signal() {
+ let worker = newWorkerWithParcel([
+ 0x01, // one inforemation record
+ 0x04, // type: signal
+ 0x01, // isPresent: non-zero
+ 0x00, // signalType: Tone signal (00)
+ 0x01, // alertPitch: High pitch
+ 0x03]); // signal: Abbreviated intercept (000011)
+ let context = worker.ContextPool._contexts[0];
+ let helper = context.CdmaPDUHelper;
+ let records = helper.decodeInformationRecord();
+
+ equal(records[0].signal.type, 0x00);
+ equal(records[0].signal.alertPitch, 0x01);
+ equal(records[0].signal.signal, 0x03);
+
+ run_next_test();
+});
+
+/**
+ * Verify decoder for Signal Type for Not Presented
+ */
+add_test(function test_signal_not_present() {
+ let worker = newWorkerWithParcel([
+ 0x01, // one inforemation record
+ 0x04, // type: signal
+ 0x00, // isPresent: zero
+ 0x00, // signalType: Tone signal (00)
+ 0x01, // alertPitch: High pitch
+ 0x03]); // signal: Abbreviated intercept (000011)
+ let context = worker.ContextPool._contexts[0];
+ let helper = context.CdmaPDUHelper;
+ let records = helper.decodeInformationRecord();
+
+ equal(records.length, 0);
+
+ run_next_test();
+});
+
+/**
+ * Verify decoder for Line Control
+ */
+add_test(function test_line_control() {
+ let worker = newWorkerWithParcel([
+ 0x01, // one inforemation record
+ 0x06, // type: line control
+ 0x01, // polarity included
+ 0x00, // not toggled
+ 0x01, // reversed
+ 0xFF]); // Power denial timeout: 255 * 5 ms
+ let context = worker.ContextPool._contexts[0];
+ let helper = context.CdmaPDUHelper;
+ let records = helper.decodeInformationRecord();
+
+ equal(records[0].lineControl.polarityIncluded, 1);
+ equal(records[0].lineControl.toggle, 0);
+ equal(records[0].lineControl.reverse, 1);
+ equal(records[0].lineControl.powerDenial, 255);
+
+ run_next_test();
+});
+
+/**
+ * Verify decoder for CLIR Cause
+ */
+add_test(function test_clir() {
+ let worker = newWorkerWithParcel([
+ 0x01, // one inforemation record
+ 0x08, // type: clir
+ 0x01]); // cause: Rejected by user
+ let context = worker.ContextPool._contexts[0];
+ let helper = context.CdmaPDUHelper;
+ let records = helper.decodeInformationRecord();
+
+ equal(records[0].clirCause, 1);
+
+ run_next_test();
+});
+
+/**
+ * Verify decoder for Audio Control
+ */
+add_test(function test_clir() {
+ let worker = newWorkerWithParcel([
+ 0x01, // one inforemation record
+ 0x0A, // type: audio control
+ 0x01, // uplink
+ 0xFF]); // downlink
+ let context = worker.ContextPool._contexts[0];
+ let helper = context.CdmaPDUHelper;
+ let records = helper.decodeInformationRecord();
+
+ equal(records[0].audioControl.upLink, 1);
+ equal(records[0].audioControl.downLink, 255);
+
+ run_next_test();
+});
diff --git a/dom/system/gonk/tests/test_ril_worker_cellbroadcast_config.js b/dom/system/gonk/tests/test_ril_worker_cellbroadcast_config.js
new file mode 100644
index 000000000..d5645a3cf
--- /dev/null
+++ b/dom/system/gonk/tests/test_ril_worker_cellbroadcast_config.js
@@ -0,0 +1,470 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+subscriptLoader.loadSubScript("resource://gre/modules/ril_consts.js", this);
+
+function run_test() {
+ run_next_test();
+}
+
+add_test(function test_ril_worker_cellbroadcast_activate() {
+ let worker = newWorker({
+ postRILMessage: function(id, parcel) {
+ // Do nothing
+ },
+ postMessage: function(message) {
+ // Do nothing
+ }
+ });
+ let context = worker.ContextPool._contexts[0];
+
+ let parcelTypes = [];
+ let org_newParcel = context.Buf.newParcel;
+ context.Buf.newParcel = function(type, options) {
+ parcelTypes.push(type);
+ org_newParcel.apply(this, arguments);
+ };
+
+ function setup(isCdma) {
+ context.RIL._isCdma = isCdma;
+ context.RIL.cellBroadcastDisabled = false;
+ context.RIL.mergedCellBroadcastConfig = [1, 2, 4, 7]; // 1, 4-6
+ parcelTypes = [];
+ }
+
+ function test(isCdma, expectedRequest) {
+ setup(isCdma);
+ context.RIL.setCellBroadcastDisabled({disabled: true});
+ // Makesure that request parcel is sent out.
+ notEqual(parcelTypes.indexOf(expectedRequest), -1);
+ equal(context.RIL.cellBroadcastDisabled, true);
+ }
+
+ test(false, REQUEST_GSM_SMS_BROADCAST_ACTIVATION);
+ test(true, REQUEST_CDMA_SMS_BROADCAST_ACTIVATION);
+
+ run_next_test();
+});
+
+add_test(function test_ril_worker_cellbroadcast_config() {
+ let currentParcel;
+ let worker = newWorker({
+ postRILMessage: function(id, parcel) {
+ currentParcel = parcel;
+ },
+ postMessage: function(message) {
+ // Do nothing
+ }
+ });
+ let context = worker.ContextPool._contexts[0];
+
+ function U32ArrayFromParcelArray(pa) {
+ do_print(pa);
+ let out = [];
+ for (let i = 0; i < pa.length; i += 4) {
+ let data = pa[i] + (pa[i+1] << 8) + (pa[i+2] << 16) + (pa[i+3] << 24);
+ out.push(data);
+ }
+ return out;
+ }
+
+ function test(isCdma, configs, expected) {
+ let parcelType = isCdma ? REQUEST_CDMA_SET_BROADCAST_SMS_CONFIG
+ : REQUEST_GSM_SET_BROADCAST_SMS_CONFIG;
+
+ let found = false;
+ worker.postRILMessage = function(id, parcel) {
+ let u32Parcel = U32ArrayFromParcelArray(Array.slice(parcel));
+ if (u32Parcel[1] != parcelType) {
+ return;
+ }
+
+ found = true;
+ // Check parcel. Data start from 4th word (32bit)
+ equal(u32Parcel.slice(3).toString(), expected);
+ };
+
+ context.RIL._isCdma = isCdma;
+ context.RIL.setSmsBroadcastConfig(configs);
+
+ // Makesure that request parcel is sent out.
+ ok(found);
+ }
+
+ // (GSM) RIL writes the following data to outgoing parcel:
+ // nums [(from, to, 0, 0xFF, 1), ... ]
+ test(false,
+ [1, 2, 4, 7] /* 1, 4-6 */,
+ ["2", "1,1,0,255,1", "4,6,0,255,1"].join());
+
+ // (CDMA) RIL writes the following data to outgoing parcel:
+ // nums [(id, 0, 1), ... ]
+ test(true,
+ [1, 2, 4, 7] /* 1, 4-6 */,
+ ["4", "1,0,1", "4,0,1", "5,0,1", "6,0,1"].join());
+
+ run_next_test();
+});
+
+add_test(function test_ril_worker_cellbroadcast_merge_config() {
+ let worker = newWorker({
+ postRILMessage: function(id, parcel) {
+ // Do nothing
+ },
+ postMessage: function(message) {
+ // Do nothing
+ }
+ });
+ let context = worker.ContextPool._contexts[0];
+
+ function test(isCdma, configs, expected) {
+ context.RIL._isCdma = isCdma;
+ context.RIL.cellBroadcastConfigs = configs;
+ context.RIL._mergeAllCellBroadcastConfigs();
+ equal(context.RIL.mergedCellBroadcastConfig.toString(), expected);
+ }
+
+ let configs = {
+ MMI: [1, 2, 4, 7], // 1, 4-6
+ CBMI: [6, 9], // 6-8
+ CBMID: [8, 11], // 8-10
+ CBMIR: [10, 13] // 10-12
+ };
+
+ test(false, configs, "1,2,4,13");
+ test(true, configs, "1,2,4,7");
+
+ run_next_test();
+});
+
+add_test(function test_ril_worker_cellbroadcast_set_search_list() {
+ let worker = newWorker({
+ postRILMessage: function(id, parcel) {
+ // Do nothing
+ },
+ postMessage: function(message) {
+ // Do nothing
+ }
+ });
+
+ let context = worker.ContextPool._contexts[0];
+
+ function test(aIsCdma, aSearchList, aExpected) {
+ context.RIL._isCdma = aIsCdma;
+
+ let options = { searchList: aSearchList };
+ context.RIL.setCellBroadcastSearchList(options);
+ // Enforce the MMI result to string for comparison.
+ equal("" + context.RIL.cellBroadcastConfigs.MMI, aExpected);
+ do_check_eq(options.errorMsg, undefined);
+ }
+
+ let searchListStr = "1,2,3,4";
+ let searchList = { gsm: "1,2,3,4", cdma: "5,6,7,8" };
+
+ test(false, searchListStr, "1,2,2,3,3,4,4,5");
+ test(true, searchListStr, "1,2,2,3,3,4,4,5");
+ test(false, searchList, "1,2,2,3,3,4,4,5");
+ test(true, searchList, "5,6,6,7,7,8,8,9");
+ test(false, null, "null");
+ test(true, null, "null");
+
+ run_next_test();
+});
+
+add_test(function test_ril_worker_mergeCellBroadcastConfigs() {
+ let worker = newWorker({
+ postRILMessage: function(data) {
+ // Do nothing
+ },
+ postMessage: function(message) {
+ // Do nothing
+ }
+ });
+
+ let context = worker.ContextPool._contexts[0];
+ let ril = context.RIL;
+
+ function test(olist, from, to, expected) {
+ let result = ril._mergeCellBroadcastConfigs(olist, from, to);
+ equal(JSON.stringify(expected), JSON.stringify(result));
+ }
+
+ test(null, 0, 1, [0, 1]);
+
+ test([10, 13], 7, 8, [ 7, 8, 10, 13]);
+ test([10, 13], 7, 9, [ 7, 9, 10, 13]);
+ test([10, 13], 7, 10, [ 7, 13]);
+ test([10, 13], 7, 11, [ 7, 13]);
+ test([10, 13], 7, 12, [ 7, 13]);
+ test([10, 13], 7, 13, [ 7, 13]);
+ test([10, 13], 7, 14, [ 7, 14]);
+ test([10, 13], 7, 15, [ 7, 15]);
+ test([10, 13], 7, 16, [ 7, 16]);
+ test([10, 13], 8, 9, [ 8, 9, 10, 13]);
+ test([10, 13], 8, 10, [ 8, 13]);
+ test([10, 13], 8, 11, [ 8, 13]);
+ test([10, 13], 8, 12, [ 8, 13]);
+ test([10, 13], 8, 13, [ 8, 13]);
+ test([10, 13], 8, 14, [ 8, 14]);
+ test([10, 13], 8, 15, [ 8, 15]);
+ test([10, 13], 8, 16, [ 8, 16]);
+ test([10, 13], 9, 10, [ 9, 13]);
+ test([10, 13], 9, 11, [ 9, 13]);
+ test([10, 13], 9, 12, [ 9, 13]);
+ test([10, 13], 9, 13, [ 9, 13]);
+ test([10, 13], 9, 14, [ 9, 14]);
+ test([10, 13], 9, 15, [ 9, 15]);
+ test([10, 13], 9, 16, [ 9, 16]);
+ test([10, 13], 10, 11, [10, 13]);
+ test([10, 13], 10, 12, [10, 13]);
+ test([10, 13], 10, 13, [10, 13]);
+ test([10, 13], 10, 14, [10, 14]);
+ test([10, 13], 10, 15, [10, 15]);
+ test([10, 13], 10, 16, [10, 16]);
+ test([10, 13], 11, 12, [10, 13]);
+ test([10, 13], 11, 13, [10, 13]);
+ test([10, 13], 11, 14, [10, 14]);
+ test([10, 13], 11, 15, [10, 15]);
+ test([10, 13], 11, 16, [10, 16]);
+ test([10, 13], 12, 13, [10, 13]);
+ test([10, 13], 12, 14, [10, 14]);
+ test([10, 13], 12, 15, [10, 15]);
+ test([10, 13], 12, 16, [10, 16]);
+ test([10, 13], 13, 14, [10, 14]);
+ test([10, 13], 13, 15, [10, 15]);
+ test([10, 13], 13, 16, [10, 16]);
+ test([10, 13], 14, 15, [10, 13, 14, 15]);
+ test([10, 13], 14, 16, [10, 13, 14, 16]);
+ test([10, 13], 15, 16, [10, 13, 15, 16]);
+
+ test([10, 13, 14, 17], 7, 8, [ 7, 8, 10, 13, 14, 17]);
+ test([10, 13, 14, 17], 7, 9, [ 7, 9, 10, 13, 14, 17]);
+ test([10, 13, 14, 17], 7, 10, [ 7, 13, 14, 17]);
+ test([10, 13, 14, 17], 7, 11, [ 7, 13, 14, 17]);
+ test([10, 13, 14, 17], 7, 12, [ 7, 13, 14, 17]);
+ test([10, 13, 14, 17], 7, 13, [ 7, 13, 14, 17]);
+ test([10, 13, 14, 17], 7, 14, [ 7, 17]);
+ test([10, 13, 14, 17], 7, 15, [ 7, 17]);
+ test([10, 13, 14, 17], 7, 16, [ 7, 17]);
+ test([10, 13, 14, 17], 7, 17, [ 7, 17]);
+ test([10, 13, 14, 17], 7, 18, [ 7, 18]);
+ test([10, 13, 14, 17], 7, 19, [ 7, 19]);
+ test([10, 13, 14, 17], 8, 9, [ 8, 9, 10, 13, 14, 17]);
+ test([10, 13, 14, 17], 8, 10, [ 8, 13, 14, 17]);
+ test([10, 13, 14, 17], 8, 11, [ 8, 13, 14, 17]);
+ test([10, 13, 14, 17], 8, 12, [ 8, 13, 14, 17]);
+ test([10, 13, 14, 17], 8, 13, [ 8, 13, 14, 17]);
+ test([10, 13, 14, 17], 8, 14, [ 8, 17]);
+ test([10, 13, 14, 17], 8, 15, [ 8, 17]);
+ test([10, 13, 14, 17], 8, 16, [ 8, 17]);
+ test([10, 13, 14, 17], 8, 17, [ 8, 17]);
+ test([10, 13, 14, 17], 8, 18, [ 8, 18]);
+ test([10, 13, 14, 17], 8, 19, [ 8, 19]);
+ test([10, 13, 14, 17], 9, 10, [ 9, 13, 14, 17]);
+ test([10, 13, 14, 17], 9, 11, [ 9, 13, 14, 17]);
+ test([10, 13, 14, 17], 9, 12, [ 9, 13, 14, 17]);
+ test([10, 13, 14, 17], 9, 13, [ 9, 13, 14, 17]);
+ test([10, 13, 14, 17], 9, 14, [ 9, 17]);
+ test([10, 13, 14, 17], 9, 15, [ 9, 17]);
+ test([10, 13, 14, 17], 9, 16, [ 9, 17]);
+ test([10, 13, 14, 17], 9, 17, [ 9, 17]);
+ test([10, 13, 14, 17], 9, 18, [ 9, 18]);
+ test([10, 13, 14, 17], 9, 19, [ 9, 19]);
+ test([10, 13, 14, 17], 10, 11, [10, 13, 14, 17]);
+ test([10, 13, 14, 17], 10, 12, [10, 13, 14, 17]);
+ test([10, 13, 14, 17], 10, 13, [10, 13, 14, 17]);
+ test([10, 13, 14, 17], 10, 14, [10, 17]);
+ test([10, 13, 14, 17], 10, 15, [10, 17]);
+ test([10, 13, 14, 17], 10, 16, [10, 17]);
+ test([10, 13, 14, 17], 10, 17, [10, 17]);
+ test([10, 13, 14, 17], 10, 18, [10, 18]);
+ test([10, 13, 14, 17], 10, 19, [10, 19]);
+ test([10, 13, 14, 17], 11, 12, [10, 13, 14, 17]);
+ test([10, 13, 14, 17], 11, 13, [10, 13, 14, 17]);
+ test([10, 13, 14, 17], 11, 14, [10, 17]);
+ test([10, 13, 14, 17], 11, 15, [10, 17]);
+ test([10, 13, 14, 17], 11, 16, [10, 17]);
+ test([10, 13, 14, 17], 11, 17, [10, 17]);
+ test([10, 13, 14, 17], 11, 18, [10, 18]);
+ test([10, 13, 14, 17], 11, 19, [10, 19]);
+ test([10, 13, 14, 17], 12, 13, [10, 13, 14, 17]);
+ test([10, 13, 14, 17], 12, 14, [10, 17]);
+ test([10, 13, 14, 17], 12, 15, [10, 17]);
+ test([10, 13, 14, 17], 12, 16, [10, 17]);
+ test([10, 13, 14, 17], 12, 17, [10, 17]);
+ test([10, 13, 14, 17], 12, 18, [10, 18]);
+ test([10, 13, 14, 17], 12, 19, [10, 19]);
+ test([10, 13, 14, 17], 13, 14, [10, 17]);
+ test([10, 13, 14, 17], 13, 15, [10, 17]);
+ test([10, 13, 14, 17], 13, 16, [10, 17]);
+ test([10, 13, 14, 17], 13, 17, [10, 17]);
+ test([10, 13, 14, 17], 13, 18, [10, 18]);
+ test([10, 13, 14, 17], 13, 19, [10, 19]);
+ test([10, 13, 14, 17], 14, 15, [10, 13, 14, 17]);
+ test([10, 13, 14, 17], 14, 16, [10, 13, 14, 17]);
+ test([10, 13, 14, 17], 14, 17, [10, 13, 14, 17]);
+ test([10, 13, 14, 17], 14, 18, [10, 13, 14, 18]);
+ test([10, 13, 14, 17], 14, 19, [10, 13, 14, 19]);
+ test([10, 13, 14, 17], 15, 16, [10, 13, 14, 17]);
+ test([10, 13, 14, 17], 15, 17, [10, 13, 14, 17]);
+ test([10, 13, 14, 17], 15, 18, [10, 13, 14, 18]);
+ test([10, 13, 14, 17], 15, 19, [10, 13, 14, 19]);
+ test([10, 13, 14, 17], 16, 17, [10, 13, 14, 17]);
+ test([10, 13, 14, 17], 16, 18, [10, 13, 14, 18]);
+ test([10, 13, 14, 17], 16, 19, [10, 13, 14, 19]);
+ test([10, 13, 14, 17], 17, 18, [10, 13, 14, 18]);
+ test([10, 13, 14, 17], 17, 19, [10, 13, 14, 19]);
+ test([10, 13, 14, 17], 18, 19, [10, 13, 14, 17, 18, 19]);
+
+ test([10, 13, 16, 19], 7, 14, [ 7, 14, 16, 19]);
+ test([10, 13, 16, 19], 7, 15, [ 7, 15, 16, 19]);
+ test([10, 13, 16, 19], 7, 16, [ 7, 19]);
+ test([10, 13, 16, 19], 8, 14, [ 8, 14, 16, 19]);
+ test([10, 13, 16, 19], 8, 15, [ 8, 15, 16, 19]);
+ test([10, 13, 16, 19], 8, 16, [ 8, 19]);
+ test([10, 13, 16, 19], 9, 14, [ 9, 14, 16, 19]);
+ test([10, 13, 16, 19], 9, 15, [ 9, 15, 16, 19]);
+ test([10, 13, 16, 19], 9, 16, [ 9, 19]);
+ test([10, 13, 16, 19], 10, 14, [10, 14, 16, 19]);
+ test([10, 13, 16, 19], 10, 15, [10, 15, 16, 19]);
+ test([10, 13, 16, 19], 10, 16, [10, 19]);
+ test([10, 13, 16, 19], 11, 14, [10, 14, 16, 19]);
+ test([10, 13, 16, 19], 11, 15, [10, 15, 16, 19]);
+ test([10, 13, 16, 19], 11, 16, [10, 19]);
+ test([10, 13, 16, 19], 12, 14, [10, 14, 16, 19]);
+ test([10, 13, 16, 19], 12, 15, [10, 15, 16, 19]);
+ test([10, 13, 16, 19], 12, 16, [10, 19]);
+ test([10, 13, 16, 19], 13, 14, [10, 14, 16, 19]);
+ test([10, 13, 16, 19], 13, 15, [10, 15, 16, 19]);
+ test([10, 13, 16, 19], 13, 16, [10, 19]);
+ test([10, 13, 16, 19], 14, 15, [10, 13, 14, 15, 16, 19]);
+ test([10, 13, 16, 19], 14, 16, [10, 13, 14, 19]);
+ test([10, 13, 16, 19], 15, 16, [10, 13, 15, 19]);
+
+ run_next_test();
+});
+
+add_test(function test_ril_consts_cellbroadcast_misc() {
+ // Must be 16 for indexing.
+ equal(CB_DCS_LANG_GROUP_1.length, 16);
+ equal(CB_DCS_LANG_GROUP_2.length, 16);
+
+ // Array length must be even.
+ equal(CB_NON_MMI_SETTABLE_RANGES.length & 0x01, 0);
+ for (let i = 0; i < CB_NON_MMI_SETTABLE_RANGES.length;) {
+ let from = CB_NON_MMI_SETTABLE_RANGES[i++];
+ let to = CB_NON_MMI_SETTABLE_RANGES[i++];
+ equal(from < to, true);
+ }
+
+ run_next_test();
+});
+
+add_test(function test_ril_worker_checkCellBroadcastMMISettable() {
+ let worker = newWorker({
+ postRILMessage: function(data) {
+ // Do nothing
+ },
+ postMessage: function(message) {
+ // Do nothing
+ }
+ });
+
+ let context = worker.ContextPool._contexts[0];
+ let ril = context.RIL;
+
+ function test(from, to, expected) {
+ equal(expected, ril._checkCellBroadcastMMISettable(from, to));
+ }
+
+ test(-2, -1, false);
+ test(-1, 0, false);
+ test(0, 1, true);
+ test(1, 1, false);
+ test(2, 1, false);
+ test(65536, 65537, false);
+
+ // We have both [4096, 4224), [4224, 4352), so it's actually [4096, 4352),
+ // and [61440, 65536), [65535, 65536), so it's actually [61440, 65536).
+ for (let i = 0; i < CB_NON_MMI_SETTABLE_RANGES.length;) {
+ let from = CB_NON_MMI_SETTABLE_RANGES[i++];
+ let to = CB_NON_MMI_SETTABLE_RANGES[i++];
+ if ((from != 4224) && (from != 65535)) {
+ test(from - 1, from, true);
+ }
+ test(from - 1, from + 1, false);
+ test(from - 1, to, false);
+ test(from - 1, to + 1, false);
+ test(from, from + 1, false);
+ test(from, to, false);
+ test(from, to + 1, false);
+ if ((from + 1) < to) {
+ test(from + 1, to, false);
+ test(from + 1, to + 1, false);
+ }
+ if ((to != 4224) && (to < 65535)) {
+ test(to, to + 1, true);
+ test(to + 1, to + 2, true);
+ }
+ }
+
+ run_next_test();
+});
+
+add_test(function test_ril_worker_CellBroadcastDisabled() {
+ let count = 0;
+ let worker = newWorker({
+ postRILMessage: function(data) {
+ // Do nothing
+ },
+ postMessage: function(message) {
+ if (message.rilMessageType == "cellbroadcast-received") {
+ ok(true, "cellbroadcast-received: " + JSON.stringify(message));
+ count++;
+ }
+ }
+ });
+
+ function buildPdu(aMessageId) {
+ return "C002" + aMessageId + "011154741914AFA7C76B9058" +
+ "FEBEBB41E6371EA4AEB7E173D0DB5E96" +
+ "83E8E832881DD6E741E4F7B9D168341A" +
+ "8D46A3D168341A8D46A3D168341A8D46" +
+ "A3D168341A8D46A3D168341A8D46A3D1" +
+ "68341A8D46A3D100";
+ }
+
+ worker.ContextPool._contexts[0].RIL.cellBroadcastDisabled = true;
+
+ let networkAlertIds = [
+ "1100", "1107", // ETWS
+ "1112", "112F", // CMAS
+ "1130", "18FF", // PWS
+ ];
+ networkAlertIds.forEach(aMessageId => {
+ worker.onRILMessage(
+ 0,
+ newIncomingParcel(
+ -1,
+ RESPONSE_TYPE_UNSOLICITED,
+ UNSOLICITED_RESPONSE_NEW_BROADCAST_SMS,
+ hexStringToParcelByteArrayData(buildPdu(aMessageId))));
+ });
+ equal(count, networkAlertIds.length, "Alerts shall not be ignored.");
+
+ count = 0;
+ let normalMsgIds = [ "0000", "03E7", "1108", "1901" ];
+ normalMsgIds.forEach(aMessageId => {
+ worker.onRILMessage(
+ 0,
+ newIncomingParcel(
+ -1,
+ RESPONSE_TYPE_UNSOLICITED,
+ UNSOLICITED_RESPONSE_NEW_BROADCAST_SMS,
+ hexStringToParcelByteArrayData(buildPdu(aMessageId))));
+ });
+ equal(count, 0, "Normal messages shall be ignored.");
+
+ run_next_test();
+});
diff --git a/dom/system/gonk/tests/test_ril_worker_cellbroadcast_gsm.js b/dom/system/gonk/tests/test_ril_worker_cellbroadcast_gsm.js
new file mode 100644
index 000000000..b08b64135
--- /dev/null
+++ b/dom/system/gonk/tests/test_ril_worker_cellbroadcast_gsm.js
@@ -0,0 +1,230 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+subscriptLoader.loadSubScript("resource://gre/modules/ril_consts.js", this);
+
+function run_test() {
+ run_next_test();
+}
+
+add_test(function test_ril_worker_GsmPDUHelper_readCbDataCodingScheme() {
+ let worker = newWorker({
+ postRILMessage: function(data) {
+ // Do nothing
+ },
+ postMessage: function(message) {
+ // Do nothing
+ }
+ });
+
+ let context = worker.ContextPool._contexts[0];
+ function test_dcs(dcs, encoding, language, hasLanguageIndicator, messageClass) {
+ context.Buf.readUint8 = function() {
+ return dcs;
+ };
+
+ let msg = {};
+ context.GsmPDUHelper.readCbDataCodingScheme(msg);
+
+ equal(msg.dcs, dcs);
+ equal(msg.encoding, encoding);
+ equal(msg.language, language);
+ equal(msg.hasLanguageIndicator, hasLanguageIndicator);
+ equal(msg.messageClass, messageClass);
+ }
+
+ function test_dcs_throws(dcs) {
+ context.Buf.readUint8 = function() {
+ return dcs;
+ };
+
+ throws(function() {
+ context.GsmPDUHelper.readCbDataCodingScheme({});
+ }, "Unsupported CBS data coding scheme: " + dcs);
+ }
+
+ // Group 0000
+ for (let i = 0; i < 16; i++) {
+ test_dcs(i, PDU_DCS_MSG_CODING_7BITS_ALPHABET, CB_DCS_LANG_GROUP_1[i],
+ false, GECKO_SMS_MESSAGE_CLASSES[PDU_DCS_MSG_CLASS_NORMAL]);
+ }
+
+ // Group 0001
+ // 0000 GSM 7 bit default alphabet; message preceded by language indication.
+ test_dcs(0x10, PDU_DCS_MSG_CODING_7BITS_ALPHABET, null, true,
+ GECKO_SMS_MESSAGE_CLASSES[PDU_DCS_MSG_CLASS_NORMAL]);
+ // 0001 UCS2; message preceded by language indication.
+ test_dcs(0x11, PDU_DCS_MSG_CODING_16BITS_ALPHABET, null, true,
+ GECKO_SMS_MESSAGE_CLASSES[PDU_DCS_MSG_CLASS_NORMAL]);
+
+ // Group 0010
+ // 0000..0100
+ for (let i = 0; i < 5; i++) {
+ test_dcs(0x20 + i, PDU_DCS_MSG_CODING_7BITS_ALPHABET,
+ CB_DCS_LANG_GROUP_2[i], false,
+ GECKO_SMS_MESSAGE_CLASSES[PDU_DCS_MSG_CLASS_NORMAL]);
+ }
+ // 0101..1111 Reserved
+ for (let i = 5; i < 16; i++) {
+ test_dcs(0x20 + i, PDU_DCS_MSG_CODING_7BITS_ALPHABET, null, false,
+ GECKO_SMS_MESSAGE_CLASSES[PDU_DCS_MSG_CLASS_NORMAL]);
+ }
+
+ // Group 0100, 0101, 1001
+ for (let group of [0x40, 0x50, 0x90]) {
+ for (let i = 0; i < 16; i++) {
+ let encoding = i & 0x0C;
+ if (encoding == 0x0C) {
+ encoding = PDU_DCS_MSG_CODING_7BITS_ALPHABET;
+ }
+ let messageClass = GECKO_SMS_MESSAGE_CLASSES[i & PDU_DCS_MSG_CLASS_BITS];
+ test_dcs(group + i, encoding, null, false, messageClass);
+ }
+ }
+
+ // Group 1111
+ for (let i = 0; i < 16; i ++) {
+ let encoding = i & 0x04 ? PDU_DCS_MSG_CODING_8BITS_ALPHABET
+ : PDU_DCS_MSG_CODING_7BITS_ALPHABET;
+ let messageClass;
+ switch(i & PDU_DCS_MSG_CLASS_BITS) {
+ case 0x01: messageClass = PDU_DCS_MSG_CLASS_USER_1; break;
+ case 0x02: messageClass = PDU_DCS_MSG_CLASS_USER_2; break;
+ case 0x03: messageClass = PDU_DCS_MSG_CLASS_3; break;
+ default: messageClass = PDU_DCS_MSG_CLASS_NORMAL; break;
+ }
+ test_dcs(0xF0 + i, encoding, null, false,
+ GECKO_SMS_MESSAGE_CLASSES[messageClass]);
+ }
+
+ // Group 0011, 1000, 1010, 1011, 1100
+ // 0000..1111 Reserved
+ for (let group of [0x30, 0x80, 0xA0, 0xB0, 0xC0]) {
+ for (let i = 0; i < 16; i++) {
+ test_dcs(group + i, PDU_DCS_MSG_CODING_7BITS_ALPHABET, null, false,
+ GECKO_SMS_MESSAGE_CLASSES[PDU_DCS_MSG_CLASS_NORMAL]);
+ }
+ }
+
+ // Group 0110, 0111, 1101, 1110
+ // TODO: unsupported
+ for (let group of [0x60, 0x70, 0xD0, 0xE0]) {
+ for (let i = 0; i < 16; i++) {
+ test_dcs_throws(group + i);
+ }
+ }
+
+ run_next_test();
+});
+
+add_test(function test_ril_worker_GsmPDUHelper_readGsmCbData() {
+ let worker = newWorker({
+ postRILMessage: function(data) {
+ // Do nothing
+ },
+ postMessage: function(message) {
+ // Do nothing
+ }
+ });
+
+ let context = worker.ContextPool._contexts[0];
+ function test_data(options, expected) {
+ let readIndex = 0;
+ context.Buf.readUint8 = function() {
+ return options[3][readIndex++];
+ };
+ context.Buf.readUint8Array = function(length) {
+ let array = new Uint8Array(length);
+ for (let i = 0; i < length; i++) {
+ array[i] = this.readUint8();
+ }
+ return array;
+ };
+
+ let msg = {
+ encoding: options[0],
+ language: options[1],
+ hasLanguageIndicator: options[2]
+ };
+ context.GsmPDUHelper.readGsmCbData(msg, options[3].length);
+
+ equal(msg.body, expected[0]);
+ equal(msg.data == null, expected[1] == null);
+ if (expected[1] != null) {
+ equal(msg.data.length, expected[1].length);
+ for (let i = 0; i < expected[1].length; i++) {
+ equal(msg.data[i], expected[1][i]);
+ }
+ }
+ equal(msg.language, expected[2]);
+ }
+
+ // We're testing Cell Broadcast message body with all zeros octet stream. As
+ // shown in 3GPP TS 23.038, septet 0x00 will be decoded as '@' when both
+ // langTableIndex and langShiftTableIndex equal to
+ // PDU_DCS_MSG_CODING_7BITS_ALPHABET.
+
+ // PDU_DCS_MSG_CODING_7BITS_ALPHABET
+ test_data([PDU_DCS_MSG_CODING_7BITS_ALPHABET, null, false,
+ [0]],
+ ["@", null, null]);
+ test_data([PDU_DCS_MSG_CODING_7BITS_ALPHABET, null, true,
+ [0, 0, 0, 0]],
+ ["@", null, "@@"]);
+ test_data([PDU_DCS_MSG_CODING_7BITS_ALPHABET, "@@", false,
+ [0]],
+ ["@", null, "@@"]);
+
+ // PDU_DCS_MSG_CODING_8BITS_ALPHABET
+ test_data([PDU_DCS_MSG_CODING_8BITS_ALPHABET, null, false,
+ [0]],
+ [null, [0], null]);
+
+ // PDU_DCS_MSG_CODING_16BITS_ALPHABET
+ test_data([PDU_DCS_MSG_CODING_16BITS_ALPHABET, null, false,
+ [0x00, 0x40]],
+ ["@", null, null]);
+ test_data([PDU_DCS_MSG_CODING_16BITS_ALPHABET, null, true,
+ [0x00, 0x00, 0x00, 0x40]],
+ ["@", null, "@@"]);
+ test_data([PDU_DCS_MSG_CODING_16BITS_ALPHABET, "@@", false,
+ [0x00, 0x40]],
+ ["@", null, "@@"]);
+
+ run_next_test();
+});
+
+add_test(function test_ril_worker_Sim_Download_Message() {
+ let worker = newWorker({
+ postRILMessage: function(data) {
+ // Do nothing
+ },
+ postMessage: function(message) {
+ ok(message.rilMessageType !== "cellbroadcast-received",
+ "Data-Download message shall be ignored.");
+ }
+ });
+
+ function buildPdu(aMessageId) {
+ return "C002" + aMessageId + "011154741914AFA7C76B9058" +
+ "FEBEBB41E6371EA4AEB7E173D0DB5E96" +
+ "83E8E832881DD6E741E4F7B9D168341A" +
+ "8D46A3D168341A8D46A3D168341A8D46" +
+ "A3D168341A8D46A3D168341A8D46A3D1" +
+ "68341A8D46A3D100";
+ }
+
+ ["1000", "107F", "1080", "10FF"].forEach(aMessageId => {
+ worker.onRILMessage(
+ 0,
+ newIncomingParcel(
+ -1,
+ RESPONSE_TYPE_UNSOLICITED,
+ UNSOLICITED_RESPONSE_NEW_BROADCAST_SMS,
+ hexStringToParcelByteArrayData(buildPdu(aMessageId))));
+ });
+
+ ok(true, "All Data-Download Messages are ingored.");
+
+ run_next_test();
+});
diff --git a/dom/system/gonk/tests/test_ril_worker_cellbroadcast_umts.js b/dom/system/gonk/tests/test_ril_worker_cellbroadcast_umts.js
new file mode 100644
index 000000000..0380c4122
--- /dev/null
+++ b/dom/system/gonk/tests/test_ril_worker_cellbroadcast_umts.js
@@ -0,0 +1,105 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+subscriptLoader.loadSubScript("resource://gre/modules/ril_consts.js", this);
+
+function run_test() {
+ run_next_test();
+}
+
+function buildHexStr(aNum, aNumSemiOctets) {
+ let str = aNum.toString(16);
+ while (str.length < aNumSemiOctets) {
+ str = "0" + str;
+ }
+ return str;
+}
+
+/**
+ * Verify GsmPDUHelper#readUmtsCbMessage with numOfPages from 1 to 15.
+ */
+add_test(function test_GsmPDUHelper_readUmtsCbMessage_MultiParts() {
+ let CB_UMTS_MESSAGE_PAGE_SIZE = 82;
+ let CB_MAX_CONTENT_PER_PAGE_7BIT = 93;
+ let workerHelper = newInterceptWorker(),
+ worker = workerHelper.worker,
+ context = worker.ContextPool._contexts[0],
+ GsmPDUHelper = context.GsmPDUHelper;
+
+ function test_MultiParts(aNumOfPages) {
+ let pdu = buildHexStr(CB_UMTS_MESSAGE_TYPE_CBS, 2) // msg_type
+ + buildHexStr(0, 4) // skip msg_id
+ + buildHexStr(0, 4) // skip SN
+ + buildHexStr(0, 2) // skip dcs
+ + buildHexStr(aNumOfPages, 2); // set num_of_pages
+ for (let i = 1; i <= aNumOfPages; i++) {
+ pdu = pdu + buildHexStr(0, CB_UMTS_MESSAGE_PAGE_SIZE * 2)
+ + buildHexStr(CB_UMTS_MESSAGE_PAGE_SIZE, 2); // msg_info_length
+ }
+
+ worker.onRILMessage(0, newIncomingParcel(-1,
+ RESPONSE_TYPE_UNSOLICITED,
+ UNSOLICITED_RESPONSE_NEW_BROADCAST_SMS,
+ hexStringToParcelByteArrayData(pdu)));
+
+ let postedMessage = workerHelper.postedMessage;
+ equal("cellbroadcast-received", postedMessage.rilMessageType);
+ equal(postedMessage.fullBody.length,
+ aNumOfPages * CB_MAX_CONTENT_PER_PAGE_7BIT);
+ }
+
+ [1, 5, 15].forEach(function(i) {
+ test_MultiParts(i);
+ });
+
+ run_next_test();
+});
+
+/**
+ * Verify GsmPDUHelper#readUmtsCbMessage with 8bit encoded.
+ */
+add_test(function test_GsmPDUHelper_readUmtsCbMessage_Binary() {
+ let CB_UMTS_MESSAGE_PAGE_SIZE = 82;
+ let CB_MAX_CONTENT_PER_PAGE_7BIT = 93;
+ let TEXT_BINARY = "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"
+ + "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"
+ + "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"
+ + "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"
+ + "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"
+ + "FFFF";
+ let workerHelper = newInterceptWorker(),
+ worker = workerHelper.worker,
+ context = worker.ContextPool._contexts[0],
+ GsmPDUHelper = context.GsmPDUHelper;
+
+ function test_MultiPartsBinary(aNumOfPages) {
+ let pdu = buildHexStr(CB_UMTS_MESSAGE_TYPE_CBS, 2) // msg_type
+ + buildHexStr(0, 4) // skip msg_id
+ + buildHexStr(0, 4) // skip SN
+ + buildHexStr(68, 2) // set DCS to 8bit data
+ + buildHexStr(aNumOfPages, 2); // set num_of_pages
+ for (let i = 1; i <= aNumOfPages; i++) {
+ pdu = pdu + TEXT_BINARY
+ + buildHexStr(CB_UMTS_MESSAGE_PAGE_SIZE, 2); // msg_info_length
+ }
+
+ worker.onRILMessage(0, newIncomingParcel(-1,
+ RESPONSE_TYPE_UNSOLICITED,
+ UNSOLICITED_RESPONSE_NEW_BROADCAST_SMS,
+ hexStringToParcelByteArrayData(pdu)));
+
+ let postedMessage = workerHelper.postedMessage;
+ equal("cellbroadcast-received", postedMessage.rilMessageType);
+ equal(postedMessage.fullData.length,
+ aNumOfPages * CB_UMTS_MESSAGE_PAGE_SIZE);
+ for (let i = 0; i < postedMessage.fullData.length; i++) {
+ equal(postedMessage.fullData[i], 255);
+ }
+ }
+
+ [1, 5, 15].forEach(function(i) {
+ test_MultiPartsBinary(i);
+ });
+
+ run_next_test();
+});
diff --git a/dom/system/gonk/tests/test_ril_worker_cf.js b/dom/system/gonk/tests/test_ril_worker_cf.js
new file mode 100644
index 000000000..b8db716b7
--- /dev/null
+++ b/dom/system/gonk/tests/test_ril_worker_cf.js
@@ -0,0 +1,126 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+subscriptLoader.loadSubScript("resource://gre/modules/ril_consts.js", this);
+
+function run_test() {
+ run_next_test();
+}
+
+function toaFromString(number) {
+ let worker = newWorker({
+ postRILMessage: function(data) {
+ // Do nothing
+ },
+ postMessage: function(message) {
+ // Do nothing
+ }
+ });
+ let context = worker.ContextPool._contexts[0];
+ return context.RIL._toaFromString(number);
+}
+
+add_test(function test_toaFromString_empty() {
+ let retval = toaFromString("");
+
+ equal(retval, TOA_UNKNOWN);
+
+ run_next_test();
+});
+
+add_test(function test_toaFromString_undefined() {
+ let retval = toaFromString();
+
+ equal(retval, TOA_UNKNOWN);
+
+ run_next_test();
+});
+
+add_test(function test_toaFromString_unknown() {
+ let retval = toaFromString("666222333");
+
+ equal(retval, TOA_UNKNOWN);
+
+ run_next_test();
+});
+
+add_test(function test_toaFromString_international() {
+ let retval = toaFromString("+34666222333");
+
+ equal(retval, TOA_INTERNATIONAL);
+
+ run_next_test();
+});
+
+add_test(function test_setCallForward_unconditional() {
+ let workerHelper = newInterceptWorker();
+ let worker = workerHelper.worker;
+ let context = worker.ContextPool._contexts[0];
+
+ context.RIL.setCallForward = function fakeSetCallForward(options) {
+ context.RIL[REQUEST_SET_CALL_FORWARD](0, {});
+ };
+
+ context.RIL.setCallForward({
+ action: CALL_FORWARD_ACTION_REGISTRATION,
+ reason: CALL_FORWARD_REASON_UNCONDITIONAL,
+ serviceClass: ICC_SERVICE_CLASS_VOICE,
+ number: "666222333",
+ timeSeconds: 10
+ });
+
+ let postedMessage = workerHelper.postedMessage;
+
+ equal(postedMessage.errorMsg, undefined);
+
+ run_next_test();
+});
+
+add_test(function test_queryCallForwardStatus_unconditional() {
+ let workerHelper = newInterceptWorker();
+ let worker = workerHelper.worker;
+ let context = worker.ContextPool._contexts[0];
+
+ context.RIL.setCallForward = function fakeSetCallForward(options) {
+ context.RIL[REQUEST_SET_CALL_FORWARD](0, {});
+ };
+
+ context.Buf.readInt32 = function fakeReadUint32() {
+ return context.Buf.int32Array.pop();
+ };
+
+ context.Buf.readString = function fakeReadString() {
+ return "+34666222333";
+ };
+
+ context.RIL.queryCallForwardStatus = function fakeQueryCallForward(options) {
+ context.Buf.int32Array = [
+ 0, // rules.timeSeconds
+ 145, // rules.toa
+ 49, // rules.serviceClass
+ CALL_FORWARD_REASON_UNCONDITIONAL, // rules.reason
+ 1, // rules.active
+ 1 // rulesLength
+ ];
+ context.RIL[REQUEST_QUERY_CALL_FORWARD_STATUS](1, {});
+ };
+
+ context.RIL.queryCallForwardStatus({
+ action: CALL_FORWARD_ACTION_QUERY_STATUS,
+ reason: CALL_FORWARD_REASON_UNCONDITIONAL,
+ serviceClass: ICC_SERVICE_CLASS_VOICE,
+ number: "666222333",
+ timeSeconds: 10
+ });
+
+ let postedMessage = workerHelper.postedMessage;
+
+ equal(postedMessage.errorMsg, undefined);
+ ok(Array.isArray(postedMessage.rules));
+ do_print(postedMessage.rules.length);
+ equal(postedMessage.rules.length, 1);
+ ok(postedMessage.rules[0].active);
+ equal(postedMessage.rules[0].reason, CALL_FORWARD_REASON_UNCONDITIONAL);
+ equal(postedMessage.rules[0].number, "+34666222333");
+ run_next_test();
+});
diff --git a/dom/system/gonk/tests/test_ril_worker_clip.js b/dom/system/gonk/tests/test_ril_worker_clip.js
new file mode 100644
index 000000000..d1ce5f617
--- /dev/null
+++ b/dom/system/gonk/tests/test_ril_worker_clip.js
@@ -0,0 +1,59 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+subscriptLoader.loadSubScript("resource://gre/modules/ril_consts.js", this);
+
+function run_test() {
+ run_next_test();
+}
+
+add_test(function test_queryCLIP_provisioned() {
+ let workerHelper = newInterceptWorker();
+ let worker = workerHelper.worker;
+ let context = worker.ContextPool._contexts[0];
+
+ context.Buf.readInt32 = function fakeReadUint32() {
+ return context.Buf.int32Array.pop();
+ };
+
+ context.RIL.queryCLIP = function fakeQueryCLIP(options) {
+ context.Buf.int32Array = [
+ 1, // CLIP provisioned.
+ 1 // Length.
+ ];
+ context.RIL[REQUEST_QUERY_CLIP](1, {});
+ };
+
+ context.RIL.queryCLIP({});
+
+ let postedMessage = workerHelper.postedMessage;
+
+ equal(postedMessage.errorMsg, undefined);
+ equal(postedMessage.provisioned, 1);
+ run_next_test();
+});
+
+add_test(function test_getCLIP_error_generic_failure_invalid_length() {
+ let workerHelper = newInterceptWorker();
+ let worker = workerHelper.worker;
+ let context = worker.ContextPool._contexts[0];
+
+ context.Buf.readInt32 = function fakeReadUint32() {
+ return context.Buf.int32Array.pop();
+ };
+
+ context.RIL.queryCLIP = function fakeQueryCLIP(options) {
+ context.Buf.int32Array = [
+ 1, // CLIP provisioned.
+ 0 // Length.
+ ];
+ context.RIL[REQUEST_QUERY_CLIP](1, {});
+ };
+
+ context.RIL.queryCLIP({});
+
+ let postedMessage = workerHelper.postedMessage;
+
+ equal(postedMessage.errorMsg, GECKO_ERROR_GENERIC_FAILURE);
+ run_next_test();
+});
diff --git a/dom/system/gonk/tests/test_ril_worker_clir.js b/dom/system/gonk/tests/test_ril_worker_clir.js
new file mode 100644
index 000000000..5882a3c4c
--- /dev/null
+++ b/dom/system/gonk/tests/test_ril_worker_clir.js
@@ -0,0 +1,122 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+subscriptLoader.loadSubScript("resource://gre/modules/ril_consts.js", this);
+
+// Calling line identification restriction constants.
+
+// Uses subscription default value.
+const CLIR_DEFAULT = 0;
+// Restricts CLI presentation.
+const CLIR_INVOCATION = 1;
+// Allows CLI presentation.
+const CLIR_SUPPRESSION = 2;
+
+function run_test() {
+ run_next_test();
+}
+
+add_test(function test_setCLIR_success() {
+ let workerHelper = newInterceptWorker();
+ let worker = workerHelper.worker;
+ let context = worker.ContextPool._contexts[0];
+
+ context.RIL.setCLIR = function fakeSetCLIR(options) {
+ context.RIL[REQUEST_SET_CLIR](0, {
+ rilMessageType: "setCLIR"
+ });
+ };
+
+ context.RIL.setCLIR({
+ clirMode: CLIR_DEFAULT
+ });
+
+ let postedMessage = workerHelper.postedMessage;
+
+ equal(postedMessage.errorMsg, undefined);
+
+ run_next_test();
+});
+
+add_test(function test_setCLIR_generic_failure() {
+ let workerHelper = newInterceptWorker();
+ let worker = workerHelper.worker;
+ let context = worker.ContextPool._contexts[0];
+
+ context.RIL.setCLIR = function fakeSetCLIR(options) {
+ context.RIL[REQUEST_SET_CLIR](0, {
+ rilMessageType: "setCLIR",
+ errorMsg: GECKO_ERROR_GENERIC_FAILURE
+ });
+ };
+
+ context.RIL.setCLIR({
+ clirMode: CLIR_DEFAULT
+ });
+
+ let postedMessage = workerHelper.postedMessage;
+
+ equal(postedMessage.errorMsg, GECKO_ERROR_GENERIC_FAILURE);
+
+ run_next_test();
+});
+
+add_test(function test_getCLIR_n0_m1() {
+ let workerHelper = newInterceptWorker();
+ let worker = workerHelper.worker;
+ let context = worker.ContextPool._contexts[0];
+
+ context.Buf.readInt32 = function fakeReadUint32() {
+ return context.Buf.int32Array.pop();
+ };
+
+ context.RIL.getCLIR = function fakeGetCLIR(options) {
+ context.Buf.int32Array = [
+ 1, // Presentation indicator is used according to the subscription
+ // of the CLIR service.
+ 0, // CLIR provisioned in permanent mode.
+ 2 // Length.
+ ];
+ context.RIL[REQUEST_GET_CLIR](1, {
+ rilMessageType: "setCLIR"
+ });
+ };
+
+ context.RIL.getCLIR({});
+
+ let postedMessage = workerHelper.postedMessage;
+
+ equal(postedMessage.errorMsg, undefined);
+ equal(postedMessage.n, 0);
+ equal(postedMessage.m, 1);
+ run_next_test();
+});
+
+add_test(function test_getCLIR_error_generic_failure_invalid_length() {
+ let workerHelper = newInterceptWorker();
+ let worker = workerHelper.worker;
+ let context = worker.ContextPool._contexts[0];
+
+ context.Buf.readInt32 = function fakeReadUint32() {
+ return context.Buf.int32Array.pop();
+ };
+
+ context.RIL.getCLIR = function fakeGetCLIR(options) {
+ context.Buf.int32Array = [
+ 1, // Presentation indicator is used according to the subscription
+ // of the CLIR service.
+ 0, // CLIR provisioned in permanent mode.
+ 0 // Length (invalid one).
+ ];
+ context.RIL[REQUEST_GET_CLIR](1, {
+ rilMessageType: "setCLIR"
+ });
+ };
+
+ context.RIL.getCLIR({});
+
+ let postedMessage = workerHelper.postedMessage;
+
+ equal(postedMessage.errorMsg, GECKO_ERROR_GENERIC_FAILURE);
+ run_next_test();
+});
diff --git a/dom/system/gonk/tests/test_ril_worker_cw.js b/dom/system/gonk/tests/test_ril_worker_cw.js
new file mode 100644
index 000000000..efa8b5c21
--- /dev/null
+++ b/dom/system/gonk/tests/test_ril_worker_cw.js
@@ -0,0 +1,104 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+subscriptLoader.loadSubScript("resource://gre/modules/ril_consts.js", this);
+
+function run_test() {
+ run_next_test();
+}
+
+add_test(function test_setCallWaiting_success() {
+ let workerHelper = newInterceptWorker();
+ let worker = workerHelper.worker;
+ let context = worker.ContextPool._contexts[0];
+
+ context.RIL.setCallWaiting = function fakeSetCallWaiting(options) {
+ context.RIL[REQUEST_SET_CALL_WAITING](0, {});
+ };
+
+ context.RIL.setCallWaiting({
+ enabled: true
+ });
+
+ let postedMessage = workerHelper.postedMessage;
+
+ equal(postedMessage.errorMsg, undefined);
+
+ run_next_test();
+});
+
+add_test(function test_setCallWaiting_generic_failure() {
+ let workerHelper = newInterceptWorker();
+ let worker = workerHelper.worker;
+ let context = worker.ContextPool._contexts[0];
+
+ context.RIL.setCallWaiting = function fakeSetCallWaiting(options) {
+ context.RIL[REQUEST_SET_CALL_WAITING](0, {
+ errorMsg: GECKO_ERROR_GENERIC_FAILURE
+ });
+ };
+
+ context.RIL.setCallWaiting({
+ enabled: true
+ });
+
+ let postedMessage = workerHelper.postedMessage;
+
+ equal(postedMessage.errorMsg, GECKO_ERROR_GENERIC_FAILURE);
+
+ run_next_test();
+});
+
+add_test(function test_queryCallWaiting_success_enabled_true() {
+ let workerHelper = newInterceptWorker();
+ let worker = workerHelper.worker;
+ let context = worker.ContextPool._contexts[0];
+
+ context.Buf.readInt32 = function fakeReadUint32() {
+ return context.Buf.int32Array.pop();
+ };
+
+ context.RIL.queryCallWaiting = function fakeQueryCallWaiting(options) {
+ context.Buf.int32Array = [
+ 1, // serviceClass
+ 1, // enabled
+ 2 // length
+ ];
+ context.RIL[REQUEST_QUERY_CALL_WAITING](1, {});
+ };
+
+ context.RIL.queryCallWaiting({});
+
+ let postedMessage = workerHelper.postedMessage;
+
+ equal(postedMessage.errorMsg, undefined);
+ equal(postedMessage.serviceClass, 1);
+ run_next_test();
+});
+
+add_test(function test_queryCallWaiting_success_enabled_false() {
+ let workerHelper = newInterceptWorker();
+ let worker = workerHelper.worker;
+ let context = worker.ContextPool._contexts[0];
+
+ context.Buf.readInt32 = function fakeReadUint32() {
+ return context.Buf.int32Array.pop();
+ };
+
+ context.RIL.queryCallWaiting = function fakeQueryCallWaiting(options) {
+ context.Buf.int32Array = [
+ 1, // serviceClass
+ 0, // enabled
+ 2 // length
+ ];
+ context.RIL[REQUEST_QUERY_CALL_WAITING](1, {});
+ };
+
+ context.RIL.queryCallWaiting({});
+
+ let postedMessage = workerHelper.postedMessage;
+
+ equal(postedMessage.errorMsg, undefined);
+ equal(postedMessage.serviceClass, ICC_SERVICE_CLASS_NONE);
+ run_next_test();
+});
diff --git a/dom/system/gonk/tests/test_ril_worker_ecm.js b/dom/system/gonk/tests/test_ril_worker_ecm.js
new file mode 100644
index 000000000..d10cba9ec
--- /dev/null
+++ b/dom/system/gonk/tests/test_ril_worker_ecm.js
@@ -0,0 +1,168 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+subscriptLoader.loadSubScript("resource://gre/modules/ril_consts.js", this);
+
+function run_test() {
+ run_next_test();
+}
+
+var timeoutCallback = null;
+var timeoutDelayMs = 0;
+const TIMER_ID = 1234;
+const TIMEOUT_VALUE = 300000; // 5 mins.
+
+// No window in xpcshell-test. Create our own timer mechanism.
+
+function setTimeout(callback, timeoutMs) {
+ timeoutCallback = callback;
+ timeoutDelayMs = timeoutMs;
+ equal(timeoutMs, TIMEOUT_VALUE);
+ return TIMER_ID;
+}
+
+function clearTimeout(timeoutId) {
+ equal(timeoutId, TIMER_ID);
+ timeoutCallback = null;
+}
+
+function fireTimeout() {
+ notEqual(timeoutCallback, null);
+ if (timeoutCallback) {
+ timeoutCallback();
+ timeoutCallback = null;
+ }
+}
+
+add_test(function test_enter_emergencyCbMode() {
+ let workerHelper = newInterceptWorker();
+ let worker = workerHelper.worker;
+ let context = worker.ContextPool._contexts[0];
+
+ // Do it twice. Should always send the event.
+ for (let i = 0; i < 2; ++i) {
+ context.RIL[UNSOLICITED_ENTER_EMERGENCY_CALLBACK_MODE]();
+ let postedMessage = workerHelper.postedMessage;
+
+ // Should store the mode.
+ equal(context.RIL._isInEmergencyCbMode, true);
+
+ // Should notify change.
+ equal(postedMessage.rilMessageType, "emergencyCbModeChange");
+ equal(postedMessage.active, true);
+ equal(postedMessage.timeoutMs, TIMEOUT_VALUE);
+
+ // Should start timer.
+ equal(context.RIL._exitEmergencyCbModeTimeoutID, TIMER_ID);
+ }
+
+ run_next_test();
+});
+
+add_test(function test_exit_emergencyCbMode() {
+ let workerHelper = newInterceptWorker();
+ let worker = workerHelper.worker;
+ let context = worker.ContextPool._contexts[0];
+
+ context.RIL[UNSOLICITED_ENTER_EMERGENCY_CALLBACK_MODE]();
+ context.RIL[UNSOLICITED_EXIT_EMERGENCY_CALLBACK_MODE]();
+ let postedMessage = workerHelper.postedMessage;
+
+ // Should store the mode.
+ equal(context.RIL._isInEmergencyCbMode, false);
+
+ // Should notify change.
+ equal(postedMessage.rilMessageType, "emergencyCbModeChange");
+ equal(postedMessage.active, false);
+
+ // Should clear timer.
+ equal(context.RIL._exitEmergencyCbModeTimeoutID, null);
+
+ run_next_test();
+});
+
+add_test(function test_request_exit_emergencyCbMode_when_timeout() {
+ let workerHelper = newInterceptWorker();
+ let worker = workerHelper.worker;
+ let context = worker.ContextPool._contexts[0];
+
+ context.RIL[UNSOLICITED_ENTER_EMERGENCY_CALLBACK_MODE]();
+ equal(context.RIL._isInEmergencyCbMode, true);
+ equal(context.RIL._exitEmergencyCbModeTimeoutID, TIMER_ID);
+
+ let parcelTypes = [];
+ context.Buf.newParcel = function(type, options) {
+ parcelTypes.push(type);
+ };
+
+ // Timeout.
+ fireTimeout();
+
+ // Should clear timeout event.
+ equal(context.RIL._exitEmergencyCbModeTimeoutID, null);
+
+ // Check indeed sent out REQUEST_EXIT_EMERGENCY_CALLBACK_MODE.
+ notEqual(parcelTypes.indexOf(REQUEST_EXIT_EMERGENCY_CALLBACK_MODE), -1);
+
+ run_next_test();
+});
+
+add_test(function test_request_exit_emergencyCbMode_when_dial() {
+ let workerHelper = newInterceptWorker();
+ let worker = workerHelper.worker;
+ let context = worker.ContextPool._contexts[0];
+
+ context.RIL[UNSOLICITED_ENTER_EMERGENCY_CALLBACK_MODE]();
+ equal(context.RIL._isInEmergencyCbMode, true);
+ equal(context.RIL._exitEmergencyCbModeTimeoutID, TIMER_ID);
+
+ let parcelTypes = [];
+ context.Buf.newParcel = function(type, options) {
+ parcelTypes.push(type);
+ };
+
+ // Dial non-emergency call.
+ context.RIL.dial({number: "0912345678",
+ isEmergency: false,
+ isDialEmergency: false});
+
+ // Should clear timeout event.
+ equal(context.RIL._exitEmergencyCbModeTimeoutID, null);
+
+ // Check indeed sent out REQUEST_EXIT_EMERGENCY_CALLBACK_MODE.
+ notEqual(parcelTypes.indexOf(REQUEST_EXIT_EMERGENCY_CALLBACK_MODE), -1);
+
+ run_next_test();
+});
+
+add_test(function test_request_exit_emergencyCbMode_explicitly() {
+ let workerHelper = newInterceptWorker();
+ let worker = workerHelper.worker;
+ let context = worker.ContextPool._contexts[0];
+
+ context.RIL[UNSOLICITED_ENTER_EMERGENCY_CALLBACK_MODE]();
+ equal(context.RIL._isInEmergencyCbMode, true);
+ equal(context.RIL._exitEmergencyCbModeTimeoutID, TIMER_ID);
+
+ let parcelTypes = [];
+ context.Buf.newParcel = function(type, options) {
+ parcelTypes.push(type);
+ };
+
+ context.RIL.handleChromeMessage({rilMessageType: "exitEmergencyCbMode"});
+ context.RIL[REQUEST_EXIT_EMERGENCY_CALLBACK_MODE](1, {
+ rilMessageType: "exitEmergencyCbMode"
+ });
+ let postedMessage = workerHelper.postedMessage;
+
+ // Should clear timeout event.
+ equal(context.RIL._exitEmergencyCbModeTimeoutID, null);
+
+ // Check indeed sent out REQUEST_EXIT_EMERGENCY_CALLBACK_MODE.
+ notEqual(parcelTypes.indexOf(REQUEST_EXIT_EMERGENCY_CALLBACK_MODE), -1);
+
+ // Send back the response.
+ equal(postedMessage.rilMessageType, "exitEmergencyCbMode");
+
+ run_next_test();
+});
diff --git a/dom/system/gonk/tests/test_ril_worker_icc_BerTlvHelper.js b/dom/system/gonk/tests/test_ril_worker_icc_BerTlvHelper.js
new file mode 100644
index 000000000..89fcd874d
--- /dev/null
+++ b/dom/system/gonk/tests/test_ril_worker_icc_BerTlvHelper.js
@@ -0,0 +1,87 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+subscriptLoader.loadSubScript("resource://gre/modules/ril_consts.js", this);
+
+function run_test() {
+ run_next_test();
+}
+
+// Test ICC_COMMAND_GET_RESPONSE with FCP template format.
+/**
+ * Verify transparent structure with FCP template format.
+ */
+add_test(function test_fcp_template_for_transparent_structure() {
+ let worker = newUint8Worker();
+ let context = worker.ContextPool._contexts[0];
+ let pduHelper = context.GsmPDUHelper;
+ let berHelper = context.BerTlvHelper;
+
+ let tag_test = [
+ 0x62,
+ 0x22,
+ 0x82, 0x02, 0x41, 0x21,
+ 0x83, 0x02, 0x2F, 0xE2,
+ 0xA5, 0x09, 0xC1, 0x04, 0x40, 0x0F, 0xF5, 0x55, 0x92, 0x01, 0x00,
+ 0x8A, 0x01, 0x05,
+ 0x8B, 0x03, 0x2F, 0x06, 0x0B,
+ 0x80, 0x02, 0x00, 0x0A,
+ 0x88, 0x01, 0x10];
+
+ for (let i = 0; i < tag_test.length; i++) {
+ pduHelper.writeHexOctet(tag_test[i]);
+ }
+
+ let berTlv = berHelper.decode(tag_test.length);
+ let iter = berTlv.value.values();
+ let tlv = berHelper.searchForNextTag(BER_FCP_FILE_DESCRIPTOR_TAG, iter);
+ equal(tlv.value.fileStructure, UICC_EF_STRUCTURE[EF_STRUCTURE_TRANSPARENT]);
+
+ tlv = berHelper.searchForNextTag(BER_FCP_FILE_IDENTIFIER_TAG, iter);
+ equal(tlv.value.fileId, 0x2FE2);
+
+ tlv = berHelper.searchForNextTag(BER_FCP_FILE_SIZE_DATA_TAG, iter);
+ equal(tlv.value.fileSizeData, 0x0A);
+
+ run_next_test();
+});
+
+/**
+ * Verify linear fixed structure with FCP template format.
+ */
+add_test(function test_fcp_template_for_linear_fixed_structure() {
+ let worker = newUint8Worker();
+ let context = worker.ContextPool._contexts[0];
+ let pduHelper = context.GsmPDUHelper;
+ let berHelper = context.BerTlvHelper;
+
+ let tag_test = [
+ 0x62,
+ 0x1E,
+ 0x82, 0x05, 0x42, 0x21, 0x00, 0x1A, 0x01,
+ 0x83, 0x02, 0x6F, 0x40,
+ 0xA5, 0x03, 0x92, 0x01, 0x00,
+ 0x8A, 0x01, 0x07,
+ 0x8B, 0x03, 0x6F, 0x06, 0x02,
+ 0x80, 0x02, 0x00, 0x1A,
+ 0x88, 0x00];
+
+ for (let i = 0; i < tag_test.length; i++) {
+ pduHelper.writeHexOctet(tag_test[i]);
+ }
+
+ let berTlv = berHelper.decode(tag_test.length);
+ let iter = berTlv.value.values();
+ let tlv = berHelper.searchForNextTag(BER_FCP_FILE_DESCRIPTOR_TAG, iter);
+ equal(tlv.value.fileStructure, UICC_EF_STRUCTURE[EF_STRUCTURE_LINEAR_FIXED]);
+ equal(tlv.value.recordLength, 0x1A);
+ equal(tlv.value.numOfRecords, 0x01);
+
+ tlv = berHelper.searchForNextTag(BER_FCP_FILE_IDENTIFIER_TAG, iter);
+ equal(tlv.value.fileId, 0x6F40);
+
+ tlv = berHelper.searchForNextTag(BER_FCP_FILE_SIZE_DATA_TAG, iter);
+ equal(tlv.value.fileSizeData, 0x1A);
+
+ run_next_test();
+});
diff --git a/dom/system/gonk/tests/test_ril_worker_icc_CardLock.js b/dom/system/gonk/tests/test_ril_worker_icc_CardLock.js
new file mode 100644
index 000000000..dc7eb93b9
--- /dev/null
+++ b/dom/system/gonk/tests/test_ril_worker_icc_CardLock.js
@@ -0,0 +1,282 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+subscriptLoader.loadSubScript("resource://gre/modules/ril_consts.js", this);
+
+function run_test() {
+ run_next_test();
+}
+
+/**
+ * Verify RIL.iccGetCardLockEnabled
+ */
+add_test(function test_icc_get_card_lock_enabled() {
+ let worker = newUint8Worker();
+ let context = worker.ContextPool._contexts[0];
+ let buf = context.Buf;
+ let ril = context.RIL;
+ ril.aid = "123456789";
+
+ function do_test(aLock) {
+ const serviceClass = ICC_SERVICE_CLASS_VOICE |
+ ICC_SERVICE_CLASS_DATA |
+ ICC_SERVICE_CLASS_FAX;
+
+ buf.sendParcel = function fakeSendParcel() {
+ // Request Type.
+ equal(this.readInt32(), REQUEST_QUERY_FACILITY_LOCK)
+
+ // Token : we don't care.
+ this.readInt32();
+
+ // Data
+ let parcel = this.readStringList();
+ equal(parcel.length, 4);
+ equal(parcel[0], GECKO_CARDLOCK_TO_FACILITY[aLock]);
+ equal(parcel[1], "");
+ equal(parcel[2], serviceClass.toString());
+ equal(parcel[3], ril.aid);
+ };
+
+ ril.iccGetCardLockEnabled({lockType: aLock});
+ }
+
+ do_test(GECKO_CARDLOCK_PIN)
+ do_test(GECKO_CARDLOCK_FDN)
+
+ run_next_test();
+});
+
+add_test(function test_path_id_for_spid_and_spn() {
+ let worker = newWorker({
+ postRILMessage: function(data) {
+ // Do nothing
+ },
+ postMessage: function(message) {
+ // Do nothing
+ }});
+ let context = worker.ContextPool._contexts[0];
+ let RIL = context.RIL;
+ let ICCFileHelper = context.ICCFileHelper;
+
+ // Test SIM
+ RIL.appType = CARD_APPTYPE_SIM;
+ equal(ICCFileHelper.getEFPath(ICC_EF_SPDI),
+ EF_PATH_MF_SIM + EF_PATH_DF_GSM);
+ equal(ICCFileHelper.getEFPath(ICC_EF_SPN),
+ EF_PATH_MF_SIM + EF_PATH_DF_GSM);
+
+ // Test USIM
+ RIL.appType = CARD_APPTYPE_USIM;
+ equal(ICCFileHelper.getEFPath(ICC_EF_SPDI),
+ EF_PATH_MF_SIM + EF_PATH_ADF_USIM);
+ equal(ICCFileHelper.getEFPath(ICC_EF_SPDI),
+ EF_PATH_MF_SIM + EF_PATH_ADF_USIM);
+ run_next_test();
+});
+
+/**
+ * Verify RIL.iccSetCardLockEnabled
+ */
+add_test(function test_icc_set_card_lock_enabled() {
+ let worker = newUint8Worker();
+ let context = worker.ContextPool._contexts[0];
+ let buf = context.Buf;
+ let ril = context.RIL;
+ ril.aid = "123456789";
+
+ function do_test(aLock, aPassword, aEnabled) {
+ const serviceClass = ICC_SERVICE_CLASS_VOICE |
+ ICC_SERVICE_CLASS_DATA |
+ ICC_SERVICE_CLASS_FAX;
+
+ buf.sendParcel = function fakeSendParcel() {
+ // Request Type.
+ equal(this.readInt32(), REQUEST_SET_FACILITY_LOCK);
+
+ // Token : we don't care
+ this.readInt32();
+
+ // Data
+ let parcel = this.readStringList();
+ equal(parcel.length, 5);
+ equal(parcel[0], GECKO_CARDLOCK_TO_FACILITY[aLock]);
+ equal(parcel[1], aEnabled ? "1" : "0");
+ equal(parcel[2], aPassword);
+ equal(parcel[3], serviceClass.toString());
+ equal(parcel[4], ril.aid);
+ };
+
+ ril.iccSetCardLockEnabled({
+ lockType: aLock,
+ enabled: aEnabled,
+ password: aPassword});
+ }
+
+ do_test(GECKO_CARDLOCK_PIN, "1234", true);
+ do_test(GECKO_CARDLOCK_PIN, "1234", false);
+ do_test(GECKO_CARDLOCK_FDN, "4321", true);
+ do_test(GECKO_CARDLOCK_FDN, "4321", false);
+
+ run_next_test();
+});
+
+/**
+ * Verify RIL.iccChangeCardLockPassword
+ */
+add_test(function test_icc_change_card_lock_password() {
+ let worker = newUint8Worker();
+ let context = worker.ContextPool._contexts[0];
+ let buf = context.Buf;
+ let ril = context.RIL;
+
+
+ function do_test(aLock, aPassword, aNewPassword) {
+ let GECKO_CARDLOCK_TO_REQUEST = {};
+ GECKO_CARDLOCK_TO_REQUEST[GECKO_CARDLOCK_PIN] = REQUEST_CHANGE_SIM_PIN;
+ GECKO_CARDLOCK_TO_REQUEST[GECKO_CARDLOCK_PIN2] = REQUEST_CHANGE_SIM_PIN2;
+
+ buf.sendParcel = function fakeSendParcel() {
+ // Request Type.
+ equal(this.readInt32(), GECKO_CARDLOCK_TO_REQUEST[aLock]);
+
+ // Token : we don't care
+ this.readInt32();
+
+ // Data
+ let parcel = this.readStringList();
+ equal(parcel.length, 3);
+ equal(parcel[0], aPassword);
+ equal(parcel[1], aNewPassword);
+ equal(parcel[2], ril.aid);
+ };
+
+ ril.iccChangeCardLockPassword({
+ lockType: aLock,
+ password: aPassword,
+ newPassword: aNewPassword});
+ }
+
+ do_test(GECKO_CARDLOCK_PIN, "1234", "4321");
+ do_test(GECKO_CARDLOCK_PIN2, "1234", "4321");
+
+ run_next_test();
+});
+
+/**
+ * Verify RIL.iccUnlockCardLock - PIN
+ */
+add_test(function test_icc_unlock_card_lock_pin() {
+ let worker = newUint8Worker();
+ let context = worker.ContextPool._contexts[0];
+ let ril = context.RIL;
+ let buf = context.Buf;
+ ril.aid = "123456789";
+
+ function do_test(aLock, aPassword) {
+ let GECKO_CARDLOCK_TO_REQUEST = {};
+ GECKO_CARDLOCK_TO_REQUEST[GECKO_CARDLOCK_PIN] = REQUEST_ENTER_SIM_PIN;
+ GECKO_CARDLOCK_TO_REQUEST[GECKO_CARDLOCK_PIN2] = REQUEST_ENTER_SIM_PIN2;
+
+ buf.sendParcel = function fakeSendParcel() {
+ // Request Type.
+ equal(this.readInt32(), GECKO_CARDLOCK_TO_REQUEST[aLock]);
+
+ // Token : we don't care
+ this.readInt32();
+
+ // Data
+ let parcel = this.readStringList();
+ equal(parcel.length, 2);
+ equal(parcel[0], aPassword);
+ equal(parcel[1], ril.aid);
+ };
+
+ ril.iccUnlockCardLock({
+ lockType: aLock,
+ password: aPassword
+ });
+ }
+
+ do_test(GECKO_CARDLOCK_PIN, "1234");
+ do_test(GECKO_CARDLOCK_PIN2, "1234");
+
+ run_next_test();
+});
+
+/**
+ * Verify RIL.iccUnlockCardLock - PUK
+ */
+add_test(function test_icc_unlock_card_lock_puk() {
+ let worker = newUint8Worker();
+ let context = worker.ContextPool._contexts[0];
+ let ril = context.RIL;
+ let buf = context.Buf;
+ ril.aid = "123456789";
+
+ function do_test(aLock, aPassword, aNewPin) {
+ let GECKO_CARDLOCK_TO_REQUEST = {};
+ GECKO_CARDLOCK_TO_REQUEST[GECKO_CARDLOCK_PUK] = REQUEST_ENTER_SIM_PUK;
+ GECKO_CARDLOCK_TO_REQUEST[GECKO_CARDLOCK_PUK2] = REQUEST_ENTER_SIM_PUK2;
+
+ buf.sendParcel = function fakeSendParcel() {
+ // Request Type.
+ equal(this.readInt32(), GECKO_CARDLOCK_TO_REQUEST[aLock]);
+
+ // Token : we don't care
+ this.readInt32();
+
+ // Data
+ let parcel = this.readStringList();
+ equal(parcel.length, 3);
+ equal(parcel[0], aPassword);
+ equal(parcel[1], aNewPin);
+ equal(parcel[2], ril.aid);
+ };
+
+ ril.iccUnlockCardLock({
+ lockType: aLock,
+ password: aPassword,
+ newPin: aNewPin
+ });
+ }
+
+ do_test(GECKO_CARDLOCK_PUK, "12345678", "1234");
+ do_test(GECKO_CARDLOCK_PUK2, "12345678", "1234");
+
+ run_next_test();
+});
+
+/**
+ * Verify RIL.iccUnlockCardLock - Depersonalization
+ */
+add_test(function test_icc_unlock_card_lock_depersonalization() {
+ let worker = newUint8Worker();
+ let context = worker.ContextPool._contexts[0];
+ let ril = context.RIL;
+ let buf = context.Buf;
+
+ function do_test(aPassword) {
+ buf.sendParcel = function fakeSendParcel() {
+ // Request Type.
+ equal(this.readInt32(), REQUEST_ENTER_NETWORK_DEPERSONALIZATION_CODE);
+
+ // Token : we don't care
+ this.readInt32();
+
+ // Data
+ let parcel = this.readStringList();
+ equal(parcel.length, 1);
+ equal(parcel[0], aPassword);
+ };
+
+ ril.iccUnlockCardLock({
+ lockType: GECKO_CARDLOCK_NCK,
+ password: aPassword
+ });
+ }
+
+ do_test("12345678");
+
+ run_next_test();
+});
diff --git a/dom/system/gonk/tests/test_ril_worker_icc_CardState.js b/dom/system/gonk/tests/test_ril_worker_icc_CardState.js
new file mode 100644
index 000000000..788df5073
--- /dev/null
+++ b/dom/system/gonk/tests/test_ril_worker_icc_CardState.js
@@ -0,0 +1,210 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+subscriptLoader.loadSubScript("resource://gre/modules/ril_consts.js", this);
+
+function run_test() {
+ run_next_test();
+}
+
+add_test(function test_personalization_state() {
+ let worker = newUint8Worker();
+ let context = worker.ContextPool._contexts[0];
+ let ril = context.RIL;
+
+ context.ICCRecordHelper.readICCID = function fakeReadICCID() {};
+
+ function testPersonalization(isCdma, cardPersoState, geckoCardState) {
+ let iccStatus = {
+ cardState: CARD_STATE_PRESENT,
+ gsmUmtsSubscriptionAppIndex: (!isCdma) ? 0 : -1,
+ cdmaSubscriptionAppIndex: (isCdma) ? 0 : -1,
+ apps: [
+ {
+ app_state: CARD_APPSTATE_SUBSCRIPTION_PERSO,
+ perso_substate: cardPersoState
+ }],
+ };
+
+ ril._isCdma = isCdma;
+ ril._processICCStatus(iccStatus);
+ equal(ril.cardState, geckoCardState);
+ }
+
+ // Test GSM personalization state.
+ testPersonalization(false, CARD_PERSOSUBSTATE_SIM_NETWORK,
+ Ci.nsIIcc.CARD_STATE_NETWORK_LOCKED);
+ testPersonalization(false, CARD_PERSOSUBSTATE_SIM_NETWORK_SUBSET,
+ Ci.nsIIcc.CARD_STATE_NETWORK_SUBSET_LOCKED);
+ testPersonalization(false, CARD_PERSOSUBSTATE_SIM_CORPORATE,
+ Ci.nsIIcc.CARD_STATE_CORPORATE_LOCKED);
+ testPersonalization(false, CARD_PERSOSUBSTATE_SIM_SERVICE_PROVIDER,
+ Ci.nsIIcc.CARD_STATE_SERVICE_PROVIDER_LOCKED);
+ testPersonalization(false, CARD_PERSOSUBSTATE_SIM_SIM,
+ Ci.nsIIcc.CARD_STATE_SIM_LOCKED);
+ testPersonalization(false, CARD_PERSOSUBSTATE_SIM_NETWORK_PUK,
+ Ci.nsIIcc.CARD_STATE_NETWORK_PUK_REQUIRED);
+ testPersonalization(false, CARD_PERSOSUBSTATE_SIM_NETWORK_SUBSET_PUK,
+ Ci.nsIIcc.CARD_STATE_NETWORK_SUBSET_PUK_REQUIRED);
+ testPersonalization(false, CARD_PERSOSUBSTATE_SIM_CORPORATE_PUK,
+ Ci.nsIIcc.CARD_STATE_CORPORATE_PUK_REQUIRED);
+ testPersonalization(false, CARD_PERSOSUBSTATE_SIM_SERVICE_PROVIDER_PUK,
+ Ci.nsIIcc.CARD_STATE_SERVICE_PROVIDER_PUK_REQUIRED);
+ testPersonalization(false, CARD_PERSOSUBSTATE_SIM_SIM_PUK,
+ Ci.nsIIcc.CARD_STATE_SIM_PUK_REQUIRED);
+
+ testPersonalization(false, CARD_PERSOSUBSTATE_UNKNOWN,
+ Ci.nsIIcc.CARD_STATE_UNKNOWN);
+ testPersonalization(false, CARD_PERSOSUBSTATE_IN_PROGRESS,
+ Ci.nsIIcc.CARD_STATE_PERSONALIZATION_IN_PROGRESS);
+ testPersonalization(false, CARD_PERSOSUBSTATE_READY,
+ Ci.nsIIcc.CARD_STATE_PERSONALIZATION_READY);
+
+ // Test CDMA personalization state.
+ testPersonalization(true, CARD_PERSOSUBSTATE_RUIM_NETWORK1,
+ Ci.nsIIcc.CARD_STATE_NETWORK1_LOCKED);
+ testPersonalization(true, CARD_PERSOSUBSTATE_RUIM_NETWORK2,
+ Ci.nsIIcc.CARD_STATE_NETWORK2_LOCKED);
+ testPersonalization(true, CARD_PERSOSUBSTATE_RUIM_HRPD,
+ Ci.nsIIcc.CARD_STATE_HRPD_NETWORK_LOCKED);
+ testPersonalization(true, CARD_PERSOSUBSTATE_RUIM_CORPORATE,
+ Ci.nsIIcc.CARD_STATE_RUIM_CORPORATE_LOCKED);
+ testPersonalization(true, CARD_PERSOSUBSTATE_RUIM_SERVICE_PROVIDER,
+ Ci.nsIIcc.CARD_STATE_RUIM_SERVICE_PROVIDER_LOCKED);
+ testPersonalization(true, CARD_PERSOSUBSTATE_RUIM_RUIM,
+ Ci.nsIIcc.CARD_STATE_RUIM_LOCKED);
+ testPersonalization(true, CARD_PERSOSUBSTATE_RUIM_NETWORK1_PUK,
+ Ci.nsIIcc.CARD_STATE_NETWORK1_PUK_REQUIRED);
+ testPersonalization(true, CARD_PERSOSUBSTATE_RUIM_NETWORK2_PUK,
+ Ci.nsIIcc.CARD_STATE_NETWORK2_PUK_REQUIRED);
+ testPersonalization(true, CARD_PERSOSUBSTATE_RUIM_HRPD_PUK,
+ Ci.nsIIcc.CARD_STATE_HRPD_NETWORK_PUK_REQUIRED);
+ testPersonalization(true, CARD_PERSOSUBSTATE_RUIM_CORPORATE_PUK,
+ Ci.nsIIcc.CARD_STATE_RUIM_CORPORATE_PUK_REQUIRED);
+ testPersonalization(true, CARD_PERSOSUBSTATE_RUIM_SERVICE_PROVIDER_PUK,
+ Ci.nsIIcc.CARD_STATE_RUIM_SERVICE_PROVIDER_PUK_REQUIRED);
+ testPersonalization(true, CARD_PERSOSUBSTATE_RUIM_RUIM_PUK,
+ Ci.nsIIcc.CARD_STATE_RUIM_PUK_REQUIRED);
+
+ testPersonalization(true, CARD_PERSOSUBSTATE_UNKNOWN,
+ Ci.nsIIcc.CARD_STATE_UNKNOWN);
+ testPersonalization(true, CARD_PERSOSUBSTATE_IN_PROGRESS,
+ Ci.nsIIcc.CARD_STATE_PERSONALIZATION_IN_PROGRESS);
+ testPersonalization(true, CARD_PERSOSUBSTATE_READY,
+ Ci.nsIIcc.CARD_STATE_PERSONALIZATION_READY);
+
+ run_next_test();
+});
+
+/**
+ * Verify SIM app_state in _processICCStatus
+ */
+add_test(function test_card_app_state() {
+ let worker = newUint8Worker();
+ let context = worker.ContextPool._contexts[0];
+ let ril = context.RIL;
+
+ context.ICCRecordHelper.readICCID = function fakeReadICCID() {};
+
+ function testCardAppState(cardAppState, geckoCardState) {
+ let iccStatus = {
+ cardState: CARD_STATE_PRESENT,
+ gsmUmtsSubscriptionAppIndex: 0,
+ apps: [
+ {
+ app_state: cardAppState
+ }],
+ };
+
+ ril._processICCStatus(iccStatus);
+ equal(ril.cardState, geckoCardState);
+ }
+
+ testCardAppState(CARD_APPSTATE_ILLEGAL,
+ Ci.nsIIcc.CARD_STATE_ILLEGAL);
+ testCardAppState(CARD_APPSTATE_PIN,
+ Ci.nsIIcc.CARD_STATE_PIN_REQUIRED);
+ testCardAppState(CARD_APPSTATE_PUK,
+ Ci.nsIIcc.CARD_STATE_PUK_REQUIRED);
+ testCardAppState(CARD_APPSTATE_READY,
+ Ci.nsIIcc.CARD_STATE_READY);
+ testCardAppState(CARD_APPSTATE_UNKNOWN,
+ Ci.nsIIcc.CARD_STATE_UNKNOWN);
+ testCardAppState(CARD_APPSTATE_DETECTED,
+ Ci.nsIIcc.CARD_STATE_UNKNOWN);
+
+ run_next_test();
+});
+
+/**
+ * Verify permanent blocked for ICC.
+ */
+add_test(function test_icc_permanent_blocked() {
+ let worker = newUint8Worker();
+ let context = worker.ContextPool._contexts[0];
+ let ril = context.RIL;
+
+ context.ICCRecordHelper.readICCID = function fakeReadICCID() {};
+
+ function testPermanentBlocked(pin1_replaced, universalPINState, pin1) {
+ let iccStatus = {
+ cardState: CARD_STATE_PRESENT,
+ gsmUmtsSubscriptionAppIndex: 0,
+ universalPINState: universalPINState,
+ apps: [
+ {
+ pin1_replaced: pin1_replaced,
+ pin1: pin1
+ }]
+ };
+
+ ril._processICCStatus(iccStatus);
+ equal(ril.cardState, Ci.nsIIcc.CARD_STATE_PERMANENT_BLOCKED);
+ }
+
+ testPermanentBlocked(1,
+ CARD_PINSTATE_ENABLED_PERM_BLOCKED,
+ CARD_PINSTATE_UNKNOWN);
+ testPermanentBlocked(1,
+ CARD_PINSTATE_ENABLED_PERM_BLOCKED,
+ CARD_PINSTATE_ENABLED_PERM_BLOCKED);
+ testPermanentBlocked(0,
+ CARD_PINSTATE_UNKNOWN,
+ CARD_PINSTATE_ENABLED_PERM_BLOCKED);
+
+ run_next_test();
+});
+
+/**
+ * Verify ICC without app index.
+ */
+add_test(function test_icc_without_app_index() {
+ const ICCID = "123456789";
+
+ let worker = newUint8Worker();
+ let context = worker.ContextPool._contexts[0];
+ let ril = context.RIL;
+
+ let iccStatus = {
+ cardState: CARD_STATE_PRESENT,
+ gsmUmtsSubscriptionAppIndex: -1,
+ universalPINState: CARD_PINSTATE_DISABLED,
+ apps: [
+ {
+ app_state: CARD_APPSTATE_READY
+ }]
+ };
+
+ context.ICCRecordHelper.readICCID = function fakeReadICCID() {
+ ril.iccInfo.iccid = ICCID;
+ };
+
+ ril._processICCStatus(iccStatus);
+
+ // Should read icc id event if the app index is -1.
+ equal(ril.iccInfo.iccid, ICCID);
+ // cardState is "unknown" if the app index is -1.
+ equal(ril.cardState, GECKO_CARDSTATE_UNKNOWN);
+
+ run_next_test();
+});
diff --git a/dom/system/gonk/tests/test_ril_worker_icc_GsmPDUHelper.js b/dom/system/gonk/tests/test_ril_worker_icc_GsmPDUHelper.js
new file mode 100644
index 000000000..0d074da79
--- /dev/null
+++ b/dom/system/gonk/tests/test_ril_worker_icc_GsmPDUHelper.js
@@ -0,0 +1,79 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+subscriptLoader.loadSubScript("resource://gre/modules/ril_consts.js", this);
+
+function run_test() {
+ run_next_test();
+}
+
+/**
+ * Verify GsmPDUHelper.writeTimestamp
+ */
+add_test(function test_write_timestamp() {
+ let worker = newUint8Worker();
+ let context = worker.ContextPool._contexts[0];
+ let helper = context.GsmPDUHelper;
+
+ // current date
+ let dateInput = new Date();
+ let dateOutput = new Date();
+ helper.writeTimestamp(dateInput);
+ dateOutput.setTime(helper.readTimestamp());
+
+ equal(dateInput.getFullYear(), dateOutput.getFullYear());
+ equal(dateInput.getMonth(), dateOutput.getMonth());
+ equal(dateInput.getDate(), dateOutput.getDate());
+ equal(dateInput.getHours(), dateOutput.getHours());
+ equal(dateInput.getMinutes(), dateOutput.getMinutes());
+ equal(dateInput.getSeconds(), dateOutput.getSeconds());
+ equal(dateInput.getTimezoneOffset(), dateOutput.getTimezoneOffset());
+
+ // 2034-01-23 12:34:56 -0800 GMT
+ let time = Date.UTC(2034, 1, 23, 12, 34, 56);
+ time = time - (8 * 60 * 60 * 1000);
+ dateInput.setTime(time);
+ helper.writeTimestamp(dateInput);
+ dateOutput.setTime(helper.readTimestamp());
+
+ equal(dateInput.getFullYear(), dateOutput.getFullYear());
+ equal(dateInput.getMonth(), dateOutput.getMonth());
+ equal(dateInput.getDate(), dateOutput.getDate());
+ equal(dateInput.getHours(), dateOutput.getHours());
+ equal(dateInput.getMinutes(), dateOutput.getMinutes());
+ equal(dateInput.getSeconds(), dateOutput.getSeconds());
+ equal(dateInput.getTimezoneOffset(), dateOutput.getTimezoneOffset());
+
+ run_next_test();
+});
+
+/**
+ * Verify GsmPDUHelper.octectToBCD and GsmPDUHelper.BCDToOctet
+ */
+add_test(function test_octect_BCD() {
+ let worker = newUint8Worker();
+ let context = worker.ContextPool._contexts[0];
+ let helper = context.GsmPDUHelper;
+
+ // 23
+ let number = 23;
+ let octet = helper.BCDToOctet(number);
+ equal(helper.octetToBCD(octet), number);
+
+ // 56
+ number = 56;
+ octet = helper.BCDToOctet(number);
+ equal(helper.octetToBCD(octet), number);
+
+ // 0x23
+ octet = 0x23;
+ number = helper.octetToBCD(octet);
+ equal(helper.BCDToOctet(number), octet);
+
+ // 0x56
+ octet = 0x56;
+ number = helper.octetToBCD(octet);
+ equal(helper.BCDToOctet(number), octet);
+
+ run_next_test();
+});
diff --git a/dom/system/gonk/tests/test_ril_worker_icc_ICCContactHelper.js b/dom/system/gonk/tests/test_ril_worker_icc_ICCContactHelper.js
new file mode 100644
index 000000000..29b83b76a
--- /dev/null
+++ b/dom/system/gonk/tests/test_ril_worker_icc_ICCContactHelper.js
@@ -0,0 +1,1042 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+subscriptLoader.loadSubScript("resource://gre/modules/ril_consts.js", this);
+
+function run_test() {
+ run_next_test();
+}
+
+/**
+ * Test error message returned in onerror for readICCContacts.
+ */
+add_test(function test_error_message_read_icc_contact () {
+ let worker = newUint8Worker();
+ let context = worker.ContextPool._contexts[0];
+ let ril = context.RIL;
+
+ function do_test(options, expectedErrorMsg) {
+ ril.sendChromeMessage = function(message) {
+ equal(message.errorMsg, expectedErrorMsg);
+ }
+ ril.readICCContacts(options);
+ }
+
+ // Error 1, didn't specify correct contactType.
+ do_test({}, CONTACT_ERR_REQUEST_NOT_SUPPORTED);
+
+ // Error 2, specifying a non-supported contactType.
+ ril.appType = CARD_APPTYPE_USIM;
+ do_test({contactType: "foo"}, CONTACT_ERR_CONTACT_TYPE_NOT_SUPPORTED);
+
+ // Error 3, suppose we update the supported PBR fields in USIM_PBR_FIELDS,
+ // but forget to add implemenetations for it.
+ USIM_PBR_FIELDS.push("pbc");
+ do_test({contactType: GECKO_CARDCONTACT_TYPE_ADN},
+ CONTACT_ERR_FIELD_NOT_SUPPORTED);
+
+ run_next_test();
+});
+
+/**
+ * Test error message returned in onerror for updateICCContact.
+ */
+add_test(function test_error_message_update_icc_contact() {
+ let worker = newUint8Worker();
+ let context = worker.ContextPool._contexts[0];
+ let ril = context.RIL;
+
+ const ICCID = "123456789";
+ ril.iccInfo.iccid = ICCID;
+
+ function do_test(options, expectedErrorMsg) {
+ ril.sendChromeMessage = function(message) {
+ equal(message.errorMsg, expectedErrorMsg);
+ }
+ ril.updateICCContact(options);
+ }
+
+ // Error 1, didn't specify correct contactType.
+ do_test({}, CONTACT_ERR_REQUEST_NOT_SUPPORTED);
+
+ // Error 2, specifying a correct contactType, but without providing 'contact'.
+ do_test({contactType: GECKO_CARDCONTACT_TYPE_ADN},
+ CONTACT_ERR_REQUEST_NOT_SUPPORTED);
+
+ // Error 3, specifying a non-supported contactType.
+ ril.appType = CARD_APPTYPE_USIM;
+ do_test({contactType: GECKO_CARDCONTACT_TYPE_SDN, contact: {}},
+ CONTACT_ERR_CONTACT_TYPE_NOT_SUPPORTED);
+
+ // Error 4, without supplying pin2.
+ do_test({contactType: GECKO_CARDCONTACT_TYPE_FDN,
+ contact: {contactId: ICCID + "1"}},
+ GECKO_ERROR_SIM_PIN2);
+
+ // Error 5, No free record found in EF_ADN.
+ let record = context.ICCRecordHelper;
+ record.readPBR = function(onsuccess, onerror) {
+ onsuccess([{adn: {fileId: 0x4f3a}}]);
+ };
+
+ let io = context.ICCIOHelper;
+ io.loadLinearFixedEF = function(options) {
+ options.totalRecords = 1;
+ options.p1 = 1;
+ options.callback(options);
+ };
+
+ do_test({contactType: GECKO_CARDCONTACT_TYPE_ADN, contact: {}},
+ CONTACT_ERR_NO_FREE_RECORD_FOUND);
+
+ // Error 6, ICC IO Error.
+ io.loadLinearFixedEF = function(options) {
+ ril[REQUEST_SIM_IO](0, {
+ errorMsg: GECKO_ERROR_GENERIC_FAILURE
+ });
+ };
+ do_test({contactType: GECKO_CARDCONTACT_TYPE_ADN,
+ contact: {contactId: ICCID + "1"}},
+ GECKO_ERROR_GENERIC_FAILURE);
+
+ // Error 7, suppose we update the supported PBR fields in USIM_PBR_FIELDS,
+ // but forget to add implemenetations for it.
+ USIM_PBR_FIELDS.push("pbc");
+ do_test({contactType: GECKO_CARDCONTACT_TYPE_ADN,
+ contact: {contactId: ICCID + "1"}},
+ CONTACT_ERR_FIELD_NOT_SUPPORTED);
+
+ // Error 8, EF_PBR doesn't exist.
+ record.readPBR = function(onsuccess, onerror) {
+ onsuccess([]);
+ };
+
+ do_test({contactType: GECKO_CARDCONTACT_TYPE_ADN,
+ contact: {contactId: ICCID + "1"}},
+ CONTACT_ERR_CANNOT_ACCESS_PHONEBOOK);
+
+ run_next_test();
+});
+
+/**
+ * Verify ICCContactHelper.readICCContacts
+ */
+add_test(function test_read_icc_contacts() {
+ let worker = newUint8Worker();
+ let context = worker.ContextPool._contexts[0];
+ let record = context.ICCRecordHelper;
+ let contactHelper = context.ICCContactHelper;
+ let ril = context.RIL;
+ let test_data = [
+ //Record 1.
+ {
+ comment: "Test read SIM adn contact",
+ rawData: {
+ simType: CARD_APPTYPE_SIM,
+ contactType: GECKO_CARDCONTACT_TYPE_ADN,
+ adnLike: [{recordId: 1, alphaId: "name", number: "111111"}],
+ },
+ expectedContact: [{
+ recordId: 1,
+ alphaId: "name",
+ number: "111111"
+ }],
+ },
+ //Record 2.
+ {
+ comment: "Test read SIM fdn contact",
+ rawData: {
+ simType: CARD_APPTYPE_SIM,
+ contactType: GECKO_CARDCONTACT_TYPE_FDN,
+ adnLike: [{recordId: 1, alphaId: "name", number: "111111"}],
+ },
+ expectedContact: [{
+ recordId: 1,
+ alphaId: "name",
+ number: "111111"
+ }],
+ },
+ //Record 3.
+ {
+ comment: "Test read USIM adn contact",
+ rawData: {
+ simType: CARD_APPTYPE_USIM,
+ contactType: GECKO_CARDCONTACT_TYPE_ADN,
+ pbrs: [{adn:{fileId: 0x6f3a}, email: {}, anr0: {}}],
+ adnLike: [{recordId: 1, alphaId: "name", number: "111111"}],
+ email: "hello@mail.com",
+ anr: "123456",
+ },
+ expectedContact: [{
+ pbrIndex: 0,
+ recordId: 1,
+ alphaId: "name",
+ number: "111111",
+ email: "hello@mail.com",
+ anr: ["123456"]
+ }],
+ },
+ //Record 4.
+ {
+ comment: "Test read USIM adn contacts",
+ rawData: {
+ simType: CARD_APPTYPE_USIM,
+ contactType: GECKO_CARDCONTACT_TYPE_ADN,
+ pbrs: [{adn:{fileId: 0x6f3a}, email: {}, anr0: {}},
+ {adn:{fileId: 0x6f3b}, email: {}, anr0: {}}],
+ adnLike: [{recordId: 1, alphaId: "name1", number: "111111"},
+ {recordId: 2, alphaId: "name2", number: "222222"}],
+ email: "hello@mail.com",
+ anr: "123456",
+ },
+ expectedContact: [
+ {
+ pbrIndex: 0,
+ recordId: 1,
+ alphaId: "name1",
+ number: "111111",
+ email: "hello@mail.com",
+ anr: ["123456"]
+ }, {
+ pbrIndex: 0,
+ recordId: 2,
+ alphaId: "name2",
+ number: "222222",
+ email: "hello@mail.com",
+ anr: ["123456"]
+ }, {
+ pbrIndex: 1,
+ recordId: 1,
+ alphaId: "name1",
+ number: "111111",
+ email: "hello@mail.com",
+ anr: ["123456"]
+ }, {
+ pbrIndex: 1,
+ recordId: 2,
+ alphaId: "name2",
+ number: "222222",
+ email: "hello@mail.com",
+ anr: ["123456"]
+ }
+ ],
+ },
+ //Record 5.
+ {
+ comment: "Test read USIM fdn contact",
+ rawData: {
+ simType: CARD_APPTYPE_USIM,
+ contactType: GECKO_CARDCONTACT_TYPE_FDN,
+ adnLike: [{recordId: 1, alphaId: "name", number: "111111"}],
+ },
+ expectedContact: [{
+ recordId: 1,
+ alphaId: "name",
+ number: "111111"
+ }],
+ },
+ //Record 6.
+ {
+ comment: "Test read RUIM adn contact",
+ rawData: {
+ simType: CARD_APPTYPE_RUIM,
+ contactType: GECKO_CARDCONTACT_TYPE_ADN,
+ adnLike: [{recordId: 1, alphaId: "name", number: "111111"}],
+ },
+ expectedContact: [{
+ recordId: 1,
+ alphaId: "name",
+ number: "111111"
+ }],
+ },
+ //Record 7.
+ {
+ comment: "Test read RUIM fdn contact",
+ rawData: {
+ simType: CARD_APPTYPE_RUIM,
+ contactType: GECKO_CARDCONTACT_TYPE_FDN,
+ adnLike: [{recordId: 1, alphaId: "name", number: "111111"}],
+ },
+ expectedContact: [{
+ recordId: 1,
+ alphaId: "name",
+ number: "111111"
+ }],
+ },
+ //Record 8.
+ {
+ comment: "Test read RUIM adn contact with enhanced phone book",
+ rawData: {
+ simType: CARD_APPTYPE_RUIM,
+ contactType: GECKO_CARDCONTACT_TYPE_ADN,
+ pbrs: [{adn:{fileId: 0x6f3a}, email: {}, anr0: {}}],
+ adnLike: [{recordId: 1, alphaId: "name", number: "111111"}],
+ email: "hello@mail.com",
+ anr: "123456",
+ enhancedPhoneBook: true,
+ },
+ expectedContact: [{
+ pbrIndex: 0,
+ recordId: 1,
+ alphaId: "name",
+ number: "111111",
+ email: "hello@mail.com",
+ anr: ["123456"]
+ }],
+ },
+ //Record 9.
+ {
+ comment: "Test read RUIM adn contacts with enhanced phone book",
+ rawData: {
+ simType: CARD_APPTYPE_RUIM,
+ contactType: GECKO_CARDCONTACT_TYPE_ADN,
+ pbrs: [{adn:{fileId: 0x6f3a}, email: {}, anr0: {}},
+ {adn:{fileId: 0x6f3b}, email: {}, anr0: {}}],
+ adnLike: [{recordId: 1, alphaId: "name1", number: "111111"},
+ {recordId: 2, alphaId: "name2", number: "222222"}],
+ email: "hello@mail.com",
+ anr: "123456",
+ enhancedPhoneBook: true,
+ },
+ expectedContact: [
+ {
+ pbrIndex: 0,
+ recordId: 1,
+ alphaId: "name1",
+ number: "111111",
+ email: "hello@mail.com",
+ anr: ["123456"]
+ }, {
+ pbrIndex: 0,
+ recordId: 2,
+ alphaId: "name2",
+ number: "222222",
+ email: "hello@mail.com",
+ anr: ["123456"]
+ }, {
+ pbrIndex: 1,
+ recordId: 1,
+ alphaId: "name1",
+ number: "111111",
+ email: "hello@mail.com",
+ anr: ["123456"]
+ }, {
+ pbrIndex: 1,
+ recordId: 2,
+ alphaId: "name2",
+ number: "222222",
+ email: "hello@mail.com",
+ anr: ["123456"]
+ }
+ ],
+ },
+ ];
+
+ function do_test(aTestData, aExpectedContact) {
+ ril.appType = aTestData.simType;
+ ril._isCdma = (aTestData.simType === CARD_APPTYPE_RUIM);
+ ril.iccInfoPrivate.cst = (aTestData.enhancedPhoneBook) ?
+ [0x20, 0x0C, 0x0, 0x0, 0x0]:
+ [0x20, 0x00, 0x0, 0x0, 0x0];
+
+ ril.iccInfoPrivate.sst = (aTestData.simType === CARD_APPTYPE_SIM)?
+ [0x20, 0x0, 0x0, 0x0, 0x0]:
+ [0x2, 0x0, 0x0, 0x0, 0x0];
+
+ // Override some functions to test.
+ contactHelper.getContactFieldRecordId = function(pbr, contact, field, onsuccess, onerror) {
+ onsuccess(1);
+ };
+
+ record.readPBR = function readPBR(onsuccess, onerror) {
+ onsuccess(JSON.parse(JSON.stringify(aTestData.pbrs)));
+ };
+
+ record.readADNLike = function readADNLike(fileId, extFileId, onsuccess, onerror) {
+ onsuccess(JSON.parse(JSON.stringify(aTestData.adnLike)));
+ };
+
+ record.readEmail = function readEmail(fileId, fileType, recordNumber, onsuccess, onerror) {
+ onsuccess(aTestData.email);
+ };
+
+ record.readANR = function readANR(fileId, fileType, recordNumber, onsuccess, onerror) {
+ onsuccess(aTestData.anr);
+ };
+
+ let onsuccess = function onsuccess(contacts) {
+ for (let i = 0; i < contacts.length; i++) {
+ do_print("check contacts[" + i + "]:" + JSON.stringify(contacts[i]));
+ deepEqual(contacts[i], aExpectedContact[i]);
+ }
+ };
+
+ let onerror = function onerror(errorMsg) {
+ do_print("readICCContacts failed: " + errorMsg);
+ ok(false);
+ };
+
+ contactHelper.readICCContacts(aTestData.simType, aTestData.contactType, onsuccess, onerror);
+ }
+
+ for (let i = 0; i < test_data.length; i++) {
+ do_print(test_data[i].comment);
+ do_test(test_data[i].rawData, test_data[i].expectedContact);
+ }
+
+ run_next_test();
+});
+
+/**
+ * Verify ICCContactHelper.updateICCContact with appType is CARD_APPTYPE_USIM.
+ */
+add_test(function test_update_icc_contact() {
+ const ADN_RECORD_ID = 100;
+ const ADN_SFI = 1;
+ const IAP_FILE_ID = 0x4f17;
+ const EMAIL_FILE_ID = 0x4f50;
+ const EMAIL_RECORD_ID = 20;
+ const ANR0_FILE_ID = 0x4f11;
+ const ANR0_RECORD_ID = 30;
+ const EXT_RECORD_ID = 0x01;
+
+ let worker = newUint8Worker();
+ let context = worker.ContextPool._contexts[0];
+ let recordHelper = context.ICCRecordHelper;
+ let contactHelper = context.ICCContactHelper;
+ let ril = context.RIL;
+
+ function do_test(aSimType, aContactType, aContact, aPin2, aFileType, aHaveIapIndex, aEnhancedPhoneBook) {
+ ril.appType = aSimType;
+ ril._isCdma = (aSimType === CARD_APPTYPE_RUIM);
+ ril.iccInfoPrivate.cst = (aEnhancedPhoneBook) ? [0x20, 0x0C, 0x28, 0x0, 0x20]
+ : [0x20, 0x0, 0x28, 0x0, 0x20];
+ ril.iccInfoPrivate.sst = (aSimType === CARD_APPTYPE_SIM)?
+ [0x20, 0x0, 0x28, 0x0, 0x20]:
+ [0x16, 0x0, 0x0, 0x0, 0x0];
+
+ recordHelper.readPBR = function(onsuccess, onerror) {
+ if (aFileType === ICC_USIM_TYPE1_TAG) {
+ onsuccess([{
+ adn: {fileId: ICC_EF_ADN},
+ email: {fileId: EMAIL_FILE_ID,
+ fileType: ICC_USIM_TYPE1_TAG},
+ anr0: {fileId: ANR0_FILE_ID,
+ fileType: ICC_USIM_TYPE1_TAG},
+ ext1: {fileId: ICC_EF_EXT1}
+
+ }]);
+ } else if (aFileType === ICC_USIM_TYPE2_TAG) {
+ onsuccess([{
+ adn: {fileId: ICC_EF_ADN,
+ sfi: ADN_SFI},
+ iap: {fileId: IAP_FILE_ID},
+ email: {fileId: EMAIL_FILE_ID,
+ fileType: ICC_USIM_TYPE2_TAG,
+ indexInIAP: 0},
+ anr0: {fileId: ANR0_FILE_ID,
+ fileType: ICC_USIM_TYPE2_TAG,
+ indexInIAP: 1},
+ ext1: {fileId: ICC_EF_EXT1}
+ }]);
+ }
+ };
+
+ recordHelper.updateADNLike = function(fileId, extRecordNumber, contact, pin2, onsuccess, onerror) {
+ if (aContactType === GECKO_CARDCONTACT_TYPE_FDN) {
+ equal(fileId, ICC_EF_FDN);
+ } else if (aContactType === GECKO_CARDCONTACT_TYPE_ADN) {
+ equal(fileId, ICC_EF_ADN);
+ }
+
+ if (aContact.number.length > ADN_MAX_NUMBER_DIGITS) {
+ equal(extRecordNumber, EXT_RECORD_ID);
+ } else {
+ equal(extRecordNumber, 0xff);
+ }
+
+ equal(pin2, aPin2);
+ equal(contact.alphaId, aContact.alphaId);
+ equal(contact.number, aContact.number);
+ onsuccess({alphaId: contact.alphaId,
+ number: contact.number.substring(0, ADN_MAX_NUMBER_DIGITS)});
+ };
+
+ recordHelper.getADNLikeExtensionRecordNumber = function(fileId, recordNumber, onsuccess, onerror) {
+ onsuccess(EXT_RECORD_ID);
+ };
+
+ recordHelper.updateExtension = function(fileId, recordNumber, number, onsuccess, onerror) {
+ onsuccess();
+ };
+
+ recordHelper.findFreeRecordId = function(fileId, onsuccess, onerror) {
+ onsuccess(EXT_RECORD_ID);
+ };
+
+ recordHelper.cleanEFRecord = function(fileId, recordNumber, onsuccess, onerror) {
+ onsuccess();
+ }
+
+ recordHelper.readIAP = function(fileId, recordNumber, onsuccess, onerror) {
+ equal(fileId, IAP_FILE_ID);
+ equal(recordNumber, ADN_RECORD_ID);
+ onsuccess((aHaveIapIndex) ? [EMAIL_RECORD_ID, ANR0_RECORD_ID]
+ : [0xff, 0xff]);
+ };
+
+ recordHelper.updateIAP = function(fileId, recordNumber, iap, onsuccess, onerror) {
+ equal(fileId, IAP_FILE_ID);
+ equal(recordNumber, ADN_RECORD_ID);
+ onsuccess();
+ };
+
+ recordHelper.updateEmail = function(pbr, recordNumber, email, adnRecordId, onsuccess, onerror) {
+ equal(pbr.email.fileId, EMAIL_FILE_ID);
+ if (pbr.email.fileType === ICC_USIM_TYPE1_TAG) {
+ equal(recordNumber, ADN_RECORD_ID);
+ } else if (pbr.email.fileType === ICC_USIM_TYPE2_TAG) {
+ equal(recordNumber, EMAIL_RECORD_ID);
+ }
+ equal(email, aContact.email);
+ onsuccess(email);
+ };
+
+ recordHelper.updateANR = function(pbr, recordNumber, number, adnRecordId, onsuccess, onerror) {
+ equal(pbr.anr0.fileId, ANR0_FILE_ID);
+ if (pbr.anr0.fileType === ICC_USIM_TYPE1_TAG) {
+ equal(recordNumber, ADN_RECORD_ID);
+ } else if (pbr.anr0.fileType === ICC_USIM_TYPE2_TAG) {
+ equal(recordNumber, ANR0_RECORD_ID);
+ }
+ if (Array.isArray(aContact.anr)) {
+ equal(number, aContact.anr[0]);
+ }
+ onsuccess(number);
+ };
+
+ recordHelper.findFreeRecordId = function(fileId, onsuccess, onerror) {
+ let recordId = 0;
+ if (fileId === EMAIL_FILE_ID) {
+ recordId = EMAIL_RECORD_ID;
+ } else if (fileId === ANR0_FILE_ID) {
+ recordId = ANR0_RECORD_ID;
+ }
+ onsuccess(recordId);
+ };
+
+ let isSuccess = false;
+ let onsuccess = function onsuccess(updatedContact) {
+ equal(ADN_RECORD_ID, updatedContact.recordId);
+ equal(aContact.alphaId, updatedContact.alphaId);
+ equal(aContact.number.substring(0, ADN_MAX_NUMBER_DIGITS + EXT_MAX_NUMBER_DIGITS),
+ updatedContact.number);
+ if ((aSimType == CARD_APPTYPE_USIM || aSimType == CARD_APPTYPE_RUIM) &&
+ (aFileType == ICC_USIM_TYPE1_TAG || aFileType == ICC_USIM_TYPE2_TAG)) {
+ if (aContact.hasOwnProperty('email')) {
+ equal(aContact.email, updatedContact.email);
+ }
+
+ if (aContact.hasOwnProperty('anr')) {
+ equal(aContact.anr[0], updatedContact.anr[0]);
+ }
+ } else {
+ equal(updatedContact.email, null);
+ equal(updatedContact.anr, null);
+ }
+
+ do_print("updateICCContact success");
+ isSuccess = true;
+ };
+
+ let onerror = function onerror(errorMsg) {
+ do_print("updateICCContact failed: " + errorMsg);
+ };
+
+ contactHelper.updateICCContact(aSimType, aContactType, aContact, aPin2, onsuccess, onerror);
+ ok(isSuccess);
+ }
+
+ let contacts = [
+ {
+ pbrIndex: 0,
+ recordId: ADN_RECORD_ID,
+ alphaId: "test",
+ number: "123456",
+ email: "test@mail.com",
+ anr: ["+654321"]
+ },
+ // a contact without email and anr.
+ {
+ pbrIndex: 0,
+ recordId: ADN_RECORD_ID,
+ alphaId: "test2",
+ number: "123456",
+ },
+ // a contact with email but no anr.
+ {
+ pbrIndex: 0,
+ recordId: ADN_RECORD_ID,
+ alphaId: "test3",
+ number: "123456",
+ email: "test@mail.com"
+ },
+ // a contact with anr but no email.
+ {
+ pbrIndex: 0,
+ recordId: ADN_RECORD_ID,
+ alphaId: "test4",
+ number: "123456",
+ anr: ["+654321"]
+ },
+ // a contact number over 20 digits.
+ {
+ pbrIndex: 0,
+ recordId: ADN_RECORD_ID,
+ alphaId: "test4",
+ number: "0123456789012345678901234567890123456789",
+ anr: ["+654321"]
+ },
+ // a contact number over 40 digits.
+ {
+ pbrIndex: 0,
+ recordId: ADN_RECORD_ID,
+ alphaId: "test5",
+ number: "01234567890123456789012345678901234567890123456789",
+ anr: ["+654321"]
+ }];
+
+ for (let i = 0; i < contacts.length; i++) {
+ let contact = contacts[i];
+ // SIM
+ do_print("Test update SIM adn contacts");
+ do_test(CARD_APPTYPE_SIM, GECKO_CARDCONTACT_TYPE_ADN, contact);
+
+ do_print("Test update SIM fdn contacts");
+ do_test(CARD_APPTYPE_SIM, GECKO_CARDCONTACT_TYPE_FDN, contact, "1234");
+
+ // USIM
+ do_print("Test update USIM adn contacts");
+ do_test(CARD_APPTYPE_USIM, GECKO_CARDCONTACT_TYPE_ADN, contact, null,
+ ICC_USIM_TYPE1_TAG);
+ do_test(CARD_APPTYPE_USIM, GECKO_CARDCONTACT_TYPE_ADN, contact, null,
+ ICC_USIM_TYPE2_TAG, true);
+ do_test(CARD_APPTYPE_USIM, GECKO_CARDCONTACT_TYPE_ADN, contact, null,
+ ICC_USIM_TYPE2_TAG, false);
+
+ do_print("Test update USIM fdn contacts");
+ do_test(CARD_APPTYPE_USIM, GECKO_CARDCONTACT_TYPE_FDN, contact, "1234");
+
+ // RUIM
+ do_print("Test update RUIM adn contacts");
+ do_test(CARD_APPTYPE_RUIM, GECKO_CARDCONTACT_TYPE_ADN, contact);
+
+ do_print("Test update RUIM fdn contacts");
+ do_test(CARD_APPTYPE_RUIM, GECKO_CARDCONTACT_TYPE_FDN, contact, "1234");
+
+ // RUIM with enhanced phone book
+ do_print("Test update RUIM adn contacts with enhanced phone book");
+ do_test(CARD_APPTYPE_RUIM, GECKO_CARDCONTACT_TYPE_ADN, contact, null,
+ ICC_USIM_TYPE1_TAG, null,true);
+ do_test(CARD_APPTYPE_RUIM, GECKO_CARDCONTACT_TYPE_ADN, contact, null,
+ ICC_USIM_TYPE2_TAG, true, true);
+ do_test(CARD_APPTYPE_RUIM, GECKO_CARDCONTACT_TYPE_ADN, contact, null,
+ ICC_USIM_TYPE2_TAG, false, true);
+
+ do_print("Test update RUIM fdn contacts with enhanced phone book");
+ do_test(CARD_APPTYPE_RUIM, GECKO_CARDCONTACT_TYPE_FDN, contact, "1234",
+ null, true);
+ }
+
+ run_next_test();
+});
+
+/**
+ * Verify ICCContactHelper.updateICCContact with appType is CARD_APPTYPE_USIM and
+ * insufficient space to store Type 2 USIM contact fields.
+ */
+add_test(function test_update_icc_contact_full_email_and_anr_field() {
+ const ADN_RECORD_ID = 100;
+ const ADN_SFI = 1;
+ const IAP_FILE_ID = 0x4f17;
+ const EMAIL_FILE_ID = 0x4f50;
+ const EMAIL_RECORD_ID = 20;
+ const ANR0_FILE_ID = 0x4f11;
+ const ANR0_RECORD_ID = 30;
+
+ let worker = newUint8Worker();
+ let context = worker.ContextPool._contexts[0];
+ let recordHelper = context.ICCRecordHelper;
+ let contactHelper = context.ICCContactHelper;
+ let ril = context.RIL;
+
+ function do_test(aSimType, aContactType, aContact, aPin2) {
+ ril.appType = CARD_APPTYPE_USIM;
+ ril.iccInfoPrivate.sst = [0x2, 0x0, 0x0, 0x0, 0x0];
+
+ recordHelper.readPBR = function(onsuccess, onerror) {
+ onsuccess([{
+ adn: {fileId: ICC_EF_ADN,
+ sfi: ADN_SFI},
+ iap: {fileId: IAP_FILE_ID},
+ email: {fileId: EMAIL_FILE_ID,
+ fileType: ICC_USIM_TYPE2_TAG,
+ indexInIAP: 0},
+ anr0: {fileId: ANR0_FILE_ID,
+ fileType: ICC_USIM_TYPE2_TAG,
+ indexInIAP: 1}
+ }]);
+ };
+
+ recordHelper.updateADNLike = function(fileId, extRecordNumber, contact, pin2, onsuccess, onerror) {
+ if (aContactType === GECKO_CARDCONTACT_TYPE_ADN) {
+ equal(fileId, ICC_EF_ADN);
+ }
+ equal(pin2, aPin2);
+ equal(contact.alphaId, aContact.alphaId);
+ equal(contact.number, aContact.number);
+ onsuccess({alphaId: contact.alphaId,
+ number: contact.number});
+ };
+
+ recordHelper.readIAP = function(fileId, recordNumber, onsuccess, onerror) {
+ equal(fileId, IAP_FILE_ID);
+ equal(recordNumber, ADN_RECORD_ID);
+ onsuccess([0xff, 0xff]);
+ };
+
+ recordHelper.updateIAP = function(fileId, recordNumber, iap, onsuccess, onerror) {
+ equal(fileId, IAP_FILE_ID);
+ equal(recordNumber, ADN_RECORD_ID);
+ onsuccess();
+ };
+
+ recordHelper.findFreeRecordId = function(fileId, onsuccess, onerror) {
+ let recordId = 0;
+ // emulate email and anr don't have free record.
+ if (fileId === EMAIL_FILE_ID || fileId === ANR0_FILE_ID) {
+ onerror(CONTACT_ERR_NO_FREE_RECORD_FOUND);
+ } else {
+ onsuccess(recordId);
+ }
+ };
+
+ let isSuccess = false;
+ let onsuccess = function onsuccess(updatedContact) {
+ equal(ADN_RECORD_ID, updatedContact.recordId);
+ equal(aContact.alphaId, updatedContact.alphaId);
+ equal(updatedContact.email, null);
+ equal(updatedContact.anr, null);
+
+ do_print("updateICCContact success");
+ isSuccess = true;
+ };
+
+ let onerror = function onerror(errorMsg) {
+ do_print("updateICCContact failed: " + errorMsg);
+ };
+
+ contactHelper.updateICCContact(aSimType, aContactType, aContact, aPin2, onsuccess, onerror);
+ ok(isSuccess);
+ }
+
+ let contact = {
+ pbrIndex: 0,
+ recordId: ADN_RECORD_ID,
+ alphaId: "test",
+ number: "123456",
+ email: "test@mail.com",
+ anr: ["+654321"]
+ };
+
+ // USIM
+ do_print("Test update USIM adn contacts");
+ do_test(CARD_APPTYPE_USIM, GECKO_CARDCONTACT_TYPE_ADN, contact, null);
+
+ run_next_test();
+});
+
+/**
+ * Verify updateICCContact with removal of anr and email with File Type 1.
+ */
+add_test(function test_update_icc_contact_with_remove_type1_attr() {
+ const ADN_RECORD_ID = 100;
+ const IAP_FILE_ID = 0x4f17;
+ const EMAIL_FILE_ID = 0x4f50;
+ const EMAIL_RECORD_ID = 20;
+ const ANR0_FILE_ID = 0x4f11;
+ const ANR0_RECORD_ID = 30;
+
+ let worker = newUint8Worker();
+ let context = worker.ContextPool._contexts[0];
+ let recordHelper = context.ICCRecordHelper;
+ let contactHelper = context.ICCContactHelper;
+
+ recordHelper.updateADNLike = function(fileId, extRecordNumber, contact, pin2, onsuccess, onerror) {
+ onsuccess({alphaId: contact.alphaId,
+ number: contact.number});
+ };
+
+ let contact = {
+ pbrIndex: 0,
+ recordId: ADN_RECORD_ID,
+ alphaId: "test2",
+ number: "123456",
+ };
+
+ recordHelper.readIAP = function(fileId, recordNumber, onsuccess, onerror) {
+ onsuccess([EMAIL_RECORD_ID, ANR0_RECORD_ID]);
+ };
+
+ recordHelper.updateEmail = function(pbr, recordNumber, email, adnRecordId, onsuccess, onerror) {
+ ok(email == null);
+ onsuccess(email);
+ };
+
+ recordHelper.updateANR = function(pbr, recordNumber, number, adnRecordId, onsuccess, onerror) {
+ ok(number == null);
+ onsuccess(number);
+ };
+
+ function do_test(type) {
+ recordHelper.readPBR = function(onsuccess, onerror) {
+ if (type == ICC_USIM_TYPE1_TAG) {
+ onsuccess([{
+ adn: {fileId: ICC_EF_ADN},
+ email: {fileId: EMAIL_FILE_ID,
+ fileType: ICC_USIM_TYPE1_TAG},
+ anr0: {fileId: ANR0_FILE_ID,
+ fileType: ICC_USIM_TYPE1_TAG}}]);
+ } else {
+ onsuccess([{
+ adn: {fileId: ICC_EF_ADN},
+ iap: {fileId: IAP_FILE_ID},
+ email: {fileId: EMAIL_FILE_ID,
+ fileType: ICC_USIM_TYPE2_TAG,
+ indexInIAP: 0},
+ anr0: {fileId: ANR0_FILE_ID,
+ fileType: ICC_USIM_TYPE2_TAG,
+ indexInIAP: 1}}]);
+ }
+ };
+
+ let successCb = function(updatedContact) {
+ equal(updatedContact.email, null);
+ equal(updatedContact.anr, null);
+ ok(true);
+ };
+
+ let errorCb = function(errorMsg) {
+ do_print(errorMsg);
+ ok(false);
+ };
+
+ contactHelper.updateICCContact(CARD_APPTYPE_USIM,
+ GECKO_CARDCONTACT_TYPE_ADN,
+ contact, null, successCb, errorCb);
+ }
+
+ do_test(ICC_USIM_TYPE1_TAG);
+ do_test(ICC_USIM_TYPE2_TAG);
+
+ run_next_test();
+});
+
+/**
+ * Verify ICCContactHelper.findFreeICCContact in SIM
+ */
+add_test(function test_find_free_icc_contact_sim() {
+ let worker = newUint8Worker();
+ let context = worker.ContextPool._contexts[0];
+ let recordHelper = context.ICCRecordHelper;
+ let contactHelper = context.ICCContactHelper;
+ // Correct record Id starts with 1, so put a null element at index 0.
+ let records = [null];
+ const MAX_RECORDS = 3;
+ const PBR_INDEX = 0;
+
+ recordHelper.findFreeRecordId = function(fileId, onsuccess, onerror) {
+ if (records.length > MAX_RECORDS) {
+ onerror("No free record found.");
+ return;
+ }
+
+ onsuccess(records.length);
+ };
+
+ let successCb = function(pbrIndex, recordId) {
+ equal(pbrIndex, PBR_INDEX);
+ records[recordId] = {};
+ };
+
+ let errorCb = function(errorMsg) {
+ do_print(errorMsg);
+ ok(false);
+ };
+
+ for (let i = 0; i < MAX_RECORDS; i++) {
+ contactHelper.findFreeICCContact(CARD_APPTYPE_SIM,
+ GECKO_CARDCONTACT_TYPE_ADN,
+ successCb, errorCb);
+ }
+ // The 1st element, records[0], is null.
+ equal(records.length - 1, MAX_RECORDS);
+
+ // Now the EF is full, so finding a free one should result failure.
+ successCb = function(pbrIndex, recordId) {
+ ok(false);
+ };
+
+ errorCb = function(errorMsg) {
+ ok(errorMsg === "No free record found.");
+ };
+ contactHelper.findFreeICCContact(CARD_APPTYPE_SIM, GECKO_CARDCONTACT_TYPE_ADN,
+ successCb, errorCb);
+
+ run_next_test();
+});
+
+/**
+ * Verify ICCContactHelper.findFreeICCContact in USIM
+ */
+add_test(function test_find_free_icc_contact_usim() {
+ let worker = newUint8Worker();
+ let context = worker.ContextPool._contexts[0];
+ let recordHelper = context.ICCRecordHelper;
+ let contactHelper = context.ICCContactHelper;
+ const ADN1_FILE_ID = 0x6f3a;
+ const ADN2_FILE_ID = 0x6f3b;
+ const MAX_RECORDS = 3;
+
+ // The adn in the first phonebook set has already two records, which means
+ // only 1 free record remained.
+ let pbrs = [{adn: {fileId: ADN1_FILE_ID, records: [null, {}, {}]}},
+ {adn: {fileId: ADN2_FILE_ID, records: [null]}}];
+
+ recordHelper.readPBR = function readPBR(onsuccess, onerror) {
+ onsuccess(pbrs);
+ };
+
+ recordHelper.findFreeRecordId = function(fileId, onsuccess, onerror) {
+ let pbr = (fileId == ADN1_FILE_ID ? pbrs[0]: pbrs[1]);
+ if (pbr.adn.records.length > MAX_RECORDS) {
+ onerror("No free record found.");
+ return;
+ }
+
+ onsuccess(pbr.adn.records.length);
+ };
+
+ let successCb = function(pbrIndex, recordId) {
+ equal(pbrIndex, 0);
+ pbrs[pbrIndex].adn.records[recordId] = {};
+ };
+
+ let errorCb = function(errorMsg) {
+ ok(false);
+ };
+
+ contactHelper.findFreeICCContact(CARD_APPTYPE_USIM,
+ GECKO_CARDCONTACT_TYPE_ADN,
+ successCb, errorCb);
+
+ // Now the EF_ADN in the 1st phonebook set is full, so the next free contact
+ // will come from the 2nd phonebook set.
+ successCb = function(pbrIndex, recordId) {
+ equal(pbrIndex, 1);
+ equal(recordId, 1);
+ }
+ contactHelper.findFreeICCContact(CARD_APPTYPE_USIM,
+ GECKO_CARDCONTACT_TYPE_ADN,
+ successCb, errorCb);
+
+ run_next_test();
+});
+
+/**
+ * Verify ICCContactHelper.updateADNLikeWithExtension
+ */
+add_test(function test_update_adn_like_with_extension() {
+ let worker = newUint8Worker();
+ let context = worker.ContextPool._contexts[0];
+ let ril = context.RIL;
+ let record = context.ICCRecordHelper;
+ let contactHelper = context.ICCContactHelper;
+ ril.appType = CARD_APPTYPE_SIM;
+ // Correct record Id starts from 1, so put a null element at index 0.
+ // ext_records contains data at index 1, and it only has 1 free record at index 2.
+ let notFree = 0x01;
+ let ext_records = [null, notFree, null];
+
+ function do_test(contact, extRecordNumber, expectedExtRecordNumber, expectedNumber, expectedCleanEFRecord) {
+ // Override some functions to test.
+ record.getADNLikeExtensionRecordNumber = function(fileId, recordNumber, onsuccess, onerror) {
+ onsuccess(extRecordNumber);
+ }
+
+ record.updateADNLike = function(fileId, extRecordNumber, contact, pin2, onsuccess, onerror) {
+ equal(extRecordNumber, expectedExtRecordNumber);
+ onsuccess({alphaId: contact.alphaId,
+ number: contact.number.substring(0, ADN_MAX_NUMBER_DIGITS)});
+ }
+
+ record.updateExtension = function(fileId, recordNumber, number, onsuccess, onerror) {
+ if (recordNumber > ext_records.length) {
+ onerror("updateExtension failed.");
+ return;
+ }
+ ext_records[recordNumber] = number;
+ onsuccess();
+ }
+
+ record.findFreeRecordId = function(fileId, onsuccess, onerror) {
+ for (let i = 1; i < ext_records.length; i++) {
+ if (!ext_records[i]) {
+ onsuccess(i);
+ return;
+ }
+ }
+
+ onerror("No free record found.");
+ }
+
+ let isCleanEFRecord = false;
+ record.cleanEFRecord = function(fileId, recordNumber, onsuccess, onerror) {
+ if (recordNumber > ext_records.length) {
+ onerror("cleanEFRecord failed.");
+ return;
+ }
+ ext_records[recordNumber] = null;
+ isCleanEFRecord = true;
+ onsuccess();
+ }
+
+ let successCb = function successCb(updatedContact) {
+ equal(updatedContact.number, expectedNumber);
+ };
+
+ let errorCb = function errorCb(errorMsg) {
+ do_print("updateADNLikeWithExtension failed, msg = " + errorMsg);
+ ok(false);
+ };
+
+ contactHelper.updateADNLikeWithExtension(ICC_EF_ADN, ICC_EF_EXT1, contact, null, successCb, errorCb);
+
+ if (expectedCleanEFRecord) {
+ ok(isCleanEFRecord);
+ }
+ }
+
+ // Update extension record with previous extension record number.
+ do_test({recordId: 1, alphaId: "test", number: "001122334455667788991234"}, 0x01, 0x01, "001122334455667788991234");
+ // Update extension record and find a free record.
+ do_test({recordId: 1, alphaId: "test", number: "001122334455667788995678"}, 0xff, 0x02, "001122334455667788995678");
+ // Update extension record with no free extension record.
+ do_test({recordId: 1, alphaId: "test", number: "001122334455667788994321"}, 0xff, 0xff, "00112233445566778899");
+ // Update extension record with clean previous extension record.
+ do_test({recordId: 1, alphaId: "test", number: "00112233445566778899"}, 0x01, 0xff, "00112233445566778899", true);
+ // Update extension record with no extension record and previous extension record.
+ do_test({recordId: 1, alphaId: "test", number: "00112233445566778899"}, 0xff, 0xff, "00112233445566778899");
+
+ run_next_test();
+}); \ No newline at end of file
diff --git a/dom/system/gonk/tests/test_ril_worker_icc_ICCIOHelper.js b/dom/system/gonk/tests/test_ril_worker_icc_ICCIOHelper.js
new file mode 100644
index 000000000..e690b1206
--- /dev/null
+++ b/dom/system/gonk/tests/test_ril_worker_icc_ICCIOHelper.js
@@ -0,0 +1,173 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+subscriptLoader.loadSubScript("resource://gre/modules/ril_consts.js", this);
+
+function run_test() {
+ run_next_test();
+}
+
+/**
+ * Verify ICCIOHelper.loadLinearFixedEF with recordSize.
+ */
+add_test(function test_load_linear_fixed_ef() {
+ let worker = newUint8Worker();
+ let context = worker.ContextPool._contexts[0];
+ let ril = context.RIL;
+ let io = context.ICCIOHelper;
+
+ io.getResponse = function fakeGetResponse(options) {
+ // When recordSize is provided, loadLinearFixedEF should call iccIO directly.
+ ok(false);
+ run_next_test();
+ };
+
+ ril.iccIO = function fakeIccIO(options) {
+ ok(true);
+ run_next_test();
+ };
+
+ io.loadLinearFixedEF({recordSize: 0x20});
+});
+
+/**
+ * Verify ICCIOHelper.loadLinearFixedEF without recordSize.
+ */
+add_test(function test_load_linear_fixed_ef() {
+ let worker = newUint8Worker();
+ let context = worker.ContextPool._contexts[0];
+ let ril = context.RIL;
+ let io = context.ICCIOHelper;
+
+ io.getResponse = function fakeGetResponse(options) {
+ ok(true);
+ run_next_test();
+ };
+
+ ril.iccIO = function fakeIccIO(options) {
+ // When recordSize is not provided, loadLinearFixedEF should call getResponse.
+ ok(false);
+ run_next_test();
+ };
+
+ io.loadLinearFixedEF({});
+});
+
+/**
+ * Verify ICC IO Error.
+ */
+add_test(function test_process_icc_io_error() {
+ let worker = newUint8Worker();
+ let context = worker.ContextPool._contexts[0];
+ let buf = context.Buf;
+
+ function do_test(sw1, sw2, expectedErrorMsg) {
+ let called = false;
+ function errorCb(errorMsg) {
+ called = true;
+ equal(errorMsg, expectedErrorMsg);
+ }
+
+ // Write sw1 and sw2 to buffer.
+ buf.writeInt32(sw1);
+ buf.writeInt32(sw2);
+
+ context.RIL[REQUEST_SIM_IO](0, {fileId: 0xffff,
+ command: 0xff,
+ onerror: errorCb});
+
+ // onerror callback should be triggered.
+ ok(called);
+ }
+
+ let TEST_DATA = [
+ // [sw1, sw2, expectError]
+ [ICC_STATUS_ERROR_COMMAND_NOT_ALLOWED, 0xff, GECKO_ERROR_GENERIC_FAILURE],
+ [ICC_STATUS_ERROR_WRONG_PARAMETERS, 0xff, GECKO_ERROR_GENERIC_FAILURE],
+ ];
+
+ for (let i = 0; i < TEST_DATA.length; i++) {
+ do_test.apply(null, TEST_DATA[i]);
+ }
+
+ run_next_test();
+});
+
+/**
+ * Verify ICCIOHelper.processICCIOGetResponse for EF_TYPE_TRANSPARENT.
+ */
+add_test(function test_icc_io_get_response_for_transparent_structure() {
+ let worker = newUint8Worker();
+ let context = worker.ContextPool._contexts[0];
+ let buf = context.Buf;
+ let iccioHelper = context.ICCIOHelper;
+ let pduHelper = context.GsmPDUHelper;
+
+ let responseArray = [
+ // SIM response.
+ [0x00, 0x00, 0x00, 0x0A, 0x2F, 0xE2, 0x04, 0x00, 0x0A, 0xA0, 0xAA, 0x00,
+ 0x02, 0x00, 0x00],
+ // USIM response.
+ [0x62, 0x22, 0x82, 0x02, 0x41, 0x21, 0x83, 0x02, 0x2F, 0xE2, 0xA5, 0x09,
+ 0xC1, 0x04, 0x40, 0x0F, 0xF5, 0x55, 0x92, 0x01, 0x00, 0x8A, 0x01, 0x05,
+ 0x8B, 0x03, 0x2F, 0x06, 0x0B, 0x80, 0x02, 0x00, 0x0A, 0x88, 0x01, 0x10]
+ ];
+
+ for (let i = 0; i < responseArray.length; i++) {
+ let strLen = responseArray[i].length * 2;
+ buf.writeInt32(strLen);
+ for (let j = 0; j < responseArray[i].length; j++) {
+ pduHelper.writeHexOctet(responseArray[i][j]);
+ }
+ buf.writeStringDelimiter(strLen);
+
+ let options = {fileId: ICC_EF_ICCID,
+ structure: EF_STRUCTURE_TRANSPARENT};
+ iccioHelper.processICCIOGetResponse(options);
+
+ equal(options.fileSize, 0x0A);
+ }
+
+ run_next_test();
+});
+
+/**
+ * Verify ICCIOHelper.processICCIOGetResponse for EF_TYPE_LINEAR_FIXED.
+ */
+add_test(function test_icc_io_get_response_for_linear_fixed_structure() {
+ let worker = newUint8Worker();
+ let context = worker.ContextPool._contexts[0];
+ let buf = context.Buf;
+ let iccioHelper = context.ICCIOHelper;
+ let pduHelper = context.GsmPDUHelper;
+
+ let responseArray = [
+ // SIM response.
+ [0x00, 0x00, 0x00, 0x1A, 0x6F, 0x40, 0x04, 0x00, 0x11, 0xA0, 0xAA, 0x00,
+ 0x02, 0x01, 0x1A],
+ // USIM response.
+ [0x62, 0x1E, 0x82, 0x05, 0x42, 0x21, 0x00, 0x1A, 0x01, 0x83, 0x02, 0x6F,
+ 0x40, 0xA5, 0x03, 0x92, 0x01, 0x00, 0x8A, 0x01, 0x07, 0x8B, 0x03, 0x6F,
+ 0x06, 0x02, 0x80, 0x02, 0x00, 0x1A, 0x88, 0x00]
+ ];
+
+ for (let i = 0; i < responseArray.length; i++) {
+ let strLen = responseArray[i].length * 2;
+ buf.writeInt32(strLen);
+ for (let j = 0; j < responseArray[i].length; j++) {
+ pduHelper.writeHexOctet(responseArray[i][j]);
+ }
+ buf.writeStringDelimiter(strLen);
+
+ let options = {fileId: ICC_EF_MSISDN,
+ structure: EF_STRUCTURE_LINEAR_FIXED};
+ iccioHelper.processICCIOGetResponse(options);
+
+ equal(options.fileSize, 0x1A);
+ equal(options.recordSize, 0x1A);
+ equal(options.totalRecords, 0x01);
+ }
+
+ run_next_test();
+});
+
diff --git a/dom/system/gonk/tests/test_ril_worker_icc_ICCPDUHelper.js b/dom/system/gonk/tests/test_ril_worker_icc_ICCPDUHelper.js
new file mode 100644
index 000000000..91495b1b7
--- /dev/null
+++ b/dom/system/gonk/tests/test_ril_worker_icc_ICCPDUHelper.js
@@ -0,0 +1,652 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+subscriptLoader.loadSubScript("resource://gre/modules/ril_consts.js", this);
+
+function run_test() {
+ run_next_test();
+}
+
+/**
+ * Verify ICCPDUHelper#readICCUCS2String()
+ */
+add_test(function test_read_icc_ucs2_string() {
+ let worker = newUint8Worker();
+ let context = worker.ContextPool._contexts[0];
+ let helper = context.GsmPDUHelper;
+ let iccHelper = context.ICCPDUHelper;
+
+ // 0x80
+ let text = "TEST";
+ helper.writeUCS2String(text);
+ // Also write two unused octets.
+ let ffLen = 2;
+ for (let i = 0; i < ffLen; i++) {
+ helper.writeHexOctet(0xff);
+ }
+ equal(iccHelper.readICCUCS2String(0x80, (2 * text.length) + ffLen), text);
+
+ // 0x81
+ let array = [0x08, 0xd2, 0x4d, 0x6f, 0x7a, 0x69, 0x6c, 0x6c, 0x61, 0xca,
+ 0xff, 0xff];
+ let len = array.length;
+ for (let i = 0; i < len; i++) {
+ helper.writeHexOctet(array[i]);
+ }
+ equal(iccHelper.readICCUCS2String(0x81, len), "Mozilla\u694a");
+
+ // 0x82
+ let array2 = [0x08, 0x69, 0x00, 0x4d, 0x6f, 0x7a, 0x69, 0x6c, 0x6c, 0x61,
+ 0xca, 0xff, 0xff];
+ let len2 = array2.length;
+ for (let i = 0; i < len2; i++) {
+ helper.writeHexOctet(array2[i]);
+ }
+ equal(iccHelper.readICCUCS2String(0x82, len2), "Mozilla\u694a");
+
+ run_next_test();
+});
+
+/**
+ * Verify ICCPDUHelper#writeICCUCS2String()
+ */
+add_test(function test_write_icc_ucs2_string() {
+ let worker = newUint8Worker();
+ let context = worker.ContextPool._contexts[0];
+ let helper = context.GsmPDUHelper;
+ let iccHelper = context.ICCPDUHelper;
+ let alphaLen = 18;
+ let test_data = [
+ {
+ encode: 0x80,
+ // string only contain one character.
+ data: "\u82b3"
+ }, {
+ encode: 0x80,
+ // 2 UCS2 character not located in the same half-page.
+ data: "Fire \u82b3\u8233"
+ }, {
+ encode: 0x80,
+ // 2 UCS2 character not located in the same half-page.
+ data: "\u694a\u704a"
+ }, {
+ encode: 0x81,
+ // 2 UCS2 character within same half-page.
+ data: "Fire \u6901\u697f"
+ }, {
+ encode: 0x81,
+ // 2 UCS2 character within same half-page.
+ data: "Fire \u6980\u69ff"
+ }, {
+ encode: 0x82,
+ // 2 UCS2 character within same half-page, but bit 8 is different.
+ data: "Fire \u0514\u0593"
+ }, {
+ encode: 0x82,
+ // 2 UCS2 character over 0x81 can encode range.
+ data: "Fire \u8000\u8001"
+ }, {
+ encode: 0x82,
+ // 2 UCS2 character over 0x81 can encode range.
+ data: "Fire \ufffd\ufffe"
+ }];
+
+ for (let i = 0; i < test_data.length; i++) {
+ let test = test_data[i];
+ let writtenStr = iccHelper.writeICCUCS2String(alphaLen, test.data);
+ equal(writtenStr, test.data);
+ equal(helper.readHexOctet(), test.encode);
+ equal(iccHelper.readICCUCS2String(test.encode, alphaLen - 1), test.data);
+ }
+
+ // This string use 0x80 encoded and the maximum capacity is 17 octets.
+ // Each alphabet takes 2 octets, thus the first 8 alphabets can be written.
+ let str = "Mozilla \u82b3\u8233 On Fire";
+ let writtenStr = iccHelper.writeICCUCS2String(alphaLen, str);
+ equal(writtenStr, str.substring(0, 8));
+ equal(helper.readHexOctet(), 0x80);
+ equal(iccHelper.readICCUCS2String(0x80, alphaLen - 1), str.substring(0, 8));
+
+ // This string use 0x81 encoded and the maximum capacity is 15 octets.
+ // Each alphabet takes 1 octets, thus the first 15 alphabets can be written.
+ str = "Mozilla \u6901\u697f On Fire";
+ writtenStr = iccHelper.writeICCUCS2String(alphaLen, str);
+ equal(writtenStr, str.substring(0, 15));
+ equal(helper.readHexOctet(), 0x81);
+ equal(iccHelper.readICCUCS2String(0x81, alphaLen - 1), str.substring(0, 15));
+
+ // This string use 0x82 encoded and the maximum capacity is 14 octets.
+ // Each alphabet takes 1 octets, thus the first 14 alphabets can be written.
+ str = "Mozilla \u0514\u0593 On Fire";
+ writtenStr = iccHelper.writeICCUCS2String(alphaLen, str);
+ equal(writtenStr, str.substring(0, 14));
+ equal(helper.readHexOctet(), 0x82);
+ equal(iccHelper.readICCUCS2String(0x82, alphaLen - 1), str.substring(0, 14));
+
+ run_next_test();
+});
+/**
+ * Verify ICCPDUHelper#readDiallingNumber
+ */
+add_test(function test_read_dialling_number() {
+ let worker = newUint8Worker();
+ let context = worker.ContextPool._contexts[0];
+ let helper = context.GsmPDUHelper;
+ let iccHelper = context.ICCPDUHelper;
+ let str = "123456789";
+
+ helper.readHexOctet = function() {
+ return 0x81;
+ };
+
+ helper.readSwappedNibbleExtendedBcdString = function(len) {
+ return str.substring(0, len);
+ };
+
+ for (let i = 0; i < str.length; i++) {
+ equal(str.substring(0, i - 1), // -1 for the TON
+ iccHelper.readDiallingNumber(i));
+ }
+
+ run_next_test();
+});
+
+/**
+ * Verify ICCPDUHelper#read8BitUnpackedToString
+ */
+add_test(function test_read_8bit_unpacked_to_string() {
+ let worker = newUint8Worker();
+ let context = worker.ContextPool._contexts[0];
+ let helper = context.GsmPDUHelper;
+ let iccHelper = context.ICCPDUHelper;
+ const langTable = PDU_NL_LOCKING_SHIFT_TABLES[PDU_NL_IDENTIFIER_DEFAULT];
+ const langShiftTable = PDU_NL_SINGLE_SHIFT_TABLES[PDU_NL_IDENTIFIER_DEFAULT];
+
+ // Test 1: Read GSM alphabets.
+ // Write alphabets before ESCAPE.
+ for (let i = 0; i < PDU_NL_EXTENDED_ESCAPE; i++) {
+ helper.writeHexOctet(i);
+ }
+
+ // Write two ESCAPEs to make it become ' '.
+ helper.writeHexOctet(PDU_NL_EXTENDED_ESCAPE);
+ helper.writeHexOctet(PDU_NL_EXTENDED_ESCAPE);
+
+ for (let i = PDU_NL_EXTENDED_ESCAPE + 1; i < langTable.length; i++) {
+ helper.writeHexOctet(i);
+ }
+
+ // Also write two unused fields.
+ let ffLen = 2;
+ for (let i = 0; i < ffLen; i++) {
+ helper.writeHexOctet(0xff);
+ }
+
+ equal(iccHelper.read8BitUnpackedToString(PDU_NL_EXTENDED_ESCAPE),
+ langTable.substring(0, PDU_NL_EXTENDED_ESCAPE));
+ equal(iccHelper.read8BitUnpackedToString(2), " ");
+ equal(iccHelper.read8BitUnpackedToString(langTable.length -
+ PDU_NL_EXTENDED_ESCAPE - 1 + ffLen),
+ langTable.substring(PDU_NL_EXTENDED_ESCAPE + 1));
+
+ // Test 2: Read GSM extended alphabets.
+ for (let i = 0; i < langShiftTable.length; i++) {
+ helper.writeHexOctet(PDU_NL_EXTENDED_ESCAPE);
+ helper.writeHexOctet(i);
+ }
+
+ // Read string before RESERVED_CONTROL.
+ equal(iccHelper.read8BitUnpackedToString(PDU_NL_RESERVED_CONTROL * 2),
+ langShiftTable.substring(0, PDU_NL_RESERVED_CONTROL));
+ // ESCAPE + RESERVED_CONTROL will become ' '.
+ equal(iccHelper.read8BitUnpackedToString(2), " ");
+ // Read string between RESERVED_CONTROL and EXTENDED_ESCAPE.
+ equal(iccHelper.read8BitUnpackedToString(
+ (PDU_NL_EXTENDED_ESCAPE - PDU_NL_RESERVED_CONTROL - 1) * 2),
+ langShiftTable.substring(PDU_NL_RESERVED_CONTROL + 1,
+ PDU_NL_EXTENDED_ESCAPE));
+ // ESCAPE + ESCAPE will become ' '.
+ equal(iccHelper.read8BitUnpackedToString(2), " ");
+ // Read remaining string.
+ equal(iccHelper.read8BitUnpackedToString(
+ (langShiftTable.length - PDU_NL_EXTENDED_ESCAPE - 1) * 2),
+ langShiftTable.substring(PDU_NL_EXTENDED_ESCAPE + 1));
+
+ run_next_test();
+});
+
+/**
+ * Verify ICCPDUHelper#writeStringTo8BitUnpacked.
+ *
+ * Test writing GSM 8 bit alphabets.
+ */
+add_test(function test_write_string_to_8bit_unpacked() {
+ let worker = newUint8Worker();
+ let context = worker.ContextPool._contexts[0];
+ let helper = context.GsmPDUHelper;
+ let iccHelper = context.ICCPDUHelper;
+ const langTable = PDU_NL_LOCKING_SHIFT_TABLES[PDU_NL_IDENTIFIER_DEFAULT];
+ const langShiftTable = PDU_NL_SINGLE_SHIFT_TABLES[PDU_NL_IDENTIFIER_DEFAULT];
+ // Length of trailing 0xff.
+ let ffLen = 2;
+ let str;
+
+ // Test 1, write GSM alphabets.
+ let writtenStr = iccHelper.writeStringTo8BitUnpacked(langTable.length + ffLen, langTable);
+ equal(writtenStr, langTable);
+
+ for (let i = 0; i < langTable.length; i++) {
+ equal(helper.readHexOctet(), i);
+ }
+
+ for (let i = 0; i < ffLen; i++) {
+ equal(helper.readHexOctet(), 0xff);
+ }
+
+ // Test 2, write GSM extended alphabets.
+ str = "\u000c\u20ac";
+ writtenStr = iccHelper.writeStringTo8BitUnpacked(4, str);
+ equal(writtenStr, str);
+ equal(iccHelper.read8BitUnpackedToString(4), str);
+
+ // Test 3, write GSM and GSM extended alphabets.
+ // \u000c, \u20ac are from gsm extended alphabets.
+ // \u00a3 is from gsm alphabet.
+ str = "\u000c\u20ac\u00a3";
+
+ // 2 octets * 2 = 4 octets for 2 gsm extended alphabets,
+ // 1 octet for 1 gsm alphabet,
+ // 2 octes for trailing 0xff.
+ // "Totally 7 octets are to be written."
+ writtenStr = iccHelper.writeStringTo8BitUnpacked(7, str);
+ equal(writtenStr, str);
+ equal(iccHelper.read8BitUnpackedToString(7), str);
+
+ run_next_test();
+});
+
+/**
+ * Verify ICCPDUHelper#writeStringTo8BitUnpacked with maximum octets written.
+ */
+add_test(function test_write_string_to_8bit_unpacked_with_max_octets_written() {
+ let worker = newUint8Worker();
+ let context = worker.ContextPool._contexts[0];
+ let helper = context.GsmPDUHelper;
+ let iccHelper = context.ICCPDUHelper;
+ const langTable = PDU_NL_LOCKING_SHIFT_TABLES[PDU_NL_IDENTIFIER_DEFAULT];
+ const langShiftTable = PDU_NL_SINGLE_SHIFT_TABLES[PDU_NL_IDENTIFIER_DEFAULT];
+
+ // The maximum of the number of octets that can be written is 3.
+ // Only 3 characters shall be written even the length of the string is 4.
+ let writtenStr = iccHelper.writeStringTo8BitUnpacked(3, langTable.substring(0, 4));
+ equal(writtenStr, langTable.substring(0, 3));
+ helper.writeHexOctet(0xff); // dummy octet.
+ for (let i = 0; i < 3; i++) {
+ equal(helper.readHexOctet(), i);
+ }
+ ok(helper.readHexOctet() != 4);
+
+ // \u000c is GSM extended alphabet, 2 octets.
+ // \u00a3 is GSM alphabet, 1 octet.
+ let str = "\u000c\u00a3";
+ writtenStr = iccHelper.writeStringTo8BitUnpacked(3, str);
+ equal(writtenStr, str.substring(0, 2));
+ equal(iccHelper.read8BitUnpackedToString(3), str);
+
+ str = "\u00a3\u000c";
+ writtenStr = iccHelper.writeStringTo8BitUnpacked(3, str);
+ equal(writtenStr, str.substring(0, 2));
+ equal(iccHelper.read8BitUnpackedToString(3), str);
+
+ // 2 GSM extended alphabets cost 4 octets, but maximum is 3, so only the 1st
+ // alphabet can be written.
+ str = "\u000c\u000c";
+ writtenStr = iccHelper.writeStringTo8BitUnpacked(3, str);
+ helper.writeHexOctet(0xff); // dummy octet.
+ equal(writtenStr, str.substring(0, 1));
+ equal(iccHelper.read8BitUnpackedToString(4), str.substring(0, 1));
+
+ run_next_test();
+});
+
+/**
+ * Verify ICCPDUHelper.readAlphaIdentifier
+ */
+add_test(function test_read_alpha_identifier() {
+ let worker = newUint8Worker();
+ let context = worker.ContextPool._contexts[0];
+ let helper = context.GsmPDUHelper;
+ let iccHelper = context.ICCPDUHelper;
+
+ // UCS2: 0x80
+ let text = "TEST";
+ helper.writeHexOctet(0x80);
+ helper.writeUCS2String(text);
+ // Also write two unused octets.
+ let ffLen = 2;
+ for (let i = 0; i < ffLen; i++) {
+ helper.writeHexOctet(0xff);
+ }
+ equal(iccHelper.readAlphaIdentifier(1 + (2 * text.length) + ffLen), text);
+
+ // UCS2: 0x81
+ let array = [0x81, 0x08, 0xd2, 0x4d, 0x6f, 0x7a, 0x69, 0x6c, 0x6c, 0x61, 0xca, 0xff, 0xff];
+ for (let i = 0; i < array.length; i++) {
+ helper.writeHexOctet(array[i]);
+ }
+ equal(iccHelper.readAlphaIdentifier(array.length), "Mozilla\u694a");
+
+ // UCS2: 0x82
+ let array2 = [0x82, 0x08, 0x69, 0x00, 0x4d, 0x6f, 0x7a, 0x69, 0x6c, 0x6c, 0x61, 0xca, 0xff, 0xff];
+ for (let i = 0; i < array2.length; i++) {
+ helper.writeHexOctet(array2[i]);
+ }
+ equal(iccHelper.readAlphaIdentifier(array2.length), "Mozilla\u694a");
+
+ // GSM 8 Bit Unpacked
+ const langTable = PDU_NL_LOCKING_SHIFT_TABLES[PDU_NL_IDENTIFIER_DEFAULT];
+ for (let i = 0; i < PDU_NL_EXTENDED_ESCAPE; i++) {
+ helper.writeHexOctet(i);
+ }
+ equal(iccHelper.readAlphaIdentifier(PDU_NL_EXTENDED_ESCAPE),
+ langTable.substring(0, PDU_NL_EXTENDED_ESCAPE));
+
+ run_next_test();
+});
+
+/**
+ * Verify ICCPDUHelper.writeAlphaIdentifier
+ */
+add_test(function test_write_alpha_identifier() {
+ let worker = newUint8Worker();
+ let context = worker.ContextPool._contexts[0];
+ let helper = context.GsmPDUHelper;
+ let iccHelper = context.ICCPDUHelper;
+ // Length of trailing 0xff.
+ let ffLen = 2;
+
+ // Removal
+ let writenAlphaId = iccHelper.writeAlphaIdentifier(10, null);
+ equal(writenAlphaId, "");
+ equal(iccHelper.readAlphaIdentifier(10), "");
+
+ // GSM 8 bit
+ let str = "Mozilla";
+ writenAlphaId = iccHelper.writeAlphaIdentifier(str.length + ffLen, str);
+ equal(writenAlphaId , str);
+ equal(iccHelper.readAlphaIdentifier(str.length + ffLen), str);
+
+ // UCS2
+ str = "Mozilla\u8000";
+ writenAlphaId = iccHelper.writeAlphaIdentifier(str.length * 2 + ffLen, str);
+ equal(writenAlphaId , str);
+ // * 2 for each character will be encoded to UCS2 alphabets.
+ equal(iccHelper.readAlphaIdentifier(str.length * 2 + ffLen), str);
+
+ // Test with maximum octets written.
+ // 1 coding scheme (0x80) and 1 UCS2 character, total 3 octets.
+ str = "\u694a";
+ writenAlphaId = iccHelper.writeAlphaIdentifier(3, str);
+ equal(writenAlphaId , str);
+ equal(iccHelper.readAlphaIdentifier(3), str);
+
+ // 1 coding scheme (0x80) and 2 UCS2 characters, total 5 octets.
+ // numOctets is limited to 4, so only 1 UCS2 character can be written.
+ str = "\u694a\u69ca";
+ writenAlphaId = iccHelper.writeAlphaIdentifier(4, str);
+ helper.writeHexOctet(0xff); // dummy octet.
+ equal(writenAlphaId , str.substring(0, 1));
+ equal(iccHelper.readAlphaIdentifier(5), str.substring(0, 1));
+
+ // Write 0 octet.
+ writenAlphaId = iccHelper.writeAlphaIdentifier(0, "1");
+ helper.writeHexOctet(0xff); // dummy octet.
+ equal(writenAlphaId, "");
+ equal(iccHelper.readAlphaIdentifier(1), "");
+
+ run_next_test();
+});
+
+/**
+ * Verify ICCPDUHelper.readAlphaIdDiallingNumber
+ */
+add_test(function test_read_alpha_id_dialling_number() {
+ let worker = newUint8Worker();
+ let context = worker.ContextPool._contexts[0];
+ let helper = context.GsmPDUHelper;
+ let iccHelper = context.ICCPDUHelper;
+ let buf = context.Buf;
+ const recordSize = 32;
+
+ function testReadAlphaIdDiallingNumber(contact) {
+ iccHelper.readAlphaIdentifier = function() {
+ return contact.alphaId;
+ };
+
+ iccHelper.readNumberWithLength = function() {
+ return contact.number;
+ };
+
+ let strLen = recordSize * 2;
+ buf.writeInt32(strLen); // fake length
+ helper.writeHexOctet(0xff); // fake CCP
+ helper.writeHexOctet(0xff); // fake EXT1
+ buf.writeStringDelimiter(strLen);
+
+ let contactR = iccHelper.readAlphaIdDiallingNumber(recordSize);
+ if (contact.alphaId == "" && contact.number == "") {
+ equal(contactR, null);
+ } else {
+ equal(contactR.alphaId, contact.alphaId);
+ equal(contactR.number, contact.number);
+ }
+ }
+
+ testReadAlphaIdDiallingNumber({alphaId: "AlphaId", number: "0987654321"});
+ testReadAlphaIdDiallingNumber({alphaId: "", number: ""});
+
+ run_next_test();
+});
+
+/**
+ * Verify ICCPDUHelper.writeAlphaIdDiallingNumber
+ */
+add_test(function test_write_alpha_id_dialling_number() {
+ let worker = newUint8Worker();
+ let context = worker.ContextPool._contexts[0];
+ let helper = context.ICCPDUHelper;
+ const recordSize = 32;
+
+ // Write a normal contact.
+ let contactW = {
+ alphaId: "Mozilla",
+ number: "1234567890"
+ };
+
+ let writtenContact = helper.writeAlphaIdDiallingNumber(recordSize,
+ contactW.alphaId,
+ contactW.number, 0xff);
+
+ let contactR = helper.readAlphaIdDiallingNumber(recordSize);
+ equal(writtenContact.alphaId, contactR.alphaId);
+ equal(writtenContact.number, contactR.number);
+ equal(0xff, contactR.extRecordNumber);
+
+ // Write a contact with alphaId encoded in UCS2 and number has '+'.
+ let contactUCS2 = {
+ alphaId: "火狐",
+ number: "+1234567890"
+ };
+
+ writtenContact = helper.writeAlphaIdDiallingNumber(recordSize,
+ contactUCS2.alphaId,
+ contactUCS2.number, 0xff);
+ contactR = helper.readAlphaIdDiallingNumber(recordSize);
+ equal(writtenContact.alphaId, contactR.alphaId);
+ equal(writtenContact.number, contactR.number);
+ equal(0xff, contactR.extRecordNumber);
+
+ // Write a null contact (Removal).
+ writtenContact = helper.writeAlphaIdDiallingNumber(recordSize);
+ contactR = helper.readAlphaIdDiallingNumber(recordSize);
+ equal(contactR, null);
+ equal(writtenContact.alphaId, "");
+ equal(writtenContact.number, "");
+
+ // Write a longer alphaId/dialling number
+ // Dialling Number : Maximum 20 digits(10 octets).
+ // Alpha Identifier: 32(recordSize) - 14 (10 octets for Dialling Number, 1
+ // octet for TON/NPI, 1 for number length octet, and 2 for
+ // Ext) = Maximum 18 octets.
+ let longContact = {
+ alphaId: "AAAAAAAAABBBBBBBBBCCCCCCCCC",
+ number: "123456789012345678901234567890",
+ };
+
+ writtenContact = helper.writeAlphaIdDiallingNumber(recordSize,
+ longContact.alphaId,
+ longContact.number, 0xff);
+ contactR = helper.readAlphaIdDiallingNumber(recordSize);
+ equal(writtenContact.alphaId, contactR.alphaId);
+ equal(writtenContact.number, contactR.number);
+ equal(0xff, contactR.extRecordNumber);
+
+ // Add '+' to number and test again.
+ longContact.number = "+123456789012345678901234567890";
+ writtenContact = helper.writeAlphaIdDiallingNumber(recordSize,
+ longContact.alphaId,
+ longContact.number, 0xff);
+ contactR = helper.readAlphaIdDiallingNumber(recordSize);
+ equal(writtenContact.alphaId, contactR.alphaId);
+ equal(writtenContact.number, contactR.number);
+ equal(0xff, contactR.extRecordNumber);
+
+ run_next_test();
+});
+
+/**
+ * Verify ICCPDUHelper.writeDiallingNumber
+ */
+add_test(function test_write_dialling_number() {
+ let worker = newUint8Worker();
+ let context = worker.ContextPool._contexts[0];
+ let helper = context.ICCPDUHelper;
+
+ // with +
+ let number = "+123456";
+ let len = 4;
+ helper.writeDiallingNumber(number);
+ equal(helper.readDiallingNumber(len), number);
+
+ // without +
+ number = "987654";
+ len = 4;
+ helper.writeDiallingNumber(number);
+ equal(helper.readDiallingNumber(len), number);
+
+ number = "9876543";
+ len = 5;
+ helper.writeDiallingNumber(number);
+ equal(helper.readDiallingNumber(len), number);
+
+ run_next_test();
+});
+
+/**
+ * Verify ICCPDUHelper.readNumberWithLength
+ */
+add_test(function test_read_number_with_length() {
+ let worker = newUint8Worker();
+ let context = worker.ContextPool._contexts[0];
+ let helper = context.GsmPDUHelper;
+ let iccHelper = context.ICCPDUHelper;
+
+ let numbers = [
+ {
+ number: "123456789",
+ expectedNumber: "123456789"
+ },
+ {
+ number: "",
+ expectedNumber: ""
+ },
+ // Invalid length of BCD number/SSC contents
+ {
+ number: "12345678901234567890123",
+ expectedNumber: ""
+ },
+ ];
+
+ // To avoid obtaining wrong buffer content.
+ context.Buf.seekIncoming = function(offset) {
+ };
+
+ function do_test(aNumber, aExpectedNumber) {
+ iccHelper.readDiallingNumber = function(numLen) {
+ return aNumber.substring(0, numLen);
+ };
+
+ if (aNumber) {
+ helper.writeHexOctet(aNumber.length + 1);
+ } else {
+ helper.writeHexOctet(0xff);
+ }
+
+ equal(iccHelper.readNumberWithLength(), aExpectedNumber);
+ }
+
+ for (let i = 0; i < numbers.length; i++) {
+ do_test(numbers[i].number, numbers[i].expectedNumber);
+ }
+
+ run_next_test();
+});
+
+/**
+ * Verify ICCPDUHelper.writeNumberWithLength
+ */
+add_test(function test_write_number_with_length() {
+ let worker = newUint8Worker();
+ let context = worker.ContextPool._contexts[0];
+ let helper = context.GsmPDUHelper;
+ let iccHelper = context.ICCPDUHelper;
+
+ function test(number, expectedNumber) {
+ expectedNumber = expectedNumber || number;
+ let writeNumber = iccHelper.writeNumberWithLength(number);
+ equal(writeNumber, expectedNumber);
+ let numLen = helper.readHexOctet();
+ equal(expectedNumber, iccHelper.readDiallingNumber(numLen));
+ for (let i = 0; i < (ADN_MAX_BCD_NUMBER_BYTES - numLen); i++) {
+ equal(0xff, helper.readHexOctet());
+ }
+ }
+
+ // without +
+ test("123456789");
+
+ // with +
+ test("+987654321");
+
+ // extended BCD coding
+ test("1*2#3,4*5#6,");
+
+ // with + and extended BCD coding
+ test("+1*2#3,4*5#6,");
+
+ // non-supported characters should not be written.
+ test("(1)23-456+789", "123456789");
+
+ test("++(01)2*3-4#5,6+7(8)9*0#1,", "+012*34#5,6789*0#1,");
+
+ // over maximum 20 digits should be truncated.
+ test("012345678901234567890123456789", "01234567890123456789");
+
+ // null
+ iccHelper.writeNumberWithLength(null);
+ for (let i = 0; i < (ADN_MAX_BCD_NUMBER_BYTES + 1); i++) {
+ equal(0xff, helper.readHexOctet());
+ }
+
+ run_next_test();
+});
diff --git a/dom/system/gonk/tests/test_ril_worker_icc_ICCRecordHelper.js b/dom/system/gonk/tests/test_ril_worker_icc_ICCRecordHelper.js
new file mode 100644
index 000000000..00c55873d
--- /dev/null
+++ b/dom/system/gonk/tests/test_ril_worker_icc_ICCRecordHelper.js
@@ -0,0 +1,1080 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+subscriptLoader.loadSubScript("resource://gre/modules/ril_consts.js", this);
+
+function run_test() {
+ run_next_test();
+}
+
+/**
+ * Verify ICCRecordHelper.readPBR
+ */
+add_test(function test_read_pbr() {
+ let worker = newUint8Worker();
+ let context = worker.ContextPool._contexts[0];
+ let helper = context.GsmPDUHelper;
+ let record = context.ICCRecordHelper;
+ let buf = context.Buf;
+ let io = context.ICCIOHelper;
+
+ io.loadLinearFixedEF = function fakeLoadLinearFixedEF(options) {
+ let pbr_1 = [
+ 0xa8, 0x05, 0xc0, 0x03, 0x4f, 0x3a, 0x01
+ ];
+
+ // Write data size
+ buf.writeInt32(pbr_1.length * 2);
+
+ // Write pbr
+ for (let i = 0; i < pbr_1.length; i++) {
+ helper.writeHexOctet(pbr_1[i]);
+ }
+
+ // Write string delimiter
+ buf.writeStringDelimiter(pbr_1.length * 2);
+
+ options.totalRecords = 2;
+ if (options.callback) {
+ options.callback(options);
+ }
+ };
+
+ io.loadNextRecord = function fakeLoadNextRecord(options) {
+ let pbr_2 = [
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff
+ ];
+
+ options.p1++;
+ if (options.callback) {
+ options.callback(options);
+ }
+ };
+
+ let successCb = function successCb(pbrs) {
+ equal(pbrs[0].adn.fileId, 0x4f3a);
+ equal(pbrs.length, 1);
+ };
+
+ let errorCb = function errorCb(errorMsg) {
+ do_print("Reading EF_PBR failed, msg = " + errorMsg);
+ ok(false);
+ };
+
+ record.readPBR(successCb, errorCb);
+
+ // Check cache pbrs when 2nd call
+ let ifLoadEF = false;
+ io.loadLinearFixedEF = function fakeLoadLinearFixedEF(options) {
+ ifLoadEF = true;
+ }
+ record.readPBR(successCb, errorCb);
+ ok(!ifLoadEF);
+
+ run_next_test();
+});
+
+/**
+ * Verify ICCRecordHelper.readEmail
+ */
+add_test(function test_read_email() {
+ let worker = newUint8Worker();
+ let context = worker.ContextPool._contexts[0];
+ let helper = context.GsmPDUHelper;
+ let record = context.ICCRecordHelper;
+ let buf = context.Buf;
+ let io = context.ICCIOHelper;
+ let recordSize;
+
+ io.loadLinearFixedEF = function fakeLoadLinearFixedEF(options) {
+ let email_1 = [
+ 0x65, 0x6D, 0x61, 0x69, 0x6C,
+ 0x00, 0x6D, 0x6F, 0x7A, 0x69,
+ 0x6C, 0x6C, 0x61, 0x2E, 0x63,
+ 0x6F, 0x6D, 0x02, 0x23];
+
+ // Write data size
+ buf.writeInt32(email_1.length * 2);
+
+ // Write email
+ for (let i = 0; i < email_1.length; i++) {
+ helper.writeHexOctet(email_1[i]);
+ }
+
+ // Write string delimiter
+ buf.writeStringDelimiter(email_1.length * 2);
+
+ recordSize = email_1.length;
+ options.recordSize = recordSize;
+ if (options.callback) {
+ options.callback(options);
+ }
+ };
+
+ function doTestReadEmail(type, expectedResult) {
+ let fileId = 0x6a75;
+ let recordNumber = 1;
+
+ // fileId and recordNumber are dummy arguments.
+ record.readEmail(fileId, type, recordNumber, function(email) {
+ equal(email, expectedResult);
+ });
+ };
+
+ doTestReadEmail(ICC_USIM_TYPE1_TAG, "email@mozilla.com$#");
+ doTestReadEmail(ICC_USIM_TYPE2_TAG, "email@mozilla.com");
+ equal(record._emailRecordSize, recordSize);
+
+ run_next_test();
+});
+
+/**
+ * Verify ICCRecordHelper.updateEmail
+ */
+add_test(function test_update_email() {
+ const recordSize = 0x20;
+ const recordNumber = 1;
+ const fileId = 0x4f50;
+ const NUM_TESTS = 2;
+ let worker = newUint8Worker();
+ let context = worker.ContextPool._contexts[0];
+ let pduHelper = context.GsmPDUHelper;
+ let iccHelper = context.ICCPDUHelper;
+ let ril = context.RIL;
+ ril.appType = CARD_APPTYPE_USIM;
+ let recordHelper = context.ICCRecordHelper;
+ let buf = context.Buf;
+ let ioHelper = context.ICCIOHelper;
+ let pbr = {email: {fileId: fileId, fileType: ICC_USIM_TYPE1_TAG},
+ adn: {sfi: 1}};
+ let count = 0;
+
+ // Override.
+ ioHelper.updateLinearFixedEF = function(options) {
+ options.pathId = context.ICCFileHelper.getEFPath(options.fileId);
+ options.command = ICC_COMMAND_UPDATE_RECORD;
+ options.p1 = options.recordNumber;
+ options.p2 = READ_RECORD_ABSOLUTE_MODE;
+ options.p3 = recordSize;
+ ril.iccIO(options);
+ };
+
+ function do_test(pbr, expectedEmail, expectedAdnRecordId) {
+ buf.sendParcel = function() {
+ count++;
+
+ // Request Type.
+ equal(this.readInt32(), REQUEST_SIM_IO);
+
+ // Token : we don't care
+ this.readInt32();
+
+ // command.
+ equal(this.readInt32(), ICC_COMMAND_UPDATE_RECORD);
+
+ // fileId.
+ equal(this.readInt32(), fileId);
+
+ // pathId.
+ equal(this.readString(),
+ EF_PATH_MF_SIM + EF_PATH_DF_TELECOM + EF_PATH_DF_PHONEBOOK);
+
+ // p1.
+ equal(this.readInt32(), recordNumber);
+
+ // p2.
+ equal(this.readInt32(), READ_RECORD_ABSOLUTE_MODE);
+
+ // p3.
+ equal(this.readInt32(), recordSize);
+
+ // data.
+ let strLen = this.readInt32();
+ let email;
+ if (pbr.email.fileType === ICC_USIM_TYPE1_TAG) {
+ email = iccHelper.read8BitUnpackedToString(recordSize);
+ } else {
+ email = iccHelper.read8BitUnpackedToString(recordSize - 2);
+ equal(pduHelper.readHexOctet(), pbr.adn.sfi);
+ equal(pduHelper.readHexOctet(), expectedAdnRecordId);
+ }
+ this.readStringDelimiter(strLen);
+ equal(email, expectedEmail);
+
+ // pin2.
+ equal(this.readString(), null);
+
+ // AID. Ignore because it's from modem.
+ this.readInt32();
+
+ if (count == NUM_TESTS) {
+ run_next_test();
+ }
+ };
+ recordHelper.updateEmail(pbr, recordNumber, expectedEmail, expectedAdnRecordId);
+ }
+
+ do_test(pbr, "test@mail.com");
+ pbr.email.fileType = ICC_USIM_TYPE2_TAG;
+ do_test(pbr, "test@mail.com", 1);
+});
+
+/**
+ * Verify ICCRecordHelper.readANR
+ */
+add_test(function test_read_anr() {
+ let worker = newUint8Worker();
+ let context = worker.ContextPool._contexts[0];
+ let helper = context.GsmPDUHelper;
+ let record = context.ICCRecordHelper;
+ let buf = context.Buf;
+ let io = context.ICCIOHelper;
+ let recordSize;
+
+ io.loadLinearFixedEF = function fakeLoadLinearFixedEF(options) {
+ let anr_1 = [
+ 0x01, 0x05, 0x81, 0x10, 0x32,
+ 0x54, 0xF6, 0xFF, 0xFF];
+
+ // Write data size
+ buf.writeInt32(anr_1.length * 2);
+
+ // Write anr
+ for (let i = 0; i < anr_1.length; i++) {
+ helper.writeHexOctet(anr_1[i]);
+ }
+
+ // Write string delimiter
+ buf.writeStringDelimiter(anr_1.length * 2);
+
+ recordSize = anr_1.length;
+ options.recordSize = recordSize;
+ if (options.callback) {
+ options.callback(options);
+ }
+ };
+
+ function doTestReadAnr(fileType, expectedResult) {
+ let fileId = 0x4f11;
+ let recordNumber = 1;
+
+ // fileId and recordNumber are dummy arguments.
+ record.readANR(fileId, fileType, recordNumber, function(anr) {
+ equal(anr, expectedResult);
+ });
+ };
+
+ doTestReadAnr(ICC_USIM_TYPE1_TAG, "0123456");
+ equal(record._anrRecordSize, recordSize);
+
+ run_next_test();
+});
+
+/**
+ * Verify ICCRecordHelper.updateANR
+ */
+add_test(function test_update_anr() {
+ const recordSize = 0x20;
+ const recordNumber = 1;
+ const fileId = 0x4f11;
+ const NUM_TESTS = 2;
+ let worker = newUint8Worker();
+ let context = worker.ContextPool._contexts[0];
+ let pduHelper = context.GsmPDUHelper;
+ let iccHelper = context.ICCPDUHelper;
+ let ril = context.RIL;
+ ril.appType = CARD_APPTYPE_USIM;
+ let recordHelper = context.ICCRecordHelper;
+ let buf = context.Buf;
+ let ioHelper = context.ICCIOHelper;
+ let pbr = {anr0: {fileId: fileId, fileType: ICC_USIM_TYPE1_TAG},
+ adn: {sfi: 1}};
+ let count = 0;
+
+ // Override.
+ ioHelper.updateLinearFixedEF = function(options) {
+ options.pathId = context.ICCFileHelper.getEFPath(options.fileId);
+ options.command = ICC_COMMAND_UPDATE_RECORD;
+ options.p1 = options.recordNumber;
+ options.p2 = READ_RECORD_ABSOLUTE_MODE;
+ options.p3 = recordSize;
+ ril.iccIO(options);
+ };
+
+ function do_test(pbr, expectedANR, expectedAdnRecordId) {
+ buf.sendParcel = function() {
+ count++;
+
+ // Request Type.
+ equal(this.readInt32(), REQUEST_SIM_IO);
+
+ // Token : we don't care
+ this.readInt32();
+
+ // command.
+ equal(this.readInt32(), ICC_COMMAND_UPDATE_RECORD);
+
+ // fileId.
+ equal(this.readInt32(), fileId);
+
+ // pathId.
+ equal(this.readString(),
+ EF_PATH_MF_SIM + EF_PATH_DF_TELECOM + EF_PATH_DF_PHONEBOOK);
+
+ // p1.
+ equal(this.readInt32(), recordNumber);
+
+ // p2.
+ equal(this.readInt32(), READ_RECORD_ABSOLUTE_MODE);
+
+ // p3.
+ equal(this.readInt32(), recordSize);
+
+ // data.
+ let strLen = this.readInt32();
+ // EF_AAS, ignore.
+ pduHelper.readHexOctet();
+ equal(iccHelper.readNumberWithLength(), expectedANR);
+ // EF_CCP, ignore.
+ pduHelper.readHexOctet();
+ // EF_EXT1, ignore.
+ pduHelper.readHexOctet();
+ if (pbr.anr0.fileType === ICC_USIM_TYPE2_TAG) {
+ equal(pduHelper.readHexOctet(), pbr.adn.sfi);
+ equal(pduHelper.readHexOctet(), expectedAdnRecordId);
+ }
+ this.readStringDelimiter(strLen);
+
+ // pin2.
+ equal(this.readString(), null);
+
+ // AID. Ignore because it's from modem.
+ this.readInt32();
+
+ if (count == NUM_TESTS) {
+ run_next_test();
+ }
+ };
+ recordHelper.updateANR(pbr, recordNumber, expectedANR, expectedAdnRecordId);
+ }
+
+ do_test(pbr, "+123456789");
+ pbr.anr0.fileType = ICC_USIM_TYPE2_TAG;
+ do_test(pbr, "123456789", 1);
+});
+
+/**
+ * Verify ICCRecordHelper.readIAP
+ */
+add_test(function test_read_iap() {
+ let worker = newUint8Worker();
+ let context = worker.ContextPool._contexts[0];
+ let helper = context.GsmPDUHelper;
+ let record = context.ICCRecordHelper;
+ let buf = context.Buf;
+ let io = context.ICCIOHelper;
+ let recordSize;
+
+ io.loadLinearFixedEF = function fakeLoadLinearFixedEF(options) {
+ let iap_1 = [0x01, 0x02];
+
+ // Write data size/
+ buf.writeInt32(iap_1.length * 2);
+
+ // Write iap.
+ for (let i = 0; i < iap_1.length; i++) {
+ helper.writeHexOctet(iap_1[i]);
+ }
+
+ // Write string delimiter.
+ buf.writeStringDelimiter(iap_1.length * 2);
+
+ recordSize = iap_1.length;
+ options.recordSize = recordSize;
+ if (options.callback) {
+ options.callback(options);
+ }
+ };
+
+ function doTestReadIAP(expectedIAP) {
+ const fileId = 0x4f17;
+ const recordNumber = 1;
+
+ let successCb = function successCb(iap) {
+ for (let i = 0; i < iap.length; i++) {
+ equal(expectedIAP[i], iap[i]);
+ }
+ run_next_test();
+ }.bind(this);
+
+ let errorCb = function errorCb(errorMsg) {
+ do_print(errorMsg);
+ ok(false);
+ run_next_test();
+ }.bind(this);
+
+ record.readIAP(fileId, recordNumber, successCb, errorCb);
+ };
+
+ doTestReadIAP([1, 2]);
+});
+
+/**
+ * Verify ICCRecordHelper.updateIAP
+ */
+add_test(function test_update_iap() {
+ const recordSize = 2;
+ const recordNumber = 1;
+ const fileId = 0x4f17;
+ let worker = newUint8Worker();
+ let context = worker.ContextPool._contexts[0];
+ let pduHelper = context.GsmPDUHelper;
+ let ril = context.RIL;
+ ril.appType = CARD_APPTYPE_USIM;
+ let recordHelper = context.ICCRecordHelper;
+ let buf = context.Buf;
+ let ioHelper = context.ICCIOHelper;
+ let count = 0;
+
+ // Override.
+ ioHelper.updateLinearFixedEF = function(options) {
+ options.pathId = context.ICCFileHelper.getEFPath(options.fileId);
+ options.command = ICC_COMMAND_UPDATE_RECORD;
+ options.p1 = options.recordNumber;
+ options.p2 = READ_RECORD_ABSOLUTE_MODE;
+ options.p3 = recordSize;
+ ril.iccIO(options);
+ };
+
+ function do_test(expectedIAP) {
+ buf.sendParcel = function() {
+ // Request Type.
+ equal(this.readInt32(), REQUEST_SIM_IO);
+
+ // Token : we don't care
+ this.readInt32();
+
+ // command.
+ equal(this.readInt32(), ICC_COMMAND_UPDATE_RECORD);
+
+ // fileId.
+ equal(this.readInt32(), fileId);
+
+ // pathId.
+ equal(this.readString(),
+ EF_PATH_MF_SIM + EF_PATH_DF_TELECOM + EF_PATH_DF_PHONEBOOK);
+
+ // p1.
+ equal(this.readInt32(), recordNumber);
+
+ // p2.
+ equal(this.readInt32(), READ_RECORD_ABSOLUTE_MODE);
+
+ // p3.
+ equal(this.readInt32(), recordSize);
+
+ // data.
+ let strLen = this.readInt32();
+ for (let i = 0; i < recordSize; i++) {
+ equal(expectedIAP[i], pduHelper.readHexOctet());
+ }
+ this.readStringDelimiter(strLen);
+
+ // pin2.
+ equal(this.readString(), null);
+
+ // AID. Ignore because it's from modem.
+ this.readInt32();
+
+ run_next_test();
+ };
+ recordHelper.updateIAP(fileId, recordNumber, expectedIAP);
+ }
+
+ do_test([1, 2]);
+});
+
+/**
+ * Verify ICCRecordHelper.readADNLike.
+ */
+add_test(function test_read_adn_like() {
+ const RECORD_SIZE = 0x20;
+
+ let worker = newUint8Worker();
+ let context = worker.ContextPool._contexts[0];
+ let helper = context.GsmPDUHelper;
+ let record = context.ICCRecordHelper;
+ let buf = context.Buf;
+ let io = context.ICCIOHelper;
+ let ril = context.RIL;
+
+ function do_test(extFileId, rawEF, expectedExtRecordNumber, expectedNumber) {
+ io.loadLinearFixedEF = function fakeLoadLinearFixedEF(options) {
+ // Write data size
+ buf.writeInt32(rawEF.length * 2);
+
+ // Write adn
+ for (let i = 0; i < rawEF.length; i += 2) {
+ helper.writeHexOctet(parseInt(rawEF.substr(i, 2), 16));
+ }
+
+ // Write string delimiter
+ buf.writeStringDelimiter(rawEF.length * 2);
+
+ options.p1 = 1;
+ options.recordSize = RECORD_SIZE;
+ options.totalRecords = 1;
+ if (options.callback) {
+ options.callback(options);
+ }
+ };
+
+ record.readExtension = function(fileId, recordNumber, onsuccess, onerror) {
+ onsuccess("1234");
+ }
+
+ let successCb = function successCb(contacts) {
+ ok(contacts[0].number == expectedNumber);
+ };
+
+ let errorCb = function errorCb(errorMsg) {
+ do_print("Reading ADNLike failed, msg = " + errorMsg);
+ ok(false);
+ };
+
+ record.readADNLike(ICC_EF_ADN, extFileId, successCb, errorCb);
+ }
+
+ ril.appType = CARD_APPTYPE_SIM;
+ // Valid extension
+ do_test(ICC_EF_EXT1,"436f6e74616374303031ffffffffffffffff0b8199887766554433221100ff01",
+ 0x01,"998877665544332211001234");
+ // Empty extension
+ do_test(ICC_EF_EXT1,"436f6e74616374303031ffffffffffffffff0b8199887766554433221100ffff",
+ 0xff, "99887766554433221100");
+ // Unsupport extension
+ do_test(null,"436f6e74616374303031ffffffffffffffff0b8199887766554433221100ffff",
+ 0xff, "99887766554433221100");
+ // Empty dialling number contact
+ do_test(null,"436f6e74616374303031ffffffffffffffffffffffffffffffffffffffffffff",
+ 0xff, "");
+
+ run_next_test();
+});
+
+/**
+ * Verify ICCRecordHelper.updateADNLike.
+ */
+add_test(function test_update_adn_like() {
+ let worker = newUint8Worker();
+ let context = worker.ContextPool._contexts[0];
+ let ril = context.RIL;
+ let record = context.ICCRecordHelper;
+ let io = context.ICCIOHelper;
+ let pdu = context.ICCPDUHelper;
+ let buf = context.Buf;
+
+ ril.appType = CARD_APPTYPE_SIM;
+ const recordSize = 0x20;
+ let fileId;
+
+ // Override.
+ io.updateLinearFixedEF = function(options) {
+ options.pathId = context.ICCFileHelper.getEFPath(options.fileId);
+ options.command = ICC_COMMAND_UPDATE_RECORD;
+ options.p1 = options.recordNumber;
+ options.p2 = READ_RECORD_ABSOLUTE_MODE;
+ options.p3 = recordSize;
+ ril.iccIO(options);
+ };
+
+ buf.sendParcel = function() {
+ // Request Type.
+ equal(this.readInt32(), REQUEST_SIM_IO);
+
+ // Token : we don't care
+ this.readInt32();
+
+ // command.
+ equal(this.readInt32(), ICC_COMMAND_UPDATE_RECORD);
+
+ // fileId.
+ equal(this.readInt32(), fileId);
+
+ // pathId.
+ equal(this.readString(), EF_PATH_MF_SIM + EF_PATH_DF_TELECOM);
+
+ // p1.
+ equal(this.readInt32(), 1);
+
+ // p2.
+ equal(this.readInt32(), READ_RECORD_ABSOLUTE_MODE);
+
+ // p3.
+ equal(this.readInt32(), 0x20);
+
+ // data.
+ let contact = pdu.readAlphaIdDiallingNumber(0x20);
+ equal(contact.alphaId, "test");
+ equal(contact.number, "123456");
+ equal(contact.extRecordNumber, "0xff");
+
+ // pin2.
+ if (fileId == ICC_EF_ADN) {
+ equal(this.readString(), null);
+ } else {
+ equal(this.readString(), "1111");
+ }
+
+ // AID. Ignore because it's from modem.
+ this.readInt32();
+
+ if (fileId == ICC_EF_FDN) {
+ run_next_test();
+ }
+ };
+
+ fileId = ICC_EF_ADN;
+ record.updateADNLike(fileId, 0xff,
+ {recordId: 1, alphaId: "test", number: "123456"});
+
+ fileId = ICC_EF_FDN;
+ record.updateADNLike(fileId, 0xff,
+ {recordId: 1, alphaId: "test", number: "123456"},
+ "1111");
+});
+
+/**
+ * Verify ICCRecordHelper.findFreeRecordId.
+ */
+add_test(function test_find_free_record_id() {
+ let worker = newUint8Worker();
+ let context = worker.ContextPool._contexts[0];
+ let pduHelper = context.GsmPDUHelper;
+ let recordHelper = context.ICCRecordHelper;
+ let buf = context.Buf;
+ let io = context.ICCIOHelper;
+ let ril = context.RIL;
+
+ function writeRecord(record) {
+ // Write data size
+ buf.writeInt32(record.length * 2);
+
+ for (let i = 0; i < record.length; i++) {
+ pduHelper.writeHexOctet(record[i]);
+ }
+
+ // Write string delimiter
+ buf.writeStringDelimiter(record.length * 2);
+ }
+
+ io.loadLinearFixedEF = function fakeLoadLinearFixedEF(options) {
+ // Some random data.
+ let record = [0x12, 0x34, 0x56, 0x78, 0x90];
+ options.p1 = 1;
+ options.totalRecords = 2;
+ writeRecord(record);
+ if (options.callback) {
+ options.callback(options);
+ }
+ };
+
+ ril.iccIO = function fakeIccIO(options) {
+ // Unused bytes.
+ let record = [0xff, 0xff, 0xff, 0xff, 0xff];
+ writeRecord(record);
+ if (options.callback) {
+ options.callback(options);
+ }
+ };
+
+ let fileId = 0x0000; // Dummy.
+ recordHelper.findFreeRecordId(
+ fileId,
+ function(recordId) {
+ equal(recordId, 2);
+ run_next_test();
+ }.bind(this),
+ function(errorMsg) {
+ do_print(errorMsg);
+ ok(false);
+ run_next_test();
+ }.bind(this));
+});
+
+/**
+ * Verify ICCRecordHelper.fetchICCRecords.
+ */
+add_test(function test_fetch_icc_recodes() {
+ let worker = newWorker();
+ let context = worker.ContextPool._contexts[0];
+ let RIL = context.RIL;
+ let iccRecord = context.ICCRecordHelper;
+ let simRecord = context.SimRecordHelper;
+ let ruimRecord = context.RuimRecordHelper;
+ let fetchTag = 0x00;
+
+ simRecord.fetchSimRecords = function() {
+ fetchTag = 0x01;
+ };
+
+ ruimRecord.fetchRuimRecords = function() {
+ fetchTag = 0x02;
+ };
+
+ RIL.appType = CARD_APPTYPE_SIM;
+ iccRecord.fetchICCRecords();
+ equal(fetchTag, 0x01);
+
+ RIL.appType = CARD_APPTYPE_RUIM;
+ iccRecord.fetchICCRecords();
+ equal(fetchTag, 0x02);
+
+ RIL.appType = CARD_APPTYPE_USIM;
+ iccRecord.fetchICCRecords();
+ equal(fetchTag, 0x01);
+
+ run_next_test();
+});
+
+/**
+ * Verify reading EF_ICCID.
+ */
+add_test(function test_handling_iccid() {
+ let worker = newUint8Worker();
+ let context = worker.ContextPool._contexts[0];
+ let record = context.ICCRecordHelper;
+ let helper = context.GsmPDUHelper;
+ let ril = context.RIL;
+ let buf = context.Buf;
+ let io = context.ICCIOHelper;
+
+ ril.reportStkServiceIsRunning = function fakeReportStkServiceIsRunning() {
+ };
+
+ function do_test(rawICCID, expectedICCID) {
+ io.loadTransparentEF = function fakeLoadTransparentEF(options) {
+ // Write data size
+ buf.writeInt32(rawICCID.length);
+
+ // Write data
+ for (let i = 0; i < rawICCID.length; i += 2) {
+ helper.writeHexOctet(parseInt(rawICCID.substr(i, 2), 16));
+ }
+
+ // Write string delimiter
+ buf.writeStringDelimiter(rawICCID.length);
+
+ if (options.callback) {
+ options.callback(options);
+ }
+ };
+
+ record.readICCID();
+
+ equal(ril.iccInfo.iccid, expectedICCID);
+ }
+
+ // Invalid value 0xE at high nibbile + low nibbile contains 0xF.
+ do_test("9868002E90909F001519", "89860020909");
+ // Invalid value 0xD at low nibbile.
+ do_test("986800D2909090001519", "8986002090909005191");
+ // Invalid value 0xC at low nibbile.
+ do_test("986800C2909090001519", "8986002090909005191");
+ // Invalid value 0xB at low nibbile.
+ do_test("986800B2909090001519", "8986002090909005191");
+ // Invalid value 0xA at low nibbile.
+ do_test("986800A2909090001519", "8986002090909005191");
+ // Valid ICCID.
+ do_test("98101430121181157002", "89014103211118510720");
+
+ run_next_test();
+});
+
+/**
+ * Verify ICCRecordHelper.readExtension
+ */
+add_test(function test_read_extension() {
+ let worker = newUint8Worker();
+ let context = worker.ContextPool._contexts[0];
+ let helper = context.GsmPDUHelper;
+ let record = context.ICCRecordHelper;
+ let buf = context.Buf;
+ let io = context.ICCIOHelper;
+
+ function do_test(rawExtension, expectedExtensionNumber) {
+ io.loadLinearFixedEF = function fakeLoadLinearFixedEF(options) {
+ // Write data size
+ buf.writeInt32(rawExtension.length * 2);
+
+ // Write ext
+ for (let i = 0; i < rawExtension.length; i += 2) {
+ helper.writeHexOctet(parseInt(rawExtension.substr(i, 2), 16));
+ }
+
+ // Write string delimiter
+ buf.writeStringDelimiter(rawExtension.length);
+
+ if (options.callback) {
+ options.callback(options);
+ }
+ };
+
+ let successCb = function successCb(number) {
+ do_print("extension number:" + number);
+ equal(number, expectedExtensionNumber);
+ };
+
+ let errorCb = function errorCb() {
+ ok(expectedExtensionNumber == null);
+ };
+
+ record.readExtension(0x6f4a, 1, successCb, errorCb);
+ }
+
+ // Test unsupported record type 0x01
+ do_test("010a10325476981032547698ff", "");
+ // Test invalid length 0xc1
+ do_test("020c10325476981032547698ff", null);
+ // Test extension chain which we don't support
+ do_test("020a1032547698103254769802", "01234567890123456789");
+ // Test valid Extension
+ do_test("020a10325476981032547698ff", "01234567890123456789");
+ // Test valid Extension
+ do_test("0209103254769810325476ffff", "012345678901234567");
+ // Test empty Extension
+ do_test("02ffffffffffffffffffffffff", "");
+
+ run_next_test();
+});
+
+/**
+ * Verify ICCRecordHelper.updateExtension
+ */
+add_test(function test_update_extension() {
+ const RECORD_SIZE = 13;
+ const RECORD_NUMBER = 1;
+
+ let worker = newUint8Worker();
+ let context = worker.ContextPool._contexts[0];
+ let pduHelper = context.GsmPDUHelper;
+ let ril = context.RIL;
+ let recordHelper = context.ICCRecordHelper;
+ let buf = context.Buf;
+ let ioHelper = context.ICCIOHelper;
+
+ // Override.
+ ioHelper.updateLinearFixedEF = function(options) {
+ options.pathId = context.ICCFileHelper.getEFPath(options.fileId);
+ options.command = ICC_COMMAND_UPDATE_RECORD;
+ options.p1 = options.recordNumber;
+ options.p2 = READ_RECORD_ABSOLUTE_MODE;
+ options.p3 = RECORD_SIZE;
+ ril.iccIO(options);
+ };
+
+ function do_test(fileId, number, expectedNumber) {
+ buf.sendParcel = function() {
+ // Request Type.
+ equal(this.readInt32(), REQUEST_SIM_IO);
+
+ // Token : we don't care
+ this.readInt32();
+
+ // command.
+ equal(this.readInt32(), ICC_COMMAND_UPDATE_RECORD);
+
+ // fileId.
+ equal(this.readInt32(), fileId);
+
+ // pathId.
+ if (ril.appType == CARD_APPTYPE_SIM || ril.appType == CARD_APPTYPE_RUIM) {
+ equal(this.readString(),
+ EF_PATH_MF_SIM + EF_PATH_DF_TELECOM);
+ } else{
+ equal(this.readString(),
+ EF_PATH_MF_SIM + EF_PATH_DF_TELECOM + EF_PATH_DF_PHONEBOOK);
+ }
+
+ // p1.
+ equal(this.readInt32(), RECORD_NUMBER);
+
+ // p2.
+ equal(this.readInt32(), READ_RECORD_ABSOLUTE_MODE);
+
+ // p3.
+ equal(this.readInt32(), RECORD_SIZE);
+
+ // data.
+ let strLen = this.readInt32();
+ // Extension record
+ let recordType = pduHelper.readHexOctet();
+
+ equal(recordType, 0x02);
+ equal(pduHelper.readHexOctet(), 10);
+ equal(
+ pduHelper.readSwappedNibbleExtendedBcdString(EXT_MAX_NUMBER_DIGITS - 1),
+ expectedNumber);
+
+ this.readStringDelimiter(strLen);
+
+ // pin2.
+ equal(this.readString(), null);
+
+ // AID. Ignore because it's from modem.
+ this.readInt32();
+ };
+
+ recordHelper.updateExtension(fileId, RECORD_NUMBER, number);
+ }
+
+ ril.appType = CARD_APPTYPE_SIM;
+ do_test(ICC_EF_EXT1, "01234567890123456789", "01234567890123456789");
+ // We don't support extension chain.
+ do_test(ICC_EF_EXT1, "012345678901234567891234", "01234567890123456789");
+
+ ril.appType = CARD_APPTYPE_USIM;
+ do_test(ICC_EF_EXT1, "01234567890123456789", "01234567890123456789");
+
+ ril.appType = CARD_APPTYPE_RUIM;
+ do_test(ICC_EF_EXT1, "01234567890123456789", "01234567890123456789");
+
+ run_next_test();
+});
+
+/**
+ * Verify ICCRecordHelper.cleanEFRecord
+ */
+add_test(function test_clean_ef_record() {
+ const RECORD_SIZE = 13;
+ const RECORD_NUMBER = 1;
+
+ let worker = newUint8Worker();
+ let context = worker.ContextPool._contexts[0];
+ let pduHelper = context.GsmPDUHelper;
+ let ril = context.RIL;
+ let recordHelper = context.ICCRecordHelper;
+ let buf = context.Buf;
+ let ioHelper = context.ICCIOHelper;
+
+ // Override.
+ ioHelper.updateLinearFixedEF = function(options) {
+ options.pathId = context.ICCFileHelper.getEFPath(options.fileId);
+ options.command = ICC_COMMAND_UPDATE_RECORD;
+ options.p1 = options.recordNumber;
+ options.p2 = READ_RECORD_ABSOLUTE_MODE;
+ options.p3 = RECORD_SIZE;
+ ril.iccIO(options);
+ };
+
+ function do_test(fileId) {
+ buf.sendParcel = function() {
+ // Request Type.
+ equal(this.readInt32(), REQUEST_SIM_IO);
+
+ // Token : we don't care
+ this.readInt32();
+
+ // command.
+ equal(this.readInt32(), ICC_COMMAND_UPDATE_RECORD);
+
+ // fileId.
+ equal(this.readInt32(), fileId);
+
+ // pathId.
+ if (ril.appType == CARD_APPTYPE_SIM || ril.appType == CARD_APPTYPE_RUIM) {
+ equal(this.readString(),
+ EF_PATH_MF_SIM + EF_PATH_DF_TELECOM);
+ } else{
+ equal(this.readString(),
+ EF_PATH_MF_SIM + EF_PATH_DF_TELECOM + EF_PATH_DF_PHONEBOOK);
+ }
+
+ // p1.
+ equal(this.readInt32(), RECORD_NUMBER);
+
+ // p2.
+ equal(this.readInt32(), READ_RECORD_ABSOLUTE_MODE);
+
+ // p3.
+ equal(this.readInt32(), RECORD_SIZE);
+
+ // data.
+ let strLen = this.readInt32();
+ // Extension record
+ for (let i = 0; i < RECORD_SIZE; i++) {
+ equal(pduHelper.readHexOctet(), 0xff);
+ }
+
+ this.readStringDelimiter(strLen);
+
+ // pin2.
+ equal(this.readString(), null);
+
+ // AID. Ignore because it's from modem.
+ this.readInt32();
+ };
+
+ recordHelper.cleanEFRecord(fileId, RECORD_NUMBER);
+ }
+
+ ril.appType = CARD_APPTYPE_SIM;
+ do_test(ICC_EF_EXT1);
+
+ run_next_test();
+});
+
+/**
+ * Verify ICCRecordHelper.getADNLikeExtensionRecordNumber
+ */
+add_test(function test_get_adn_like_extension_record_number() {
+ const RECORD_SIZE = 0x20;
+
+ let worker = newUint8Worker();
+ let context = worker.ContextPool._contexts[0];
+ let helper = context.GsmPDUHelper;
+ let record = context.ICCRecordHelper;
+ let buf = context.Buf;
+ let io = context.ICCIOHelper;
+
+ function do_test(rawEF, expectedRecordNumber) {
+ io.loadLinearFixedEF = function fakeLoadLinearFixedEF(options) {
+ // Write data size
+ buf.writeInt32(rawEF.length * 2);
+
+ // Write ext
+ for (let i = 0; i < rawEF.length; i += 2) {
+ helper.writeHexOctet(parseInt(rawEF.substr(i, 2), 16));
+ }
+
+ // Write string delimiter
+ buf.writeStringDelimiter(rawEF.length);
+ options.recordSize = RECORD_SIZE;
+ if (options.callback) {
+ options.callback(options);
+ }
+ };
+
+ let isSuccess = false;
+ let successCb = function successCb(number) {
+ equal(number, expectedRecordNumber);
+ isSuccess = true;
+ };
+
+ let errorCb = function errorCb(errorMsg) {
+ do_print("Reading ADNLike failed, msg = " + errorMsg);
+ ok(false);
+ };
+
+ record.getADNLikeExtensionRecordNumber(ICC_EF_ADN, 1, successCb, errorCb);
+ ok(isSuccess);
+ }
+
+ // Valid Extension, Alpha Id(Encoded with GSM 8 bit): "Contact001",
+ // Dialling Number: 99887766554433221100, Ext1: 0x01
+ do_test("436f6e74616374303031ffffffffffffffff0b8199887766554433221100ff01", 0x01);
+ // Empty Extension, Alpha Id(Encoded with GSM 8 bit): "Contact001", Ext1: 0xff
+ do_test("436f6e74616374303031ffffffffffffffffffffffffffffffffffffffffffff", 0xff);
+
+ run_next_test();
+});
diff --git a/dom/system/gonk/tests/test_ril_worker_icc_ICCUtilsHelper.js b/dom/system/gonk/tests/test_ril_worker_icc_ICCUtilsHelper.js
new file mode 100644
index 000000000..b23d0b598
--- /dev/null
+++ b/dom/system/gonk/tests/test_ril_worker_icc_ICCUtilsHelper.js
@@ -0,0 +1,326 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+subscriptLoader.loadSubScript("resource://gre/modules/ril_consts.js", this);
+
+function run_test() {
+ run_next_test();
+}
+
+/**
+ * Verify ICCUtilsHelper.isICCServiceAvailable.
+ */
+add_test(function test_is_icc_service_available() {
+ let worker = newUint8Worker();
+ let context = worker.ContextPool._contexts[0];
+ let ICCUtilsHelper = context.ICCUtilsHelper;
+ let RIL = context.RIL;
+
+ function test_table(sst, geckoService, simEnabled, usimEnabled) {
+ RIL.iccInfoPrivate.sst = sst;
+ RIL.appType = CARD_APPTYPE_SIM;
+ equal(ICCUtilsHelper.isICCServiceAvailable(geckoService), simEnabled);
+ RIL.appType = CARD_APPTYPE_USIM;
+ equal(ICCUtilsHelper.isICCServiceAvailable(geckoService), usimEnabled);
+ }
+
+ test_table([0x08], "ADN", true, false);
+ test_table([0x08], "FDN", false, false);
+ test_table([0x08], "SDN", false, true);
+
+ run_next_test();
+});
+
+/**
+ * Verify ICCUtilsHelper.isGsm8BitAlphabet
+ */
+add_test(function test_is_gsm_8bit_alphabet() {
+ let worker = newUint8Worker();
+ let context = worker.ContextPool._contexts[0];
+ let ICCUtilsHelper = context.ICCUtilsHelper;
+ const langTable = PDU_NL_LOCKING_SHIFT_TABLES[PDU_NL_IDENTIFIER_DEFAULT];
+ const langShiftTable = PDU_NL_SINGLE_SHIFT_TABLES[PDU_NL_IDENTIFIER_DEFAULT];
+
+ equal(ICCUtilsHelper.isGsm8BitAlphabet(langTable), true);
+ equal(ICCUtilsHelper.isGsm8BitAlphabet(langShiftTable), true);
+ equal(ICCUtilsHelper.isGsm8BitAlphabet("\uaaaa"), false);
+
+ run_next_test();
+});
+
+/**
+ * Verify ICCUtilsHelper.parsePbrTlvs
+ */
+add_test(function test_parse_pbr_tlvs() {
+ let worker = newUint8Worker();
+ let context = worker.ContextPool._contexts[0];
+ let buf = context.Buf;
+
+ let pbrTlvs = [
+ {tag: ICC_USIM_TYPE1_TAG,
+ length: 0x0F,
+ value: [{tag: ICC_USIM_EFADN_TAG,
+ length: 0x03,
+ value: [0x4F, 0x3A, 0x02]},
+ {tag: ICC_USIM_EFIAP_TAG,
+ length: 0x03,
+ value: [0x4F, 0x25, 0x01]},
+ {tag: ICC_USIM_EFPBC_TAG,
+ length: 0x03,
+ value: [0x4F, 0x09, 0x04]}]
+ },
+ {tag: ICC_USIM_TYPE2_TAG,
+ length: 0x05,
+ value: [{tag: ICC_USIM_EFEMAIL_TAG,
+ length: 0x03,
+ value: [0x4F, 0x50, 0x0B]},
+ {tag: ICC_USIM_EFANR_TAG,
+ length: 0x03,
+ value: [0x4F, 0x11, 0x02]},
+ {tag: ICC_USIM_EFANR_TAG,
+ length: 0x03,
+ value: [0x4F, 0x12, 0x03]}]
+ },
+ {tag: ICC_USIM_TYPE3_TAG,
+ length: 0x0A,
+ value: [{tag: ICC_USIM_EFCCP1_TAG,
+ length: 0x03,
+ value: [0x4F, 0x3D, 0x0A]},
+ {tag: ICC_USIM_EFEXT1_TAG,
+ length: 0x03,
+ value: [0x4F, 0x4A, 0x03]}]
+ },
+ ];
+
+ let pbr = context.ICCUtilsHelper.parsePbrTlvs(pbrTlvs);
+ equal(pbr.adn.fileId, 0x4F3a);
+ equal(pbr.iap.fileId, 0x4F25);
+ equal(pbr.pbc.fileId, 0x4F09);
+ equal(pbr.email.fileId, 0x4F50);
+ equal(pbr.anr0.fileId, 0x4f11);
+ equal(pbr.anr1.fileId, 0x4f12);
+ equal(pbr.ccp1.fileId, 0x4F3D);
+ equal(pbr.ext1.fileId, 0x4F4A);
+
+ run_next_test();
+});
+
+/**
+ * Verify MCC and MNC parsing
+ */
+add_test(function test_mcc_mnc_parsing() {
+ let worker = newUint8Worker();
+ let context = worker.ContextPool._contexts[0];
+ let helper = context.ICCUtilsHelper;
+
+ function do_test(imsi, mncLength, expectedMcc, expectedMnc) {
+ let result = helper.parseMccMncFromImsi(imsi, mncLength);
+
+ if (!imsi) {
+ equal(result, null);
+ return;
+ }
+
+ equal(result.mcc, expectedMcc);
+ equal(result.mnc, expectedMnc);
+ }
+
+ // Test the imsi is null.
+ do_test(null, null, null, null);
+
+ // Test MCC is Taiwan
+ do_test("466923202422409", 0x02, "466", "92");
+ do_test("466923202422409", 0x03, "466", "923");
+ do_test("466923202422409", null, "466", "92");
+
+ // Test MCC is US
+ do_test("310260542718417", 0x02, "310", "26");
+ do_test("310260542718417", 0x03, "310", "260");
+ do_test("310260542718417", null, "310", "260");
+
+ run_next_test();
+});
+
+add_test(function test_get_network_name_from_icc() {
+ let worker = newUint8Worker();
+ let context = worker.ContextPool._contexts[0];
+ let RIL = context.RIL;
+ let ICCUtilsHelper = context.ICCUtilsHelper;
+
+ function testGetNetworkNameFromICC(operatorData, expectedResult) {
+ let result = ICCUtilsHelper.getNetworkNameFromICC(operatorData.mcc,
+ operatorData.mnc,
+ operatorData.lac);
+
+ if (expectedResult == null) {
+ equal(result, expectedResult);
+ } else {
+ equal(result.fullName, expectedResult.longName);
+ equal(result.shortName, expectedResult.shortName);
+ }
+ }
+
+ // Before EF_OPL and EF_PNN have been loaded.
+ testGetNetworkNameFromICC({mcc: "123", mnc: "456", lac: 0x1000}, null);
+ testGetNetworkNameFromICC({mcc: "321", mnc: "654", lac: 0x2000}, null);
+
+ // Set HPLMN
+ RIL.iccInfo.mcc = "123";
+ RIL.iccInfo.mnc = "456";
+
+ RIL.voiceRegistrationState = {
+ cell: {
+ gsmLocationAreaCode: 0x1000
+ }
+ };
+ RIL.operator = {};
+
+ // Set EF_PNN
+ RIL.iccInfoPrivate = {
+ PNN: [
+ {"fullName": "PNN1Long", "shortName": "PNN1Short"},
+ {"fullName": "PNN2Long", "shortName": "PNN2Short"},
+ {"fullName": "PNN3Long", "shortName": "PNN3Short"},
+ {"fullName": "PNN4Long", "shortName": "PNN4Short"},
+ {"fullName": "PNN5Long", "shortName": "PNN5Short"},
+ {"fullName": "PNN6Long", "shortName": "PNN6Short"},
+ {"fullName": "PNN7Long", "shortName": "PNN7Short"},
+ {"fullName": "PNN8Long", "shortName": "PNN8Short"}
+ ]
+ };
+
+ // EF_OPL isn't available
+ ICCUtilsHelper.isICCServiceAvailable = function fakeIsICCServiceAvailable(service) {
+ return false;
+ };
+
+ // EF_OPL isn't available and current isn't in HPLMN,
+ testGetNetworkNameFromICC({mcc: "321", mnc: "654", lac: 0x1000}, null);
+
+ // EF_OPL isn't available and current is in HPLMN,
+ // the first record of PNN should be returned.
+ testGetNetworkNameFromICC({mcc: "123", mnc: "456", lac: 0x1000},
+ {longName: "PNN1Long", shortName: "PNN1Short"});
+
+ // EF_OPL is available
+ ICCUtilsHelper.isICCServiceAvailable = function fakeIsICCServiceAvailable(service) {
+ return service === "OPL";
+ };
+
+ // Set EF_OPL
+ RIL.iccInfoPrivate.OPL = [
+ {
+ "mcc": "123",
+ "mnc": "456",
+ "lacTacStart": 0,
+ "lacTacEnd": 0xFFFE,
+ "pnnRecordId": 4
+ },
+ {
+ "mcc": "321",
+ "mnc": "654",
+ "lacTacStart": 0,
+ "lacTacEnd": 0x0010,
+ "pnnRecordId": 3
+ },
+ {
+ "mcc": "321",
+ "mnc": "654",
+ "lacTacStart": 0x0100,
+ "lacTacEnd": 0x1010,
+ "pnnRecordId": 2
+ },
+ {
+ "mcc": ";;;",
+ "mnc": "01",
+ "lacTacStart": 0,
+ "lacTacEnd": 0xFFFE,
+ "pnnRecordId": 5
+ },
+ {
+ "mcc": "00;",
+ "mnc": "02",
+ "lacTacStart": 0,
+ "lacTacEnd": 0xFFFE,
+ "pnnRecordId": 6
+ },
+ {
+ "mcc": "001",
+ "mnc": ";;",
+ "lacTacStart": 0,
+ "lacTacEnd": 0xFFFE,
+ "pnnRecordId": 7
+ },
+ {
+ "mcc": "002",
+ "mnc": "0;",
+ "lacTacStart": 0,
+ "lacTacEnd": 0xFFFE,
+ "pnnRecordId": 8
+ }
+ ];
+
+ // Both EF_PNN and EF_OPL are presented, and current PLMN is HPLMN,
+ testGetNetworkNameFromICC({mcc: "123", mnc: "456", lac: 0x1000},
+ {longName: "PNN4Long", shortName: "PNN4Short"});
+
+ // Current PLMN is not HPLMN, and according to LAC, we should get
+ // the second PNN record.
+ testGetNetworkNameFromICC({mcc: "321", mnc: "654", lac: 0x1000},
+ {longName: "PNN2Long", shortName: "PNN2Short"});
+
+ // Current PLMN is not HPLMN, and according to LAC, we should get
+ // the thrid PNN record.
+ testGetNetworkNameFromICC({mcc: "321", mnc: "654", lac: 0x0001},
+ {longName: "PNN3Long", shortName: "PNN3Short"});
+
+ // Current PLMN is not HPLMN, and according to LAC, we should get
+ // the 5th PNN record after wild char (ie: ';') handling.
+ testGetNetworkNameFromICC({mcc: "001", mnc: "01", lac: 0x0001},
+ {longName: "PNN5Long", shortName: "PNN5Short"});
+
+ // Current PLMN is not HPLMN, and according to LAC, we should get
+ // the 6th PNN record after wild char (ie: ';') handling.
+ testGetNetworkNameFromICC({mcc: "001", mnc: "02", lac: 0x0001},
+ {longName: "PNN6Long", shortName: "PNN6Short"});
+
+ // Current PLMN is not HPLMN, and according to LAC, we should get
+ // the 7th PNN record after wild char (ie: ';') handling.
+ testGetNetworkNameFromICC({mcc: "001", mnc: "03", lac: 0x0001},
+ {longName: "PNN7Long", shortName: "PNN7Short"});
+
+ // Current PLMN is not HPLMN, and according to LAC, we should get
+ // the 8th PNN record after wild char (ie: ';') handling.
+ testGetNetworkNameFromICC({mcc: "002", mnc: "03", lac: 0x0001},
+ {longName: "PNN8Long", shortName: "PNN8Short"});
+
+ run_next_test();
+});
+
+/**
+ * Verify ICCUtilsHelper.isCphsServiceAvailable.
+ */
+add_test(function test_is_cphs_service_available() {
+ let worker = newUint8Worker();
+ let context = worker.ContextPool._contexts[0];
+ let ICCUtilsHelper = context.ICCUtilsHelper;
+ let RIL = context.RIL;
+ RIL.iccInfoPrivate.cphsSt = new Uint8Array(2);
+
+ function test_table(cphsSt, geckoService) {
+ RIL.iccInfoPrivate.cphsSt.set(cphsSt);
+
+ for (let service in GECKO_ICC_SERVICES.cphs) {
+ equal(ICCUtilsHelper.isCphsServiceAvailable(service),
+ geckoService == service);
+ }
+ }
+
+ test_table([0x03, 0x00], "CSP");
+ test_table([0x0C, 0x00], "SST");
+ test_table([0x30, 0x00], "MBN");
+ test_table([0xC0, 0x00], "ONSF");
+ test_table([0x00, 0x03], "INFO_NUM");
+
+ run_next_test();
+});
diff --git a/dom/system/gonk/tests/test_ril_worker_icc_IconLoader.js b/dom/system/gonk/tests/test_ril_worker_icc_IconLoader.js
new file mode 100644
index 000000000..8bcd26ffe
--- /dev/null
+++ b/dom/system/gonk/tests/test_ril_worker_icc_IconLoader.js
@@ -0,0 +1,771 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+subscriptLoader.loadSubScript("resource://gre/modules/ril_consts.js", this);
+
+function run_test() {
+ run_next_test();
+}
+
+/**
+ * Verify IconLoader.loadIcons with recordNumbers array length being 1.
+ * Query images of one record at a time.
+ */
+add_test(function test_load_icon_basic() {
+ let worker = newUint8Worker();
+ let context = worker.ContextPool._contexts[0];
+ let ril = context.RIL;
+ let iconLoader = context.IconLoader;
+ let simRecordHelper = context.SimRecordHelper;
+
+ let test_data = [
+ {rawData: [
+ {codingScheme: ICC_IMG_CODING_SCHEME_BASIC,
+ width: 0x10,
+ height: 0x10,
+ body: [0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x00,
+ 0x00, 0x9d, 0xe9, 0xa1, 0x2d, 0xa1, 0x2d, 0xa1, 0x2b,
+ 0xa1, 0x2b, 0x9d, 0xe9, 0x00, 0x00, 0xff, 0xff, 0xff,
+ 0xff, 0x00, 0x00, 0xff, 0xff]}],
+ expected: [
+ {width: 0x10,
+ height: 0x10,
+ pixels: [/* 1st byte of body: 0xff */
+ 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+ 0xffffffff, 0xffffffff, 0xffffffff,
+ /* 2nd byte of body: 0xff */
+ 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+ 0xffffffff, 0xffffffff, 0xffffffff,
+ /* 3rd byte of body: 0x00 */
+ 0x000000ff, 0x000000ff, 0x000000ff, 0x000000ff, 0x000000ff,
+ 0x000000ff, 0x000000ff, 0x000000ff,
+ /* 4th byte of body: 0x00 */
+ 0x000000ff, 0x000000ff, 0x000000ff, 0x000000ff, 0x000000ff,
+ 0x000000ff, 0x000000ff, 0x000000ff,
+ /* 5th byte of body: 0xff */
+ 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+ 0xffffffff, 0xffffffff, 0xffffffff,
+ /* 6th byte of body: 0xff */
+ 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+ 0xffffffff, 0xffffffff, 0xffffffff,
+ /* 7th byte of body: 0xff */
+ 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+ 0xffffffff, 0xffffffff, 0xffffffff,
+ /* 8th byte of body: 0xff */
+ 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+ 0xffffffff, 0xffffffff, 0xffffffff,
+ /* 9th byte of body: 0x00 */
+ 0x000000ff, 0x000000ff, 0x000000ff, 0x000000ff, 0x000000ff,
+ 0x000000ff, 0x000000ff, 0x000000ff,
+ /* 10th byte of body: 0x00 */
+ 0x000000ff, 0x000000ff, 0x000000ff, 0x000000ff, 0x000000ff,
+ 0x000000ff, 0x000000ff, 0x000000ff,
+ /* 11th byte of body: 0x9d */
+ 0xffffffff, 0x000000ff, 0x000000ff, 0xffffffff, 0xffffffff,
+ 0xffffffff, 0x000000ff, 0xffffffff,
+ /* 12th byte of body: 0xe9 */
+ 0xffffffff, 0xffffffff, 0xffffffff, 0x000000ff, 0xffffffff,
+ 0x000000ff, 0x000000ff, 0xffffffff,
+ /* 13th byte of body: 0xa1 */
+ 0xffffffff, 0x000000ff, 0xffffffff, 0x000000ff, 0x000000ff,
+ 0x000000ff, 0x000000ff, 0xffffffff,
+ /* 14th byte of body: 0x2d */
+ 0x000000ff, 0x000000ff, 0xffffffff, 0x000000ff, 0xffffffff,
+ 0xffffffff, 0x000000ff, 0xffffffff,
+ /* 15th byte of body: 0xa1 */
+ 0xffffffff, 0x000000ff, 0xffffffff, 0x000000ff, 0x000000ff,
+ 0x000000ff, 0x000000ff, 0xffffffff,
+ /* 16th byte of body: 0x2d */
+ 0x000000ff, 0x000000ff, 0xffffffff, 0x000000ff, 0xffffffff,
+ 0xffffffff, 0x000000ff, 0xffffffff,
+ /* 17th byte of body: 0xa1 */
+ 0xffffffff, 0x000000ff, 0xffffffff, 0x000000ff, 0x000000ff,
+ 0x000000ff, 0x000000ff, 0xffffffff,
+ /* 18th byte of body: 0x2b */
+ 0x000000ff, 0x000000ff, 0xffffffff, 0x000000ff, 0xffffffff,
+ 0x000000ff, 0xffffffff, 0xffffffff,
+ /* 19th byte of body: 0xa1 */
+ 0xffffffff, 0x000000ff, 0xffffffff, 0x000000ff, 0x000000ff,
+ 0x000000ff, 0x000000ff, 0xffffffff,
+ /* 20th byte of body: 0x2b */
+ 0x000000ff, 0x000000ff, 0xffffffff, 0x000000ff, 0xffffffff,
+ 0x000000ff, 0xffffffff, 0xffffffff,
+ /* 21th byte of body: 0x9d */
+ 0xffffffff, 0x000000ff, 0x000000ff, 0xffffffff, 0xffffffff,
+ 0xffffffff, 0x000000ff, 0xffffffff,
+ /* 22th byte of body: 0xe9 */
+ 0xffffffff, 0xffffffff, 0xffffffff, 0x000000ff, 0xffffffff,
+ 0x000000ff, 0x000000ff, 0xffffffff,
+ /* 23th byte of body: 0x00 */
+ 0x000000ff, 0x000000ff, 0x000000ff, 0x000000ff, 0x000000ff,
+ 0x000000ff, 0x000000ff, 0x000000ff,
+ /* 24th byte of body: 0x00 */
+ 0x000000ff, 0x000000ff, 0x000000ff, 0x000000ff, 0x000000ff,
+ 0x000000ff, 0x000000ff, 0x000000ff,
+ /* 25th byte of body: 0xff */
+ 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+ 0xffffffff, 0xffffffff, 0xffffffff,
+ /* 26th byte of body: 0xff */
+ 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+ 0xffffffff, 0xffffffff, 0xffffffff,
+ /* 27th byte of body: 0xff */
+ 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+ 0xffffffff, 0xffffffff, 0xffffffff,
+ /* 28th byte of body: 0xff */
+ 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+ 0xffffffff, 0xffffffff, 0xffffffff,
+ /* 29th byte of body: 0x00 */
+ 0x000000ff, 0x000000ff, 0x000000ff, 0x000000ff, 0x000000ff,
+ 0x000000ff, 0x000000ff, 0x000000ff,
+ /* 30th byte of body: 0x00 */
+ 0x000000ff, 0x000000ff, 0x000000ff, 0x000000ff, 0x000000ff,
+ 0x000000ff, 0x000000ff, 0x000000ff,
+ /* 31th byte of body: 0xff */
+ 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+ 0xffffffff, 0xffffffff, 0xffffffff,
+ /* 32th byte of body: 0xff */
+ 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+ 0xffffffff, 0xffffffff, 0xffffffff]}]},
+ {rawData: [
+ {codingScheme: ICC_IMG_CODING_SCHEME_COLOR,
+ width: 0x10,
+ height: 0x10,
+ bitsPerImgPoint: 0x04,
+ numOfClutEntries: 0x10,
+ body: [0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xaa, 0xaa,
+ 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa,
+ 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xcf, 0xfc, 0xcc, 0xfc, 0xcc,
+ 0xcf, 0xcf, 0xfc, 0xcf, 0xcf, 0xff, 0xfc, 0xff, 0xcf,
+ 0xcc, 0xfc, 0xcf, 0xcf, 0xff, 0xfc, 0xff, 0xcf, 0xcc,
+ 0xfc, 0xcf, 0xcf, 0xff, 0xfc, 0xff, 0xcf, 0xcf, 0xcc,
+ 0xcf, 0xcf, 0xff, 0xfc, 0xff, 0xcf, 0xcf, 0xcc, 0xcf,
+ 0xfc, 0xcc, 0xfc, 0xcc, 0xcf, 0xcf, 0xfc, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xaa, 0xaa, 0xaa,
+ 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa,
+ 0xaa, 0xaa, 0xaa, 0xaa, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99,
+ 0x99, 0x99],
+ clut: [0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x80, 0x00,
+ 0x80, 0x80, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x80,
+ 0x00, 0x80, 0x80, 0xc0, 0xc0, 0xc0, 0x80, 0x80, 0x80,
+ 0xff, 0x00, 0x00, 0x00, 0xff, 0x00, 0xff, 0xff, 0x00,
+ 0x00, 0x00, 0xff, 0xff, 0x00, 0xff, 0x00, 0xff, 0xff,
+ 0xff, 0xff, 0xff]}],
+ expected: [
+ {width: 0x10,
+ height: 0x10,
+ pixels: [0xff0000ff, 0xff0000ff, 0xff0000ff, 0xff0000ff, 0xff0000ff,
+ 0xff0000ff, 0xff0000ff, 0xff0000ff, 0xff0000ff, 0xff0000ff,
+ 0xff0000ff, 0xff0000ff, 0xff0000ff, 0xff0000ff, 0xff0000ff,
+ 0xff0000ff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+ 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+ 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+ 0xffffffff, 0xffffffff, 0x00ff00ff, 0x00ff00ff, 0x00ff00ff,
+ 0x00ff00ff, 0x00ff00ff, 0x00ff00ff, 0x00ff00ff, 0x00ff00ff,
+ 0x00ff00ff, 0x00ff00ff, 0x00ff00ff, 0x00ff00ff, 0x00ff00ff,
+ 0x00ff00ff, 0x00ff00ff, 0x00ff00ff, 0x00ff00ff, 0x00ff00ff,
+ 0x00ff00ff, 0x00ff00ff, 0x00ff00ff, 0x00ff00ff, 0x00ff00ff,
+ 0x00ff00ff, 0x00ff00ff, 0x00ff00ff, 0x00ff00ff, 0x00ff00ff,
+ 0x00ff00ff, 0x00ff00ff, 0x00ff00ff, 0x00ff00ff, 0xffffffff,
+ 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+ 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+ 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+ 0x0000ffff, 0xffffffff, 0xffffffff, 0x0000ffff, 0x0000ffff,
+ 0x0000ffff, 0xffffffff, 0x0000ffff, 0x0000ffff, 0x0000ffff,
+ 0x0000ffff, 0xffffffff, 0x0000ffff, 0xffffffff, 0xffffffff,
+ 0x0000ffff, 0x0000ffff, 0xffffffff, 0x0000ffff, 0xffffffff,
+ 0xffffffff, 0xffffffff, 0xffffffff, 0x0000ffff, 0xffffffff,
+ 0xffffffff, 0x0000ffff, 0xffffffff, 0x0000ffff, 0x0000ffff,
+ 0xffffffff, 0x0000ffff, 0x0000ffff, 0xffffffff, 0x0000ffff,
+ 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0x0000ffff,
+ 0xffffffff, 0xffffffff, 0x0000ffff, 0xffffffff, 0x0000ffff,
+ 0x0000ffff, 0xffffffff, 0x0000ffff, 0x0000ffff, 0xffffffff,
+ 0x0000ffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+ 0x0000ffff, 0xffffffff, 0xffffffff, 0x0000ffff, 0xffffffff,
+ 0x0000ffff, 0xffffffff, 0x0000ffff, 0x0000ffff, 0x0000ffff,
+ 0xffffffff, 0x0000ffff, 0xffffffff, 0xffffffff, 0xffffffff,
+ 0xffffffff, 0x0000ffff, 0xffffffff, 0xffffffff, 0x0000ffff,
+ 0xffffffff, 0x0000ffff, 0xffffffff, 0x0000ffff, 0x0000ffff,
+ 0x0000ffff, 0xffffffff, 0xffffffff, 0x0000ffff, 0x0000ffff,
+ 0x0000ffff, 0xffffffff, 0x0000ffff, 0x0000ffff, 0x0000ffff,
+ 0x0000ffff, 0xffffffff, 0x0000ffff, 0xffffffff, 0xffffffff,
+ 0x0000ffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+ 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+ 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+ 0xffffffff, 0xffffffff, 0x00ff00ff, 0x00ff00ff, 0x00ff00ff,
+ 0x00ff00ff, 0x00ff00ff, 0x00ff00ff, 0x00ff00ff, 0x00ff00ff,
+ 0x00ff00ff, 0x00ff00ff, 0x00ff00ff, 0x00ff00ff, 0x00ff00ff,
+ 0x00ff00ff, 0x00ff00ff, 0x00ff00ff, 0x00ff00ff, 0x00ff00ff,
+ 0x00ff00ff, 0x00ff00ff, 0x00ff00ff, 0x00ff00ff, 0x00ff00ff,
+ 0x00ff00ff, 0x00ff00ff, 0x00ff00ff, 0x00ff00ff, 0x00ff00ff,
+ 0x00ff00ff, 0x00ff00ff, 0x00ff00ff, 0x00ff00ff, 0xffffffff,
+ 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+ 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+ 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+ 0xff0000ff, 0xff0000ff, 0xff0000ff, 0xff0000ff, 0xff0000ff,
+ 0xff0000ff, 0xff0000ff, 0xff0000ff, 0xff0000ff, 0xff0000ff,
+ 0xff0000ff, 0xff0000ff, 0xff0000ff, 0xff0000ff, 0xff0000ff,
+ 0xff0000ff]}]},
+ {rawData: [
+ {codingScheme: ICC_IMG_CODING_SCHEME_COLOR,
+ width: 0x03,
+ height: 0x03,
+ bitsPerImgPoint: 0x05,
+ numOfClutEntries: 0x20,
+ body: [0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88],
+ clut: [0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
+ 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11,
+ 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a,
+ 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, 0x21, 0x22, 0x23,
+ 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2c,
+ 0x2d, 0x2e, 0x2f, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35,
+ 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e,
+ 0x3f, 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47,
+ 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, 0x50,
+ 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59,
+ 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f]},
+ {codingScheme: ICC_IMG_CODING_SCHEME_BASIC,
+ width: 0x10,
+ height: 0x10,
+ body: [0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x00,
+ 0x00, 0x9d, 0xe9, 0xa1, 0x2d, 0xa1, 0x2d, 0xa1, 0x2b,
+ 0xa1, 0x2b, 0x9d, 0xe9, 0x00, 0x00, 0xff, 0xff, 0xff,
+ 0xff, 0x00, 0x00, 0xff, 0xff]}],
+ expected: [
+ {width: 0x03,
+ height: 0x03,
+ pixels: [0x000102ff, 0x060708ff, 0x0c0d0eff, 0x121314ff, 0x18191aff,
+ 0x1e1f20ff, 0x242526ff, 0x2a2b2cff, 0x333435ff]},
+ {width: 0x10,
+ height: 0x10,
+ pixels: [/* 1st byte of body: 0xff */
+ 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+ 0xffffffff, 0xffffffff, 0xffffffff,
+ /* 2nd byte of body: 0xff */
+ 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+ 0xffffffff, 0xffffffff, 0xffffffff,
+ /* 3rd byte of body: 0x00 */
+ 0x000000ff, 0x000000ff, 0x000000ff, 0x000000ff, 0x000000ff,
+ 0x000000ff, 0x000000ff, 0x000000ff,
+ /* 4th byte of body: 0x00 */
+ 0x000000ff, 0x000000ff, 0x000000ff, 0x000000ff, 0x000000ff,
+ 0x000000ff, 0x000000ff, 0x000000ff,
+ /* 5th byte of body: 0xff */
+ 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+ 0xffffffff, 0xffffffff, 0xffffffff,
+ /* 6th byte of body: 0xff */
+ 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+ 0xffffffff, 0xffffffff, 0xffffffff,
+ /* 7th byte of body: 0xff */
+ 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+ 0xffffffff, 0xffffffff, 0xffffffff,
+ /* 8th byte of body: 0xff */
+ 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+ 0xffffffff, 0xffffffff, 0xffffffff,
+ /* 9th byte of body: 0x00 */
+ 0x000000ff, 0x000000ff, 0x000000ff, 0x000000ff, 0x000000ff,
+ 0x000000ff, 0x000000ff, 0x000000ff,
+ /* 10th byte of body: 0x00 */
+ 0x000000ff, 0x000000ff, 0x000000ff, 0x000000ff, 0x000000ff,
+ 0x000000ff, 0x000000ff, 0x000000ff,
+ /* 11th byte of body: 0x9d */
+ 0xffffffff, 0x000000ff, 0x000000ff, 0xffffffff, 0xffffffff,
+ 0xffffffff, 0x000000ff, 0xffffffff,
+ /* 12th byte of body: 0xe9 */
+ 0xffffffff, 0xffffffff, 0xffffffff, 0x000000ff, 0xffffffff,
+ 0x000000ff, 0x000000ff, 0xffffffff,
+ /* 13th byte of body: 0xa1 */
+ 0xffffffff, 0x000000ff, 0xffffffff, 0x000000ff, 0x000000ff,
+ 0x000000ff, 0x000000ff, 0xffffffff,
+ /* 14th byte of body: 0x2d */
+ 0x000000ff, 0x000000ff, 0xffffffff, 0x000000ff, 0xffffffff,
+ 0xffffffff, 0x000000ff, 0xffffffff,
+ /* 15th byte of body: 0xa1 */
+ 0xffffffff, 0x000000ff, 0xffffffff, 0x000000ff, 0x000000ff,
+ 0x000000ff, 0x000000ff, 0xffffffff,
+ /* 16th byte of body: 0x2d */
+ 0x000000ff, 0x000000ff, 0xffffffff, 0x000000ff, 0xffffffff,
+ 0xffffffff, 0x000000ff, 0xffffffff,
+ /* 17th byte of body: 0xa1 */
+ 0xffffffff, 0x000000ff, 0xffffffff, 0x000000ff, 0x000000ff,
+ 0x000000ff, 0x000000ff, 0xffffffff,
+ /* 18th byte of body: 0x2b */
+ 0x000000ff, 0x000000ff, 0xffffffff, 0x000000ff, 0xffffffff,
+ 0x000000ff, 0xffffffff, 0xffffffff,
+ /* 19th byte of body: 0xa1 */
+ 0xffffffff, 0x000000ff, 0xffffffff, 0x000000ff, 0x000000ff,
+ 0x000000ff, 0x000000ff, 0xffffffff,
+ /* 20th byte of body: 0x2b */
+ 0x000000ff, 0x000000ff, 0xffffffff, 0x000000ff, 0xffffffff,
+ 0x000000ff, 0xffffffff, 0xffffffff,
+ /* 21th byte of body: 0x9d */
+ 0xffffffff, 0x000000ff, 0x000000ff, 0xffffffff, 0xffffffff,
+ 0xffffffff, 0x000000ff, 0xffffffff,
+ /* 22th byte of body: 0xe9 */
+ 0xffffffff, 0xffffffff, 0xffffffff, 0x000000ff, 0xffffffff,
+ 0x000000ff, 0x000000ff, 0xffffffff,
+ /* 23th byte of body: 0x00 */
+ 0x000000ff, 0x000000ff, 0x000000ff, 0x000000ff, 0x000000ff,
+ 0x000000ff, 0x000000ff, 0x000000ff,
+ /* 24th byte of body: 0x00 */
+ 0x000000ff, 0x000000ff, 0x000000ff, 0x000000ff, 0x000000ff,
+ 0x000000ff, 0x000000ff, 0x000000ff,
+ /* 25th byte of body: 0xff */
+ 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+ 0xffffffff, 0xffffffff, 0xffffffff,
+ /* 26th byte of body: 0xff */
+ 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+ 0xffffffff, 0xffffffff, 0xffffffff,
+ /* 27th byte of body: 0xff */
+ 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+ 0xffffffff, 0xffffffff, 0xffffffff,
+ /* 28th byte of body: 0xff */
+ 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+ 0xffffffff, 0xffffffff, 0xffffffff,
+ /* 29th byte of body: 0x00 */
+ 0x000000ff, 0x000000ff, 0x000000ff, 0x000000ff, 0x000000ff,
+ 0x000000ff, 0x000000ff, 0x000000ff,
+ /* 30th byte of body: 0x00 */
+ 0x000000ff, 0x000000ff, 0x000000ff, 0x000000ff, 0x000000ff,
+ 0x000000ff, 0x000000ff, 0x000000ff,
+ /* 31th byte of body: 0xff */
+ 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+ 0xffffffff, 0xffffffff, 0xffffffff,
+ /* 32th byte of body: 0xff */
+ 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+ 0xffffffff, 0xffffffff, 0xffffffff]}]},
+ {rawData: [
+ {codingScheme: ICC_IMG_CODING_SCHEME_COLOR_TRANSPARENCY,
+ width: 0x04,
+ height: 0x04,
+ bitsPerImgPoint: 0x04,
+ numOfClutEntries: 0x10,
+ body: [0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88],
+ clut: [0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
+ 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11,
+ 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a,
+ 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, 0x21, 0x22, 0x23,
+ 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2c,
+ 0x2d, 0x2e, 0x2f]}],
+ expected: [
+ {width: 0x04,
+ height: 0x04,
+ pixels: [0x00000000, 0x00000000, 0x2a2b2cff, 0x2a2b2cff, 0x272829ff,
+ 0x272829ff, 0x242526ff, 0x242526ff, 0x212223ff, 0x212223ff,
+ 0x1e1f20ff, 0x1e1f20ff, 0x1b1c1dff, 0x1b1c1dff, 0x18191aff,
+ 0x18191aff]}]}];
+
+ function do_test(test_data, expected) {
+ simRecordHelper.readIMG = function fakeReadIMG(recordNumber, onsuccess, onerror) {
+ onsuccess(test_data);
+ };
+
+ let onsuccess = function(icons) {
+ // Query one record at a time.
+ equal(icons.length, 1);
+ equal(icons[0].length, expected.length);
+ for (let i = 0; i < icons[0].length; i++) {
+ // Read the i_th image of the record.
+ let icon = icons[0][i];
+ let exp = expected[i];
+ equal(icon.width, exp.width);
+ equal(icon.height, exp.height);
+ equal(icon.pixels.length, exp.pixels.length);
+ for (let j = 0; j < icon.pixels.length; j++) {
+ equal(icon.pixels[j], exp.pixels[j]);
+ }
+ }
+ };
+
+ iconLoader.loadIcons([0], onsuccess);
+ }
+
+ for (let i = 0; i < test_data.length; i++) {
+ do_test(test_data[i].rawData, test_data[i].expected);
+ }
+
+ run_next_test();
+});
+
+/**
+ * Verify IconLoader.loadIcons.
+ */
+add_test(function test_load_icons() {
+ let worker = newUint8Worker();
+ let context = worker.ContextPool._contexts[0];
+ let ril = context.RIL;
+ let iconLoader = context.IconLoader;
+ let simRecordHelper = context.SimRecordHelper;
+
+ let test_data = {
+ rawData: [
+ // Record 1.
+ [{codingScheme: ICC_IMG_CODING_SCHEME_BASIC,
+ width: 0x10,
+ height: 0x10,
+ body: [0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x00,
+ 0x00, 0x9d, 0xe9, 0xa1, 0x2d, 0xa1, 0x2d, 0xa1, 0x2b,
+ 0xa1, 0x2b, 0x9d, 0xe9, 0x00, 0x00, 0xff, 0xff, 0xff,
+ 0xff, 0x00, 0x00, 0xff, 0xff]}],
+ // Record 2.
+ [{codingScheme: ICC_IMG_CODING_SCHEME_COLOR,
+ width: 0x10,
+ height: 0x10,
+ bitsPerImgPoint: 0x04,
+ numOfClutEntries: 0x10,
+ body: [0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xaa, 0xaa,
+ 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa,
+ 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xcf, 0xfc, 0xcc, 0xfc, 0xcc,
+ 0xcf, 0xcf, 0xfc, 0xcf, 0xcf, 0xff, 0xfc, 0xff, 0xcf,
+ 0xcc, 0xfc, 0xcf, 0xcf, 0xff, 0xfc, 0xff, 0xcf, 0xcc,
+ 0xfc, 0xcf, 0xcf, 0xff, 0xfc, 0xff, 0xcf, 0xcf, 0xcc,
+ 0xcf, 0xcf, 0xff, 0xfc, 0xff, 0xcf, 0xcf, 0xcc, 0xcf,
+ 0xfc, 0xcc, 0xfc, 0xcc, 0xcf, 0xcf, 0xfc, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xaa, 0xaa, 0xaa,
+ 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa,
+ 0xaa, 0xaa, 0xaa, 0xaa, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99,
+ 0x99, 0x99],
+ clut: [0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x80, 0x00,
+ 0x80, 0x80, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x80,
+ 0x00, 0x80, 0x80, 0xc0, 0xc0, 0xc0, 0x80, 0x80, 0x80,
+ 0xff, 0x00, 0x00, 0x00, 0xff, 0x00, 0xff, 0xff, 0x00,
+ 0x00, 0x00, 0xff, 0xff, 0x00, 0xff, 0x00, 0xff, 0xff,
+ 0xff, 0xff, 0xff]}],
+ // Record 3.
+ [{codingScheme: ICC_IMG_CODING_SCHEME_COLOR,
+ width: 0x03,
+ height: 0x03,
+ bitsPerImgPoint: 0x05,
+ numOfClutEntries: 0x20,
+ body: [0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88],
+ clut: [0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
+ 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11,
+ 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a,
+ 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, 0x21, 0x22, 0x23,
+ 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2c,
+ 0x2d, 0x2e, 0x2f, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35,
+ 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e,
+ 0x3f, 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47,
+ 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, 0x50,
+ 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59,
+ 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f]},
+ {codingScheme: ICC_IMG_CODING_SCHEME_BASIC,
+ width: 0x10,
+ height: 0x10,
+ body: [0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x00,
+ 0x00, 0x9d, 0xe9, 0xa1, 0x2d, 0xa1, 0x2d, 0xa1, 0x2b,
+ 0xa1, 0x2b, 0x9d, 0xe9, 0x00, 0x00, 0xff, 0xff, 0xff,
+ 0xff, 0x00, 0x00, 0xff, 0xff]}],
+ // Record 4.
+ [{codingScheme: ICC_IMG_CODING_SCHEME_COLOR_TRANSPARENCY,
+ width: 0x04,
+ height: 0x04,
+ bitsPerImgPoint: 0x04,
+ numOfClutEntries: 0x10,
+ body: [0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88],
+ clut: [0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
+ 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11,
+ 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a,
+ 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, 0x21, 0x22, 0x23,
+ 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2c,
+ 0x2d, 0x2e, 0x2f]}]],
+ expected: [
+ // Record 1.
+ [{width: 0x10,
+ height: 0x10,
+ pixels: [/* 1st byte of body: 0xff */
+ 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+ 0xffffffff, 0xffffffff, 0xffffffff,
+ /* 2nd byte of body: 0xff */
+ 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+ 0xffffffff, 0xffffffff, 0xffffffff,
+ /* 3rd byte of body: 0x00 */
+ 0x000000ff, 0x000000ff, 0x000000ff, 0x000000ff, 0x000000ff,
+ 0x000000ff, 0x000000ff, 0x000000ff,
+ /* 4th byte of body: 0x00 */
+ 0x000000ff, 0x000000ff, 0x000000ff, 0x000000ff, 0x000000ff,
+ 0x000000ff, 0x000000ff, 0x000000ff,
+ /* 5th byte of body: 0xff */
+ 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+ 0xffffffff, 0xffffffff, 0xffffffff,
+ /* 6th byte of body: 0xff */
+ 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+ 0xffffffff, 0xffffffff, 0xffffffff,
+ /* 7th byte of body: 0xff */
+ 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+ 0xffffffff, 0xffffffff, 0xffffffff,
+ /* 8th byte of body: 0xff */
+ 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+ 0xffffffff, 0xffffffff, 0xffffffff,
+ /* 9th byte of body: 0x00 */
+ 0x000000ff, 0x000000ff, 0x000000ff, 0x000000ff, 0x000000ff,
+ 0x000000ff, 0x000000ff, 0x000000ff,
+ /* 10th byte of body: 0x00 */
+ 0x000000ff, 0x000000ff, 0x000000ff, 0x000000ff, 0x000000ff,
+ 0x000000ff, 0x000000ff, 0x000000ff,
+ /* 11th byte of body: 0x9d */
+ 0xffffffff, 0x000000ff, 0x000000ff, 0xffffffff, 0xffffffff,
+ 0xffffffff, 0x000000ff, 0xffffffff,
+ /* 12th byte of body: 0xe9 */
+ 0xffffffff, 0xffffffff, 0xffffffff, 0x000000ff, 0xffffffff,
+ 0x000000ff, 0x000000ff, 0xffffffff,
+ /* 13th byte of body: 0xa1 */
+ 0xffffffff, 0x000000ff, 0xffffffff, 0x000000ff, 0x000000ff,
+ 0x000000ff, 0x000000ff, 0xffffffff,
+ /* 14th byte of body: 0x2d */
+ 0x000000ff, 0x000000ff, 0xffffffff, 0x000000ff, 0xffffffff,
+ 0xffffffff, 0x000000ff, 0xffffffff,
+ /* 15th byte of body: 0xa1 */
+ 0xffffffff, 0x000000ff, 0xffffffff, 0x000000ff, 0x000000ff,
+ 0x000000ff, 0x000000ff, 0xffffffff,
+ /* 16th byte of body: 0x2d */
+ 0x000000ff, 0x000000ff, 0xffffffff, 0x000000ff, 0xffffffff,
+ 0xffffffff, 0x000000ff, 0xffffffff,
+ /* 17th byte of body: 0xa1 */
+ 0xffffffff, 0x000000ff, 0xffffffff, 0x000000ff, 0x000000ff,
+ 0x000000ff, 0x000000ff, 0xffffffff,
+ /* 18th byte of body: 0x2b */
+ 0x000000ff, 0x000000ff, 0xffffffff, 0x000000ff, 0xffffffff,
+ 0x000000ff, 0xffffffff, 0xffffffff,
+ /* 19th byte of body: 0xa1 */
+ 0xffffffff, 0x000000ff, 0xffffffff, 0x000000ff, 0x000000ff,
+ 0x000000ff, 0x000000ff, 0xffffffff,
+ /* 20th byte of body: 0x2b */
+ 0x000000ff, 0x000000ff, 0xffffffff, 0x000000ff, 0xffffffff,
+ 0x000000ff, 0xffffffff, 0xffffffff,
+ /* 21th byte of body: 0x9d */
+ 0xffffffff, 0x000000ff, 0x000000ff, 0xffffffff, 0xffffffff,
+ 0xffffffff, 0x000000ff, 0xffffffff,
+ /* 22th byte of body: 0xe9 */
+ 0xffffffff, 0xffffffff, 0xffffffff, 0x000000ff, 0xffffffff,
+ 0x000000ff, 0x000000ff, 0xffffffff,
+ /* 23th byte of body: 0x00 */
+ 0x000000ff, 0x000000ff, 0x000000ff, 0x000000ff, 0x000000ff,
+ 0x000000ff, 0x000000ff, 0x000000ff,
+ /* 24th byte of body: 0x00 */
+ 0x000000ff, 0x000000ff, 0x000000ff, 0x000000ff, 0x000000ff,
+ 0x000000ff, 0x000000ff, 0x000000ff,
+ /* 25th byte of body: 0xff */
+ 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+ 0xffffffff, 0xffffffff, 0xffffffff,
+ /* 26th byte of body: 0xff */
+ 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+ 0xffffffff, 0xffffffff, 0xffffffff,
+ /* 27th byte of body: 0xff */
+ 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+ 0xffffffff, 0xffffffff, 0xffffffff,
+ /* 28th byte of body: 0xff */
+ 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+ 0xffffffff, 0xffffffff, 0xffffffff,
+ /* 29th byte of body: 0x00 */
+ 0x000000ff, 0x000000ff, 0x000000ff, 0x000000ff, 0x000000ff,
+ 0x000000ff, 0x000000ff, 0x000000ff,
+ /* 30th byte of body: 0x00 */
+ 0x000000ff, 0x000000ff, 0x000000ff, 0x000000ff, 0x000000ff,
+ 0x000000ff, 0x000000ff, 0x000000ff,
+ /* 31th byte of body: 0xff */
+ 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+ 0xffffffff, 0xffffffff, 0xffffffff,
+ /* 32th byte of body: 0xff */
+ 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+ 0xffffffff, 0xffffffff, 0xffffffff]}],
+ // Record 2.
+ [{width: 0x10,
+ height: 0x10,
+ pixels: [0xff0000ff, 0xff0000ff, 0xff0000ff, 0xff0000ff, 0xff0000ff,
+ 0xff0000ff, 0xff0000ff, 0xff0000ff, 0xff0000ff, 0xff0000ff,
+ 0xff0000ff, 0xff0000ff, 0xff0000ff, 0xff0000ff, 0xff0000ff,
+ 0xff0000ff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+ 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+ 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+ 0xffffffff, 0xffffffff, 0x00ff00ff, 0x00ff00ff, 0x00ff00ff,
+ 0x00ff00ff, 0x00ff00ff, 0x00ff00ff, 0x00ff00ff, 0x00ff00ff,
+ 0x00ff00ff, 0x00ff00ff, 0x00ff00ff, 0x00ff00ff, 0x00ff00ff,
+ 0x00ff00ff, 0x00ff00ff, 0x00ff00ff, 0x00ff00ff, 0x00ff00ff,
+ 0x00ff00ff, 0x00ff00ff, 0x00ff00ff, 0x00ff00ff, 0x00ff00ff,
+ 0x00ff00ff, 0x00ff00ff, 0x00ff00ff, 0x00ff00ff, 0x00ff00ff,
+ 0x00ff00ff, 0x00ff00ff, 0x00ff00ff, 0x00ff00ff, 0xffffffff,
+ 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+ 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+ 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+ 0x0000ffff, 0xffffffff, 0xffffffff, 0x0000ffff, 0x0000ffff,
+ 0x0000ffff, 0xffffffff, 0x0000ffff, 0x0000ffff, 0x0000ffff,
+ 0x0000ffff, 0xffffffff, 0x0000ffff, 0xffffffff, 0xffffffff,
+ 0x0000ffff, 0x0000ffff, 0xffffffff, 0x0000ffff, 0xffffffff,
+ 0xffffffff, 0xffffffff, 0xffffffff, 0x0000ffff, 0xffffffff,
+ 0xffffffff, 0x0000ffff, 0xffffffff, 0x0000ffff, 0x0000ffff,
+ 0xffffffff, 0x0000ffff, 0x0000ffff, 0xffffffff, 0x0000ffff,
+ 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0x0000ffff,
+ 0xffffffff, 0xffffffff, 0x0000ffff, 0xffffffff, 0x0000ffff,
+ 0x0000ffff, 0xffffffff, 0x0000ffff, 0x0000ffff, 0xffffffff,
+ 0x0000ffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+ 0x0000ffff, 0xffffffff, 0xffffffff, 0x0000ffff, 0xffffffff,
+ 0x0000ffff, 0xffffffff, 0x0000ffff, 0x0000ffff, 0x0000ffff,
+ 0xffffffff, 0x0000ffff, 0xffffffff, 0xffffffff, 0xffffffff,
+ 0xffffffff, 0x0000ffff, 0xffffffff, 0xffffffff, 0x0000ffff,
+ 0xffffffff, 0x0000ffff, 0xffffffff, 0x0000ffff, 0x0000ffff,
+ 0x0000ffff, 0xffffffff, 0xffffffff, 0x0000ffff, 0x0000ffff,
+ 0x0000ffff, 0xffffffff, 0x0000ffff, 0x0000ffff, 0x0000ffff,
+ 0x0000ffff, 0xffffffff, 0x0000ffff, 0xffffffff, 0xffffffff,
+ 0x0000ffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+ 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+ 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+ 0xffffffff, 0xffffffff, 0x00ff00ff, 0x00ff00ff, 0x00ff00ff,
+ 0x00ff00ff, 0x00ff00ff, 0x00ff00ff, 0x00ff00ff, 0x00ff00ff,
+ 0x00ff00ff, 0x00ff00ff, 0x00ff00ff, 0x00ff00ff, 0x00ff00ff,
+ 0x00ff00ff, 0x00ff00ff, 0x00ff00ff, 0x00ff00ff, 0x00ff00ff,
+ 0x00ff00ff, 0x00ff00ff, 0x00ff00ff, 0x00ff00ff, 0x00ff00ff,
+ 0x00ff00ff, 0x00ff00ff, 0x00ff00ff, 0x00ff00ff, 0x00ff00ff,
+ 0x00ff00ff, 0x00ff00ff, 0x00ff00ff, 0x00ff00ff, 0xffffffff,
+ 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+ 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+ 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+ 0xff0000ff, 0xff0000ff, 0xff0000ff, 0xff0000ff, 0xff0000ff,
+ 0xff0000ff, 0xff0000ff, 0xff0000ff, 0xff0000ff, 0xff0000ff,
+ 0xff0000ff, 0xff0000ff, 0xff0000ff, 0xff0000ff, 0xff0000ff,
+ 0xff0000ff]}],
+ // Record 3.
+ [{width: 0x03,
+ height: 0x03,
+ pixels: [0x000102ff, 0x060708ff, 0x0c0d0eff, 0x121314ff, 0x18191aff,
+ 0x1e1f20ff, 0x242526ff, 0x2a2b2cff, 0x333435ff]},
+ {width: 0x10,
+ height: 0x10,
+ pixels: [/* 1st byte of body: 0xff */
+ 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+ 0xffffffff, 0xffffffff, 0xffffffff,
+ /* 2nd byte of body: 0xff */
+ 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+ 0xffffffff, 0xffffffff, 0xffffffff,
+ /* 3rd byte of body: 0x00 */
+ 0x000000ff, 0x000000ff, 0x000000ff, 0x000000ff, 0x000000ff,
+ 0x000000ff, 0x000000ff, 0x000000ff,
+ /* 4th byte of body: 0x00 */
+ 0x000000ff, 0x000000ff, 0x000000ff, 0x000000ff, 0x000000ff,
+ 0x000000ff, 0x000000ff, 0x000000ff,
+ /* 5th byte of body: 0xff */
+ 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+ 0xffffffff, 0xffffffff, 0xffffffff,
+ /* 6th byte of body: 0xff */
+ 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+ 0xffffffff, 0xffffffff, 0xffffffff,
+ /* 7th byte of body: 0xff */
+ 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+ 0xffffffff, 0xffffffff, 0xffffffff,
+ /* 8th byte of body: 0xff */
+ 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+ 0xffffffff, 0xffffffff, 0xffffffff,
+ /* 9th byte of body: 0x00 */
+ 0x000000ff, 0x000000ff, 0x000000ff, 0x000000ff, 0x000000ff,
+ 0x000000ff, 0x000000ff, 0x000000ff,
+ /* 10th byte of body: 0x00 */
+ 0x000000ff, 0x000000ff, 0x000000ff, 0x000000ff, 0x000000ff,
+ 0x000000ff, 0x000000ff, 0x000000ff,
+ /* 11th byte of body: 0x9d */
+ 0xffffffff, 0x000000ff, 0x000000ff, 0xffffffff, 0xffffffff,
+ 0xffffffff, 0x000000ff, 0xffffffff,
+ /* 12th byte of body: 0xe9 */
+ 0xffffffff, 0xffffffff, 0xffffffff, 0x000000ff, 0xffffffff,
+ 0x000000ff, 0x000000ff, 0xffffffff,
+ /* 13th byte of body: 0xa1 */
+ 0xffffffff, 0x000000ff, 0xffffffff, 0x000000ff, 0x000000ff,
+ 0x000000ff, 0x000000ff, 0xffffffff,
+ /* 14th byte of body: 0x2d */
+ 0x000000ff, 0x000000ff, 0xffffffff, 0x000000ff, 0xffffffff,
+ 0xffffffff, 0x000000ff, 0xffffffff,
+ /* 15th byte of body: 0xa1 */
+ 0xffffffff, 0x000000ff, 0xffffffff, 0x000000ff, 0x000000ff,
+ 0x000000ff, 0x000000ff, 0xffffffff,
+ /* 16th byte of body: 0x2d */
+ 0x000000ff, 0x000000ff, 0xffffffff, 0x000000ff, 0xffffffff,
+ 0xffffffff, 0x000000ff, 0xffffffff,
+ /* 17th byte of body: 0xa1 */
+ 0xffffffff, 0x000000ff, 0xffffffff, 0x000000ff, 0x000000ff,
+ 0x000000ff, 0x000000ff, 0xffffffff,
+ /* 18th byte of body: 0x2b */
+ 0x000000ff, 0x000000ff, 0xffffffff, 0x000000ff, 0xffffffff,
+ 0x000000ff, 0xffffffff, 0xffffffff,
+ /* 19th byte of body: 0xa1 */
+ 0xffffffff, 0x000000ff, 0xffffffff, 0x000000ff, 0x000000ff,
+ 0x000000ff, 0x000000ff, 0xffffffff,
+ /* 20th byte of body: 0x2b */
+ 0x000000ff, 0x000000ff, 0xffffffff, 0x000000ff, 0xffffffff,
+ 0x000000ff, 0xffffffff, 0xffffffff,
+ /* 21th byte of body: 0x9d */
+ 0xffffffff, 0x000000ff, 0x000000ff, 0xffffffff, 0xffffffff,
+ 0xffffffff, 0x000000ff, 0xffffffff,
+ /* 22th byte of body: 0xe9 */
+ 0xffffffff, 0xffffffff, 0xffffffff, 0x000000ff, 0xffffffff,
+ 0x000000ff, 0x000000ff, 0xffffffff,
+ /* 23th byte of body: 0x00 */
+ 0x000000ff, 0x000000ff, 0x000000ff, 0x000000ff, 0x000000ff,
+ 0x000000ff, 0x000000ff, 0x000000ff,
+ /* 24th byte of body: 0x00 */
+ 0x000000ff, 0x000000ff, 0x000000ff, 0x000000ff, 0x000000ff,
+ 0x000000ff, 0x000000ff, 0x000000ff,
+ /* 25th byte of body: 0xff */
+ 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+ 0xffffffff, 0xffffffff, 0xffffffff,
+ /* 26th byte of body: 0xff */
+ 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+ 0xffffffff, 0xffffffff, 0xffffffff,
+ /* 27th byte of body: 0xff */
+ 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+ 0xffffffff, 0xffffffff, 0xffffffff,
+ /* 28th byte of body: 0xff */
+ 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+ 0xffffffff, 0xffffffff, 0xffffffff,
+ /* 29th byte of body: 0x00 */
+ 0x000000ff, 0x000000ff, 0x000000ff, 0x000000ff, 0x000000ff,
+ 0x000000ff, 0x000000ff, 0x000000ff,
+ /* 30th byte of body: 0x00 */
+ 0x000000ff, 0x000000ff, 0x000000ff, 0x000000ff, 0x000000ff,
+ 0x000000ff, 0x000000ff, 0x000000ff,
+ /* 31th byte of body: 0xff */
+ 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+ 0xffffffff, 0xffffffff, 0xffffffff,
+ /* 32th byte of body: 0xff */
+ 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
+ 0xffffffff, 0xffffffff, 0xffffffff]}],
+ // Record 4.
+ [{width: 0x04,
+ height: 0x04,
+ pixels: [0x00000000, 0x00000000, 0x2a2b2cff, 0x2a2b2cff, 0x272829ff,
+ 0x272829ff, 0x242526ff, 0x242526ff, 0x212223ff, 0x212223ff,
+ 0x1e1f20ff, 0x1e1f20ff, 0x1b1c1dff, 0x1b1c1dff, 0x18191aff,
+ 0x18191aff]}]]};
+
+ function do_test() {
+ simRecordHelper.readIMG = function fakeReadIMG(recordNumber, onsuccess, onerror) {
+ onsuccess(test_data.rawData[recordNumber]);
+ };
+
+ let onsuccess = function(icons) {
+ equal(icons.length, test_data.expected.length);
+ for (let i = 0; i < icons.length; i++) {
+ for (let j = 0; j < icons[i].length; j++) {
+ // Read the j_th image from the i_th record.
+ let icon = icons[i][j];
+ let expected = test_data.expected[i][j];
+ equal(icon.width, expected.width);
+ equal(icon.height, expected.height);
+ equal(icon.pixels.length, expected.pixels.length);
+ for (let k = 0; k < icon.pixels.length; k++) {
+ equal(icon.pixels[k], expected.pixels[k]);
+ }
+ }
+ }
+ };
+
+ let recordNumbers = [0, 1, 2, 3];
+ iconLoader.loadIcons(recordNumbers, onsuccess);
+ }
+
+ do_test();
+ run_next_test();
+});
diff --git a/dom/system/gonk/tests/test_ril_worker_icc_SimRecordHelper.js b/dom/system/gonk/tests/test_ril_worker_icc_SimRecordHelper.js
new file mode 100644
index 000000000..6500cc663
--- /dev/null
+++ b/dom/system/gonk/tests/test_ril_worker_icc_SimRecordHelper.js
@@ -0,0 +1,1648 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+subscriptLoader.loadSubScript("resource://gre/modules/ril_consts.js", this);
+
+function run_test() {
+ run_next_test();
+}
+
+/**
+ * Verify reading EF_AD and parsing MCC/MNC
+ */
+add_test(function test_reading_ad_and_parsing_mcc_mnc() {
+ let worker = newUint8Worker();
+ let context = worker.ContextPool._contexts[0];
+ let record = context.SimRecordHelper;
+ let helper = context.GsmPDUHelper;
+ let ril = context.RIL;
+ let buf = context.Buf;
+ let io = context.ICCIOHelper;
+
+ function do_test(mncLengthInEf, imsi, expectedMcc, expectedMnc) {
+ ril.iccInfoPrivate.imsi = imsi;
+
+ io.loadTransparentEF = function fakeLoadTransparentEF(options) {
+ let ad = [0x00, 0x00, 0x00];
+ if (typeof mncLengthInEf === 'number') {
+ ad.push(mncLengthInEf);
+ }
+
+ // Write data size
+ buf.writeInt32(ad.length * 2);
+
+ // Write data
+ for (let i = 0; i < ad.length; i++) {
+ helper.writeHexOctet(ad[i]);
+ }
+
+ // Write string delimiter
+ buf.writeStringDelimiter(ad.length * 2);
+
+ if (options.callback) {
+ options.callback(options);
+ }
+ };
+
+ record.readAD();
+
+ equal(ril.iccInfo.mcc, expectedMcc);
+ equal(ril.iccInfo.mnc, expectedMnc);
+ }
+
+ do_test(undefined, "466923202422409", "466", "92" );
+ do_test(0x00, "466923202422409", "466", "92" );
+ do_test(0x01, "466923202422409", "466", "92" );
+ do_test(0x02, "466923202422409", "466", "92" );
+ do_test(0x03, "466923202422409", "466", "923");
+ do_test(0x04, "466923202422409", "466", "92" );
+ do_test(0xff, "466923202422409", "466", "92" );
+
+ do_test(undefined, "310260542718417", "310", "260");
+ do_test(0x00, "310260542718417", "310", "260");
+ do_test(0x01, "310260542718417", "310", "260");
+ do_test(0x02, "310260542718417", "310", "26" );
+ do_test(0x03, "310260542718417", "310", "260");
+ do_test(0x04, "310260542718417", "310", "260");
+ do_test(0xff, "310260542718417", "310", "260");
+
+ run_next_test();
+});
+
+add_test(function test_reading_optional_efs() {
+ let worker = newUint8Worker();
+ let context = worker.ContextPool._contexts[0];
+ let record = context.SimRecordHelper;
+ let gsmPdu = context.GsmPDUHelper;
+ let ril = context.RIL;
+ let buf = context.Buf;
+ let io = context.ICCIOHelper;
+
+ function buildSST(supportedEf) {
+ let sst = [];
+ let len = supportedEf.length;
+ for (let i = 0; i < len; i++) {
+ let index, bitmask, iccService;
+ if (ril.appType === CARD_APPTYPE_SIM) {
+ iccService = GECKO_ICC_SERVICES.sim[supportedEf[i]];
+ iccService -= 1;
+ index = Math.floor(iccService / 4);
+ bitmask = 2 << ((iccService % 4) << 1);
+ } else if (ril.appType === CARD_APPTYPE_USIM){
+ iccService = GECKO_ICC_SERVICES.usim[supportedEf[i]];
+ iccService -= 1;
+ index = Math.floor(iccService / 8);
+ bitmask = 1 << ((iccService % 8) << 0);
+ }
+
+ if (sst) {
+ sst[index] |= bitmask;
+ }
+ }
+ return sst;
+ }
+
+ ril.updateCellBroadcastConfig = function fakeUpdateCellBroadcastConfig() {
+ // Ignore updateCellBroadcastConfig after reading SST
+ };
+
+ function do_test(sst, supportedEf) {
+ // Clone supportedEf to local array for testing
+ let testEf = supportedEf.slice(0);
+
+ record.readMSISDN = function fakeReadMSISDN() {
+ testEf.splice(testEf.indexOf("MSISDN"), 1);
+ };
+
+ record.readMBDN = function fakeReadMBDN() {
+ testEf.splice(testEf.indexOf("MDN"), 1);
+ };
+
+ record.readMWIS = function fakeReadMWIS() {
+ testEf.splice(testEf.indexOf("MWIS"), 1);
+ };
+
+ io.loadTransparentEF = function fakeLoadTransparentEF(options) {
+ // Write data size
+ buf.writeInt32(sst.length * 2);
+
+ // Write data
+ for (let i = 0; i < sst.length; i++) {
+ gsmPdu.writeHexOctet(sst[i] || 0);
+ }
+
+ // Write string delimiter
+ buf.writeStringDelimiter(sst.length * 2);
+
+ if (options.callback) {
+ options.callback(options);
+ }
+
+ if (testEf.length !== 0) {
+ do_print("Un-handled EF: " + JSON.stringify(testEf));
+ ok(false);
+ }
+ };
+
+ record.readSST();
+ }
+
+ // TODO: Add all necessary optional EFs eventually
+ let supportedEf = ["MSISDN", "MDN", "MWIS"];
+ ril.appType = CARD_APPTYPE_SIM;
+ do_test(buildSST(supportedEf), supportedEf);
+ ril.appType = CARD_APPTYPE_USIM;
+ do_test(buildSST(supportedEf), supportedEf);
+
+ run_next_test();
+});
+
+/**
+ * Verify fetchSimRecords.
+ */
+add_test(function test_fetch_sim_records() {
+ let worker = newWorker();
+ let context = worker.ContextPool._contexts[0];
+ let RIL = context.RIL;
+ let iccRecord = context.ICCRecordHelper;
+ let simRecord = context.SimRecordHelper;
+
+ function testFetchSimRecordes(expectCalled, expectCphsSuccess) {
+ let ifCalled = [];
+
+ RIL.getIMSI = function() {
+ ifCalled.push("getIMSI");
+ };
+
+ simRecord.readAD = function() {
+ ifCalled.push("readAD");
+ };
+
+ simRecord.readCphsInfo = function(onsuccess, onerror) {
+ ifCalled.push("readCphsInfo");
+ if (expectCphsSuccess) {
+ onsuccess();
+ } else {
+ onerror();
+ }
+ };
+
+ simRecord.readSST = function() {
+ ifCalled.push("readSST");
+ };
+
+ simRecord.fetchSimRecords();
+
+ for (let i = 0; i < expectCalled.length; i++ ) {
+ if (ifCalled[i] != expectCalled[i]) {
+ do_print(expectCalled[i] + " is not called.");
+ ok(false);
+ }
+ }
+ }
+
+ let expectCalled = ["getIMSI", "readAD", "readCphsInfo", "readSST"];
+ testFetchSimRecordes(expectCalled, true);
+ testFetchSimRecordes(expectCalled, false);
+
+ run_next_test();
+});
+
+/**
+ * Verify SimRecordHelper.readMWIS
+ */
+add_test(function test_read_mwis() {
+ let worker = newUint8Worker();
+ let context = worker.ContextPool._contexts[0];
+ let helper = context.GsmPDUHelper;
+ let recordHelper = context.SimRecordHelper;
+ let buf = context.Buf;
+ let io = context.ICCIOHelper;
+ let mwisData;
+ let postedMessage;
+
+ worker.postMessage = function fakePostMessage(message) {
+ postedMessage = message;
+ };
+
+ io.loadLinearFixedEF = function fakeLoadLinearFixedEF(options) {
+ if (mwisData) {
+ // Write data size
+ buf.writeInt32(mwisData.length * 2);
+
+ // Write MWIS
+ for (let i = 0; i < mwisData.length; i++) {
+ helper.writeHexOctet(mwisData[i]);
+ }
+
+ // Write string delimiter
+ buf.writeStringDelimiter(mwisData.length * 2);
+
+ options.recordSize = mwisData.length;
+ if (options.callback) {
+ options.callback(options);
+ }
+ } else {
+ do_print("mwisData[] is not set.");
+ }
+ };
+
+ function buildMwisData(isActive, msgCount) {
+ if (msgCount < 0 || msgCount === GECKO_VOICEMAIL_MESSAGE_COUNT_UNKNOWN) {
+ msgCount = 0;
+ } else if (msgCount > 255) {
+ msgCount = 255;
+ }
+
+ mwisData = [ (isActive) ? 0x01 : 0x00,
+ msgCount,
+ 0xFF, 0xFF, 0xFF ];
+ }
+
+ function do_test(isActive, msgCount) {
+ buildMwisData(isActive, msgCount);
+ recordHelper.readMWIS();
+
+ equal("iccmwis", postedMessage.rilMessageType);
+ equal(isActive, postedMessage.mwi.active);
+ equal((isActive) ? msgCount : 0, postedMessage.mwi.msgCount);
+ }
+
+ do_test(true, GECKO_VOICEMAIL_MESSAGE_COUNT_UNKNOWN);
+ do_test(true, 1);
+ do_test(true, 255);
+
+ do_test(false, 0);
+ do_test(false, 255); // Test the corner case when mwi is disable with incorrect msgCount.
+
+ run_next_test();
+});
+
+/**
+ * Verify SimRecordHelper.updateMWIS
+ */
+add_test(function test_update_mwis() {
+ let worker = newUint8Worker();
+ let context = worker.ContextPool._contexts[0];
+ let pduHelper = context.GsmPDUHelper;
+ let ril = context.RIL;
+ ril.appType = CARD_APPTYPE_USIM;
+ ril.iccInfoPrivate.mwis = [0x00, 0x00, 0x00, 0x00, 0x00];
+ let recordHelper = context.SimRecordHelper;
+ let buf = context.Buf;
+ let ioHelper = context.ICCIOHelper;
+ let recordSize = ril.iccInfoPrivate.mwis.length;
+ let recordNum = 1;
+
+ ioHelper.updateLinearFixedEF = function(options) {
+ options.pathId = context.ICCFileHelper.getEFPath(options.fileId);
+ options.command = ICC_COMMAND_UPDATE_RECORD;
+ options.p1 = options.recordNumber;
+ options.p2 = READ_RECORD_ABSOLUTE_MODE;
+ options.p3 = recordSize;
+ ril.iccIO(options);
+ };
+
+ function do_test(isActive, count) {
+ let mwis = ril.iccInfoPrivate.mwis;
+ let isUpdated = false;
+
+ function buildMwisData() {
+ let result = mwis.slice(0);
+ result[0] = isActive? (mwis[0] | 0x01) : (mwis[0] & 0xFE);
+ result[1] = (count === GECKO_VOICEMAIL_MESSAGE_COUNT_UNKNOWN) ? 0 : count;
+
+ return result;
+ }
+
+ buf.sendParcel = function() {
+ isUpdated = true;
+
+ // Request Type.
+ equal(this.readInt32(), REQUEST_SIM_IO);
+
+ // Token : we don't care
+ this.readInt32();
+
+ // command.
+ equal(this.readInt32(), ICC_COMMAND_UPDATE_RECORD);
+
+ // fileId.
+ equal(this.readInt32(), ICC_EF_MWIS);
+
+ // pathId.
+ equal(this.readString(),
+ EF_PATH_MF_SIM + ((ril.appType === CARD_APPTYPE_USIM) ? EF_PATH_ADF_USIM : EF_PATH_DF_GSM));
+
+ // p1.
+ equal(this.readInt32(), recordNum);
+
+ // p2.
+ equal(this.readInt32(), READ_RECORD_ABSOLUTE_MODE);
+
+ // p3.
+ equal(this.readInt32(), recordSize);
+
+ // data.
+ let strLen = this.readInt32();
+ equal(recordSize * 2, strLen);
+ let expectedMwis = buildMwisData();
+ for (let i = 0; i < recordSize; i++) {
+ equal(expectedMwis[i], pduHelper.readHexOctet());
+ }
+ this.readStringDelimiter(strLen);
+
+ // pin2.
+ equal(this.readString(), null);
+
+ // AID. Ignore because it's from modem.
+ this.readInt32();
+ };
+
+ ok(!isUpdated);
+
+ recordHelper.updateMWIS({ active: isActive,
+ msgCount: count });
+
+ ok((ril.iccInfoPrivate.mwis) ? isUpdated : !isUpdated);
+ }
+
+ do_test(true, GECKO_VOICEMAIL_MESSAGE_COUNT_UNKNOWN);
+ do_test(true, 1);
+ do_test(true, 255);
+
+ do_test(false, 0);
+
+ // Test if Path ID is correct for SIM.
+ ril.appType = CARD_APPTYPE_SIM;
+ do_test(false, 0);
+
+ // Test if loadLinearFixedEF() is not invoked in updateMWIS() when
+ // EF_MWIS is not loaded/available.
+ delete ril.iccInfoPrivate.mwis;
+ do_test(false, 0);
+
+ run_next_test();
+});
+
+/**
+ * Verify the call flow of receiving Class 2 SMS stored in SIM:
+ * 1. UNSOLICITED_RESPONSE_NEW_SMS_ON_SIM.
+ * 2. SimRecordHelper.readSMS().
+ * 3. sendChromeMessage() with rilMessageType == "sms-received".
+ */
+add_test(function test_read_new_sms_on_sim() {
+ // Instead of reusing newUint8Worker defined in this file,
+ // we define our own worker to fake the methods in WorkerBuffer dynamically.
+ function newSmsOnSimWorkerHelper() {
+ let _postedMessage;
+ let _worker = newWorker({
+ postRILMessage: function(data) {
+ },
+ postMessage: function(message) {
+ _postedMessage = message;
+ }
+ });
+
+ _worker.debug = do_print;
+
+ return {
+ get postedMessage() {
+ return _postedMessage;
+ },
+ get worker() {
+ return _worker;
+ },
+ fakeWokerBuffer: function() {
+ let context = _worker.ContextPool._contexts[0];
+ let index = 0; // index for read
+ let buf = [];
+ context.Buf.writeUint8 = function(value) {
+ buf.push(value);
+ };
+ context.Buf.readUint8 = function() {
+ return buf[index++];
+ };
+ context.Buf.seekIncoming = function(offset) {
+ index += offset;
+ };
+ context.Buf.getReadAvailable = function() {
+ return buf.length - index;
+ };
+ }
+ };
+ }
+
+ let workerHelper = newSmsOnSimWorkerHelper();
+ let worker = workerHelper.worker;
+ let context = worker.ContextPool._contexts[0];
+
+ context.ICCIOHelper.loadLinearFixedEF = function fakeLoadLinearFixedEF(options) {
+ // SimStatus: Unread, SMSC:+0123456789, Sender: +9876543210, Text: How are you?
+ let SimSmsPduHex = "0306911032547698040A9189674523010000208062917314080CC8F71D14969741F977FD07"
+ // In 4.2.25 EF_SMS Short Messages of 3GPP TS 31.102:
+ // 1. Record length == 176 bytes.
+ // 2. Any bytes in the record following the TPDU shall be filled with 'FF'.
+ + "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"
+ + "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"
+ + "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"
+ + "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF";
+
+ workerHelper.fakeWokerBuffer();
+
+ context.Buf.writeString(SimSmsPduHex);
+
+ options.recordSize = 176; // Record length is fixed to 176 bytes.
+ if (options.callback) {
+ options.callback(options);
+ }
+ };
+
+ function newSmsOnSimParcel() {
+ let data = new Uint8Array(4 + 4); // Int32List with 1 element.
+ let offset = 0;
+
+ function writeInt(value) {
+ data[offset++] = value & 0xFF;
+ data[offset++] = (value >> 8) & 0xFF;
+ data[offset++] = (value >> 16) & 0xFF;
+ data[offset++] = (value >> 24) & 0xFF;
+ }
+
+ writeInt(1); // Length of Int32List
+ writeInt(1); // RecordNum = 1.
+
+ return newIncomingParcel(-1,
+ RESPONSE_TYPE_UNSOLICITED,
+ UNSOLICITED_RESPONSE_NEW_SMS_ON_SIM,
+ data);
+ }
+
+ function do_test() {
+ worker.onRILMessage(0, newSmsOnSimParcel());
+
+ let postedMessage = workerHelper.postedMessage;
+
+ equal("sms-received", postedMessage.rilMessageType);
+ equal("+0123456789", postedMessage.SMSC);
+ equal("+9876543210", postedMessage.sender);
+ equal("How are you?", postedMessage.body);
+ }
+
+ do_test();
+
+ run_next_test();
+});
+
+/**
+ * Verify the result of updateDisplayCondition after reading EF_SPDI | EF_SPN.
+ */
+add_test(function test_update_display_condition() {
+ let worker = newUint8Worker();
+ let context = worker.ContextPool._contexts[0];
+ let record = context.SimRecordHelper;
+ let helper = context.GsmPDUHelper;
+ let ril = context.RIL;
+ let buf = context.Buf;
+ let io = context.ICCIOHelper;
+
+ function do_test_spdi() {
+ // No EF_SPN, but having EF_SPDI.
+ // It implies "ril.iccInfoPrivate.spnDisplayCondition = undefined;".
+ io.loadTransparentEF = function fakeLoadTransparentEF(options) {
+ // PLMN lists are : 234-136 and 466-92.
+ let spdi = [0xA3, 0x0B, 0x80, 0x09, 0x32, 0x64, 0x31, 0x64, 0x26, 0x9F,
+ 0xFF, 0xFF, 0xFF];
+
+ // Write data size.
+ buf.writeInt32(spdi.length * 2);
+
+ // Write data.
+ for (let i = 0; i < spdi.length; i++) {
+ helper.writeHexOctet(spdi[i]);
+ }
+
+ // Write string delimiter.
+ buf.writeStringDelimiter(spdi.length * 2);
+
+ if (options.callback) {
+ options.callback(options);
+ }
+ };
+
+ record.readSPDI();
+
+ equal(ril.iccInfo.isDisplayNetworkNameRequired, true);
+ equal(ril.iccInfo.isDisplaySpnRequired, false);
+ }
+
+ function do_test_spn(displayCondition,
+ expectedPlmnNameDisplay,
+ expectedSpnDisplay) {
+ io.loadTransparentEF = function fakeLoadTransparentEF(options) {
+ // "Android" as Service Provider Name.
+ let spn = [0x41, 0x6E, 0x64, 0x72, 0x6F, 0x69, 0x64];
+ if (typeof displayCondition === 'number') {
+ spn.unshift(displayCondition);
+ }
+
+ // Write data size.
+ buf.writeInt32(spn.length * 2);
+
+ // Write data.
+ for (let i = 0; i < spn.length; i++) {
+ helper.writeHexOctet(spn[i]);
+ }
+
+ // Write string delimiter.
+ buf.writeStringDelimiter(spn.length * 2);
+
+ if (options.callback) {
+ options.callback(options);
+ }
+ };
+
+ record.readSPN();
+
+ equal(ril.iccInfo.isDisplayNetworkNameRequired, expectedPlmnNameDisplay);
+ equal(ril.iccInfo.isDisplaySpnRequired, expectedSpnDisplay);
+ }
+
+ // Create empty operator object.
+ ril.operator = {};
+ // Setup SIM MCC-MNC to 310-260 as home network.
+ ril.iccInfo.mcc = 310;
+ ril.iccInfo.mnc = 260;
+
+ do_test_spdi();
+
+ // No network.
+ do_test_spn(0x00, true, true);
+ do_test_spn(0x01, true, true);
+ do_test_spn(0x02, true, false);
+ do_test_spn(0x03, true, false);
+
+ // Home network.
+ ril.operator.mcc = 310;
+ ril.operator.mnc = 260;
+ do_test_spn(0x00, false, true);
+ do_test_spn(0x01, true, true);
+ do_test_spn(0x02, false, true);
+ do_test_spn(0x03, true, true);
+
+ // Not HPLMN but in PLMN list.
+ ril.iccInfoPrivate.SPDI = [{"mcc":"234","mnc":"136"},{"mcc":"466","mnc":"92"}];
+ ril.operator.mcc = 466;
+ ril.operator.mnc = 92;
+ do_test_spn(0x00, false, true);
+ do_test_spn(0x01, true, true);
+ do_test_spn(0x02, false, true);
+ do_test_spn(0x03, true, true);
+ ril.iccInfoPrivate.SPDI = null; // reset SPDI to null;
+
+ // Non-Home network.
+ ril.operator.mcc = 466;
+ ril.operator.mnc = 01;
+ do_test_spn(0x00, true, true);
+ do_test_spn(0x01, true, true);
+ do_test_spn(0x02, true, false);
+ do_test_spn(0x03, true, false);
+
+ run_next_test();
+});
+
+/**
+ * Verify reading EF_IMG and EF_IIDF with ICC_IMG_CODING_SCHEME_BASIC
+ */
+add_test(function test_reading_img_basic() {
+ let worker = newUint8Worker();
+ let context = worker.ContextPool._contexts[0];
+ let record = context.SimRecordHelper;
+ let helper = context.GsmPDUHelper;
+ let ril = context.RIL;
+ let buf = context.Buf;
+ let io = context.ICCIOHelper;
+
+ let test_data = [
+ {img: [0x01, 0x05, 0x05, 0x11, 0x4f, 0x00, 0x00, 0x00, 0x00, 0x06],
+ iidf: [
+ [/* Header */
+ 0x05, 0x05,
+ /* Image body */
+ 0x11, 0x33, 0x55, 0xfe]],
+ expected: [
+ {width: 0x05,
+ height: 0x05,
+ codingScheme: ICC_IMG_CODING_SCHEME_BASIC,
+ body: [0x11, 0x33, 0x55, 0xfe]}]},
+ {img: [0x01, 0x05, 0x05, 0x11, 0x4f, 0x00, 0x00, 0x00, 0x00, 0x06,
+ /* Padding */
+ 0xff, 0xff],
+ iidf: [
+ [/* Header */
+ 0x05, 0x05,
+ /* Image body */
+ 0x11, 0x33, 0x55, 0xfe]],
+ expected: [
+ {width: 0x05,
+ height: 0x05,
+ codingScheme: ICC_IMG_CODING_SCHEME_BASIC,
+ body: [0x11, 0x33, 0x55, 0xfe]}]},
+ {img: [0x02, 0x10, 0x01, 0x11, 0x4f, 0x04, 0x00, 0x05, 0x00, 0x04, 0x10,
+ 0x01, 0x11, 0x4f, 0x05, 0x00, 0x05, 0x00, 0x04],
+ iidf: [
+ [/* Data offset */
+ 0xff, 0xff, 0xff, 0xff, 0xff,
+ /* Header */
+ 0x10, 0x01,
+ /* Image body */
+ 0x11, 0x99,
+ /* Trailing data */
+ 0xff, 0xff, 0xff],
+ [/* Data offset */
+ 0xff, 0xff, 0xff, 0xff, 0xff,
+ /* Header */
+ 0x10, 0x01,
+ /* Image body */
+ 0x99, 0x11]],
+ expected: [
+ {width: 0x10,
+ height: 0x01,
+ codingScheme: ICC_IMG_CODING_SCHEME_BASIC,
+ body: [0x11, 0x99]},
+ {width: 0x10,
+ height: 0x01,
+ codingScheme: ICC_IMG_CODING_SCHEME_BASIC,
+ body: [0x99, 0x11]}]},
+ {img: [0x01, 0x28, 0x20, 0x11, 0x4f, 0xac, 0x00, 0x0b, 0x00, 0xa2],
+ iidf: [
+ [/* Data offset */
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ /* Header */
+ 0x28, 0x20,
+ /* Image body */
+ 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a,
+ 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15,
+ 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20,
+ 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b,
+ 0x2c, 0x2d, 0x2e, 0x2f, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36,
+ 0x37, 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, 0x40, 0x41,
+ 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a, 0x4b, 0x4c,
+ 0x4d, 0x4e, 0x4f, 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57,
+ 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, 0x00, 0x01, 0x02,
+ 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d,
+ 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18,
+ 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, 0x21, 0x22, 0x23,
+ 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e,
+ 0x2f, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39,
+ 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f]],
+ expected: [
+ {width: 0x28,
+ height: 0x20,
+ codingScheme: ICC_IMG_CODING_SCHEME_BASIC,
+ body: [0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09,
+ 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13,
+ 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d,
+ 0x1e, 0x1f, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27,
+ 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, 0x30, 0x31,
+ 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b,
+ 0x3c, 0x3d, 0x3e, 0x3f, 0x40, 0x41, 0x42, 0x43, 0x44, 0x45,
+ 0x46, 0x47, 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f,
+ 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59,
+ 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, 0x00, 0x01, 0x02, 0x03,
+ 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d,
+ 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
+ 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, 0x21,
+ 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b,
+ 0x2c, 0x2d, 0x2e, 0x2f, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35,
+ 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f]}]}];
+
+ function do_test(img, iidf, expected) {
+ io.loadLinearFixedEF = function fakeLoadLinearFixedEF(options) {
+ // Write data size
+ buf.writeInt32(img.length * 2);
+
+ // Write data
+ for (let i = 0; i < img.length; i++) {
+ helper.writeHexOctet(img[i]);
+ }
+
+ // Write string delimiter
+ buf.writeStringDelimiter(img.length * 2);
+
+ if (options.callback) {
+ options.callback(options);
+ }
+ };
+
+ let instanceIndex = 0;
+ io.loadTransparentEF = function fakeLoadTransparentEF(options) {
+ // Write data size
+ buf.writeInt32(iidf[instanceIndex].length * 2);
+
+ // Write data
+ for (let i = 0; i < iidf[instanceIndex].length; i++) {
+ helper.writeHexOctet(iidf[instanceIndex][i]);
+ }
+
+ // Write string delimiter
+ buf.writeStringDelimiter(iidf[instanceIndex].length * 2);
+
+ instanceIndex++;
+
+ if (options.callback) {
+ options.callback(options);
+ }
+ };
+
+ let onsuccess = function(icons) {
+ equal(icons.length, expected.length);
+ for (let i = 0; i < icons.length; i++) {
+ let icon = icons[i];
+ let exp = expected[i];
+ equal(icon.width, exp.width);
+ equal(icon.height, exp.height);
+ equal(icon.codingScheme, exp.codingScheme);
+
+ equal(icon.body.length, exp.body.length);
+ for (let j = 0; j < icon.body.length; j++) {
+ equal(icon.body[j], exp.body[j]);
+ }
+ }
+ };
+ record.readIMG(0, onsuccess);
+ }
+
+ for (let i = 0; i< test_data.length; i++) {
+ do_test(test_data[i].img, test_data[i].iidf, test_data[i].expected);
+ }
+ run_next_test();
+});
+
+/**
+ * Verify reading EF_IMG and EF_IIDF with the case data length is not enough
+ */
+add_test(function test_reading_img_length_error() {
+ let worker = newUint8Worker();
+ let context = worker.ContextPool._contexts[0];
+ let record = context.SimRecordHelper;
+ let helper = context.GsmPDUHelper;
+ let ril = context.RIL;
+ let buf = context.Buf;
+ let io = context.ICCIOHelper;
+
+ let test_data = [
+ {/* Offset length not enough, should be 4. */
+ img: [0x01, 0x05, 0x05, 0x11, 0x4f, 0x00, 0x00, 0x04, 0x00, 0x06],
+ iidf: [0xff, 0xff, 0xff, // Offset.
+ 0x05, 0x05, 0x11, 0x22, 0x33, 0xfe]},
+ {/* iidf data length not enough, should be 6. */
+ img: [0x01, 0x05, 0x05, 0x11, 0x4f, 0x00, 0x00, 0x00, 0x00, 0x06],
+ iidf: [0x05, 0x05, 0x11, 0x22, 0x33]}];
+
+ function do_test(img, iidf) {
+ io.loadLinearFixedEF = function fakeLoadLinearFixedEF(options) {
+ // Write data size
+ buf.writeInt32(img.length * 2);
+
+ // Write data
+ for (let i = 0; i < img.length; i++) {
+ helper.writeHexOctet(img[i]);
+ }
+
+ // Write string delimiter
+ buf.writeStringDelimiter(img.length * 2);
+
+ if (options.callback) {
+ options.callback(options);
+ }
+ };
+
+ io.loadTransparentEF = function fakeLoadTransparentEF(options) {
+ // Write data size
+ buf.writeInt32(iidf.length * 2);
+
+ // Write data
+ for (let i = 0; i < iidf.length; i++) {
+ helper.writeHexOctet(iidf[i]);
+ }
+
+ // Write string delimiter
+ buf.writeStringDelimiter(iidf.length * 2);
+
+ if (options.callback) {
+ options.callback(options);
+ }
+ };
+
+ let onsuccess = function() {
+ do_print("onsuccess shouldn't be called.");
+ ok(false);
+ };
+
+ let onerror = function() {
+ do_print("onerror called as expected.");
+ ok(true);
+ };
+
+ record.readIMG(0, onsuccess, onerror);
+ }
+
+ for (let i = 0; i < test_data.length; i++) {
+ do_test(test_data[i].img, test_data[i].iidf);
+ }
+ run_next_test();
+});
+
+/**
+ * Verify reading EF_IMG and EF_IIDF with an invalid fileId
+ */
+add_test(function test_reading_img_invalid_fileId() {
+ let worker = newUint8Worker();
+ let context = worker.ContextPool._contexts[0];
+ let record = context.SimRecordHelper;
+ let helper = context.GsmPDUHelper;
+ let ril = context.RIL;
+ let buf = context.Buf;
+ let io = context.ICCIOHelper;
+
+ // Test invalid fileId: 0x5f00.
+ let img_test = [0x01, 0x05, 0x05, 0x11, 0x5f, 0x00, 0x00, 0x00, 0x00, 0x06];
+ let iidf_test = [0x05, 0x05, 0x11, 0x22, 0x33, 0xfe];
+
+ io.loadLinearFixedEF = function fakeLoadLinearFixedEF(options) {
+ // Write data size
+ buf.writeInt32(img_test.length * 2);
+
+ // Write data
+ for (let i = 0; i < img_test.length; i++) {
+ helper.writeHexOctet(img_test[i]);
+ }
+
+ // Write string delimiter
+ buf.writeStringDelimiter(img_test.length * 2);
+
+ if (options.callback) {
+ options.callback(options);
+ }
+ };
+
+ io.loadTransparentEF = function fakeLoadTransparentEF(options) {
+ // Write data size
+ buf.writeInt32(iidf_test.length * 2);
+
+ // Write data
+ for (let i = 0; i < iidf_test.length; i++) {
+ helper.writeHexOctet(iidf_test[i]);
+ }
+
+ // Write string delimiter
+ buf.writeStringDelimiter(iidf_test.length * 2);
+
+ if (options.callback) {
+ options.callback(options);
+ }
+ };
+
+ let onsuccess = function() {
+ do_print("onsuccess shouldn't be called.");
+ ok(false);
+ };
+
+ let onerror = function() {
+ do_print("onerror called as expected.");
+ ok(true);
+ };
+
+ record.readIMG(0, onsuccess, onerror);
+
+ run_next_test();
+});
+
+/**
+ * Verify reading EF_IMG with a wrong record length
+ */
+add_test(function test_reading_img_wrong_record_length() {
+ let worker = newUint8Worker();
+ let context = worker.ContextPool._contexts[0];
+ let record = context.SimRecordHelper;
+ let helper = context.GsmPDUHelper;
+ let ril = context.RIL;
+ let buf = context.Buf;
+ let io = context.ICCIOHelper;
+
+ let test_data = [
+ [0x01, 0x05, 0x05, 0x11, 0x4f, 0x00, 0x00, 0x00],
+ [0x02, 0x05, 0x05, 0x11, 0x4f, 0x00, 0x00, 0x00, 0x00, 0x06]];
+
+ function do_test(img) {
+ io.loadLinearFixedEF = function fakeLoadLinearFixedEF(options) {
+ // Write data size
+ buf.writeInt32(img.length * 2);
+
+ // Write data
+ for (let i = 0; i < img.length; i++) {
+ helper.writeHexOctet(img[i]);
+ }
+
+ // Write string delimiter
+ buf.writeStringDelimiter(img.length * 2);
+
+ if (options.callback) {
+ options.callback(options);
+ }
+ };
+
+ let onsuccess = function() {
+ do_print("onsuccess shouldn't be called.");
+ ok(false);
+ };
+
+ let onerror = function() {
+ do_print("onerror called as expected.");
+ ok(true);
+ };
+
+ record.readIMG(0, onsuccess, onerror);
+ }
+
+ for (let i = 0; i < test_data.length; i++) {
+ do_test(test_data[i]);
+ }
+ run_next_test();
+});
+
+/**
+ * Verify reading EF_IMG and EF_IIDF with ICC_IMG_CODING_SCHEME_COLOR
+ */
+add_test(function test_reading_img_color() {
+ let worker = newUint8Worker();
+ let context = worker.ContextPool._contexts[0];
+ let record = context.SimRecordHelper;
+ let helper = context.GsmPDUHelper;
+ let ril = context.RIL;
+ let buf = context.Buf;
+ let io = context.ICCIOHelper;
+
+ let test_data = [
+ {img: [0x01, 0x05, 0x05, 0x21, 0x4f, 0x11, 0x00, 0x00, 0x00, 0x13],
+ iidf: [
+ [/* Header */
+ 0x05, 0x05, 0x03, 0x08, 0x00, 0x13,
+ /* Image body */
+ 0x10, 0x20, 0x30, 0x40, 0x50, 0x60, 0x70, 0x80, 0x90, 0xa0,
+ 0xb0, 0xc0, 0xd0,
+ /* Clut entries */
+ 0x00, 0x01, 0x02,
+ 0x10, 0x11, 0x12,
+ 0x20, 0x21, 0x22,
+ 0x30, 0x31, 0x32,
+ 0x40, 0x41, 0x42,
+ 0x50, 0x51, 0x52,
+ 0x60, 0x61, 0x62,
+ 0x70, 0x71, 0x72]],
+ expected: [
+ {width: 0x05,
+ height: 0x05,
+ codingScheme: ICC_IMG_CODING_SCHEME_COLOR,
+ bitsPerImgPoint: 0x03,
+ numOfClutEntries: 0x08,
+ body: [0x10, 0x20, 0x30, 0x40, 0x50, 0x60, 0x70, 0x80, 0x90, 0xa0, 0xb0,
+ 0xc0, 0xd0],
+ clut: [0x00, 0x01, 0x02,
+ 0x10, 0x11, 0x12,
+ 0x20, 0x21, 0x22,
+ 0x30, 0x31, 0x32,
+ 0x40, 0x41, 0x42,
+ 0x50, 0x51, 0x52,
+ 0x60, 0x61, 0x62,
+ 0x70, 0x71, 0x72]}]},
+ {img: [0x02, 0x01, 0x06, 0x21, 0x4f, 0x33, 0x00, 0x02, 0x00, 0x08, 0x01,
+ 0x06, 0x21, 0x4f, 0x44, 0x00, 0x02, 0x00, 0x08],
+ iidf: [
+ [/* Data offset */
+ 0xff, 0xff,
+ /* Header */
+ 0x01, 0x06, 0x02, 0x04, 0x00, 0x0d,
+ /* Image body */
+ 0x40, 0x50,
+ /* Clut offset */
+ 0xaa, 0xbb, 0xcc,
+ /* Clut entries */
+ 0x01, 0x03, 0x05,
+ 0x21, 0x23, 0x25,
+ 0x41, 0x43, 0x45,
+ 0x61, 0x63, 0x65],
+ [/* Data offset */
+ 0xff, 0xff,
+ /* Header */
+ 0x01, 0x06, 0x02, 0x04, 0x00, 0x0d,
+ /* Image body */
+ 0x4f, 0x5f,
+ /* Clut offset */
+ 0xaa, 0xbb, 0xcc,
+ /* Clut entries */
+ 0x11, 0x13, 0x15,
+ 0x21, 0x23, 0x25,
+ 0x41, 0x43, 0x45,
+ 0x61, 0x63, 0x65]],
+ expected: [
+ {width: 0x01,
+ height: 0x06,
+ codingScheme: ICC_IMG_CODING_SCHEME_COLOR,
+ bitsPerImgPoint: 0x02,
+ numOfClutEntries: 0x04,
+ body: [0x40, 0x50],
+ clut: [0x01, 0x03, 0x05,
+ 0x21, 0x23, 0x25,
+ 0x41, 0x43, 0x45,
+ 0x61, 0x63, 0x65]},
+ {width: 0x01,
+ height: 0x06,
+ codingScheme: ICC_IMG_CODING_SCHEME_COLOR,
+ bitsPerImgPoint: 0x02,
+ numOfClutEntries: 0x04,
+ body: [0x4f, 0x5f],
+ clut: [0x11, 0x13, 0x15,
+ 0x21, 0x23, 0x25,
+ 0x41, 0x43, 0x45,
+ 0x61, 0x63, 0x65]}]}];
+
+ function do_test(img, iidf, expected) {
+ io.loadLinearFixedEF = function fakeLoadLinearFixedEF(options) {
+ // Write data size
+ buf.writeInt32(img.length * 2);
+
+ // Write data
+ for (let i = 0; i < img.length; i++) {
+ helper.writeHexOctet(img[i]);
+ }
+
+ // Write string delimiter
+ buf.writeStringDelimiter(img.length * 2);
+
+ if (options.callback) {
+ options.callback(options);
+ }
+ };
+
+ let instanceIndex = 0;
+ io.loadTransparentEF = function fakeLoadTransparentEF(options) {
+ // Write data size
+ buf.writeInt32(iidf[instanceIndex].length * 2);
+
+ // Write data
+ for (let i = 0; i < iidf[instanceIndex].length; i++) {
+ helper.writeHexOctet(iidf[instanceIndex][i]);
+ }
+
+ // Write string delimiter
+ buf.writeStringDelimiter(iidf[instanceIndex].length * 2);
+
+ instanceIndex++;
+
+ if (options.callback) {
+ options.callback(options);
+ }
+ };
+
+ let onsuccess = function(icons) {
+ equal(icons.length, expected.length);
+ for (let i = 0; i < icons.length; i++) {
+ let icon = icons[i];
+ let exp = expected[i];
+ equal(icon.width, exp.width);
+ equal(icon.height, exp.height);
+ equal(icon.codingScheme, exp.codingScheme);
+
+ equal(icon.body.length, exp.body.length);
+ for (let j = 0; j < icon.body.length; j++) {
+ equal(icon.body[j], exp.body[j]);
+ }
+
+ equal(icon.clut.length, exp.clut.length);
+ for (let j = 0; j < icon.clut.length; j++) {
+ equal(icon.clut[j], exp.clut[j]);
+ }
+ }
+ };
+
+ record.readIMG(0, onsuccess);
+ }
+
+ for (let i = 0; i< test_data.length; i++) {
+ do_test(test_data[i].img, test_data[i].iidf, test_data[i].expected);
+ }
+ run_next_test();
+});
+
+/**
+ * Verify reading EF_IMG and EF_IIDF with
+ * ICC_IMG_CODING_SCHEME_COLOR_TRANSPARENCY
+ */
+add_test(function test_reading_img_color() {
+ let worker = newUint8Worker();
+ let context = worker.ContextPool._contexts[0];
+ let record = context.SimRecordHelper;
+ let helper = context.GsmPDUHelper;
+ let ril = context.RIL;
+ let buf = context.Buf;
+ let io = context.ICCIOHelper;
+
+ let test_data = [
+ {img: [0x01, 0x05, 0x05, 0x22, 0x4f, 0x11, 0x00, 0x00, 0x00, 0x13],
+ iidf: [
+ [/* Header */
+ 0x05, 0x05, 0x03, 0x08, 0x00, 0x13,
+ /* Image body */
+ 0x10, 0x20, 0x30, 0x40, 0x50, 0x60, 0x70, 0x80, 0x90, 0xa0,
+ 0xb0, 0xc0, 0xd0,
+ /* Clut entries */
+ 0x00, 0x01, 0x02,
+ 0x10, 0x11, 0x12,
+ 0x20, 0x21, 0x22,
+ 0x30, 0x31, 0x32,
+ 0x40, 0x41, 0x42,
+ 0x50, 0x51, 0x52,
+ 0x60, 0x61, 0x62,
+ 0x70, 0x71, 0x72]],
+ expected: [
+ {width: 0x05,
+ height: 0x05,
+ codingScheme: ICC_IMG_CODING_SCHEME_COLOR_TRANSPARENCY,
+ bitsPerImgPoint: 0x03,
+ numOfClutEntries: 0x08,
+ body: [0x10, 0x20, 0x30, 0x40, 0x50, 0x60, 0x70, 0x80, 0x90,
+ 0xa0, 0xb0, 0xc0, 0xd0],
+ clut: [0x00, 0x01, 0x02,
+ 0x10, 0x11, 0x12,
+ 0x20, 0x21, 0x22,
+ 0x30, 0x31, 0x32,
+ 0x40, 0x41, 0x42,
+ 0x50, 0x51, 0x52,
+ 0x60, 0x61, 0x62,
+ 0x70, 0x71, 0x72]}]},
+ {img: [0x02, 0x01, 0x06, 0x22, 0x4f, 0x33, 0x00, 0x02, 0x00, 0x08, 0x01,
+ 0x06, 0x22, 0x4f, 0x33, 0x00, 0x02, 0x00, 0x08],
+ iidf: [
+ [/* Data offset */
+ 0xff, 0xff,
+ /* Header */
+ 0x01, 0x06, 0x02, 0x04, 0x00, 0x0d,
+ /* Image body */
+ 0x40, 0x50,
+ /* Clut offset */
+ 0x0a, 0x0b, 0x0c,
+ /* Clut entries */
+ 0x01, 0x03, 0x05,
+ 0x21, 0x23, 0x25,
+ 0x41, 0x43, 0x45,
+ 0x61, 0x63, 0x65],
+ [/* Data offset */
+ 0xff, 0xff,
+ /* Header */
+ 0x01, 0x06, 0x02, 0x04, 0x00, 0x0d,
+ /* Image body */
+ 0x4f, 0x5f,
+ /* Clut offset */
+ 0x0a, 0x0b, 0x0c,
+ /* Clut entries */
+ 0x11, 0x13, 0x15,
+ 0x21, 0x23, 0x25,
+ 0x41, 0x43, 0x45,
+ 0x61, 0x63, 0x65]],
+ expected: [
+ {width: 0x01,
+ height: 0x06,
+ codingScheme: ICC_IMG_CODING_SCHEME_COLOR_TRANSPARENCY,
+ bitsPerImgPoint: 0x02,
+ numOfClutEntries: 0x04,
+ body: [0x40, 0x50],
+ clut: [0x01, 0x03, 0x05,
+ 0x21, 0x23, 0x25,
+ 0x41, 0x43, 0x45,
+ 0x61, 0x63, 0x65]},
+ {width: 0x01,
+ height: 0x06,
+ codingScheme: ICC_IMG_CODING_SCHEME_COLOR_TRANSPARENCY,
+ bitsPerImgPoint: 0x02,
+ numOfClutEntries: 0x04,
+ body: [0x4f, 0x5f],
+ clut: [0x11, 0x13, 0x15,
+ 0x21, 0x23, 0x25,
+ 0x41, 0x43, 0x45,
+ 0x61, 0x63, 0x65]}]}];
+
+ function do_test(img, iidf, expected) {
+ io.loadLinearFixedEF = function fakeLoadLinearFixedEF(options) {
+ // Write data size
+ buf.writeInt32(img.length * 2);
+
+ // Write data
+ for (let i = 0; i < img.length; i++) {
+ helper.writeHexOctet(img[i]);
+ }
+
+ // Write string delimiter
+ buf.writeStringDelimiter(img.length * 2);
+
+ if (options.callback) {
+ options.callback(options);
+ }
+ };
+
+ let instanceIndex = 0;
+ io.loadTransparentEF = function fakeLoadTransparentEF(options) {
+ // Write data size
+ buf.writeInt32(iidf[instanceIndex].length * 2);
+
+ // Write data
+ for (let i = 0; i < iidf[instanceIndex].length; i++) {
+ helper.writeHexOctet(iidf[instanceIndex][i]);
+ }
+
+ // Write string delimiter
+ buf.writeStringDelimiter(iidf[instanceIndex].length * 2);
+
+ instanceIndex++;
+
+ if (options.callback) {
+ options.callback(options);
+ }
+ };
+
+ let onsuccess = function(icons) {
+ equal(icons.length, expected.length);
+ for (let i = 0; i < icons.length; i++) {
+ let icon = icons[i];
+ let exp = expected[i];
+ equal(icon.width, exp.width);
+ equal(icon.height, exp.height);
+ equal(icon.codingScheme, exp.codingScheme);
+
+ equal(icon.body.length, exp.body.length);
+ for (let j = 0; j < icon.body.length; j++) {
+ equal(icon.body[j], exp.body[j]);
+ }
+
+ equal(icon.clut.length, exp.clut.length);
+ for (let j = 0; j < icon.clut.length; j++) {
+ equal(icon.clut[j], exp.clut[j]);
+ }
+ }
+ };
+
+ record.readIMG(0, onsuccess);
+ }
+
+ for (let i = 0; i< test_data.length; i++) {
+ do_test(test_data[i].img, test_data[i].iidf, test_data[i].expected);
+ }
+ run_next_test();
+});
+
+/**
+ * Verify SimRecordHelper.readCphsInfo
+ */
+add_test(function test_read_cphs_info() {
+ let worker = newUint8Worker();
+ let context = worker.ContextPool._contexts[0];
+ let RIL = context.RIL;
+ let pduHelper = context.GsmPDUHelper;
+ let recordHelper = context.SimRecordHelper;
+ let buf = context.Buf;
+ let io = context.ICCIOHelper;
+ let cphsPDU = new Uint8Array(3);
+
+ io.loadTransparentEF = function(options) {
+ if (cphsPDU) {
+ // Write data size
+ buf.writeInt32(cphsPDU.length * 2);
+
+ // Write CPHS INFO
+ for (let i = 0; i < cphsPDU.length; i++) {
+ pduHelper.writeHexOctet(cphsPDU[i]);
+ }
+
+ // Write string delimiter
+ buf.writeStringDelimiter(cphsPDU.length * 2);
+
+ if (options.callback) {
+ options.callback(options);
+ }
+ } else {
+ do_print("cphsPDU[] is not set.");
+ }
+ };
+
+ function do_test(cphsInfo, cphsSt) {
+ let onsuccess = false;
+ let onerror = false;
+
+ delete RIL.iccInfoPrivate.cphsSt;
+ cphsPDU.set(cphsInfo);
+ recordHelper.readCphsInfo(() => { onsuccess = true; },
+ () => { onerror = true; });
+
+ ok((cphsSt) ? onsuccess : onerror);
+ ok(!((cphsSt) ? onerror : onsuccess));
+ if (cphsSt) {
+ equal(RIL.iccInfoPrivate.cphsSt.length, cphsSt.length);
+ for (let i = 0; i < cphsSt.length; i++) {
+ equal(RIL.iccInfoPrivate.cphsSt[i], cphsSt[i]);
+ }
+ } else {
+ equal(RIL.iccInfoPrivate.cphsSt, cphsSt);
+ }
+ }
+
+ do_test([
+ 0x01, // Phase 1
+ 0xFF, // All available & activated
+ 0x03 // All available & activated
+ ],
+ [
+ 0x3F, // All services except ONSF(bit 8-7) are available and activated.
+ 0x00 // INFO_NUM shall not be available & activated.
+ ]);
+
+ do_test([
+ 0x02, // Phase 2
+ 0xFF, // All available & activated
+ 0x03 // All available & activated
+ ],
+ [
+ 0xF3, // All services except ONSF are available and activated.
+ 0x03 // INFO_NUM shall not be available & activated.
+ ]);
+
+ do_test([
+ 0x03, // Phase 3
+ 0xFF, // All available & activated
+ 0x03 // All available & activated
+ ],
+ undefined); // RIL.iccInfoPrivate.cphsSt shall be remained as 'undefined'.
+
+ run_next_test();
+});
+
+/**
+ * Verify SimRecordHelper.readMBDN/SimRecordHelper.readCphsMBN
+ */
+add_test(function test_read_voicemail_number() {
+ let worker = newUint8Worker();
+ let context = worker.ContextPool._contexts[0];
+ let RIL = context.RIL;
+ let pduHelper = context.GsmPDUHelper;
+ let recordHelper = context.SimRecordHelper;
+ let buf = context.Buf;
+ let io = context.ICCIOHelper;
+ let postedMessage;
+
+ worker.postMessage = function(message) {
+ postedMessage = message;
+ };
+
+ io.loadLinearFixedEF = function(options) {
+ let mbnData = [
+ 0x56, 0x6F, 0x69, 0x63, 0x65, 0x6D, 0x61, 0x69,
+ 0x6C, 0xFF, // Alpha Identifier: Voicemail
+ 0x03, // Length of BCD number: 3
+ 0x80, // TOA: Unknown
+ 0x11, 0xF1, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, // Dialing Number: 111
+ 0xFF, // Capability/Configuration Record Identifier
+ 0xFF // Extension Record Identifier
+ ];
+
+ // Write data size
+ buf.writeInt32(mbnData.length * 2);
+
+ // Write MBN
+ for (let i = 0; i < mbnData.length; i++) {
+ pduHelper.writeHexOctet(mbnData[i]);
+ }
+
+ // Write string delimiter
+ buf.writeStringDelimiter(mbnData.length * 2);
+
+ options.recordSize = mbnData.length;
+ if (options.callback) {
+ options.callback(options);
+ }
+ };
+
+ function do_test(funcName, msgCount) {
+ postedMessage = null;
+ delete RIL.iccInfoPrivate.mbdn;
+ recordHelper[funcName]();
+
+ equal("iccmbdn", postedMessage.rilMessageType);
+ equal("Voicemail", postedMessage.alphaId);
+ equal("111", postedMessage.number);
+ }
+
+ do_test("readMBDN");
+ do_test("readCphsMBN");
+
+ run_next_test();
+});
+
+/**
+ * Verify the recovery from SimRecordHelper.readCphsMBN() if MBDN is not valid
+ * or is empty after SimRecordHelper.readMBDN().
+ */
+add_test(function test_read_mbdn_recovered_from_cphs_mbn() {
+ let worker = newUint8Worker();
+ let context = worker.ContextPool._contexts[0];
+ let RIL = context.RIL;
+ let pduHelper = context.GsmPDUHelper;
+ let recordHelper = context.SimRecordHelper;
+ let iccUtilsHelper = context.ICCUtilsHelper;
+ let buf = context.Buf;
+ let io = context.ICCIOHelper;
+
+ io.loadLinearFixedEF = function(options) {
+ let mbnData = [
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF
+ ];
+
+ // Write data size
+ buf.writeInt32(mbnData.length * 2);
+
+ // Write MBN
+ for (let i = 0; i < mbnData.length; i++) {
+ pduHelper.writeHexOctet(mbnData[i]);
+ }
+
+ // Write string delimiter
+ buf.writeStringDelimiter(mbnData.length * 2);
+
+ options.recordSize = mbnData.length;
+ if (options.callback) {
+ options.callback(options);
+ }
+ };
+
+ iccUtilsHelper.isCphsServiceAvailable = function(geckoService) {
+ return geckoService == "MBN";
+ };
+
+ let isRecovered = false;
+ recordHelper.readCphsMBN = function(onComplete) {
+ isRecovered = true;
+ };
+
+ recordHelper.readMBDN();
+
+ equal(RIL.iccInfoPrivate.mbdn, undefined);
+ ok(isRecovered);
+
+ run_next_test();
+});
+
+/**
+ * Verify reading EF_PNN with different coding scheme.
+ */
+add_test(function test_pnn_with_different_coding_scheme() {
+ let worker = newUint8Worker();
+ let context = worker.ContextPool._contexts[0];
+ let record = context.SimRecordHelper;
+ let pduHelper = context.GsmPDUHelper;
+ let ril = context.RIL;
+ let buf = context.Buf;
+ let io = context.ICCIOHelper;
+
+ let test_data = [{
+ // Cell Broadcast data coding scheme - "Test1"
+ pnn: [0x43, 0x06, 0x85, 0xD4, 0xF2, 0x9C, 0x1E, 0x03],
+ expectedResult: "Test1"
+ },{
+ // UCS2 with 0x80 - "Test1"
+ pnn: [0x43, 0x0C, 0x90, 0x80, 0x00, 0x54, 0x00, 0x65, 0x00, 0x73, 0x00, 0x74, 0x00, 0x31],
+ expectedResult: "Test1"
+ },{
+ // UCS2 with 0x81 - "Mozilla\u694a"
+ pnn: [0x43, 0x0E, 0x90, 0x81, 0x08, 0xd2, 0x4d, 0x6f, 0x7a, 0x69, 0x6c, 0x6c, 0x61, 0xca, 0xff, 0xff],
+ expectedResult: "Mozilla\u694a"
+ },{
+ // UCS2 with 0x82 - "Mozilla\u694a"
+ pnn: [0x43, 0x0F, 0x90, 0x82, 0x08, 0x69, 0x00, 0x4d, 0x6f, 0x7a, 0x69, 0x6c, 0x6c, 0x61, 0xca, 0xff, 0xff],
+ expectedResult: "Mozilla\u694a"
+ }];
+
+ function do_test_pnn(pnn, expectedResult) {
+ io.loadLinearFixedEF = function fakeLoadLinearFixedEF(options) {
+ // Write data size.
+ buf.writeInt32(pnn.length * 2);
+
+ // Write data.
+ for (let i = 0; i < pnn.length; i++) {
+ pduHelper.writeHexOctet(pnn[i]);
+ }
+
+ // Write string delimiter.
+ buf.writeStringDelimiter(pnn.length * 2);
+
+ if (options.callback) {
+ options.callback(options);
+ }
+ };
+
+ record.readPNN();
+
+ equal(ril.iccInfoPrivate.PNN[0].fullName, expectedResult);
+ // Reset PNN info for next test
+ ril.iccInfoPrivate.PNN = null;
+ }
+
+ ril.appType = CARD_APPTYPE_SIM;
+ for (let i = 0; i < test_data.length; i++) {
+ do_test_pnn(test_data[i].pnn, test_data[i].expectedResult);
+ }
+
+ run_next_test();
+});
+
+/**
+ * Verify reading EF_PNN with different content.
+ */
+add_test(function test_pnn_with_different_content() {
+ let worker = newUint8Worker();
+ let context = worker.ContextPool._contexts[0];
+ let record = context.SimRecordHelper;
+ let pduHelper = context.GsmPDUHelper;
+ let ril = context.RIL;
+ let buf = context.Buf;
+ let io = context.ICCIOHelper;
+
+ let test_data = [{
+ // [0]: {"fullName":"Test1","shortName":"Test1"}
+ pnn: [0x43, 0x06, 0x85, 0xD4, 0xF2, 0x9C, 0x1E, 0x03,
+ 0x45, 0x06, 0x85, 0xD4, 0xF2, 0x9C, 0x1E, 0x03],
+ expectedResult: {"fullName": "Test1","shortName": "Test1"}
+ },{
+ // [1]: {"fullName":"Test2"}
+ pnn: [0x43, 0x06, 0x85, 0xD4, 0xF2, 0x9C, 0x2E, 0x03],
+ expectedResult: {"fullName": "Test2"}
+ },{
+ // [2]: undefined
+ pnn: [0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF],
+ },{
+ // [3]: {"fullName": "Test4"}
+ pnn: [0x43, 0x06, 0x85, 0xD4, 0xF2, 0x9C, 0x4E, 0x03],
+ expectedResult: {"fullName": "Test4"}
+ },{
+ // [4]: undefined
+ pnn: [0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF],
+ }];
+
+ function do_test_pnn() {
+ ril.iccIO = function fakeIccIO(options) {
+ let index = options.p1 - 1;
+ let pnn = test_data[index].pnn;
+
+ // Write data size.
+ buf.writeInt32(pnn.length * 2);
+
+ // Write data.
+ for (let i = 0; i < pnn.length; i++) {
+ pduHelper.writeHexOctet(pnn[i]);
+ }
+
+ // Write string delimiter.
+ buf.writeStringDelimiter(pnn.length * 2);
+
+ if (options.callback) {
+ options.callback(options);
+ }
+ };
+
+ io.loadLinearFixedEF = function fakeLoadLinearFixedEF(options) {
+ options.p1 = 1;
+ options.totalRecords = test_data.length;
+
+ ril.iccIO(options);
+ };
+
+ record.readPNN();
+
+ equal(test_data.length, ril.iccInfoPrivate.PNN.length);
+ for (let i = 0; i < test_data.length; i++) {
+ if (test_data[i].expectedResult) {
+ equal(test_data[i].expectedResult.fullName,
+ ril.iccInfoPrivate.PNN[i].fullName);
+ equal(test_data[i].expectedResult.shortName,
+ ril.iccInfoPrivate.PNN[i].shortName);
+ } else {
+ equal(test_data[i].expectedResult, ril.iccInfoPrivate.PNN[i]);
+ }
+ }
+ }
+
+ ril.appType = CARD_APPTYPE_SIM;
+ do_test_pnn();
+
+ run_next_test();
+});
diff --git a/dom/system/gonk/tests/test_ril_worker_ruim.js b/dom/system/gonk/tests/test_ril_worker_ruim.js
new file mode 100644
index 000000000..0ddc10f29
--- /dev/null
+++ b/dom/system/gonk/tests/test_ril_worker_ruim.js
@@ -0,0 +1,328 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+subscriptLoader.loadSubScript("resource://gre/modules/ril_consts.js", this);
+
+function run_test() {
+ run_next_test();
+}
+
+/**
+ * Verify RUIM Service.
+ */
+add_test(function test_is_ruim_service_available() {
+ let worker = newWorker();
+ let context = worker.ContextPool._contexts[0];
+ context.RIL._isCdma = true;
+ context.RIL.appType = CARD_APPTYPE_RUIM;
+
+ function test_table(cst, geckoService, enabled) {
+ context.RIL.iccInfoPrivate.cst = cst;
+ equal(context.ICCUtilsHelper.isICCServiceAvailable(geckoService),
+ enabled);
+ }
+
+ test_table([0x0, 0x0, 0x0, 0x0, 0x03], "SPN", true);
+ test_table([0x0, 0x0, 0x0, 0x03, 0x0], "SPN", false);
+ test_table([0x0, 0x0C, 0x0, 0x0, 0x0], "ENHANCED_PHONEBOOK", true);
+ test_table([0x0, 0x0, 0x0, 0x0, 0x0], "ENHANCED_PHONEBOOK", false);
+
+ run_next_test();
+});
+
+/**
+ * Verify EF_PATH for RUIM file.
+ */
+add_test(function test_ruim_file_path_id() {
+ let worker = newWorker();
+ let context = worker.ContextPool._contexts[0];
+ let RIL = context.RIL;
+ let ICCFileHelper = context.ICCFileHelper;
+
+ RIL.appType = CARD_APPTYPE_RUIM;
+ equal(ICCFileHelper.getEFPath(ICC_EF_CSIM_CST),
+ EF_PATH_MF_SIM + EF_PATH_DF_CDMA);
+
+ run_next_test();
+});
+
+add_test(function test_fetch_ruim_recodes() {
+ let worker = newWorker();
+ let context = worker.ContextPool._contexts[0];
+ let RIL = context.RIL;
+ let ruimHelper = context.RuimRecordHelper;
+
+ function testFetchRuimRecordes(expectCalled) {
+ let ifCalled = [];
+
+ ruimHelper.getIMSI_M = function() {
+ ifCalled.push("getIMSI_M");
+ };
+
+ ruimHelper.readCST = function() {
+ ifCalled.push("readCST");
+ };
+
+ ruimHelper.readCDMAHome = function() {
+ ifCalled.push("readCDMAHome");
+ };
+
+ RIL.getCdmaSubscription = function() {
+ ifCalled.push("getCdmaSubscription");
+ };
+
+ ruimHelper.fetchRuimRecords();
+
+ for (let i = 0; i < expectCalled.length; i++ ) {
+ if (ifCalled[i] != expectCalled[i]) {
+ do_print(expectCalled[i] + " is not called.");
+ ok(false);
+ }
+ }
+ }
+
+ let expectCalled = ["getIMSI_M", "readCST", "readCDMAHome",
+ "getCdmaSubscription"];
+ testFetchRuimRecordes(expectCalled);
+
+ run_next_test();
+});
+
+/**
+ * Verify RuimRecordHelper.decodeIMSIValue
+ */
+add_test(function test_decode_imsi_value() {
+ let worker = newUint8Worker();
+ let context = worker.ContextPool._contexts[0];
+
+ function testDecodeImsiValue(encoded, length, expect) {
+ let decoded = context.RuimRecordHelper.decodeIMSIValue(encoded, length);
+
+ equal(expect, decoded);
+ }
+
+ testDecodeImsiValue( 99, 2, "00");
+ testDecodeImsiValue( 90, 2, "01");
+ testDecodeImsiValue( 19, 2, "20");
+ testDecodeImsiValue( 23, 2, "34");
+ testDecodeImsiValue(999, 3, "000");
+ testDecodeImsiValue(990, 3, "001");
+ testDecodeImsiValue(909, 3, "010");
+ testDecodeImsiValue( 99, 3, "100");
+ testDecodeImsiValue(901, 3, "012");
+ testDecodeImsiValue( 19, 3, "120");
+ testDecodeImsiValue( 91, 3, "102");
+ testDecodeImsiValue(199, 3, "200");
+ testDecodeImsiValue(123, 3, "234");
+ testDecodeImsiValue(578, 3, "689");
+
+ run_next_test();
+});
+
+/**
+ * Verify RuimRecordHelper.getIMSI_M
+ */
+add_test(function test_get_imsi_m() {
+ let worker = newUint8Worker();
+ let context = worker.ContextPool._contexts[0];
+ let helper = context.GsmPDUHelper;
+ let buf = context.Buf;
+ let io = context.ICCIOHelper;
+
+ function testDecodeImsi(encodedImsi, expectedImsi) {
+ io.loadTransparentEF = function fakeLoadTransparentEF(options) {
+ // Write data size
+ buf.writeInt32(encodedImsi.length * 2);
+
+ // Write imsi
+ for (let i = 0; i < encodedImsi.length; i++) {
+ helper.writeHexOctet(encodedImsi[i]);
+ }
+
+ // Write string delimiter
+ buf.writeStringDelimiter(encodedImsi.length * 2);
+
+ if (options.callback) {
+ options.callback(options);
+ }
+ };
+
+ context.RuimRecordHelper.getIMSI_M();
+ let imsi = context.RIL.iccInfoPrivate.imsi;
+
+ equal(expectedImsi, imsi)
+ }
+
+ let imsi_1 = "466050081062861";
+ testDecodeImsi([0x0, 0xe5, 0x03, 0xee, 0xca, 0x17, 0x5e, 0x80, 0x63, 0x01], imsi_1);
+
+ let imsi_2 = "460038351175976";
+ testDecodeImsi([0x0, 0xd4, 0x02, 0x61, 0x97, 0x01, 0x5c, 0x80, 0x67, 0x01], imsi_2);
+
+ run_next_test();
+});
+
+/**
+ * Verify RuimRecordHelper.readCDMAHome
+ */
+add_test(function test_read_cdmahome() {
+ let worker = newUint8Worker();
+ let context = worker.ContextPool._contexts[0];
+ let helper = context.GsmPDUHelper;
+ let buf = context.Buf;
+ let io = context.ICCIOHelper;
+
+ io.loadLinearFixedEF = function fakeLoadLinearFixedEF(options) {
+ let cdmaHome = [0xc1, 0x34, 0xff, 0xff, 0x00];
+
+ // Write data size
+ buf.writeInt32(cdmaHome.length * 2);
+
+ // Write cdma home file.
+ for (let i = 0; i < cdmaHome.length; i++) {
+ helper.writeHexOctet(cdmaHome[i]);
+ }
+
+ // Write string delimiter
+ buf.writeStringDelimiter(cdmaHome.length * 2);
+
+ // We just have 1 test record.
+
+ options.totalRecords = 1;
+ if (options.callback) {
+ options.callback(options);
+ }
+ };
+
+ function testCdmaHome(expectedSystemIds, expectedNetworkIds) {
+ context.RuimRecordHelper.readCDMAHome();
+ let cdmaHome = context.RIL.cdmaHome;
+ for (let i = 0; i < expectedSystemIds.length; i++) {
+ equal(cdmaHome.systemId[i], expectedSystemIds[i]);
+ equal(cdmaHome.networkId[i], expectedNetworkIds[i]);
+ }
+ equal(cdmaHome.systemId.length, expectedSystemIds.length);
+ equal(cdmaHome.networkId.length, expectedNetworkIds.length);
+ }
+
+ testCdmaHome([13505], [65535]);
+
+ run_next_test();
+});
+
+/**
+ * Verify reading CDMA EF_SPN
+ */
+add_test(function test_read_cdmaspn() {
+ let worker = newUint8Worker();
+ let context = worker.ContextPool._contexts[0];
+ let helper = context.GsmPDUHelper;
+ let buf = context.Buf;
+ let io = context.ICCIOHelper;
+
+ function testReadSpn(file, expectedSpn, expectedDisplayCondition) {
+ io.loadTransparentEF = function fakeLoadTransparentEF(options) {
+ // Write data size
+ buf.writeInt32(file.length * 2);
+
+ // Write file.
+ for (let i = 0; i < file.length; i++) {
+ helper.writeHexOctet(file[i]);
+ }
+
+ // Write string delimiter
+ buf.writeStringDelimiter(file.length * 2);
+
+ if (options.callback) {
+ options.callback(options);
+ }
+ };
+
+ context.RuimRecordHelper.readSPN();
+ equal(context.RIL.iccInfo.spn, expectedSpn);
+ equal(context.RIL.iccInfoPrivate.spnDisplayCondition,
+ expectedDisplayCondition);
+ }
+
+ testReadSpn([0x01, 0x04, 0x06, 0x4e, 0x9e, 0x59, 0x2a, 0x96,
+ 0xfb, 0x4f, 0xe1, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff],
+ String.fromCharCode(0x4e9e) +
+ String.fromCharCode(0x592a) +
+ String.fromCharCode(0x96fb) +
+ String.fromCharCode(0x4fe1),
+ 0x1);
+
+ // Test when there's no tailing 0xff in spn string.
+ testReadSpn([0x01, 0x04, 0x06, 0x4e, 0x9e, 0x59, 0x2a, 0x96,
+ 0xfb, 0x4f, 0xe1],
+ String.fromCharCode(0x4e9e) +
+ String.fromCharCode(0x592a) +
+ String.fromCharCode(0x96fb) +
+ String.fromCharCode(0x4fe1),
+ 0x1);
+
+ run_next_test();
+});
+
+/**
+ * Verify display condition for CDMA.
+ */
+add_test(function test_cdma_spn_display_condition() {
+ let worker = newWorker({
+ postRILMessage: function(data) {
+ // Do nothing
+ },
+ postMessage: function(message) {
+ // Do nothing
+ }
+ });
+ let context = worker.ContextPool._contexts[0];
+ let RIL = context.RIL;
+ let ICCUtilsHelper = context.ICCUtilsHelper;
+
+ // Set cdma.
+ RIL._isCdma = true;
+
+ // Test updateDisplayCondition runs before any of SIM file is ready.
+ equal(ICCUtilsHelper.updateDisplayCondition(), true);
+ equal(RIL.iccInfo.isDisplayNetworkNameRequired, true);
+ equal(RIL.iccInfo.isDisplaySpnRequired, false);
+
+ // Test with value.
+ function testDisplayCondition(ruimDisplayCondition,
+ homeSystemIds, homeNetworkIds,
+ currentSystemId, currentNetworkId,
+ expectUpdateDisplayCondition,
+ expectIsDisplaySPNRequired) {
+ RIL.iccInfoPrivate.spnDisplayCondition = ruimDisplayCondition;
+ RIL.cdmaHome = {
+ systemId: homeSystemIds,
+ networkId: homeNetworkIds
+ };
+ RIL.voiceRegistrationState.cell = {
+ cdmaSystemId: currentSystemId,
+ cdmaNetworkId: currentNetworkId
+ };
+
+ equal(ICCUtilsHelper.updateDisplayCondition(), expectUpdateDisplayCondition);
+ equal(RIL.iccInfo.isDisplayNetworkNameRequired, false);
+ equal(RIL.iccInfo.isDisplaySpnRequired, expectIsDisplaySPNRequired);
+ };
+
+ // SPN is not required when ruimDisplayCondition is false.
+ testDisplayCondition(0x0, [123], [345], 123, 345, true, false);
+
+ // System id and network id are all match.
+ testDisplayCondition(0x1, [123], [345], 123, 345, true, true);
+
+ // Network is 65535, we should only need to match system id.
+ testDisplayCondition(0x1, [123], [65535], 123, 345, false, true);
+
+ // Not match.
+ testDisplayCondition(0x1, [123], [456], 123, 345, true, false);
+
+ run_next_test();
+});
diff --git a/dom/system/gonk/tests/test_ril_worker_sms.js b/dom/system/gonk/tests/test_ril_worker_sms.js
new file mode 100644
index 000000000..7c1b972a7
--- /dev/null
+++ b/dom/system/gonk/tests/test_ril_worker_sms.js
@@ -0,0 +1,273 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+subscriptLoader.loadSubScript("resource://gre/modules/ril_consts.js", this);
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
+XPCOMUtils.defineLazyGetter(this, "gSmsSegmentHelper", function() {
+ let ns = {};
+ Cu.import("resource://gre/modules/SmsSegmentHelper.jsm", ns);
+ return ns.SmsSegmentHelper;
+});
+
+const ESCAPE = "\uffff";
+const RESCTL = "\ufffe";
+
+function run_test() {
+ run_next_test();
+}
+
+/**
+ * Verify receiving SMS-DELIVERY messages
+ */
+
+function hexToNibble(nibble) {
+ nibble &= 0x0f;
+ if (nibble < 10) {
+ nibble += 48; // ASCII '0'
+ } else {
+ nibble += 55; // ASCII 'A'
+ }
+ return nibble;
+}
+
+function pduToParcelData(pdu) {
+ let dataLength = 4 + pdu.length * 4 + 4;
+ let data = new Uint8Array(dataLength);
+ let offset = 0;
+
+ // String length
+ data[offset++] = pdu.length & 0xFF;
+ data[offset++] = (pdu.length >> 8) & 0xFF;
+ data[offset++] = (pdu.length >> 16) & 0xFF;
+ data[offset++] = (pdu.length >> 24) & 0xFF;
+
+ // PDU data
+ for (let i = 0; i < pdu.length; i++) {
+ let hi = (pdu[i] >>> 4) & 0x0F;
+ let lo = pdu[i] & 0x0F;
+
+ data[offset++] = hexToNibble(hi);
+ data[offset++] = 0;
+ data[offset++] = hexToNibble(lo);
+ data[offset++] = 0;
+ }
+
+ // String delimitor
+ data[offset++] = 0;
+ data[offset++] = 0;
+ data[offset++] = 0;
+ data[offset++] = 0;
+
+ return data;
+}
+
+function compose7bitPdu(lst, sst, data, septets) {
+ if ((lst == 0) && (sst == 0)) {
+ return [0x00, // SMSC
+ PDU_MTI_SMS_DELIVER, // firstOctet
+ 1, 0x00, 0, // senderAddress
+ 0x00, // protocolIdentifier
+ PDU_DCS_MSG_CODING_7BITS_ALPHABET, // dataCodingScheme
+ 0, 0, 0, 0, 0, 0, 0, // y m d h m s tz
+ septets] // userDataLength
+ .concat(data);
+ }
+
+ return [0x00, // SMSC
+ PDU_MTI_SMS_DELIVER | PDU_UDHI, // firstOctet
+ 1, 0x00, 0, // senderAddress
+ 0x00, // protocolIdentifier
+ PDU_DCS_MSG_CODING_7BITS_ALPHABET, // dataCodingScheme
+ 0, 0, 0, 0, 0, 0, 0, // y m d h m s tz
+ 8 + septets, // userDataLength
+ 6, // user data header length
+ PDU_IEI_NATIONAL_LANGUAGE_LOCKING_SHIFT, 1, lst, // PDU_IEI_NATIONAL_LANGUAGE_LOCKING_SHIFT
+ PDU_IEI_NATIONAL_LANGUAGE_SINGLE_SHIFT, 1, sst] // PDU_IEI_NATIONAL_LANGUAGE_SINGLE_SHIFT
+ .concat(data);
+}
+
+function composeUcs2Pdu(rawBytes) {
+ return [0x00, // SMSC
+ PDU_MTI_SMS_DELIVER, // firstOctet
+ 1, 0x00, 0, // senderAddress
+ 0x00, // protocolIdentifier
+ PDU_DCS_MSG_CODING_16BITS_ALPHABET, // dataCodingScheme
+ 0, 0, 0, 0, 0, 0, 0, // y m d h m s tz
+ rawBytes.length] // userDataLength
+ .concat(rawBytes);
+}
+
+function newSmsParcel(pdu) {
+ return newIncomingParcel(-1,
+ RESPONSE_TYPE_UNSOLICITED,
+ UNSOLICITED_RESPONSE_NEW_SMS,
+ pduToParcelData(pdu));
+}
+
+function removeSpecialChar(str, needle) {
+ for (let i = 0; i < needle.length; i++) {
+ let pos;
+ while ((pos = str.indexOf(needle[i])) >= 0) {
+ str = str.substring(0, pos) + str.substring(pos + 1);
+ }
+ }
+ return str;
+}
+
+function newWriteHexOctetAsUint8Worker() {
+ let worker = newWorker({
+ postRILMessage: function(data) {
+ // Do nothing
+ },
+ postMessage: function(message) {
+ // Do nothing
+ }
+ });
+
+ let context = worker.ContextPool._contexts[0];
+ context.GsmPDUHelper.writeHexOctet = function(value) {
+ context.Buf.writeUint8(value);
+ };
+
+ return worker;
+}
+
+function add_test_receiving_sms(expected, pdu) {
+ add_test(function test_receiving_sms() {
+ let worker = newWorker({
+ postRILMessage: function(data) {
+ // Do nothing
+ },
+ postMessage: function(message) {
+ do_print("fullBody: " + message.fullBody);
+ equal(expected, message.fullBody)
+ }
+ });
+
+ do_print("expect: " + expected);
+ do_print("pdu: " + pdu);
+ worker.onRILMessage(0, newSmsParcel(pdu));
+
+ run_next_test();
+ });
+}
+
+var test_receiving_7bit_alphabets__worker;
+function test_receiving_7bit_alphabets(lst, sst) {
+ if (!test_receiving_7bit_alphabets__worker) {
+ test_receiving_7bit_alphabets__worker = newWriteHexOctetAsUint8Worker();
+ }
+ let worker = test_receiving_7bit_alphabets__worker;
+ let context = worker.ContextPool._contexts[0];
+ let helper = context.GsmPDUHelper;
+ let buf = context.Buf;
+
+ function get7bitRawBytes(expected) {
+ buf.outgoingIndex = 0;
+ helper.writeStringAsSeptets(expected, 0, lst, sst);
+
+ let subArray = buf.outgoingBytes.subarray(0, buf.outgoingIndex);
+ return Array.slice(subArray);
+ }
+
+ let langTable = PDU_NL_LOCKING_SHIFT_TABLES[lst];
+ let langShiftTable = PDU_NL_SINGLE_SHIFT_TABLES[sst];
+
+ let text = removeSpecialChar(langTable + langShiftTable, ESCAPE + RESCTL);
+ for (let i = 0; i < text.length;) {
+ let len = Math.min(70, text.length - i);
+ let expected = text.substring(i, i + len);
+ let septets =
+ gSmsSegmentHelper.countGsm7BitSeptets(expected, langTable, langShiftTable);
+ let rawBytes = get7bitRawBytes(expected);
+ let pdu = compose7bitPdu(lst, sst, rawBytes, septets);
+ add_test_receiving_sms(expected, pdu);
+
+ i += len;
+ }
+}
+
+function test_receiving_ucs2_alphabets(text) {
+ let worker = test_receiving_7bit_alphabets__worker;
+ let context = worker.ContextPool._contexts[0];
+ let buf = context.Buf;
+
+ function getUCS2RawBytes(expected) {
+ buf.outgoingIndex = 0;
+ context.GsmPDUHelper.writeUCS2String(expected);
+
+ let subArray = buf.outgoingBytes.subarray(0, buf.outgoingIndex);
+ return Array.slice(subArray);
+ }
+
+ for (let i = 0; i < text.length;) {
+ let len = Math.min(70, text.length - i);
+ let expected = text.substring(i, i + len);
+ let rawBytes = getUCS2RawBytes(expected);
+ let pdu = composeUcs2Pdu(rawBytes);
+ add_test_receiving_sms(expected, pdu);
+
+ i += len;
+ }
+}
+
+var ucs2str = "";
+for (let lst = 0; lst < PDU_NL_LOCKING_SHIFT_TABLES.length; lst++) {
+ ucs2str += PDU_NL_LOCKING_SHIFT_TABLES[lst];
+ for (let sst = 0; sst < PDU_NL_SINGLE_SHIFT_TABLES.length; sst++) {
+ test_receiving_7bit_alphabets(lst, sst);
+
+ if (lst == 0) {
+ ucs2str += PDU_NL_SINGLE_SHIFT_TABLES[sst];
+ }
+ }
+}
+test_receiving_ucs2_alphabets(ucs2str);
+
+// Bug 820220: B2G SMS: wrong order and truncated content in multi-part messages
+add_test(function test_sendSMS_UCS2_without_langIndex_langShiftIndex_defined() {
+ let worker = newWriteHexOctetAsUint8Worker();
+ let context = worker.ContextPool._contexts[0];
+
+ context.Buf.sendParcel = function() {
+ // Each sendParcel() call represents one outgoing segment of a multipart
+ // SMS message. Here, we have the first segment send, so it's "Hello "
+ // only.
+ //
+ // 4(parcel size) + 4(request type) + 4(token)
+ // + 4(two messages) + 4(null SMSC) + 4(message string length)
+ // + 1(first octet) + 1(message reference)
+ // + 2(DA len, TOA) + 4(addr)
+ // + 1(pid) + 1(dcs)
+ // + 1(UDL) + 6(UDHL, type, len, ref, max, seq)
+ // + 12(2 * strlen("Hello "))
+ // + 4(two delimitors) = 57
+ //
+ // If we have additional 6(type, len, langIndex, type len, langShiftIndex)
+ // octets here, then bug 809553 is not fixed.
+ equal(this.outgoingIndex, 57);
+
+ run_next_test();
+ };
+
+ context.RIL.sendSMS({
+ number: "1",
+ segmentMaxSeq: 2,
+ fullBody: "Hello World!",
+ dcs: PDU_DCS_MSG_CODING_16BITS_ALPHABET,
+ segmentRef16Bit: false,
+ userDataHeaderLength: 5,
+ requestStatusReport: true,
+ segments: [
+ {
+ body: "Hello ",
+ encodedBodyLength: 12,
+ }, {
+ body: "World!",
+ encodedBodyLength: 12,
+ }
+ ],
+ });
+});
diff --git a/dom/system/gonk/tests/test_ril_worker_sms_cdma.js b/dom/system/gonk/tests/test_ril_worker_sms_cdma.js
new file mode 100644
index 000000000..85d0b6e0c
--- /dev/null
+++ b/dom/system/gonk/tests/test_ril_worker_sms_cdma.js
@@ -0,0 +1,298 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+subscriptLoader.loadSubScript("resource://gre/modules/ril_consts.js", this);
+
+function run_test() {
+ run_next_test();
+}
+
+/*
+ * Helper function to covert a HEX string to a byte array.
+ *
+ * @param hexString
+ * A hexadecimal string of which the length is even.
+ */
+function hexStringToBytes(hexString) {
+ let bytes = [];
+
+ let length = hexString.length;
+
+ for (let i = 0; i < length; i += 2) {
+ bytes.push(Number.parseInt(hexString.substr(i, 2), 16));
+ }
+
+ return bytes;
+}
+
+/*
+ * Helper function to covert a byte array to a HEX string.
+ *
+ * @param bytes
+ * Could be a regular byte array or Uint8Array.
+ */
+function bytesToHexString(bytes) {
+ let hexString = "";
+ let hex;
+
+ for (let i = 0; i < bytes.length; i++) {
+ hex = bytes[i].toString(16).toUpperCase();
+ if (hex.length === 1) {
+ hexString += "0";
+ }
+ hexString += hex;
+ }
+
+ return hexString;
+}
+
+/*
+ * Helper function to ecode Opaque UserData
+ *
+ * @param msg_type
+ * PDU_CDMA_MSG_TYPE_SUBMIT or PDU_CDMA_MSG_TYPE_DELIVER
+ * @param data
+ * The byte array of opaque data to be encoded.
+ */
+function encodeOpaqueUserData(bitBufferHelper, options) {
+ let bearerDataBuffer = [];
+ bitBufferHelper.startWrite(bearerDataBuffer);
+
+ // Msg-Id
+ bitBufferHelper.writeBits(PDU_CDMA_MSG_USERDATA_MSG_ID, 8);
+ bitBufferHelper.writeBits(3, 8);
+ bitBufferHelper.writeBits(options.msg_type, 4); // MSG_TYPE
+ bitBufferHelper.writeBits(1, 16); // MSG_ID
+ bitBufferHelper.flushWithPadding(); // HEADER_IND (1) + RESERVED (3)
+
+ // User Data
+ bitBufferHelper.writeBits(PDU_CDMA_MSG_USERDATA_BODY, 8);
+ let dataLength = options.data.length;
+ bitBufferHelper.writeBits(2 + dataLength, 8); // 2 bytes for MSG_ENCODING, NUM_FIELDS
+ bitBufferHelper.writeBits(PDU_CDMA_MSG_CODING_OCTET, 5); //MSG_ENCODING
+ // MSG_TYPE is omitted if MSG_ENCODING is CODING_OCTET
+ bitBufferHelper.writeBits(dataLength, 8); // NUM_FIELDS
+ for (let i = 0; i < dataLength; i++) { // CHARi
+ bitBufferHelper.writeBits(options.data[i], 8);
+ }
+ bitBufferHelper.flushWithPadding(); // RESERVED (3 filling bits)
+
+ return bearerDataBuffer;
+}
+
+function newSmsParcel(cdmaPduHelper, pdu) {
+ return newIncomingParcel(-1,
+ RESPONSE_TYPE_UNSOLICITED,
+ UNSOLICITED_RESPONSE_CDMA_NEW_SMS,
+ pduToParcelData(cdmaPduHelper, pdu));
+}
+
+/*
+ * Helper function to encode PDU into Parcel.
+ * See ril_cdma_sms.h for the structure definition of RIL_CDMA_SMS_Message
+ *
+ * @param teleservice
+ * The Teleservice-Id of this PDU.
+ * See PDU_CDMA_MSG_TELESERIVCIE_ID_XXX in ril_const.js.
+ * @param address
+ * The Orginating or Destinating address.
+ * @param bearerData
+ * The byte array of the encoded bearer data.
+ */
+function pduToParcelData(cdmaPduHelper, pdu) {
+
+ let addrInfo = cdmaPduHelper.encodeAddr(pdu.address);
+ // Teleservice, isServicePresent, ServiceCategory,
+ // addrInfo {digitMode, numberMode, numberType, numberPlan, address.length, address}
+ // Sub Address
+ // bearerData length, bearerData.
+ let dataLength = 4 + 4 + 4
+ + (5 + addrInfo.address.length) * 4
+ + 3 * 4
+ + 4 + pdu.bearerData.length * 4;
+
+ let data = new Uint8Array(dataLength);
+ let offset = 0;
+
+ function writeInt(value) {
+ data[offset++] = value & 0xFF;
+ data[offset++] = (value >> 8) & 0xFF;
+ data[offset++] = (value >> 16) & 0xFF;
+ data[offset++] = (value >> 24) & 0xFF;
+ }
+
+ function writeByte(value) {
+ data[offset++] = value & 0xFF;
+ data[offset++] = 0;
+ data[offset++] = 0;
+ data[offset++] = 0;
+ }
+
+ // Teleservice
+ writeInt(pdu.teleservice);
+
+ // isServicePresent
+ writeByte(0);
+
+ // ServiceCategory
+ writeInt(PDU_CDMA_MSG_CATEGORY_UNSPEC);
+
+ // AddrInfo
+ writeByte(addrInfo.digitMode);
+ writeByte(addrInfo.numberMode);
+ writeByte(addrInfo.numberType);
+ writeByte(addrInfo.numberPlan);
+ let addressLength = addrInfo.address.length;
+ writeByte(addressLength);
+ for (let i = 0; i < addressLength; i++) {
+ writeByte(addrInfo.address[i]);
+ }
+
+ // Subaddress
+ writeByte(0);
+ writeByte(0);
+ writeByte(0);
+
+ // Bearer Data Length
+ dataLength = pdu.bearerData.length;
+ writeByte(dataLength);
+
+ // Bearer Data
+ for (let i = 0; i < dataLength; i++) {
+ writeByte(pdu.bearerData[i]);
+ }
+
+ return data;
+}
+
+/**
+ * Verify CDMA SMS Delivery ACK Message.
+ */
+add_test(function test_processCdmaSmsStatusReport() {
+ let workerHelper = newInterceptWorker();
+ let worker = workerHelper.worker;
+ let context = worker.ContextPool._contexts[0];
+
+ function test_StatusReport(errorClass, msgStatus) {
+ let msgId = 0;
+ let sentSmsMap = context.RIL._pendingSentSmsMap;
+
+ sentSmsMap[msgId] = {};
+
+ let message = {
+ SMSC: "",
+ mti: 0,
+ udhi: 0,
+ sender: "0987654321",
+ recipient: null,
+ pid: PDU_PID_DEFAULT,
+ epid: PDU_PID_DEFAULT,
+ dcs: 0,
+ mwi: null,
+ replace: false,
+ header: null,
+ body: "Status: Sent, Dest: 0987654321",
+ data: null,
+ timestamp: new Date().valueOf(),
+ language: null,
+ status: null,
+ scts: null,
+ dt: null,
+ encoding: PDU_CDMA_MSG_CODING_7BITS_ASCII,
+ messageClass: GECKO_SMS_MESSAGE_CLASSES[PDU_DCS_MSG_CLASS_NORMAL],
+ messageType: PDU_CDMA_MSG_TYPE_P2P,
+ serviceCategory: 0,
+ subMsgType: PDU_CDMA_MSG_TYPE_DELIVER_ACK,
+ msgId: msgId,
+ errorClass: errorClass,
+ msgStatus: msgStatus
+ };
+
+ context.RIL._processCdmaSmsStatusReport(message);
+
+ let postedMessage = workerHelper.postedMessage;
+
+ // Check if pending token is removed.
+ ok((errorClass === 2) ? !!sentSmsMap[msgId] : !sentSmsMap[msgId]);
+
+ // Check the response message accordingly.
+ if (errorClass === -1) {
+ // Check if the report is treated as normal incoming SMS
+ equal("sms-received", postedMessage.rilMessageType);
+ } else if (errorClass === 2) {
+ // Do nothing.
+ } else {
+ // Check Delivery Status
+ if (errorClass === 0) {
+ equal(postedMessage.deliveryStatus, GECKO_SMS_DELIVERY_STATUS_SUCCESS);
+ } else {
+ equal(postedMessage.deliveryStatus, GECKO_SMS_DELIVERY_STATUS_ERROR);
+ }
+ }
+ }
+
+ test_StatusReport(-1, -1); // Message Status Sub-parameter is absent.
+ test_StatusReport(0, 0); // 00|000000: no error|Message accepted
+ test_StatusReport(2, 4); // 10|000100: temporary condition|Network congestion
+ test_StatusReport(3, 5); // 11|000101: permanent condition|Network error
+
+ run_next_test();
+});
+
+/**
+ * Verify WAP Push over CDMA SMS Message.
+ */
+add_test(function test_processCdmaSmsWapPush() {
+ let workerHelper = newInterceptWorker(),
+ worker = workerHelper.worker,
+ context = worker.ContextPool._contexts[0],
+ bitBufferHelper = context.BitBufferHelper,
+ cdmaPduHelper = context.CdmaPDUHelper;
+
+ function test_CdmaSmsWapPdu(wdpData, reversed) {
+ let orig_address = "0987654321",
+ hexString,
+ fullDataHexString = "";
+
+ for (let i = 0; i < wdpData.length; i++) {
+ let dataIndex = (reversed) ? (wdpData.length - i - 1) : i;
+ hexString = "00"; // MSG_TYPE
+ hexString += bytesToHexString([wdpData.length]); // TOTAL_SEG
+ hexString += bytesToHexString([dataIndex]); // SEG_NUM (zero-based)
+ if ((dataIndex === 0)) {
+ hexString += "23F00B84"; // SOURCE_PORT, DEST_PORT for 1st segment
+ }
+ hexString += wdpData[dataIndex]; // WDP DATA
+
+ do_print("hexString: " + hexString);
+
+ fullDataHexString += wdpData[i];
+
+ let pdu = {
+ teleservice: PDU_CDMA_MSG_TELESERIVCIE_ID_WAP,
+ address: orig_address,
+ bearerData: encodeOpaqueUserData(bitBufferHelper,
+ { msg_type: PDU_CDMA_MSG_TYPE_DELIVER,
+ data: hexStringToBytes(hexString) })
+ };
+
+ worker.onRILMessage(0, newSmsParcel(cdmaPduHelper, pdu));
+ }
+
+ let postedMessage = workerHelper.postedMessage;
+
+ do_print("fullDataHexString: " + fullDataHexString);
+
+ equal("sms-received", postedMessage.rilMessageType);
+ equal(PDU_CDMA_MSG_TELESERIVCIE_ID_WAP, postedMessage.teleservice);
+ equal(orig_address, postedMessage.sender);
+ equal(0x23F0, postedMessage.header.originatorPort);
+ equal(0x0B84, postedMessage.header.destinationPort);
+ equal(fullDataHexString, bytesToHexString(postedMessage.data));
+ }
+
+ // Verify Single WAP PDU
+ test_CdmaSmsWapPdu(["000102030405060708090A0B0C0D0E0F"]);
+
+ run_next_test();
+});
diff --git a/dom/system/gonk/tests/test_ril_worker_sms_cdmapduhelper.js b/dom/system/gonk/tests/test_ril_worker_sms_cdmapduhelper.js
new file mode 100644
index 000000000..276728f2f
--- /dev/null
+++ b/dom/system/gonk/tests/test_ril_worker_sms_cdmapduhelper.js
@@ -0,0 +1,210 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+subscriptLoader.loadSubScript("resource://gre/modules/ril_consts.js", this);
+
+function run_test() {
+ run_next_test();
+}
+
+/**
+ * Verify CdmaPDUHelper#encodeUserDataReplyOption.
+ */
+add_test(function test_CdmaPDUHelper_encodeUserDataReplyOption() {
+ let worker = newWorker({
+ postRILMessage: function(data) {
+ // Do nothing
+ },
+ postMessage: function(message) {
+ // Do nothing
+ }
+ });
+ let context = worker.ContextPool._contexts[0];
+
+ let testDataBuffer = [];
+ context.BitBufferHelper.startWrite(testDataBuffer);
+
+ let helper = context.CdmaPDUHelper;
+ helper.encodeUserDataReplyOption({requestStatusReport: true});
+
+ let expectedDataBuffer = [PDU_CDMA_MSG_USERDATA_REPLY_OPTION, 0x01, 0x40];
+
+ equal(testDataBuffer.length, expectedDataBuffer.length);
+
+ for (let i = 0; i < expectedDataBuffer.length; i++) {
+ equal(testDataBuffer[i], expectedDataBuffer[i]);
+ }
+
+ run_next_test();
+});
+
+/**
+ * Verify CdmaPDUHelper#cdma_decodeUserDataMsgStatus.
+ */
+add_test(function test_CdmaPDUHelper_decodeUserDataMsgStatus() {
+ let worker = newWorker({
+ postRILMessage: function(data) {
+ // Do nothing
+ },
+ postMessage: function(message) {
+ // Do nothing
+ }
+ });
+ let context = worker.ContextPool._contexts[0];
+
+ let helper = context.CdmaPDUHelper;
+ function test_MsgStatus(octet) {
+ let testDataBuffer = [octet];
+ context.BitBufferHelper.startRead(testDataBuffer);
+ let result = helper.decodeUserDataMsgStatus();
+
+ equal(result.errorClass, octet >>> 6);
+ equal(result.msgStatus, octet & 0x3F);
+ }
+
+ // 00|000000: no error|Message accepted
+ test_MsgStatus(0x00);
+
+ // 10|000100: temporary condition|Network congestion
+ test_MsgStatus(0x84);
+
+ // 11|000101: permanent condition|Network error
+ test_MsgStatus(0xC5);
+
+ run_next_test();
+});
+
+/**
+ * Verify CdmaPDUHelper#decodeCdmaPDUMsg.
+ * - encoding by shift-jis
+ */
+add_test(function test_CdmaPDUHelper_decodeCdmaPDUMsg_Shift_jis() {
+ let worker = newWorker({
+ postRILMessage: function(data) {
+ // Do nothing
+ },
+ postMessage: function(message) {
+ // Do nothing
+ }
+ });
+ let context = worker.ContextPool._contexts[0];
+
+ let helper = context.CdmaPDUHelper;
+ function test_decodePDUMsg(testDataBuffer, expected, encoding, msgType, msgBodySize) {
+ context.BitBufferHelper.startRead(testDataBuffer);
+ let result = helper.decodeCdmaPDUMsg(encoding, msgType, msgBodySize);
+ equal(result, expected);
+ }
+
+ // Shift-JIS has 1 byte and 2 byte code for one character and has some types of characters:
+ // Hiragana, Kanji, Katakana(fullwidth, halfwidth)...
+ // This test is a combination of 1 byte and 2 byte code and types of characters.
+
+ // test case 1
+ let testDataBuffer1 = [0x82, 0x58, 0x33, 0x41, 0x61, 0x33, 0x82, 0x60,
+ 0x82, 0x81, 0x33, 0xB1, 0xAF, 0x33, 0x83, 0x41,
+ 0x83, 0x96, 0x33, 0x82, 0xA0, 0x33, 0x93, 0xFA,
+ 0x33, 0x3A, 0x3C, 0x33, 0x81, 0x80, 0x81, 0x8E,
+ 0x33, 0x31, 0x82, 0x51, 0x41, 0x61, 0x82, 0x51,
+ 0x82, 0x60, 0x82, 0x81, 0x82, 0x51, 0xB1, 0xAF,
+ 0x82, 0x51, 0x83, 0x41, 0x83, 0x96, 0x82, 0x51,
+ 0x82, 0xA0, 0x82, 0x51, 0x93, 0xFA, 0x82, 0x51,
+ 0x3A, 0x3C, 0x82, 0x51, 0x81, 0x80, 0x81, 0x8E,
+ 0x82, 0x51];
+
+ test_decodePDUMsg(
+ testDataBuffer1,
+ "\uFF19\u0033\u0041\u0061\u0033\uFF21\uFF41\u0033\uFF71\uFF6F\u0033\u30A2\u30F6\u0033\u3042\u0033\u65E5\u0033\u003A\u003C\u0033\u00F7\u2103\u0033\u0031\uFF12\u0041\u0061\uFF12\uFF21\uFF41\uFF12\uFF71\uFF6F\uFF12\u30A2\u30F6\uFF12\u3042\uFF12\u65E5\uFF12\u003A\u003C\uFF12\u00F7\u2103\uFF12",
+ PDU_CDMA_MSG_CODING_SHIFT_JIS,
+ undefined,
+ testDataBuffer1.length
+ );
+
+ // test case 2
+ let testDataBuffer2 = [0x31, 0x51, 0x63, 0x82, 0x58, 0x51, 0x63, 0x82,
+ 0x60, 0x82, 0x81, 0x51, 0x63, 0xB1, 0xAF, 0x51,
+ 0x63, 0x83, 0x41, 0x83, 0x96, 0x51, 0x63, 0x82,
+ 0xA0, 0x51, 0x63, 0x93, 0xFA, 0x51, 0x63, 0x3A,
+ 0x3C, 0x51, 0x63, 0x81, 0x80, 0x81, 0x8E, 0x51,
+ 0x63, 0x31, 0x82, 0x70, 0x82, 0x85, 0x82, 0x58,
+ 0x82, 0x70, 0x82, 0x85, 0x41, 0x61, 0x82, 0x70,
+ 0x82, 0x85, 0xB1, 0xAF, 0x82, 0x70, 0x82, 0x85,
+ 0x83, 0x41, 0x83, 0x96, 0x82, 0x70, 0x82, 0x85,
+ 0x82, 0xA0, 0x82, 0x70, 0x82, 0x85, 0x93, 0xFA,
+ 0x82, 0x70, 0x82, 0x85, 0x3A, 0x3C, 0x82, 0x70,
+ 0x82, 0x85, 0x81, 0x80, 0x81, 0x8E, 0x82, 0x70,
+ 0x82, 0x85];
+
+ test_decodePDUMsg(
+ testDataBuffer2,
+ "\u0031\u0051\u0063\uFF19\u0051\u0063\uFF21\uFF41\u0051\u0063\uFF71\uFF6F\u0051\u0063\u30A2\u30F6\u0051\u0063\u3042\u0051\u0063\u65E5\u0051\u0063\u003A\u003C\u0051\u0063\u00F7\u2103\u0051\u0063\u0031\uFF31\uFF45\uFF19\uFF31\uFF45\u0041\u0061\uFF31\uFF45\uFF71\uFF6F\uFF31\uFF45\u30A2\u30F6\uFF31\uFF45\u3042\uFF31\uFF45\u65E5\uFF31\uFF45\u003A\u003C\uFF31\uFF45\u00F7\u2103\uFF31\uFF45",
+ PDU_CDMA_MSG_CODING_SHIFT_JIS,
+ undefined,
+ testDataBuffer2.length
+ );
+
+ // test case 3
+ let testDataBuffer3 = [0x31, 0xC2, 0xDF, 0x82, 0x58, 0xC2, 0xDF, 0x41,
+ 0x61, 0xC2, 0xDF, 0x82, 0x60, 0x82, 0x81, 0xC2,
+ 0xDF, 0x83, 0x41, 0x83, 0x96, 0xC2, 0xDF, 0x82,
+ 0xA0, 0xC2, 0xDF, 0x93, 0xFA, 0xC2, 0xDF, 0x3A,
+ 0x3C, 0xC2, 0xDF, 0x81, 0x80, 0x81, 0x8E, 0xC2,
+ 0xDF, 0x31, 0x83, 0x51, 0x83, 0x87, 0x82, 0x58,
+ 0x83, 0x51, 0x83, 0x87, 0x41, 0x61, 0x83, 0x51,
+ 0x83, 0x87, 0x82, 0x60, 0x82, 0x81, 0x83, 0x51,
+ 0x83, 0x87, 0xB1, 0xAF, 0x83, 0x51, 0x83, 0x87,
+ 0x82, 0xA0, 0x83, 0x51, 0x83, 0x87, 0x93, 0xFA,
+ 0x83, 0x51, 0x83, 0x87, 0x3A, 0x3C, 0x83, 0x51,
+ 0x83, 0x87, 0x81, 0x80, 0x81, 0x8E, 0x83, 0x51,
+ 0x83, 0x87];
+
+ test_decodePDUMsg(
+ testDataBuffer3,
+ "\u0031\uFF82\uFF9F\uFF19\uFF82\uFF9F\u0041\u0061\uFF82\uFF9F\uFF21\uFF41\uFF82\uFF9F\u30A2\u30F6\uFF82\uFF9F\u3042\uFF82\uFF9F\u65E5\uFF82\uFF9F\u003A\u003C\uFF82\uFF9F\u00F7\u2103\uFF82\uFF9F\u0031\u30B2\u30E7\uFF19\u30B2\u30E7\u0041\u0061\u30B2\u30E7\uFF21\uFF41\u30B2\u30E7\uFF71\uFF6F\u30B2\u30E7\u3042\u30B2\u30E7\u65E5\u30B2\u30E7\u003A\u003C\u30B2\u30E7\u00F7\u2103\u30B2\u30E7",
+ PDU_CDMA_MSG_CODING_SHIFT_JIS,
+ undefined,
+ testDataBuffer3.length
+ );
+
+ // test case 4
+ let testDataBuffer4 = [0x31, 0x82, 0xB0, 0x82, 0x58, 0x82, 0xB0, 0x41,
+ 0x61, 0x82, 0xB0, 0x82, 0x60, 0x82, 0x81, 0x82,
+ 0xB0, 0xB1, 0xAF, 0x82, 0xB0, 0x83, 0x41, 0x83,
+ 0x96, 0x82, 0xB0, 0x93, 0xFA, 0x82, 0xB0, 0x3A,
+ 0x3C, 0x82, 0xB0, 0x81, 0x80, 0x81, 0x8E, 0x82,
+ 0xB0, 0x31, 0x88, 0xA4, 0x82, 0x58, 0x88, 0xA4,
+ 0x41, 0x61, 0x88, 0xA4, 0x82, 0x60, 0x82, 0x81,
+ 0x88, 0xA4, 0xB1, 0xAF, 0x88, 0xA4, 0x83, 0x41,
+ 0x83, 0x96, 0x88, 0xA4, 0x82, 0xA0, 0x88, 0xA4,
+ 0x3A, 0x3C, 0x88, 0xA4, 0x81, 0x80, 0x81, 0x8E,
+ 0x88, 0xA4];
+
+ test_decodePDUMsg(
+ testDataBuffer4,
+ "\u0031\u3052\uFF19\u3052\u0041\u0061\u3052\uFF21\uFF41\u3052\uFF71\uFF6F\u3052\u30A2\u30F6\u3052\u65E5\u3052\u003A\u003C\u3052\u00F7\u2103\u3052\u0031\u611B\uFF19\u611B\u0041\u0061\u611B\uFF21\uFF41\u611B\uFF71\uFF6F\u611B\u30A2\u30F6\u611B\u3042\u611B\u003A\u003C\u611B\u00F7\u2103\u611B",
+ PDU_CDMA_MSG_CODING_SHIFT_JIS,
+ undefined,
+ testDataBuffer4.length
+ );
+
+ // test case 5
+ let testDataBuffer5 = [0x31, 0x40, 0x82, 0x58, 0x40, 0x41, 0x61, 0x40,
+ 0x82, 0x60, 0x82, 0x81, 0x40, 0xB1, 0xAF, 0x40,
+ 0x83, 0x41, 0x83, 0x96, 0x40, 0x82, 0xA0, 0x40,
+ 0x93, 0xFA, 0x40, 0x81, 0x80, 0x81, 0x8E, 0x40,
+ 0x31, 0x81, 0x9B, 0x82, 0x58, 0x81, 0x9B, 0x41,
+ 0x61, 0x81, 0x9B, 0x82, 0x60, 0x82, 0x81, 0x81,
+ 0x9B, 0xB1, 0xAF, 0x81, 0x9B, 0x83, 0x41, 0x83,
+ 0x96, 0x81, 0x9B, 0x82, 0xA0, 0x81, 0x9B, 0x93,
+ 0xFA, 0x81, 0x9B, 0x3A, 0x3C, 0x81, 0x9B];
+
+ test_decodePDUMsg(
+ testDataBuffer5,
+ "\u0031\u0040\uFF19\u0040\u0041\u0061\u0040\uFF21\uFF41\u0040\uFF71\uFF6F\u0040\u30A2\u30F6\u0040\u3042\u0040\u65E5\u0040\u00F7\u2103\u0040\u0031\u25CB\uFF19\u25CB\u0041\u0061\u25CB\uFF21\uFF41\u25CB\uFF71\uFF6F\u25CB\u30A2\u30F6\u25CB\u3042\u25CB\u65E5\u25CB\u003A\u003C\u25CB",
+ PDU_CDMA_MSG_CODING_SHIFT_JIS,
+ undefined,
+ testDataBuffer5.length
+ );
+
+ run_next_test();
+});
diff --git a/dom/system/gonk/tests/test_ril_worker_sms_gsmpduhelper.js b/dom/system/gonk/tests/test_ril_worker_sms_gsmpduhelper.js
new file mode 100644
index 000000000..f52c64cf8
--- /dev/null
+++ b/dom/system/gonk/tests/test_ril_worker_sms_gsmpduhelper.js
@@ -0,0 +1,282 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+subscriptLoader.loadSubScript("resource://gre/modules/ril_consts.js", this);
+
+function run_test() {
+ run_next_test();
+}
+
+/**
+ * Verify GsmPDUHelper#readDataCodingScheme.
+ */
+add_test(function test_GsmPDUHelper_readDataCodingScheme() {
+ let worker = newWorker({
+ postRILMessage: function(data) {
+ // Do nothing
+ },
+ postMessage: function(message) {
+ // Do nothing
+ }
+ });
+
+ let context = worker.ContextPool._contexts[0];
+ let helper = context.GsmPDUHelper;
+ function test_dcs(dcs, encoding, messageClass, mwi) {
+ helper.readHexOctet = function() {
+ return dcs;
+ }
+
+ let msg = {};
+ helper.readDataCodingScheme(msg);
+
+ equal(msg.dcs, dcs);
+ equal(msg.encoding, encoding);
+ equal(msg.messageClass, messageClass);
+ equal(msg.mwi == null, mwi == null);
+ if (mwi != null) {
+ equal(msg.mwi.active, mwi.active);
+ equal(msg.mwi.discard, mwi.discard);
+ equal(msg.mwi.msgCount, mwi.msgCount);
+ }
+ }
+
+ // Group 00xx
+ // Bit 3 and 2 indicate the character set being used.
+ test_dcs(0x00, PDU_DCS_MSG_CODING_7BITS_ALPHABET,
+ GECKO_SMS_MESSAGE_CLASSES[PDU_DCS_MSG_CLASS_NORMAL]);
+ test_dcs(0x04, PDU_DCS_MSG_CODING_8BITS_ALPHABET,
+ GECKO_SMS_MESSAGE_CLASSES[PDU_DCS_MSG_CLASS_NORMAL]);
+ test_dcs(0x08, PDU_DCS_MSG_CODING_16BITS_ALPHABET,
+ GECKO_SMS_MESSAGE_CLASSES[PDU_DCS_MSG_CLASS_NORMAL]);
+ test_dcs(0x0C, PDU_DCS_MSG_CODING_7BITS_ALPHABET,
+ GECKO_SMS_MESSAGE_CLASSES[PDU_DCS_MSG_CLASS_NORMAL]);
+ // Bit 4, if set to 0, indicates that bits 1 to 0 are reserved and have no
+ // message class meaning.
+ test_dcs(0x01, PDU_DCS_MSG_CODING_7BITS_ALPHABET,
+ GECKO_SMS_MESSAGE_CLASSES[PDU_DCS_MSG_CLASS_NORMAL]);
+ test_dcs(0x02, PDU_DCS_MSG_CODING_7BITS_ALPHABET,
+ GECKO_SMS_MESSAGE_CLASSES[PDU_DCS_MSG_CLASS_NORMAL]);
+ test_dcs(0x03, PDU_DCS_MSG_CODING_7BITS_ALPHABET,
+ GECKO_SMS_MESSAGE_CLASSES[PDU_DCS_MSG_CLASS_NORMAL]);
+ // Bit 4, if set to 1, indicates that bits 1 to 0 have a message class meaning.
+ test_dcs(0x10, PDU_DCS_MSG_CODING_7BITS_ALPHABET,
+ GECKO_SMS_MESSAGE_CLASSES[PDU_DCS_MSG_CLASS_0]);
+ test_dcs(0x11, PDU_DCS_MSG_CODING_7BITS_ALPHABET,
+ GECKO_SMS_MESSAGE_CLASSES[PDU_DCS_MSG_CLASS_1]);
+ test_dcs(0x12, PDU_DCS_MSG_CODING_7BITS_ALPHABET,
+ GECKO_SMS_MESSAGE_CLASSES[PDU_DCS_MSG_CLASS_2]);
+ test_dcs(0x13, PDU_DCS_MSG_CODING_7BITS_ALPHABET,
+ GECKO_SMS_MESSAGE_CLASSES[PDU_DCS_MSG_CLASS_3]);
+
+ // Group 01xx
+ test_dcs(0x50, PDU_DCS_MSG_CODING_7BITS_ALPHABET,
+ GECKO_SMS_MESSAGE_CLASSES[PDU_DCS_MSG_CLASS_0]);
+
+ // Group 1000..1011: reserved
+ test_dcs(0x8F, PDU_DCS_MSG_CODING_7BITS_ALPHABET,
+ GECKO_SMS_MESSAGE_CLASSES[PDU_DCS_MSG_CLASS_NORMAL]);
+ test_dcs(0x9F, PDU_DCS_MSG_CODING_7BITS_ALPHABET,
+ GECKO_SMS_MESSAGE_CLASSES[PDU_DCS_MSG_CLASS_NORMAL]);
+ test_dcs(0xAF, PDU_DCS_MSG_CODING_7BITS_ALPHABET,
+ GECKO_SMS_MESSAGE_CLASSES[PDU_DCS_MSG_CLASS_NORMAL]);
+ test_dcs(0xBF, PDU_DCS_MSG_CODING_7BITS_ALPHABET,
+ GECKO_SMS_MESSAGE_CLASSES[PDU_DCS_MSG_CLASS_NORMAL]);
+
+ // Group 1100: Message Waiting Indication Group: Discard Message
+ // Bit 3 indicates Indication Sense:
+ test_dcs(0xC0, PDU_DCS_MSG_CODING_7BITS_ALPHABET,
+ GECKO_SMS_MESSAGE_CLASSES[PDU_DCS_MSG_CLASS_NORMAL],
+ {active: false, discard: true, msgCount: 0});
+ test_dcs(0xC8, PDU_DCS_MSG_CODING_7BITS_ALPHABET,
+ GECKO_SMS_MESSAGE_CLASSES[PDU_DCS_MSG_CLASS_NORMAL],
+ {active: true, discard: true, msgCount: -1});
+ // Bit 2 is reserved, and set to 0:
+ test_dcs(0xCC, PDU_DCS_MSG_CODING_7BITS_ALPHABET,
+ GECKO_SMS_MESSAGE_CLASSES[PDU_DCS_MSG_CLASS_NORMAL],
+ {active: true, discard: true, msgCount: -1});
+
+ // Group 1101: Message Waiting Indication Group: Store Message
+ // Bit 3 indicates Indication Sense:
+ test_dcs(0xD0, PDU_DCS_MSG_CODING_7BITS_ALPHABET,
+ GECKO_SMS_MESSAGE_CLASSES[PDU_DCS_MSG_CLASS_NORMAL],
+ {active: false, discard: false, msgCount: 0});
+ test_dcs(0xD8, PDU_DCS_MSG_CODING_7BITS_ALPHABET,
+ GECKO_SMS_MESSAGE_CLASSES[PDU_DCS_MSG_CLASS_NORMAL],
+ {active: true, discard: false, msgCount: -1});
+ // Bit 2 is reserved, and set to 0:
+ test_dcs(0xDC, PDU_DCS_MSG_CODING_7BITS_ALPHABET,
+ GECKO_SMS_MESSAGE_CLASSES[PDU_DCS_MSG_CLASS_NORMAL],
+ {active: true, discard: false, msgCount: -1});
+
+ // Group 1110: Message Waiting Indication Group: Store Message, UCS2
+ // Bit 3 indicates Indication Sense:
+ test_dcs(0xE0, PDU_DCS_MSG_CODING_16BITS_ALPHABET,
+ GECKO_SMS_MESSAGE_CLASSES[PDU_DCS_MSG_CLASS_NORMAL],
+ {active: false, discard: false, msgCount: 0});
+ test_dcs(0xE8, PDU_DCS_MSG_CODING_16BITS_ALPHABET,
+ GECKO_SMS_MESSAGE_CLASSES[PDU_DCS_MSG_CLASS_NORMAL],
+ {active: true, discard: false, msgCount: -1});
+ // Bit 2 is reserved, and set to 0:
+ test_dcs(0xEC, PDU_DCS_MSG_CODING_16BITS_ALPHABET,
+ GECKO_SMS_MESSAGE_CLASSES[PDU_DCS_MSG_CLASS_NORMAL],
+ {active: true, discard: false, msgCount: -1});
+
+ // Group 1111
+ test_dcs(0xF0, PDU_DCS_MSG_CODING_7BITS_ALPHABET,
+ GECKO_SMS_MESSAGE_CLASSES[PDU_DCS_MSG_CLASS_0]);
+ test_dcs(0xF1, PDU_DCS_MSG_CODING_7BITS_ALPHABET,
+ GECKO_SMS_MESSAGE_CLASSES[PDU_DCS_MSG_CLASS_1]);
+ test_dcs(0xF2, PDU_DCS_MSG_CODING_7BITS_ALPHABET,
+ GECKO_SMS_MESSAGE_CLASSES[PDU_DCS_MSG_CLASS_2]);
+ test_dcs(0xF3, PDU_DCS_MSG_CODING_7BITS_ALPHABET,
+ GECKO_SMS_MESSAGE_CLASSES[PDU_DCS_MSG_CLASS_3]);
+ test_dcs(0xF4, PDU_DCS_MSG_CODING_8BITS_ALPHABET,
+ GECKO_SMS_MESSAGE_CLASSES[PDU_DCS_MSG_CLASS_0]);
+ test_dcs(0xF5, PDU_DCS_MSG_CODING_8BITS_ALPHABET,
+ GECKO_SMS_MESSAGE_CLASSES[PDU_DCS_MSG_CLASS_1]);
+ test_dcs(0xF6, PDU_DCS_MSG_CODING_8BITS_ALPHABET,
+ GECKO_SMS_MESSAGE_CLASSES[PDU_DCS_MSG_CLASS_2]);
+ test_dcs(0xF7, PDU_DCS_MSG_CODING_8BITS_ALPHABET,
+ GECKO_SMS_MESSAGE_CLASSES[PDU_DCS_MSG_CLASS_3]);
+ // Bit 3 is reserved and should be set to 0, but if it doesn't we should
+ // ignore it.
+ test_dcs(0xF8, PDU_DCS_MSG_CODING_7BITS_ALPHABET,
+ GECKO_SMS_MESSAGE_CLASSES[PDU_DCS_MSG_CLASS_0]);
+
+ run_next_test();
+});
+
+/**
+ * Verify GsmPDUHelper#writeStringAsSeptets() padding bits handling.
+ */
+add_test(function test_GsmPDUHelper_writeStringAsSeptets() {
+ let worker = newWorker({
+ postRILMessage: function(data) {
+ // Do nothing
+ },
+ postMessage: function(message) {
+ // Do nothing
+ }
+ });
+
+ let context = worker.ContextPool._contexts[0];
+ let helper = context.GsmPDUHelper;
+ helper.resetOctetWritten = function() {
+ helper.octetsWritten = 0;
+ };
+ helper.writeHexOctet = function() {
+ helper.octetsWritten++;
+ };
+
+ let base = "AAAAAAAA"; // Base string of 8 characters long
+ for (let len = 0; len < 8; len++) {
+ let str = base.substring(0, len);
+
+ for (let paddingBits = 0; paddingBits < 8; paddingBits++) {
+ do_print("Verifying GsmPDUHelper.writeStringAsSeptets("
+ + str + ", " + paddingBits + ", <default>, <default>)");
+ helper.resetOctetWritten();
+ helper.writeStringAsSeptets(str, paddingBits, PDU_NL_IDENTIFIER_DEFAULT,
+ PDU_NL_IDENTIFIER_DEFAULT);
+ equal(Math.ceil(((len * 7) + paddingBits) / 8),
+ helper.octetsWritten);
+ }
+ }
+
+ run_next_test();
+});
+
+/**
+ * Verify that encoding with Spanish locking shift table generates the same
+ * septets as with GSM default alphabet table.
+ *
+ * Bug 1138841 - Incorrect Spanish national language locking shift table
+ * definition.
+ */
+add_test(function test_GsmPDUHelper_writeStringAsSeptets_spanish_fallback() {
+ let worker = newWorker({
+ postRILMessage: function(data) {
+ // Do nothing
+ },
+ postMessage: function(message) {
+ // Do nothing
+ }
+ });
+
+ let context = worker.ContextPool._contexts[0];
+ let helper = context.GsmPDUHelper;
+ let buf = [];
+ helper.writeHexOctet = function(octet) {
+ buf.push(octet);
+ }
+
+ // Simple message string which is covered by GSM default alphabet.
+ let msg = "The quick brown fox jumps over the lazy dog";
+
+ // Encoded with GSM default alphabet.
+ helper.writeStringAsSeptets(msg, 0 /* paddingBits */,
+ PDU_NL_IDENTIFIER_DEFAULT, PDU_NL_IDENTIFIER_DEFAULT);
+ let octetsWithDefaultTable = buf;
+ buf = [];
+
+ // Encoded with Spanish locking shift table.
+ helper.writeStringAsSeptets(msg, 0 /* paddingBits */,
+ PDU_NL_IDENTIFIER_SPANISH, PDU_NL_IDENTIFIER_SPANISH);
+
+ // The length and content should be equal to what encoded with GSM default
+ // alphabet.
+ equal(octetsWithDefaultTable.length, buf.length);
+ for (let i = 0; i < buf.length; i++) {
+ equal(octetsWithDefaultTable[i], buf[i]);
+ }
+
+ run_next_test();
+});
+
+/**
+ * Verify GsmPDUHelper#readAddress
+ */
+add_test(function test_GsmPDUHelper_readAddress() {
+ let worker = newWorker({
+ postRILMessage: function(data) {
+ // Do nothing
+ },
+ postMessage: function(message) {
+ // Do nothing
+ }
+ });
+
+ let context = worker.ContextPool._contexts[0];
+ let helper = context.GsmPDUHelper;
+ function test_address(addrHex, addrString) {
+ let uint16Array = [];
+ let ix = 0;
+ for (let i = 0; i < addrHex.length; ++i) {
+ uint16Array[i] = addrHex[i].charCodeAt();
+ }
+
+ context.Buf.readUint16 = function(){
+ if(ix >= uint16Array.length) {
+ do_throw("out of range in uint16Array");
+ }
+ return uint16Array[ix++];
+ }
+ let length = helper.readHexOctet();
+ let parsedAddr = helper.readAddress(length);
+ equal(parsedAddr, addrString);
+ }
+
+ // For AlphaNumeric
+ test_address("04D01100", "_@");
+ test_address("04D01000", "\u0394@");
+
+ // Direct prepand
+ test_address("0B914151245584F6", "+14154255486");
+ test_address("0E914151245584B633", "+14154255486#33");
+
+ // PDU_TOA_NATIONAL
+ test_address("0BA14151245584F6", "14154255486");
+
+ run_next_test();
+});
diff --git a/dom/system/gonk/tests/test_ril_worker_sms_nl_tables.js b/dom/system/gonk/tests/test_ril_worker_sms_nl_tables.js
new file mode 100644
index 000000000..32bc5dc2a
--- /dev/null
+++ b/dom/system/gonk/tests/test_ril_worker_sms_nl_tables.js
@@ -0,0 +1,77 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+subscriptLoader.loadSubScript("resource://gre/modules/ril_consts.js", this);
+
+const ESCAPE = "\uffff";
+const RESCTL = "\ufffe";
+const LF = "\n";
+const CR = "\r";
+const SP = " ";
+const FF = "\u000c";
+
+function run_test() {
+ run_next_test();
+}
+
+/**
+ * Verify validity of the national language tables
+ */
+add_test(function test_nl_locking_shift_tables_validity() {
+ for (let lst = 0; lst < PDU_NL_LOCKING_SHIFT_TABLES.length; lst++) {
+ do_print("Verifying PDU_NL_LOCKING_SHIFT_TABLES[" + lst + "]");
+
+ let table = PDU_NL_LOCKING_SHIFT_TABLES[lst];
+
+ // Make sure table length is 128, or it will break table lookup algorithm.
+ equal(table.length, 128);
+
+ // Make sure special values are preserved.
+ equal(table[PDU_NL_EXTENDED_ESCAPE], ESCAPE);
+ equal(table[PDU_NL_LINE_FEED], LF);
+ equal(table[PDU_NL_CARRIAGE_RETURN], CR);
+ equal(table[PDU_NL_SPACE], SP);
+ }
+
+ run_next_test();
+});
+
+add_test(function test_nl_single_shift_tables_validity() {
+ for (let sst = 0; sst < PDU_NL_SINGLE_SHIFT_TABLES.length; sst++) {
+ do_print("Verifying PDU_NL_SINGLE_SHIFT_TABLES[" + sst + "]");
+
+ let table = PDU_NL_SINGLE_SHIFT_TABLES[sst];
+
+ // Make sure table length is 128, or it will break table lookup algorithm.
+ equal(table.length, 128);
+
+ // Make sure special values are preserved.
+ equal(table[PDU_NL_EXTENDED_ESCAPE], ESCAPE);
+ equal(table[PDU_NL_PAGE_BREAK], FF);
+ equal(table[PDU_NL_RESERVED_CONTROL], RESCTL);
+ }
+
+ run_next_test();
+});
+
+add_test(function test_gsm_sms_strict_7bit_charmap_validity() {
+ let defaultTable = PDU_NL_LOCKING_SHIFT_TABLES[PDU_NL_IDENTIFIER_DEFAULT];
+ let defaultShiftTable = PDU_NL_SINGLE_SHIFT_TABLES[PDU_NL_IDENTIFIER_DEFAULT];
+ for (let from in GSM_SMS_STRICT_7BIT_CHARMAP) {
+ let to = GSM_SMS_STRICT_7BIT_CHARMAP[from];
+ do_print("Verifying GSM_SMS_STRICT_7BIT_CHARMAP[\"\\u0x"
+ + from.charCodeAt(0).toString(16) + "\"] => \"\\u"
+ + to.charCodeAt(0).toString(16) + "\"");
+
+ // Make sure "from" is not in default table
+ equal(defaultTable.indexOf(from), -1);
+ equal(defaultShiftTable.indexOf(from), -1);
+ // Make sure "to" is in default table
+ if ((defaultTable.indexOf(to) < 0)
+ && (defaultShiftTable.indexOf(to) < 0)) {
+ equal(false, true);
+ }
+ }
+
+ run_next_test();
+});
diff --git a/dom/system/gonk/tests/test_ril_worker_sms_segment_info.js b/dom/system/gonk/tests/test_ril_worker_sms_segment_info.js
new file mode 100644
index 000000000..2b29ac60e
--- /dev/null
+++ b/dom/system/gonk/tests/test_ril_worker_sms_segment_info.js
@@ -0,0 +1,115 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+subscriptLoader.loadSubScript("resource://gre/modules/ril_consts.js", this);
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
+XPCOMUtils.defineLazyGetter(this, "gSmsSegmentHelper", function() {
+ let ns = {};
+ Cu.import("resource://gre/modules/SmsSegmentHelper.jsm", ns);
+ return ns.SmsSegmentHelper;
+});
+
+const ESCAPE = "\uffff";
+const RESCTL = "\ufffe";
+
+function run_test() {
+ run_next_test();
+}
+
+/**
+ * Verify SmsSegmentHelper#countGsm7BitSeptets() and
+ * GsmPDUHelper#writeStringAsSeptets() algorithm match each other.
+ */
+add_test(function test_SmsSegmentHelper__countGsm7BitSeptets() {
+ let worker = newWorker({
+ postRILMessage: function(data) {
+ // Do nothing
+ },
+ postMessage: function(message) {
+ // Do nothing
+ }
+ });
+
+ let context = worker.ContextPool._contexts[0];
+ let helper = context.GsmPDUHelper;
+ helper.resetOctetWritten = function() {
+ helper.octetsWritten = 0;
+ };
+ helper.writeHexOctet = function() {
+ helper.octetsWritten++;
+ };
+
+ function do_check_calc(str, expectedCalcLen, lst, sst, strict7BitEncoding, strToWrite) {
+ equal(expectedCalcLen,
+ gSmsSegmentHelper
+ .countGsm7BitSeptets(str,
+ PDU_NL_LOCKING_SHIFT_TABLES[lst],
+ PDU_NL_SINGLE_SHIFT_TABLES[sst],
+ strict7BitEncoding));
+
+ helper.resetOctetWritten();
+ strToWrite = strToWrite || str;
+ helper.writeStringAsSeptets(strToWrite, 0, lst, sst);
+ equal(Math.ceil(expectedCalcLen * 7 / 8), helper.octetsWritten);
+ }
+
+ // Test calculation encoded message length using both locking/single shift tables.
+ for (let lst = 0; lst < PDU_NL_LOCKING_SHIFT_TABLES.length; lst++) {
+ let langTable = PDU_NL_LOCKING_SHIFT_TABLES[lst];
+
+ let str = langTable.substring(0, PDU_NL_EXTENDED_ESCAPE)
+ + langTable.substring(PDU_NL_EXTENDED_ESCAPE + 1);
+
+ for (let sst = 0; sst < PDU_NL_SINGLE_SHIFT_TABLES.length; sst++) {
+ let langShiftTable = PDU_NL_SINGLE_SHIFT_TABLES[sst];
+
+ // <escape>, <resctrl> should be ignored.
+ do_check_calc(ESCAPE + RESCTL, 0, lst, sst);
+
+ // Characters defined in locking shift table should be encoded directly.
+ do_check_calc(str, str.length, lst, sst);
+
+ let [str1, str2] = ["", ""];
+ for (let i = 0; i < langShiftTable.length; i++) {
+ if ((i == PDU_NL_EXTENDED_ESCAPE) || (i == PDU_NL_RESERVED_CONTROL)) {
+ continue;
+ }
+
+ let c = langShiftTable[i];
+ if (langTable.indexOf(c) >= 0) {
+ str1 += c;
+ } else {
+ str2 += c;
+ }
+ }
+
+ // Characters found in both locking/single shift tables should be
+ // directly encoded.
+ do_check_calc(str1, str1.length, lst, sst);
+
+ // Characters found only in single shift tables should be encoded as
+ // <escape><code>, therefore doubles its original length.
+ do_check_calc(str2, str2.length * 2, lst, sst);
+ }
+ }
+
+ // Bug 790192: support strict GSM SMS 7-Bit encoding
+ let str = "", strToWrite = "", gsmLen = 0;
+ for (let c in GSM_SMS_STRICT_7BIT_CHARMAP) {
+ str += c;
+ strToWrite += GSM_SMS_STRICT_7BIT_CHARMAP[c];
+ if (PDU_NL_LOCKING_SHIFT_TABLES.indexOf(GSM_SMS_STRICT_7BIT_CHARMAP[c])) {
+ gsmLen += 1;
+ } else {
+ gsmLen += 2;
+ }
+ }
+ do_check_calc(str, gsmLen,
+ PDU_NL_IDENTIFIER_DEFAULT, PDU_NL_IDENTIFIER_DEFAULT,
+ true, strToWrite);
+
+ run_next_test();
+});
+
diff --git a/dom/system/gonk/tests/test_ril_worker_smsc_address.js b/dom/system/gonk/tests/test_ril_worker_smsc_address.js
new file mode 100644
index 000000000..c8c283b7c
--- /dev/null
+++ b/dom/system/gonk/tests/test_ril_worker_smsc_address.js
@@ -0,0 +1,112 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+subscriptLoader.loadSubScript("resource://gre/modules/ril_consts.js", this);
+
+const SMSC_ATT = '+13123149810';
+const SMSC_ATT_TYPO = '+++1312@@@314$$$9,8,1,0';
+const SMSC_ATT_TEXT = '"+13123149810",145';
+const SMSC_ATT_TEXT_INCORRECT_TOA = '"+13123149810",129';
+const SMSC_ATT_PDU = '07913121139418F0';
+const SMSC_O2 = '+447802000332';
+const SMSC_O2_TEXT = '"+447802000332",145';
+const SMSC_O2_PDU = '0791448720003023';
+const SMSC_EMPTY = '';
+const SMSC_TON_UNKNOWN = '0407485455'
+const SMSC_TON_UNKNOWN_TEXT = '"0407485455",129';
+const SMSC_TON_UNKNOWN_TEXT_NO_TOA = '"0407485455"';
+const SMSC_TON_UNKNOWN_TEXT_INVALID_TOA = '"0407485455",abc';
+const SMSC_TON_UNKNOWN_PDU = '06814070844555';
+const SMSC_EMPTY_PDU = 'FFFFFFFFFFFFFFFFFFFFFFFF';
+const SMSC_EMPTY_TEXT = '';
+
+function run_test() {
+ run_next_test();
+}
+
+function setSmsc(context, smsc, ton, npi, expected) {
+ context.Buf.postRILMessage = function() {
+ equal(this.readString(), expected);
+ };
+
+ context.RIL.setSmscAddress({
+ smscAddress: smsc,
+ typeOfNumber: ton,
+ numberPlanIdentification: npi
+ });
+}
+
+function getSmsc(worker, context, raw, smsc, ton, npi) {
+ worker.postMessage = function(message) {
+ equal(message.smscAddress, smsc);
+ equal(message.typeOfNumber, ton);
+ equal(message.numberPlanIdentification, npi);
+ }
+
+ context.Buf.writeString(raw);
+ context.RIL[REQUEST_GET_SMSC_ADDRESS](0, { rilMessageType: "getSmscAddress"});
+}
+
+add_test(function test_setSmscAddress() {
+ let worker = newUint8Worker();
+ let context = worker.ContextPool._contexts[0];
+ let parcelTypes = [];
+ context.Buf.newParcel = (type, options) => parcelTypes.push(type);
+
+ // Test text mode.
+ worker.RILQUIRKS_SMSC_ADDRESS_FORMAT = "text";
+
+ setSmsc(context, SMSC_ATT, 1, 1, SMSC_ATT_TEXT);
+ equal(parcelTypes.pop(), REQUEST_SET_SMSC_ADDRESS);
+
+ setSmsc(context, SMSC_O2, 1, 1, SMSC_O2_TEXT);
+ equal(parcelTypes.pop(), REQUEST_SET_SMSC_ADDRESS);
+
+ setSmsc(context, SMSC_ATT_TYPO, 1, 1, SMSC_ATT_TEXT);
+ equal(parcelTypes.pop(), REQUEST_SET_SMSC_ADDRESS);
+
+ setSmsc(context, SMSC_TON_UNKNOWN, 0, 1, SMSC_TON_UNKNOWN_TEXT);
+ equal(parcelTypes.pop(), REQUEST_SET_SMSC_ADDRESS);
+
+ // Test pdu mode.
+ worker.RILQUIRKS_SMSC_ADDRESS_FORMAT = "pdu";
+
+ setSmsc(context, SMSC_ATT, 1, 1, SMSC_ATT_PDU);
+ equal(parcelTypes.pop(), REQUEST_SET_SMSC_ADDRESS);
+
+ setSmsc(context, SMSC_O2, 1, 1, SMSC_O2_PDU);
+ equal(parcelTypes.pop(), REQUEST_SET_SMSC_ADDRESS);
+
+ setSmsc(context, SMSC_ATT_TYPO, 1, 1, SMSC_ATT_PDU);
+ equal(parcelTypes.pop(), REQUEST_SET_SMSC_ADDRESS);
+
+ setSmsc(context, SMSC_TON_UNKNOWN, 0, 1, SMSC_TON_UNKNOWN_PDU);
+ equal(parcelTypes.pop(), REQUEST_SET_SMSC_ADDRESS);
+
+ run_next_test();
+});
+
+add_test(function test_getSmscAddress() {
+ let worker = newUint8Worker();
+ let context = worker.ContextPool._contexts[0];
+
+ // Test text mode.
+ worker.RILQUIRKS_SMSC_ADDRESS_FORMAT = "text";
+ getSmsc(worker, context, SMSC_ATT_TEXT, SMSC_ATT, 1, 1);
+ getSmsc(worker, context, SMSC_ATT_TEXT_INCORRECT_TOA, SMSC_ATT, 1, 1);
+ getSmsc(worker, context, SMSC_O2_TEXT, SMSC_O2, 1, 1);
+ getSmsc(worker, context, SMSC_TON_UNKNOWN_TEXT, SMSC_TON_UNKNOWN, 0, 1);
+ getSmsc(worker, context, SMSC_TON_UNKNOWN_TEXT_NO_TOA, SMSC_TON_UNKNOWN, 0, 1);
+ getSmsc(worker, context, SMSC_TON_UNKNOWN_TEXT_INVALID_TOA, SMSC_TON_UNKNOWN,
+ 0, 1);
+ getSmsc(worker, context, SMSC_EMPTY_TEXT, SMSC_EMPTY, 0, 1);
+
+ // Test pdu mode.
+ worker.RILQUIRKS_SMSC_ADDRESS_FORMAT = "pdu";
+ getSmsc(worker, context, SMSC_ATT_PDU, SMSC_ATT, 1, 1);
+ getSmsc(worker, context, SMSC_O2_PDU, SMSC_O2, 1, 1);
+ getSmsc(worker, context, SMSC_TON_UNKNOWN_PDU, SMSC_TON_UNKNOWN, 0, 1);
+ getSmsc(worker, context, SMSC_EMPTY_PDU, SMSC_EMPTY, 0, 1);
+
+ run_next_test();
+});
diff --git a/dom/system/gonk/tests/test_ril_worker_ssn.js b/dom/system/gonk/tests/test_ril_worker_ssn.js
new file mode 100644
index 000000000..ea0a2a599
--- /dev/null
+++ b/dom/system/gonk/tests/test_ril_worker_ssn.js
@@ -0,0 +1,104 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+subscriptLoader.loadSubScript("resource://gre/modules/ril_consts.js", this);
+
+function run_test() {
+ run_next_test();
+}
+
+add_test(function test_notification() {
+ let workerHelper = newInterceptWorker();
+ let worker = workerHelper.worker;
+ let context = worker.ContextPool._contexts[0];
+
+ function Call(callIndex, number) {
+ this.callIndex = callIndex;
+ this.number = number;
+ }
+
+ Call.prototype = {
+ // Should use CALL_STATE_ACTIVE.
+ // Any new outgoing call (state = dialing or alerting) will be drop if there
+ // is no pending outgoing call created before.
+ state: CALL_STATE_ACTIVE,
+ //callIndex: 0,
+ toa: 0,
+ isMpty: false,
+ isMT: false,
+ als: 0,
+ isVoice: true,
+ isVoicePrivacy: false,
+ //number: null,
+ numberPresentation: 0,
+ name: null,
+ namePresentation: 0,
+ uusInfo: null
+ };
+
+ let oneCall = {
+ 0: new Call(0, '00000')
+ };
+
+ let twoCalls = {
+ 0: new Call(0, '00000'),
+ 1: new Call(1, '11111')
+ };
+
+ function testNotification(calls, code, number, resultNotification) {
+
+ let testInfo = {calls: calls, code: code, number: number,
+ resultNotification: resultNotification};
+ do_print('Test case info: ' + JSON.stringify(testInfo));
+
+ // Set current calls.
+ context.RIL.sendChromeMessage({
+ rilMessageType: "currentCalls",
+ calls: calls
+ });
+
+ let notificationInfo = {
+ notificationType: 1, // MT
+ code: code,
+ index: 0,
+ type: 0,
+ number: number
+ };
+
+ context.RIL._processSuppSvcNotification(notificationInfo);
+
+ let postedMessage = workerHelper.postedMessage;
+ equal(postedMessage.rilMessageType, 'suppSvcNotification');
+ equal(postedMessage.number, number);
+ equal(postedMessage.notification, resultNotification);
+
+ // Clear all existed calls.
+ context.RIL.sendChromeMessage({
+ rilMessageType: "currentCalls",
+ calls: {}
+ });
+ }
+
+ testNotification(oneCall, SUPP_SVC_NOTIFICATION_CODE2_PUT_ON_HOLD, null,
+ GECKO_SUPP_SVC_NOTIFICATION_REMOTE_HELD);
+
+ testNotification(oneCall, SUPP_SVC_NOTIFICATION_CODE2_RETRIEVED, null,
+ GECKO_SUPP_SVC_NOTIFICATION_REMOTE_RESUMED);
+
+ testNotification(twoCalls, SUPP_SVC_NOTIFICATION_CODE2_PUT_ON_HOLD, null,
+ GECKO_SUPP_SVC_NOTIFICATION_REMOTE_HELD);
+
+ testNotification(twoCalls, SUPP_SVC_NOTIFICATION_CODE2_RETRIEVED, null,
+ GECKO_SUPP_SVC_NOTIFICATION_REMOTE_RESUMED);
+
+ testNotification(twoCalls, SUPP_SVC_NOTIFICATION_CODE2_PUT_ON_HOLD, '00000',
+ GECKO_SUPP_SVC_NOTIFICATION_REMOTE_HELD);
+
+ testNotification(twoCalls, SUPP_SVC_NOTIFICATION_CODE2_PUT_ON_HOLD, '11111',
+ GECKO_SUPP_SVC_NOTIFICATION_REMOTE_HELD);
+
+ testNotification(twoCalls, SUPP_SVC_NOTIFICATION_CODE2_PUT_ON_HOLD, '22222',
+ GECKO_SUPP_SVC_NOTIFICATION_REMOTE_HELD);
+
+ run_next_test();
+});
diff --git a/dom/system/gonk/tests/test_ril_worker_stk.js b/dom/system/gonk/tests/test_ril_worker_stk.js
new file mode 100644
index 000000000..49b914e89
--- /dev/null
+++ b/dom/system/gonk/tests/test_ril_worker_stk.js
@@ -0,0 +1,1698 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+subscriptLoader.loadSubScript("resource://gre/modules/ril_consts.js", this);
+
+function run_test() {
+ run_next_test();
+}
+
+/**
+ * Helper function.
+ */
+function newUint8SupportOutgoingIndexWorker() {
+ let worker = newWorker();
+ let index = 4; // index for read
+ let buf = [0, 0, 0, 0]; // Preserved parcel size
+ let context = worker.ContextPool._contexts[0];
+
+ context.Buf.writeUint8 = function(value) {
+ if (context.Buf.outgoingIndex >= buf.length) {
+ buf.push(value);
+ } else {
+ buf[context.Buf.outgoingIndex] = value;
+ }
+
+ context.Buf.outgoingIndex++;
+ };
+
+ context.Buf.readUint8 = function() {
+ return buf[index++];
+ };
+
+ context.Buf.seekIncoming = function(offset) {
+ index += offset;
+ };
+
+ worker.debug = do_print;
+
+ return worker;
+}
+
+// Test RIL requests related to STK.
+/**
+ * Verify if RIL.sendStkTerminalProfile be called.
+ */
+add_test(function test_if_send_stk_terminal_profile() {
+ let worker = newUint8Worker();
+ let context = worker.ContextPool._contexts[0];
+ let profileSend = false;
+ context.RIL.sendStkTerminalProfile = function(data) {
+ profileSend = true;
+ };
+
+ let iccStatus = {
+ gsmUmtsSubscriptionAppIndex: 0,
+ apps: [{
+ app_state: CARD_APPSTATE_READY,
+ app_type: CARD_APPTYPE_USIM
+ }],
+ };
+ worker.RILQUIRKS_SEND_STK_PROFILE_DOWNLOAD = false;
+
+ context.RIL._processICCStatus(iccStatus);
+
+ equal(profileSend, false);
+
+ run_next_test();
+});
+
+/**
+ * Verify RIL.sendStkTerminalProfile
+ */
+add_test(function test_send_stk_terminal_profile() {
+ let worker = newUint8Worker();
+ let context = worker.ContextPool._contexts[0];
+ let ril = context.RIL;
+ let buf = context.Buf;
+
+ ril.sendStkTerminalProfile(STK_SUPPORTED_TERMINAL_PROFILE);
+
+ buf.seekIncoming(8);
+ let profile = buf.readString();
+ for (let i = 0; i < STK_SUPPORTED_TERMINAL_PROFILE.length; i++) {
+ equal(parseInt(profile.substring(2 * i, 2 * i + 2), 16),
+ STK_SUPPORTED_TERMINAL_PROFILE[i]);
+ }
+
+ run_next_test();
+});
+
+/**
+ * Verify STK terminal response
+ */
+add_test(function test_stk_terminal_response() {
+ let worker = newUint8SupportOutgoingIndexWorker();
+ let context = worker.ContextPool._contexts[0];
+ let buf = context.Buf;
+ let pduHelper = context.GsmPDUHelper;
+
+ buf.sendParcel = function() {
+ // Type
+ equal(this.readInt32(), REQUEST_STK_SEND_TERMINAL_RESPONSE);
+
+ // Token : we don't care
+ this.readInt32();
+
+ // Data Size, 44 = 2 * (TLV_COMMAND_DETAILS_SIZE(5) +
+ // TLV_DEVICE_ID_SIZE(4) +
+ // TLV_RESULT_SIZE(3) +
+ // TEXT LENGTH(10))
+ equal(this.readInt32(), 44);
+
+ // Command Details, Type-Length-Value
+ equal(pduHelper.readHexOctet(), COMPREHENSIONTLV_TAG_COMMAND_DETAILS |
+ COMPREHENSIONTLV_FLAG_CR);
+ equal(pduHelper.readHexOctet(), 3);
+ equal(pduHelper.readHexOctet(), 0x01);
+ equal(pduHelper.readHexOctet(), STK_CMD_PROVIDE_LOCAL_INFO);
+ equal(pduHelper.readHexOctet(), STK_LOCAL_INFO_NNA);
+
+ // Device Identifies, Type-Length-Value(Source ID-Destination ID)
+ equal(pduHelper.readHexOctet(), COMPREHENSIONTLV_TAG_DEVICE_ID);
+ equal(pduHelper.readHexOctet(), 2);
+ equal(pduHelper.readHexOctet(), STK_DEVICE_ID_ME);
+ equal(pduHelper.readHexOctet(), STK_DEVICE_ID_SIM);
+
+ // Result
+ equal(pduHelper.readHexOctet(), COMPREHENSIONTLV_TAG_RESULT |
+ COMPREHENSIONTLV_FLAG_CR);
+ equal(pduHelper.readHexOctet(), 1);
+ equal(pduHelper.readHexOctet(), STK_RESULT_OK);
+
+ // Text
+ equal(pduHelper.readHexOctet(), COMPREHENSIONTLV_TAG_TEXT_STRING |
+ COMPREHENSIONTLV_FLAG_CR);
+ equal(pduHelper.readHexOctet(), 8);
+ equal(pduHelper.readHexOctet(), STK_TEXT_CODING_GSM_7BIT_PACKED);
+ equal(pduHelper.readSeptetsToString(7, 0, PDU_NL_IDENTIFIER_DEFAULT,
+ PDU_NL_IDENTIFIER_DEFAULT), "Mozilla");
+
+ run_next_test();
+ };
+
+ let response = {
+ command: {
+ commandNumber: 0x01,
+ typeOfCommand: STK_CMD_PROVIDE_LOCAL_INFO,
+ commandQualifier: STK_LOCAL_INFO_NNA,
+ options: {
+ isPacked: true
+ }
+ },
+ input: "Mozilla",
+ resultCode: STK_RESULT_OK
+ };
+ context.RIL.sendStkTerminalResponse(response);
+});
+
+/**
+ * Verify STK terminal response : GET INPUT with empty string.
+ *
+ * @See |TERMINAL RESPONSE: GET INPUT 1.9.1A| of 27.22.4.3.1 GET INPUT (normal)
+ * in TS 102 384.
+ */
+add_test(function test_stk_terminal_response_get_input_empty_string() {
+ let worker = newUint8SupportOutgoingIndexWorker();
+ let context = worker.ContextPool._contexts[0];
+ let buf = context.Buf;
+ let pduHelper = context.GsmPDUHelper;
+
+ buf.sendParcel = function() {
+ // Type
+ equal(this.readInt32(), REQUEST_STK_SEND_TERMINAL_RESPONSE);
+
+ // Token : we don't care
+ this.readInt32();
+
+ // Data Size, 30 = 2 * (TLV_COMMAND_DETAILS_SIZE(5) +
+ // TLV_DEVICE_ID_SIZE(4) +
+ // TLV_RESULT_SIZE(3) +
+ // TEXT LENGTH(3))
+ equal(this.readInt32(), 30);
+
+ // Command Details, Type-Length-Value
+ equal(pduHelper.readHexOctet(), COMPREHENSIONTLV_TAG_COMMAND_DETAILS |
+ COMPREHENSIONTLV_FLAG_CR);
+ equal(pduHelper.readHexOctet(), 3);
+ equal(pduHelper.readHexOctet(), 0x01);
+ equal(pduHelper.readHexOctet(), STK_CMD_GET_INPUT);
+ equal(pduHelper.readHexOctet(), 0x00);
+
+ // Device Identifies, Type-Length-Value(Source ID-Destination ID)
+ equal(pduHelper.readHexOctet(), COMPREHENSIONTLV_TAG_DEVICE_ID);
+ equal(pduHelper.readHexOctet(), 2);
+ equal(pduHelper.readHexOctet(), STK_DEVICE_ID_ME);
+ equal(pduHelper.readHexOctet(), STK_DEVICE_ID_SIM);
+
+ // Result
+ equal(pduHelper.readHexOctet(), COMPREHENSIONTLV_TAG_RESULT |
+ COMPREHENSIONTLV_FLAG_CR);
+ equal(pduHelper.readHexOctet(), 1);
+ equal(pduHelper.readHexOctet(), STK_RESULT_OK);
+
+ // Text
+ equal(pduHelper.readHexOctet(), COMPREHENSIONTLV_TAG_TEXT_STRING |
+ COMPREHENSIONTLV_FLAG_CR);
+ equal(pduHelper.readHexOctet(), 1);
+ equal(pduHelper.readHexOctet(), STK_TEXT_CODING_GSM_8BIT);
+
+ run_next_test();
+ };
+
+ let response = {
+ command: {
+ commandNumber: 0x01,
+ typeOfCommand: STK_CMD_GET_INPUT,
+ commandQualifier: 0x00,
+ options: {
+ minLength: 0,
+ maxLength: 1,
+ defaultText: "<SEND>"
+ }
+ },
+ input: "",
+ resultCode: STK_RESULT_OK
+ };
+ context.RIL.sendStkTerminalResponse(response);
+});
+
+/**
+ * Verify STK terminal response : GET INPUT with 160 unpacked characters.
+ *
+ * @See |TERMINAL RESPONSE: GET INPUT 1.8.1| of 27.22.4.3.1 GET INPUT (normal)
+ * in TS 102 384.
+ */
+add_test(function test_stk_terminal_response_get_input_160_unpacked_characters() {
+ let worker = newUint8SupportOutgoingIndexWorker();
+ let context = worker.ContextPool._contexts[0];
+ let buf = context.Buf;
+ let pduHelper = context.GsmPDUHelper;
+ let iccPduHelper = context.ICCPDUHelper;
+ let TEST_TEXT_STRING = "***1111111111###" +
+ "***2222222222###" +
+ "***3333333333###" +
+ "***4444444444###" +
+ "***5555555555###" +
+ "***6666666666###" +
+ "***7777777777###" +
+ "***8888888888###" +
+ "***9999999999###" +
+ "***0000000000###";
+
+ buf.sendParcel = function() {
+ // Type
+ equal(this.readInt32(), REQUEST_STK_SEND_TERMINAL_RESPONSE);
+
+ // Token : we don't care
+ this.readInt32();
+
+ // Data Size, 352 = 2 * (TLV_COMMAND_DETAILS_SIZE(5) +
+ // TLV_DEVICE_ID_SIZE(4) +
+ // TLV_RESULT_SIZE(3) +
+ // TEXT LENGTH(164))
+ equal(this.readInt32(), 352);
+
+ // Command Details, Type-Length-Value
+ equal(pduHelper.readHexOctet(), COMPREHENSIONTLV_TAG_COMMAND_DETAILS |
+ COMPREHENSIONTLV_FLAG_CR);
+ equal(pduHelper.readHexOctet(), 3);
+ equal(pduHelper.readHexOctet(), 0x01);
+ equal(pduHelper.readHexOctet(), STK_CMD_GET_INPUT);
+ equal(pduHelper.readHexOctet(), 0x00);
+
+ // Device Identifies, Type-Length-Value(Source ID-Destination ID)
+ equal(pduHelper.readHexOctet(), COMPREHENSIONTLV_TAG_DEVICE_ID);
+ equal(pduHelper.readHexOctet(), 2);
+ equal(pduHelper.readHexOctet(), STK_DEVICE_ID_ME);
+ equal(pduHelper.readHexOctet(), STK_DEVICE_ID_SIM);
+
+ // Result
+ equal(pduHelper.readHexOctet(), COMPREHENSIONTLV_TAG_RESULT |
+ COMPREHENSIONTLV_FLAG_CR);
+ equal(pduHelper.readHexOctet(), 1);
+ equal(pduHelper.readHexOctet(), STK_RESULT_OK);
+
+ // Text
+ equal(pduHelper.readHexOctet(), COMPREHENSIONTLV_TAG_TEXT_STRING |
+ COMPREHENSIONTLV_FLAG_CR);
+ // C-TLV Length Encoding: 161 = 0x81 0xA1
+ equal(pduHelper.readHexOctet(), 0x81);
+ equal(pduHelper.readHexOctet(), 0xA1);
+ equal(pduHelper.readHexOctet(), STK_TEXT_CODING_GSM_8BIT);
+ equal(iccPduHelper.read8BitUnpackedToString(160), TEST_TEXT_STRING);
+
+ run_next_test();
+ };
+
+ let response = {
+ command: {
+ commandNumber: 0x01,
+ typeOfCommand: STK_CMD_GET_INPUT,
+ commandQualifier: 0x00,
+ options: {
+ minLength: 160,
+ maxLength: 160,
+ text: TEST_TEXT_STRING
+ }
+ },
+ input: TEST_TEXT_STRING,
+ resultCode: STK_RESULT_OK
+ };
+ context.RIL.sendStkTerminalResponse(response);
+});
+
+/**
+ * Verify STK terminal response : GET_INKEY - NO_RESPONSE_FROM_USER with
+ * duration provided.
+ *
+ * @See |27.22.4.2.8 GET INKEY (Variable Time out)| in TS 102 384.
+ */
+add_test(function test_stk_terminal_response_get_inkey_no_response_from_user() {
+ let worker = newUint8SupportOutgoingIndexWorker();
+ let context = worker.ContextPool._contexts[0];
+ let buf = context.Buf;
+ let pduHelper = context.GsmPDUHelper;
+
+ buf.sendParcel = function() {
+ // Type
+ equal(this.readInt32(), REQUEST_STK_SEND_TERMINAL_RESPONSE);
+
+ // Token : we don't care
+ this.readInt32();
+
+ // Data Size, 32 = 2 * (TLV_COMMAND_DETAILS_SIZE(5) +
+ // TLV_DEVICE_ID_SIZE(4) +
+ // TLV_RESULT_SIZE(3) +
+ // DURATION(4))
+ equal(this.readInt32(), 32);
+
+ // Command Details, Type-Length-Value
+ equal(pduHelper.readHexOctet(), COMPREHENSIONTLV_TAG_COMMAND_DETAILS |
+ COMPREHENSIONTLV_FLAG_CR);
+ equal(pduHelper.readHexOctet(), 3);
+ equal(pduHelper.readHexOctet(), 0x01);
+ equal(pduHelper.readHexOctet(), STK_CMD_GET_INKEY);
+ equal(pduHelper.readHexOctet(), 0x00);
+
+ // Device Identifies, Type-Length-Value(Source ID-Destination ID)
+ equal(pduHelper.readHexOctet(), COMPREHENSIONTLV_TAG_DEVICE_ID);
+ equal(pduHelper.readHexOctet(), 2);
+ equal(pduHelper.readHexOctet(), STK_DEVICE_ID_ME);
+ equal(pduHelper.readHexOctet(), STK_DEVICE_ID_SIM);
+
+ // Result
+ equal(pduHelper.readHexOctet(), COMPREHENSIONTLV_TAG_RESULT |
+ COMPREHENSIONTLV_FLAG_CR);
+ equal(pduHelper.readHexOctet(), 1);
+ equal(pduHelper.readHexOctet(), STK_RESULT_NO_RESPONSE_FROM_USER);
+
+ // Duration, Type-Length-Value(Time unit, Time interval)
+ equal(pduHelper.readHexOctet(), COMPREHENSIONTLV_TAG_DURATION);
+ equal(pduHelper.readHexOctet(), 2);
+ equal(pduHelper.readHexOctet(), STK_TIME_UNIT_SECOND);
+ equal(pduHelper.readHexOctet(), 10);
+
+ run_next_test();
+ };
+
+ let response = {
+ command: {
+ commandNumber: 0x01,
+ typeOfCommand: STK_CMD_GET_INKEY,
+ commandQualifier: 0x00,
+ options: {
+ duration: {
+ timeUnit: STK_TIME_UNIT_SECOND,
+ timeInterval: 10
+ },
+ text: 'Enter "+"'
+ }
+ },
+ resultCode: STK_RESULT_NO_RESPONSE_FROM_USER
+ };
+ context.RIL.sendStkTerminalResponse(response);
+});
+
+/**
+ * Verify STK terminal response : GET_INKEY - YES/NO request
+ */
+add_test(function test_stk_terminal_response_get_inkey() {
+ function do_test(isYesNo) {
+ let worker = newUint8SupportOutgoingIndexWorker();
+ let context = worker.ContextPool._contexts[0];
+ let buf = context.Buf;
+ let pduHelper = context.GsmPDUHelper;
+
+ buf.sendParcel = function() {
+ // Type
+ equal(this.readInt32(), REQUEST_STK_SEND_TERMINAL_RESPONSE);
+
+ // Token : we don't care
+ this.readInt32();
+
+ // Data Size, 32 = 2 * (TLV_COMMAND_DETAILS_SIZE(5) +
+ // TLV_DEVICE_ID_SIZE(4) +
+ // TLV_RESULT_SIZE(3) +
+ // TEXT LENGTH(4))
+ equal(this.readInt32(), 32);
+
+ // Command Details, Type-Length-Value
+ equal(pduHelper.readHexOctet(), COMPREHENSIONTLV_TAG_COMMAND_DETAILS |
+ COMPREHENSIONTLV_FLAG_CR);
+ equal(pduHelper.readHexOctet(), 3);
+ equal(pduHelper.readHexOctet(), 0x01);
+ equal(pduHelper.readHexOctet(), STK_CMD_GET_INKEY);
+ equal(pduHelper.readHexOctet(), 0x04);
+
+ // Device Identifies, Type-Length-Value(Source ID-Destination ID)
+ equal(pduHelper.readHexOctet(), COMPREHENSIONTLV_TAG_DEVICE_ID);
+ equal(pduHelper.readHexOctet(), 2);
+ equal(pduHelper.readHexOctet(), STK_DEVICE_ID_ME);
+ equal(pduHelper.readHexOctet(), STK_DEVICE_ID_SIM);
+
+ // Result
+ equal(pduHelper.readHexOctet(), COMPREHENSIONTLV_TAG_RESULT |
+ COMPREHENSIONTLV_FLAG_CR);
+ equal(pduHelper.readHexOctet(), 1);
+ equal(pduHelper.readHexOctet(), STK_RESULT_OK);
+
+ // Yes/No response
+ equal(pduHelper.readHexOctet(), COMPREHENSIONTLV_TAG_TEXT_STRING |
+ COMPREHENSIONTLV_FLAG_CR);
+ equal(pduHelper.readHexOctet(), 2);
+ equal(pduHelper.readHexOctet(), STK_TEXT_CODING_GSM_8BIT);
+ equal(pduHelper.readHexOctet(), isYesNo ? 0x01 : 0x00);
+ };
+
+ let response = {
+ command: {
+ commandNumber: 0x01,
+ typeOfCommand: STK_CMD_GET_INKEY,
+ commandQualifier: 0x04,
+ options: {
+ isYesNoRequested: true
+ }
+ },
+ isYesNo: isYesNo,
+ resultCode: STK_RESULT_OK
+ };
+
+ context.RIL.sendStkTerminalResponse(response);
+ };
+
+ // Test "Yes" response
+ do_test(true);
+ // Test "No" response
+ do_test(false);
+
+ run_next_test();
+});
+
+/**
+ * Verify STK terminal response with additional information.
+ */
+add_test(function test_stk_terminal_response_with_additional_info() {
+ function do_test(aInfo) {
+ let worker = newUint8SupportOutgoingIndexWorker();
+ let context = worker.ContextPool._contexts[0];
+ let buf = context.Buf;
+ let pduHelper = context.GsmPDUHelper;
+
+ buf.sendParcel = function() {
+ // Type
+ equal(this.readInt32(), REQUEST_STK_SEND_TERMINAL_RESPONSE);
+
+ // Token : we don't care
+ this.readInt32();
+
+ // Data Length 26 = 2 * (TLV_COMMAND_DETAILS_SIZE(5) +
+ // TLV_DEVICE_ID_SIZE(4) +
+ // TLV_RESULT_SIZE(4))
+ equal(this.readInt32(), 26);
+
+ // Command Details, Type-Length-Value(commandNumber, typeOfCommand, commandQualifier)
+ equal(pduHelper.readHexOctet(), COMPREHENSIONTLV_TAG_COMMAND_DETAILS |
+ COMPREHENSIONTLV_FLAG_CR);
+ equal(pduHelper.readHexOctet(), 3);
+ equal(pduHelper.readHexOctet(), 0x01);
+ equal(pduHelper.readHexOctet(), STK_CMD_DISPLAY_TEXT);
+ equal(pduHelper.readHexOctet(), 0x01);
+
+ // Device Identifies, Type-Length-Value(Source ID-Destination ID)
+ equal(pduHelper.readHexOctet(), COMPREHENSIONTLV_TAG_DEVICE_ID);
+ equal(pduHelper.readHexOctet(), 2);
+ equal(pduHelper.readHexOctet(), STK_DEVICE_ID_ME);
+ equal(pduHelper.readHexOctet(), STK_DEVICE_ID_SIM);
+
+ // Result, Type-Length-Value(General result, Additional information on result)
+ equal(pduHelper.readHexOctet(), COMPREHENSIONTLV_TAG_RESULT |
+ COMPREHENSIONTLV_FLAG_CR);
+ equal(pduHelper.readHexOctet(), 2);
+ equal(pduHelper.readHexOctet(), STK_RESULT_TERMINAL_CRNTLY_UNABLE_TO_PROCESS);
+ equal(pduHelper.readHexOctet(), aInfo);
+ };
+
+ let response = {
+ command: {
+ commandNumber: 0x01,
+ typeOfCommand: STK_CMD_DISPLAY_TEXT,
+ commandQualifier: 0x01,
+ options: {
+ isHighPriority: true
+ }
+ },
+ resultCode: STK_RESULT_TERMINAL_CRNTLY_UNABLE_TO_PROCESS,
+ additionalInformation: aInfo
+ };
+
+ context.RIL.sendStkTerminalResponse(response);
+ };
+
+ do_test(0x01); // 'Screen is busy'
+
+ run_next_test();
+});
+
+// Test ComprehensionTlvHelper
+
+/**
+ * Verify ComprehensionTlvHelper.writeLocationInfoTlv
+ */
+add_test(function test_write_location_info_tlv() {
+ let worker = newUint8Worker();
+ let context = worker.ContextPool._contexts[0];
+ let pduHelper = context.GsmPDUHelper;
+ let tlvHelper = context.ComprehensionTlvHelper;
+
+ // Test with 2-digit mnc, and gsmCellId obtained from UMTS network.
+ let loc = {
+ mcc: "466",
+ mnc: "92",
+ gsmLocationAreaCode : 10291,
+ gsmCellId: 19072823
+ };
+ tlvHelper.writeLocationInfoTlv(loc);
+
+ let tag = pduHelper.readHexOctet();
+ equal(tag, COMPREHENSIONTLV_TAG_LOCATION_INFO |
+ COMPREHENSIONTLV_FLAG_CR);
+
+ let length = pduHelper.readHexOctet();
+ equal(length, 9);
+
+ let mcc_mnc = pduHelper.readSwappedNibbleBcdString(3);
+ equal(mcc_mnc, "46692");
+
+ let lac = (pduHelper.readHexOctet() << 8) | pduHelper.readHexOctet();
+ equal(lac, 10291);
+
+ let cellId = (pduHelper.readHexOctet() << 24) |
+ (pduHelper.readHexOctet() << 16) |
+ (pduHelper.readHexOctet() << 8) |
+ (pduHelper.readHexOctet());
+ equal(cellId, 19072823);
+
+ // Test with 1-digit mnc, and gsmCellId obtained from GSM network.
+ loc = {
+ mcc: "466",
+ mnc: "02",
+ gsmLocationAreaCode : 10291,
+ gsmCellId: 65534
+ };
+ tlvHelper.writeLocationInfoTlv(loc);
+
+ tag = pduHelper.readHexOctet();
+ equal(tag, COMPREHENSIONTLV_TAG_LOCATION_INFO |
+ COMPREHENSIONTLV_FLAG_CR);
+
+ length = pduHelper.readHexOctet();
+ equal(length, 7);
+
+ mcc_mnc = pduHelper.readSwappedNibbleBcdString(3);
+ equal(mcc_mnc, "46602");
+
+ lac = (pduHelper.readHexOctet() << 8) | pduHelper.readHexOctet();
+ equal(lac, 10291);
+
+ cellId = (pduHelper.readHexOctet() << 8) | (pduHelper.readHexOctet());
+ equal(cellId, 65534);
+
+ // Test with 3-digit mnc, and gsmCellId obtained from GSM network.
+ loc = {
+ mcc: "466",
+ mnc: "222",
+ gsmLocationAreaCode : 10291,
+ gsmCellId: 65534
+ };
+ tlvHelper.writeLocationInfoTlv(loc);
+
+ tag = pduHelper.readHexOctet();
+ equal(tag, COMPREHENSIONTLV_TAG_LOCATION_INFO |
+ COMPREHENSIONTLV_FLAG_CR);
+
+ length = pduHelper.readHexOctet();
+ equal(length, 7);
+
+ mcc_mnc = pduHelper.readSwappedNibbleBcdString(3);
+ equal(mcc_mnc, "466222");
+
+ lac = (pduHelper.readHexOctet() << 8) | pduHelper.readHexOctet();
+ equal(lac, 10291);
+
+ cellId = (pduHelper.readHexOctet() << 8) | (pduHelper.readHexOctet());
+ equal(cellId, 65534);
+
+ run_next_test();
+});
+
+/**
+ * Verify ComprehensionTlvHelper.writeErrorNumber
+ */
+add_test(function test_write_disconnecting_cause() {
+ let worker = newUint8Worker();
+ let context = worker.ContextPool._contexts[0];
+ let pduHelper = context.GsmPDUHelper;
+ let tlvHelper = context.ComprehensionTlvHelper;
+
+ tlvHelper.writeCauseTlv(RIL_CALL_FAILCAUSE_TO_GECKO_CALL_ERROR[CALL_FAIL_BUSY]);
+ let tag = pduHelper.readHexOctet();
+ equal(tag, COMPREHENSIONTLV_TAG_CAUSE | COMPREHENSIONTLV_FLAG_CR);
+ let len = pduHelper.readHexOctet();
+ equal(len, 2); // We have one cause.
+ let standard = pduHelper.readHexOctet();
+ equal(standard, 0x60);
+ let cause = pduHelper.readHexOctet();
+ equal(cause, 0x80 | CALL_FAIL_BUSY);
+
+ run_next_test();
+});
+
+/**
+ * Verify ComprehensionTlvHelper.getSizeOfLengthOctets
+ */
+add_test(function test_get_size_of_length_octets() {
+ let worker = newUint8Worker();
+ let context = worker.ContextPool._contexts[0];
+ let tlvHelper = context.ComprehensionTlvHelper;
+
+ let length = 0x70;
+ equal(tlvHelper.getSizeOfLengthOctets(length), 1);
+
+ length = 0x80;
+ equal(tlvHelper.getSizeOfLengthOctets(length), 2);
+
+ length = 0x180;
+ equal(tlvHelper.getSizeOfLengthOctets(length), 3);
+
+ length = 0x18000;
+ equal(tlvHelper.getSizeOfLengthOctets(length), 4);
+
+ run_next_test();
+});
+
+/**
+ * Verify ComprehensionTlvHelper.writeLength
+ */
+add_test(function test_write_length() {
+ let worker = newUint8Worker();
+ let context = worker.ContextPool._contexts[0];
+ let pduHelper = context.GsmPDUHelper;
+ let tlvHelper = context.ComprehensionTlvHelper;
+
+ let length = 0x70;
+ tlvHelper.writeLength(length);
+ equal(pduHelper.readHexOctet(), length);
+
+ length = 0x80;
+ tlvHelper.writeLength(length);
+ equal(pduHelper.readHexOctet(), 0x81);
+ equal(pduHelper.readHexOctet(), length);
+
+ length = 0x180;
+ tlvHelper.writeLength(length);
+ equal(pduHelper.readHexOctet(), 0x82);
+ equal(pduHelper.readHexOctet(), (length >> 8) & 0xff);
+ equal(pduHelper.readHexOctet(), length & 0xff);
+
+ length = 0x18000;
+ tlvHelper.writeLength(length);
+ equal(pduHelper.readHexOctet(), 0x83);
+ equal(pduHelper.readHexOctet(), (length >> 16) & 0xff);
+ equal(pduHelper.readHexOctet(), (length >> 8) & 0xff);
+ equal(pduHelper.readHexOctet(), length & 0xff);
+
+ run_next_test();
+});
+
+// Test Proactive commands.
+
+function test_stk_proactive_command(aOptions) {
+ let worker = newUint8Worker();
+ let context = worker.ContextPool._contexts[0];
+ let pduHelper = context.GsmPDUHelper;
+ let berHelper = context.BerTlvHelper;
+ let stkHelper = context.StkProactiveCmdHelper;
+ let stkFactory = context.StkCommandParamsFactory;
+
+ let testPdu = aOptions.pdu;
+ let testTypeOfCommand = aOptions.typeOfCommand;
+ let testIcons = aOptions.icons;
+ let testFunc = aOptions.testFunc;
+
+ if (testIcons) {
+ let ril = context.RIL;
+ ril.iccInfoPrivate.sst = [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x30]; //IMG: 39
+ ril.appType = CARD_APPTYPE_SIM;
+
+ // skip asynchornous process in IconLoader.loadIcons().
+ let iconLoader = context.IconLoader;
+ iconLoader.loadIcons = (recordNumbers, onsuccess, onerror) => {
+ onsuccess(testIcons);
+ };
+ }
+
+ for(let i = 0 ; i < testPdu.length; i++) {
+ pduHelper.writeHexOctet(testPdu[i]);
+ }
+
+ let berTlv = berHelper.decode(testPdu.length);
+ let ctlvs = berTlv.value;
+ let ctlv = stkHelper.searchForTag(COMPREHENSIONTLV_TAG_COMMAND_DETAILS, ctlvs);
+ let cmdDetails = ctlv.value;
+ equal(cmdDetails.typeOfCommand, testTypeOfCommand);
+
+ stkFactory.createParam(cmdDetails, ctlvs, (aResult) => {
+ cmdDetails.options = aResult;
+ testFunc(context, cmdDetails, ctlvs);
+ });
+}
+
+/**
+ * Verify Proactive command helper : searchForSelectedTags
+ */
+add_test(function test_stk_proactive_command_search_for_selected_tags() {
+ let worker = newUint8Worker();
+ let context = worker.ContextPool._contexts[0];
+ let pduHelper = context.GsmPDUHelper;
+ let berHelper = context.BerTlvHelper;
+ let stkHelper = context.StkProactiveCmdHelper;
+
+ let tag_test = [
+ 0xD0,
+ 0x3E,
+ 0x85, 0x0A, 0x61, 0x6C, 0x70, 0x68, 0x61, 0x20, 0x69, 0x64, 0x20, 0x31,
+ 0x85, 0x0A, 0x61, 0x6C, 0x70, 0x68, 0x61, 0x20, 0x69, 0x64, 0x20, 0x32,
+ 0x85, 0x0A, 0x61, 0x6C, 0x70, 0x68, 0x61, 0x20, 0x69, 0x64, 0x20, 0x33,
+ 0x85, 0x0A, 0x61, 0x6C, 0x70, 0x68, 0x61, 0x20, 0x69, 0x64, 0x20, 0x34,
+ 0x85, 0x0A, 0x61, 0x6C, 0x70, 0x68, 0x61, 0x20, 0x69, 0x64, 0x20, 0x35,
+ 0x85, 0x00];
+
+ for (let i = 0; i < tag_test.length; i++) {
+ pduHelper.writeHexOctet(tag_test[i]);
+ }
+
+ let berTlv = berHelper.decode(tag_test.length);
+ let selectedCtlvs =
+ stkHelper.searchForSelectedTags(berTlv.value, [COMPREHENSIONTLV_TAG_ALPHA_ID]);
+ let tlv = selectedCtlvs.retrieve(COMPREHENSIONTLV_TAG_ALPHA_ID);
+ equal(tlv.value.identifier, "alpha id 1");
+
+ tlv = selectedCtlvs.retrieve(COMPREHENSIONTLV_TAG_ALPHA_ID);
+ equal(tlv.value.identifier, "alpha id 2");
+
+ tlv = selectedCtlvs.retrieve(COMPREHENSIONTLV_TAG_ALPHA_ID);
+ equal(tlv.value.identifier, "alpha id 3");
+
+ tlv = selectedCtlvs.retrieve(COMPREHENSIONTLV_TAG_ALPHA_ID);
+ equal(tlv.value.identifier, "alpha id 4");
+
+ tlv = selectedCtlvs.retrieve(COMPREHENSIONTLV_TAG_ALPHA_ID);
+ equal(tlv.value.identifier, "alpha id 5");
+
+ // emulate that the alpha identifier is provided and is a null data object,
+ // which is converted to an empty string in ICCPDUHelper.
+ tlv = selectedCtlvs.retrieve(COMPREHENSIONTLV_TAG_ALPHA_ID);
+ strictEqual(tlv.value.identifier, "");
+
+ // emulate that the alpha identifier is not provided
+ tlv = selectedCtlvs.retrieve(COMPREHENSIONTLV_TAG_ALPHA_ID);
+ strictEqual(tlv, undefined);
+
+ run_next_test();
+});
+
+/**
+ * Verify Proactive Command : Refresh
+ */
+add_test(function test_stk_proactive_command_refresh() {
+ test_stk_proactive_command({
+ pdu: [
+ 0xD0,
+ 0x10,
+ 0x81, 0x03, 0x01, 0x01, 0x01,
+ 0x82, 0x02, 0x81, 0x82,
+ 0x92, 0x05, 0x01, 0x3F, 0x00, 0x2F, 0xE2
+ ],
+ typeOfCommand: STK_CMD_REFRESH,
+ icons: null,
+ testFunc: (context, cmdDetails, ctlvs) => {
+ let stkHelper = context.StkProactiveCmdHelper;
+ let ctlv = stkHelper.searchForTag(COMPREHENSIONTLV_TAG_FILE_LIST, ctlvs);
+ equal(ctlv.value.fileList, "3F002FE2");
+ }
+ });
+
+ run_next_test();
+});
+
+/**
+ * Verify Proactive Command : Play Tone
+ */
+add_test(function test_stk_proactive_command_play_tone() {
+ test_stk_proactive_command({
+ pdu: [
+ 0xD0,
+ 0x1F,
+ 0x81, 0x03, 0x01, 0x20, 0x00,
+ 0x82, 0x02, 0x81, 0x03,
+ 0x85, 0x09, 0x44, 0x69, 0x61, 0x6C, 0x20, 0x54, 0x6F, 0x6E, 0x65,
+ 0x8E, 0x01, 0x01,
+ 0x84, 0x02, 0x01, 0x05,
+ 0x9E, 0x02, 0x00, 0x01
+ ],
+ typeOfCommand: STK_CMD_PLAY_TONE,
+ icons: [1],
+ testFunc: (context, cmdDetails, ctlvs) => {
+ let playTone = cmdDetails.options;
+
+ equal(playTone.text, "Dial Tone");
+ equal(playTone.tone, STK_TONE_TYPE_DIAL_TONE);
+ equal(playTone.duration.timeUnit, STK_TIME_UNIT_SECOND);
+ equal(playTone.duration.timeInterval, 5);
+ equal(playTone.iconSelfExplanatory, true);
+ equal(playTone.icons, 1);
+ }
+ });
+
+ run_next_test();
+});
+
+/**
+ * Verify Proactive Command : Poll Interval
+ */
+add_test(function test_stk_proactive_command_poll_interval() {
+ test_stk_proactive_command({
+ pdu: [
+ 0xD0,
+ 0x0D,
+ 0x81, 0x03, 0x01, 0x03, 0x00,
+ 0x82, 0x02, 0x81, 0x82,
+ 0x84, 0x02, 0x01, 0x14
+ ],
+ typeOfCommand: STK_CMD_POLL_INTERVAL,
+ icons: null,
+ testFunc: (context, cmdDetails, ctlvs) => {
+ let interval = cmdDetails.options;
+
+ equal(interval.timeUnit, STK_TIME_UNIT_SECOND);
+ equal(interval.timeInterval, 0x14);
+ }
+ });
+
+ run_next_test();
+});
+
+/**
+ * Verify Proactive Command: Display Text
+ */
+add_test(function test_stk_proactive_command_display_text() {
+ test_stk_proactive_command({
+ pdu: [
+ 0xd0,
+ 0x2c,
+ 0x81, 0x03, 0x01, 0x21, 0x80,
+ 0x82, 0x02, 0x81, 0x02,
+ 0x0d, 0x1d, 0x00, 0xd3, 0x30, 0x9b, 0xfc, 0x06, 0xc9, 0x5c, 0x30, 0x1a,
+ 0xa8, 0xe8, 0x02, 0x59, 0xc3, 0xec, 0x34, 0xb9, 0xac, 0x07, 0xc9, 0x60,
+ 0x2f, 0x58, 0xed, 0x15, 0x9b, 0xb9, 0x40,
+ 0x9e, 0x02, 0x00, 0x01
+ ],
+ typeOfCommand: STK_CMD_DISPLAY_TEXT,
+ icons: [1],
+ testFunc: (context, cmdDetails, ctlvs) => {
+ let textMsg = cmdDetails.options;
+
+ equal(textMsg.text, "Saldo 2.04 E. Validez 20/05/13. ");
+ equal(textMsg.iconSelfExplanatory, true);
+ equal(textMsg.icons, 1);
+ }
+ });
+
+ run_next_test();
+});
+
+/**
+ * Verify Proactive Command: Set Up Event List.
+ */
+add_test(function test_stk_proactive_command_event_list() {
+ test_stk_proactive_command({
+ pdu: [
+ 0xD0,
+ 0x0F,
+ 0x81, 0x03, 0x01, 0x05, 0x00,
+ 0x82, 0x02, 0x81, 0x82,
+ 0x99, 0x04, 0x00, 0x01, 0x02, 0x03
+ ],
+ typeOfCommand: STK_CMD_SET_UP_EVENT_LIST,
+ icons: null,
+ testFunc: (context, cmdDetails, ctlvs) => {
+ let event = cmdDetails.options;
+
+ equal(Array.isArray(event.eventList), true);
+
+ for (let i = 0; i < event.eventList.length; i++) {
+ equal(event.eventList[i], i);
+ }
+ }
+ });
+
+ run_next_test();
+});
+
+/**
+ * Verify Proactive Command : Get Input
+ */
+add_test(function test_stk_proactive_command_get_input() {
+ test_stk_proactive_command({
+ pdu: [
+ 0xD0,
+ 0x22,
+ 0x81, 0x03, 0x01, 0x23, 0x8F,
+ 0x82, 0x02, 0x81, 0x82,
+ 0x8D, 0x05, 0x04, 0x54, 0x65, 0x78, 0x74,
+ 0x91, 0x02, 0x01, 0x10,
+ 0x17, 0x08, 0x04, 0x44, 0x65, 0x66, 0x61, 0x75, 0x6C, 0x74,
+ 0x9E, 0x02, 0x00, 0x01
+ ],
+ typeOfCommand: STK_CMD_GET_INPUT,
+ icons: [1],
+ testFunc: (context, cmdDetails, ctlvs) => {
+ let input = cmdDetails.options;
+
+ equal(input.text, "Text");
+ equal(input.isAlphabet, true);
+ equal(input.isUCS2, true);
+ equal(input.hideInput, true);
+ equal(input.isPacked, true);
+ equal(input.isHelpAvailable, true);
+ equal(input.minLength, 0x01);
+ equal(input.maxLength, 0x10);
+ equal(input.defaultText, "Default");
+ equal(input.iconSelfExplanatory, true);
+ equal(input.icons, 1);
+ }
+ });
+
+ test_stk_proactive_command({
+ pdu: [
+ 0xD0,
+ 0x11,
+ 0x81, 0x03, 0x01, 0x23, 0x00,
+ 0x82, 0x02, 0x81, 0x82,
+ 0x8D, 0x00,
+ 0x91, 0x02, 0x01, 0x10,
+ 0x17, 0x00
+ ],
+ typeOfCommand: STK_CMD_GET_INPUT,
+ icons: null,
+ testFunc: (context, cmdDetails, ctlvs) => {
+ let input = cmdDetails.options;
+
+ equal(input.text, null);
+ equal(input.minLength, 0x01);
+ equal(input.maxLength, 0x10);
+ equal(input.defaultText, null);
+ }
+ });
+
+ run_next_test();
+});
+
+/**
+ * Verify Proactive Command : More Time
+ */
+add_test(function test_stk_proactive_command_more_time() {
+ let worker = newUint8Worker();
+ let context = worker.ContextPool._contexts[0];
+ let pduHelper = context.GsmPDUHelper;
+ let berHelper = context.BerTlvHelper;
+ let stkHelper = context.StkProactiveCmdHelper;
+
+ let more_time_1 = [
+ 0xD0,
+ 0x09,
+ 0x81, 0x03, 0x01, 0x02, 0x00,
+ 0x82, 0x02, 0x81, 0x82];
+
+ for(let i = 0 ; i < more_time_1.length; i++) {
+ pduHelper.writeHexOctet(more_time_1[i]);
+ }
+
+ let berTlv = berHelper.decode(more_time_1.length);
+ let ctlvs = berTlv.value;
+ let tlv = stkHelper.searchForTag(COMPREHENSIONTLV_TAG_COMMAND_DETAILS, ctlvs);
+ equal(tlv.value.commandNumber, 0x01);
+ equal(tlv.value.typeOfCommand, STK_CMD_MORE_TIME);
+ equal(tlv.value.commandQualifier, 0x00);
+
+ run_next_test();
+});
+
+/**
+ * Verify Proactive Command : Select Item
+ */
+add_test(function test_stk_proactive_command_select_item() {
+ test_stk_proactive_command({
+ pdu: [
+ 0xD0,
+ 0x3D,
+ 0x81, 0x03, 0x01, 0x24, 0x00,
+ 0x82, 0x02, 0x81, 0x82,
+ 0x85, 0x05, 0x54, 0x69, 0x74, 0x6C, 0x65,
+ 0x8F, 0x07, 0x01, 0x69, 0x74, 0x65, 0x6D, 0x20, 0x31,
+ 0x8F, 0x07, 0x02, 0x69, 0x74, 0x65, 0x6D, 0x20, 0x32,
+ 0x8F, 0x07, 0x03, 0x69, 0x74, 0x65, 0x6D, 0x20, 0x33,
+ 0x18, 0x03, 0x10, 0x15, 0x20,
+ 0x90, 0x01, 0x01,
+ 0x9E, 0x02, 0x00, 0x01,
+ 0x9F, 0x04, 0x00, 0x01, 0x02, 0x03
+ ],
+ typeOfCommand: STK_CMD_SELECT_ITEM,
+ icons: [1, 1, 2, 3],
+ testFunc: (context, cmdDetails, ctlvs) => {
+ let menu = cmdDetails.options;
+
+ equal(menu.title, "Title");
+ equal(menu.iconSelfExplanatory, true);
+ equal(menu.icons, 1);
+ equal(menu.items[0].identifier, 1);
+ equal(menu.items[0].text, "item 1");
+ equal(menu.items[0].iconSelfExplanatory, true);
+ equal(menu.items[0].icons, 1);
+ equal(menu.items[1].identifier, 2);
+ equal(menu.items[1].text, "item 2");
+ equal(menu.items[1].iconSelfExplanatory, true);
+ equal(menu.items[1].icons, 2);
+ equal(menu.items[2].identifier, 3);
+ equal(menu.items[2].text, "item 3");
+ equal(menu.items[2].iconSelfExplanatory, true);
+ equal(menu.items[2].icons, 3);
+ equal(menu.nextActionList[0], STK_CMD_SET_UP_CALL);
+ equal(menu.nextActionList[1], STK_CMD_LAUNCH_BROWSER);
+ equal(menu.nextActionList[2], STK_CMD_PLAY_TONE);
+ equal(menu.defaultItem, 0x00);
+ }
+ });
+
+ test_stk_proactive_command({
+ pdu: [
+ 0xD0,
+ 0x33,
+ 0x81, 0x03, 0x01, 0x24, 0x00,
+ 0x82, 0x02, 0x81, 0x82,
+ 0x85, 0x05, 0x54, 0x69, 0x74, 0x6C, 0x65,
+ 0x8F, 0x07, 0x01, 0x69, 0x74, 0x65, 0x6D, 0x20, 0x31,
+ 0x8F, 0x07, 0x02, 0x69, 0x74, 0x65, 0x6D, 0x20, 0x32,
+ 0x8F, 0x07, 0x03, 0x69, 0x74, 0x65, 0x6D, 0x20, 0x33,
+ 0x18, 0x03, 0x00, 0x15, 0x81,
+ 0x90, 0x01, 0x03
+ ],
+ typeOfCommand: STK_CMD_SELECT_ITEM,
+ icons: null,
+ testFunc: (context, cmdDetails, ctlvs) => {
+ let menu = cmdDetails.options;
+
+ equal(menu.title, "Title");
+ equal(menu.items[0].identifier, 1);
+ equal(menu.items[0].text, "item 1");
+ equal(menu.items[1].identifier, 2);
+ equal(menu.items[1].text, "item 2");
+ equal(menu.items[2].identifier, 3);
+ equal(menu.items[2].text, "item 3");
+ equal(menu.nextActionList[0], STK_NEXT_ACTION_NULL);
+ equal(menu.nextActionList[1], STK_CMD_LAUNCH_BROWSER);
+ equal(menu.nextActionList[2], STK_NEXT_ACTION_END_PROACTIVE_SESSION);
+ equal(menu.defaultItem, 0x02);
+ }
+ });
+
+ run_next_test();
+});
+
+/**
+ * Verify Proactive Command : Set Up Menu
+ */
+add_test(function test_stk_proactive_command_set_up_menu() {
+ test_stk_proactive_command({
+ pdu: [
+ 0xD0,
+ 0x3A,
+ 0x81, 0x03, 0x01, 0x25, 0x00,
+ 0x82, 0x02, 0x81, 0x82,
+ 0x85, 0x05, 0x54, 0x69, 0x74, 0x6C, 0x65,
+ 0x8F, 0x07, 0x01, 0x69, 0x74, 0x65, 0x6D, 0x20, 0x31,
+ 0x8F, 0x07, 0x02, 0x69, 0x74, 0x65, 0x6D, 0x20, 0x32,
+ 0x8F, 0x07, 0x03, 0x69, 0x74, 0x65, 0x6D, 0x20, 0x33,
+ 0x18, 0x03, 0x10, 0x15, 0x20,
+ 0x9E, 0x02, 0x00, 0x01,
+ 0x9F, 0x04, 0x00, 0x01, 0x02, 0x03
+ ],
+ typeOfCommand: STK_CMD_SET_UP_MENU,
+ icons: [1, 1, 2, 3],
+ testFunc: (context, cmdDetails, ctlvs) => {
+ let menu = cmdDetails.options;
+
+ equal(menu.title, "Title");
+ equal(menu.iconSelfExplanatory, true);
+ equal(menu.icons, 1);
+ equal(menu.items[0].identifier, 1);
+ equal(menu.items[0].text, "item 1");
+ equal(menu.items[0].iconSelfExplanatory, true);
+ equal(menu.items[0].icons, 1);
+ equal(menu.items[1].identifier, 2);
+ equal(menu.items[1].text, "item 2");
+ equal(menu.items[1].iconSelfExplanatory, true);
+ equal(menu.items[1].icons, 2);
+ equal(menu.items[2].identifier, 3);
+ equal(menu.items[2].text, "item 3");
+ equal(menu.items[2].iconSelfExplanatory, true);
+ equal(menu.items[2].icons, 3);
+ equal(menu.nextActionList[0], STK_CMD_SET_UP_CALL);
+ equal(menu.nextActionList[1], STK_CMD_LAUNCH_BROWSER);
+ equal(menu.nextActionList[2], STK_CMD_PLAY_TONE);
+ }
+ });
+
+ test_stk_proactive_command({
+ pdu: [
+ 0xD0,
+ 0x30,
+ 0x81, 0x03, 0x01, 0x25, 0x00,
+ 0x82, 0x02, 0x81, 0x82,
+ 0x85, 0x05, 0x54, 0x69, 0x74, 0x6C, 0x65,
+ 0x8F, 0x07, 0x01, 0x69, 0x74, 0x65, 0x6D, 0x20, 0x31,
+ 0x8F, 0x07, 0x02, 0x69, 0x74, 0x65, 0x6D, 0x20, 0x32,
+ 0x8F, 0x07, 0x03, 0x69, 0x74, 0x65, 0x6D, 0x20, 0x33,
+ 0x18, 0x03, 0x81, 0x00, 0x00
+ ],
+ typeOfCommand: STK_CMD_SET_UP_MENU,
+ icons: null,
+ testFunc: (context, cmdDetails, ctlvs) => {
+ let menu = cmdDetails.options;
+
+ equal(menu.title, "Title");
+ equal(menu.items[0].identifier, 1);
+ equal(menu.items[0].text, "item 1");
+ equal(menu.items[1].identifier, 2);
+ equal(menu.items[1].text, "item 2");
+ equal(menu.items[2].identifier, 3);
+ equal(menu.items[2].text, "item 3");
+ equal(menu.nextActionList[0], STK_NEXT_ACTION_END_PROACTIVE_SESSION);
+ equal(menu.nextActionList[1], STK_NEXT_ACTION_NULL);
+ equal(menu.nextActionList[2], STK_NEXT_ACTION_NULL);
+ }
+ });
+
+ run_next_test();
+});
+
+/**
+ * Verify Proactive Command : Set Up Call
+ */
+add_test(function test_stk_proactive_command_set_up_call() {
+ test_stk_proactive_command({
+ pdu: [
+ 0xD0,
+ 0x31,
+ 0x81, 0x03, 0x01, 0x10, 0x04,
+ 0x82, 0x02, 0x81, 0x82,
+ 0x05, 0x0A, 0x44, 0x69, 0x73, 0x63, 0x6F, 0x6E, 0x6E, 0x65, 0x63, 0x74,
+ 0x86, 0x09, 0x81, 0x10, 0x32, 0x04, 0x21, 0x43, 0x65, 0x1C, 0x2C,
+ 0x05, 0x07, 0x4D, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65,
+ 0x9E, 0x02, 0x00, 0x01,
+ 0x9E, 0x02, 0x01, 0x02
+ ],
+ typeOfCommand: STK_CMD_SET_UP_CALL,
+ icons: [1, 2],
+ testFunc: (context, cmdDetails, ctlvs) => {
+ let setupCall = cmdDetails.options;
+
+ equal(setupCall.address, "012340123456,1,2");
+ equal(setupCall.confirmMessage.text, "Disconnect");
+ equal(setupCall.confirmMessage.iconSelfExplanatory, true);
+ equal(setupCall.confirmMessage.icons, 1);
+ equal(setupCall.callMessage.text, "Message");
+ equal(setupCall.callMessage.iconSelfExplanatory, false);
+ equal(setupCall.callMessage.icons, 2);
+ }
+ });
+
+ run_next_test();
+});
+
+/**
+ * Verify Proactive Command : Timer Management
+ */
+add_test(function test_stk_proactive_command_timer_management() {
+ // Timer Management - Start
+ test_stk_proactive_command({
+ pdu: [
+ 0xD0,
+ 0x11,
+ 0x81, 0x03, 0x01, 0x27, 0x00,
+ 0x82, 0x02, 0x81, 0x82,
+ 0xA4, 0x01, 0x01,
+ 0xA5, 0x03, 0x10, 0x20, 0x30
+ ],
+ typeOfCommand: STK_CMD_TIMER_MANAGEMENT,
+ icons: null,
+ testFunc: (context, cmdDetails, ctlvs) => {
+ equal(cmdDetails.commandQualifier, STK_TIMER_START);
+
+ let timer = cmdDetails.options;
+
+ equal(timer.timerId, 0x01);
+ equal(timer.timerValue, (0x01 * 60 * 60) + (0x02 * 60) + 0x03);
+ }
+ });
+
+ // Timer Management - Deactivate
+ test_stk_proactive_command({
+ pdu: [
+ 0xD0,
+ 0x0C,
+ 0x81, 0x03, 0x01, 0x27, 0x01,
+ 0x82, 0x02, 0x81, 0x82,
+ 0xA4, 0x01, 0x01
+ ],
+ typeOfCommand: STK_CMD_TIMER_MANAGEMENT,
+ icons: null,
+ testFunc: (context, cmdDetails, ctlvs) => {
+ equal(cmdDetails.commandQualifier, STK_TIMER_DEACTIVATE);
+
+ let timer = cmdDetails.options;
+
+ equal(timer.timerId, 0x01);
+ ok(timer.timerValue === undefined);
+ }
+ });
+
+ run_next_test();
+});
+
+/**
+ * Verify Proactive Command : Provide Local Information
+ */
+add_test(function test_stk_proactive_command_provide_local_information() {
+ let worker = newUint8Worker();
+ let context = worker.ContextPool._contexts[0];
+ let pduHelper = context.GsmPDUHelper;
+ let berHelper = context.BerTlvHelper;
+ let stkHelper = context.StkProactiveCmdHelper;
+ let stkCmdHelper = context.StkCommandParamsFactory;
+
+ // Verify IMEI
+ test_stk_proactive_command({
+ pdu: [
+ 0xD0,
+ 0x09,
+ 0x81, 0x03, 0x01, 0x26, 0x01,
+ 0x82, 0x02, 0x81, 0x82
+ ],
+ typeOfCommand: STK_CMD_PROVIDE_LOCAL_INFO,
+ icons: null,
+ testFunc: (context, cmdDetails, ctlvs) => {
+ equal(cmdDetails.commandQualifier, STK_LOCAL_INFO_IMEI);
+
+ let provideLocalInfo = cmdDetails.options;
+ equal(provideLocalInfo.localInfoType, STK_LOCAL_INFO_IMEI);
+ }
+ });
+
+ // Verify Date and Time Zone
+ test_stk_proactive_command({
+ pdu: [
+ 0xD0,
+ 0x09,
+ 0x81, 0x03, 0x01, 0x26, 0x03,
+ 0x82, 0x02, 0x81, 0x82
+ ],
+ typeOfCommand: STK_CMD_PROVIDE_LOCAL_INFO,
+ icons: null,
+ testFunc: (context, cmdDetails, ctlvs) => {
+ equal(cmdDetails.commandQualifier, STK_LOCAL_INFO_DATE_TIME_ZONE);
+
+ let provideLocalInfo = cmdDetails.options;
+ equal(provideLocalInfo.localInfoType, STK_LOCAL_INFO_DATE_TIME_ZONE);
+ }
+ });
+
+ run_next_test();
+});
+
+/**
+ * Verify Proactive command : BIP Messages
+ */
+add_test(function test_stk_proactive_command_open_channel() {
+ let worker = newUint8Worker();
+ let context = worker.ContextPool._contexts[0];
+ let pduHelper = context.GsmPDUHelper;
+ let berHelper = context.BerTlvHelper;
+ let stkHelper = context.StkProactiveCmdHelper;
+ let stkCmdHelper = context.StkCommandParamsFactory;
+
+ // Open Channel
+ test_stk_proactive_command({
+ pdu: [
+ 0xD0,
+ 0x0F,
+ 0x81, 0x03, 0x01, 0x40, 0x00,
+ 0x82, 0x02, 0x81, 0x82,
+ 0x85, 0x04, 0x4F, 0x70, 0x65, 0x6E //alpha id: "Open"
+ ],
+ typeOfCommand: STK_CMD_OPEN_CHANNEL,
+ icons: null,
+ testFunc: (context, cmdDetails, ctlvs) => {
+ let bipMsg = cmdDetails.options;
+
+ equal(bipMsg.text, "Open");
+ }
+ });
+
+ // Close Channel
+ test_stk_proactive_command({
+ pdu: [
+ 0xD0,
+ 0x10,
+ 0x81, 0x03, 0x01, 0x41, 0x00,
+ 0x82, 0x02, 0x81, 0x82,
+ 0x85, 0x05, 0x43, 0x6C, 0x6F, 0x73, 0x65 //alpha id: "Close"
+ ],
+ typeOfCommand: STK_CMD_CLOSE_CHANNEL,
+ icons: null,
+ testFunc: (context, cmdDetails, ctlvs) => {
+ let bipMsg = cmdDetails.options;
+
+ equal(bipMsg.text, "Close");
+ }
+ });
+
+ // Receive Data
+ test_stk_proactive_command({
+ pdu: [
+ 0XD0,
+ 0X12,
+ 0x81, 0x03, 0x01, 0x42, 0x00,
+ 0x82, 0x02, 0x81, 0x82,
+ 0x85, 0x07, 0x52, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65 //alpha id: "Receive"
+ ],
+ typeOfCommand: STK_CMD_RECEIVE_DATA,
+ icons: null,
+ testFunc: (context, cmdDetails, ctlvs) => {
+ let bipMsg = cmdDetails.options;
+
+ equal(bipMsg.text, "Receive");
+ }
+ });
+
+ // Send Data
+ test_stk_proactive_command({
+ pdu: [
+ 0xD0,
+ 0x0F,
+ 0x81, 0x03, 0x01, 0x43, 0x00,
+ 0x82, 0x02, 0x81, 0x82,
+ 0x85, 0x04, 0x53, 0x65, 0x6E, 0x64 //alpha id: "Send"
+ ],
+ typeOfCommand: STK_CMD_SEND_DATA,
+ icons: null,
+ testFunc: (context, cmdDetails, ctlvs) => {
+ let bipMsg = cmdDetails.options;
+
+ equal(bipMsg.text, "Send");
+ }
+ });
+
+ run_next_test();
+});
+
+/**
+ * Verify Event Download Command : Location Status
+ */
+add_test(function test_stk_event_download_location_status() {
+ let worker = newUint8SupportOutgoingIndexWorker();
+ let context = worker.ContextPool._contexts[0];
+ let buf = context.Buf;
+ let pduHelper = context.GsmPDUHelper;
+
+ buf.sendParcel = function() {
+ // Type
+ equal(this.readInt32(), REQUEST_STK_SEND_ENVELOPE_COMMAND);
+
+ // Token : we don't care
+ this.readInt32();
+
+ // Data Size, 42 = 2 * (2 + TLV_DEVICE_ID_SIZE(4) +
+ // TLV_EVENT_LIST_SIZE(3) +
+ // TLV_LOCATION_STATUS_SIZE(3) +
+ // TLV_LOCATION_INFO_GSM_SIZE(9))
+ equal(this.readInt32(), 42);
+
+ // BER tag
+ equal(pduHelper.readHexOctet(), BER_EVENT_DOWNLOAD_TAG);
+
+ // BER length, 19 = TLV_DEVICE_ID_SIZE(4) +
+ // TLV_EVENT_LIST_SIZE(3) +
+ // TLV_LOCATION_STATUS_SIZE(3) +
+ // TLV_LOCATION_INFO_GSM_SIZE(9)
+ equal(pduHelper.readHexOctet(), 19);
+
+ // Event List, Type-Length-Value
+ equal(pduHelper.readHexOctet(), COMPREHENSIONTLV_TAG_EVENT_LIST |
+ COMPREHENSIONTLV_FLAG_CR);
+ equal(pduHelper.readHexOctet(), 1);
+ equal(pduHelper.readHexOctet(), STK_EVENT_TYPE_LOCATION_STATUS);
+
+ // Device Identifies, Type-Length-Value(Source ID-Destination ID)
+ equal(pduHelper.readHexOctet(), COMPREHENSIONTLV_TAG_DEVICE_ID |
+ COMPREHENSIONTLV_FLAG_CR);
+ equal(pduHelper.readHexOctet(), 2);
+ equal(pduHelper.readHexOctet(), STK_DEVICE_ID_ME);
+ equal(pduHelper.readHexOctet(), STK_DEVICE_ID_SIM);
+
+ // Location Status, Type-Length-Value
+ equal(pduHelper.readHexOctet(), COMPREHENSIONTLV_TAG_LOCATION_STATUS |
+ COMPREHENSIONTLV_FLAG_CR);
+ equal(pduHelper.readHexOctet(), 1);
+ equal(pduHelper.readHexOctet(), STK_SERVICE_STATE_NORMAL);
+
+ // Location Info, Type-Length-Value
+ equal(pduHelper.readHexOctet(), COMPREHENSIONTLV_TAG_LOCATION_INFO |
+ COMPREHENSIONTLV_FLAG_CR);
+ equal(pduHelper.readHexOctet(), 7);
+
+ equal(pduHelper.readHexOctet(), 0x21); // MCC + MNC
+ equal(pduHelper.readHexOctet(), 0x63);
+ equal(pduHelper.readHexOctet(), 0x54);
+ equal(pduHelper.readHexOctet(), 0); // LAC
+ equal(pduHelper.readHexOctet(), 0);
+ equal(pduHelper.readHexOctet(), 0); // Cell ID
+ equal(pduHelper.readHexOctet(), 0);
+
+ run_next_test();
+ };
+
+ let event = {
+ eventType: STK_EVENT_TYPE_LOCATION_STATUS,
+ locationStatus: STK_SERVICE_STATE_NORMAL,
+ locationInfo: {
+ mcc: "123",
+ mnc: "456",
+ gsmLocationAreaCode: 0,
+ gsmCellId: 0
+ }
+ };
+ context.RIL.sendStkEventDownload({
+ event: event
+ });
+});
+
+// Test Event Download commands.
+
+/**
+ * Verify Event Download Command : Language Selection
+ */
+add_test(function test_stk_event_download_language_selection() {
+ let worker = newUint8SupportOutgoingIndexWorker();
+ let context = worker.ContextPool._contexts[0];
+ let buf = context.Buf;
+ let pduHelper = context.GsmPDUHelper;
+ let iccHelper = context.ICCPDUHelper;
+
+ buf.sendParcel = function() {
+ // Type
+ equal(this.readInt32(), REQUEST_STK_SEND_ENVELOPE_COMMAND);
+
+ // Token : we don't care
+ this.readInt32();
+
+ // Data Size, 26 = 2 * (2 + TLV_DEVICE_ID_SIZE(4) +
+ // TLV_EVENT_LIST_SIZE(3) +
+ // TLV_LANGUAGE(4))
+ equal(this.readInt32(), 26);
+
+ // BER tag
+ equal(pduHelper.readHexOctet(), BER_EVENT_DOWNLOAD_TAG);
+
+ // BER length, 19 = TLV_DEVICE_ID_SIZE(4) +
+ // TLV_EVENT_LIST_SIZE(3) +
+ // TLV_LANGUAGE(4)
+ equal(pduHelper.readHexOctet(), 11);
+
+ // Event List, Type-Length-Value
+ equal(pduHelper.readHexOctet(), COMPREHENSIONTLV_TAG_EVENT_LIST |
+ COMPREHENSIONTLV_FLAG_CR);
+ equal(pduHelper.readHexOctet(), 1);
+ equal(pduHelper.readHexOctet(), STK_EVENT_TYPE_LANGUAGE_SELECTION);
+
+ // Device Identifies, Type-Length-Value(Source ID-Destination ID)
+ equal(pduHelper.readHexOctet(), COMPREHENSIONTLV_TAG_DEVICE_ID |
+ COMPREHENSIONTLV_FLAG_CR);
+ equal(pduHelper.readHexOctet(), 2);
+ equal(pduHelper.readHexOctet(), STK_DEVICE_ID_ME);
+ equal(pduHelper.readHexOctet(), STK_DEVICE_ID_SIM);
+
+ // Language, Type-Length-Value
+ equal(pduHelper.readHexOctet(), COMPREHENSIONTLV_TAG_LANGUAGE);
+ equal(pduHelper.readHexOctet(), 2);
+ equal(iccHelper.read8BitUnpackedToString(2), "zh");
+
+ run_next_test();
+ };
+
+ let event = {
+ eventType: STK_EVENT_TYPE_LANGUAGE_SELECTION,
+ language: "zh"
+ };
+ context.RIL.sendStkEventDownload({
+ event: event
+ });
+});
+
+/**
+ * Verify Event Download Command : User Activity
+ */
+add_test(function test_stk_event_download_user_activity() {
+ let worker = newUint8SupportOutgoingIndexWorker();
+ let context = worker.ContextPool._contexts[0];
+ let buf = context.Buf;
+ let pduHelper = context.GsmPDUHelper;
+
+ buf.sendParcel = function() {
+ // Type
+ equal(this.readInt32(), REQUEST_STK_SEND_ENVELOPE_COMMAND);
+
+ // Token : we don't care
+ this.readInt32();
+
+ // Data Size, 18 = 2 * (2 + TLV_DEVICE_ID_SIZE(4) + TLV_EVENT_LIST_SIZE(3))
+ equal(this.readInt32(), 18);
+
+ // BER tag
+ equal(pduHelper.readHexOctet(), BER_EVENT_DOWNLOAD_TAG);
+
+ // BER length, 7 = TLV_DEVICE_ID_SIZE(4) + TLV_EVENT_LIST_SIZE(3)
+ equal(pduHelper.readHexOctet(), 7);
+
+ // Event List, Type-Length-Value
+ equal(pduHelper.readHexOctet(), COMPREHENSIONTLV_TAG_EVENT_LIST |
+ COMPREHENSIONTLV_FLAG_CR);
+ equal(pduHelper.readHexOctet(), 1);
+ equal(pduHelper.readHexOctet(), STK_EVENT_TYPE_USER_ACTIVITY);
+
+ // Device Identities, Type-Length-Value(Source ID-Destination ID)
+ equal(pduHelper.readHexOctet(), COMPREHENSIONTLV_TAG_DEVICE_ID |
+ COMPREHENSIONTLV_FLAG_CR);
+ equal(pduHelper.readHexOctet(), 2);
+ equal(pduHelper.readHexOctet(), STK_DEVICE_ID_ME);
+ equal(pduHelper.readHexOctet(), STK_DEVICE_ID_SIM);
+
+ run_next_test();
+ };
+
+ let event = {
+ eventType: STK_EVENT_TYPE_USER_ACTIVITY
+ };
+ context.RIL.sendStkEventDownload({
+ event: event
+ });
+});
+
+/**
+ * Verify Event Download Command : Idle Screen Available
+ */
+add_test(function test_stk_event_download_idle_screen_available() {
+ let worker = newUint8SupportOutgoingIndexWorker();
+ let context = worker.ContextPool._contexts[0];
+ let buf = context.Buf;
+ let pduHelper = context.GsmPDUHelper;
+
+ buf.sendParcel = function() {
+ // Type
+ equal(this.readInt32(), REQUEST_STK_SEND_ENVELOPE_COMMAND);
+
+ // Token : we don't care
+ this.readInt32();
+
+ // Data Size, 18 = 2 * (2 + TLV_DEVICE_ID_SIZE(4) + TLV_EVENT_LIST_SIZE(3))
+ equal(this.readInt32(), 18);
+
+ // BER tag
+ equal(pduHelper.readHexOctet(), BER_EVENT_DOWNLOAD_TAG);
+
+ // BER length, 7 = TLV_DEVICE_ID_SIZE(4) + TLV_EVENT_LIST_SIZE(3)
+ equal(pduHelper.readHexOctet(), 7);
+
+ // Event List, Type-Length-Value
+ equal(pduHelper.readHexOctet(), COMPREHENSIONTLV_TAG_EVENT_LIST |
+ COMPREHENSIONTLV_FLAG_CR);
+ equal(pduHelper.readHexOctet(), 1);
+ equal(pduHelper.readHexOctet(), STK_EVENT_TYPE_IDLE_SCREEN_AVAILABLE);
+
+ // Device Identities, Type-Length-Value(Source ID-Destination ID)
+ equal(pduHelper.readHexOctet(), COMPREHENSIONTLV_TAG_DEVICE_ID |
+ COMPREHENSIONTLV_FLAG_CR);
+ equal(pduHelper.readHexOctet(), 2);
+ equal(pduHelper.readHexOctet(), STK_DEVICE_ID_DISPLAY);
+ equal(pduHelper.readHexOctet(), STK_DEVICE_ID_SIM);
+
+ run_next_test();
+ };
+
+ let event = {
+ eventType: STK_EVENT_TYPE_IDLE_SCREEN_AVAILABLE
+ };
+ context.RIL.sendStkEventDownload({
+ event: event
+ });
+});
+
+/**
+ * Verify Event Downloaded Command :Browser Termination
+ */
+add_test(function test_stk_event_download_browser_termination() {
+ let worker = newUint8SupportOutgoingIndexWorker();
+ let context = worker.ContextPool._contexts[0];
+ let buf = context.Buf;
+ let pduHelper = context.GsmPDUHelper;
+
+ buf.sendParcel = function() {
+ // Type
+ equal(this.readInt32(), REQUEST_STK_SEND_ENVELOPE_COMMAND);
+
+ // Token : we don't care
+ this.readInt32();
+
+ // Data Size, 24 = 2 * ( 2+TLV_DEVICE_ID(4)+TLV_EVENT_LIST_SIZE(3)
+ // +TLV_BROWSER_TERMINATION_CAUSE(3) )
+ equal(this.readInt32(), 24);
+
+ // BER tag
+ equal(pduHelper.readHexOctet(), BER_EVENT_DOWNLOAD_TAG);
+
+ // BER length, 10 = TLV_DEVICE_ID(4)+TLV_EVENT_LIST_SIZE(3)
+ // ++TLV_BROWSER_TERMINATION_CAUSE(3)
+ equal(pduHelper.readHexOctet(), 10);
+
+ // Event List, Type-Length-Value
+ equal(pduHelper.readHexOctet(), COMPREHENSIONTLV_TAG_EVENT_LIST |
+ COMPREHENSIONTLV_FLAG_CR);
+ equal(pduHelper.readHexOctet(), 1);
+ equal(pduHelper.readHexOctet(), STK_EVENT_TYPE_BROWSER_TERMINATION);
+
+ // Device Identities, Type-Length-Value(Source ID-Destination ID)
+ equal(pduHelper.readHexOctet(), COMPREHENSIONTLV_TAG_DEVICE_ID |
+ COMPREHENSIONTLV_FLAG_CR);
+ equal(pduHelper.readHexOctet(), 2);
+ equal(pduHelper.readHexOctet(), STK_DEVICE_ID_ME);
+ equal(pduHelper.readHexOctet(), STK_DEVICE_ID_SIM);
+
+ // Browser Termination Case, Type-Length-Value
+ equal(pduHelper.readHexOctet(), COMPREHENSIONTLV_TAG_BROWSER_TERMINATION_CAUSE |
+ COMPREHENSIONTLV_FLAG_CR);
+ equal(pduHelper.readHexOctet(), 1);
+ equal(pduHelper.readHexOctet(), STK_BROWSER_TERMINATION_CAUSE_USER);
+
+ run_next_test();
+ };
+
+ let event = {
+ eventType: STK_EVENT_TYPE_BROWSER_TERMINATION,
+ terminationCause: STK_BROWSER_TERMINATION_CAUSE_USER
+ };
+ context.RIL.sendStkEventDownload({
+ event: event
+ });
+});
diff --git a/dom/system/gonk/tests/test_ril_worker_voiceprivacy.js b/dom/system/gonk/tests/test_ril_worker_voiceprivacy.js
new file mode 100644
index 000000000..21829da22
--- /dev/null
+++ b/dom/system/gonk/tests/test_ril_worker_voiceprivacy.js
@@ -0,0 +1,94 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+subscriptLoader.loadSubScript("resource://gre/modules/ril_consts.js", this);
+
+function run_test() {
+ run_next_test();
+}
+
+add_test(function test_setVoicePrivacyMode_success() {
+ let workerHelper = newInterceptWorker();
+ let worker = workerHelper.worker;
+ let context = worker.ContextPool._contexts[0];
+
+ context.RIL.setVoicePrivacyMode = function fakeSetVoicePrivacyMode(options) {
+ context.RIL[REQUEST_CDMA_SET_PREFERRED_VOICE_PRIVACY_MODE](0, {});
+ };
+
+ context.RIL.setVoicePrivacyMode({
+ enabled: true
+ });
+
+ let postedMessage = workerHelper.postedMessage;
+
+ equal(postedMessage.errorMsg, undefined);
+
+ run_next_test();
+});
+
+add_test(function test_setVoicePrivacyMode_generic_failure() {
+ let workerHelper = newInterceptWorker();
+ let worker = workerHelper.worker;
+ let context = worker.ContextPool._contexts[0];
+
+ context.RIL.setVoicePrivacyMode = function fakeSetVoicePrivacyMode(options) {
+ context.RIL[REQUEST_CDMA_SET_PREFERRED_VOICE_PRIVACY_MODE](0, {
+ errorMsg: GECKO_ERROR_GENERIC_FAILURE
+ });
+ };
+
+ context.RIL.setVoicePrivacyMode({
+ enabled: true
+ });
+
+ let postedMessage = workerHelper.postedMessage;
+
+ equal(postedMessage.errorMsg, GECKO_ERROR_GENERIC_FAILURE);
+
+ run_next_test();
+});
+
+add_test(function test_queryVoicePrivacyMode_success_enabled_true() {
+ let workerHelper = newInterceptWorker();
+ let worker = workerHelper.worker;
+ let context = worker.ContextPool._contexts[0];
+
+ context.Buf.readInt32List = function fakeReadUint32List() {
+ return [1];
+ };
+
+ context.RIL.queryVoicePrivacyMode = function fakeQueryVoicePrivacyMode(options) {
+ context.RIL[REQUEST_CDMA_QUERY_PREFERRED_VOICE_PRIVACY_MODE](1, {});
+ };
+
+ context.RIL.queryVoicePrivacyMode();
+
+ let postedMessage = workerHelper.postedMessage;
+
+ equal(postedMessage.errorMsg, undefined);
+ ok(postedMessage.enabled);
+ run_next_test();
+});
+
+add_test(function test_queryVoicePrivacyMode_success_enabled_false() {
+ let workerHelper = newInterceptWorker();
+ let worker = workerHelper.worker;
+ let context = worker.ContextPool._contexts[0];
+
+ context.Buf.readInt32List = function fakeReadUint32List() {
+ return [0];
+ };
+
+ context.RIL.queryVoicePrivacyMode = function fakeQueryVoicePrivacyMode(options) {
+ context.RIL[REQUEST_CDMA_QUERY_PREFERRED_VOICE_PRIVACY_MODE](1, {});
+ };
+
+ context.RIL.queryVoicePrivacyMode();
+
+ let postedMessage = workerHelper.postedMessage;
+
+ equal(postedMessage.errorMsg, undefined);
+ ok(!postedMessage.enabled);
+ run_next_test();
+});
diff --git a/dom/system/gonk/tests/xpcshell.ini b/dom/system/gonk/tests/xpcshell.ini
new file mode 100644
index 000000000..1e8b798a3
--- /dev/null
+++ b/dom/system/gonk/tests/xpcshell.ini
@@ -0,0 +1,43 @@
+[DEFAULT]
+head = header_helpers.js
+tail =
+
+[test_ril_worker_buf.js]
+[test_ril_worker_icc_CardLock.js]
+[test_ril_worker_icc_CardState.js]
+[test_ril_worker_icc_BerTlvHelper.js]
+[test_ril_worker_icc_GsmPDUHelper.js]
+[test_ril_worker_icc_ICCContactHelper.js]
+[test_ril_worker_icc_ICCIOHelper.js]
+[test_ril_worker_icc_ICCPDUHelper.js]
+[test_ril_worker_icc_ICCRecordHelper.js]
+[test_ril_worker_icc_IconLoader.js]
+[test_ril_worker_icc_ICCUtilsHelper.js]
+[test_ril_worker_icc_SimRecordHelper.js]
+[test_ril_worker_sms.js]
+# Bug 916067 - B2G RIL: test_ril_worker_sms.js takes too long to finish
+skip-if = true
+[test_ril_worker_sms_cdma.js]
+[test_ril_worker_sms_cdmapduhelper.js]
+[test_ril_worker_sms_nl_tables.js]
+[test_ril_worker_sms_gsmpduhelper.js]
+[test_ril_worker_sms_segment_info.js]
+[test_ril_worker_smsc_address.js]
+[test_ril_worker_cf.js]
+[test_ril_worker_cellbroadcast_config.js]
+[test_ril_worker_cellbroadcast_gsm.js]
+[test_ril_worker_cellbroadcast_umts.js]
+[test_ril_worker_ruim.js]
+[test_ril_worker_cw.js]
+[test_ril_worker_clir.js]
+[test_ril_worker_clip.js]
+[test_ril_worker_ssn.js]
+[test_ril_worker_voiceprivacy.js]
+[test_ril_worker_ecm.js]
+[test_ril_worker_stk.js]
+requesttimeoutfactor = 4
+[test_ril_worker_barring_password.js]
+[test_ril_worker_cdma_info_rec.js]
+[test_ril_system_messenger.js]
+# header_helpers.js is not needed for test_ril_system_messenger.js
+head =
diff --git a/dom/system/gonk/worker_buf.js b/dom/system/gonk/worker_buf.js
new file mode 100644
index 000000000..7064eeac5
--- /dev/null
+++ b/dom/system/gonk/worker_buf.js
@@ -0,0 +1,623 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+
+/**
+ * This object contains helpers buffering incoming data & deconstructing it
+ * into parcels as well as buffering outgoing data & constructing parcels.
+ * For that it maintains two buffers and corresponding uint8 views, indexes.
+ *
+ * The incoming buffer is a circular buffer where we store incoming data.
+ * As soon as a complete parcel is received, it is processed right away, so
+ * the buffer only needs to be large enough to hold one parcel.
+ *
+ * The outgoing buffer is to prepare outgoing parcels. The index is reset
+ * every time a parcel is sent.
+ */
+
+var Buf = {
+ INT32_MAX: 2147483647,
+ UINT8_SIZE: 1,
+ UINT16_SIZE: 2,
+ UINT32_SIZE: 4,
+ PARCEL_SIZE_SIZE: 4,
+ PDU_HEX_OCTET_SIZE: 4,
+
+ incomingBufferLength: 1024,
+ incomingBuffer: null,
+ incomingBytes: null,
+ incomingWriteIndex: 0,
+ incomingReadIndex: 0,
+ readIncoming: 0,
+ readAvailable: 0,
+ currentParcelSize: 0,
+
+ outgoingBufferLength: 1024,
+ outgoingBuffer: null,
+ outgoingBytes: null,
+ outgoingIndex: 0,
+ outgoingBufferCalSizeQueue: null,
+
+ _init: function() {
+ this.incomingBuffer = new ArrayBuffer(this.incomingBufferLength);
+ this.outgoingBuffer = new ArrayBuffer(this.outgoingBufferLength);
+
+ this.incomingBytes = new Uint8Array(this.incomingBuffer);
+ this.outgoingBytes = new Uint8Array(this.outgoingBuffer);
+
+ // Track where incoming data is read from and written to.
+ this.incomingWriteIndex = 0;
+ this.incomingReadIndex = 0;
+
+ // Leave room for the parcel size for outgoing parcels.
+ this.outgoingIndex = this.PARCEL_SIZE_SIZE;
+
+ // How many bytes we've read for this parcel so far.
+ this.readIncoming = 0;
+
+ // How many bytes available as parcel data.
+ this.readAvailable = 0;
+
+ // Size of the incoming parcel. If this is zero, we're expecting a new
+ // parcel.
+ this.currentParcelSize = 0;
+
+ // Queue for storing outgoing override points
+ this.outgoingBufferCalSizeQueue = [];
+ },
+
+ /**
+ * Mark current outgoingIndex as start point for calculation length of data
+ * written to outgoingBuffer.
+ * Mark can be nested for here uses queue to remember marks.
+ *
+ * @param writeFunction
+ * Function to write data length into outgoingBuffer, this function is
+ * also used to allocate buffer for data length.
+ * Raw data size(in Uint8) is provided as parameter calling writeFunction.
+ * If raw data size is not in proper unit for writing, user can adjust
+ * the length value in writeFunction before writing.
+ **/
+ startCalOutgoingSize: function(writeFunction) {
+ let sizeInfo = {index: this.outgoingIndex,
+ write: writeFunction};
+
+ // Allocate buffer for data lemgtj.
+ writeFunction.call(0);
+
+ // Get size of data length buffer for it is not counted into data size.
+ sizeInfo.size = this.outgoingIndex - sizeInfo.index;
+
+ // Enqueue size calculation information.
+ this.outgoingBufferCalSizeQueue.push(sizeInfo);
+ },
+
+ /**
+ * Calculate data length since last mark, and write it into mark position.
+ **/
+ stopCalOutgoingSize: function() {
+ let sizeInfo = this.outgoingBufferCalSizeQueue.pop();
+
+ // Remember current outgoingIndex.
+ let currentOutgoingIndex = this.outgoingIndex;
+ // Calculate data length, in uint8.
+ let writeSize = this.outgoingIndex - sizeInfo.index - sizeInfo.size;
+
+ // Write data length to mark, use same function for allocating buffer to make
+ // sure there is no buffer overloading.
+ this.outgoingIndex = sizeInfo.index;
+ sizeInfo.write(writeSize);
+
+ // Restore outgoingIndex.
+ this.outgoingIndex = currentOutgoingIndex;
+ },
+
+ /**
+ * Grow the incoming buffer.
+ *
+ * @param min_size
+ * Minimum new size. The actual new size will be the the smallest
+ * power of 2 that's larger than this number.
+ */
+ growIncomingBuffer: function(min_size) {
+ if (DEBUG) {
+ debug("Current buffer of " + this.incomingBufferLength +
+ " can't handle incoming " + min_size + " bytes.");
+ }
+ let oldBytes = this.incomingBytes;
+ this.incomingBufferLength =
+ 2 << Math.floor(Math.log(min_size)/Math.log(2));
+ if (DEBUG) debug("New incoming buffer size: " + this.incomingBufferLength);
+ this.incomingBuffer = new ArrayBuffer(this.incomingBufferLength);
+ this.incomingBytes = new Uint8Array(this.incomingBuffer);
+ if (this.incomingReadIndex <= this.incomingWriteIndex) {
+ // Read and write index are in natural order, so we can just copy
+ // the old buffer over to the bigger one without having to worry
+ // about the indexes.
+ this.incomingBytes.set(oldBytes, 0);
+ } else {
+ // The write index has wrapped around but the read index hasn't yet.
+ // Write whatever the read index has left to read until it would
+ // circle around to the beginning of the new buffer, and the rest
+ // behind that.
+ let head = oldBytes.subarray(this.incomingReadIndex);
+ let tail = oldBytes.subarray(0, this.incomingReadIndex);
+ this.incomingBytes.set(head, 0);
+ this.incomingBytes.set(tail, head.length);
+ this.incomingReadIndex = 0;
+ this.incomingWriteIndex += head.length;
+ }
+ if (DEBUG) {
+ debug("New incoming buffer size is " + this.incomingBufferLength);
+ }
+ },
+
+ /**
+ * Grow the outgoing buffer.
+ *
+ * @param min_size
+ * Minimum new size. The actual new size will be the the smallest
+ * power of 2 that's larger than this number.
+ */
+ growOutgoingBuffer: function(min_size) {
+ if (DEBUG) {
+ debug("Current buffer of " + this.outgoingBufferLength +
+ " is too small.");
+ }
+ let oldBytes = this.outgoingBytes;
+ this.outgoingBufferLength =
+ 2 << Math.floor(Math.log(min_size)/Math.log(2));
+ this.outgoingBuffer = new ArrayBuffer(this.outgoingBufferLength);
+ this.outgoingBytes = new Uint8Array(this.outgoingBuffer);
+ this.outgoingBytes.set(oldBytes, 0);
+ if (DEBUG) {
+ debug("New outgoing buffer size is " + this.outgoingBufferLength);
+ }
+ },
+
+ /**
+ * Functions for reading data from the incoming buffer.
+ *
+ * These are all little endian, apart from readParcelSize();
+ */
+
+ /**
+ * Ensure position specified is readable.
+ *
+ * @param index
+ * Data position in incoming parcel, valid from 0 to
+ * currentParcelSize.
+ */
+ ensureIncomingAvailable: function(index) {
+ if (index >= this.currentParcelSize) {
+ throw new Error("Trying to read data beyond the parcel end!");
+ } else if (index < 0) {
+ throw new Error("Trying to read data before the parcel begin!");
+ }
+ },
+
+ /**
+ * Seek in current incoming parcel.
+ *
+ * @param offset
+ * Seek offset in relative to current position.
+ */
+ seekIncoming: function(offset) {
+ // Translate to 0..currentParcelSize
+ let cur = this.currentParcelSize - this.readAvailable;
+
+ let newIndex = cur + offset;
+ this.ensureIncomingAvailable(newIndex);
+
+ // ... incomingReadIndex -->|
+ // 0 new cur currentParcelSize
+ // |================|=======|====================|
+ // |<-- cur -->|<- readAvailable ->|
+ // |<-- newIndex -->|<-- new readAvailable -->|
+ this.readAvailable = this.currentParcelSize - newIndex;
+
+ // Translate back:
+ if (this.incomingReadIndex < cur) {
+ // The incomingReadIndex is wrapped.
+ newIndex += this.incomingBufferLength;
+ }
+ newIndex += (this.incomingReadIndex - cur);
+ newIndex %= this.incomingBufferLength;
+ this.incomingReadIndex = newIndex;
+ },
+
+ readUint8Unchecked: function() {
+ let value = this.incomingBytes[this.incomingReadIndex];
+ this.incomingReadIndex = (this.incomingReadIndex + 1) %
+ this.incomingBufferLength;
+ return value;
+ },
+
+ readUint8: function() {
+ // Translate to 0..currentParcelSize
+ let cur = this.currentParcelSize - this.readAvailable;
+ this.ensureIncomingAvailable(cur);
+
+ this.readAvailable--;
+ return this.readUint8Unchecked();
+ },
+
+ readUint8Array: function(length) {
+ // Translate to 0..currentParcelSize
+ let last = this.currentParcelSize - this.readAvailable;
+ last += (length - 1);
+ this.ensureIncomingAvailable(last);
+
+ let array = new Uint8Array(length);
+ for (let i = 0; i < length; i++) {
+ array[i] = this.readUint8Unchecked();
+ }
+
+ this.readAvailable -= length;
+ return array;
+ },
+
+ readUint16: function() {
+ return this.readUint8() | this.readUint8() << 8;
+ },
+
+ readInt32: function() {
+ return this.readUint8() | this.readUint8() << 8 |
+ this.readUint8() << 16 | this.readUint8() << 24;
+ },
+
+ readInt64: function() {
+ // Avoid using bitwise operators as the operands of all bitwise operators
+ // are converted to signed 32-bit integers.
+ return this.readUint8() +
+ this.readUint8() * Math.pow(2, 8) +
+ this.readUint8() * Math.pow(2, 16) +
+ this.readUint8() * Math.pow(2, 24) +
+ this.readUint8() * Math.pow(2, 32) +
+ this.readUint8() * Math.pow(2, 40) +
+ this.readUint8() * Math.pow(2, 48) +
+ this.readUint8() * Math.pow(2, 56);
+ },
+
+ readInt32List: function() {
+ let length = this.readInt32();
+ let ints = [];
+ for (let i = 0; i < length; i++) {
+ ints.push(this.readInt32());
+ }
+ return ints;
+ },
+
+ readString: function() {
+ let string_len = this.readInt32();
+ if (string_len < 0 || string_len >= this.INT32_MAX) {
+ return null;
+ }
+ let s = "";
+ for (let i = 0; i < string_len; i++) {
+ s += String.fromCharCode(this.readUint16());
+ }
+ // Strings are \0\0 delimited, but that isn't part of the length. And
+ // if the string length is even, the delimiter is two characters wide.
+ // It's insane, I know.
+ this.readStringDelimiter(string_len);
+ return s;
+ },
+
+ readStringList: function() {
+ let num_strings = this.readInt32();
+ let strings = [];
+ for (let i = 0; i < num_strings; i++) {
+ strings.push(this.readString());
+ }
+ return strings;
+ },
+
+ readStringDelimiter: function(length) {
+ let delimiter = this.readUint16();
+ if (!(length & 1)) {
+ delimiter |= this.readUint16();
+ }
+ if (DEBUG) {
+ if (delimiter !== 0) {
+ debug("Something's wrong, found string delimiter: " + delimiter);
+ }
+ }
+ },
+
+ readParcelSize: function() {
+ return this.readUint8Unchecked() << 24 |
+ this.readUint8Unchecked() << 16 |
+ this.readUint8Unchecked() << 8 |
+ this.readUint8Unchecked();
+ },
+
+ /**
+ * Functions for writing data to the outgoing buffer.
+ */
+
+ /**
+ * Ensure position specified is writable.
+ *
+ * @param index
+ * Data position in outgoing parcel, valid from 0 to
+ * outgoingBufferLength.
+ */
+ ensureOutgoingAvailable: function(index) {
+ if (index >= this.outgoingBufferLength) {
+ this.growOutgoingBuffer(index + 1);
+ }
+ },
+
+ writeUint8: function(value) {
+ this.ensureOutgoingAvailable(this.outgoingIndex);
+
+ this.outgoingBytes[this.outgoingIndex] = value;
+ this.outgoingIndex++;
+ },
+
+ writeUint16: function(value) {
+ this.writeUint8(value & 0xff);
+ this.writeUint8((value >> 8) & 0xff);
+ },
+
+ writeInt32: function(value) {
+ this.writeUint8(value & 0xff);
+ this.writeUint8((value >> 8) & 0xff);
+ this.writeUint8((value >> 16) & 0xff);
+ this.writeUint8((value >> 24) & 0xff);
+ },
+
+ writeString: function(value) {
+ if (value == null) {
+ this.writeInt32(-1);
+ return;
+ }
+ this.writeInt32(value.length);
+ for (let i = 0; i < value.length; i++) {
+ this.writeUint16(value.charCodeAt(i));
+ }
+ // Strings are \0\0 delimited, but that isn't part of the length. And
+ // if the string length is even, the delimiter is two characters wide.
+ // It's insane, I know.
+ this.writeStringDelimiter(value.length);
+ },
+
+ writeStringList: function(strings) {
+ this.writeInt32(strings.length);
+ for (let i = 0; i < strings.length; i++) {
+ this.writeString(strings[i]);
+ }
+ },
+
+ writeStringDelimiter: function(length) {
+ this.writeUint16(0);
+ if (!(length & 1)) {
+ this.writeUint16(0);
+ }
+ },
+
+ writeParcelSize: function(value) {
+ /**
+ * Parcel size will always be the first thing in the parcel byte
+ * array, but the last thing written. Store the current index off
+ * to a temporary to be reset after we write the size.
+ */
+ let currentIndex = this.outgoingIndex;
+ this.outgoingIndex = 0;
+ this.writeUint8((value >> 24) & 0xff);
+ this.writeUint8((value >> 16) & 0xff);
+ this.writeUint8((value >> 8) & 0xff);
+ this.writeUint8(value & 0xff);
+ this.outgoingIndex = currentIndex;
+ },
+
+ copyIncomingToOutgoing: function(length) {
+ if (!length || (length < 0)) {
+ return;
+ }
+
+ let translatedReadIndexEnd =
+ this.currentParcelSize - this.readAvailable + length - 1;
+ this.ensureIncomingAvailable(translatedReadIndexEnd);
+
+ let translatedWriteIndexEnd = this.outgoingIndex + length - 1;
+ this.ensureOutgoingAvailable(translatedWriteIndexEnd);
+
+ let newIncomingReadIndex = this.incomingReadIndex + length;
+ if (newIncomingReadIndex < this.incomingBufferLength) {
+ // Reading won't cause wrapping, go ahead with builtin copy.
+ this.outgoingBytes
+ .set(this.incomingBytes.subarray(this.incomingReadIndex,
+ newIncomingReadIndex),
+ this.outgoingIndex);
+ } else {
+ // Not so lucky.
+ newIncomingReadIndex %= this.incomingBufferLength;
+ this.outgoingBytes
+ .set(this.incomingBytes.subarray(this.incomingReadIndex,
+ this.incomingBufferLength),
+ this.outgoingIndex);
+ if (newIncomingReadIndex) {
+ let firstPartLength = this.incomingBufferLength - this.incomingReadIndex;
+ this.outgoingBytes.set(this.incomingBytes.subarray(0, newIncomingReadIndex),
+ this.outgoingIndex + firstPartLength);
+ }
+ }
+
+ this.incomingReadIndex = newIncomingReadIndex;
+ this.readAvailable -= length;
+ this.outgoingIndex += length;
+ },
+
+ /**
+ * Parcel management
+ */
+
+ /**
+ * Write incoming data to the circular buffer.
+ *
+ * @param incoming
+ * Uint8Array containing the incoming data.
+ */
+ writeToIncoming: function(incoming) {
+ // We don't have to worry about the head catching the tail since
+ // we process any backlog in parcels immediately, before writing
+ // new data to the buffer. So the only edge case we need to handle
+ // is when the incoming data is larger than the buffer size.
+ let minMustAvailableSize = incoming.length + this.readIncoming;
+ if (minMustAvailableSize > this.incomingBufferLength) {
+ this.growIncomingBuffer(minMustAvailableSize);
+ }
+
+ // We can let the typed arrays do the copying if the incoming data won't
+ // wrap around the edges of the circular buffer.
+ let remaining = this.incomingBufferLength - this.incomingWriteIndex;
+ if (remaining >= incoming.length) {
+ this.incomingBytes.set(incoming, this.incomingWriteIndex);
+ } else {
+ // The incoming data would wrap around it.
+ let head = incoming.subarray(0, remaining);
+ let tail = incoming.subarray(remaining);
+ this.incomingBytes.set(head, this.incomingWriteIndex);
+ this.incomingBytes.set(tail, 0);
+ }
+ this.incomingWriteIndex = (this.incomingWriteIndex + incoming.length) %
+ this.incomingBufferLength;
+ },
+
+ /**
+ * Process incoming data.
+ *
+ * @param incoming
+ * Uint8Array containing the incoming data.
+ */
+ processIncoming: function(incoming) {
+ if (DEBUG) {
+ debug("Received " + incoming.length + " bytes.");
+ debug("Already read " + this.readIncoming);
+ }
+
+ this.writeToIncoming(incoming);
+ this.readIncoming += incoming.length;
+ while (true) {
+ if (!this.currentParcelSize) {
+ // We're expecting a new parcel.
+ if (this.readIncoming < this.PARCEL_SIZE_SIZE) {
+ // We don't know how big the next parcel is going to be, need more
+ // data.
+ if (DEBUG) debug("Next parcel size unknown, going to sleep.");
+ return;
+ }
+ this.currentParcelSize = this.readParcelSize();
+ if (DEBUG) {
+ debug("New incoming parcel of size " + this.currentParcelSize);
+ }
+ // The size itself is not included in the size.
+ this.readIncoming -= this.PARCEL_SIZE_SIZE;
+ }
+
+ if (this.readIncoming < this.currentParcelSize) {
+ // We haven't read enough yet in order to be able to process a parcel.
+ if (DEBUG) debug("Read " + this.readIncoming + ", but parcel size is "
+ + this.currentParcelSize + ". Going to sleep.");
+ return;
+ }
+
+ // Alright, we have enough data to process at least one whole parcel.
+ // Let's do that.
+ let expectedAfterIndex = (this.incomingReadIndex + this.currentParcelSize)
+ % this.incomingBufferLength;
+
+ if (DEBUG) {
+ let parcel;
+ if (expectedAfterIndex < this.incomingReadIndex) {
+ let head = this.incomingBytes.subarray(this.incomingReadIndex);
+ let tail = this.incomingBytes.subarray(0, expectedAfterIndex);
+ parcel = Array.slice(head).concat(Array.slice(tail));
+ } else {
+ parcel = Array.slice(this.incomingBytes.subarray(
+ this.incomingReadIndex, expectedAfterIndex));
+ }
+ debug("Parcel (size " + this.currentParcelSize + "): " + parcel);
+ }
+
+ if (DEBUG) debug("We have at least one complete parcel.");
+ try {
+ this.readAvailable = this.currentParcelSize;
+ this.processParcel();
+ } catch (ex) {
+ if (DEBUG) debug("Parcel handling threw " + ex + "\n" + ex.stack);
+ }
+
+ // Ensure that the whole parcel was consumed.
+ if (this.incomingReadIndex != expectedAfterIndex) {
+ if (DEBUG) {
+ debug("Parcel handler didn't consume whole parcel, " +
+ Math.abs(expectedAfterIndex - this.incomingReadIndex) +
+ " bytes left over");
+ }
+ this.incomingReadIndex = expectedAfterIndex;
+ }
+ this.readIncoming -= this.currentParcelSize;
+ this.readAvailable = 0;
+ this.currentParcelSize = 0;
+ }
+ },
+
+ /**
+ * Communicate with the IPC thread.
+ */
+ sendParcel: function() {
+ // Compute the size of the parcel and write it to the front of the parcel
+ // where we left room for it. Note that he parcel size does not include
+ // the size itself.
+ let parcelSize = this.outgoingIndex - this.PARCEL_SIZE_SIZE;
+ this.writeParcelSize(parcelSize);
+
+ // This assumes that postRILMessage will make a copy of the ArrayBufferView
+ // right away!
+ let parcel = this.outgoingBytes.subarray(0, this.outgoingIndex);
+ if (DEBUG) debug("Outgoing parcel: " + Array.slice(parcel));
+ this.onSendParcel(parcel);
+ this.outgoingIndex = this.PARCEL_SIZE_SIZE;
+ },
+
+ getCurrentParcelSize: function() {
+ return this.currentParcelSize;
+ },
+
+ getReadAvailable: function() {
+ return this.readAvailable;
+ }
+
+ /**
+ * Process one parcel.
+ *
+ * |processParcel| is an implementation provided incoming parcel processing
+ * function invoked when we have received a complete parcel. Implementation
+ * may call multiple read functions to extract data from the incoming buffer.
+ */
+ //processParcel: function() {
+ // let something = this.readInt32();
+ // ...
+ //},
+
+ /**
+ * Write raw data out to underlying channel.
+ *
+ * |onSendParcel| is an implementation provided stream output function
+ * invoked when we're really going to write something out. We assume the
+ * data are completely copied to some output buffer in this call and may
+ * be destroyed when it's done.
+ *
+ * @param parcel
+ * An array of numeric octet data.
+ */
+ //onSendParcel: function(parcel) {
+ // ...
+ //}
+};
+
+module.exports = { Buf: Buf };