Web   ·   Wiki   ·   Activities   ·   Blog   ·   Lists   ·   Chat   ·   Meeting   ·   Bugs   ·   Git   ·   Translate   ·   Archive   ·   People   ·   Donate
summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDaniel Narvaez <dwnarvaez@gmail.com>2013-02-06 14:30:36 (GMT)
committer Daniel Narvaez <dwnarvaez@gmail.com>2013-02-06 14:30:36 (GMT)
commitf5bac2f1e1a51b83d215a07f9d5d87db337873f3 (patch)
tree15c5f594b1e00c6272552cc5544a1bc757713e34
parentc301ead6bb9c201801400964427b517dafb6a03c (diff)
Get the build to work
-rw-r--r--.gitignore1
-rw-r--r--Makefile147
-rw-r--r--apps/system/camera/index.html66
-rw-r--r--apps/system/camera/js/camera.js982
-rw-r--r--apps/system/camera/js/filmstrip.js568
-rw-r--r--apps/system/camera/locales/camera.ar.properties19
-rw-r--r--apps/system/camera/locales/camera.en-US.properties19
-rw-r--r--apps/system/camera/locales/camera.fr.properties19
-rw-r--r--apps/system/camera/locales/camera.zh-TW.properties20
-rw-r--r--apps/system/camera/locales/locales.ini11
-rw-r--r--apps/system/camera/resources/sounds/shutter.oggbin15807 -> 0 bytes
-rw-r--r--apps/system/camera/style/VideoPlayer.css152
-rw-r--r--apps/system/camera/style/camera.css314
-rw-r--r--apps/system/camera/style/filmstrip.css187
-rw-r--r--apps/system/camera/style/icons/60/Camera.pngbin5909 -> 0 bytes
-rw-r--r--apps/system/camera/style/icons/Camera.pngbin5909 -> 0 bytes
-rw-r--r--apps/system/camera/style/images/camera.pngbin1654 -> 0 bytes
-rw-r--r--apps/system/camera/style/images/delete.pngbin472 -> 0 bytes
-rw-r--r--apps/system/camera/style/images/flash_auto.pngbin1880 -> 0 bytes
-rw-r--r--apps/system/camera/style/images/flash_off.pngbin1613 -> 0 bytes
-rw-r--r--apps/system/camera/style/images/flash_on.pngbin1673 -> 0 bytes
-rw-r--r--apps/system/camera/style/images/flash_torch.pngbin1880 -> 0 bytes
-rw-r--r--apps/system/camera/style/images/hud_button_underlay.pngbin548 -> 0 bytes
-rw-r--r--apps/system/camera/style/images/hud_button_underlay_focus.pngbin954 -> 0 bytes
-rw-r--r--apps/system/camera/style/images/play_overlay.pngbin2498 -> 0 bytes
-rw-r--r--apps/system/camera/style/images/share.pngbin855 -> 0 bytes
-rw-r--r--apps/system/camera/style/images/stop.pngbin3233 -> 0 bytes
-rw-r--r--apps/system/camera/style/images/toggle_back.pngbin2106 -> 0 bytes
-rw-r--r--apps/system/camera/style/images/toggle_front.pngbin2249 -> 0 bytes
-rw-r--r--apps/system/camera/style/images/video_pause_button.pngbin1722 -> 0 bytes
-rw-r--r--apps/system/camera/style/images/video_play_button.pngbin3862 -> 0 bytes
-rw-r--r--apps/system/camera/style/images/video_play_focus.pngbin1373 -> 0 bytes
-rw-r--r--apps/system/camera/style/images/video_play_normal.pngbin1361 -> 0 bytes
-rw-r--r--apps/system/camera/test/unit/_proxy.html49
-rw-r--r--apps/system/camera/test/unit/_sandbox.html28
-rw-r--r--build/applications-data.js158
-rw-r--r--build/utils.js12
-rw-r--r--build/webapp-manifests.js84
-rw-r--r--shared/js/async_storage.js187
-rw-r--r--shared/js/blobview.js412
-rw-r--r--shared/js/custom_dialog.js112
-rw-r--r--shared/js/desktop.js115
-rw-r--r--shared/js/gesture_detector.js891
-rw-r--r--shared/js/idletimer.js127
-rw-r--r--shared/js/l10n.js1014
-rw-r--r--shared/js/l10n_date.js141
-rw-r--r--shared/js/manifest_helper.js26
-rw-r--r--shared/js/media/README1
-rw-r--r--shared/js/media/get_video_rotation.js143
-rw-r--r--shared/js/media/jpeg_metadata_parser.js314
-rw-r--r--shared/js/media/media_frame.js537
-rw-r--r--shared/js/media/video_player.js313
-rw-r--r--shared/js/mediadb.js1532
-rw-r--r--shared/js/mobile_operator.js96
-rw-r--r--shared/js/mouse_event_shim.js282
-rw-r--r--shared/js/notification_helper.js68
-rw-r--r--shared/js/phoneNumberJS/PhoneNumber.js335
-rw-r--r--shared/js/phoneNumberJS/PhoneNumberMetaData.js218
-rw-r--r--shared/js/phoneNumberJS/mcc_iso3166_table.js242
-rw-r--r--shared/js/settings_listener.js69
-rw-r--r--shared/js/simple_phone_matcher.js366
-rw-r--r--shared/js/tz_select.js155
-rw-r--r--shared/js/visibility_monitor.js494
-rw-r--r--shared/resources/apn.json3175
-rw-r--r--shared/resources/apn/README.md20
-rw-r--r--shared/resources/apn/apns_conf.xml13517
-rw-r--r--shared/resources/apn/apns_conf_local.xml20
-rw-r--r--shared/resources/apn/index.html54
-rw-r--r--shared/resources/apn/operator_variant.xml499
-rw-r--r--shared/resources/apn/query.js288
-rw-r--r--shared/resources/apn/service_providers.xml11021
-rw-r--r--shared/resources/branding/official/Browser.pngbin0 -> 10252 bytes
-rw-r--r--shared/resources/branding/official/about_logo.pngbin0 -> 31474 bytes
-rw-r--r--shared/resources/branding/official/initlogo.pngbin0 -> 617754 bytes
-rw-r--r--shared/resources/branding/official/logosmall.pngbin0 -> 9320 bytes
-rw-r--r--shared/resources/branding/official/powered.pngbin0 -> 617754 bytes
-rw-r--r--shared/resources/branding/official/privacy_sprite.pngbin0 -> 9897 bytes
-rw-r--r--shared/resources/branding/official/splash_screen_generic.pngbin0 -> 99450 bytes
-rw-r--r--shared/resources/branding/unofficial/Browser.pngbin0 -> 5861 bytes
-rw-r--r--shared/resources/branding/unofficial/about_logo.pngbin0 -> 25567 bytes
-rw-r--r--shared/resources/branding/unofficial/initlogo.pngbin0 -> 95166 bytes
-rw-r--r--shared/resources/branding/unofficial/logosmall.pngbin0 -> 5122 bytes
-rw-r--r--shared/resources/branding/unofficial/powered.pngbin0 -> 138815 bytes
-rw-r--r--shared/resources/branding/unofficial/privacy_sprite.pngbin0 -> 9450 bytes
-rw-r--r--shared/resources/branding/unofficial/splash_screen_generic.pngbin0 -> 97918 bytes
-rw-r--r--shared/resources/keyboard_layouts.json46
-rw-r--r--shared/resources/languages.json6
-rw-r--r--shared/resources/media/alarms/ac_classic_clock_alarm.opusbin0 -> 7512 bytes
-rw-r--r--shared/resources/media/alarms/ac_classic_clock_alarm_prog.opusbin0 -> 163092 bytes
-rw-r--r--shared/resources/media/alarms/ac_classic_clock_radio.opusbin0 -> 4960 bytes
-rw-r--r--shared/resources/media/alarms/ac_normal_gem_echoes.opusbin0 -> 103177 bytes
-rw-r--r--shared/resources/media/alarms/ac_normal_ringing_strings.opusbin0 -> 83977 bytes
-rw-r--r--shared/resources/media/alarms/ac_soft_humming_waves.opusbin0 -> 246457 bytes
-rw-r--r--shared/resources/media/alarms/ac_soft_into_the_void.opusbin0 -> 104433 bytes
-rw-r--r--shared/resources/media/alarms/ac_soft_smooth_strings.opusbin0 -> 97654 bytes
-rw-r--r--shared/resources/media/notifications/list.json15
-rw-r--r--shared/resources/media/notifications/notifier_bap.opusbin0 -> 923 bytes
-rw-r--r--shared/resources/media/notifications/notifier_bell.opusbin0 -> 9790 bytes
-rw-r--r--shared/resources/media/notifications/notifier_bell_extd.opusbin0 -> 9754 bytes
-rw-r--r--shared/resources/media/notifications/notifier_boomer.opusbin0 -> 5543 bytes
-rw-r--r--shared/resources/media/notifications/notifier_bop.opusbin0 -> 880 bytes
-rw-r--r--shared/resources/media/notifications/notifier_dididi.opusbin0 -> 1784 bytes
-rw-r--r--shared/resources/media/notifications/notifier_exclamation.oggbin0 -> 14625 bytes
-rw-r--r--shared/resources/media/notifications/notifier_minimal_bands.oggbin0 -> 34604 bytes
-rw-r--r--shared/resources/media/notifications/notifier_rewind.oggbin0 -> 11265 bytes
-rw-r--r--shared/resources/media/notifications/notifier_ring.oggbin0 -> 11028 bytes
-rw-r--r--shared/resources/media/notifications/notifier_spring.oggbin0 -> 15475 bytes
-rw-r--r--shared/resources/media/notifications/notifier_ting.opusbin0 -> 3289 bytes
-rw-r--r--shared/resources/media/ringtones/list.json17
-rw-r--r--shared/resources/media/ringtones/ringer_bitbounce.opusbin0 -> 64069 bytes
-rw-r--r--shared/resources/media/ringtones/ringer_classic_courier.opusbin0 -> 31194 bytes
-rw-r--r--shared/resources/media/ringtones/ringer_classic_electric.oggbin0 -> 40822 bytes
-rw-r--r--shared/resources/media/ringtones/ringer_classic_prism.oggbin0 -> 40427 bytes
-rw-r--r--shared/resources/media/ringtones/ringer_classic_touchmatic.opusbin0 -> 15844 bytes
-rw-r--r--shared/resources/media/ringtones/ringer_classic_wallphone.oggbin0 -> 45896 bytes
-rw-r--r--shared/resources/media/ringtones/ringer_digital_dapple.opusbin0 -> 89909 bytes
-rw-r--r--shared/resources/media/ringtones/ringer_digitalascent.opusbin0 -> 166171 bytes
-rw-r--r--shared/resources/media/ringtones/ringer_loud_windchimes.opusbin0 -> 65252 bytes
-rw-r--r--shared/resources/media/ringtones/ringer_low_bit_swing.opusbin0 -> 49118 bytes
-rw-r--r--shared/resources/media/ringtones/ringer_progressive_dapple.opusbin0 -> 258206 bytes
-rw-r--r--shared/resources/media/ringtones/ringer_rain_echoes.opusbin0 -> 34598 bytes
-rw-r--r--shared/resources/media/ringtones/ringer_ringing_gems.opusbin0 -> 41378 bytes
-rw-r--r--shared/resources/media/ringtones/ringer_soft_disco_drive.opusbin0 -> 190652 bytes
-rw-r--r--shared/resources/media/ringtones/ringer_vamos_la_elektro.opusbin0 -> 306026 bytes
-rw-r--r--shared/resources/tz.json484
-rw-r--r--shared/style/README.md118
-rw-r--r--shared/style/action_menu.css151
-rw-r--r--shared/style/action_menu/images/ui/alpha.pngbin0 -> 993 bytes
-rw-r--r--shared/style/action_menu/images/ui/default.pngbin0 -> 1014 bytes
-rw-r--r--shared/style/action_menu/images/ui/gradient.png (copied from apps/system/camera/style/images/ui/gradient.png)bin3713 -> 3713 bytes
-rw-r--r--shared/style/action_menu/images/ui/pattern.png (copied from apps/system/camera/style/images/ui/pattern.png)bin6851 -> 6851 bytes
-rw-r--r--shared/style/action_menu/index.html39
-rw-r--r--shared/style/buttons.css230
-rw-r--r--shared/style/buttons/images/icons/dialog.pngbin0 -> 1167 bytes
-rw-r--r--shared/style/buttons/images/icons/view.pngbin0 -> 1235 bytes
-rw-r--r--shared/style/buttons/images/ui/danger-disabled.pngbin0 -> 1020 bytes
-rw-r--r--shared/style/buttons/images/ui/danger-press.pngbin0 -> 1015 bytes
-rw-r--r--shared/style/buttons/images/ui/danger.pngbin0 -> 1031 bytes
-rw-r--r--shared/style/buttons/images/ui/default.pngbin0 -> 1014 bytes
-rw-r--r--shared/style/buttons/images/ui/disabled.pngbin0 -> 1013 bytes
-rw-r--r--shared/style/buttons/images/ui/recommend.pngbin0 -> 1020 bytes
-rw-r--r--shared/style/buttons/images/ui/shadow.pngbin0 -> 146 bytes
-rw-r--r--shared/style/buttons/index.html126
-rw-r--r--shared/style/confirm.css184
-rw-r--r--shared/style/confirm/content.html48
-rw-r--r--shared/style/confirm/images/ui/danger-disabled.pngbin0 -> 1020 bytes
-rw-r--r--shared/style/confirm/images/ui/danger-press.pngbin0 -> 1015 bytes
-rw-r--r--shared/style/confirm/images/ui/danger.pngbin0 -> 1031 bytes
-rw-r--r--shared/style/confirm/images/ui/default.pngbin0 -> 1014 bytes
-rw-r--r--shared/style/confirm/images/ui/disabled.pngbin0 -> 1013 bytes
-rw-r--r--shared/style/confirm/images/ui/gradient.png (copied from apps/system/camera/style/images/ui/gradient.png)bin3713 -> 3713 bytes
-rw-r--r--shared/style/confirm/images/ui/pattern.png (copied from apps/system/camera/style/images/ui/pattern.png)bin6851 -> 6851 bytes
-rw-r--r--shared/style/confirm/images/ui/recommend.pngbin0 -> 1020 bytes
-rw-r--r--shared/style/confirm/index.html43
-rw-r--r--shared/style/confirm/long_content.html43
-rw-r--r--shared/style/confirm/no_title.html42
-rw-r--r--shared/style/edit_mode.css238
-rw-r--r--shared/style/edit_mode/images/icons/close.pngbin0 -> 1434 bytes
-rw-r--r--shared/style/edit_mode/images/ui/alpha.pngbin0 -> 73 bytes
-rw-r--r--shared/style/edit_mode/images/ui/danger-disabled.pngbin0 -> 1020 bytes
-rw-r--r--shared/style/edit_mode/images/ui/danger-press.pngbin0 -> 1015 bytes
-rw-r--r--shared/style/edit_mode/images/ui/danger.pngbin0 -> 1031 bytes
-rw-r--r--shared/style/edit_mode/images/ui/default.pngbin0 -> 1014 bytes
-rw-r--r--shared/style/edit_mode/images/ui/disabled.pngbin0 -> 102 bytes
-rw-r--r--shared/style/edit_mode/images/ui/recommend.pngbin0 -> 1020 bytes
-rw-r--r--shared/style/edit_mode/images/ui/separator-large.pngbin0 -> 143 bytes
-rw-r--r--shared/style/edit_mode/images/ui/separator.pngbin0 -> 142 bytes
-rw-r--r--shared/style/edit_mode/images/ui/shadow.pngbin0 -> 76 bytes
-rw-r--r--shared/style/edit_mode/index.html48
-rw-r--r--shared/style/headers.css381
-rw-r--r--shared/style/headers/images/icons/add.png (copied from apps/system/camera/style/images/video.png)bin1360 -> 1335 bytes
-rw-r--r--shared/style/headers/images/icons/back-rtl.png (copied from apps/system/camera/style/images/video.png)bin1360 -> 1335 bytes
-rw-r--r--shared/style/headers/images/icons/back.png (renamed from apps/system/camera/style/images/video.png)bin1360 -> 1372 bytes
-rw-r--r--shared/style/headers/images/icons/clear.pngbin0 -> 3278 bytes
-rw-r--r--shared/style/headers/images/icons/close.png (copied from apps/system/camera/style/images/actionicon_cancel.png)bin1423 -> 1423 bytes
-rw-r--r--shared/style/headers/images/icons/compose.pngbin0 -> 1577 bytes
-rw-r--r--shared/style/headers/images/icons/edit.pngbin0 -> 1612 bytes
-rw-r--r--shared/style/headers/images/icons/menu.png (renamed from apps/system/camera/style/images/grid.png)bin1127 -> 1161 bytes
-rw-r--r--shared/style/headers/images/icons/reply-all.pngbin0 -> 1572 bytes
-rw-r--r--shared/style/headers/images/icons/reply.pngbin0 -> 1605 bytes
-rw-r--r--shared/style/headers/images/icons/send.png (copied from apps/system/camera/style/images/actionicon_cancel.png)bin1423 -> 1475 bytes
-rw-r--r--shared/style/headers/images/icons/user.png (renamed from apps/system/camera/style/images/actionicon_cancel.png)bin1423 -> 1498 bytes
-rw-r--r--shared/style/headers/images/ui/dark/header.pngbin0 -> 2849 bytes
-rw-r--r--shared/style/headers/images/ui/dark/negative.pngbin0 -> 2797 bytes
-rw-r--r--shared/style/headers/images/ui/dark/separator.pngbin0 -> 168 bytes
-rw-r--r--shared/style/headers/images/ui/dark/subheader.pngbin0 -> 90 bytes
-rw-r--r--shared/style/headers/images/ui/header.pngbin0 -> 149 bytes
-rw-r--r--shared/style/headers/images/ui/negative.pngbin0 -> 114 bytes
-rw-r--r--shared/style/headers/images/ui/organic/header.pngbin0 -> 94 bytes
-rw-r--r--shared/style/headers/images/ui/organic/negative.pngbin0 -> 73 bytes
-rw-r--r--shared/style/headers/images/ui/organic/pattern.pngbin0 -> 1852 bytes
-rw-r--r--shared/style/headers/images/ui/organic/separator.pngbin0 -> 168 bytes
-rw-r--r--shared/style/headers/images/ui/organic/subheader.pngbin0 -> 828 bytes
-rw-r--r--shared/style/headers/images/ui/overlay/header.pngbin0 -> 73 bytes
-rw-r--r--shared/style/headers/images/ui/overlay/separator.pngbin0 -> 142 bytes
-rw-r--r--shared/style/headers/images/ui/search.pngbin0 -> 92 bytes
-rw-r--r--shared/style/headers/images/ui/separator-large.pngbin0 -> 143 bytes
-rw-r--r--shared/style/headers/images/ui/separator.pngbin0 -> 175 bytes
-rw-r--r--shared/style/headers/images/ui/shadow.pngbin0 -> 76 bytes
-rw-r--r--shared/style/headers/images/ui/subheader.pngbin0 -> 73 bytes
-rw-r--r--shared/style/headers/index.html134
-rw-r--r--shared/style/input_areas.css309
-rw-r--r--shared/style/input_areas/images/icons/clear.pngbin0 -> 3278 bytes
-rw-r--r--shared/style/input_areas/images/ui/active.pngbin0 -> 956 bytes
-rw-r--r--shared/style/input_areas/images/ui/background.pngbin0 -> 936 bytes
-rw-r--r--shared/style/input_areas/images/ui/separator.pngbin0 -> 1000 bytes
-rw-r--r--shared/style/input_areas/images/ui/shadow-invert.pngbin0 -> 932 bytes
-rw-r--r--shared/style/input_areas/images/ui/shadow-search.pngbin0 -> 930 bytes
-rw-r--r--shared/style/input_areas/images/ui/shadow.pngbin0 -> 927 bytes
-rw-r--r--shared/style/input_areas/index.html123
-rw-r--r--shared/style/status.css43
-rw-r--r--shared/style/status/images/ui/gradient.png (renamed from apps/system/camera/style/images/ui/gradient.png)bin3713 -> 3713 bytes
-rw-r--r--shared/style/status/images/ui/pattern.png (renamed from apps/system/camera/style/images/ui/pattern.png)bin6851 -> 6851 bytes
-rw-r--r--shared/style/status/index.html35
-rw-r--r--shared/style/switches.css127
-rw-r--r--shared/style/switches/images/check/danger.pngbin0 -> 3689 bytes
-rw-r--r--shared/style/switches/images/check/default.pngbin0 -> 3726 bytes
-rw-r--r--shared/style/switches/images/radio/danger.pngbin0 -> 1589 bytes
-rw-r--r--shared/style/switches/images/radio/default.pngbin0 -> 1591 bytes
-rw-r--r--shared/style/switches/images/switch/background.pngbin0 -> 1015 bytes
-rw-r--r--shared/style/switches/images/switch/handler.pngbin0 -> 769 bytes
-rw-r--r--shared/style/switches/images/switch/icon.pngbin0 -> 2955 bytes
-rw-r--r--shared/style/switches/index.html89
223 files changed, 39907 insertions, 2832 deletions
diff --git a/.gitignore b/.gitignore
index 3bedf6b..ce9669c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1 +1,2 @@
docs/*.html
+profile
diff --git a/Makefile b/Makefile
index a0a6cdb..6d8d6a0 100644
--- a/Makefile
+++ b/Makefile
@@ -49,46 +49,12 @@ endif
REPORTER?=Spec
-GAIA_APP_SRCDIRS?=apps test_apps showcase_apps
+GAIA_APP_SRCDIRS?=apps
GAIA_INSTALL_PARENT?=/data/local
ADB_REMOUNT?=0
GAIA_ALL_APP_SRCDIRS=$(GAIA_APP_SRCDIRS)
-ifeq ($(MAKECMDGOALS), demo)
-GAIA_DOMAIN=thisdomaindoesnotexist.org
-GAIA_APP_SRCDIRS=apps showcase_apps
-else ifeq ($(MAKECMDGOALS), dogfood)
-DOGFOOD=1
-PRODUCTION=1
-B2G_SYSTEM_APPS=1
-else ifeq ($(MAKECMDGOALS), production)
-PRODUCTION=1
-B2G_SYSTEM_APPS=1
-endif
-
-# PRODUCTION is also set for user and userdebug B2G builds
-ifeq ($(PRODUCTION), 1)
-GAIA_APP_SRCDIRS=apps
-ADB_REMOUNT=1
-endif
-
-ifeq ($(MAKECMDGOALS), dogfood)
-GAIA_APP_SRCDIRS=apps dogfood_apps
-endif
-
-ifeq ($(B2G_SYSTEM_APPS), 1)
-GAIA_INSTALL_PARENT=/system/b2g
-endif
-
-ifneq ($(GAIA_OUTOFTREE_APP_SRCDIRS),)
- $(shell mkdir -p outoftree_apps \
- $(foreach dir,$(GAIA_OUTOFTREE_APP_SRCDIRS),\
- $(foreach appdir,$(wildcard $(dir)/*),\
- && ln -sf $(appdir) outoftree_apps/)))
- GAIA_APP_SRCDIRS += outoftree_apps
-endif
-
GAIA_LOCALES_PATH?=locales
LOCALES_FILE?=shared/resources/languages.json
GAIA_LOCALE_SRCDIRS=shared $(GAIA_APP_SRCDIRS)
@@ -180,7 +146,7 @@ TEST_DIRS ?= $(CURDIR)/tests
# Generate profile/
-profile: multilocale applications-data preferences app-makefiles test-agent-config offline extensions profile/settings.json
+profile: multilocale applications-data preferences app-makefiles offline extensions profile/settings.json
@echo "Profile Ready: please run [b2g|firefox] -profile $(CURDIR)$(SEP)profile"
LANG=POSIX # Avoiding sort order differences between OSes
@@ -240,11 +206,8 @@ webapp-manifests:
@#cat profile/webapps/webapps.json
# Generate profile/webapps/APP/application.zip
-webapp-zip: stamp-commit-hash
+webapp-zip:
ifneq ($(DEBUG),1)
- @rm -rf apps/system/camera
- @cp -r apps/camera apps/system/camera
- @rm apps/system/camera/manifest.webapp
@mkdir -p profile/webapps
@$(call run-js-command, webapp-zip)
endif
@@ -357,96 +320,6 @@ common-install:
cd $(TEST_AGENT_DIR) && npm install .
-.PHONY: update-common
-update-common: common-install
- # integration tests
- rm -f tests/vendor/marionette.js
- cp $(TEST_AGENT_DIR)/node_modules/marionette-client/marionette.js tests/js/vendor/
-
- # common testing tools
- mkdir -p $(TEST_COMMON)/vendor/test-agent/
- mkdir -p $(TEST_COMMON)/vendor/chai/
- rm -Rf tools/xpcwindow
- rm -f $(TEST_COMMON)/vendor/test-agent/test-agent*.js
- rm -f $(TEST_COMMON)/vendor/chai/*.js
- cp -R $(TEST_AGENT_DIR)/node_modules/xpcwindow tools/xpcwindow
- rm -R tools/xpcwindow/vendor/
- cp $(TEST_AGENT_DIR)/node_modules/test-agent/test-agent.js $(TEST_COMMON)/vendor/test-agent/
- cp $(TEST_AGENT_DIR)/node_modules/test-agent/test-agent.css $(TEST_COMMON)/vendor/test-agent/
- cp $(TEST_AGENT_DIR)/node_modules/chai/chai.js $(TEST_COMMON)/vendor/chai/
-
-# Create the json config file
-# for use with the test agent GUI
-test-agent-config: test-agent-bootstrap-apps
- @rm -f $(TEST_AGENT_CONFIG)
- @touch $(TEST_AGENT_CONFIG)
- @rm -f /tmp/test-agent-config;
- @# Build json array of all test files
- @for d in ${GAIA_APP_SRCDIRS}; \
- do \
- find $$d -name '*_test.js' | sed "s:$$d/::g" >> /tmp/test-agent-config; \
- done;
- @echo '{"tests": [' >> $(TEST_AGENT_CONFIG)
- @cat /tmp/test-agent-config | \
- sed 's:\(.*\):"\1":' | \
- sed -e ':a' -e 'N' -e '$$!ba' -e 's/\n/,\
- /g' >> $(TEST_AGENT_CONFIG);
- @echo ' ]}' >> $(TEST_AGENT_CONFIG);
- @echo "Finished: test ui config file: $(TEST_AGENT_CONFIG)"
- @rm -f /tmp/test-agent-config
-
-.PHONY: test-agent-bootstrap-apps
-test-agent-bootstrap-apps:
- @for d in `find -L ${GAIA_APP_SRCDIRS} -mindepth 1 -maxdepth 1 -type d` ;\
- do \
- mkdir -p $$d/test/unit ; \
- mkdir -p $$d/test/integration ; \
- cp -f $(TEST_COMMON)/test/boilerplate/_proxy.html $$d/test/unit/_proxy.html; \
- cp -f $(TEST_COMMON)/test/boilerplate/_sandbox.html $$d/test/unit/_sandbox.html; \
- done
- @echo "Finished: bootstrapping test proxies/sandboxes";
-
-# Temp make file method until we can switch
-# over everything in test
-ifneq ($(strip $(APP)),)
-APP_TEST_LIST=$(shell find apps/$(APP)/test/unit -name '*_test.js')
-endif
-.PHONY: test-agent-test
-test-agent-test:
-ifneq ($(strip $(APP)),)
- @echo 'Running tests for $(APP)';
- @$(TEST_AGENT_DIR)/node_modules/test-agent/bin/js-test-agent test --reporter $(REPORTER) $(APP_TEST_LIST)
-else
- @echo 'Running all tests';
- @$(TEST_AGENT_DIR)/node_modules/test-agent/bin/js-test-agent test --reporter $(REPORTER)
-endif
-
-.PHONY: test-agent-server
-test-agent-server: common-install
- $(TEST_AGENT_DIR)/node_modules/test-agent/bin/js-test-agent server -c ./$(TEST_AGENT_DIR)/test-agent-server.js --http-path . --growl
-
-.PHONY: marionette
-marionette:
-#need the profile
- test -d $(GAIA)/profile || $(MAKE) profile
-ifneq ($(PYTHON_MAJOR), 2)
- @echo "Python 2.7.x is needed for the marionette client. You can set the PYTHON_27 variable to your python2.7 path." && exit 1
-endif
-ifneq ($(PYTHON_MINOR), 7)
- @echo "Python 2.7.x is needed for the marionette client. You can set the PYTHON_27 variable to your python2.7 path." && exit 1
-endif
-ifeq ($(strip $(MC_DIR)),)
- @echo "Please have the MC_DIR environment variable point to the top of your mozilla-central tree." && exit 1
-endif
-#if B2G_BIN is defined, we will run the b2g binary, otherwise, we assume an instance is running
-ifneq ($(strip $(B2G_BIN)),)
- cd $(MC_DIR)/testing/marionette/client/marionette && \
- sh venv_test.sh $(PYTHON_27) --address=$(MARIONETTE_HOST):$(MARIONETTE_PORT) --b2gbin=$(B2G_BIN) $(TEST_DIRS)
-else
- cd $(MC_DIR)/testing/marionette/client/marionette && \
- sh venv_test.sh $(PYTHON_27) --address=$(MARIONETTE_HOST):$(MARIONETTE_PORT) $(TEST_DIRS)
-endif
-
###############################################################################
# Utils #
###############################################################################
@@ -460,20 +333,6 @@ lint:
@gjslint --nojsdoc -r apps -e 'homescreen/everything.me,sms/js/ext,pdfjs/content,pdfjs/test,email/js/ext,music/js/ext,calendar/js/ext'
@gjslint --nojsdoc -r shared/js
-# Generate a text file containing the current changeset of Gaia
-# XXX I wonder if this should be a replace-in-file hack. This would let us
-# let us remove the update-offline-manifests target dependancy of the
-# default target.
-stamp-commit-hash:
- @(if [ -e gaia_commit_override.txt ]; then \
- cp gaia_commit_override.txt apps/settings/resources/gaia_commit.txt; \
- elif [ -d ./.git ]; then \
- git log -1 --format="%H%n%at" HEAD > apps/settings/resources/gaia_commit.txt; \
- else \
- echo 'Unknown Git commit; build date shown here.' > apps/settings/resources/gaia_commit.txt; \
- date +%s >> apps/settings/resources/gaia_commit.txt; \
- fi)
-
# Erase all the indexedDB databases on the phone, so apps have to rebuild them.
delete-databases:
@echo 'Stopping b2g'
diff --git a/apps/system/camera/index.html b/apps/system/camera/index.html
deleted file mode 100644
index c06bf41..0000000
--- a/apps/system/camera/index.html
+++ /dev/null
@@ -1,66 +0,0 @@
-<!DOCTYPE html>
-<html>
- <head>
- <meta charset="utf-8">
- <meta http-equiv="pragma" content="no-cache">
- <title>Camera</title>
- <link rel="resource" type="application/l10n" href="locales/locales.ini" />
- <link rel="resource" type="application/l10n" href="/shared/locales/date.ini" />
- <link type="text/css" rel="stylesheet" href="style/camera.css"/>
- <link type="text/css" rel="stylesheet" href="style/filmstrip.css"/>
- <link type="text/css" rel="stylesheet" href="style/VideoPlayer.css"/>
- </head>
- <body>
- <div id="focus-ring"></div>
- <video id="viewfinder" autoplay></video>
-
- <div id="hud">
- <a id="toggle-camera" class="hidden"></an>
- <a id="toggle-flash" class="hidden"></a>
- </div>
-
- <div id="controls">
- <a id="switch-button" name="Switch source" class="hidden" disabled="disabled"><span></span></a>
- <a id="capture-button" name="Capture" disabled="disabled"><span></span></a>
- <div id="misc-button">
- <a id="gallery-button" class="hidden" name="View Gallery"><span></span></a>
- <a id="cancel-pick" class="hidden"><span></span></a>
- <span id="video-timer">00:00</span>
- </div>
- </div>
-
- <div id="overlay" class="hidden">
- <div id="overlay-content">
- <h1 id="overlay-title"></h1>
- <p id="overlay-text"><p>
- </div>
- </div>
-
- <!-- see filmstrip.js and filmstrip.css for these elements -->
- <div id="filmstrip" class="hidden">
- <a id="filmstrip-gallery-button" class="hidden button"></a>
- </div>
- <div id="preview" class="offscreen">
- <div id="frame-container"> <!-- media frame rotates inside this -->
- <div id="media-frame"></div> <!-- image or video here -->
- </div>
- <footer id="preview-controls"> <!-- camera, delete, share buttons -->
- <a id="camera-button" class="button"></a>
- <a id="delete-button" class="button"></a>
- <a id="share-button" class="button"></a>
- </footer>
- </div>
-
- <script type="text/javascript" src="/shared/js/l10n.js"></script>
- <script type="text/javascript" src="/shared/js/l10n_date.js"></script>
- <script type="text/javascript" src="/shared/js/async_storage.js"></script>
- <script type="text/javascript" src="/shared/js/blobview.js"></script>
- <script type="text/javascript" src="/shared/js/media/jpeg_metadata_parser.js"></script>
- <script type="text/javascript" src="/shared/js/media/get_video_rotation.js"></script>
- <script type="text/javascript" src="/shared/js/media/video_player.js"></script>
- <script type="text/javascript" src="/shared/js/media/media_frame.js"></script>
- <script type="text/javascript" src="/shared/js/gesture_detector.js"></script>
- <script type="text/javascript" src="js/camera.js"></script>
- <script type="text/javascript" src="js/filmstrip.js"></script>
- </body>
-</html>
diff --git a/apps/system/camera/js/camera.js b/apps/system/camera/js/camera.js
deleted file mode 100644
index 25f12ae..0000000
--- a/apps/system/camera/js/camera.js
+++ /dev/null
@@ -1,982 +0,0 @@
-'use strict';
-
-// Utility functions
-function padLeft(num, length) {
- var r = String(num);
- while (r.length < length) {
- r = '0' + r;
- }
- return r;
-}
-
-
-// This handles the logic pertaining to the naming of files according
-// to the Design rule for Camera File System
-// * http://en.wikipedia.org/wiki/Design_rule_for_Camera_File_system
-var DCFApi = (function() {
-
- var api = {};
-
- var dcfConfigLoaded = false;
- var deferredArgs = null;
- var defaultSeq = {file: 1, dir: 100};
-
- var dcfConfig = {
- key: 'dcf_key',
- seq: null,
- postFix: 'MZLLA',
- prefix: {video: 'VID_', image: 'IMG_'},
- ext: {video: '3gp', image: 'jpg'}
- };
-
- api.init = function() {
-
- asyncStorage.getItem(dcfConfig.key, function(value) {
-
- dcfConfigLoaded = true;
- dcfConfig.seq = value ? value : defaultSeq;
-
- // We have a previous call to createDCFFilename that is waiting for
- // a response, fire it again
- if (deferredArgs) {
- var args = deferredArgs;
- api.createDCFFilename(args.storage, args.type, args.callback);
- deferredArgs = null;
- }
- });
- };
-
- api.createDCFFilename = function(storage, type, callback) {
-
- // We havent loaded the current counters from indexedDB yet, defer
- // the call
- if (!dcfConfigLoaded) {
- deferredArgs = {storage: storage, type: type, callback: callback};
- return;
- }
-
- var filepath = 'DCIM/' + dcfConfig.seq.dir + dcfConfig.postFix + '/';
- var filename = dcfConfig.prefix[type] +
- padLeft(dcfConfig.seq.file, 4) + '.' +
- dcfConfig.ext[type];
-
- // A file with this name may have been written by the user or
- // our indexeddb sequence tracker was cleared, check we wont overwrite
- // anything
- var req = storage.get(filepath + filename);
-
- // A file existed, we bump the directory then try to generate a
- // new filename
- req.onsuccess = function() {
- dcfConfig.seq.file = 1;
- dcfConfig.seq.dir += 1;
- asyncStorage.setItem(dcfConfig.key, dcfConfig.seq, function() {
- api.createDCFFilename(storage, type, callback);
- });
- };
-
- // No file existed, we are good to go
- req.onerror = function() {
- if (dcfConfig.seq.file < 9999) {
- dcfConfig.seq.file += 1;
- } else {
- dcfConfig.seq.file = 1;
- dcfConfig.seq.dir += 1;
- }
- asyncStorage.setItem(dcfConfig.key, dcfConfig.seq, function() {
- callback(filepath, filename);
- });
- };
- };
-
- return api;
-
-})();
-
-var Camera = {
- _cameras: null,
- _camera: 0,
- _captureMode: null,
- _recording: false,
-
- // In secure mode the user cannot browse to the gallery
- _secureMode: window.parent !== window,
- _currentOverlay: null,
-
- CAMERA: 'camera',
- VIDEO: 'video',
-
- _videoTimer: null,
- _videoStart: null,
- _videoPath: null,
-
- _autoFocusSupported: 0,
- _manuallyFocused: false,
-
- _timeoutId: 0,
- _cameraObj: null,
-
- _photosTaken: [],
- _cameraProfile: null,
-
- _resumeViewfinderTimer: null,
- _waitingToGenerateThumb: false,
-
- _styleSheet: document.styleSheets[0],
- _orientationRule: null,
- _phoneOrientation: 0,
-
- _pictureStorage: null,
- _videoStorage: null,
- _storageState: null,
-
- STORAGE_INIT: 0,
- STORAGE_AVAILABLE: 1,
- STORAGE_NOCARD: 2,
- STORAGE_UNMOUNTED: 3,
- STORAGE_CAPACITY: 4,
-
- _pictureSize: null,
- _previewPaused: false,
- _previewActive: false,
-
- PREVIEW_PAUSE: 500,
- FILMSTRIP_DURATION: 5000, // show filmstrip for 5s before fading
-
- _flashModes: [],
- _currentFlashMode: 0,
-
- _config: {
- fileFormat: 'jpeg'
- },
-
- get _previewConfig() {
- delete this._previewConfig;
- return this._previewConfig = {
- width: document.body.clientHeight,
- height: document.body.clientWidth
- };
- },
-
- _previewConfigVideo: {
- profile: 'cif',
- rotation: 0,
- width: 352,
- height: 288
- },
-
- _shutterKey: 'camera.shutter.enabled',
- _shutterSound: null,
- _shutterSoundEnabled: true,
-
- PROMPT_DELAY: 2000,
-
- _watchId: null,
- _position: null,
-
- _pendingPick: null,
-
- // The minimum available disk space to start recording a video.
- RECORD_SPACE_MIN: 1024 * 1024 * 2,
-
- // Number of bytes left on disk to let us stop recording.
- RECORD_SPACE_PADDING: 1024 * 1024 * 1,
-
- // Maximum image resolution for still photos taken with camera
- MAX_IMAGE_RES: 1600 * 1200, // Just under 2 megapixels
-
- get overlayTitle() {
- return document.getElementById('overlay-title');
- },
-
- get overlayText() {
- return document.getElementById('overlay-text');
- },
-
- get overlay() {
- return document.getElementById('overlay');
- },
-
- get viewfinder() {
- return document.getElementById('viewfinder');
- },
-
- get switchButton() {
- return document.getElementById('switch-button');
- },
-
- get captureButton() {
- return document.getElementById('capture-button');
- },
-
- get galleryButton() {
- return document.getElementById('gallery-button');
- },
-
- get videoTimer() {
- return document.getElementById('video-timer');
- },
-
- get focusRing() {
- return document.getElementById('focus-ring');
- },
-
- get toggleButton() {
- return document.getElementById('toggle-camera');
- },
-
- get toggleFlashBtn() {
- return document.getElementById('toggle-flash');
- },
-
- // We have seperated init and delayedInit as we want to make sure
- // that on first launch we dont interfere and load the camera
- // previewStream as fast as possible, once the previewStream is
- // active we do the rest of the initialisation.
- init: function() {
- this.setCaptureMode(this.CAMERA);
- this.loadCameraPreview(this._camera, this.delayedInit.bind(this));
- },
-
- delayedInit: function camera_delayedInit() {
- // If we don't have any pending messages, show the usual UI
- // Otherwise, determine which buttons to show once we get our
- // activity message
- if (!navigator.mozHasPendingMessage('activity')) {
- this.galleryButton.classList.remove('hidden');
- this.switchButton.classList.remove('hidden');
- this.enableButtons();
- }
-
- // Dont let the phone go to sleep while the camera is
- // active, user must manually close it
- if (navigator.requestWakeLock) {
- navigator.requestWakeLock('screen');
- }
-
- this.setToggleCameraStyle();
-
- // We lock the screen orientation and deal with rotating
- // the icons manually
- var css = '#switch-button span, #capture-button span, ' +
- '#gallery-button span { -moz-transform: rotate(0deg); }';
- var insertId = this._styleSheet.cssRules.length - 1;
- this._orientationRule = this._styleSheet.insertRule(css, insertId);
- window.addEventListener('deviceorientation', this.orientChange.bind(this));
-
- this.toggleButton.addEventListener('click', this.toggleCamera.bind(this));
- this.toggleFlashBtn.addEventListener('click', this.toggleFlash.bind(this));
- this.viewfinder.addEventListener('click', this.toggleFilmStrip.bind(this));
-
- this.switchButton
- .addEventListener('click', this.toggleModePressed.bind(this));
- this.captureButton
- .addEventListener('click', this.capturePressed.bind(this));
- this.galleryButton
- .addEventListener('click', this.galleryBtnPressed.bind(this));
-
- if (!navigator.mozCameras) {
- this.captureButton.setAttribute('disabled', 'disabled');
- return;
- }
-
- if (this._secureMode) {
- this.galleryButton.setAttribute('disabled', 'disabled');
- }
-
- this._shutterSound = new Audio('./resources/sounds/shutter.ogg');
- this._shutterSound.mozAudioChannelType = 'publicnotification';
-
- if ('mozSettings' in navigator) {
- var req = navigator.mozSettings.createLock().get(this._shutterKey);
- req.onsuccess = (function onsuccess() {
- this._shutterSoundEnabled = req.result[this._shutterKey];
- }).bind(this);
-
- navigator.mozSettings.addObserver(this._shutterKey, (function(e) {
- this._shutterSoundEnabled = e.settingValue;
- }).bind(this));
- }
-
- this._storageState = this.STORAGE_INIT;
-
- this._pictureStorage = navigator.getDeviceStorage('pictures');
- this._videoStorage = navigator.getDeviceStorage('videos'),
-
- this._pictureStorage
- .addEventListener('change', this.deviceStorageChangeHandler.bind(this));
-
- navigator.mozSetMessageHandler('activity', function(activity) {
- var name = activity.source.name;
- if (name === 'pick') {
- Camera.initPick(activity);
- }
- else {
- // We got another activity. Perhaps we were launched from gallery
- // So show our usual buttons
- Camera.galleryButton.classList.remove('hidden');
- Camera.switchButton.classList.remove('hidden');
- }
- Camera.enableButtons();
- });
-
- DCFApi.init();
- },
-
- enableButtons: function camera_enableButtons() {
- if (!this._pendingPick) {
- this.switchButton.removeAttribute('disabled');
- }
- this.captureButton.removeAttribute('disabled');
- },
-
- disableButtons: function camera_disableButtons() {
- this.switchButton.setAttribute('disabled', 'disabled');
- this.captureButton.setAttribute('disabled', 'disabled');
- },
-
- // When inside an activity the user cannot switch between
- // the gallery or video recording.
- initPick: function camera_initPick(activity) {
- this._pendingPick = activity;
-
- // Hide the gallery and switch buttons, leaving only the shutter
- this.galleryButton.classList.add('hidden');
- this.switchButton.classList.add('hidden');
-
- // Display the cancel button and add an event listener for it
- var cancelButton = document.getElementById('cancel-pick');
- cancelButton.classList.remove('hidden');
- cancelButton.onclick = this.cancelPick.bind(this);
- },
-
- cancelPick: function camera_cancelPick() {
- if (this._pendingPick) {
- this._pendingPick.postError('pick cancelled');
- }
- this._pendingPick = null;
- },
-
- toggleModePressed: function camera_toggleCaptureMode(e) {
- if (e.target.getAttribute('disabled')) {
- return;
- }
-
- var newMode = (this.captureMode === this.CAMERA) ? this.VIDEO : this.CAMERA;
- this.disableButtons();
- this.setCaptureMode(newMode);
-
- function gotPreviewStream(stream) {
- this.viewfinder.mozSrcObject = stream;
- this.viewfinder.play();
- this.enableButtons();
- }
- if (this.captureMode === this.CAMERA) {
- this._cameraObj.getPreviewStream(this._previewConfig,
- gotPreviewStream.bind(this));
- } else {
- this._previewConfigVideo.rotation = this._phoneOrientation;
- this._cameraObj.getPreviewStreamVideoMode(this._previewConfigVideo,
- gotPreviewStream.bind(this));
- }
- },
-
- toggleCamera: function camera_toggleCamera() {
- this._camera = 1 - this._camera;
- this.loadCameraPreview(this._camera, this.enableButtons.bind(this));
- this.setToggleCameraStyle();
- },
-
- setToggleCameraStyle: function camera_setToggleCameraStyle() {
- var modeName = this._camera === 0 ? 'back' : 'front';
- this.toggleButton.setAttribute('data-mode', modeName);
- },
-
- toggleFlash: function camera_toggleFlash() {
- if (this._currentFlashMode === this._flashModes.length - 1) {
- this._currentFlashMode = 0;
- } else {
- this._currentFlashMode = this._currentFlashMode + 1;
- }
- this.setFlashMode();
- },
-
- setFlashMode: function camera_setFlashMode() {
- var flashModeName = this._flashModes[this._currentFlashMode];
- this.toggleFlashBtn.setAttribute('data-mode', flashModeName);
- this._cameraObj.flashMode = flashModeName;
- },
-
- toggleRecording: function camera_toggleRecording() {
- if (this._recording) {
- this.stopRecording();
- return;
- }
-
- this.startRecording();
- },
-
- startRecording: function camera_startRecording() {
- var captureButton = this.captureButton;
- var switchButton = this.switchButton;
-
- var onerror = function() {
- handleError('error-recording');
- }
- var onsuccess = (function onsuccess() {
- document.body.classList.add('capturing');
- captureButton.removeAttribute('disabled');
- this._recording = true;
- this.startRecordingTimer();
-
- // Hide the filmstrip to prevent the users from
- // entering the preview mode after Camera starts recording
- if (Filmstrip.isShown())
- Filmstrip.hide();
-
- // User closed app while recording was trying to start
- if (document.mozHidden) {
- this.stopRecording();
- }
- }).bind(this);
-
- var handleError = (function handleError(id) {
- this.enableButtons();
- alert(navigator.mozL10n.get(id + '-title') + '. ' +
- navigator.mozL10n.get(id + '-text'));
- }).bind(this);
-
- this.disableButtons();
-
- var startRecording = (function startRecording(freeBytes) {
- if (freeBytes < this.RECORD_SPACE_MIN) {
- handleError('nospace');
- return;
- }
-
- var config = {
- rotation: this._phoneOrientation,
- maxFileSizeBytes: freeBytes - this.RECORD_SPACE_PADDING
- };
- this._cameraObj.startRecording(config,
- this._videoStorage, this._videoPath,
- onsuccess, onerror);
- }).bind(this);
-
- DCFApi.createDCFFilename(this._videoStorage, 'video', (function(path, name) {
- this._videoPath = path + name;
-
- // The CameraControl API will not automatically create directories
- // for the new file if they do not exist, so write a dummy file
- // to the same directory via DeviceStorage to ensure that the directory
- // exists before recording starts.
- var dummyblob = new Blob([''], {type: 'video/3gpp'});
- var dummyfilename = path + '.' + name;
- var req = this._videoStorage.addNamed(dummyblob, dummyfilename);
- req.onerror = onerror;
- req.onsuccess = (function fileCreated() {
- this._videoStorage.delete(dummyfilename); // No need to wait for success
- // Determine the number of bytes available on disk.
- var spaceReq = this._videoStorage.freeSpace();
- spaceReq.onerror = onerror;
- spaceReq.onsuccess = function() {
- startRecording(spaceReq.result);
- }
- }).bind(this);
- }).bind(this));
- },
-
- startRecordingTimer: function camera_startRecordingTimer() {
- this._videoStart = new Date().getTime();
- this.videoTimer.textContent = this.formatTimer(0);
- this._videoTimer =
- window.setInterval(this.updateVideoTimer.bind(this), 1000);
- },
-
- updateVideoTimer: function camera_updateVideoTimer() {
- var videoLength =
- Math.round((new Date().getTime() - this._videoStart) / 1000);
- this.videoTimer.textContent = this.formatTimer(videoLength);
- },
-
- stopRecording: function camera_stopRecording() {
- this._cameraObj.stopRecording();
- this._recording = false;
- window.clearInterval(this._videoTimer);
- this.enableButtons();
- document.body.classList.remove('capturing');
-
- // XXX
- // I need some way to know when the camera is done writing this file
- // currently I'm sending this to the filmstrip which is trying to
- // determine its rotation and fails sometimes if the file is not
- // yet complete. For now, I just defer for a second, but
- // there ought to be a better way.
- // See https://bugzilla.mozilla.org/show_bug.cgi?id=817367
- // Maybe I'll get a device storage callback... check this.
- var videofile = this._videoPath;
- setTimeout(function() {
- Filmstrip.addVideo(videofile);
- Filmstrip.show(Camera.FILMSTRIP_DURATION);
- }, 1000);
- },
-
- formatTimer: function camera_formatTimer(time) {
- var minutes = Math.floor(time / 60);
- var seconds = Math.round(time % 60);
- if (minutes < 60) {
- return padLeft(minutes, 2) + ':' + padLeft(seconds, 2);
- }
- return '';
- },
-
- capturePressed: function camera_doCapture(e) {
- if (e.target.getAttribute('disabled')) {
- return;
- }
-
- if (this.captureMode === this.CAMERA) {
- this.prepareTakePicture();
- } else {
- this.toggleRecording();
- }
- },
-
- galleryBtnPressed: function camera_galleryBtnPressed() {
- // Can't launch the gallery if the lockscreen is locked.
- // The button shouldn't even be visible in this case, but
- // let's be really sure here.
- if (this._secureMode)
- return;
-
- // Launch the gallery with an activity
- var a = new MozActivity({
- name: 'browse',
- data: {
- type: 'photos'
- }
- });
- },
-
- orientChange: function camera_orientChange(e) {
- // Orientation is 0 starting at 'natural portrait' increasing
- // going clockwise
- var orientation = (e.beta > 45) ? 180 :
- (e.beta < -45) ? 0 :
- (e.gamma < -45) ? 90 :
- (e.gamma > 45) ? 270 : 0;
-
- if (orientation !== this._phoneOrientation) {
- var rule = this._styleSheet.cssRules[this._orientationRule];
- // PLEASE DO SOMETHING KITTENS ARE DYING
- // Setting MozRotate to 90 or 270 causes element to disappear
- rule.style.MozTransform = 'rotate(' + -(orientation + 1) + 'deg)';
- this._phoneOrientation = orientation;
-
- Filmstrip.setOrientation(orientation);
- }
- },
-
- setCaptureMode: function camera_setCaptureMode(mode) {
- if (this.captureMode) {
- document.body.classList.remove(this.captureMode);
- }
- this.captureMode = mode;
- document.body.classList.add(mode);
- },
-
- toggleFilmStrip: function camera_toggleFilmStrip(ev) {
- // We will just ignore
- // because the filmstrip shouldn't be shown
- // while Camera is recording
- if (this._recording)
- return;
-
- if (Filmstrip.isShown())
- Filmstrip.hide();
- else
- Filmstrip.show();
- },
-
- loadCameraPreview: function camera_loadCameraPreview(camera, callback) {
-
- this.viewfinder.mozSrcObject = null;
- this._timeoutId = 0;
-
- var viewfinder = this.viewfinder;
- var style = viewfinder.style;
- var width = document.body.clientHeight;
- var height = document.body.clientWidth;
-
- style.top = ((width / 2) - (height / 2)) + 'px';
- style.left = -((width / 2) - (height / 2)) + 'px';
-
- var transform = 'rotate(90deg)';
- var rotation;
- if (camera == 1) {
- /* backwards-facing camera */
- transform += ' scale(-1, 1)';
- rotation = 0;
- } else {
- /* forwards-facing camera */
- rotation = 0;
- }
-
- style.MozTransform = transform;
- style.width = width + 'px';
- style.height = height + 'px';
-
- this._cameras = navigator.mozCameras.getListOfCameras();
- var options = {camera: this._cameras[this._camera]};
-
- function gotPreviewScreen(stream) {
- viewfinder.mozSrcObject = stream;
- viewfinder.play();
-
- if (callback) {
- callback();
- }
-
- this._previewActive = true;
- this.checkStorageSpace();
- setTimeout(this.initPositionUpdate.bind(this), this.PROMPT_DELAY);
- }
-
- function gotCamera(camera) {
- this._cameraObj = camera;
- this._config.rotation = rotation;
- this._autoFocusSupported =
- camera.capabilities.focusModes.indexOf('auto') !== -1;
- this._pictureSize =
- this.pickPictureSize(camera.capabilities.pictureSizes);
- this.enableCameraFeatures(camera.capabilities);
- camera.onShutter = (function() {
- if (this._shutterSoundEnabled) {
- this._shutterSound.play();
- }
- }).bind(this);
- camera.onRecorderStateChange = this.recordingStateChanged.bind(this);
- if (this.captureMode === this.CAMERA) {
- camera.getPreviewStream(this._previewConfig, gotPreviewScreen.bind(this));
- } else {
- this._previewConfigVideo.rotation = this._phoneOrientation;
- this._cameraObj.getPreviewStreamVideoMode(this._previewConfigVideo,
- gotPreviewScreen.bind(this));
- }
- }
-
- // If there is already a camera, we would have to release it first.
- if (this._cameraObj) {
- this.release(function camera_release_callback() {
- navigator.mozCameras.getCamera(options, gotCamera.bind(this));
- });
- } else {
- navigator.mozCameras.getCamera(options, gotCamera.bind(this));
- }
- },
-
- recordingStateChanged: function(msg) {
- if (msg === 'FileSizeLimitReached') {
- this.stopRecording();
- alert(navigator.mozL10n.get('size-limit-reached'));
- }
- },
-
- enableCameraFeatures: function camera_enableCameraFeatures(capabilities) {
- if (this._cameras.length > 1) {
- this.toggleButton.classList.remove('hidden');
- } else {
- this.toggleButton.classList.add('hidden');
- }
-
- this._flashModes = capabilities.flashModes;
- if (this._flashModes) {
- this.setFlashMode();
- this.toggleFlashBtn.classList.remove('hidden');
- } else {
- this.toggleFlashBtn.classList.add('hidden');
- }
- },
-
- startPreview: function camera_startPreview() {
- this.viewfinder.play();
- this.loadCameraPreview(this._camera, this.enableButtons.bind(this));
- this._previewActive = true;
- },
-
- stopPreview: function camera_stopPreview() {
- if (this._recording) {
- this.stopRecording();
- }
- this.disableButtons();
- this.viewfinder.pause();
- this._previewActive = false;
- this.viewfinder.mozSrcObject = null;
- this.release();
- },
-
- resumePreview: function camera_resumePreview() {
- this._cameraObj.resumePreview();
- this._previewActive = true;
- this.enableButtons();
- },
-
- restartPreview: function camera_restartPreview() {
- this._resumeViewfinderTimer =
- window.setTimeout(this.resumePreview.bind(this), this.PREVIEW_PAUSE);
- },
-
- takePictureSuccess: function camera_takePictureSuccess(blob) {
- this._manuallyFocused = false;
- this.hideFocusRing();
- this.restartPreview();
- DCFApi.createDCFFilename(this._pictureStorage, 'image', (function(path, name) {
- var addreq = this._pictureStorage.addNamed(blob, path + name);
- addreq.onsuccess = (function() {
- if (this._pendingPick) {
- // XXX: https://bugzilla.mozilla.org/show_bug.cgi?id=806503
- // We ought to just be able to pass this blob to the activity.
- // But there seems to be a bug with blob lifetimes and activities.
- // So we'll get a new blob back out of device storage to ensure
- // that we've got a file-backed blob instead of a memory-backed blob.
- var getreq = this._pictureStorage.get(path + name);
- getreq.onsuccess = (function() {
- this._pendingPick.postResult({
- type: 'image/jpeg',
- blob: getreq.result
- });
- this.cancelPick();
- }).bind(this);
-
- return;
- }
-
- Filmstrip.addImage(path + name, blob);
- Filmstrip.show(Camera.FILMSTRIP_DURATION);
- this.checkStorageSpace();
-
- }).bind(this);
-
- addreq.onerror = function() {
- alert(navigator.mozL10n.get('error-saving-title') + '. ' +
- navigator.mozL10n.get('error-saving-text'));
- };
- }).bind(this));
- },
-
- hideFocusRing: function camera_hideFocusRing() {
- this.focusRing.removeAttribute('data-state');
- },
-
- checkStorageSpace: function camera_checkStorageSpace() {
- if (this.updateOverlay()) {
- return;
- }
-
- // The first time we're called, we need to make sure that there
- // is an sdcard and that it is mounted. (Subsequently the device
- // storage change handler will track that.)
- if (this._storageState === this.STORAGE_INIT) {
- this._pictureStorage.available().onsuccess = (function(e) {
- this.updateStorageState(e.target.result);
- this.updateOverlay();
- // Now call the parent method again, so that if the sdcard is
- // available we will actually verify that there is enough space on it
- this.checkStorageSpace();
- }.bind(this));
- return;
- }
-
- // Now verify that there is enough space to store a picture
- // 4 bytes per pixel plus some room for a header should be more
- // than enough for a JPEG image.
- var MAX_IMAGE_SIZE =
- (this._pictureSize.width * this._pictureSize.height * 4) + 4096;
-
- this._pictureStorage.freeSpace().onsuccess = (function(e) {
- // XXX
- // If we ever enter this out-of-space condition, it looks like
- // this code will never be able to exit. The user will have to
- // quit the app and start it again. Just deleting files will
- // not be enough to get back to the STORAGE_AVAILABLE state.
- // To fix this, we need an else clause here, and also a change
- // in the updateOverlay() method.
- if (e.target.result < MAX_IMAGE_SIZE) {
- this._storageState = this.STORAGE_CAPACITY;
- }
- this.updateOverlay();
- }).bind(this);
- },
-
- deviceStorageChangeHandler: function camera_deviceStorageChangeHandler(e) {
- switch (e.reason) {
- case 'available':
- case 'unavailable':
- case 'shared':
- this.updateStorageState(e.reason);
- break;
- }
- this.checkStorageSpace();
- },
-
- updateStorageState: function camera_updateStorageState(state) {
- switch (state) {
- case 'available':
- this._storageState = this.STORAGE_AVAILABLE;
- break;
- case 'unavailable':
- this._storageState = this.STORAGE_NOCARD;
- break;
- case 'shared':
- this._storageState = this.STORAGE_UNMOUNTED;
- break;
- }
- },
-
- updateOverlay: function camera_updateOverlay() {
- if (this._storageState === this.STORAGE_INIT) {
- return false;
- }
-
- if (this._storageState === this.STORAGE_AVAILABLE) {
- // Preview may have previously been paused if storage
- // was not available
- if (!this._previewActive && !document.mozHidden) {
- this.startPreview();
- }
- this.showOverlay(null);
- return false;
- }
-
- switch (this._storageState) {
- case this.STORAGE_NOCARD:
- this.showOverlay('nocard');
- break;
- case this.STORAGE_UNMOUNTED:
- this.showOverlay('pluggedin');
- break;
- case this.STORAGE_CAPACITY:
- this.showOverlay('nospace2');
- break;
- }
- if (this._previewActive) {
- this.stopPreview();
- }
- return true;
- },
-
- prepareTakePicture: function camera_takePicture() {
- this.disableButtons();
- this.focusRing.setAttribute('data-state', 'focusing');
- if (this._autoFocusSupported && !this._manuallyFocused) {
- this._cameraObj.autoFocus(this.autoFocusDone.bind(this));
- } else {
- this.takePicture();
- }
- },
-
- autoFocusDone: function camera_autoFocusDone(success) {
- if (!success) {
- this.enableButtons();
- this.focusRing.setAttribute('data-state', 'fail');
- window.setTimeout(this.hideFocusRing.bind(this), 1000);
- return;
- }
- this.focusRing.setAttribute('data-state', 'focused');
- this.takePicture();
- },
-
- takePicture: function camera_takePicture() {
- this._config.rotation = this._phoneOrientation;
- this._config.pictureSize = this._pictureSize;
- if (this._position) {
- this._config.position = this._position;
- }
- this._cameraObj
- .takePicture(this._config, this.takePictureSuccess.bind(this));
- },
-
- showOverlay: function camera_showOverlay(id) {
- this._currentOverlay = id;
-
- if (id === null) {
- this.overlay.classList.add('hidden');
- return;
- }
-
- this.overlayTitle.textContent = navigator.mozL10n.get(id + '-title');
- this.overlayText.textContent = navigator.mozL10n.get(id + '-text');
- this.overlay.classList.remove('hidden');
- },
-
- pickPictureSize: function camera_pickPictureSize(pictureSizes) {
- var maxRes = this.MAX_IMAGE_RES;
- var size = pictureSizes.reduce(function(acc, size) {
- var mp = size.width * size.height;
- return (mp > acc.width * acc.height && mp <= maxRes) ? size : acc;
- }, {width: 0, height: 0});
-
- if (size.width === 0 && size.height === 0) {
- return pictureSizes[0];
- } else {
- return size;
- }
- },
-
- initPositionUpdate: function camera_initPositionUpdate() {
- if (this._watchId || document.mozHidden) {
- return;
- }
- this._watchId = navigator.geolocation
- .watchPosition(this.updatePosition.bind(this));
- },
-
- updatePosition: function camera_updatePosition(position) {
- this._position = {
- timestamp: position.timestamp,
- altitude: position.coords.altitude,
- latitude: position.coords.latitude,
- longitude: position.coords.longitude
- };
- },
-
- cancelPositionUpdate: function camera_cancelPositionUpdate() {
- navigator.geolocation.clearWatch(this._watchId);
- this._watchId = null;
- },
-
- release: function camera_release(callback) {
- if (!this._cameraObj)
- return;
-
- this._cameraObj.release(function cameraReleased() {
- Camera._cameraObj = null;
- if (callback)
- callback.call(Camera);
- }, function releaseError() {
- console.warn('Camera: failed to release hardware?');
- if (callback)
- callback.call(Camera);
- });
- }
-};
-
-Camera.init();
-
-document.addEventListener('mozvisibilitychange', function() {
- if (document.mozHidden) {
- Camera.stopPreview();
- Camera.cancelPick();
- Camera.cancelPositionUpdate();
- if (this._secureMode) // If the lockscreen is locked
- Filmstrip.clear(); // then forget everything when closing camera
- } else {
- Camera.startPreview();
- }
-});
-
-window.addEventListener('beforeunload', function() {
- window.clearTimeout(Camera._timeoutId);
- delete Camera._timeoutId;
- Camera.viewfinder.mozSrcObject = null;
-});
diff --git a/apps/system/camera/js/filmstrip.js b/apps/system/camera/js/filmstrip.js
deleted file mode 100644
index d428801..0000000
--- a/apps/system/camera/js/filmstrip.js
+++ /dev/null
@@ -1,568 +0,0 @@
-/*
- * filmstrip.js: filmstrip, thumbnails and previews for the camera.
- */
-
-'use strict';
-
-var Filmstrip = (function() {
-
- // This array holds all the data we need for image and video previews
- var items = [];
- var currentItemIndex;
-
- // Maximum number of thumbnails in the filmstrip
- var MAX_THUMBNAILS = 5;
- var THUMBNAIL_WIDTH = 46; // size of each thumbnail
- var THUMBNAIL_HEIGHT = 46;
-
- // Timer for auto-hiding the filmstrip
- var hideTimer = null;
-
- // Document elements we care about
- var filmstrip = document.getElementById('filmstrip');
- var preview = document.getElementById('preview');
- var frameContainer = document.getElementById('frame-container');
- var mediaFrame = document.getElementById('media-frame');
- var cameraButton = document.getElementById('camera-button');
- var shareButton = document.getElementById('share-button');
- var deleteButton = document.getElementById('delete-button');
- var filmstripGalleryButton =
- document.getElementById('filmstrip-gallery-button');
-
- // Offscreen elements for generating thumbnails with
- var offscreenImage = new Image();
- var offscreenVideo = document.createElement('video');
-
- // Set up event handlers
- cameraButton.onclick = returnToCameraMode;
- deleteButton.onclick = deleteCurrentItem;
- shareButton.onclick = shareCurrentItem;
- filmstripGalleryButton.onclick = Camera.galleryBtnPressed;
- mediaFrame.addEventListener('dbltap', handleDoubleTap);
- mediaFrame.addEventListener('transform', handleTransform);
- mediaFrame.addEventListener('pan', handlePan);
- mediaFrame.addEventListener('swipe', handleSwipe);
-
- // Generate gesture events
- var gestureDetector = new GestureDetector(mediaFrame);
- gestureDetector.startDetecting();
-
- // Create the MediaFrame for previews
- var frame = new MediaFrame(mediaFrame);
-
- // Start off with it positioned correctly.
- setOrientation(Camera._phoneOrientation);
-
- // If we're running in secure mode, we never want the user to see the
- // gallery button or the share button.
- filmstrip.removeChild(filmstripGalleryButton);
- shareButton.parentNode.removeChild(shareButton);
-
- function isShown() {
- return !filmstrip.classList.contains('hidden');
- }
-
- function hide() {
- filmstrip.classList.add('hidden');
- if (hideTimer) {
- clearTimeout(hideTimer);
- hideTimer = null;
- }
- }
-
- /*
- * With a time, show the filmstrip and then hide it after the time is up.
- * Without time, show until hidden.
- * Tapping in the camera toggles it. And if toggled on, it will be on
- * without a timer.
- * It is always on when a preview is shown.
- * After recording a photo or video, it is shown for 5 seconds.
- * And it is also shown for 5 seconds after leaving preview mode.
- */
- function show(time) {
- filmstrip.classList.remove('hidden');
- if (hideTimer) {
- clearTimeout(hideTimer);
- hideTimer = null;
- }
- if (time)
- hideTimer = setTimeout(hide, time);
- }
-
- filmstrip.onclick = function(event) {
- var target = event.target;
- if (!target || !target.classList.contains('thumbnail'))
- return;
-
- var index = parseInt(target.dataset.index);
- previewItem(index);
- // If we're showing previews be sure we're showing the filmstrip
- // with no timeout and be sure that the viewfinder video is paused.
- show();
- Camera.viewfinder.pause();
- // If there is a preview shown, we want the gallery button in
- // the filmstrip
- filmstripGalleryButton.classList.remove('hidden');
- };
-
- function previewItem(index) {
- // Don't redisplay the item if it is already displayed
- if (currentItemIndex === index)
- return;
-
- var item = items[index];
-
- if (item.isImage) {
- frame.displayImage(item.blob, item.width, item.height, item.preview);
- }
- else if (item.isVideo) {
- frame.displayVideo(item.blob, item.width, item.height, item.rotation);
- }
-
- preview.classList.remove('offscreen');
- currentItemIndex = index;
-
- // Highlight the border of the thumbnail we're previewing
- // and clear the highlight on all others
- items.forEach(function(item, itemindex) {
- if (itemindex === index)
- item.element.classList.add('previewed');
- else
- item.element.classList.remove('previewed');
- });
- }
-
- function returnToCameraMode() {
- Camera.viewfinder.play(); // Restart the viewfinder
- show(Camera.FILMSTRIP_DURATION); // Fade the filmstrip after a delay
- // hide the gallery button in the filmstrip
- filmstripGalleryButton.classList.add('hidden');
- preview.classList.add('offscreen');
- frame.clear();
- if (items.length > 0)
- items[currentItemIndex].element.classList.remove('previewed');
- currentItemIndex = null;
- }
-
- function deleteCurrentItem() {
- var item = items[currentItemIndex];
- var msg, storage, filename;
-
- if (item.isImage) {
- msg = navigator.mozL10n.get('delete-photo?');
- storage = Camera._pictureStorage;
- filename = item.filename;
- }
- else {
- msg = navigator.mozL10n.get('delete-video?');
- storage = Camera._videoStorage;
- filename = item.filename;
- }
-
- // The system app is not allowed to use confirm, I think
- // so if we're running in secure mode, just delete the file without
- // confirmation
- if (Camera._secureMode || confirm(msg)) {
- // Remove the item from the array of items
- items.splice(currentItemIndex, 1);
-
- // Remove the thumbnail image from the filmstrip
- filmstrip.removeChild(item.element);
- URL.revokeObjectURL(item.element.src);
-
- // Renumber the item elements
- items.forEach(function(item, index) {
- item.element.dataset.index = index;
- });
-
- // If there are no more items, go back to the camera
- if (items.length === 0) {
- returnToCameraMode();
- }
- else {
- // Otherwise, switch the frame to display the next item. But if
- // we just deleted the last item, then we'll need to display the
- // previous item.
- var newindex = currentItemIndex;
- if (newindex >= items.length)
- newindex = items.length - 1;
- currentItemIndex = null;
- previewItem(newindex);
- }
-
- // Actually delete the file
- storage.delete(filename).onerror = function(e) {
- console.warn('Failed to delete', filename,
- 'from DeviceStorage:', e.target.error);
- }
- }
- }
-
- function shareCurrentItem() {
- if (Camera._secureMode)
- return;
- var item = items[currentItemIndex];
- var type = item.isImage ? 'image/*' : 'video/*';
- var nameonly = item.filename.substring(item.filename.lastIndexOf('/') + 1);
- var activity = new MozActivity({
- name: 'share',
- data: {
- type: type,
- number: 1,
- blobs: [item.blob],
- filenames: [nameonly],
- filepaths: [item.filename] /* temporary hack for bluetooth app */
- }
- });
- activity.onerror = function(e) {
- console.warn('Share activity error:', activity.error.name);
- }
- }
-
- function handleDoubleTap(e) {
- if (!items[currentItemIndex].isImage)
- return;
-
- var scale;
- if (frame.fit.scale > frame.fit.baseScale)
- scale = frame.fit.baseScale / frame.fit.scale;
- else
- scale = 2;
-
- // If the phone orientation is 0 (unrotated) then the gesture detector's
- // event coordinates match what's on the screen, and we use them to
- // specify a point to zoom in or out on. For other orientations we could
- // calculate the correct point, but instead just use the midpoint.
- var x, y;
- if (Camera._phoneOrientation === 0) {
- x = e.detail.clientX;
- y = e.detail.clientY;
- }
- else {
- x = mediaFrame.offsetWidth / 2;
- y = mediaFrame.offsetHeight / 2;
- }
-
- frame.zoom(scale, x, y, 200);
- }
-
- function handleTransform(e) {
- if (!items[currentItemIndex].isImage)
- return;
-
- // If the phone orientation is 0 (unrotated) then the gesture detector's
- // event coordinates match what's on the screen, and we use them to
- // specify a point to zoom in or out on. For other orientations we could
- // calculate the correct point, but instead just use the midpoint.
- var x, y;
- if (Camera._phoneOrientation === 0) {
- x = e.detail.midpoint.clientX;
- y = e.detail.midpoint.clientY;
- }
- else {
- x = mediaFrame.offsetWidth / 2;
- y = mediaFrame.offsetHeight / 2;
- }
-
- frame.zoom(e.detail.relative.scale, x, y);
- }
-
- function handlePan(e) {
- if (!items[currentItemIndex].isImage)
- return;
-
- // The gesture detector event does not take our CSS rotation into
- // account, so we have to pan by a dx and dy that depend on how
- // the MediaFrame is rotated
- var dx, dy;
- switch (Camera._phoneOrientation) {
- case 0:
- dx = e.detail.relative.dx;
- dy = e.detail.relative.dy;
- break;
- case 90:
- dx = -e.detail.relative.dy;
- dy = e.detail.relative.dx;
- break;
- case 180:
- dx = -e.detail.relative.dx;
- dy = -e.detail.relative.dy;
- break;
- case 270:
- dx = e.detail.relative.dy;
- dy = -e.detail.relative.dx;
- break;
- }
-
- frame.pan(dx, dy);
- }
-
- function handleSwipe(e) {
- // Because the stuff around the media frame does not change position
- // when the phone is rotated, we don't alter these directions based
- // on orientation. To dismiss the preview, the user always swipes toward
- // the filmstrip.
-
- switch (e.detail.direction) {
- case 'up': // close the preview if the swipe is fast enough
- if (e.detail.vy < -1)
- returnToCameraMode();
- break;
- case 'left': // go to next image if fast enough
- if (e.detail.vx < -1 && currentItemIndex < items.length - 1)
- previewItem(currentItemIndex + 1);
- break;
- case 'right': // go to previous image if fast enough
- if (e.detail.vx > 1 && currentItemIndex > 0)
- previewItem(currentItemIndex - 1);
- break;
- }
- }
-
- function addImage(filename, blob) {
- parseJPEGMetadata(blob, function getPreviewBlob(metadata) {
- if (metadata.preview) {
- var previewBlob = blob.slice(metadata.preview.start,
- metadata.preview.end,
- 'image/jpeg');
-
- offscreenImage.src = URL.createObjectURL(previewBlob);
- offscreenImage.onload = function() {
- createThumbnailFromElement(offscreenImage, false, 0,
- function(thumbnail) {
- addItem({
- isImage: true,
- filename: filename,
- thumbnail: thumbnail,
- blob: blob,
- width: metadata.width,
- height: metadata.height,
- preview: metadata.preview
- });
- });
- URL.revokeObjectURL(offscreenImage.src);
- offscreenImage.onload = null;
- offscreenImage.src = null;
- };
- }
- }, function logerr(msg) { console.warn(msg); });
- }
-
- function addVideo(filename) {
- var request = Camera._videoStorage.get(filename);
- request.onerror = function() {
- console.warn('addVideo:', filename, request.error.name);
- };
- request.onsuccess = function() {
- var blob = request.result;
- getVideoRotation(blob, function(rotation) {
- if (typeof rotation !== 'number') {
- console.warn('Unexpected rotation:', rotation);
- rotation = 0;
- }
-
- var url = URL.createObjectURL(blob);
-
- offscreenVideo.preload = 'metadata';
- offscreenVideo.style.width = THUMBNAIL_WIDTH + 'px';
- offscreenVideo.style.height = THUMBNAIL_HEIGHT + 'px';
- offscreenVideo.src = url;
-
- offscreenVideo.onerror = function() {
- URL.revokeObjectURL(url);
- offscreenVideo.onerror = null;
- offscreenVideo.onloadedmetadata = null;
- offscreenVideo.removeAttribute('src');
- offscreenVideo.load();
- console.warn('not a video file', filename);
- }
-
- offscreenVideo.onloadedmetadata = function() {
- createThumbnailFromElement(offscreenVideo, true, rotation,
- function(thumbnail) {
- addItem({
- isVideo: true,
- filename: filename,
- thumbnail: thumbnail,
- blob: blob,
- width: offscreenVideo.videoWidth,
- height: offscreenVideo.videoHeight,
- rotation: rotation
- });
- });
- URL.revokeObjectURL(url);
- offscreenVideo.onerror = null;
- offscreenVideo.onloadedmetadata = null;
- offscreenVideo.removeAttribute('src');
- offscreenVideo.load();
- };
- });
- };
- }
-
- // Add a thumbnail to the filmstrip.
- // The details object contains everything we need to know
- // to display the thumbnail and preview the image or video
- function addItem(item) {
- // Thumbnails go from most recent to least recent.
- items.unshift(item);
-
- // Create an image element for this new thumbnail and display it
- item.element = new Image();
- item.element.src = URL.createObjectURL(item.thumbnail);
- item.element.classList.add('thumbnail');
- filmstrip.insertBefore(item.element, filmstrip.firstElementChild);
-
- // If we have too many thumbnails now, remove the oldest one from
- // the array, and remove its element from the filmstrip and release
- // its blob url
- if (items.length > MAX_THUMBNAILS) {
- var oldest = items.pop();
- filmstrip.removeChild(oldest.element);
- URL.revokeObjectURL(oldest.element.src);
- }
-
- // Now update the index associated with each of the remaining elements
- // so that the click event handle knows which one it clicked on
- items.forEach(function(item, index) {
- item.element.dataset.index = index;
- });
- }
-
- // Remove all items from the filmstrip. Don't delete the files, but
- // forget all of our state. This also exits preview mode if we're in it.
- function clear() {
- if (!preview.classList.contains('offscreen'))
- returnToCameraMode();
- items.forEach(function(item) {
- filmstrip.removeChild(item.element);
- URL.revokeObjectURL(item.element.src);
- });
- items.length = 0;
- }
-
- // Create a thumbnail size canvas, copy the <img> or <video> into it
- // cropping the edges as needed to make it fit, and then extract the
- // thumbnail image as a blob and pass it to the callback.
- function createThumbnailFromElement(elt, video, rotation, callback) {
- // Create a thumbnail image
- var canvas = document.createElement('canvas');
- var context = canvas.getContext('2d');
- canvas.width = THUMBNAIL_WIDTH;
- canvas.height = THUMBNAIL_HEIGHT;
- var eltwidth = video ? elt.videoWidth : elt.width;
- var eltheight = video ? elt.videoHeight : elt.height;
- var scalex = canvas.width / eltwidth;
- var scaley = canvas.height / eltheight;
-
- // Take the larger of the two scales: we crop the image to the thumbnail
- var scale = Math.max(scalex, scaley);
-
- // Calculate the region of the image that will be copied to the
- // canvas to create the thumbnail
- var w = Math.round(THUMBNAIL_WIDTH / scale);
- var h = Math.round(THUMBNAIL_HEIGHT / scale);
- var x = Math.round((eltwidth - w) / 2);
- var y = Math.round((eltheight - h) / 2);
-
- // If a rotation is specified, rotate the canvas context
- if (rotation) {
- context.save();
- switch (rotation) {
- case 90:
- context.translate(THUMBNAIL_WIDTH, 0);
- context.rotate(Math.PI / 2);
- break;
- case 180:
- context.translate(THUMBNAIL_WIDTH, THUMBNAIL_HEIGHT);
- context.rotate(Math.PI);
- break;
- case 270:
- context.translate(0, THUMBNAIL_HEIGHT);
- context.rotate(-Math.PI / 2);
- break;
- }
- }
-
- // Draw that region of the image into the canvas, scaling it down
- context.drawImage(elt, x, y, w, h,
- 0, 0, THUMBNAIL_WIDTH, THUMBNAIL_HEIGHT);
-
- // Restore the default rotation so the play arrow comes out correctly
- if (rotation) {
- context.restore();
- }
-
- // If this is a video, superimpose a translucent play button over
- // the captured video frame to distinguish it from a still photo thumbnail
- if (video) {
- // First draw a transparent gray circle
- context.fillStyle = 'rgba(0, 0, 0, .3)';
- context.beginPath();
- context.arc(THUMBNAIL_WIDTH / 2, THUMBNAIL_HEIGHT / 2,
- THUMBNAIL_HEIGHT / 3, 0, 2 * Math.PI, false);
- context.fill();
-
- // Now outline the circle in white
- context.strokeStyle = 'rgba(255,255,255,.6)';
- context.lineWidth = 2;
- context.stroke();
-
- // And add a white play arrow.
- context.beginPath();
- context.fillStyle = 'rgba(255,255,255,.6)';
- // The height of an equilateral triangle is sqrt(3)/2 times the side
- var side = THUMBNAIL_HEIGHT / 3;
- var triangle_height = side * Math.sqrt(3) / 2;
- context.moveTo(THUMBNAIL_WIDTH / 2 + triangle_height * 2 / 3,
- THUMBNAIL_HEIGHT / 2);
- context.lineTo(THUMBNAIL_WIDTH / 2 - triangle_height / 3,
- THUMBNAIL_HEIGHT / 2 - side / 2);
- context.lineTo(THUMBNAIL_WIDTH / 2 - triangle_height / 3,
- THUMBNAIL_HEIGHT / 2 + side / 2);
- context.closePath();
- context.fill();
- }
-
- canvas.toBlob(callback, 'image/jpeg');
- }
-
- function setOrientation(orientation) {
- preview.dataset.orientation = orientation;
- filmstrip.dataset.orientation = orientation;
- mediaFrame.dataset.orientation = orientation;
-
- // When we rotate the media frame, we also have to change its size
- var containerWidth = frameContainer.offsetWidth;
- var containerHeight = frameContainer.offsetHeight;
- if (orientation === 0 || orientation === 180) {
- mediaFrame.style.width = containerWidth + 'px';
- mediaFrame.style.height = containerHeight + 'px';
- mediaFrame.style.top = 0 + 'px';
- mediaFrame.style.left = 0 + 'px';
- }
- else {
- mediaFrame.style.width = containerHeight + 'px';
- mediaFrame.style.height = containerWidth + 'px';
- mediaFrame.style.top = ((containerHeight - containerWidth) / 2) + 'px';
- mediaFrame.style.left = ((containerWidth - containerHeight) / 2) + 'px';
- }
-
- // And rotate so this new size fills the screen
- mediaFrame.style.transform = 'rotate(-' + orientation + 'deg)';
-
- // And we have to resize the frame (and its video player)
- frame.resize();
- frame.video.setPlayerSize();
- }
-
- return {
- isShown: isShown,
- hide: hide,
- show: show,
- addImage: addImage,
- addVideo: addVideo,
- clear: clear,
- setOrientation: setOrientation
- };
-}());
diff --git a/apps/system/camera/locales/camera.ar.properties b/apps/system/camera/locales/camera.ar.properties
deleted file mode 100644
index 9ca7690..0000000
--- a/apps/system/camera/locales/camera.ar.properties
+++ /dev/null
@@ -1,19 +0,0 @@
-nospace2-title = لا توجد مساحة كافية على بطاقة الذاكرة
-nospace2-text = حرِّر مساحة بحذف ملفات.
-
-error-saving-title = لم يتم حفظ الصورة
-error-saving-text = حدث خطأ منع الكاميرا من حفظ الصورة.
-
-error-recording-title = لم يتم تصوير الفيديو
-error-recording-text = هنالك خطأ منع الكاميرا من تصوير الفيديو
-
-nocard-title = لم يتم العثور على بطاقة الذاكرة
-nocard-text = أدخل بطاقة الذاكرة لأخذ صور.
-
-pluggedin-title = لا يمكن استخدام الكاميرا والجوَّال موصول.
-pluggedin-text = إفصل الجوَّال لعرض الصور.
-
-size-limit-reached = لا توجد مساحة كافية على بطاقة الذاكرة
-
-delete-photo? = حذف الصورة ؟
-delete-video? = حذف الفيديو؟
diff --git a/apps/system/camera/locales/camera.en-US.properties b/apps/system/camera/locales/camera.en-US.properties
deleted file mode 100644
index 90b318d..0000000
--- a/apps/system/camera/locales/camera.en-US.properties
+++ /dev/null
@@ -1,19 +0,0 @@
-nospace2-title = Not enough space on memory card
-nospace2-text = Try freeing up space by deleting media.
-
-error-saving-title = Picture not saved
-error-saving-text = An error prevented Camera from saving the picture.
-
-error-recording-title = Video not recorded
-error-recording-text = An error prevented Camera from recording the video.
-
-nocard-title = No memory card found
-nocard-text = Insert a memory card to take pictures.
-
-pluggedin-title = Camera can not be used while plugged in
-pluggedin-text = Unplug the phone to view pictures.
-
-size-limit-reached = You have run out of space on your SD card.
-
-delete-photo? = Delete photo?
-delete-video? = Delete video?
diff --git a/apps/system/camera/locales/camera.fr.properties b/apps/system/camera/locales/camera.fr.properties
deleted file mode 100644
index 56d7d3b..0000000
--- a/apps/system/camera/locales/camera.fr.properties
+++ /dev/null
@@ -1,19 +0,0 @@
-nospace2-title = Espace libre sur la carte mémoire insuffisant
-nospace2-text = Essayez de libérer de l’espace en supprimant des médias.
-
-error-saving-title = Photo non sauvegardée
-error-saving-text = Une erreur a empêché l’application Photo de sauvegarder la photo.
-
-error-recording-title = Vidéo non enregistrée
-error-recording-text = Une erreur a empêché l’application Photo d’enregistrer la vidéo.
-
-nocard-title = Aucune carte mémoire trouvée
-nocard-text = Pour prendre des photos, insérez une carte mémoire.
-
-pluggedin-title = L’application Photo ne peut pas être utilisée tant que le téléphone est branché
-pluggedin-text = Pour afficher les photos, débranchez le téléphone.
-
-size-limit-reached = Vous n’avez pas assez d’espace sur votre carte SD.
-
-delete-photo? = Supprimer la photo ?
-delete-video? = Supprimer la vidéo ?
diff --git a/apps/system/camera/locales/camera.zh-TW.properties b/apps/system/camera/locales/camera.zh-TW.properties
deleted file mode 100644
index 9cf042a..0000000
--- a/apps/system/camera/locales/camera.zh-TW.properties
+++ /dev/null
@@ -1,20 +0,0 @@
-nospace2-title = 記憶卡的空間不足
-nospace2-text = 刪除媒體檔案以釋放可用空間。
-
-error-saving-title = 相片未儲存
-error-saving-text = 發生錯誤,相機無法儲存相片。
-
-error-recording-title = 影片未紀錄
-error-recording-text = 有錯誤發生使照相機無法錄製影片。
-
-nocard-title = 找不到記憶卡
-nocard-text = 插入記憶卡以拍照。
-
-pluggedin-title = 連接到電腦時無法使用相機
-pluggedin-text = 拔線以檢視照片。
-
-size-limit-reached = 您已用盡 SD 卡上的空間。
-
-delete-photo? = 刪除相片?
-delete-video? = 刪除影片?
-
diff --git a/apps/system/camera/locales/locales.ini b/apps/system/camera/locales/locales.ini
deleted file mode 100644
index 93f5cbc..0000000
--- a/apps/system/camera/locales/locales.ini
+++ /dev/null
@@ -1,11 +0,0 @@
-@import url(camera.en-US.properties)
-
-[ar]
-@import url(camera.ar.properties)
-
-[fr]
-@import url(camera.fr.properties)
-
-[zh-TW]
-@import url(camera.zh-TW.properties)
-
diff --git a/apps/system/camera/resources/sounds/shutter.ogg b/apps/system/camera/resources/sounds/shutter.ogg
deleted file mode 100644
index f0c67d6..0000000
--- a/apps/system/camera/resources/sounds/shutter.ogg
+++ /dev/null
Binary files differ
diff --git a/apps/system/camera/style/VideoPlayer.css b/apps/system/camera/style/VideoPlayer.css
deleted file mode 100644
index bc2b23c..0000000
--- a/apps/system/camera/style/VideoPlayer.css
+++ /dev/null
@@ -1,152 +0,0 @@
-/* styles for the video element itself */
-.videoPlayer {
- position: absolute;
- left: 0; /* we position it with a transform */
- top:0;
- transform-origin: 0 0;
-}
-
-/* video player controls */
-.videoPlayerControls {
- position: absolute;
- left: 0px;
- right: 0px;
- top: 0px;
- bottom: 0px;
- margin: 0;
- padding: 0;
-}
-
-.videoPlayerPlayButton {
- position: absolute;
- width: 106px;
- height: 106px;
- left: calc(50% - 53px);
- top: calc(50% - 53px);
- background: url("images/video_play_button.png") center no-repeat,
- url("images/video_play_normal.png") center no-repeat;
- border-width: 0;
-}
-
-.videoPlayerPlayButton:active {
- background: url("images/video_play_button.png") center no-repeat,
- url("images/video_play_focus.png") center no-repeat
-}
-
-.videoPlayerPlayButton.hidden {
- opacity: 0;
-}
-
-.videoPlayerFooter {
- position: absolute;
- left: 0px;
- right: 0px;
- bottom: 0px;
- height: 50px;
- margin: 0;
- padding: 0;
- background-color: rgba(0, 0, 0, 0.3);
- overflow: hidden;
- opacity: 1;
- transition: opacity 0.5s;
- font-family: "MozTT", sans-serif;
- -moz-user-select: none;
-}
-
-.videoPlayerFooter.hidden {
- opacity: 0;
- pointer-events: none;
-}
-
-.videoPlayerPauseButton {
- position: absolute;
- width: 100px;
- height: 100px;
- padding: 0;
- margin: 0;
- background: url("images/video_pause_button.png") center no-repeat,
- rgba(0,0,0,.5);
- border-radius: 53px;
- border: solid #ccc 3px;
- top: -25px;
- left: 10px;
-}
-
-.videoPlayerPauseButton:active {
- background: url("images/video_pause_button.png") center no-repeat,
- url("images/video_play_focus.png") center no-repeat
-}
-
-button::-moz-focus-inner {
- padding: 0;
- border: none;
-}
-
-/* time slider */
-.videoPlayerSlider {
- position: absolute;
- left: 110px;
- top: 0px;
- right: 0px;
- height: 100%;
-}
-
-.videoPlayerSlider > span {
- display: block;
- width: 45px;
- position: absolute;
- color: white;
- height: 100%;
- line-height: 50px;
- text-align: center;
- font-size: 15px;
-}
-
-.videoPlayerElapsedText {
- left: 10px;
-}
-
-.videoPlayerDurationText {
- right: 10px;
-}
-
-.videoPlayerProgress {
- position: absolute;
- top: 0px;
- left: 70px;
- right: 70px;
- height: 100%;
-}
-
-.videoPlayerProgress > div {
- position: absolute;
- pointer-events: none;
-}
-
-.videoPlayerElapsedBar, .videoPlayerBackgroundBar {
- height: 4px;
- width: 0%;
- top: 50%;
- margin-top: -2px;
- border-radius: 6px;
-}
-
-.videoPlayerElapsedBar {
- background-color: #0ac;
-}
-
-.videoPlayerBackgroundBar {
- background-color: #333;
- width: 100%;
-}
-
-.videoPlayerPlayHead {
- display: block;
- height: 20px;
- width: 25px;
- border-radius: 25px;
- background-color: white;
- top: 50%;
- margin: -10px 0 0 -12px;
-}
-
diff --git a/apps/system/camera/style/camera.css b/apps/system/camera/style/camera.css
deleted file mode 100644
index 6a1ca02..0000000
--- a/apps/system/camera/style/camera.css
+++ /dev/null
@@ -1,314 +0,0 @@
-html, body {
- font-family: "MozTT", sans-serif;
- font-size: 10px;
- height: 100%;
- width: 100%;
- padding: 0;
- margin: 0;
- overflow: hidden;
- background-color: black;
-}
-
-#viewfinder {
- position: absolute;
- z-index: 25;
-}
-
-#controls {
- position: absolute;
- bottom: 0;
- right: 0;
- left: 0;
- height: 45px;
- z-index: 50;
- background-color: rgba(0, 0, 0, 0.8);
- overflow: hidden;
-}
-
-#switch-button, #capture-button, #misc-button {
- position: absolute;
-}
-
-#switch-button, #misc-button {
- height: 45px;
- width: 33%;
-}
-
-#switch-button span,
-#capture-button span,
-#gallery-button span,
-#cancel-pick span
-{
- -moz-transition: 0.2s ease-in-out;
- pointer-events: none;
- background-position: center center;
- background-repeat: no-repeat;
- display: block;
- position: absolute;
- top: 50%;
- left: 50%;
- margin-left: -15px;
- margin-top: -15px;
- width: 30px;
- height: 30px;
-}
-
-#switch-button {
- left: 66%;
-}
-
-#misc-button {
- text-align: center;
- left: 0;
-}
-
-#video-timer {
- position:relative;
- top:50%;
- margin-top:-0.5em;
-}
-
-#gallery-button {
- display: block;
- width: 100%;
- height: 100%;
-}
-
-#gallery-button.hidden {
- display:none;
-}
-
-#gallery-button span {
- background-image: url(images/grid.png);
-}
-
-#gallery-button[disabled=disabled] {
- display: none;
-}
-
-#cancel-pick {
- display:block;
- width: 100%;
- height: 100%;
-}
-
-#cancel-pick.hidden {
- display:none
-}
-
-#cancel-pick span {
- background-image: url(images/actionicon_cancel.png);
-}
-
-#capture-button[disabled=disabled] {
- opacity: 0.5;
-}
-
-#switch-button[disabled=disabled] {
- opacity: 0.5;
-}
-
-#capture-button {
- background-color: #03a2b4;
- border-radius: 100px;
- left: 33%;
- height: 100px;
- width: 33%;
- top: -28px;
-}
-
-#video-timer {
- display: none;
- color: white;
-}
-
-/* Specific to when we are capturing video */
-.capturing #video-timer {
- display: block;
-}
-
-.capturing #gallery-button {
- display: none;
-}
-
-.capturing #capture-button {
- background-color: #d3361c;
-}
-
-.video.capturing #capture-button span {
- background-image: url(images/stop.png);
-}
-
-/* Swap the camera and video icons depending on mode */
-.video #switch-button span {
- background-image: url(images/camera.png);
-}
-
-.camera #switch-button span {
- background-image: url(images/video.png);
-}
-
-.camera #capture-button span {
- background-image: url(images/camera.png);
-}
-
-.video #capture-button span {
- background-image: url(images/video.png);
-}
-
-#focus-ring {
- position: absolute;
- z-index: 100;
- display: none;
- width: 50px;
- height: 50px;
- border-radius: 50px;
- top: 50%;
- left: 50%;
- margin-top: -30px;
- margin-left: -30px;
-}
-
-#focus-ring[data-state=focused] {
- border: 4px solid rgba(0, 255, 0, 0.3);
- display: block;
-}
-
-#focus-ring[data-state=focusing] {
- border: 4px solid rgba(0, 0, 0, 0.8);
- display: block;
-}
-
-#focus-ring[data-state=fail] {
- border: 4px solid rgba(255, 0, 0, 0.3);
- display: block;
-}
-
-/*
- * The overlay is where we display messages like Scanning, No Videos,
- * No SD card and SD Card in Use along with instructions for resolving
- * the issue. The user can't interact with the app while the overlay
- * is displayed.
- */
-#overlay {
- /* it takes up the whole screen */
- position: absolute;
- top: 0;
- left: 0;
- bottom: 0;
- right: 0;
-
- /* almost transparent gray */
- background-color: rgba(0, 0, 0, 0.4);
- z-index: 100;
-}
-
-/*
- * The overlay content area holds the text of the overlay.
- * It has borders and a less transparent background so that
- * the overlay text shows up more clearly
- */
-#overlay-content {
- background:
- url(images/ui/pattern.png) repeat left top,
- url(images/ui/gradient.png) no-repeat left top;
- background-size: auto auto, 100% 100%;
- /* We can't use shortand with background size because is not implemented yet:
- https://bugzilla.mozilla.org/show_bug.cgi?id=570326; */
- overflow: hidden;
- position: absolute;
- z-index: 100;
- top: 0;
- left: 0;
- right: 0;
- bottom: 0;
- font-family: "MozTT", Sans-serif;
- font-size: 0;
- /* Using font-size: 0; we avoid the unwanted visual space (about 3px)
- created by white-spaces and break lines in the code betewen inline-block elements */
- color: #fff;
- padding: 110px 25px 0px 25px;
-}
-
-#overlay-title {
- font-weight: normal;
- font-size: 1.9rem;
- color: #fff;
- margin: 0 5px -10px 5px;
-}
-
-#overlay-text {
- padding: 10px 5px 0 5px;
- border-top: 1px solid #686868;
- font-weight: 300;
- font-size: 2.5rem;
- color: #ebebeb;
-}
-
-.hidden {
- display: none;
-}
-
-#hud {
- position: absolute;
- top: 20px;
- height: 75px;
- left: 0;
- right: 0;
- z-index: 50;
-}
-
-#hud a {
- position: absolute;
- z-index: 50;
- height: 75px;
- width: 75px;
- border: 0;
- background-position: center center;
- background-repeat: no-repeat;
- background-image: url(images/hud_button_underlay.png);
-}
-
-#hud a:after {
- content: " ";
- display: block;
- position: relative;
- z-index: 60;
- height: 75px;
- width: 75px;
- background: transparent;
- background-position: center center;
- background-repeat: no-repeat;
-}
-
-#hud a:active {
- background-image: url(images/hud_button_underlay_focus.png);
-}
-
-#toggle-camera {
- right: 20px;
-}
-
-#toggle-flash {
- left: 20px;
-}
-
-#toggle-camera[data-mode=back]:after {
- background-image: url(images/toggle_front.png);
-}
-#toggle-camera[data-mode=front]:after {
- background-image: url(images/toggle_back.png);
-}
-
-#toggle-flash[data-mode=on]:after {
- background-image: url(images/flash_on.png);
-}
-#toggle-flash[data-mode=off]:after {
- background-image: url(images/flash_off.png);
-}
-#toggle-flash[data-mode=auto]:after {
- background-image: url(images/flash_auto.png);
-}
-#toggle-flash[data-mode=torch]:after {
- background-image: url(images/flash_torch.png);
-}
diff --git a/apps/system/camera/style/filmstrip.css b/apps/system/camera/style/filmstrip.css
deleted file mode 100644
index fd32dc6..0000000
--- a/apps/system/camera/style/filmstrip.css
+++ /dev/null
@@ -1,187 +0,0 @@
-#filmstrip {
- transition: 0.2s ease-in-out;
- position: absolute;
- z-index: 100;
- left: 0;
- right: 0;
- height: 50px;
- /*
- * the background must be solid for preview mode because otherwise some
- * of the frozen viewfinder shows through. If it really need to be translucent
- * in camera mode, we'll have to change it with javascript
- */
- background: black;
-}
-
-#filmstrip.hidden {
- transform: translateY(-50px);
-}
-
-img.thumbnail {
- position: relative;
- width: 46px;
- height: 46px;
- border: 2px solid white;
- margin-right: 4px;
- float: left; /* XXX: do we need this? */
- -moz-user-select: none;
- transition: 0.2s ease-in-out;
-}
-
-img.thumbnail.previewed { /* if the thumbnail is being previewed */
- border: 2px solid #0ac;
-}
-
-/*
- * Make the thumbnails rotate with the phone
- */
-#filmstrip[data-orientation="90"] img.thumbnail {
- transform: rotate(-90deg);
-}
-#filmstrip[data-orientation="180"] img.thumbnail {
- transform: rotate(-180deg);
-}
-#filmstrip[data-orientation="270"] img.thumbnail {
- transform: rotate(-270deg);
-}
-
-/* this is where we display image and video previews */
-#preview {
- position: absolute;
- left: 0;
- width: 100%;
- top: 50px;
- bottom: 0px;
- padding: 0;
- margin: 0;
- border-width: 0;
- background: #000; /* opaque */
- z-index: 100; /* on top of all the camera stuff */
- transition: transform 0.5s linear;
- overflow: hidden;
- transform-origin: 0 0;
-}
-
-#preview.offscreen {
- transform: translateY(-100%) scale(.125) translateX(50%);
-}
-
-#frame-container {
- position: absolute;
- left: 0;
- top: 0;
- width: 100%;
- bottom: 40px;
- padding: 0px;
- margin: 0px;
- overflow: hidden;
- -moz-user-select: none;
-}
-
-#media-frame {
- position: absolute;
- /* size, position, and rotation are set based on the phone orientation */
-}
-
-#media-frame > img {
- top: 0px; /* javascript modifies this position with a transform */
- left: 0px;
- position: absolute;
- border-width: 0px;
- padding: 0px;
- margin: 0px;
- transform-origin: 0px 0px;
- pointer-events: none;
- -moz-user-select: none;
-}
-
-/*
- * these styes apply when we're swapping out a preview image to replace
- * it with a full-resolution image, but the full image isn't ready yet.
- * This happens when the user starts to zoom in on the image. We need
- * some sort of simple visual effect to fill ~500ms of dead time so the
- * user doesn't think the app has frozen up
- */
-#media-frame > img.swapping {
- opacity: 0.8;
- outline: dashed #0ac 4px;
- outline-offset: -4px;
-}
-
-#media-frame > video {
- transform-origin: 0px 0px;
-}
-
-#preview-controls {
- position: absolute;
- left: 0;
- right: 0;
- bottom: 0px;
- height: 40px;
- background-color: rgba(0, 0, 0, 0.8);
- z-index: 100; /* above the dynamically inserted frame elements */
-}
-
-a.button {
- display: block;
- padding: 0;
- margin: 0;
- border-width: 0;
- background-position: center center;
- background-repeat: no-repeat;
- transition: 0.2s ease-in-out;
-}
-
-a.button:active, a.button:focus {
- outline: none;
-}
-
-a.button.hidden {
- display: none;
-}
-
-#camera-button {
- position: absolute;
- left: 0;
- width: 33%;
- height: 100%;
- background-image: url(images/camera.png);
-}
-
-#share-button {
- position: absolute;
- left: 33%;
- width: 33%;
- height: 100%;
- background-image: url(images/share.png);
-}
-
-#delete-button {
- position: absolute;
- left: 67%;
- width: 33%;
- height: 100%;
- background-image: url(images/delete.png);
-}
-
-#filmstrip-gallery-button {
- position: absolute;
- right: 0;
- top: 0;
- width: 50px;
- height: 50px;
- background-image: url(images/grid.png);
-}
-
-/*
- * Make the button icons rotate with the phone
- */
-#preview[data-orientation="90"] a.button {
- transform: rotate(-90deg);
-}
-#preview[data-orientation="180"] a.button {
- transform: rotate(-180deg);
-}
-#preview[data-orientation="270"] a.button {
- transform: rotate(-270deg);
-}
diff --git a/apps/system/camera/style/icons/60/Camera.png b/apps/system/camera/style/icons/60/Camera.png
deleted file mode 100644
index f27f506..0000000
--- a/apps/system/camera/style/icons/60/Camera.png
+++ /dev/null
Binary files differ
diff --git a/apps/system/camera/style/icons/Camera.png b/apps/system/camera/style/icons/Camera.png
deleted file mode 100644
index f27f506..0000000
--- a/apps/system/camera/style/icons/Camera.png
+++ /dev/null
Binary files differ
diff --git a/apps/system/camera/style/images/camera.png b/apps/system/camera/style/images/camera.png
deleted file mode 100644
index 85a80f5..0000000
--- a/apps/system/camera/style/images/camera.png
+++ /dev/null
Binary files differ
diff --git a/apps/system/camera/style/images/delete.png b/apps/system/camera/style/images/delete.png
deleted file mode 100644
index 0f2450e..0000000
--- a/apps/system/camera/style/images/delete.png
+++ /dev/null
Binary files differ
diff --git a/apps/system/camera/style/images/flash_auto.png b/apps/system/camera/style/images/flash_auto.png
deleted file mode 100644
index 3018d8d..0000000
--- a/apps/system/camera/style/images/flash_auto.png
+++ /dev/null
Binary files differ
diff --git a/apps/system/camera/style/images/flash_off.png b/apps/system/camera/style/images/flash_off.png
deleted file mode 100644
index 0fc7112..0000000
--- a/apps/system/camera/style/images/flash_off.png
+++ /dev/null
Binary files differ
diff --git a/apps/system/camera/style/images/flash_on.png b/apps/system/camera/style/images/flash_on.png
deleted file mode 100644
index c7983d1..0000000
--- a/apps/system/camera/style/images/flash_on.png
+++ /dev/null
Binary files differ
diff --git a/apps/system/camera/style/images/flash_torch.png b/apps/system/camera/style/images/flash_torch.png
deleted file mode 100644
index 3018d8d..0000000
--- a/apps/system/camera/style/images/flash_torch.png
+++ /dev/null
Binary files differ
diff --git a/apps/system/camera/style/images/hud_button_underlay.png b/apps/system/camera/style/images/hud_button_underlay.png
deleted file mode 100644
index 5853adb..0000000
--- a/apps/system/camera/style/images/hud_button_underlay.png
+++ /dev/null
Binary files differ
diff --git a/apps/system/camera/style/images/hud_button_underlay_focus.png b/apps/system/camera/style/images/hud_button_underlay_focus.png
deleted file mode 100644
index c3542bc..0000000
--- a/apps/system/camera/style/images/hud_button_underlay_focus.png
+++ /dev/null
Binary files differ
diff --git a/apps/system/camera/style/images/play_overlay.png b/apps/system/camera/style/images/play_overlay.png
deleted file mode 100644
index 2a56d04..0000000
--- a/apps/system/camera/style/images/play_overlay.png
+++ /dev/null
Binary files differ
diff --git a/apps/system/camera/style/images/share.png b/apps/system/camera/style/images/share.png
deleted file mode 100644
index 6a56f19..0000000
--- a/apps/system/camera/style/images/share.png
+++ /dev/null
Binary files differ
diff --git a/apps/system/camera/style/images/stop.png b/apps/system/camera/style/images/stop.png
deleted file mode 100644
index b358cc5..0000000
--- a/apps/system/camera/style/images/stop.png
+++ /dev/null
Binary files differ
diff --git a/apps/system/camera/style/images/toggle_back.png b/apps/system/camera/style/images/toggle_back.png
deleted file mode 100644
index 5e767b4..0000000
--- a/apps/system/camera/style/images/toggle_back.png
+++ /dev/null
Binary files differ
diff --git a/apps/system/camera/style/images/toggle_front.png b/apps/system/camera/style/images/toggle_front.png
deleted file mode 100644
index b67507f..0000000
--- a/apps/system/camera/style/images/toggle_front.png
+++ /dev/null
Binary files differ
diff --git a/apps/system/camera/style/images/video_pause_button.png b/apps/system/camera/style/images/video_pause_button.png
deleted file mode 100644
index b0224f8..0000000
--- a/apps/system/camera/style/images/video_pause_button.png
+++ /dev/null
Binary files differ
diff --git a/apps/system/camera/style/images/video_play_button.png b/apps/system/camera/style/images/video_play_button.png
deleted file mode 100644
index 56dba6b..0000000
--- a/apps/system/camera/style/images/video_play_button.png
+++ /dev/null
Binary files differ
diff --git a/apps/system/camera/style/images/video_play_focus.png b/apps/system/camera/style/images/video_play_focus.png
deleted file mode 100644
index 1bb0537..0000000
--- a/apps/system/camera/style/images/video_play_focus.png
+++ /dev/null
Binary files differ
diff --git a/apps/system/camera/style/images/video_play_normal.png b/apps/system/camera/style/images/video_play_normal.png
deleted file mode 100644
index 0cabf3d..0000000
--- a/apps/system/camera/style/images/video_play_normal.png
+++ /dev/null
Binary files differ
diff --git a/apps/system/camera/test/unit/_proxy.html b/apps/system/camera/test/unit/_proxy.html
deleted file mode 100644
index 2102451..0000000
--- a/apps/system/camera/test/unit/_proxy.html
+++ /dev/null
@@ -1,49 +0,0 @@
-<!DOCTYPE HTML>
-<html>
-<head>
- <meta http-equiv="content-type" content="text/html; charset=utf-8" />
- <title>Serve the tests</title>
-
- <script type="text/javascript" charset="utf-8">
- (function(window){
- var Loader = window.CommonResourceLoader = {},
- host = document.location.host,
- domain = host.replace(/(^[\w\d-]+\.)?([\w\d]+\.[a-z]+)/, 'test-agent.$2');
-
- Loader.domain = document.location.protocol + '//' + domain;
- Loader.url = function(url){
- return this.domain + url;
- }
-
- Loader.script = function(url, doc){
- doc = doc || document;
- doc.write('<script type="application/javascript;version=1.8" src="' + this.url(url) + '"><\/script>');
- return this;
- };
- }(this));
- </script>
-
- <style type="text/css" media="all">
- html,body,iframe {
- height: 100%;
- width: 100%;
- }
- </style>
-
-</head>
-<body>
-
-<!-- Test Agent UI will be loaded in here -->
-<div id="test-agent-ui">
-</div>
-
-<script type="text/javascript" charset="utf-8">
-CommonResourceLoader.
- script('/common/test/test_url_resolver.js').
- script('/common/vendor/test-agent/test-agent.js').
- script('/common/test/agent_proxy.js');
-</script>
-</body>
-</html>
-
-
diff --git a/apps/system/camera/test/unit/_sandbox.html b/apps/system/camera/test/unit/_sandbox.html
deleted file mode 100644
index 70d0efa..0000000
--- a/apps/system/camera/test/unit/_sandbox.html
+++ /dev/null
@@ -1,28 +0,0 @@
-<!DOCTYPE HTML>
-<html>
-<head>
- <meta http-equiv="content-type" content="text/html; charset=utf-8" />
- <title>Tests</title>
- <link rel="stylesheet" type="text/css" href="/vendor/mocha/mocha.css" />
- <style type="text/css" media="all">
- iframe {
- border: none;
- padding: 0px;
- }
- </style>
- <script type="text/javascript" charset="utf-8">
- </script>
-</head>
-
-<body>
-
-<div id="mocha">
-</div>
-
-<div id="test">
-</div>
-
-</body>
-</html>
-
-
diff --git a/build/applications-data.js b/build/applications-data.js
index 69ef24b..c94303d 100644
--- a/build/applications-data.js
+++ b/build/applications-data.js
@@ -83,161 +83,3 @@ function iconDescriptor(directory, app_name, entry_point) {
icon: icon
};
}
-
-// zeroth grid page is the dock
-let customize = {"homescreens": [
- [
- ["apps", "communications", "dialer"],
- ["apps", "sms"],
- ["apps", "communications", "contacts"],
- ["apps", "browser"]
- ], [
- ["apps", "camera"],
- ["apps", "gallery"],
- ["apps", "fm"],
- ["apps", "settings"],
- ["external-apps", "marketplace"]
- ], [
- ["apps", "calendar"],
- ["apps", "clock"],
- ["apps", "costcontrol"],
- ["apps", "email"],
- ["apps", "music"],
- ["apps", "video"]
- ]
-]};
-
-if (DOGFOOD == 1) {
- customize.homescreens[0].push(["dogfood_apps", "feedback"]);
-}
-
-let init = getFile(GAIA_DIR, 'customize.json');
-if (init.exists()) {
- customize = getJSON(init);
-}
-
-let content = {
- search_page: {
- provider: 'EverythingME',
- enabled: true
- },
-
- grid: customize.homescreens.map(
- function map_homescreens(applist) {
- var output = [];
- for (var i = 0; i < applist.length; i++) {
- if (applist[i] !== null) {
- output.push(iconDescriptor.apply(null, applist[i]));
- }
- }
- return output;
- }
- )
-};
-
-init = getFile(GAIA_DIR, GAIA_CORE_APP_SRCDIR, 'homescreen', 'js', 'init.json');
-writeContent(init, JSON.stringify(content));
-
-// Apps that should never appear in settings > app permissions
-// bug 830659: We want homescreen to appear in order to remove e.me geolocation permission
-let hidden_apps = [
- gaiaManifestURL('keyboard'),
- gaiaManifestURL('wallpaper'),
- gaiaManifestURL('bluetooth'),
- gaiaManifestURL('pdfjs')
-];
-
-init = getFile(GAIA_DIR, GAIA_CORE_APP_SRCDIR, 'settings', 'js', 'hiddenapps.js');
-writeContent(init, "var HIDDEN_APPS = " + JSON.stringify(hidden_apps));
-
-// Apps that should never appear as icons in the homescreen grid or dock
-hidden_apps = hidden_apps.concat([
- gaiaManifestURL('homescreen'),
- gaiaManifestURL('system')
-]);
-
-init = getFile(GAIA_DIR, GAIA_CORE_APP_SRCDIR, 'homescreen', 'js', 'hiddenapps.js');
-writeContent(init, "var HIDDEN_APPS = " + JSON.stringify(hidden_apps));
-
-// Cost Control
-init = getFile(GAIA_DIR, 'apps', 'costcontrol', 'js', 'config.json');
-
-content = {
- provider: 'Vivo',
- enable_on: { 724: [6, 10, 11, 23] }, // { MCC: [ MNC1, MNC2, ...] }
- is_free: true,
- is_roaming_free: true,
- credit: { currency : 'R$' },
- balance: {
- destination: '8000',
- text: 'SALDO',
- senders: ['1515'],
- regexp: 'Saldo Recarga: R\\$\\s*([0-9]+)(?:[,\\.]([0-9]+))?'
- },
- topup: {
- destination: '7000',
- ussd_destination: '*321#',
- text: '&code',
- senders: ['1515', '7000'],
- confirmation_regexp: 'Voce recarregou R\\$\\s*([0-9]+)(?:[,\\.]([0-9]+))?',
- incorrect_code_regexp: '(Favor enviar|envie novamente|Verifique) o codigo de recarga'
- },
- default_low_limit_threshold: 3
-};
-
-writeContent(init, JSON.stringify(content));
-
-// SMS
-init = getFile(GAIA_DIR, 'apps', 'sms', 'js', 'blacklist.json');
-content = ["1515", "7000"];
-writeContent(init, JSON.stringify(content));
-
-// Browser
-init = getFile(GAIA_DIR, 'apps', 'browser', 'js', 'init.json');
-
-content = {
- "bookmarks": [
- { "title": "Vivo Busca",
- "uri": "http://www.google.com.br/m/search?client=ms-hms-tef-br",
- "iconUri": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAAXNSR0IArs4c6QAAAAZiS0dEAP8A/wD/oL2nkwAAAAlwSFlzAAAN1wAADdcBQiibeAAAAAd0SU1FB9wMDg8JHqyWLdEAAAbmSURBVFjD1ZdrjF1VFcd/6+xzz7nPzqOlFNpBKqA8Uoi0Cg1BjaARa+SDFDEKamk7My2tMZIYIUZ8pISoCbaEzsyFkqZSBaLhUUKMBLSAYMm0hqaNSFvSKTJMS2fambmP89h7+eFOHyOdYUD7wfVtn7Nz1m899n+vA/8Ptpz1U9zX/YG/LZO9XOb1UHbLj6+XsC4XTstl/YwfIOKpw9k0SaLharSL3srf2AhAu9dNt2v/7wDacz101xrOl+d7mgPfv1iM+bIJ/Ks9412IJyVBaurcGzZOt9nU/SGq13eW6+2HADpyZbpqyz4cQEf+AbqqS/kqPzFnFGbP94PM9/zAvynM5zC+Ac/DZAye7yHGA1Vqw1XiSv25aLR69+GRgRce4Y6os/gg60dv/WAAHWGZrmgZN/ELv6XYujiTDe4xvt+mTonqdU1qsaSJxTOAJ2SKAfmWgpZaW8RzQvXIaK06NHLn4NC7PZu5vdIe9tAdLZ8QwIxLO11023auYaW0lc5bHOSza41nzorjSCtDI+SmGWm7tJXzr5rFeVfMZPbFLRSbA+qjNTm4/5BKVig1N2d8439WrBcV6jNf/r29y63wNvKqPj71EnQWH1gQFnIPe57/sXq1otFoXc67cibzb5zL+VefyayLmvCzBhQGD1To2/Yurz3Vx44/7scv5LW1daZER6qHB/sPrequLPvtZCUwJ0ffyxa+zdqmQnPp9iCb/WJcq2t9pCqXLTqH6++ezyVfmk1heohNlDRypLEjKPjMuqiJCz49i4xn2PPXt8Uap4VCKS9Ky4XDV2/dwTNH2tlAL09MDNDLFgCualk8L8hl7zW+Hwy9M8gFC8+U69fM5+xLm0gjxVkFBBnLnTpFLZjA0Hb5dFzk2PXsAfLTi+KTOSdN4t2vRk9uP5VzAO/kxde4J/BNuDCTCwpRra75pows+Ppczp7XxPCIIbEeiCCiJ2oogqqiTvGzhvk3zOUj81pl9MiwelljsoXCZ5axdvpEJRgHkCNb9DLegiCXJarUmT2vhXM/NYNq5FOt+6iCoO9tpGMQVmmek2fedXOoHKogPgTZsC1FZ04JIBMGgWe8NjFCmiRyxtwSMz5aYrjig4DxGs5V5ZQQAGHB56wLm/GNiE0t4nltmSCcMSUA8Y2HSIgHaiAs+fjFDKkVjKfHUy8T6Kdqoz+CnE+Y93HWgdJiPFOaEoBNU6uqFRGDlzHYxKGJwxNwTjhF9k95sK1V4loKThCRIWft6JQAXJQkmtgBdY6gGDI0UOXoW6Pkcxargh1LvU4AIiKkkWPoQIXoqEUUrLVvJ0l6eEoAEfVhl7oXklpEsaVE/56jvPXaEZqLCYFvcU5Qxp+CE6kH8WDkYI1//rmfsBjirCOpxe/kaRp8X4AOr8xmfpAmSbQtqUTVbDbH0YFYd/7pACMH67QWY4KMbYR/UhOq6vEGdBYO7DjM35/o01wph9YtcbW+9T5u7v9pbs3kAF2ucXXWhitvxtWox9Ys0+fMlN4n9+srm/YQVS2eSMOZHItax60P7DjM43duJxOG4olHEsUvuRH7PMC+2sL3v4xWZrvpSVdGl6Wf7/fEfCFfKrVKKLL7uT51sZOW2QX80OCHBs8IYoQ0dlQOR+x96SC/W/UKR/sjCqUiLnHER2uP3G+XPAzwSRayfUxtJwQ4N72F3TxGr3164BP2ugGQa0vTmrOm4MuuZ/t074sDUj8Sk8aOkUN1BvsqvLF1gL+s/wfPrNmJSz0KhQLqVKPhuqRRkr+cRbu383TfdrawnO7jkj/hbbjaPMha2xgiVmQfvCU/Y9rPCq3TztHAURmpUD00qvFoLJnQI64moEKQCwjzIcYYNHFEIzVsnKhDBeRlYFWZjt5jc2MP7ZNfx7eZDdxnl4zNd+XPhS25FcXmadf4uaCZjOKsJY0smlqcU0gcNnYk9Yi0Equ1VhSngtDoWHleobNMx+sAS7mfB1gx+Uz4XfMbfm2/2cgEG5rJ8BVT9K8IctlLMkGmIMaEqEvS2MZpVN8XDUcFG6XXOywNryI0pGtMweQxRW8r03lwFQ+xju+8/1QMsNrfxNr05hONysaPp0RFhwYCaUg+LjBjbz+vNwWE9wI3CMeFYhyEwo+BX5bprE5pLD/Zvh88yq/iGyfds4yuuQLrgEWcEG5piKeKIO8CV/TQse+USjiZTeZ8OV0AlOl4E/gh8OIEwdUAN6EUf1jroeM4RA8dO0HvAN2l46IHRX+u6L/+5wDvheh8QZEfgQ7SSP1+Rb4FuqlMZ3JaAI5BtHP/mMLZpwQWKnK5wpXA5jIrao1eWc9ps8U8ytKxn9TVrOdWHvqPRj2Nzo/ZXdzFN9g07tlSyqfc+2+KJinaWejipgAAAABJRU5ErkJggg=="
- },
- { "title": "Serviços e Downloads",
- "uri": "http://vds.vivo.com.br",
- "iconUri": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAABOWlDQ1BQaG90b3Nob3AgSUNDIHByb2ZpbGUAAHjarZGxSsNQFIa/G0XFoVYI4uBwJ1FQbNXBjElbiiBYq0OSrUlDldIk3NyqfQhHtw4u7j6Bk6PgoPgEvoHi1MEhSHASwW/6zs/hcOAHo2LXnYZRhkGsVbvpSNfz5ewTM0wBQCfMUrvVOgCIkzjiJwI+XxEAz5t23WnwN+bDVGlgAmx3oywEUQH6FzrVIMaAGfRTDeIOMNVJuwbiASj1cn8BSkHub0BJuZ4P4gMwe67ngzEHmEHuK4Cpo0sNUEvSkTrrnWpZtSxL2t0kiOTxKNPRIJP7cZioNFEdHXWB/D8AFvPFdtORa1XL2lvnn3E9X+b2foQAxNJjkRWEQ3X+3YWx8/tc3Bgvw+EtTE+KbPcKbjZg4brIVqtQ3oL78RfCs0/+HAmzJwAAAAZiS0dEAP8A/wD/oL2nkwAAAAlwSFlzAAAPYQAAD2EBqD+naQAAAAd0SU1FB9wMDg8FJ18m6tUAAAjBSURBVFjDrVd7cFXFGf/tnnPuM++bhBSD8rQq+MCkZVoQaEc7aivRqY8RLVroyB/BUmuSiqMdWxGQURkBR7AkPnCKVUSxVmYqRQTCACUE0YgPCCDP5L5C7r3nsbtnv/5xEyDVDsykO7MzZ3Z/+51vf99zgQscH6Pixl0/qan/fOEDhf8L8+XihqKOeQ/Ut44YeQcA/P0C5PLzAbo6NkZbh47+KlBVsSE0fORyp+3L3g8Bawsqz2Bahw7HRoBnWtsSVklsuVle/mb7vb9wfrxne/H55JvnAxz/w5LrKx+6d0xo1EiQlFCpNGXXdkybjO63+zETTxzG1srq6wqvnWCFho/G0Ecug0zFg+nNH84AsGxQChx5/wM99qe18E4d619iZqxk7cdJ32CwNC8NYFL6GMyS4s1GZRFEsisPYmD7//i8P2gT8HDZpkMvrIHqSUCl4lCpOEpn/wwE/dBkdGNS+hi2hCpvKrzxh1DJ/L5KJ9D5/OvQPtYPWoE6J5lzvum+L7PvK8h0CjKdgi9tWBXlz2wxqsIAYA4pXUchUP++e+wE5TpPPlHnpI6fTz47H+C9aDmm5RJ4v7SKqn85AaQJAKAzLjJv7XqcwYxHb7lyhVHRFxyM4cT6f0PlvCDjTEzLJQbnhP0CVM4bf3pvZ3v0klgfdxxmRckjAGyEGfzTvQAAN5GFyrp1dW5KXEh4s6ZhjQBDMfk0I+2kfeL0t+ZES/I3FbOwKt48ALw+Ejv4vUmjRiJPAnROAkqDFwfPYE62HsxahaHCm7vOsm8fWo3IiF/BO7EupqHv0na3QaTXFYyZc5wBQONFDU71j0YFw2UR1rG5HZlETyPAlrSkXh7gxevDsXCwJGQXX1JKoD7zMQAEgIGceA6ZE70VdU4yee45op2wD31dT35muerZCxgBsoouY9HR9Yw1Dmu8w4pYbw6bNBpaaRAIbsamAzv3M+F4U61QYMvKEyvpHBbeKasquZUbA/2XtEbyZE9HnZ0cd+766c9WTADUDj/zGcjPAoyDAbDKxoEZ4Vnc0qgMWCaE40IKD9L1wE2DXTppLKqvuGSzEvKbWbGZFQCwoGQK6uzkbb3dvTalBem0B532QGlBmXjWr7OT47bfMhEAEN/5dDjZ9uxB2fP5Du/UR9B2CloIaOFBS4Hc4U+R+bI9zJ4e0lQYlKxX15TDL7IAIpAmEJ2d3Z0nqberZ2tzomVKHwsPRmAsDVHehwU0skyuqbOT0wGge9ufN5FMTiX7EGOMAX2TcQaAQbsepfccZEZhYREDgNcwj2uDplghY9OpawvgFDGgX4m8EUl5knUfOOlKV85vTrQ89V6kvAPAFQBAjA7V5ZIjj/+z6UFobxFzD0ZAksAYY2AAZwDnIOkjte8wtOPdTtp/9+rFR3wOANyEvt9f9NGInDYu3nr6ntjODCjjQSoJKQSEEMyHRmzMkGC0vGD+rPKZrdPsxFhiVA6GqrpccuThd+es9bNHl+r03rB2c9BCMi0EtC8hMzbiezpxctv+6a7tGVct6nybmZb/rUT0qjUP98mFAIDFpY2PZGL8MRkzorAIRABR3hdTX8cBILIq3uwAwBev3sOtUNA3nf0A5+inXXk+cl2ZbK47s9CMWEtqF3zttM8bjfELD3x3IuqoUsDR/HcijLSGtknoKBGdMcl35nPLgLJtcCkBzs4ooIUPJRUUAO3IfKL6r9zHAaBpWGNfLIE3XdQ4vWF4A8lKf4UsVhWKJJSQkFJBSUVujw0i2r4q3uzs2nd5+a59lw+5dPpr2ndy64SMQAtBvhDQngDnGqVDowVVY8qeMgtC9s4nxt+djXdxAPji5bqztaBpWCMHYQpx2iQKFXxDD4gGEBH5xNRp6Wpfz29OtDzV2n7ZNgKbCDAw6PjE8V9U7nvuhgcZiUVBIxXhnPJOyPpqM+MgxiFYDEZB8e3gePfS6Wt91jSsoZCAXifqQTO/74cYEIbkaNKe3tKcaJkKABt3jJ3TIyqWZWQMACFsZFEePr7m+gkd0wGg7clJ75iGe2skmMmHYF/45RVh4MEIhcdcx4KFRUU8rNkMAxrCF3l7SQUlJJRQ8G0F3eMf054e0pxomTr//p/ns5sbXHC8N4KM6yDjuujKmdSdLbwTADasrUHN49tuEx6PpJPRTifDQZ4EybPTz/YyQ+VghaMzzLIc7xaccIoLcN13cw0yJGeM2GQwbGuOtxAAPPbKP/DGxqtf7UyXFiqtBhS1o16B8ca/rt6f1epyAPjBn3Y5AEZtf3jshNxpY0dhsYBlAsQYCICXTkHZHQ4DgL8ajzq7iuPBhOWwgLbAiTcSaElzomVAMXplwzWRrAjkuuwoMQwsRgRQcdBlZSH3qpk3t3967rk1E4GLrhxRb5r+8uIYgRsB4qEq9v2mVsaXhx5FsU9VN6Qq51Z50XoGXr4q0fwMY+xb/VzO41uOZsLwlGau0rCFQtaWcJWGpzTrzgVge3zri+tr+LNv1Zw5V33lcExecegFkVHliSOqXsTVXPfIN9UX1BH1j2ffvPaqXhn4JCMt6j/n2h6I6Ei4IHwxiBgABAwfsYB7d+Nde97A/+NdMK+lNn97wXekPBNKa6a0hvQ1lPQfU1I3CSGZ0hpKa9iSISeMNU+urgk/+nItBt0T5pWoudcha7Wks3DfVSCfIivn7nZmL6t1zUgg0M8CB6iAiyULZ7Y9PGgG6l+sMRzJV3vEoYlBEwMRg/Z1w8q5u518M0K3aalZ/74CY7bkv//dippRg1ZAerjJY8bZHgEE3xUAsHL20lrMXlqLl+a2bdBCAejDaILgJjzJbhz000z4MCJE0Izl+woiIqKGl+a2ZQf2fXQN+XovDA4iwASRD+KDZiBp8w9CzD8woliiIqwQJMWUj2Wzlw50sJf+EvyEKZUuDSiMKpEoMpUnfbw+aAVGVEA+98DuMaZBN4UMPcfkqG5+qE2u/O3ugcDPtsMy2PAgp/qAiTsX/Hp3eNzFPH0++f8BWpzorRJD7KcAAAAASUVORK5CYII="
- },
- {
- "title": "Site Vivo",
- "uri": "http://www.vivo.com.br/conteudosmartphone",
- "iconUri": "data:image/jpg;base64,/9j/4AAQSkZJRgABAQEAlgCWAAD/2wBDAAUDBAQEAwUEBAQFBQUGBwwIBwcHBw8LCwkMEQ8SEhEPERETFhwXExQaFRERGCEYGh0dHx8fExciJCIeJBweHx7/2wBDAQUFBQcGBw4ICA4eFBEUHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh7/wAARCAAgACADASIAAhEBAxEB/8QAGgAAAgIDAAAAAAAAAAAAAAAABQYBAwQHCP/EAC4QAAEDAwIEBQIHAAAAAAAAAAECAwQFBhEAIQcSMUETFFFhcSKBMkKCkqGxwf/EABkBAAIDAQAAAAAAAAAAAAAAAAMGAAECBP/EACQRAAIBAwQCAgMAAAAAAAAAAAECEQMEEgAFITEiQVFxMmGh/9oADAMBAAIRAxEAPwDrG7rkg25AEiVlx1eQywk/U4f8A7nSXFrXEW4k+ZpMJiFEV+BxYSAfgqyT8gasmQBcPEQpmDnjNuFPIenI2OnwVf3ozOnSKnJ8rDeUhnPIyyyoJKwO59Btn2Gky53F7lncswQMVVVMFiOzPxpko0KVsiqEDORJLcgA9CPnQGbVOJdvtGXUIrE+IjdxTaUr5R6kJwoD3wQNN9lXVCuaEXGR4UlsAuMk52PRST3ToLBRNgywtp59pxDwbcaWvmC9wMehznY++sOi01qlcRZSYIDbHmikIT0AW0lah8cxOpZX9ak6sC2JYKysZImYIPB4jkau4pUbimwKgMBIKiAY7BHXvgjQldRXa/F+WmouLEOWQ6ypXQJUCDj9RP7dTSLHrsC6mpER1C6c49zpmMPAEtE53Gc5x6ZGn29LUpd1QEx56VNvNEqYkN7LaJ649QcDIOxwO4B0jxba4kW2S1RqjGqEXP0guBKvulY5QfcHJ0W82rB/KmXTLIY/kCYkR7BgaNbbgtWl4OEfEKQ3RA6IPo/erJ9PumJeTtRapbstIfUtsBWW1D8pOD22ODo9Zdt1RmsP1+vPDzTpJbjpVlKCeqj2zjYY6DQZUHilUh4cmQ1BQepEhKMfdsc3862HSWpLFMjMzHQ7IbbSlxYJIUoDc7763tW3UzcM5RwAchnAGR98cn9Trm3C7dKIQMhJGJxkmB/PvX//2Q=="
- }
- ]
-}
-
-writeContent(init, JSON.stringify(content));
-
-// Support
-init = getFile(GAIA_DIR, 'apps', 'settings', 'resources', 'support.json');
-content = {
- "onlinesupport": {
- "href": "http://www.vivo.com.br/portalweb/appmanager/env/web?_nfls=false&_nfpb=true&_pageLabel=vcAtendMovelBook&WT.ac=portal.atendimento.movel",
- "title": "Vivo"
- },
- "callsupport": [
- {
- "href": "tel:*8486",
- "title": "*8486"
- },
- {
- "href": "tel:1058",
- "title": "1058"
- }
- ]
-}
-writeContent(init, JSON.stringify(content));
-
-// ICC / STK
-init = getFile(GAIA_DIR, 'apps', 'settings', 'resources', 'icc.json');
-content = {
- "defaultURL": "http://www.mozilla.org/en-US/firefoxos/"
-}
-writeContent(init, JSON.stringify(content));
diff --git a/build/utils.js b/build/utils.js
index 5b7a58b..eef810e 100644
--- a/build/utils.js
+++ b/build/utils.js
@@ -168,22 +168,10 @@ function makeWebappsObject(dirs) {
};
}
-let externalAppsDirs = ['external-apps'];
-
-// External apps are built differently from other apps by webapp-manifests.js,
-// and we need apps that are both external and dogfood to be treated like
-// external apps (to properly test external apps on dogfood devices), so we
-// segregate them into their own directory that we add to the list of external
-// apps dirs here when building a dogfood profile.
-if (DOGFOOD === '1') {
- externalAppsDirs.push('external-dogfood-apps');
-}
-
const Gaia = {
engine: GAIA_ENGINE,
sharedFolder: getFile(GAIA_DIR, 'shared'),
webapps: makeWebappsObject(GAIA_APP_SRCDIRS),
- externalWebapps: makeWebappsObject(externalAppsDirs.join(' ')),
aggregatePrefix: 'gaia_build_'
};
diff --git a/build/webapp-manifests.js b/build/webapp-manifests.js
index 6bd79b9..b5090e4 100644
--- a/build/webapp-manifests.js
+++ b/build/webapp-manifests.js
@@ -86,90 +86,6 @@ Gaia.webapps.forEach(function (webapp) {
});
-// Process external webapps from /gaia/external-app/ folder
-Gaia.externalWebapps.forEach(function (webapp) {
- // If BUILD_APP_NAME isn't `*`, we only accept one webapp
- if (BUILD_APP_NAME != '*' && webapp.sourceDirectoryName != BUILD_APP_NAME)
- return;
-
- if (!webapp.metaData) {
- return;
- }
-
- // Compute webapp folder name in profile
- let webappTargetDirName = webapp.sourceDirectoryName;
-
- // Copy webapp's manifest to the profile
- let webappTargetDir = webappsTargetDir.clone();
- webappTargetDir.append(webappTargetDirName);
-
- let origin;
- let installOrigin;
- let manifestURL;
-
- let removable;
-
- // In case of packaged app, just copy `application.zip` and `update.webapp`
- let appPackage = webapp.sourceDirectoryFile.clone();
- appPackage.append('application.zip');
- if (appPackage.exists()) {
- let updateManifest = webapp.sourceDirectoryFile.clone();
- updateManifest.append('update.webapp');
- if (!updateManifest.exists()) {
- throw new Error('External packaged webapp `' + webapp.domain + ' is ' +
- 'missing an `update.webapp` file. This JSON file ' +
- 'contains a `package_path` attribute specifying where ' +
- 'to download the application zip package from the origin ' +
- 'specified in `metadata.json` file.');
- }
- appPackage.copyTo(webappTargetDir, 'application.zip');
- updateManifest.copyTo(webappTargetDir, 'update.webapp');
- removable = true;
- origin = webapp.metaData.origin;
- installOrigin = webapp.metaData.installOrigin;
- manifestURL = webapp.metaData.manifestURL;
- } else {
- webapp.manifestFile.copyTo(webappTargetDir, 'manifest.webapp');
- origin = webapp.metaData.origin;
- installOrigin = webapp.metaData.origin;
- manifestURL = webapp.metaData.origin + 'manifest.webapp';
-
- // This is an hosted app. Check if there is an offline cache.
- let srcCacheFolder = webapp.sourceDirectoryFile.clone();
- srcCacheFolder.append('cache');
- if (srcCacheFolder.exists()) {
- let cacheManifest = srcCacheFolder.clone();
- cacheManifest.append('manifest.appcache');
- if (!cacheManifest.exists())
- throw new Error('External webapp `' + webapp.domain + '` has a cache ' +
- 'directory without `manifest.appcache` file.');
-
- // Copy recursively the whole cache folder to webapp folder
- let targetCacheFolder = webappTargetDir.clone();
- targetCacheFolder.append('cache');
- copyRec(srcCacheFolder, targetCacheFolder);
- }
- }
-
- let etag = webapp.metaData.etag || null;
- let packageEtag = webapp.metaData.packageEtag || null;
-
- // Add webapp's entry to the webapps global manifest
- manifests[webappTargetDirName] = {
- origin: origin,
- installOrigin: installOrigin,
- receipt: null,
- installTime: 132333986000,
- manifestURL: manifestURL,
- removable: removable,
- localId: id++,
- etag: etag,
- packageEtag: packageEtag,
- appStatus: getAppStatus(webapp.metaData.type || "web"),
- };
-
-});
-
// Write webapps global manifest
let manifestFile = webappsTargetDir.clone();
manifestFile.append('webapps.json');
diff --git a/shared/js/async_storage.js b/shared/js/async_storage.js
new file mode 100644
index 0000000..6cca66d
--- /dev/null
+++ b/shared/js/async_storage.js
@@ -0,0 +1,187 @@
+/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- /
+/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
+
+'use strict';
+
+/**
+ * This file defines an asynchronous version of the localStorage API, backed by
+ * an IndexedDB database. It creates a global asyncStorage object that has
+ * methods like the localStorage object.
+ *
+ * To store a value use setItem:
+ *
+ * asyncStorage.setItem('key', 'value');
+ *
+ * If you want confirmation that the value has been stored, pass a callback
+ * function as the third argument:
+ *
+ * asyncStorage.setItem('key', 'newvalue', function() {
+ * console.log('new value stored');
+ * });
+ *
+ * To read a value, call getItem(), but note that you must supply a callback
+ * function that the value will be passed to asynchronously:
+ *
+ * asyncStorage.getItem('key', function(value) {
+ * console.log('The value of key is:', value);
+ * });
+ *
+ * Note that unlike localStorage, asyncStorage does not allow you to store and
+ * retrieve values by setting and querying properties directly. You cannot just
+ * write asyncStorage.key; you have to explicitly call setItem() or getItem().
+ *
+ * removeItem(), clear(), length(), and key() are like the same-named methods of
+ * localStorage, but, like getItem() and setItem() they take a callback
+ * argument.
+ *
+ * The asynchronous nature of getItem() makes it tricky to retrieve multiple
+ * values. But unlike localStorage, asyncStorage does not require the values you
+ * store to be strings. So if you need to save multiple values and want to
+ * retrieve them together, in a single asynchronous operation, just group the
+ * values into a single object. The properties of this object may not include
+ * DOM elements, but they may include things like Blobs and typed arrays.
+ *
+ * Unit tests are in apps/gallery/test/unit/asyncStorage_test.js
+ */
+
+this.asyncStorage = (function() {
+
+ var DBNAME = 'asyncStorage';
+ var DBVERSION = 1;
+ var STORENAME = 'keyvaluepairs';
+ var db = null;
+
+ function withStore(type, f) {
+ if (db) {
+ f(db.transaction(STORENAME, type).objectStore(STORENAME));
+ } else {
+ var openreq = indexedDB.open(DBNAME, DBVERSION);
+ openreq.onerror = function withStoreOnError() {
+ console.error("asyncStorage: can't open database:", openreq.error.name);
+ };
+ openreq.onupgradeneeded = function withStoreOnUpgradeNeeded() {
+ // First time setup: create an empty object store
+ openreq.result.createObjectStore(STORENAME);
+ };
+ openreq.onsuccess = function withStoreOnSuccess() {
+ db = openreq.result;
+ f(db.transaction(STORENAME, type).objectStore(STORENAME));
+ };
+ }
+ }
+
+ function getItem(key, callback) {
+ withStore('readonly', function getItemBody(store) {
+ var req = store.get(key);
+ req.onsuccess = function getItemOnSuccess() {
+ var value = req.result;
+ if (value === undefined)
+ value = null;
+ callback(value);
+ };
+ req.onerror = function getItemOnError() {
+ console.error('Error in asyncStorage.getItem(): ', req.error.name);
+ };
+ });
+ }
+
+ function setItem(key, value, callback) {
+ withStore('readwrite', function setItemBody(store) {
+ var req = store.put(value, key);
+ if (callback) {
+ req.onsuccess = function setItemOnSuccess() {
+ callback();
+ };
+ }
+ req.onerror = function setItemOnError() {
+ console.error('Error in asyncStorage.setItem(): ', req.error.name);
+ };
+ });
+ }
+
+ function removeItem(key, callback) {
+ withStore('readwrite', function removeItemBody(store) {
+ var req = store.delete(key);
+ if (callback) {
+ req.onsuccess = function removeItemOnSuccess() {
+ callback();
+ };
+ }
+ req.onerror = function removeItemOnError() {
+ console.error('Error in asyncStorage.removeItem(): ', req.error.name);
+ };
+ });
+ }
+
+ function clear(callback) {
+ withStore('readwrite', function clearBody(store) {
+ var req = store.clear();
+ if (callback) {
+ req.onsuccess = function clearOnSuccess() {
+ callback();
+ };
+ }
+ req.onerror = function clearOnError() {
+ console.error('Error in asyncStorage.clear(): ', req.error.name);
+ };
+ });
+ }
+
+ function length(callback) {
+ withStore('readonly', function lengthBody(store) {
+ var req = store.count();
+ req.onsuccess = function lengthOnSuccess() {
+ callback(req.result);
+ };
+ req.onerror = function lengthOnError() {
+ console.error('Error in asyncStorage.length(): ', req.error.name);
+ };
+ });
+ }
+
+ function key(n, callback) {
+ if (n < 0) {
+ callback(null);
+ return;
+ }
+
+ withStore('readonly', function keyBody(store) {
+ var advanced = false;
+ var req = store.openCursor();
+ req.onsuccess = function keyOnSuccess() {
+ var cursor = req.result;
+ if (!cursor) {
+ // this means there weren't enough keys
+ callback(null);
+ return;
+ }
+ if (n === 0) {
+ // We have the first key, return it if that's what they wanted
+ callback(cursor.key);
+ } else {
+ if (!advanced) {
+ // Otherwise, ask the cursor to skip ahead n records
+ advanced = true;
+ cursor.advance(n);
+ } else {
+ // When we get here, we've got the nth key.
+ callback(cursor.key);
+ }
+ }
+ };
+ req.onerror = function keyOnError() {
+ console.error('Error in asyncStorage.key(): ', req.error.name);
+ };
+ });
+ }
+
+ return {
+ getItem: getItem,
+ setItem: setItem,
+ removeItem: removeItem,
+ clear: clear,
+ length: length,
+ key: key
+ };
+}());
+
diff --git a/shared/js/blobview.js b/shared/js/blobview.js
new file mode 100644
index 0000000..eda519a
--- /dev/null
+++ b/shared/js/blobview.js
@@ -0,0 +1,412 @@
+'use strict';
+
+var BlobView = (function() {
+ function fail(msg) {
+ throw Error(msg);
+ }
+
+ // This constructor is for internal use only.
+ // Use the BlobView.get() factory function or the getMore instance method
+ // to obtain a BlobView object.
+ function BlobView(blob, sliceOffset, sliceLength, slice,
+ viewOffset, viewLength, littleEndian)
+ {
+ this.blob = blob; // The parent blob that the data is from
+ this.sliceOffset = sliceOffset; // The start address within the blob
+ this.sliceLength = sliceLength; // How long the slice is
+ this.slice = slice; // The ArrayBuffer of slice data
+ this.viewOffset = viewOffset; // The start of the view within the slice
+ this.viewLength = viewLength; // The length of the view
+ this.littleEndian = littleEndian; // Read little endian by default?
+
+ // DataView wrapper around the ArrayBuffer
+ this.view = new DataView(slice, viewOffset, viewLength);
+
+ // These fields mirror those of DataView
+ this.buffer = slice;
+ this.byteLength = viewLength;
+ this.byteOffset = viewOffset;
+
+ this.index = 0; // The read methods keep track of the read position
+ }
+
+ // Async factory function
+ BlobView.get = function(blob, offset, length, callback, littleEndian) {
+ if (offset < 0)
+ fail('negative offset');
+ if (length < 0)
+ fail('negative length');
+ if (offset > blob.size)
+ fail('offset larger than blob size');
+
+ // Don't fail if the length is too big; just reduce the length
+ if (offset + length > blob.size)
+ length = blob.size - offset;
+
+ var slice = blob.slice(offset, offset + length);
+ var reader = new FileReader();
+ reader.readAsArrayBuffer(slice);
+ reader.onloadend = function() {
+ var result = null;
+ if (reader.result) {
+ result = new BlobView(blob, offset, length, reader.result,
+ 0, length, littleEndian || false);
+ }
+ callback(result, reader.error);
+ }
+ };
+
+ BlobView.prototype = {
+ constructor: BlobView,
+
+ // This instance method is like the BlobView.get() factory method,
+ // but it is here because if the current buffer includes the requested
+ // range of bytes, they can be passed directly to the callback without
+ // going back to the blob to read them
+ getMore: function(offset, length, callback) {
+ if (offset >= this.sliceOffset &&
+ offset + length <= this.sliceOffset + this.sliceLength) {
+ // The quick case: we already have that region of the blob
+ callback(new BlobView(this.blob,
+ this.sliceOffset, this.sliceLength, this.slice,
+ offset - this.sliceOffset, length,
+ this.littleEndian));
+ }
+ else {
+ // Otherwise, we have to do an async read to get more bytes
+ BlobView.get(this.blob, offset, length, callback, this.littleEndian);
+ }
+ },
+
+ // Set the default endianness for the other methods
+ littleEndian: function() {
+ this.littleEndian = true;
+ },
+ bigEndian: function() {
+ this.littleEndian = false;
+ },
+
+ // These "get" methods are just copies of the DataView methods, except
+ // that they honor the default endianness
+ getUint8: function(offset) {
+ return this.view.getUint8(offset);
+ },
+ getInt8: function(offset) {
+ return this.view.getInt8(offset);
+ },
+ getUint16: function(offset, le) {
+ return this.view.getUint16(offset,
+ le !== undefined ? le : this.littleEndian);
+ },
+ getInt16: function(offset, le) {
+ return this.view.getInt16(offset,
+ le !== undefined ? le : this.littleEndian);
+ },
+ getUint32: function(offset, le) {
+ return this.view.getUint32(offset,
+ le !== undefined ? le : this.littleEndian);
+ },
+ getInt32: function(offset, le) {
+ return this.view.getInt32(offset,
+ le !== undefined ? le : this.littleEndian);
+ },
+ getFloat32: function(offset, le) {
+ return this.view.getFloat32(offset,
+ le !== undefined ? le : this.littleEndian);
+ },
+ getFloat64: function(offset, le) {
+ return this.view.getFloat64(offset,
+ le !== undefined ? le : this.littleEndian);
+ },
+
+ // These "read" methods read from the current position in the view and
+ // update that position accordingly
+ readByte: function() {
+ return this.view.getInt8(this.index++);
+ },
+ readUnsignedByte: function() {
+ return this.view.getUint8(this.index++);
+ },
+ readShort: function(le) {
+ var val = this.view.getInt16(this.index,
+ le !== undefined ? le : this.littleEndian);
+ this.index += 2;
+ return val;
+ },
+ readUnsignedShort: function(le) {
+ var val = this.view.getUint16(this.index,
+ le !== undefined ? le : this.littleEndian);
+ this.index += 2;
+ return val;
+ },
+ readInt: function(le) {
+ var val = this.view.getInt32(this.index,
+ le !== undefined ? le : this.littleEndian);
+ this.index += 4;
+ return val;
+ },
+ readUnsignedInt: function(le) {
+ var val = this.view.getUint32(this.index,
+ le !== undefined ? le : this.littleEndian);
+ this.index += 4;
+ return val;
+ },
+ readFloat: function(le) {
+ var val = this.view.getFloat32(this.index,
+ le !== undefined ? le : this.littleEndian);
+ this.index += 4;
+ return val;
+ },
+ readDouble: function(le) {
+ var val = this.view.getFloat64(this.index,
+ le !== undefined ? le : this.littleEndian);
+ this.index += 8;
+ return val;
+ },
+
+ // Methods to get and set the current position
+ tell: function() {
+ return this.index;
+ },
+ seek: function(index) {
+ if (index < 0)
+ fail('negative index');
+ if (index >= this.byteLength)
+ fail('index greater than buffer size');
+ this.index = index;
+ },
+ advance: function(n) {
+ var index = this.index + n;
+ if (index < 0)
+ fail('advance past beginning of buffer');
+ if (index >= this.byteLength)
+ fail('advance past end of buffer');
+ this.index = index;
+ },
+
+ // Additional methods to read other useful things
+ getUnsignedByteArray: function(offset, n) {
+ return new Uint8Array(this.buffer, offset + this.viewOffset, n);
+ },
+
+ // Additional methods to read other useful things
+ readUnsignedByteArray: function(n) {
+ var val = new Uint8Array(this.buffer, this.index + this.viewOffset, n);
+ this.index += n;
+ return val;
+ },
+
+ getBit: function(offset, bit) {
+ var byte = this.view.getUint8(offset);
+ return (byte & (1 << bit)) !== 0;
+ },
+
+ getUint24: function(offset, le) {
+ var b1, b2, b3;
+ if (le !== undefined ? le : this.littleEndian) {
+ b1 = this.view.getUint8(offset);
+ b2 = this.view.getUint8(offset + 1);
+ b3 = this.view.getUint8(offset + 2);
+ }
+ else { // big end first
+ b3 = this.view.getUint8(offset);
+ b2 = this.view.getUint8(offset + 1);
+ b1 = this.view.getUint8(offset + 2);
+ }
+
+ return (b3 << 16) + (b2 << 8) + b1;
+ },
+
+ readUint24: function(le) {
+ var value = this.getUint24(this.index, le);
+ this.index += 3;
+ return value;
+ },
+
+ // There are lots of ways to read strings.
+ // ASCII, UTF-8, UTF-16.
+ // null-terminated, character length, byte length
+ // I'll implement string reading methods as needed
+
+ getASCIIText: function(offset, len) {
+ var bytes = new Uint8Array(this.buffer, offset + this.viewOffset, len);
+ return String.fromCharCode.apply(String, bytes);
+ },
+
+ readASCIIText: function(len) {
+ var bytes = new Uint8Array(this.buffer,
+ this.index + this.viewOffset,
+ len);
+ this.index += len;
+ return String.fromCharCode.apply(String, bytes);
+ },
+
+ // Replace this with the StringEncoding API when we've got it.
+ // See https://bugzilla.mozilla.org/show_bug.cgi?id=764234
+ getUTF8Text: function(offset, len) {
+ function fail() { throw new Error('Illegal UTF-8'); }
+
+ var pos = offset; // Current position in this.view
+ var end = offset + len; // Last position
+ var charcode; // Current charcode
+ var s = ''; // Accumulate the string
+ var b1, b2, b3, b4; // Up to 4 bytes per charcode
+
+ // See http://en.wikipedia.org/wiki/UTF-8
+ while (pos < end) {
+ var b1 = this.view.getUint8(pos);
+ if (b1 < 128) {
+ s += String.fromCharCode(b1);
+ pos += 1;
+ }
+ else if (b1 < 194) {
+ // unexpected continuation character...
+ fail();
+ }
+ else if (b1 < 224) {
+ // 2-byte sequence
+ if (pos + 1 >= end)
+ fail();
+ b2 = this.view.getUint8(pos + 1);
+ if (b2 < 128 || b2 > 191)
+ fail();
+ charcode = ((b1 & 0x1f) << 6) + (b2 & 0x3f);
+ s += String.fromCharCode(charcode);
+ pos += 2;
+ }
+ else if (b1 < 240) {
+ // 3-byte sequence
+ if (pos + 3 >= end)
+ fail();
+ b2 = this.view.getUint8(pos + 1);
+ if (b2 < 128 || b2 > 191)
+ fail();
+ b3 = this.view.getUint8(pos + 2);
+ if (b3 < 128 || b3 > 191)
+ fail();
+ charcode = ((b1 & 0x0f) << 12) + ((b2 & 0x3f) << 6) + (b3 & 0x3f);
+ s += String.fromCharCode(charcode);
+ pos += 3;
+ }
+ else if (b1 < 245) {
+ // 4-byte sequence
+ if (pos + 3 >= end)
+ fail();
+ b2 = this.view.getUint8(pos + 1);
+ if (b2 < 128 || b2 > 191)
+ fail();
+ b3 = this.view.getUint8(pos + 2);
+ if (b3 < 128 || b3 > 191)
+ fail();
+ b4 = this.view.getUint8(pos + 3);
+ if (b4 < 128 || b4 > 191)
+ fail();
+ charcode = ((b1 & 0x07) << 18) +
+ ((b2 & 0x3f) << 12) +
+ ((b3 & 0x3f) << 6) +
+ (b4 & 0x3f);
+
+ // Now turn this code point into two surrogate pairs
+ charcode -= 0x10000;
+ s += String.fromCharCode(0xd800 + ((charcode & 0x0FFC00) >>> 10));
+ s += String.fromCharCode(0xdc00 + (charcode & 0x0003FF));
+
+ pos += 4;
+ }
+ else {
+ // Illegal byte
+ fail();
+ }
+ }
+
+ return s;
+ },
+
+ readUTF8Text: function(len) {
+ try {
+ return this.getUTF8Text(this.index, len);
+ }
+ finally {
+ this.index += len;
+ }
+ },
+
+ // Read 4 bytes, ignore the high bit and combine them into a 28-bit
+ // big-endian unsigned integer.
+ // This format is used by the ID3v2 metadata.
+ getID3Uint28BE: function(offset) {
+ var b1 = this.view.getUint8(offset) & 0x7f;
+ var b2 = this.view.getUint8(offset + 1) & 0x7f;
+ var b3 = this.view.getUint8(offset + 2) & 0x7f;
+ var b4 = this.view.getUint8(offset + 3) & 0x7f;
+ return (b1 << 21) | (b2 << 14) | (b3 << 7) | b4;
+ },
+
+ readID3Uint28BE: function() {
+ var value = this.getID3Uint28BE(this.index);
+ this.index += 4;
+ return value;
+ },
+
+ // Read bytes up to and including a null terminator, but never
+ // more than size bytes. And return as a Latin1 string
+ readNullTerminatedLatin1Text: function(size) {
+ var s = '';
+ for (var i = 0; i < size; i++) {
+ var charcode = this.view.getUint8(this.index + i);
+ if (charcode === 0) {
+ i++;
+ break;
+ }
+ s += String.fromCharCode(charcode);
+ }
+ this.index += i;
+ return s;
+ },
+
+ // Read bytes up to and including a null terminator, but never
+ // more than size bytes. And return as a UTF8 string
+ readNullTerminatedUTF8Text: function(size) {
+ for (var len = 0; len < size; len++) {
+ if (this.view.getUint8(this.index + len) === 0)
+ break;
+ }
+ var s = this.readUTF8Text(len);
+ if (len < size) // skip the null terminator if we found one
+ this.advance(1);
+ return s;
+ },
+
+ // Read UTF16 text. If le is not specified, expect a BOM to define
+ // endianness. If le is true, read UTF16LE, if false, UTF16BE
+ // Read until we find a null-terminator, but never more than size bytes
+ readNullTerminatedUTF16Text: function(size, le) {
+ if (le == null) {
+ var BOM = this.readUnsignedShort();
+ size -= 2;
+ if (BOM === 0xFEFF)
+ le = false;
+ else
+ le = true;
+ }
+
+ var s = '';
+ for (var i = 0; i < size; i += 2) {
+ var charcode = this.getUint16(this.index + i, le);
+ if (charcode === 0) {
+ i += 2;
+ break;
+ }
+ s += String.fromCharCode(charcode);
+ }
+ this.index += i;
+ return s;
+ }
+ };
+
+ // We don't want users of this library to accidentally call the constructor
+ // instead of using the factory function, so we return a dummy object
+ // instead of the real constructor. If someone really needs to get at the
+ // real constructor, the contructor property of the prototype refers to it.
+ return { get: BlobView.get };
+}());
diff --git a/shared/js/custom_dialog.js b/shared/js/custom_dialog.js
new file mode 100644
index 0000000..f4fa772
--- /dev/null
+++ b/shared/js/custom_dialog.js
@@ -0,0 +1,112 @@
+//XXX: Waiting for the window.showModalDialog support in B2G
+
+'use strict';
+
+var CustomDialog = (function() {
+
+ var screen = null;
+ var dialog = null;
+ var header = null;
+ var message = null;
+ var yes = null;
+ var no = null;
+
+ return {
+ hide: function dialog_hide() {
+ if (screen === null)
+ return;
+
+ document.body.removeChild(screen);
+ screen = null;
+ dialog = null;
+ header = null;
+ message = null;
+ yes = null;
+ no = null;
+ },
+
+ /**
+ * Method that shows the dialog
+ * @param {String} title the title of the dialog. null or empty for
+ * no title.
+ * @param {String} msg message for the dialog.
+ * @param {Object} cancel {title, callback} object when confirm.
+ * @param {Object} confirm {title, callback} object when cancel.
+ */
+ show: function dialog_show(title, msg, cancel, confirm) {
+ if (screen === null) {
+ screen = document.createElement('section');
+ screen.setAttribute('role', 'region');
+ screen.id = 'dialog-screen';
+
+ dialog = document.createElement('div');
+ dialog.id = 'dialog-dialog';
+ dialog.setAttribute('role', 'dialog');
+ screen.appendChild(dialog);
+
+ var info = document.createElement('div');
+ info.className = 'inner';
+
+ if (title && title != '') {
+ header = document.createElement('h3');
+ header.id = 'dialog-title';
+ header.textContent = title;
+ info.appendChild(header);
+ }
+
+ message = document.createElement('p');
+ message.id = 'dialog-message';
+ info.appendChild(message);
+ dialog.appendChild(info);
+
+ var menu = document.createElement('menu');
+ menu.dataset['items'] = 1;
+
+ no = document.createElement('button');
+ var noText = document.createTextNode(cancel.title);
+ no.appendChild(noText);
+ no.id = 'dialog-no';
+ no.addEventListener('click', clickHandler);
+ menu.appendChild(no);
+
+ if (confirm) {
+ menu.dataset['items'] = 2;
+ yes = document.createElement('button');
+ var yesText = document.createTextNode(confirm.title);
+ yes.appendChild(yesText);
+ yes.id = 'dialog-yes';
+ yes.className = 'negative';
+ yes.addEventListener('click', clickHandler);
+ menu.appendChild(yes);
+ }
+
+ dialog.appendChild(menu);
+
+ document.body.appendChild(screen);
+ }
+
+ // Put the message in the dialog.
+ // Note plain text since this may include text from
+ // untrusted app manifests, for example.
+ message.textContent = msg;
+
+ // Make the screen visible
+ screen.classList.add('visible');
+
+ // This is the event listener function for the buttons
+ function clickHandler(evt) {
+
+ // Hide the dialog
+ screen.classList.remove('visible');
+
+ // Call the appropriate callback, if it is defined
+ if (evt.target === yes && confirm.callback) {
+ confirm.callback();
+ } else if (evt.target === no && cancel.callback) {
+ cancel.callback();
+ }
+ }
+ }
+ };
+}());
+
diff --git a/shared/js/desktop.js b/shared/js/desktop.js
new file mode 100644
index 0000000..af0294a
--- /dev/null
+++ b/shared/js/desktop.js
@@ -0,0 +1,115 @@
+/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- /
+/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
+
+'use strict';
+
+/**
+ * This library should help debugging Gaia on a desktop browser, where APIs like
+ * mozTelephony or mozApps are not supported.
+ */
+
+// navigator.mozTelephony
+(function(window) {
+ var navigator = window.navigator;
+ if ('mozTelephony' in navigator)
+ return;
+
+ var TelephonyCalls = [];
+ if (typeof(RecentsDBManager) != 'undefined' && RecentsDBManager) {
+ RecentsDBManager.init(function() {
+ RecentsDBManager.prepopulateDB(function() {
+ RecentsDBManager.close();
+ });
+ });
+ }
+ navigator.mozTelephony = {
+ dial: function(number) {
+ var TelephonyCall = {
+ number: number,
+ state: 'dialing',
+ addEventListener: function() {},
+ hangUp: function() {},
+ removeEventListener: function() {}
+ };
+
+ TelephonyCalls.push(TelephonyCall);
+
+ return TelephonyCall;
+ },
+ addEventListener: function(name, handler) {
+ },
+ get calls() {
+ return TelephonyCalls;
+ },
+ muted: false,
+ speakerEnabled: false,
+
+ // Stubs
+ onincoming: null,
+ oncallschanged: null
+ };
+})(this);
+
+// Emulate device buttons. This is groteskly unsafe and should be removed soon.
+(function(window) {
+ var supportedEvents = { keydown: true, keyup: true };
+ var listeners = [];
+
+ var originalAddEventListener = window.addEventListener;
+ window.addEventListener = function(type, listener, capture) {
+ if (this === window && supportedEvents[type]) {
+ listeners.push({ type: type, listener: listener, capture: capture });
+ }
+ originalAddEventListener.call(this, type, listener, capture);
+ };
+
+ var originalRemoveEventListener = window.removeEventListener;
+ window.removeEventListener = function(type, listener) {
+ if (this === window && supportedEvents[type]) {
+ var newListeners = [];
+ for (var n = 0; n < listeners.length; ++n) {
+ if (listeners[n].type == type && listeners[n].listener == listener)
+ continue;
+ newListeners.push(listeners[n]);
+ }
+ listeners = newListeners;
+ }
+ originalRemoveEventListener.call(this, type, listener);
+ };
+
+ var KeyEventProto = {
+ DOM_VK_HOME: 36
+ };
+
+ window.addEventListener('message', function(event) {
+ var data = event.data;
+ if (typeof data === 'string' && data.indexOf('moz-key-') == 0) {
+ var type, key;
+ if (data.indexOf('moz-key-down-') == 0) {
+ type = 'keydown';
+ key = data.substr(13);
+ } else if (data.indexOf('moz-key-up-') == 0) {
+ type = 'keyup';
+ key = data.substr(11);
+ } else {
+ return;
+ }
+ key = KeyEvent[key];
+ for (var n = 0; n < listeners.length; ++n) {
+ if (listeners[n].type == type) {
+ var fn = listeners[n].listener;
+ var e = Object.create(KeyEventProto);
+ e.type = type;
+ e.keyCode = key;
+ if (typeof fn === 'function')
+ fn(e);
+ else if (typeof fn === 'object' && fn.handleEvent)
+ fn.handleEvent(e);
+ if (listeners[n].capture)
+ return;
+ }
+ }
+ }
+ });
+})(this);
+
diff --git a/shared/js/gesture_detector.js b/shared/js/gesture_detector.js
new file mode 100644
index 0000000..61e2759
--- /dev/null
+++ b/shared/js/gesture_detector.js
@@ -0,0 +1,891 @@
+/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- /
+/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
+
+'use strict';
+
+/**
+ * GestureDetector.js: generate events for one and two finger gestures.
+ *
+ * A GestureDetector object listens for touch and mouse events on a specified
+ * element and generates higher-level events that describe one and two finger
+ * gestures on the element. The hope is that this will be useful for webapps
+ * that need to run on mouse (or trackpad)-based desktop browsers and also in
+ * touch-based mobile devices.
+ *
+ * Supported events:
+ *
+ * tap like a click event
+ * dbltap like dblclick
+ * pan one finger motion, or mousedown followed by mousemove
+ * swipe when a finger is released following pan events
+ * holdstart touch (or mousedown) and hold. Must set an option to get these.
+ * holdmove motion after a holdstart event
+ * holdend when the finger or mouse goes up after holdstart/holdmove
+ * transform 2-finger pinch and twist gestures for scaling and rotation
+ * These are touch-only; they can't be simulated with a mouse.
+ *
+ * Each of these events is a bubbling CustomEvent with important details in the
+ * event.detail field. The event details are not yet stable and are not yet
+ * documented. See the calls to emitEvent() for details.
+ *
+ * To use this library, create a GestureDetector object by passing an element to
+ * the GestureDetector() constructor and then calling startDetecting() on it.
+ * The element will be the target of all the emitted gesture events. You can
+ * also pass an optional object as the second constructor argument. If you're
+ * interested in holdstart/holdmove/holdend events, pass {holdEvents:true} as
+ * this second argument. Otherwise they will not be generated.
+ *
+ * Implementation note: event processing is done with a simple finite-state
+ * machine. This means that in general, the various kinds of gestures are
+ * mutually exclusive. You won't get pan events until your finger or mouse has
+ * moved more than a minimum threshold, for example, but it does, the FSM enters
+ * a new state in which it can emit pan and swipe events and cannot emit hold
+ * events. Similarly, if you've started a 1 finger pan/swipe gesture and
+ * accidentally touch with a second finger, you'll continue to get pan events,
+ * and won't suddenly start getting 2-finger transform events.
+ *
+ * This library never calls preventDefault() or stopPropagation on any of the
+ * events it processes, so the raw touch or mouse events should still be
+ * available for other code to process. It is not clear to me whether this is a
+ * feature or a bug.
+ */
+
+var GestureDetector = (function() {
+
+ //
+ // Constructor
+ //
+ function GD(e, options) {
+ this.element = e;
+ this.options = options || {};
+ this.state = initialState;
+ this.timers = {};
+ this.listeningForMouseEvents = true;
+ }
+
+ //
+ // Public methods
+ //
+
+ GD.prototype.startDetecting = function() {
+ var self = this;
+ eventtypes.forEach(function(t) {
+ self.element.addEventListener(t, self);
+ });
+ };
+
+ GD.prototype.stopDetecting = function() {
+ var self = this;
+ eventtypes.forEach(function(t) {
+ self.element.removeEventListener(t, self);
+ });
+ };
+
+ //
+ // Internal methods
+ //
+
+ GD.prototype.handleEvent = function(e) {
+ var handler = this.state[e.type];
+ if (!handler) return;
+
+ // If this is a touch event handle each changed touch separately
+ if (e.changedTouches) {
+ // If we ever receive a touch event, then we know we are on a
+ // touch device and we stop listening for mouse events. If we
+ // don't do that, then the touchstart touchend mousedown mouseup
+ // generated by a single tap gesture will cause us to output
+ // tap tap dbltap, which is wrong
+ if (this.listeningForMouseEvents) {
+ this.listeningForMouseEvents = false;
+ this.element.removeEventListener('mousedown', this);
+ }
+
+ // XXX https://bugzilla.mozilla.org/show_bug.cgi?id=785554
+ // causes touchend events to list all touches as changed, so
+ // warn if we see that bug
+ if (e.type === 'touchend' && e.changedTouches.length > 1) {
+ console.warn('gesture_detector.js: spurious extra changed touch on ' +
+ 'touchend. See ' +
+ 'https://bugzilla.mozilla.org/show_bug.cgi?id=785554');
+ }
+
+ for (var i = 0; i < e.changedTouches.length; i++) {
+ handler(this, e, e.changedTouches[i]);
+ // The first changed touch might have changed the state of the
+ // FSM. We need this line to workaround the bug 785554, but it is
+ // probably the right thing to have here, even once that bug is fixed.
+ handler = this.state[e.type];
+ }
+ }
+ else { // Otherwise, just dispatch the event to the handler
+ handler(this, e);
+ }
+ };
+
+ GD.prototype.startTimer = function(type, time) {
+ this.clearTimer(type);
+ var self = this;
+ this.timers[type] = setTimeout(function() {
+ self.timers[type] = null;
+ var handler = self.state[type];
+ if (handler)
+ handler(self, type);
+ }, time);
+ };
+
+ GD.prototype.clearTimer = function(type) {
+ if (this.timers[type]) {
+ clearTimeout(this.timers[type]);
+ this.timers[type] = null;
+ }
+ };
+
+ // Switch to a new FSM state, and call the init() function of that
+ // state, if it has one. The event and touch arguments are optional
+ // and are just passed through to the state init function.
+ GD.prototype.switchTo = function(state, event, touch) {
+ this.state = state;
+ if (state.init)
+ state.init(this, event, touch);
+ };
+
+ GD.prototype.emitEvent = function(type, detail) {
+ if (!this.target) {
+ console.error('Attempt to emit event with no target');
+ return;
+ }
+
+ var event = this.element.ownerDocument.createEvent('CustomEvent');
+ event.initCustomEvent(type, true, true, detail);
+ this.target.dispatchEvent(event);
+ }
+
+ //
+ // Tuneable parameters
+ //
+ GD.HOLD_INTERVAL = 1000; // Hold events after 1000 ms
+ GD.PAN_THRESHOLD = 20; // 20 pixels movement before touch panning
+ GD.MOUSE_PAN_THRESHOLD = 15; // Mice are more precise, so smaller threshold
+ GD.DOUBLE_TAP_DISTANCE = 50;
+ GD.DOUBLE_TAP_TIME = 500;
+ GD.VELOCITY_SMOOTHING = .5;
+
+ // Don't start sending transform events until the gesture exceeds a threshold
+ GD.SCALE_THRESHOLD = 20; // pixels
+ GD.ROTATE_THRESHOLD = 22.5; // degrees
+
+ // For pans and zooms, we compute new starting coordinates that are part way
+ // between the initial event and the event that crossed the threshold so that
+ // the first event we send doesn't cause a big lurch. This constant must be
+ // between 0 and 1 and says how far along the line between the initial value
+ // and the new value we pick
+ GD.THRESHOLD_SMOOTHING = 0.9;
+
+ //
+ // Helpful shortcuts and utility functions
+ //
+
+ var abs = Math.abs, floor = Math.floor, sqrt = Math.sqrt, atan2 = Math.atan2;
+ var PI = Math.PI;
+
+ // The names of events that we need to register handlers for
+ var eventtypes = [
+ 'touchstart',
+ 'touchmove',
+ 'touchend',
+ 'mousedown' // We register mousemove and mouseup manually
+ ];
+
+ // Return the event's timestamp in ms
+ function eventTime(e) {
+ // In gecko, synthetic events seem to be in microseconds rather than ms.
+ // So if the timestamp is much larger than the current time, assue it is
+ // in microseconds and divide by 1000
+ var ts = e.timeStamp;
+ if (ts > 2 * Date.now())
+ return Math.floor(ts / 1000);
+ else
+ return ts;
+ }
+
+
+ // Return an object containg the space and time coordinates of
+ // and event and touch. We freeze the object to make it immutable so
+ // we can pass it in events and not worry about values being changed.
+ function coordinates(e, t) {
+ return Object.freeze({
+ screenX: t.screenX,
+ screenY: t.screenY,
+ clientX: t.clientX,
+ clientY: t.clientY,
+ timeStamp: eventTime(e)
+ });
+ }
+
+ // Like coordinates(), but return the midpoint between two touches
+ function midpoints(e, t1, t2) {
+ return Object.freeze({
+ screenX: floor((t1.screenX + t2.screenX) / 2),
+ screenY: floor((t1.screenY + t2.screenY) / 2),
+ clientX: floor((t1.clientX + t2.clientX) / 2),
+ clientY: floor((t1.clientY + t2.clientY) / 2),
+ timeStamp: eventTime(e)
+ });
+ }
+
+ // Like coordinates(), but for a mouse event
+ function mouseCoordinates(e) {
+ return Object.freeze({
+ screenX: e.screenX,
+ screenY: e.screenY,
+ clientX: e.clientX,
+ clientY: e.clientY,
+ timeStamp: eventTime(e)
+ });
+ }
+
+ // Given coordinates objects c1 and c2, return a new coordinates object
+ // representing a point and time along the line between those points.
+ // The position of the point is controlled by the THRESHOLD_SMOOTHING constant
+ function between(c1, c2) {
+ var r = GD.THRESHOLD_SMOOTHING;
+ return Object.freeze({
+ screenX: floor(c1.screenX + r * (c2.screenX - c1.screenX)),
+ screenY: floor(c1.screenY + r * (c2.screenY - c1.screenY)),
+ clientX: floor(c1.clientX + r * (c2.clientX - c1.clientX)),
+ clientY: floor(c1.clientY + r * (c2.clientY - c1.clientY)),
+ timeStamp: floor(c1.timeStamp + r * (c2.timeStamp - c1.timeStamp))
+ });
+ }
+
+ // Compute the distance between two touches
+ function touchDistance(t1, t2) {
+ var dx = t2.screenX - t1.screenX;
+ var dy = t2.screenY - t1.screenY;
+ return sqrt(dx * dx + dy * dy);
+ }
+
+ // Compute the direction (as an angle) of the line between two touches
+ // Returns a number d, -180 < d <= 180
+ function touchDirection(t1, t2) {
+ return atan2(t2.screenY - t1.screenY,
+ t2.screenX - t1.screenX) * 180 / PI;
+ }
+
+ // Compute the clockwise angle between direction d1 and direction d2.
+ // Returns an angle a -180 < a <= 180.
+ function touchRotation(d1, d2) {
+ var angle = d2 - d1;
+ if (angle > 180)
+ angle -= 360;
+ else if (angle <= -180)
+ angle += 360;
+ return angle;
+ }
+
+ // Determine if two taps are close enough in time and space to
+ // trigger a dbltap event. The arguments are objects returned
+ // by the coordinates() function.
+ function isDoubleTap(lastTap, thisTap) {
+ var dx = abs(thisTap.screenX - lastTap.screenX);
+ var dy = abs(thisTap.screenY - lastTap.screenY);
+ var dt = thisTap.timeStamp - lastTap.timeStamp;
+ return (dx < GD.DOUBLE_TAP_DISTANCE &&
+ dy < GD.DOUBLE_TAP_DISTANCE &&
+ dt < GD.DOUBLE_TAP_TIME);
+ }
+
+ //
+ // The following objects are the states of our Finite State Machine
+ //
+
+ // In this state we're not processing any gestures, just waiting
+ // for an event to start a gesture and ignoring others
+ var initialState = {
+ name: 'initialState',
+ init: function(d) {
+ // When we enter or return to the initial state, clear
+ // the detector properties that were tracking gestures
+ // Don't clear d.lastTap here, though. We need it for dbltap events
+ d.target = null;
+ d.start = d.last = null;
+ d.touch1 = d.touch2 = null;
+ d.vx = d.vy = null;
+ d.startDistance = d.lastDistance = null;
+ d.startDirection = d.lastDirection = null;
+ d.lastMidpoint = null;
+ d.scaled = d.rotated = null;
+ },
+
+ // Switch to the touchstarted state and process the touch event there
+ // Once we've started processing a touch gesture we'll ignore mouse events
+ touchstart: function(d, e, t) {
+ d.switchTo(touchStartedState, e, t);
+ },
+
+ // Or if we see a mouse event first, then start processing a mouse-based
+ // gesture, and ignore any touch events
+ mousedown: function(d, e) {
+ d.switchTo(mouseDownState, e);
+ }
+ };
+
+ // One finger is down but we haven't generated any event yet. We're
+ // waiting to see... If the finger goes up soon, its a tap. If the finger
+ // stays down and still, its a hold. If the finger moves its a pan/swipe.
+ // And if a second finger goes down, its a transform
+ var touchStartedState = {
+ name: 'touchStartedState',
+ init: function(d, e, t) {
+ // Remember the target of the event
+ d.target = e.target;
+ // Remember the id of the touch that started
+ d.touch1 = t.identifier;
+ // Get the coordinates of the touch
+ d.start = d.last = coordinates(e, t);
+ // Start a timer for a hold
+ // If we're doing hold events, start a timer for them
+ if (d.options.holdEvents)
+ d.startTimer('holdtimeout', GD.HOLD_INTERVAL);
+ },
+
+ touchstart: function(d, e, t) {
+ // If another finger goes down in this state, then
+ // go to transform state to start 2-finger gestures.
+ d.clearTimer('holdtimeout');
+ d.switchTo(transformState, e, t);
+ },
+ touchmove: function(d, e, t) {
+ // Ignore any touches but the initial one
+ // This could happen if there was still a finger down after
+ // the end of a previous 2-finger gesture, e.g.
+ if (t.identifier !== d.touch1)
+ return;
+
+ if (abs(t.screenX - d.start.screenX) > GD.PAN_THRESHOLD ||
+ abs(t.screenY - d.start.screenY) > GD.PAN_THRESHOLD) {
+ d.clearTimer('holdtimeout');
+ d.switchTo(panStartedState, e, t);
+ }
+ },
+ touchend: function(d, e, t) {
+ // Ignore any touches but the initial one
+ if (t.identifier !== d.touch1)
+ return;
+
+ // If there was a previous tap that was close enough in time
+ // and space, then emit a 'dbltap' event
+ if (d.lastTap && isDoubleTap(d.lastTap, d.start)) {
+ d.emitEvent('tap', d.start);
+ d.emitEvent('dbltap', d.start);
+ // clear the lastTap property, so we don't get another one
+ d.lastTap = null;
+ }
+ else {
+ // Emit a 'tap' event using the starting coordinates
+ // as the event details
+ d.emitEvent('tap', d.start);
+
+ // Remember the coordinates of this tap so we can detect double taps
+ d.lastTap = coordinates(e, t);
+ }
+
+ // In either case clear the timer and go back to the initial state
+ d.clearTimer('holdtimeout');
+ d.switchTo(initialState);
+ },
+
+ holdtimeout: function(d) {
+ d.switchTo(holdState);
+ }
+
+ };
+
+ // A single touch has moved enough to exceed the pan threshold and now
+ // we're going to generate pan events after each move and a swipe event
+ // when the touch ends. We ignore any other touches that occur while this
+ // pan/swipe gesture is in progress.
+ var panStartedState = {
+ name: 'panStartedState',
+ init: function(d, e, t) {
+ // Panning doesn't start until the touch has moved more than a
+ // certain threshold. But we don't want the pan to have a jerky
+ // start where the first event is a big distance. So proceed as
+ // pan actually started at a point along the path between the
+ // first touch and this current touch.
+ d.start = d.last = between(d.start, coordinates(e, t));
+
+ // If we transition into this state with a touchmove event,
+ // then process it with that handler. If we don't do this then
+ // we can end up with swipe events that don't know their velocity
+ if (e.type === 'touchmove')
+ panStartedState.touchmove(d, e, t);
+ },
+
+ touchmove: function(d, e, t) {
+ // Ignore any fingers other than the one we're tracking
+ if (t.identifier !== d.touch1)
+ return;
+
+ // Each time the touch moves, emit a pan event but stay in this state
+ var current = coordinates(e, t);
+ d.emitEvent('pan', {
+ absolute: {
+ dx: current.screenX - d.start.screenX,
+ dy: current.screenY - d.start.screenY
+ },
+ relative: {
+ dx: current.screenX - d.last.screenX,
+ dy: current.screenY - d.last.screenY
+ },
+ position: current
+ });
+
+ // Track the pan velocity so we can report this with the swipe
+ // Use a exponential moving average for a bit of smoothing
+ // on the velocity
+ var dt = current.timeStamp - d.last.timeStamp;
+ var vx = (current.screenX - d.last.screenX) / dt;
+ var vy = (current.screenY - d.last.screenY) / dt;
+
+ if (d.vx == null) { // first time; no average
+ d.vx = vx;
+ d.vy = vy;
+ }
+ else {
+ d.vx = d.vx * GD.VELOCITY_SMOOTHING +
+ vx * (1 - GD.VELOCITY_SMOOTHING);
+ d.vy = d.vy * GD.VELOCITY_SMOOTHING +
+ vy * (1 - GD.VELOCITY_SMOOTHING);
+ }
+
+ d.last = current;
+ },
+ touchend: function(d, e, t) {
+ // Ignore any fingers other than the one we're tracking
+ if (t.identifier !== d.touch1)
+ return;
+
+ // Emit a swipe event when the finger goes up.
+ // Report start and end point, dx, dy, dt, velocity and direction
+ var current = coordinates(e, t);
+ var dx = current.screenX - d.start.screenX;
+ var dy = current.screenY - d.start.screenY;
+ // angle is a positive number of degrees, starting at 0 on the
+ // positive x axis and increasing clockwise.
+ var angle = atan2(dy, dx) * 180 / PI;
+ if (angle < 0)
+ angle += 360;
+
+ // Direction is 'right', 'down', 'left' or 'up'
+ var direction;
+ if (angle >= 315 || angle < 45)
+ direction = 'right';
+ else if (angle >= 45 && angle < 135)
+ direction = 'down';
+ else if (angle >= 135 && angle < 225)
+ direction = 'left';
+ else if (angle >= 225 && angle < 315)
+ direction = 'up';
+
+ d.emitEvent('swipe', {
+ start: d.start,
+ end: current,
+ dx: dx,
+ dy: dy,
+ dt: e.timeStamp - d.start.timeStamp,
+ vx: d.vx,
+ vy: d.vy,
+ direction: direction,
+ angle: angle
+ });
+
+ // Go back to the initial state
+ d.switchTo(initialState);
+ }
+ };
+
+ // We enter this state if the user touches and holds for long enough
+ // without moving much. When we enter we emit a holdstart event. Motion
+ // after the holdstart generates holdmove events. And when the touch ends
+ // we generate a holdend event. holdmove and holdend events can be used
+ // kind of like drag and drop events in a mouse-based UI. Currently,
+ // these events just report the coordinates of the touch. Do we need
+ // other details?
+ var holdState = {
+ name: 'holdState',
+ init: function(d) {
+ d.emitEvent('holdstart', d.start);
+ },
+
+ touchmove: function(d, e, t) {
+ var current = coordinates(e, t);
+ d.emitEvent('holdmove', {
+ absolute: {
+ dx: current.screenX - d.start.screenX,
+ dy: current.screenY - d.start.screenY
+ },
+ relative: {
+ dx: current.screenX - d.last.screenX,
+ dy: current.screenY - d.last.screenY
+ },
+ position: current
+ });
+
+ d.last = current;
+ },
+
+ touchend: function(d, e, t) {
+ var current = coordinates(e, t);
+ d.emitEvent('holdend', {
+ start: d.start,
+ end: current,
+ dx: current.screenX - d.start.screenX,
+ dy: current.screenY - d.start.screenY
+ });
+ d.switchTo(initialState);
+ }
+ };
+
+ // We enter this state if a second touch starts before we start
+ // recoginzing any other gesture. As the touches move we track the
+ // distance and angle between them to report scale and rotation values
+ // in transform events.
+ var transformState = {
+ name: 'transformState',
+ init: function(d, e, t) {
+ // Remember the id of the second touch
+ d.touch2 = t.identifier;
+
+ // Get the two Touch objects
+ var t1 = e.touches.identifiedTouch(d.touch1);
+ var t2 = e.touches.identifiedTouch(d.touch2);
+
+ // Compute and remember the initial distance and angle
+ d.startDistance = d.lastDistance = touchDistance(t1, t2);
+ d.startDirection = d.lastDirection = touchDirection(t1, t2);
+
+ // Don't start emitting events until we're past a threshold
+ d.scaled = d.rotated = false;
+ },
+
+ touchmove: function(d, e, t) {
+ // Ignore touches we're not tracking
+ if (t.identifier !== d.touch1 && t.identifier !== d.touch2)
+ return;
+
+ // Get the two Touch objects
+ var t1 = e.touches.identifiedTouch(d.touch1);
+ var t2 = e.touches.identifiedTouch(d.touch2);
+
+ // Compute the new midpoints, distance and direction
+ var midpoint = midpoints(e, t1, t2);
+ var distance = touchDistance(t1, t2);
+ var direction = touchDirection(t1, t2);
+ var rotation = touchRotation(d.startDirection, direction);
+
+ // Check all of these numbers against the thresholds. Otherwise
+ // the transforms are too jittery even when you try to hold your
+ // fingers still.
+ if (!d.scaled) {
+ if (abs(distance - d.startDistance) > GD.SCALE_THRESHOLD) {
+ d.scaled = true;
+ d.startDistance = d.lastDistance =
+ floor(d.startDistance +
+ GD.THRESHOLD_SMOOTHING * (distance - d.startDistance));
+ }
+ else
+ distance = d.startDistance;
+ }
+ if (!d.rotated) {
+ if (abs(rotation) > GD.ROTATE_THRESHOLD)
+ d.rotated = true;
+ else
+ direction = d.startDirection;
+ }
+
+ // If nothing has exceeded the threshold yet, then we
+ // don't even have to fire an event.
+ if (d.scaled || d.rotated) {
+ // The detail field for the transform gesture event includes
+ // 'absolute' transformations against the initial values and
+ // 'relative' transformations against the values from the last
+ // transformgesture event.
+ d.emitEvent('transform', {
+ absolute: { // transform details since gesture start
+ scale: distance / d.startDistance,
+ rotate: touchRotation(d.startDirection, direction)
+ },
+ relative: { // transform since last gesture change
+ scale: distance / d.lastDistance,
+ rotate: touchRotation(d.lastDirection, direction)
+ },
+ midpoint: midpoint
+ });
+
+ d.lastDistance = distance;
+ d.lastDirection = direction;
+ d.lastMidpoint = midpoint;
+ }
+ },
+
+ touchend: function(d, e, t) {
+ // If either finger goes up, we're done with the gesture.
+ // The user might move that finger and put it right back down
+ // again to begin another 2-finger gesture, so we can't go
+ // back to the initial state while one of the fingers remains up.
+ // On the other hand, we can't go back to touchStartedState because
+ // that would mean that the finger left down could cause a tap or
+ // pan event. So we need an afterTransform state that waits for
+ // a finger to come back down or the other finger to go up.
+ if (t.identifier === d.touch2)
+ d.touch2 = null;
+ else if (t.identifier === d.touch1) {
+ d.touch1 = d.touch2;
+ d.touch2 = null;
+ }
+ else
+ return; // It was a touch we weren't tracking
+
+ // If we emitted any transform events, now we need to emit
+ // a transformend event to end the series. The details of this
+ // event use the values from the last touchmove, and the
+ // relative amounts will 1 and 0, but they are included for
+ // completeness even though they are not useful.
+ if (d.scaled || d.rotated) {
+ d.emitEvent('transformend', {
+ absolute: { // transform details since gesture start
+ scale: d.lastDistance / d.startDistance,
+ rotate: touchRotation(d.startDirection, d.lastDirection)
+ },
+ relative: { // nothing has changed relative to the last touchmove
+ scale: 1,
+ rotate: 0
+ },
+ midpoint: d.lastMidpoint
+ });
+ }
+
+ d.switchTo(afterTransformState);
+ }
+ };
+
+ // We did a tranform and one finger went up. Wait for that finger to
+ // come back down or the other finger to go up too.
+ var afterTransformState = {
+ name: 'afterTransformState',
+ touchstart: function(d, e, t) {
+ d.switchTo(transformState, e, t);
+ },
+
+ touchend: function(d, e, t) {
+ if (t.identifier === d.touch1)
+ d.switchTo(initialState);
+ }
+ };
+
+ var mouseDownState = {
+ name: 'mouseDownState',
+ init: function(d, e) {
+ // Remember the target of the event
+ d.target = e.target;
+
+ // Register this detector as a *capturing* handler on the document
+ // so we get all subsequent mouse events until we remove these handlers
+ var doc = d.element.ownerDocument;
+ doc.addEventListener('mousemove', d, true);
+ doc.addEventListener('mouseup', d, true);
+
+ // Get the coordinates of the mouse event
+ d.start = d.last = mouseCoordinates(e);
+
+ // Start a timer for a hold
+ // If we're doing hold events, start a timer for them
+ if (d.options.holdEvents)
+ d.startTimer('holdtimeout', GD.HOLD_INTERVAL);
+ },
+
+ mousemove: function(d, e) {
+ // If the mouse has moved more than the panning threshold,
+ // then switch to the mouse panning state. Otherwise remain
+ // in this state
+
+ if (abs(e.screenX - d.start.screenX) > GD.MOUSE_PAN_THRESHOLD ||
+ abs(e.screenY - d.start.screenY) > GD.MOUSE_PAN_THRESHOLD) {
+ d.clearTimer('holdtimeout');
+ d.switchTo(mousePannedState, e);
+ }
+ },
+
+ mouseup: function(d, e) {
+ // Remove the capturing event handlers
+ var doc = d.element.ownerDocument;
+ doc.removeEventListener('mousemove', d, true);
+ doc.removeEventListener('mouseup', d, true);
+
+ // If there was a previous tap that was close enough in time
+ // and space, then emit a 'dbltap' event
+ if (d.lastTap && isDoubleTap(d.lastTap, d.start)) {
+ d.emitEvent('tap', d.start);
+ d.emitEvent('dbltap', d.start);
+ d.lastTap = null; // so we don't get another one
+ }
+ else {
+ // Emit a 'tap' event using the starting coordinates
+ // as the event details
+ d.emitEvent('tap', d.start);
+
+ // Remember the coordinates of this tap so we can detect double taps
+ d.lastTap = mouseCoordinates(e);
+ }
+
+ // In either case clear the timer and go back to the initial state
+ d.clearTimer('holdtimeout');
+ d.switchTo(initialState);
+ },
+
+ holdtimeout: function(d) {
+ d.switchTo(mouseHoldState);
+ }
+ };
+
+ // Like holdState, but for mouse events instead of touch events
+ var mouseHoldState = {
+ name: 'mouseHoldState',
+ init: function(d) {
+ d.emitEvent('holdstart', d.start);
+ },
+
+ mousemove: function(d, e) {
+ var current = mouseCoordinates(e);
+ d.emitEvent('holdmove', {
+ absolute: {
+ dx: current.screenX - d.start.screenX,
+ dy: current.screenY - d.start.screenY
+ },
+ relative: {
+ dx: current.screenX - d.last.screenX,
+ dy: current.screenY - d.last.screenY
+ },
+ position: current
+ });
+
+ d.last = current;
+ },
+
+ mouseup: function(d, e) {
+ var current = mouseCoordinates(e);
+ d.emitEvent('holdend', {
+ start: d.start,
+ end: current,
+ dx: current.screenX - d.start.screenX,
+ dy: current.screenY - d.start.screenY
+ });
+ d.switchTo(initialState);
+ }
+ };
+
+ var mousePannedState = {
+ name: 'mousePannedState',
+ init: function(d, e) {
+ // Panning doesn't start until the mouse has moved more than
+ // a certain threshold. But we don't want the pan to have a jerky
+ // start where the first event is a big distance. So reset the
+ // starting point to a point between the start point and this
+ // current point
+ d.start = d.last = between(d.start, mouseCoordinates(e));
+
+ // If we transition into this state with a mousemove event,
+ // then process it with that handler. If we don't do this then
+ // we can end up with swipe events that don't know their velocity
+ if (e.type === 'mousemove')
+ mousePannedState.mousemove(d, e);
+ },
+ mousemove: function(d, e) {
+ // Each time the mouse moves, emit a pan event but stay in this state
+ var current = mouseCoordinates(e);
+ d.emitEvent('pan', {
+ absolute: {
+ dx: current.screenX - d.start.screenX,
+ dy: current.screenY - d.start.screenY
+ },
+ relative: {
+ dx: current.screenX - d.last.screenX,
+ dy: current.screenY - d.last.screenY
+ },
+ position: current
+ });
+
+ // Track the pan velocity so we can report this with the swipe
+ // Use a exponential moving average for a bit of smoothing
+ // on the velocity
+ var dt = current.timeStamp - d.last.timeStamp;
+ var vx = (current.screenX - d.last.screenX) / dt;
+ var vy = (current.screenY - d.last.screenY) / dt;
+
+ if (d.vx == null) { // first time; no average
+ d.vx = vx;
+ d.vy = vy;
+ }
+ else {
+ d.vx = d.vx * GD.VELOCITY_SMOOTHING +
+ vx * (1 - GD.VELOCITY_SMOOTHING);
+ d.vy = d.vy * GD.VELOCITY_SMOOTHING +
+ vy * (1 - GD.VELOCITY_SMOOTHING);
+ }
+
+ d.last = current;
+ },
+ mouseup: function(d, e) {
+ // Remove the capturing event handlers
+ var doc = d.element.ownerDocument;
+ doc.removeEventListener('mousemove', d, true);
+ doc.removeEventListener('mouseup', d, true);
+
+ // Emit a swipe event when the mouse goes up.
+ // Report start and end point, dx, dy, dt, velocity and direction
+ var current = mouseCoordinates(e);
+
+ // FIXME:
+ // lots of code duplicated between this state and the corresponding
+ // touch state, can I combine them somehow?
+ var dx = current.screenX - d.start.screenX;
+ var dy = current.screenY - d.start.screenY;
+ // angle is a positive number of degrees, starting at 0 on the
+ // positive x axis and increasing clockwise.
+ var angle = atan2(dy, dx) * 180 / PI;
+ if (angle < 0)
+ angle += 360;
+
+ // Direction is 'right', 'down', 'left' or 'up'
+ var direction;
+ if (angle >= 315 || angle < 45)
+ direction = 'right';
+ else if (angle >= 45 && angle < 135)
+ direction = 'down';
+ else if (angle >= 135 && angle < 225)
+ direction = 'left';
+ else if (angle >= 225 && angle < 315)
+ direction = 'up';
+
+ d.emitEvent('swipe', {
+ start: d.start,
+ end: current,
+ dx: dx,
+ dy: dy,
+ dt: current.timeStamp - d.start.timeStamp,
+ vx: d.vx,
+ vy: d.vy,
+ direction: direction,
+ angle: angle
+ });
+
+ // Go back to the initial state
+ d.switchTo(initialState);
+ }
+ };
+
+ return GD;
+}());
+
diff --git a/shared/js/idletimer.js b/shared/js/idletimer.js
new file mode 100644
index 0000000..5c42384
--- /dev/null
+++ b/shared/js/idletimer.js
@@ -0,0 +1,127 @@
+/*
+ This file implements window.setIdleTimeout() and
+ window.clearIdleTimeout(). They look like setTimeout() except the
+ setIdleTimeout() function takes two callbacks:
+
+ setIdleTimeout(idleCallback, activeCallback, ms):
+ idleCallback will fire after specific microsecond of idle.
+ The time it takes will calculated from the time
+ setIdleTimeout() is called and resets as user interacts.
+
+ activeCallback will fire when the first user action *after*
+ idleCallback fires
+
+ returns id.
+
+ clearIdleTimeout(id):
+ takes the id returns from setIdleTimeout() and cancels it.
+
+*/
+
+// Wrap everything into a closure so we will not expose idleTimerRegistry
+
+'use strict';
+
+(function idleTimerAsAIdleObserverWrapper(win) {
+
+ // stuff the 0th element so id is always a truey value
+ var idleTimerRegistry = [undefined];
+
+ // setIdleTimeout()
+ win.setIdleTimeout = function setIdleTimeout(idleCallback,
+ activeCallback,
+ ms) {
+ var startTimestamp = Date.now();
+ var idleFired = false;
+
+ var idleTimer = {
+ timer: undefined,
+ resetStartTimestamp: function resetStartTimestamp() {
+ startTimestamp = Date.now();
+ }
+ };
+
+ // If the system unix time changes, we would need to update
+ // the number we kept in startTimestamp, or bad things will happen.
+ window.addEventListener('moztimechange', idleTimer.resetStartTimestamp);
+
+ // Create an idle observer with a very short time as
+ // we are not interested in when onidle fires (since it's inaccuate),
+ // instead, we need to know when onactive callback calls.
+ idleTimer.observer = {
+ onidle: function observerReportIdle() {
+ // Once the onidle fires, the next user action will trigger
+ // onactive.
+
+ // The time it takes for onidle to fire need to be subtracted from
+ // the real time we are going to set to setTimeout()
+ var time = (ms - (Date.now() - startTimestamp));
+
+ // Let's start the real count down and wait for that.
+ idleTimer.timer = setTimeout(function idled() {
+ // remove the timer
+ idleTimer.timer = undefined;
+
+ // set idleFired to true
+ idleFired = true;
+
+ // fire the real idleCallback
+ idleCallback();
+ }, time);
+ },
+ onactive: function observerReportActive() {
+ // Remove the timer set by onidle
+ if (idleTimer.timer) {
+ clearTimeout(idleTimer.timer);
+ idleTimer.timer = undefined;
+ }
+
+ // Reset the timestamp; the next real count down should start
+ // from the time onactive fires
+ startTimestamp = Date.now();
+
+ // If idleCallback is not called yet,
+ // we should not trigger activeCallback here
+ if (!idleFired)
+ return;
+
+ // fire the real activeCallback
+ activeCallback();
+
+ // reset the flag
+ idleFired = false;
+
+ // After a short time, onidle will fire again.
+ // timer will be registered there again.
+ },
+ time: 1
+ };
+
+ // Register the idleObserver
+ navigator.addIdleObserver(idleTimer.observer);
+
+ // Push the idleTimer object to the registry
+ idleTimerRegistry.push(idleTimer);
+
+ // return the id so people can do clearIdleTimeout();
+ return (idleTimerRegistry.length - 1);
+ };
+
+ // clearIdleTimeout()
+ win.clearIdleTimeout = function clearIdleTimeout(id) {
+ if (!idleTimerRegistry[id])
+ return;
+
+ // Get the idleTimer object and remove it from registry
+ var idleTimer = idleTimerRegistry[id];
+ idleTimerRegistry[id] = undefined;
+
+ // Properly clean it up, make sure we will never heard from
+ // those callbacks ever again.
+ navigator.removeIdleObserver(idleTimer.observer);
+ window.removeEventListener('moztimechange', idleTimer.resetStartTimestamp);
+ if (idleTimer.timer)
+ clearTimeout(idleTimer.timer);
+ };
+
+})(this);
diff --git a/shared/js/l10n.js b/shared/js/l10n.js
new file mode 100644
index 0000000..adbc51a
--- /dev/null
+++ b/shared/js/l10n.js
@@ -0,0 +1,1014 @@
+/* -*- Mode: js; js-indent-level: 2; indent-tabs-mode: nil -*- */
+/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
+
+'use strict';
+
+/**
+ * This library exposes a `navigator.mozL10n' object to handle client-side
+ * application localization. See: https://github.com/fabi1cazenave/webL10n
+ */
+
+(function(window) {
+ var gL10nData = {};
+ var gTextProp = 'textContent';
+ var gLanguage = '';
+ var gMacros = {};
+ var gReadyState = 'loading';
+
+
+ /**
+ * Synchronously loading l10n resources significantly minimizes flickering
+ * from displaying the app with non-localized strings and then updating the
+ * strings. Although this will block all script execution on this page, we
+ * expect that the l10n resources are available locally on flash-storage.
+ *
+ * As synchronous XHR is generally considered as a bad idea, we're still
+ * loading l10n resources asynchronously -- but we keep this in a setting,
+ * just in case... and applications using this library should hide their
+ * content until the `localized' event happens.
+ */
+
+ var gAsyncResourceLoading = true; // read-only
+
+
+ /**
+ * Debug helpers
+ *
+ * gDEBUG == 0: don't display any console message
+ * gDEBUG == 1: display only warnings, not logs
+ * gDEBUG == 2: display all console messages
+ */
+
+ var gDEBUG = 1;
+
+ function consoleLog(message) {
+ if (gDEBUG >= 2) {
+ console.log('[l10n] ' + message);
+ }
+ };
+
+ function consoleWarn(message) {
+ if (gDEBUG) {
+ console.warn('[l10n] ' + message);
+ }
+ };
+
+
+ /**
+ * DOM helpers for the so-called "HTML API".
+ *
+ * These functions are written for modern browsers. For old versions of IE,
+ * they're overridden in the 'startup' section at the end of this file.
+ */
+
+ function getL10nResourceLinks() {
+ return document.querySelectorAll('link[type="application/l10n"]');
+ }
+
+ function getL10nDictionary() {
+ var script = document.querySelector('script[type="application/l10n"]');
+ // TODO: support multiple and external JSON dictionaries
+ return script ? JSON.parse(script.innerHTML) : null;
+ }
+
+ function getTranslatableChildren(element) {
+ return element ? element.querySelectorAll('*[data-l10n-id]') : [];
+ }
+
+ function getL10nAttributes(element) {
+ if (!element)
+ return {};
+
+ var l10nId = element.getAttribute('data-l10n-id');
+ var l10nArgs = element.getAttribute('data-l10n-args');
+ var args = {};
+ if (l10nArgs) {
+ try {
+ args = JSON.parse(l10nArgs);
+ } catch (e) {
+ consoleWarn('could not parse arguments for #' + l10nId);
+ }
+ }
+ return { id: l10nId, args: args };
+ }
+
+ function fireL10nReadyEvent() {
+ var evtObject = document.createEvent('Event');
+ evtObject.initEvent('localized', false, false);
+ evtObject.language = gLanguage;
+ window.dispatchEvent(evtObject);
+ }
+
+
+ /**
+ * l10n resource parser:
+ * - reads (async XHR) the l10n resource matching `lang';
+ * - imports linked resources (synchronously) when specified;
+ * - parses the text data (fills `gL10nData');
+ * - triggers success/failure callbacks when done.
+ *
+ * @param {string} href
+ * URL of the l10n resource to parse.
+ *
+ * @param {string} lang
+ * locale (language) to parse.
+ *
+ * @param {Function} successCallback
+ * triggered when the l10n resource has been successully parsed.
+ *
+ * @param {Function} failureCallback
+ * triggered when the an error has occured.
+ *
+ * @return {void}
+ * uses the following global variables: gL10nData, gTextProp.
+ */
+
+ function parseResource(href, lang, successCallback, failureCallback) {
+ var baseURL = href.replace(/\/[^\/]*$/, '/');
+
+ // handle escaped characters (backslashes) in a string
+ function evalString(text) {
+ if (text.lastIndexOf('\\') < 0)
+ return text;
+ return text.replace(/\\\\/g, '\\')
+ .replace(/\\n/g, '\n')
+ .replace(/\\r/g, '\r')
+ .replace(/\\t/g, '\t')
+ .replace(/\\b/g, '\b')
+ .replace(/\\f/g, '\f')
+ .replace(/\\{/g, '{')
+ .replace(/\\}/g, '}')
+ .replace(/\\"/g, '"')
+ .replace(/\\'/g, "'");
+ }
+
+ // parse *.properties text data into an l10n dictionary
+ function parseProperties(text) {
+ var dictionary = [];
+
+ // token expressions
+ var reBlank = /^\s*|\s*$/;
+ var reComment = /^\s*#|^\s*$/;
+ var reSection = /^\s*\[(.*)\]\s*$/;
+ var reImport = /^\s*@import\s+url\((.*)\)\s*$/i;
+ var reSplit = /^([^=\s]*)\s*=\s*(.+)$/; // TODO: escape EOLs with '\'
+
+ // parse the *.properties file into an associative array
+ function parseRawLines(rawText, extendedSyntax) {
+ var entries = rawText.replace(reBlank, '').split(/[\r\n]+/);
+ var currentLang = '*';
+ var genericLang = lang.replace(/-[a-z]+$/i, '');
+ var skipLang = false;
+ var match = '';
+
+ for (var i = 0; i < entries.length; i++) {
+ var line = entries[i];
+
+ // comment or blank line?
+ if (reComment.test(line))
+ continue;
+
+ // the extended syntax supports [lang] sections and @import rules
+ if (extendedSyntax) {
+ if (reSection.test(line)) { // section start?
+ match = reSection.exec(line);
+ currentLang = match[1];
+ skipLang = (currentLang !== '*') &&
+ (currentLang !== lang) && (currentLang !== genericLang);
+ continue;
+ } else if (skipLang) {
+ continue;
+ }
+ if (reImport.test(line)) { // @import rule?
+ match = reImport.exec(line);
+ loadImport(baseURL + match[1]); // load the resource synchronously
+ }
+ }
+
+ // key-value pair
+ var tmp = line.match(reSplit);
+ if (tmp && tmp.length == 3) {
+ dictionary[tmp[1]] = evalString(tmp[2]);
+ }
+ }
+ }
+
+ // import another *.properties file
+ function loadImport(url) {
+ loadResource(url, function(content) {
+ parseRawLines(content, false); // don't allow recursive imports
+ }, null, false); // load synchronously
+ }
+
+ // fill the dictionary
+ parseRawLines(text, true);
+ return dictionary;
+ }
+
+ // load the specified resource file
+ function loadResource(url, onSuccess, onFailure, asynchronous) {
+ onSuccess = onSuccess || function _onSuccess(data) {};
+ onFailure = onFailure || function _onFailure() {
+ consoleWarn(url + ' not found.');
+ };
+
+ var xhr = new XMLHttpRequest();
+ xhr.open('GET', url, asynchronous);
+ if (xhr.overrideMimeType) {
+ xhr.overrideMimeType('text/plain; charset=utf-8');
+ }
+ xhr.onreadystatechange = function() {
+ if (xhr.readyState == 4) {
+ if (xhr.status == 200 || xhr.status === 0) {
+ onSuccess(xhr.responseText);
+ } else {
+ onFailure();
+ }
+ }
+ };
+ xhr.onerror = onFailure;
+ xhr.ontimeout = onFailure;
+
+ // in Firefox OS with the app:// protocol, trying to XHR a non-existing
+ // URL will raise an exception here -- hence this ugly try...catch.
+ try {
+ xhr.send(null);
+ } catch (e) {
+ onFailure();
+ }
+ }
+
+ // load and parse l10n data (warning: global variables are used here)
+ loadResource(href, function(response) {
+ // parse *.properties text data into an l10n dictionary
+ var data = parseProperties(response);
+
+ // find attribute descriptions, if any
+ for (var key in data) {
+ var id, prop, index = key.lastIndexOf('.');
+ if (index > 0) { // an attribute has been specified
+ id = key.substring(0, index);
+ prop = key.substr(index + 1);
+ } else { // no attribute: assuming text content by default
+ id = key;
+ prop = gTextProp;
+ }
+ if (!gL10nData[id]) {
+ gL10nData[id] = {};
+ }
+ gL10nData[id][prop] = data[key];
+ }
+
+ // trigger callback
+ if (successCallback) {
+ successCallback();
+ }
+ }, failureCallback, gAsyncResourceLoading);
+ };
+
+ // load and parse all resources for the specified locale
+ function loadLocale(lang, callback) {
+ callback = callback || function _callback() {};
+
+ clear();
+ gLanguage = lang;
+
+ // check all <link type="application/l10n" href="..." /> nodes
+ // and load the resource files
+ var langLinks = getL10nResourceLinks();
+ var langCount = langLinks.length;
+ if (langCount == 0) {
+ // we might have a pre-compiled dictionary instead
+ var dict = getL10nDictionary();
+ if (dict && dict.locales && dict.default_locale) {
+ consoleLog('using the embedded JSON directory, early way out');
+ gL10nData = dict.locales[lang] || dict.locales[dict.default_locale];
+ callback();
+ } else {
+ consoleLog('no resource to load, early way out');
+ }
+ // early way out
+ fireL10nReadyEvent(lang);
+ gReadyState = 'complete';
+ return;
+ }
+
+ // start the callback when all resources are loaded
+ var onResourceLoaded = null;
+ var gResourceCount = 0;
+ onResourceLoaded = function() {
+ gResourceCount++;
+ if (gResourceCount >= langCount) {
+ callback();
+ fireL10nReadyEvent(lang);
+ gReadyState = 'complete';
+ }
+ };
+
+ // load all resource files
+ function l10nResourceLink(link) {
+ var href = link.href;
+ var type = link.type;
+ this.load = function(lang, callback) {
+ var applied = lang;
+ parseResource(href, lang, callback, function() {
+ consoleWarn(href + ' not found.');
+ applied = '';
+ });
+ return applied; // return lang if found, an empty string if not found
+ };
+ }
+
+ for (var i = 0; i < langCount; i++) {
+ var resource = new l10nResourceLink(langLinks[i]);
+ var rv = resource.load(lang, onResourceLoaded);
+ if (rv != lang) { // lang not found, used default resource instead
+ consoleWarn('"' + lang + '" resource not found');
+ gLanguage = '';
+ }
+ }
+ }
+
+ // clear all l10n data
+ function clear() {
+ gL10nData = {};
+ gLanguage = '';
+ // TODO: clear all non predefined macros.
+ // There's no such macro /yet/ but we're planning to have some...
+ }
+
+
+ /**
+ * Get rules for plural forms (shared with JetPack), see:
+ * http://unicode.org/repos/cldr-tmp/trunk/diff/supplemental/language_plural_rules.html
+ * https://github.com/mozilla/addon-sdk/blob/master/python-lib/plural-rules-generator.p
+ *
+ * @param {string} lang
+ * locale (language) used.
+ *
+ * @return {Function}
+ * returns a function that gives the plural form name for a given integer:
+ * var fun = getPluralRules('en');
+ * fun(1) -> 'one'
+ * fun(0) -> 'other'
+ * fun(1000) -> 'other'.
+ */
+
+ function getPluralRules(lang) {
+ var locales2rules = {
+ 'af': 3,
+ 'ak': 4,
+ 'am': 4,
+ 'ar': 1,
+ 'asa': 3,
+ 'az': 0,
+ 'be': 11,
+ 'bem': 3,
+ 'bez': 3,
+ 'bg': 3,
+ 'bh': 4,
+ 'bm': 0,
+ 'bn': 3,
+ 'bo': 0,
+ 'br': 20,
+ 'brx': 3,
+ 'bs': 11,
+ 'ca': 3,
+ 'cgg': 3,
+ 'chr': 3,
+ 'cs': 12,
+ 'cy': 17,
+ 'da': 3,
+ 'de': 3,
+ 'dv': 3,
+ 'dz': 0,
+ 'ee': 3,
+ 'el': 3,
+ 'en': 3,
+ 'eo': 3,
+ 'es': 3,
+ 'et': 3,
+ 'eu': 3,
+ 'fa': 0,
+ 'ff': 5,
+ 'fi': 3,
+ 'fil': 4,
+ 'fo': 3,
+ 'fr': 5,
+ 'fur': 3,
+ 'fy': 3,
+ 'ga': 8,
+ 'gd': 24,
+ 'gl': 3,
+ 'gsw': 3,
+ 'gu': 3,
+ 'guw': 4,
+ 'gv': 23,
+ 'ha': 3,
+ 'haw': 3,
+ 'he': 2,
+ 'hi': 4,
+ 'hr': 11,
+ 'hu': 0,
+ 'id': 0,
+ 'ig': 0,
+ 'ii': 0,
+ 'is': 3,
+ 'it': 3,
+ 'iu': 7,
+ 'ja': 0,
+ 'jmc': 3,
+ 'jv': 0,
+ 'ka': 0,
+ 'kab': 5,
+ 'kaj': 3,
+ 'kcg': 3,
+ 'kde': 0,
+ 'kea': 0,
+ 'kk': 3,
+ 'kl': 3,
+ 'km': 0,
+ 'kn': 0,
+ 'ko': 0,
+ 'ksb': 3,
+ 'ksh': 21,
+ 'ku': 3,
+ 'kw': 7,
+ 'lag': 18,
+ 'lb': 3,
+ 'lg': 3,
+ 'ln': 4,
+ 'lo': 0,
+ 'lt': 10,
+ 'lv': 6,
+ 'mas': 3,
+ 'mg': 4,
+ 'mk': 16,
+ 'ml': 3,
+ 'mn': 3,
+ 'mo': 9,
+ 'mr': 3,
+ 'ms': 0,
+ 'mt': 15,
+ 'my': 0,
+ 'nah': 3,
+ 'naq': 7,
+ 'nb': 3,
+ 'nd': 3,
+ 'ne': 3,
+ 'nl': 3,
+ 'nn': 3,
+ 'no': 3,
+ 'nr': 3,
+ 'nso': 4,
+ 'ny': 3,
+ 'nyn': 3,
+ 'om': 3,
+ 'or': 3,
+ 'pa': 3,
+ 'pap': 3,
+ 'pl': 13,
+ 'ps': 3,
+ 'pt': 3,
+ 'rm': 3,
+ 'ro': 9,
+ 'rof': 3,
+ 'ru': 11,
+ 'rwk': 3,
+ 'sah': 0,
+ 'saq': 3,
+ 'se': 7,
+ 'seh': 3,
+ 'ses': 0,
+ 'sg': 0,
+ 'sh': 11,
+ 'shi': 19,
+ 'sk': 12,
+ 'sl': 14,
+ 'sma': 7,
+ 'smi': 7,
+ 'smj': 7,
+ 'smn': 7,
+ 'sms': 7,
+ 'sn': 3,
+ 'so': 3,
+ 'sq': 3,
+ 'sr': 11,
+ 'ss': 3,
+ 'ssy': 3,
+ 'st': 3,
+ 'sv': 3,
+ 'sw': 3,
+ 'syr': 3,
+ 'ta': 3,
+ 'te': 3,
+ 'teo': 3,
+ 'th': 0,
+ 'ti': 4,
+ 'tig': 3,
+ 'tk': 3,
+ 'tl': 4,
+ 'tn': 3,
+ 'to': 0,
+ 'tr': 0,
+ 'ts': 3,
+ 'tzm': 22,
+ 'uk': 11,
+ 'ur': 3,
+ 've': 3,
+ 'vi': 0,
+ 'vun': 3,
+ 'wa': 4,
+ 'wae': 3,
+ 'wo': 0,
+ 'xh': 3,
+ 'xog': 3,
+ 'yo': 0,
+ 'zh': 0,
+ 'zu': 3
+ };
+
+ // utility functions for plural rules methods
+ function isIn(n, list) {
+ return list.indexOf(n) !== -1;
+ }
+ function isBetween(n, start, end) {
+ return start <= n && n <= end;
+ }
+
+ // list of all plural rules methods:
+ // map an integer to the plural form name to use
+ var pluralRules = {
+ '0': function(n) {
+ return 'other';
+ },
+ '1': function(n) {
+ if ((isBetween((n % 100), 3, 10)))
+ return 'few';
+ if (n === 0)
+ return 'zero';
+ if ((isBetween((n % 100), 11, 99)))
+ return 'many';
+ if (n == 2)
+ return 'two';
+ if (n == 1)
+ return 'one';
+ return 'other';
+ },
+ '2': function(n) {
+ if (n !== 0 && (n % 10) === 0)
+ return 'many';
+ if (n == 2)
+ return 'two';
+ if (n == 1)
+ return 'one';
+ return 'other';
+ },
+ '3': function(n) {
+ if (n == 1)
+ return 'one';
+ return 'other';
+ },
+ '4': function(n) {
+ if ((isBetween(n, 0, 1)))
+ return 'one';
+ return 'other';
+ },
+ '5': function(n) {
+ if ((isBetween(n, 0, 2)) && n != 2)
+ return 'one';
+ return 'other';
+ },
+ '6': function(n) {
+ if (n === 0)
+ return 'zero';
+ if ((n % 10) == 1 && (n % 100) != 11)
+ return 'one';
+ return 'other';
+ },
+ '7': function(n) {
+ if (n == 2)
+ return 'two';
+ if (n == 1)
+ return 'one';
+ return 'other';
+ },
+ '8': function(n) {
+ if ((isBetween(n, 3, 6)))
+ return 'few';
+ if ((isBetween(n, 7, 10)))
+ return 'many';
+ if (n == 2)
+ return 'two';
+ if (n == 1)
+ return 'one';
+ return 'other';
+ },
+ '9': function(n) {
+ if (n === 0 || n != 1 && (isBetween((n % 100), 1, 19)))
+ return 'few';
+ if (n == 1)
+ return 'one';
+ return 'other';
+ },
+ '10': function(n) {
+ if ((isBetween((n % 10), 2, 9)) && !(isBetween((n % 100), 11, 19)))
+ return 'few';
+ if ((n % 10) == 1 && !(isBetween((n % 100), 11, 19)))
+ return 'one';
+ return 'other';
+ },
+ '11': function(n) {
+ if ((isBetween((n % 10), 2, 4)) && !(isBetween((n % 100), 12, 14)))
+ return 'few';
+ if ((n % 10) === 0 ||
+ (isBetween((n % 10), 5, 9)) ||
+ (isBetween((n % 100), 11, 14)))
+ return 'many';
+ if ((n % 10) == 1 && (n % 100) != 11)
+ return 'one';
+ return 'other';
+ },
+ '12': function(n) {
+ if ((isBetween(n, 2, 4)))
+ return 'few';
+ if (n == 1)
+ return 'one';
+ return 'other';
+ },
+ '13': function(n) {
+ if ((isBetween((n % 10), 2, 4)) && !(isBetween((n % 100), 12, 14)))
+ return 'few';
+ if (n != 1 && (isBetween((n % 10), 0, 1)) ||
+ (isBetween((n % 10), 5, 9)) ||
+ (isBetween((n % 100), 12, 14)))
+ return 'many';
+ if (n == 1)
+ return 'one';
+ return 'other';
+ },
+ '14': function(n) {
+ if ((isBetween((n % 100), 3, 4)))
+ return 'few';
+ if ((n % 100) == 2)
+ return 'two';
+ if ((n % 100) == 1)
+ return 'one';
+ return 'other';
+ },
+ '15': function(n) {
+ if (n === 0 || (isBetween((n % 100), 2, 10)))
+ return 'few';
+ if ((isBetween((n % 100), 11, 19)))
+ return 'many';
+ if (n == 1)
+ return 'one';
+ return 'other';
+ },
+ '16': function(n) {
+ if ((n % 10) == 1 && n != 11)
+ return 'one';
+ return 'other';
+ },
+ '17': function(n) {
+ if (n == 3)
+ return 'few';
+ if (n === 0)
+ return 'zero';
+ if (n == 6)
+ return 'many';
+ if (n == 2)
+ return 'two';
+ if (n == 1)
+ return 'one';
+ return 'other';
+ },
+ '18': function(n) {
+ if (n === 0)
+ return 'zero';
+ if ((isBetween(n, 0, 2)) && n !== 0 && n != 2)
+ return 'one';
+ return 'other';
+ },
+ '19': function(n) {
+ if ((isBetween(n, 2, 10)))
+ return 'few';
+ if ((isBetween(n, 0, 1)))
+ return 'one';
+ return 'other';
+ },
+ '20': function(n) {
+ if ((isBetween((n % 10), 3, 4) || ((n % 10) == 9)) && !(
+ isBetween((n % 100), 10, 19) ||
+ isBetween((n % 100), 70, 79) ||
+ isBetween((n % 100), 90, 99)
+ ))
+ return 'few';
+ if ((n % 1000000) === 0 && n !== 0)
+ return 'many';
+ if ((n % 10) == 2 && !isIn((n % 100), [12, 72, 92]))
+ return 'two';
+ if ((n % 10) == 1 && !isIn((n % 100), [11, 71, 91]))
+ return 'one';
+ return 'other';
+ },
+ '21': function(n) {
+ if (n === 0)
+ return 'zero';
+ if (n == 1)
+ return 'one';
+ return 'other';
+ },
+ '22': function(n) {
+ if ((isBetween(n, 0, 1)) || (isBetween(n, 11, 99)))
+ return 'one';
+ return 'other';
+ },
+ '23': function(n) {
+ if ((isBetween((n % 10), 1, 2)) || (n % 20) === 0)
+ return 'one';
+ return 'other';
+ },
+ '24': function(n) {
+ if ((isBetween(n, 3, 10) || isBetween(n, 13, 19)))
+ return 'few';
+ if (isIn(n, [2, 12]))
+ return 'two';
+ if (isIn(n, [1, 11]))
+ return 'one';
+ return 'other';
+ }
+ };
+
+ // return a function that gives the plural form name for a given integer
+ var index = locales2rules[lang.replace(/-.*$/, '')];
+ if (!(index in pluralRules)) {
+ consoleWarn('plural form unknown for [' + lang + ']');
+ return function() { return 'other'; };
+ }
+ return pluralRules[index];
+ }
+
+ // pre-defined 'plural' macro
+ gMacros.plural = function(str, param, key, prop) {
+ var n = parseFloat(param);
+ if (isNaN(n))
+ return str;
+
+ // TODO: support other properties (l20n still doesn't...)
+ if (prop != gTextProp)
+ return str;
+
+ // initialize _pluralRules
+ if (!gMacros._pluralRules) {
+ gMacros._pluralRules = getPluralRules(gLanguage);
+ }
+ var index = '[' + gMacros._pluralRules(n) + ']';
+
+ // try to find a [zero|one|two] key if it's defined
+ if (n === 0 && (key + '[zero]') in gL10nData) {
+ str = gL10nData[key + '[zero]'][prop];
+ } else if (n == 1 && (key + '[one]') in gL10nData) {
+ str = gL10nData[key + '[one]'][prop];
+ } else if (n == 2 && (key + '[two]') in gL10nData) {
+ str = gL10nData[key + '[two]'][prop];
+ } else if ((key + index) in gL10nData) {
+ str = gL10nData[key + index][prop];
+ } else if ((key + '[other]') in gL10nData) {
+ str = gL10nData[key + '[other]'][prop];
+ }
+
+ return str;
+ };
+
+
+ /**
+ * l10n dictionary functions
+ */
+
+ // fetch an l10n object, warn if not found, apply `args' if possible
+ function getL10nData(key, args) {
+ var data = gL10nData[key];
+ if (!data) {
+ consoleWarn('#' + key + ' is undefined.');
+ }
+
+ /** This is where l10n expressions should be processed.
+ * The plan is to support C-style expressions from the l20n project;
+ * until then, only two kinds of simple expressions are supported:
+ * {[ index ]} and {{ arguments }}.
+ */
+ var rv = {};
+ for (var prop in data) {
+ var str = data[prop];
+ str = substIndexes(str, args, key, prop);
+ str = substArguments(str, args, key);
+ rv[prop] = str;
+ }
+ return rv;
+ }
+
+ // replace {[macros]} with their values
+ function substIndexes(str, args, key, prop) {
+ var reIndex = /\{\[\s*([a-zA-Z]+)\(([a-zA-Z]+)\)\s*\]\}/;
+ var reMatch = reIndex.exec(str);
+ if (!reMatch || !reMatch.length)
+ return str;
+
+ // an index/macro has been found
+ // Note: at the moment, only one parameter is supported
+ var macroName = reMatch[1];
+ var paramName = reMatch[2];
+ var param;
+ if (args && paramName in args) {
+ param = args[paramName];
+ } else if (paramName in gL10nData) {
+ param = gL10nData[paramName];
+ }
+
+ // there's no macro parser yet: it has to be defined in gMacros
+ if (macroName in gMacros) {
+ var macro = gMacros[macroName];
+ str = macro(str, param, key, prop);
+ }
+ return str;
+ }
+
+ // replace {{arguments}} with their values
+ function substArguments(str, args, key) {
+ var reArgs = /\{\{\s*(.+?)\s*\}\}/;
+ var match = reArgs.exec(str);
+ while (match) {
+ if (!match || match.length < 2)
+ return str; // argument key not found
+
+ var arg = match[1];
+ var sub = '';
+ if (args && arg in args) {
+ sub = args[arg];
+ } else if (arg in gL10nData) {
+ sub = gL10nData[arg][gTextProp];
+ } else {
+ consoleLog('argument {{' + arg + '}} for #' + key + ' is undefined.');
+ return str;
+ }
+
+ str = str.substring(0, match.index) + sub +
+ str.substr(match.index + match[0].length);
+ match = reArgs.exec(str);
+ }
+ return str;
+ }
+
+ // translate an HTML element
+ function translateElement(element) {
+ var l10n = getL10nAttributes(element);
+ if (!l10n.id) {
+ return;
+ }
+
+ // get the related l10n object
+ var data = getL10nData(l10n.id, l10n.args);
+ if (!data) {
+ consoleWarn('#' + l10n.id + ' is undefined.');
+ return;
+ }
+
+ // translate element (TODO: security checks?)
+ if (data[gTextProp]) { // XXX
+ if (element.children.length === 0) {
+ element[gTextProp] = data[gTextProp];
+ } else {
+ // this element has element children: replace the content of the first
+ // (non-empty) child textNode and clear other child textNodes
+ var children = element.childNodes;
+ var found = false;
+ for (var i = 0, l = children.length; i < l; i++) {
+ if (children[i].nodeType === 3 && /\S/.test(children[i].nodeValue)) {
+ if (found) {
+ children[i].nodeValue = '';
+ } else {
+ children[i].nodeValue = data[gTextProp];
+ found = true;
+ }
+ }
+ }
+ // if no (non-empty) textNode is found, insert a textNode before the
+ // first element child.
+ if (!found) {
+ var textNode = document.createTextNode(data[gTextProp]);
+ element.insertBefore(textNode, element.firstChild);
+ }
+ }
+ delete data[gTextProp];
+ }
+
+ for (var k in data) {
+ element[k] = data[k];
+ }
+ }
+
+ // translate an HTML subtree
+ function translateFragment(element) {
+ element = element || document.documentElement;
+
+ // check all translatable children (= w/ a `data-l10n-id' attribute)
+ var children = getTranslatableChildren(element);
+ var elementCount = children.length;
+ for (var i = 0; i < elementCount; i++) {
+ translateElement(children[i]);
+ }
+
+ // translate element itself if necessary
+ translateElement(element);
+ }
+
+
+ /**
+ * Startup & Public API
+ *
+ * This section is quite specific to the B2G project: old browsers are not
+ * supported and the API is slightly different from the standard webl10n one.
+ */
+
+ // load the default locale on startup
+ function l10nStartup() {
+ gReadyState = 'interactive';
+ consoleLog('loading [' + navigator.language + '] resources, ' +
+ (gAsyncResourceLoading ? 'asynchronously.' : 'synchronously.'));
+
+ // load the default locale and translate the document if required
+ if (document.documentElement.lang === navigator.language) {
+ loadLocale(navigator.language);
+ } else {
+ loadLocale(navigator.language, translateFragment);
+ }
+ }
+
+ // the B2G build system doesn't expose any `document'...
+ if (typeof(document) !== 'undefined') {
+ if (document.readyState === 'complete' ||
+ document.readyState === 'interactive') {
+ window.setTimeout(l10nStartup);
+ } else {
+ document.addEventListener('DOMContentLoaded', l10nStartup);
+ }
+ }
+
+ // load the appropriate locale if the language setting has changed
+ if ('mozSettings' in navigator && navigator.mozSettings) {
+ navigator.mozSettings.addObserver('language.current', function(event) {
+ loadLocale(event.settingValue, translateFragment);
+ });
+ }
+
+ // public API
+ navigator.mozL10n = {
+ // get a localized string
+ get: function l10n_get(key, args, fallback) {
+ var data = getL10nData(key, args) || fallback;
+ if (data) {
+ return 'textContent' in data ? data.textContent : '';
+ }
+ return '{{' + key + '}}';
+ },
+
+ // get|set the document language and direction
+ get language() {
+ return {
+ // get|set the document language (ISO-639-1)
+ get code() { return gLanguage; },
+ set code(lang) { loadLocale(lang, translateFragment); },
+
+ // get the direction (ltr|rtl) of the current language
+ get direction() {
+ // http://www.w3.org/International/questions/qa-scripts
+ // Arabic, Hebrew, Farsi, Pashto, Urdu
+ var rtlList = ['ar', 'he', 'fa', 'ps', 'ur'];
+ return (rtlList.indexOf(gLanguage) >= 0) ? 'rtl' : 'ltr';
+ }
+ };
+ },
+
+ // translate an element or document fragment
+ translate: translateFragment,
+
+ // get (a clone of) the dictionary for the current locale
+ get dictionary() { return JSON.parse(JSON.stringify(gL10nData)); },
+
+ // this can be used to prevent race conditions
+ get readyState() { return gReadyState; },
+ ready: function l10n_ready(callback) {
+ if (!callback) {
+ return;
+ } else if (gReadyState == 'complete' || gReadyState == 'interactive') {
+ window.setTimeout(callback);
+ } else {
+ window.addEventListener('localized', callback);
+ }
+ }
+ };
+
+ consoleLog('library loaded.');
+})(this);
+
diff --git a/shared/js/l10n_date.js b/shared/js/l10n_date.js
new file mode 100644
index 0000000..eb461a3
--- /dev/null
+++ b/shared/js/l10n_date.js
@@ -0,0 +1,141 @@
+/* -*- Mode: js; js-indent-level: 2; indent-tabs-mode: nil -*- */
+/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
+
+'use strict';
+
+/**
+ * This lib relies on `l10n.js' to implement localizable date/time strings.
+ *
+ * The proposed `DateTimeFormat' object should provide all the features that are
+ * planned for the `Intl.DateTimeFormat' constructor, but the API does not match
+ * exactly the ES-i18n draft.
+ * - https://bugzilla.mozilla.org/show_bug.cgi?id=769872
+ * - http://wiki.ecmascript.org/doku.php?id=globalization:specification_drafts
+ *
+ * Besides, this `DateTimeFormat' object provides two features that aren't
+ * planned in the ES-i18n spec:
+ * - a `toLocaleFormat()' that really works (i.e. fully translated);
+ * - a `fromNow()' method to handle relative dates ("pretty dates").
+ *
+ * WARNING: this library relies on the non-standard `toLocaleFormat()' method,
+ * which is specific to Firefox -- no other browser is supported.
+ */
+
+navigator.mozL10n.DateTimeFormat = function(locales, options) {
+ var _ = navigator.mozL10n.get;
+
+ // https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Date/toLocaleFormat
+ function localeFormat(d, format) {
+ var tokens = format.match(/(%E.|%O.|%.)/g);
+
+ for (var i = 0; tokens && i < tokens.length; i++) {
+ var value = '';
+
+ // http://pubs.opengroup.org/onlinepubs/007908799/xsh/strftime.html
+ switch (tokens[i]) {
+ // localized day/month names
+ case '%a':
+ value = _('weekday-' + d.getDay() + '-short');
+ break;
+ case '%A':
+ value = _('weekday-' + d.getDay() + '-long');
+ break;
+ case '%b':
+ case '%h':
+ value = _('month-' + d.getMonth() + '-short');
+ break;
+ case '%B':
+ value = _('month-' + d.getMonth() + '-long');
+ break;
+
+ // like %H, but in 12-hour format and without any leading zero
+ case '%I':
+ value = d.getHours() % 12 || 12;
+ break;
+
+ // like %d, without any leading zero
+ case '%e':
+ value = d.getDate();
+ break;
+
+ // localized date/time strings
+ case '%c':
+ case '%x':
+ case '%X':
+ // ensure the localized format string doesn't contain any %c|%x|%X
+ var tmp = _('dateTimeFormat_' + tokens[i]);
+ if (tmp && !(/(%c|%x|%X)/).test(tmp)) {
+ value = localeFormat(d, tmp);
+ }
+ break;
+
+ // other tokens don't require any localization
+ }
+
+ format = format.replace(tokens[i], value || d.toLocaleFormat(tokens[i]));
+ }
+
+ return format;
+ }
+
+ // variant of John Resig's PrettyDate.js
+ function prettyDate(time, useCompactFormat) {
+ switch (time.constructor) {
+ case String: // timestamp
+ time = parseInt(time);
+ break;
+ case Date:
+ time = time.getTime();
+ break;
+ }
+
+ var secDiff = (Date.now() - time) / 1000;
+ if (isNaN(secDiff)) {
+ return _('incorrectDate');
+ }
+
+ var f = useCompactFormat ? '-short' : '-long';
+
+ if (secDiff >= 0) { // past
+ var dayDiff = Math.floor(secDiff / 86400);
+ if (secDiff < 3600) {
+ return _('minutesAgo' + f, { m: Math.floor(secDiff / 60) });
+ } else if (dayDiff === 0) {
+ return _('hoursAgo' + f, { h: Math.floor(secDiff / 3600) });
+ } else if (dayDiff < 10) {
+ return _('daysAgo' + f, { d: dayDiff });
+ }
+ }
+
+ if (secDiff < 0) { // future
+ secDiff = -secDiff;
+ dayDiff = Math.floor(secDiff / 86400);
+ if (secDiff < 3600) {
+ return _('inMinutes' + f, { m: Math.floor(secDiff / 60) });
+ } else if (dayDiff === 0) {
+ return _('inHours' + f, { h: Math.floor(secDiff / 3600) });
+ } else if (dayDiff < 10) {
+ return _('inDays' + f, { d: dayDiff });
+ }
+ }
+
+ // too far: return an absolute date
+ return localeFormat(new Date(time), '%x');
+ }
+
+ // API
+ return {
+ localeDateString: function localeDateString(d) {
+ return localeFormat(d, '%x');
+ },
+ localeTimeString: function localeTimeString(d) {
+ return localeFormat(d, '%X');
+ },
+ localeString: function localeString(d) {
+ return localeFormat(d, '%c');
+ },
+ localeFormat: localeFormat,
+ fromNow: prettyDate
+ };
+};
+
diff --git a/shared/js/manifest_helper.js b/shared/js/manifest_helper.js
new file mode 100644
index 0000000..d207603
--- /dev/null
+++ b/shared/js/manifest_helper.js
@@ -0,0 +1,26 @@
+/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- /
+/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
+
+'use strict';
+
+/**
+ * Helper object to access manifest information with locale support.
+ */
+
+var ManifestHelper = function(manifest) {
+ var localeRoot = manifest;
+ var locales = manifest.locales;
+
+ if (locales) {
+ var lang = document.documentElement.lang;
+
+ // If there is a manifest entry for the curret locale, use it, otherwise
+ // fallback on the default manifest.
+ localeRoot = locales[lang] || locales[lang.split('-')[0]] || manifest;
+ }
+
+ // Bind the localized property values.
+ for (var prop in manifest) {
+ this[prop] = localeRoot[prop] || manifest[prop];
+ }
+};
diff --git a/shared/js/media/README b/shared/js/media/README
new file mode 100644
index 0000000..05a67a0
--- /dev/null
+++ b/shared/js/media/README
@@ -0,0 +1 @@
+This directory contains files shared by the Camera, Gallery and Video apps.
diff --git a/shared/js/media/get_video_rotation.js b/shared/js/media/get_video_rotation.js
new file mode 100644
index 0000000..3359b92
--- /dev/null
+++ b/shared/js/media/get_video_rotation.js
@@ -0,0 +1,143 @@
+'use strict';
+
+//
+// Given an MP4/Quicktime based video file as a blob, read through its
+// atoms to find the track header "tkhd" atom and extract the rotation
+// matrix from it. Convert the matrix value to rotation in degrees and
+// pass that number to the specified callback function. If no value is
+// found but the video file is valid, pass null to the callback. If
+// any errors occur, pass an error message (a string) callback.
+//
+// See also:
+// http://androidxref.com/4.0.4/xref/frameworks/base/media/libstagefright/MPEG4Writer.cpp
+// https://developer.apple.com/library/mac/#documentation/QuickTime/QTFF/QTFFChap2/qtff2.html
+//
+function getVideoRotation(blob, rotationCallback) {
+
+ // A utility for traversing the tree of atoms in an MP4 file
+ function MP4Parser(blob, handlers) {
+ // Start off with a 1024 chunk from the start of the blob.
+ BlobView.get(blob, 0, 1024, function(data, error) {
+ // Make sure that the blob is, in fact, some kind of MP4 file
+ if (data.getASCIIText(4, 4) !== 'ftyp') {
+ handlers.errorHandler('not an MP4 file');
+ return;
+ }
+ parseAtom(data);
+ });
+
+ // Call this with a BlobView object that includes the first 16 bytes of
+ // an atom. It doesn't matter whether the body of the atom is included.
+ function parseAtom(data) {
+ var offset = data.sliceOffset + data.viewOffset; // atom position in blob
+ var size = data.readUnsignedInt(); // atom length
+ var type = data.readASCIIText(4); // atom type
+ var contentOffset = 8; // position of content
+
+ if (size === 0) {
+ // Zero size means the rest of the file
+ size = blob.size - offset;
+ }
+ else if (size === 1) {
+ // A size of 1 means the size is in bytes 8-15
+ size = data.readUnsignedInt() * 4294967296 + data.readUnsignedInt();
+ contentOffset = 16;
+ }
+
+ var handler = handlers[type] || handlers.defaultHandler;
+ if (typeof handler === 'function') {
+ // If the handler is a function, pass that function a
+ // DataView object that contains the entire atom
+ // including size and type. Then use the return value
+ // of the function as instructions on what to do next.
+ data.getMore(data.sliceOffset + data.viewOffset, size, function(atom) {
+ // Pass the entire atom to the handler function
+ var rv = handler(atom);
+
+ // If the return value is 'done', stop parsing.
+ // Otherwise, continue with the next atom.
+ // XXX: For more general parsing we need a way to pop some
+ // stack levels. A return value that is an atom name should mean
+ // pop back up to this atom type and go on to the next atom
+ // after that.
+ if (rv !== 'done') {
+ parseAtomAt(data, offset + size);
+ }
+ });
+ }
+ else if (handler === 'children') {
+ // If the handler is this string, then assume that the atom is
+ // a container atom and do its next child atom next
+ var skip = (type === 'meta') ? 4 : 0; // special case for meta atoms
+ parseAtomAt(data, offset + contentOffset + skip);
+ }
+ else if (handler === 'skip' || !handler) {
+ // Skip the atom entirely and go on to the next one.
+ // If there is no next one, call the eofHandler or just return
+ parseAtomAt(data, offset + size);
+ }
+ else if (handler === 'done') {
+ // Stop parsing
+ return;
+ }
+ }
+
+ function parseAtomAt(data, offset) {
+ if (offset >= blob.size) {
+ if (handlers.eofHandler)
+ handlers.eofHandler();
+ return;
+ }
+ else {
+ data.getMore(offset, 8, parseAtom);
+ }
+ }
+ }
+
+ // We want to loop through the top-level atoms until we find the 'moov'
+ // atom. Then, within this atom, there are one or more 'trak' atoms.
+ // Each 'trak' should begin with a 'tkhd' atom. The tkhd atom has
+ // a transformation matrix at byte 48. The matrix is 9 32 bit integers.
+ // We'll interpret those numbers as a rotation of 0, 90, 180 or 270.
+ // If the video has more than one track, we expect all of them to have
+ // the same rotation, so we'll only look at the first 'trak' atom that
+ // we find.
+ MP4Parser(blob, {
+ errorHandler: function(msg) { rotationCallback(msg); },
+ eofHandler: function() { rotationCallback(null); },
+ defaultHandler: 'skip', // Skip all atoms other than those listed below
+ moov: 'children', // Enumerate children of the moov atom
+ trak: 'children', // Enumerate children of the trak atom
+ tkhd: function(data) { // Pass the tkhd atom to this function
+ // The matrix begins at byte 48
+ data.advance(48);
+
+ var a = data.readUnsignedInt();
+ var b = data.readUnsignedInt();
+ data.advance(4); // we don't care about this number
+ var c = data.readUnsignedInt();
+ var d = data.readUnsignedInt();
+
+ if (a === 0 && d === 0) { // 90 or 270 degrees
+ if (b === 0x00010000 && c === 0xFFFF0000)
+ rotationCallback(90);
+ else if (b === 0xFFFF0000 && c === 0x00010000)
+ rotationCallback(270);
+ else
+ rotationCallback('unexpected rotation matrix');
+ }
+ else if (b === 0 && c === 0) { // 0 or 180 degrees
+ if (a === 0x00010000 && d === 0x00010000)
+ rotationCallback(0);
+ else if (a === 0xFFFF0000 && d === 0xFFFF0000)
+ rotationCallback(180);
+ else
+ rotationCallback('unexpected rotation matrix');
+ }
+ else {
+ rotationCallback('unexpected rotation matrix');
+ }
+ return 'done';
+ }
+ });
+}
diff --git a/shared/js/media/jpeg_metadata_parser.js b/shared/js/media/jpeg_metadata_parser.js
new file mode 100644
index 0000000..d8b2b02
--- /dev/null
+++ b/shared/js/media/jpeg_metadata_parser.js
@@ -0,0 +1,314 @@
+'use strict';
+
+//
+// This file defines a single function that asynchronously reads a
+// JPEG file (or blob) to determine its width and height and find the
+// location and size of the embedded preview image, if it has one. If
+// it succeeds, it passes an object containing this data to the
+// specified callback function. If it fails, it passes an error message
+// to the specified error function instead.
+//
+// This function is capable of parsing and returning EXIF data for a
+// JPEG file, but for speed, it ignores all EXIF data except the embedded
+// preview image.
+//
+// This function requires the BlobView utility class
+//
+function parseJPEGMetadata(file, metadataCallback, metadataError) {
+ // This is the object we'll pass to metadataCallback
+ var metadata = {};
+
+ // Start off reading a 16kb slice of the JPEG file.
+ // Hopefully, this will be all we need and everything else will
+ // be synchronous
+ BlobView.get(file, 0, Math.min(16 * 1024, file.size), function(data) {
+ if (data.byteLength < 2 ||
+ data.getUint8(0) !== 0xFF ||
+ data.getUint8(1) !== 0xD8) {
+ metadataError('Not a JPEG file');
+ return;
+ }
+
+ // Now start reading JPEG segments
+ // getSegment() and segmentHandler() are defined below.
+ getSegment(data, 2, segmentHandler);
+ });
+
+ // Read the JPEG segment at the specified offset and
+ // pass it to the callback function.
+ // Offset is relative to the current data offsets.
+ // We assume that data has enough data in it that we can
+ // can determine the size of the segment, and we guarantee that
+ // we read extra bytes so the next call works
+ function getSegment(data, offset, callback) {
+ try {
+ var header = data.getUint8(offset);
+ if (header !== 0xFF) {
+ metadataError('Malformed JPEG file: bad segment header');
+ return;
+ }
+
+ var type = data.getUint8(offset + 1);
+ var size = data.getUint16(offset + 2) + 2;
+
+ // the absolute position of the segment
+ var start = data.sliceOffset + data.viewOffset + offset;
+ // If this isn't the last segment in the file, add 4 bytes
+ // so we can read the size of the next segment
+ var isLast = (start + size >= file.size);
+ var length = isLast ? size : size + 4;
+
+ data.getMore(start, length,
+ function(data) {
+ callback(type, size, data, isLast);
+ });
+ }
+ catch (e) {
+ metadataError(e.toString() + '\n' + e.stack);
+ }
+ }
+
+ // This is a callback function for getNextSegment that handles the
+ // various types of segments we expect to see in a jpeg file
+ function segmentHandler(type, size, data, isLastSegment) {
+ try {
+ switch (type) {
+ case 0xC0: // Some actual image data, including image dimensions
+ case 0xC1:
+ case 0xC2:
+ case 0xC3:
+ // Get image dimensions
+ metadata.height = data.getUint16(5);
+ metadata.width = data.getUint16(7);
+
+ // We're done. All the EXIF data will come before this segment
+ // So call the callback
+ metadataCallback(metadata);
+ break;
+
+ case 0xE1: // APP1 segment. Probably holds EXIF metadata
+ parseAPP1(data);
+ /* fallthrough */
+
+ default:
+ // A segment we don't care about, so just go on and read the next one
+ if (isLastSegment) {
+ metadataError('unexpected end of JPEG file');
+ return;
+ }
+ getSegment(data, size, segmentHandler);
+ }
+ }
+ catch (e) {
+ metadataError(e.toString() + '\n' + e.stack);
+ }
+ }
+
+ function parseAPP1(data) {
+ if (data.getUint32(4, false) === 0x45786966) { // "Exif"
+ var exif = parseEXIFData(data);
+
+ if (exif.THUMBNAIL && exif.THUMBNAILLENGTH) {
+ var start = data.sliceOffset + data.viewOffset + 10 + exif.THUMBNAIL;
+ metadata.preview = {
+ start: start,
+ end: start + exif.THUMBNAILLENGTH
+ };
+ }
+ }
+ }
+
+ // Parse an EXIF segment from a JPEG file and return an object
+ // of metadata attributes. The argument must be a DataView object
+ function parseEXIFData(data) {
+ var exif = {};
+
+ var byteorder = data.getUint8(10);
+ if (byteorder === 0x4D) { // big endian
+ byteorder = false;
+ } else if (byteorder === 0x49) { // little endian
+ byteorder = true;
+ } else {
+ throw Error('invalid byteorder in EXIF segment');
+ }
+
+ if (data.getUint16(12, byteorder) !== 42) { // magic number
+ throw Error('bad magic number in EXIF segment');
+ }
+
+ var offset = data.getUint32(14, byteorder);
+
+ /*
+ * This is how we would parse all EXIF metadata more generally.
+ * I'm leaving this code in as a comment in case we need other EXIF
+ * data in the future.
+ *
+ parseIFD(data, offset + 10, byteorder, exif);
+
+ if (exif.EXIFIFD) {
+ parseIFD(data, exif.EXIFIFD + 10, byteorder, exif);
+ delete exif.EXIFIFD;
+ }
+
+ if (exif.GPSIFD) {
+ parseIFD(data, exif.GPSIFD + 10, byteorder, exif);
+ delete exif.GPSIFD;
+ }
+ */
+
+ // Instead of a general purpose EXIF parse, we're going to drill
+ // down directly to the thumbnail image.
+ // We're in IFD0 here. We want the offset of IFD1
+ var ifd0entries = data.getUint16(offset + 10, byteorder);
+ var ifd1 = data.getUint32(offset + 12 + 12 * ifd0entries, byteorder);
+ // If there is an offset for IFD1, parse that
+ if (ifd1 !== 0)
+ parseIFD(data, ifd1 + 10, byteorder, exif, true);
+
+ return exif;
+ }
+
+ function parseIFD(data, offset, byteorder, exif, onlyParseOne) {
+ var numentries = data.getUint16(offset, byteorder);
+ for (var i = 0; i < numentries; i++) {
+ parseEntry(data, offset + 2 + 12 * i, byteorder, exif);
+ }
+
+ if (onlyParseOne)
+ return;
+
+ var next = data.getUint32(offset + 2 + 12 * numentries, byteorder);
+ if (next !== 0 && next < file.size) {
+ parseIFD(data, next + 10, byteorder, exif);
+ }
+ }
+
+ // size, in bytes, of each TIFF data type
+ var typesize = [
+ 0, // Unused
+ 1, // BYTE
+ 1, // ASCII
+ 2, // SHORT
+ 4, // LONG
+ 8, // RATIONAL
+ 1, // SBYTE
+ 1, // UNDEFINED
+ 2, // SSHORT
+ 4, // SLONG
+ 8, // SRATIONAL
+ 4, // FLOAT
+ 8 // DOUBLE
+ ];
+
+ // This object maps EXIF tag numbers to their names.
+ // Only list the ones we want to bother parsing and returning.
+ // All others will be ignored.
+ var tagnames = {
+ /*
+ * We don't currently use any of these EXIF tags for anything.
+ *
+ *
+ '256': 'ImageWidth',
+ '257': 'ImageHeight',
+ '40962': 'PixelXDimension',
+ '40963': 'PixelYDimension',
+ '306': 'DateTime',
+ '315': 'Artist',
+ '33432': 'Copyright',
+ '36867': 'DateTimeOriginal',
+ '33434': 'ExposureTime',
+ '33437': 'FNumber',
+ '34850': 'ExposureProgram',
+ '34867': 'ISOSpeed',
+ '37377': 'ShutterSpeedValue',
+ '37378': 'ApertureValue',
+ '37379': 'BrightnessValue',
+ '37380': 'ExposureBiasValue',
+ '37382': 'SubjectDistance',
+ '37383': 'MeteringMode',
+ '37384': 'LightSource',
+ '37385': 'Flash',
+ '37386': 'FocalLength',
+ '41986': 'ExposureMode',
+ '41987': 'WhiteBalance',
+ '41991': 'GainControl',
+ '41992': 'Contrast',
+ '41993': 'Saturation',
+ '41994': 'Sharpness',
+ // These are special tags that we handle internally
+ '34665': 'EXIFIFD', // Offset of EXIF data
+ '34853': 'GPSIFD', // Offset of GPS data
+ */
+ '513': 'THUMBNAIL', // Offset of thumbnail
+ '514': 'THUMBNAILLENGTH' // Length of thumbnail
+ };
+
+ function parseEntry(data, offset, byteorder, exif) {
+ var tag = data.getUint16(offset, byteorder);
+ var tagname = tagnames[tag];
+
+ if (!tagname) // If we don't know about this tag type, skip it
+ return;
+
+ var type = data.getUint16(offset + 2, byteorder);
+ var count = data.getUint32(offset + 4, byteorder);
+
+ var total = count * typesize[type];
+ var valueOffset = total <= 4 ? offset + 8 :
+ data.getUint32(offset + 8, byteorder);
+ exif[tagname] = parseValue(data, valueOffset, type, count, byteorder);
+ }
+
+ function parseValue(data, offset, type, count, byteorder) {
+ if (type === 2) { // ASCII string
+ var codes = [];
+ for (var i = 0; i < count - 1; i++) {
+ codes[i] = data.getUint8(offset + i);
+ }
+ return String.fromCharCode.apply(String, codes);
+ } else {
+ if (count == 1) {
+ return parseOneValue(data, offset, type, byteorder);
+ } else {
+ var values = [];
+ var size = typesize[type];
+ for (var i = 0; i < count; i++) {
+ values[i] = parseOneValue(data, offset + size * i, type, byteorder);
+ }
+ return values;
+ }
+ }
+ }
+
+ function parseOneValue(data, offset, type, byteorder) {
+ switch (type) {
+ case 1: // BYTE
+ case 7: // UNDEFINED
+ return data.getUint8(offset);
+ case 2: // ASCII
+ // This case is handed in parseValue
+ return null;
+ case 3: // SHORT
+ return data.getUint16(offset, byteorder);
+ case 4: // LONG
+ return data.getUint32(offset, byteorder);
+ case 5: // RATIONAL
+ return data.getUint32(offset, byteorder) /
+ data.getUint32(offset + 4, byteorder);
+ case 6: // SBYTE
+ return data.getInt8(offset);
+ case 8: // SSHORT
+ return data.getInt16(offset, byteorder);
+ case 9: // SLONG
+ return data.getInt32(offset, byteorder);
+ case 10: // SRATIONAL
+ return data.getInt32(offset, byteorder) /
+ data.getInt32(offset + 4, byteorder);
+ case 11: // FLOAT
+ return data.getFloat32(offset, byteorder);
+ case 12: // DOUBLE
+ return data.getFloat64(offset, byteorder);
+ }
+ return null;
+ }
+}
diff --git a/shared/js/media/media_frame.js b/shared/js/media/media_frame.js
new file mode 100644
index 0000000..aaf8fbe
--- /dev/null
+++ b/shared/js/media/media_frame.js
@@ -0,0 +1,537 @@
+/*
+ * media_frame.js:
+ *
+ * A MediaFrame displays a photo or a video. The gallery app uses
+ * three side by side to support smooth panning from one item to the
+ * next. The Camera app uses one for image and video preview. The
+ * Gallery app's open activity uses one of these to display the opened
+ * item.
+ *
+ * MediaFrames have different behavior depending on whether they display
+ * images or videos. Photo frames allow the user to zoom and pan on the photo.
+ * Video frames allow the user to play and pause but don't allow zooming.
+ *
+ * When a frame is displaying a video, it handles mouse events.
+ * When display a picture, however, it expects the client to handle events
+ * and call the pan() and zoom() methods.
+ *
+ * The pan() method is a little unusual. It "uses" as much of the pan
+ * event as it can, and returns a number indicating how much of the
+ * horizontal motion it did not use. The gallery uses this returned
+ * value for transitioning between frames. If a frame displays a
+ * photo that is not zoomed in at all, then it can't use any of the
+ * pan, and returns the full amount which the gallery app turns into a
+ * panning motion between frames. But if the photo is zoomed in, then
+ * the MediaFrame will just move the photo within itself, if it can, and
+ * return 0.
+ *
+ * Much of the code in this file used to be part of the PhotoState class.
+ */
+function MediaFrame(container, includeVideo) {
+ if (typeof container === 'string')
+ container = document.getElementById(container);
+ this.container = container;
+ this.image = document.createElement('img');
+ this.container.appendChild(this.image);
+ this.image.style.display = 'none';
+ if (includeVideo !== false) {
+ this.video = new VideoPlayer(container);
+ this.video.hide();
+ }
+ this.displayingVideo = false;
+ this.displayingImage = false;
+ this.blob = null;
+ this.url = null;
+}
+
+MediaFrame.prototype.displayImage = function displayImage(blob, width, height,
+ preview)
+{
+ this.clear(); // Reset everything
+
+ // Remember what we're displaying
+ this.blob = blob;
+ this.fullsizeWidth = width;
+ this.fullsizeHeight = height;
+ this.preview = preview;
+
+ // Keep track of what kind of content we have
+ this.displayingImage = true;
+
+ // Make the image element visible
+ this.image.style.display = 'block';
+
+ // If the preview is at least as big as the screen, display that.
+ // Otherwise, display the full-size image.
+ if (preview &&
+ (preview.width >= window.innerWidth ||
+ preview.height >= window.innerHeight)) {
+ this.displayingPreview = true;
+ this._displayImage(blob.slice(preview.start, preview.end, 'image/jpeg'),
+ preview.width, preview.height);
+ }
+ else {
+ this._displayImage(blob, width, height);
+ }
+};
+
+// A utility function we use to display the full-size image or the
+// preview The last two arguments are optimizations used by
+// switchToFullSizeImage() to make the transition from preview to
+// fullscreen smooth. If waitForPaint is true, then this function will
+// keep the old image on the screen until the new image is painted
+// over it so we (hopefully) don't end up with a blank screen or
+// flash. And if callback is specified, it will call the callback
+// when thew new images is visible on the screen. If either of those
+// arguments are specified, the width and height must be specified.
+MediaFrame.prototype._displayImage = function _displayImage(blob, width, height,
+ waitForPaint,
+ callback)
+{
+ var self = this;
+ var oldImage;
+
+ // Create a URL for the blob (or preview blob)
+ if (this.url)
+ URL.revokeObjectURL(this.url);
+ this.url = URL.createObjectURL(blob);
+
+ // If we don't know the width or the height yet, then set up an event
+ // handler to set the image size and position once it is loaded.
+ // This happens for the open activity.
+ if (!width || !height) {
+ this.image.src = this.url;
+ this.image.addEventListener('load', function onload() {
+ this.removeEventListener('load', onload);
+ self.itemWidth = this.naturalWidth;
+ self.itemHeight = this.naturalHeight;
+ self.computeFit();
+ self.setPosition();
+ });
+ return;
+ }
+
+ // Otherwise, we have a width and height, and we may also have to handle
+ // the waitForPaint and callback arguments
+
+ // If waitForPaint is set, then keep the old image around and displayed
+ // until the new image is loaded.
+ if (waitForPaint) {
+ // Remember the old image
+ oldImage = this.image;
+
+ // Create a new element to load the new image into.
+ // Insert it into the frame, but don't remove the old image yet
+ this.image = document.createElement('img');
+ this.container.appendChild(this.image);
+
+ // Change the old image slightly to give the user some immediate
+ // feedback that something is happening
+ oldImage.classList.add('swapping');
+ }
+
+ // Start loading the new image
+ this.image.src = this.url;
+ // Update image size and position
+ this.itemWidth = width;
+ this.itemHeight = height;
+ this.computeFit();
+ this.setPosition();
+
+ // If waitForPaint is set, or if there is a callback, then we need to
+ // run some code when the new image has loaded and been painted.
+ if (waitForPaint || callback) {
+ whenLoadedAndVisible(this.image, 1000, function() {
+ if (waitForPaint) {
+ // Remove the old image now that the new one is visible
+ self.container.removeChild(oldImage);
+ oldImage.src = null;
+ }
+
+ if (callback) {
+ // Let the caller know that the new image is ready, but
+ // wait for an animation frame before doing it. The point of
+ // using mozRequestAnimationFrame here is that it gives the
+ // removeChild() call above a chance to take effect.
+ mozRequestAnimationFrame(function() {
+ callback();
+ });
+ }
+ });
+ }
+
+ // Wait until the load event on the image fires, and then wait for a
+ // MozAfterPaint event after that, and then, finally, invoke the
+ // callback. Don't wait more than the timeout, though: we need to
+ // ensure that we always call the callback even if the image does not
+ // load or if we don't get a MozAfterPaint event.
+ function whenLoadedAndVisible(image, timeout, callback) {
+ var called = false;
+ var timer = setTimeout(function()
+ {
+ called = true;
+ callback();
+ },
+ timeout || 1000);
+
+ image.addEventListener('load', function onload() {
+ image.removeEventListener('load', onload);
+ window.addEventListener('MozAfterPaint', function onpaint() {
+ window.removeEventListener('MozAfterPaint', onpaint);
+ clearTimeout(timer);
+ if (!called) {
+ callback();
+ }
+ });
+ });
+ }
+};
+
+MediaFrame.prototype._switchToFullSizeImage = function _switchToFull(cb) {
+ if (this.displayingImage && this.displayingPreview) {
+ this.displayingPreview = false;
+ this._displayImage(this.blob, this.fullsizeWidth, this.fullsizeHeight,
+ true, cb);
+ }
+};
+
+MediaFrame.prototype._switchToPreviewImage = function _switchToPreview() {
+ if (this.displayingImage && !this.displayingPreview) {
+ this.displayingPreview = true;
+ this._displayImage(this.blob.slice(this.preview.start,
+ this.preview.end,
+ 'image/jpeg'),
+ this.preview.width,
+ this.preview.height);
+ }
+};
+
+MediaFrame.prototype.displayVideo = function displayVideo(blob, width, height,
+ rotation)
+{
+ if (!this.video)
+ return;
+
+ this.clear(); // reset everything
+
+ // Keep track of what kind of content we have
+ this.displayingVideo = true;
+
+ // Show the video player and hide the image
+ this.video.show();
+
+ // Remember the blob
+ this.blob = blob;
+
+ // Get a new URL for this blob
+ this.url = URL.createObjectURL(blob);
+
+ // Display it in the video element.
+ // The VideoPlayer class takes care of positioning itself, so we
+ // don't have to do anything here with computeFit() or setPosition()
+ this.video.load(this.url, rotation || 0);
+};
+
+// Reset the frame state, release any urls and and hide everything
+MediaFrame.prototype.clear = function clear() {
+ // Reset the saved state
+ this.displayingImage = false;
+ this.displayingPreview = false;
+ this.displayingVideo = false;
+ this.itemWidth = this.itemHeight = null;
+ this.blob = null;
+ this.fullsizeWidth = this.fullsizeHeight = null;
+ this.preview = null;
+ this.fit = null;
+ if (this.url) {
+ URL.revokeObjectURL(this.url);
+ this.url = null;
+ }
+
+ // Hide the image
+ this.image.style.display = 'none';
+ this.image.src = null; // XXX: use about:blank or '' here?
+
+ // Hide the video player
+ if (this.video) {
+ this.video.hide();
+
+ // If the video player has its src set, clear it and release resources
+ // We do this in a roundabout way to avoid getting a warning in the console
+ if (this.video.player.src) {
+ this.video.player.removeAttribute('src');
+ this.video.player.load();
+ }
+ }
+};
+
+// Set the item's position based on this.fit
+// The VideoPlayer object fits itself to its container, and it
+// can't be zoomed or panned, so we only need to do this for images
+MediaFrame.prototype.setPosition = function setPosition() {
+ if (!this.fit || !this.displayingImage)
+ return;
+
+ this.image.style.transform =
+ 'translate(' + this.fit.left + 'px,' + this.fit.top + 'px) ' +
+ 'scale(' + this.fit.scale + ')';
+};
+
+MediaFrame.prototype.computeFit = function computeFit() {
+ if (!this.displayingImage)
+ return;
+ this.viewportWidth = this.container.offsetWidth;
+ this.viewportHeight = this.container.offsetHeight;
+
+ var scalex = this.viewportWidth / this.itemWidth;
+ var scaley = this.viewportHeight / this.itemHeight;
+ var scale = Math.min(Math.min(scalex, scaley), 1);
+
+ // Set the image size and position
+ var width = Math.floor(this.itemWidth * scale);
+ var height = Math.floor(this.itemHeight * scale);
+
+ this.fit = {
+ width: width,
+ height: height,
+ left: Math.floor((this.viewportWidth - width) / 2),
+ top: Math.floor((this.viewportHeight - height) / 2),
+ scale: scale,
+ baseScale: scale
+ };
+};
+
+MediaFrame.prototype.reset = function reset() {
+ // If we're not displaying the preview image, but we have one,
+ // and it is the right size, then switch to it
+ if (this.displayingImage && !this.displayingPreview && this.preview &&
+ (this.preview.width >= window.innerWidth ||
+ this.preview.height >= window.innerHeight)) {
+ this._switchToPreviewImage(); // resets image size and position
+ return;
+ }
+
+ // Otherwise, if we are displaying the preview image but it is no
+ // longer big enough for the screen (such as after a resize event)
+ // then switch to full size. This case should be rare.
+ if (this.displayingImage && this.displayingPreview &&
+ this.preview.width < window.innerWidth &&
+ this.preview.height < window.innerHeight) {
+ this._switchToFullSizeImage(); // resets image size and position
+ return;
+ }
+
+ // Otherwise, just resize and position the item we're already displaying
+ this.computeFit();
+ this.setPosition();
+};
+
+// We call this from the resize handler when the user rotates the
+// screen or when going into or out of fullscreen mode. If the user
+// has not zoomed in, then we just fit the image to the new size (same
+// as reset). But if the user has zoomed in (and we need to stay
+// zoomed for the new size) then we adjust the fit properties so that
+// the pixel that was at the center of the screen before remains at
+// the center now, or as close as possible
+MediaFrame.prototype.resize = function resize() {
+ var oldWidth = this.viewportWidth;
+ var oldHeight = this.viewportHeight;
+ var newWidth = this.container.offsetWidth;
+ var newHeight = this.container.offsetHeight;
+
+ var oldfit = this.fit; // The current image fit
+
+ // If this is triggered by a resize event before the frame has computed
+ // its size, then there is nothing we can do yet.
+ if (!oldfit)
+ return;
+
+ // Compute the new fit.
+ // This updates the the viewportWidth, viewportHeight and fit properties
+ this.computeFit();
+
+ // This is how the image would fit at the new screen size
+ var newfit = this.fit;
+
+ // If no zooming has been done, then a resize is just a reset.
+ // The same is true if the new fit base scale is greater than the
+ // old scale.
+ if (oldfit.scale === oldfit.baseScale || newfit.baseScale > oldfit.scale) {
+ this.reset();
+ return;
+ }
+
+ // Otherwise, just adjust the old fit as needed and use that so we
+ // retain the zoom factor.
+ oldfit.left += (newWidth - oldWidth) / 2;
+ oldfit.top += (newHeight - oldHeight) / 2;
+ oldfit.baseScale = newfit.baseScale;
+ this.fit = oldfit;
+
+ // Reposition this image without resetting the zoom
+ this.setPosition();
+};
+
+// Zoom in by the specified factor, adjusting the pan amount so that
+// the image pixels at (centerX, centerY) remain at that position.
+// Assume that zoom gestures can't be done in the middle of swipes, so
+// if we're calling zoom, then the swipe property will be 0.
+// If time is specified and non-zero, then we set a CSS transition
+// to animate the zoom.
+MediaFrame.prototype.zoom = function zoom(scale, centerX, centerY, time) {
+ // Ignore zooms if we're not displaying an image
+ if (!this.displayingImage)
+ return;
+
+ // If we were displaying the preview, switch to the full-size image
+ if (this.displayingPreview) {
+ // If we want to to animate the zoom, then switch images, wait
+ // for the new one to load, and call this function again to process
+ // the zoom and animation. But if we're not animating, then just
+ // switch images and continue.
+ if (time) { // if animating
+ var self = this;
+ this._switchToFullSizeImage(function() {
+ self.zoom(scale, centerX, centerY, time);
+ });
+ return;
+ }
+ else {
+ this.switching = true;
+ var self = this;
+ this._switchToFullSizeImage(function() { self.switching = false; });
+ }
+ }
+
+ // Never zoom in farther than the native resolution of the image
+ if (this.fit.scale * scale > 1) {
+ scale = 1 / (this.fit.scale);
+ }
+ // And never zoom out to make the image smaller than it would normally be
+ else if (this.fit.scale * scale < this.fit.baseScale) {
+ scale = this.fit.baseScale / this.fit.scale;
+ }
+
+ this.fit.scale = this.fit.scale * scale;
+
+ // Change the size of the photo
+ this.fit.width = Math.floor(this.itemWidth * this.fit.scale);
+ this.fit.height = Math.floor(this.itemHeight * this.fit.scale);
+
+ // centerX and centerY are in viewport coordinates.
+ // These are the photo coordinates displayed at that point in the viewport
+ var photoX = centerX - this.fit.left;
+ var photoY = centerY - this.fit.top;
+
+ // After zooming, these are the new photo coordinates.
+ // Note we just use the relative scale amount here, not this.fit.scale
+ var photoX = Math.floor(photoX * scale);
+ var photoY = Math.floor(photoY * scale);
+
+ // To keep that point still, here are the new left and top values we need
+ this.fit.left = centerX - photoX;
+ this.fit.top = centerY - photoY;
+
+ // Now make sure we didn't pan too much: If the image fits on the
+ // screen, center it. If the image is bigger than the screen, then
+ // make sure we haven't gone past any edges
+ if (this.fit.width <= this.viewportWidth) {
+ this.fit.left = (this.viewportWidth - this.fit.width) / 2;
+ }
+ else {
+ // Don't let the left of the photo be past the left edge of the screen
+ if (this.fit.left > 0)
+ this.fit.left = 0;
+
+ // Right of photo shouldn't be to the left of the right edge
+ if (this.fit.left + this.fit.width < this.viewportWidth) {
+ this.fit.left = this.viewportWidth - this.fit.width;
+ }
+ }
+
+ if (this.fit.height <= this.viewportHeight) {
+ this.fit.top = (this.viewportHeight - this.fit.height) / 2;
+ }
+ else {
+ // Don't let the top of the photo be below the top of the screen
+ if (this.fit.top > 0)
+ this.fit.top = 0;
+
+ // bottom of photo shouldn't be above the bottom of screen
+ if (this.fit.top + this.fit.height < this.viewportHeight) {
+ this.fit.top = this.viewportHeight - this.fit.height;
+ }
+ }
+
+ if (this.switching)
+ return;
+
+ // If a time was specified, set up a transition so that the
+ // call to setPosition() below is animated
+ if (time) {
+ // If a time was specfied, animate the transformation
+ this.image.style.transition = 'transform ' + time + 'ms ease';
+ var self = this;
+ this.image.addEventListener('transitionend', function done(e) {
+ self.image.removeEventListener('transitionend', done);
+ self.image.style.transition = null;
+ });
+ }
+
+ this.setPosition();
+};
+
+// If the item being displayed is larger than the continer, pan it by
+// the specified amounts. Return the "unused" dx amount for the gallery app
+// to use for sideways swiping
+MediaFrame.prototype.pan = function(dx, dy) {
+ // We can only pan images, so return the entire dx amount
+ if (!this.displayingImage) {
+ return dx;
+ }
+
+ // Handle panning in the y direction first, since it is easier.
+ // Don't pan in the y direction if we already fit on the screen
+ if (this.fit.height > this.viewportHeight) {
+ this.fit.top += dy;
+
+ // Don't let the top of the photo be below the top of the screen
+ if (this.fit.top > 0)
+ this.fit.top = 0;
+
+ // bottom of photo shouldn't be above the bottom of screen
+ if (this.fit.top + this.fit.height < this.viewportHeight)
+ this.fit.top = this.viewportHeight - this.fit.height;
+ }
+
+ // Now handle the X dimension. If we've already panned as far as we can
+ // within the image (or if it isn't zoomed in) then return the "extra"
+ // unused dx amount to the caller so that the caller can use them to
+ // shift the frame left or right.
+ var extra = 0;
+
+ if (this.fit.width <= this.viewportWidth) {
+ // In this case, the photo isn't zoomed in, so it is all extra
+ extra = dx;
+ }
+ else {
+ this.fit.left += dx;
+
+ // If this would take the left edge of the photo past the
+ // left edge of the screen, then some of the motion is extra
+ if (this.fit.left > 0) {
+ extra = this.fit.left;
+ this.fit.left = 0;
+ }
+
+ // Or, if this would take the right edge of the photo past the
+ // right edge of the screen, then we've got extra.
+ if (this.fit.left + this.fit.width < this.viewportWidth) {
+ extra = this.fit.left + this.fit.width - this.viewportWidth;
+ this.fit.left = this.viewportWidth - this.fit.width;
+ }
+ }
+
+ this.setPosition();
+ return extra;
+};
diff --git a/shared/js/media/video_player.js b/shared/js/media/video_player.js
new file mode 100644
index 0000000..c79bb8b
--- /dev/null
+++ b/shared/js/media/video_player.js
@@ -0,0 +1,313 @@
+'use strict';
+
+// Create a <video> element and <div> containing a video player UI and
+// add them to the specified container. The UI requires a GestureDetector
+// to be running for the container or one of its ancestors.
+function VideoPlayer(container) {
+ if (typeof container === 'string')
+ container = document.getElementById(container);
+
+ function newelt(parent, type, classes) {
+ var e = document.createElement(type);
+ if (classes)
+ e.className = classes;
+ parent.appendChild(e);
+ return e;
+ }
+
+ // This copies the controls structure of the Video app
+ var player = newelt(container, 'video', 'videoPlayer');
+ var controls = newelt(container, 'div', 'videoPlayerControls');
+ var playbutton = newelt(controls, 'button', 'videoPlayerPlayButton');
+ var footer = newelt(controls, 'div', 'videoPlayerFooter hidden');
+ var pausebutton = newelt(footer, 'button', 'videoPlayerPauseButton');
+ var slider = newelt(footer, 'div', 'videoPlayerSlider');
+ var elapsedText = newelt(slider, 'span', 'videoPlayerElapsedText');
+ var progress = newelt(slider, 'div', 'videoPlayerProgress');
+ var backgroundBar = newelt(progress, 'div', 'videoPlayerBackgroundBar');
+ var elapsedBar = newelt(progress, 'div', 'videoPlayerElapsedBar');
+ var playHead = newelt(progress, 'div', 'videoPlayerPlayHead');
+ var durationText = newelt(slider, 'span', 'videoPlayerDurationText');
+
+ this.player = player;
+ this.controls = controls;
+
+ player.preload = 'metadata';
+
+ var self = this;
+ var controlsHidden = false;
+ var dragging = false;
+ var pausedBeforeDragging = false;
+ var screenLock; // keep the screen on when playing
+ var endedTimer;
+ var rotation; // Do we have to rotate the video? Set by load()
+
+ this.load = function(url, rotate) {
+ rotation = rotate || 0;
+ player.mozAudioChannelType = 'content';
+ player.src = url;
+ };
+
+ // Call this when the container size changes
+ this.setPlayerSize = setPlayerSize;
+
+ // Set up everything for the initial paused state
+ this.pause = function pause() {
+ // Pause video playback
+ player.pause();
+
+ // Hide the pause button and slider
+ footer.classList.add('hidden');
+ controlsHidden = true;
+
+ // Show the big central play button
+ playbutton.classList.remove('hidden');
+
+ // Unlock the screen so it can sleep on idle
+ if (screenLock) {
+ screenLock.unlock();
+ screenLock = null;
+ }
+
+ if (this.onpaused)
+ this.onpaused();
+ };
+
+ // Set up the playing state
+ this.play = function play() {
+ // If we're at the end of the video, restart at the beginning.
+ // This seems to happen automatically when an 'ended' event was fired.
+ // But some media types don't generate the ended event and don't
+ // automatically go back to the start.
+ if (player.currentTime >= player.duration - 0.5)
+ player.currentTime = 0;
+
+ // Start playing the video
+ player.play();
+
+ // Hide the play button
+ playbutton.classList.add('hidden');
+
+ // Show the controls
+ footer.classList.remove('hidden');
+ controlsHidden = false;
+
+ // Don't let the screen go to sleep
+ if (!screenLock)
+ screenLock = navigator.requestWakeLock('screen');
+
+ if (this.onplaying)
+ this.onplaying();
+ };
+
+ // Hook up the play button
+ playbutton.addEventListener('tap', function(e) {
+ // If we're paused, go to the play state
+ if (player.paused) {
+ self.play();
+ }
+ e.stopPropagation();
+ });
+
+ // Hook up the pause button
+ pausebutton.addEventListener('tap', function(e) {
+ self.pause();
+ e.stopPropagation();
+ });
+
+ // A click anywhere else on the screen should toggle the footer
+ // But only when the video is playing.
+ controls.addEventListener('tap', function(e) {
+ if (e.target === controls && !player.paused) {
+ footer.classList.toggle('hidden');
+ controlsHidden = !controlsHidden;
+ }
+ });
+
+ // Set the video size and duration when we get metadata
+ player.onloadedmetadata = function() {
+ durationText.textContent = formatTime(player.duration);
+ setPlayerSize();
+ // start off in the paused state
+ self.pause();
+ };
+
+ // Also resize the player on a resize event
+ // (when the user rotates the phone)
+ window.addEventListener('resize', function() {
+ setPlayerSize();
+ });
+
+ // If we reach the end of a video, reset to beginning
+ // This isn't always reliable, so we also set a timer in updateTime()
+ player.onended = ended;
+
+ function ended() {
+ if (dragging)
+ return;
+ if (endedTimer) {
+ clearTimeout(endedTimer);
+ endedTimer = null;
+ }
+ self.pause();
+ };
+
+ // Update the slider and elapsed time as the video plays
+ player.ontimeupdate = updateTime;
+
+ // Set the elapsed time and slider position
+ function updateTime() {
+ if (!controlsHidden) {
+ elapsedText.textContent = formatTime(player.currentTime);
+
+ // We can't update a progress bar if we don't know how long
+ // the video is. It is kind of a bug that the <video> element
+ // can't figure this out for ogv videos.
+ if (player.duration === Infinity || player.duration === 0)
+ return;
+
+ var percent = (player.currentTime / player.duration) * 100 + '%';
+ elapsedBar.style.width = percent;
+ playHead.style.left = percent;
+ }
+
+ // Since we don't always get reliable 'ended' events, see if
+ // we've reached the end this way.
+ // See: https://bugzilla.mozilla.org/show_bug.cgi?id=783512
+ // If we're within 1 second of the end of the video, register
+ // a timeout a half a second after we'd expect an ended event.
+ if (!endedTimer) {
+ if (!dragging && player.currentTime >= player.duration - 1) {
+ var timeUntilEnd = (player.duration - player.currentTime + .5);
+ endedTimer = setTimeout(ended, timeUntilEnd * 1000);
+ }
+ }
+ else if (dragging && player.currentTime < player.duration - 1) {
+ // If there is a timer set and we drag away from the end, cancel the timer
+ clearTimeout(endedTimer);
+ endedTimer = null;
+ }
+ }
+
+ // Make the video fit the container
+ function setPlayerSize() {
+ var containerWidth = container.clientWidth;
+ var containerHeight = container.clientHeight;
+
+ // Don't do anything if we don't know our size.
+ // This could happen if we get a resize event before our metadata loads
+ if (!player.videoWidth || !player.videoHeight)
+ return;
+
+ var width, height; // The size the video will appear, after rotation
+ switch (rotation) {
+ case 0:
+ case 180:
+ width = player.videoWidth;
+ height = player.videoHeight;
+ break;
+ case 90:
+ case 270:
+ width = player.videoHeight;
+ height = player.videoWidth;
+ }
+
+ var xscale = containerWidth / width;
+ var yscale = containerHeight / height;
+ var scale = Math.min(xscale, yscale);
+
+ // Scale large videos down, and scale small videos up.
+ // This might reduce image quality for small videos.
+ width *= scale;
+ height *= scale;
+
+ var left = ((containerWidth - width) / 2);
+ var top = ((containerHeight - height) / 2);
+
+ var transform;
+ switch (rotation) {
+ case 0:
+ transform = 'translate(' + left + 'px,' + top + 'px)';
+ break;
+ case 90:
+ transform =
+ 'translate(' + (left + width) + 'px,' + top + 'px) ' +
+ 'rotate(90deg)';
+ break;
+ case 180:
+ transform =
+ 'translate(' + (left + width) + 'px,' + (top + height) + 'px) ' +
+ 'rotate(180deg)';
+ break;
+ case 270:
+ transform =
+ 'translate(' + left + 'px,' + (top + height) + 'px) ' +
+ 'rotate(270deg)';
+ break;
+ }
+
+ transform += ' scale(' + scale + ')';
+
+ player.style.transform = transform;
+ }
+
+ // handle drags on the time slider
+ slider.addEventListener('pan', function pan(e) {
+ e.stopPropagation();
+ // We can't do anything if we don't know our duration
+ if (player.duration === Infinity)
+ return;
+
+ if (!dragging) { // Do this stuff on the first pan event only
+ dragging = true;
+ pausedBeforeDragging = player.paused;
+ if (!pausedBeforeDragging) {
+ player.pause();
+ }
+ }
+
+ var rect = backgroundBar.getBoundingClientRect();
+ var position = (e.detail.position.clientX - rect.left) / rect.width;
+ var pos = Math.min(Math.max(position, 0), 1);
+ player.currentTime = player.duration * pos;
+ updateTime();
+ });
+
+ slider.addEventListener('swipe', function swipe(e) {
+ e.stopPropagation();
+ dragging = false;
+ if (player.currentTime >= player.duration) {
+ self.pause();
+ } else if (!pausedBeforeDragging) {
+ player.play();
+ }
+ });
+
+ function formatTime(time) {
+ function padLeft(num, length) {
+ var r = String(num);
+ while (r.length < length) {
+ r = '0' + r;
+ }
+ return r;
+ }
+
+ time = Math.round(time);
+ var minutes = Math.floor(time / 60);
+ var seconds = time % 60;
+ if (minutes < 60) {
+ return padLeft(minutes, 2) + ':' + padLeft(seconds, 2);
+ }
+ return '';
+ }
+}
+
+VideoPlayer.prototype.hide = function() {
+ this.player.style.display = 'none';
+ this.controls.style.display = 'none';
+};
+
+VideoPlayer.prototype.show = function() {
+ this.player.style.display = 'block';
+ this.controls.style.display = 'block';
+};
diff --git a/shared/js/mediadb.js b/shared/js/mediadb.js
new file mode 100644
index 0000000..cfdab33
--- /dev/null
+++ b/shared/js/mediadb.js
@@ -0,0 +1,1532 @@
+/* -*- Mode: js; js-indent-level: 2; indent-tabs-mode: nil -*- */
+/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
+
+'use strict';
+
+/**
+ * MediaDB.js: a simple interface to DeviceStorage and IndexedDB that serves
+ * as a model of the filesystem and provides easy access to the
+ * user's media files and their metadata.
+ *
+ * Gaia's media apps (Gallery, Music, Videos) read media files from the phone
+ * using the DeviceStorage API. They need to keep track of the complete list of
+ * media files, as well as the metadata (image thumbnails, song titles, etc.)
+ * they have extracted from those files. It would be much too slow to scan the
+ * filesystem and read all the metadata from all files each time the apps starts
+ * up, so the apps need to store the list of files and metadata in an IndexedDB
+ * database. This library integrates both DeviceStorage and IndexedDB into a
+ * single API. It keeps the database in sync with the filesystem and provides
+ * notifications when files are added or deleted.
+ *
+ * CONSTRUCTOR
+ *
+ * Create a MediaDB object with the MediaDB() constructor. It takes three
+ * arguments:
+ *
+ * mediaType:
+ * one of the DeviceStorage media types: "pictures", "movies" or "music".
+ *
+ * metadataParser:
+ * your metadata parser function. This function should expect three
+ * arguments. It will be called with a file to parse and two callback
+ * functions. It should read metadata from the file and then pass an object
+ * of metadata to the first callback. If parsing fails it should pass an
+ * Error object or error message to the second callback. If you omit this
+ * argument or pass null, a dummy parser that invokes the callback with an
+ * empty object will be used instead.
+ *
+ * options:
+ * An optional object containing additional MediaDB options.
+ * Supported options are:
+ *
+ * directory:
+ * a subdirectory of the DeviceStorage directory. If you are only
+ * interested in images in the screenshots/ subdirectory for example,
+ * you can set this property to "screenshots/".
+ *
+ * mimeTypes:
+ * an array of MIME types that specifies the kind of files you are
+ * interested in and that your metadata parser function knows how to
+ * handle. DeviceStorage infers MIME type from filename extension and
+ * filters the files it returns based on their extension. Use this
+ * property if you want to restrict the set of mime types further.
+ *
+ * indexes:
+ * an array of IndexedDB key path specifications that specify which
+ * properties of each media record should be indexed. If you want to
+ * search or sort on anything other than the file name and date you
+ * should set this property. "size", and "type" are valid keypaths as
+ * is "metadata.x" where x is any metadata property returned by your
+ * metadata parser.
+ *
+ * version:
+ * The version of your IndexedDB database. The default value is 1
+ * Setting it to a larger value will delete all data in the database
+ * and rebuild it from scratch. If you ever change your metadata parser
+ * function or alter the array of indexes.
+ *
+ * autoscan:
+ * Whether MediaDB should automatically scan every time it becomes
+ * ready. The default is true. If you set this to false you are
+ * responsible for calling scan() in response to the 'ready' event.
+ *
+ * batchHoldTime:
+ * How long (in ms) to wait after finding a new file during a scan
+ * before reporting it. Longer hold times allow more batching of
+ * changes. The default is 100ms.
+ *
+ * batchSize:
+ * When batching changes, don't allow the batches to exceed this
+ * amount. The default is 0 which means no maximum batch size.
+ *
+ * MediaDB STATE
+ *
+ * A MediaDB object must asynchronously open a connection to its database, and
+ * asynchronously check on the availability of device storage, which means that
+ * it is not ready for use when first created. After calling the MediaDB()
+ * constructor, register an event listener for 'ready' events with
+ * addEventListener() or by setting the onready property. You must not use
+ * the MediaDB object until the ready event has been delivered or until
+ * the state property is set to MediaDB.READY.
+ *
+ * The DeviceStorage API is not always available, and MediaDB is not usable if
+ * DeviceStorage is not usable. If the user removes the SD card from their
+ * phone, then DeviceStorage will not be able to read or write files,
+ * obviously. Also, when a USB Mass Storage session is in progress,
+ * DeviceStorage is not available either. If DeviceStorage is not available
+ * when a MediaDB object is created, an 'unavailable' event will be fired
+ * instead of a 'ready' event. Subsequently, a 'ready' event will be fired
+ * whenever DeviceStorage becomes available, and 'unavailable' will be fired
+ * whenever DeviceStorage becomes unavailable. Media apps can handle the
+ * unavailable case by displaying an informative message in an overlay that
+ * prevents all user interaction with the app.
+ *
+ * The 'ready' and 'unavailable' events signal changes to the state of a
+ * MediaDB object. The state is also available in the state property of the
+ * object. The possible values of this property are the following:
+ *
+ * Value Constant Meaning
+ * ----------------------------------------------------------------------
+ * 'opening' MediaDB.OPENING MediaDB is initializing itself
+ * 'ready' MediaDB.READY MediaDB is available and ready for use
+ * 'nocard' MediaDB.NOCARD Unavailable because there is no sd card
+ * 'unmounted' MediaDB.UNMOUNTED Unavailable because the card is unmounted
+ * 'closed' MediaDB.CLOSED Unavailable because close() was called
+ *
+ * When an 'unavailable' event is fired, the detail property of the event
+ * object specifies the reason that the MediaDB is unavailable. It is one of
+ * the state values 'nocard', 'unmounted' or 'closed'.
+ *
+ * The 'nocard' state occurs when device storage is not available because
+ * there is no SD card in the device. This is typically a permanent failure
+ * state, and media apps cannot run without an SD card. It can occur
+ * transiently, however, if the user is swapping SD cards while a media app is
+ * open.
+ *
+ * The 'unmounted' state occurs when the device's SD card is unmounted. This
+ * is generally a temporary condition that occurs when the user starts a USB
+ * Mass Storage transfer session by plugging their device into a computer. In
+ * this case, MediaDB will become available again as soon as the device is
+ * unplugged (it may have different files on it, though: see the SCANNING
+ * section below).
+ *
+ * DATABASE RECORDS
+ *
+ * MediaDB stores a record in its IndexedDB database for each DeviceStorage
+ * file of the appropriate media type, directory and mime type. The records
+ * are objects of this form:
+ *
+ * {
+ * name: // the filename (relative to the DeviceStorage root)
+ * type: // the file MIME type (extension-based, from DeviceStorage)
+ * size: // the file size in bytes
+ * date: // file modification time (as ms since the epoch)
+ * metadata: // whatever object the metadata parser returned
+ * }
+ *
+ * Note that the database records do not include the file itself, but only its
+ * name. Use the getFile() method to get a File object (a Blob) by name.
+ *
+ * ENUMERATING FILES
+ *
+ * Typically, the first thing an app will do with a MediaDB object after the
+ * ready event is triggered is call the enumerate() method to obtain the list
+ * of files that MediaDB already knows about from previous app invocations.
+ * enumerate() gets records from the database and passes them to the specified
+ * callback. Each record that is passed to the callback is an object in the
+ * form shown above.
+ *
+ * If you pass only a callback to enumerate(), it calls the callback once for
+ * each entry in the database and then calls the callback with an argument of
+ * null to indicate that it is done.
+ *
+ * By default, entries are returned in alphabetical order by filename and all
+ * entries in the database are returned. You can specify other arguments to
+ * enumerate() to change the set of entries that are returned and the order that
+ * they are enumerated in. The full set of arguments are:
+ *
+ * key:
+ * A keypath specification that specifies what field to sort on. If you
+ * specify this argument, it must be 'name', 'date', or one of the values
+ * in the options.indexes array passed to the MediaDB() constructor. This
+ * argument is optional. If omitted, the default is to use the file name
+ * as the key.
+ *
+ * range:
+ * An IDBKeyRange object that optionally specifies upper and lower bounds on
+ * the specified key. This argument is optional. If omitted, all entries in
+ * the database are enumerated. See IndexedDB documentation for more on
+ * key ranges.
+ *
+ * direction:
+ * One of the IndexedDB direction string "next", "nextunique", "prev" or
+ * "prevunique". This argument is optional. If omitted, the default is
+ * "next", which enumerates entries in ascending order.
+ *
+ * callback:
+ * The function that database entries should be passed to. This argument is
+ * not optional, and is always passed as the last argument to enumerate().
+ *
+ * The enumerate() method returns database entries. These include file names,
+ * but not the files themselves. enumerate() interacts solely with the
+ * IndexedDB; it does not use DeviceStorage. If you want to use a media file
+ * (to play a song or display a photo, for example) call the getFile() method.
+ *
+ * enumerate() returns an object with a 'state' property that starts out as
+ * 'enumerating' and switches to 'complete' when the enumeration is done. You
+ * can cancel a pending enumeration by passing this object to the
+ * cancelEnumeration() method. This switches the state to 'cancelling' and then
+ * it switches to 'cancelled' when the cancellation is complete. If you call
+ * cancelEnumeration(), the callback function you passed to enumerate() is
+ * guaranteed not to be called again.
+ *
+ * In addition to enumerate(), there are two other methods you can use
+ * to enumerate database entries:
+ *
+ * enumerateAll() takes the same arguments and returns the same values
+ * as enumerate(), but it batches the results and passes them in an
+ * array to the callback function.
+ *
+ * getAll() takes a callback argument and passes it an array of all
+ * entries in the database, sorted by filename. It does not allow you
+ * to specify a key, range, or direction, but if you need all entries
+ * from the database, this method is is much faster than enumerating
+ * entries individually.
+ *
+ * FILESYSTEM CHANGES
+ *
+ * When media files are added or removed, MediaDB reports this by triggering
+ * 'created' and 'deleted' events.
+ *
+ * When a 'created' event is fired, the detail property of the event is an
+ * array of database record objects. When a single file is created (for
+ * example when the user takes a picture with the Camera app) this array has
+ * only a single element. But when MediaDB scans for new files (see SCANNING
+ * below) it may batch multiple records into a single created event. If a
+ * 'created' event has many records, apps may choose to simply rebuild their
+ * UI from scratch with a new call to enumerate() instead of handling the new
+ * files one at a time.
+ *
+ * When a 'deleted' event is fired, the detail property of the event is an
+ * array of the names of the files that have been deleted. As with 'created'
+ * events, the array may have a single element or may have many batched
+ * elements.
+ *
+ * If MediaDB detects that a file has been modified in place (because its
+ * size or date changes) it treats this as a deletion of the old version and
+ * the creation of a new version, and will fire a deleted event followed by
+ * a created event.
+ *
+ * The created and deleted events are not triggered until the corresponding
+ * files have actually been created and deleted and their database records
+ * have been updated.
+ *
+ * SCANNING
+ *
+ * MediaDB automatically scans for new and deleted files every time it enters
+ * the MediaDB.READY state. This happens when the MediaDB object is first
+ * created, and also when an SD card is removed and reinserted or when the
+ * user finishes a USB Mass Storage session. If the scan finds new files, it
+ * reports them with one or more 'created' events. If the scan finds that
+ * files have been deleted, it reports them with one or more 'deleted' events.
+ *
+ * MediaDB fires a 'scanstart' event when a scan begins and fires a 'scanend'
+ * event when the scan is complete. Apps can use these events to let the user
+ * know that a scan is in progress.
+ *
+ * The scan algorithm attempts to quickly look for new files and reports those
+ * first. It then begins a slower full scan phase where it checks that each of
+ * the files it already knows about is still present.
+ *
+ * EVENTS
+ *
+ * As described above, MediaDB sends events to communicate with the apps
+ * that use it. The event types and their meanings are:
+ *
+ * Event Meaning
+ * --------------------------------------------------------------------------
+ * ready MediaDB is ready for use
+ * unavailable MediaDB is unavailable (often because of USB file transfer)
+ * created One or more files were created
+ * deleted One or more files were deleted
+ * scanstart MediaDB is scanning
+ * scanend MediaDB has finished scanning
+ *
+ * Because MediaDB is a JavaScript library, these are not real DOM events, but
+ * simulations.
+ *
+ * MediaDB defines two-argument versions of addEventListener() and
+ * removeEventListener() and also allows you to define event handlers by
+ * setting 'on' properties like 'onready' and 'onscanstart'.
+ *
+ * The objects passed on MediaDB event handlers are not true Event objects but
+ * simulate a CustomEvent by defining type, target, currentTarget, timestamp
+ * and detail properties. For MediaDB events, it is the detail property that
+ * always holds the useful information. These simulated event objects do not
+ * have preventDefault(), stopPropagation() or stopImmediatePropagation()
+ * methods.
+ *
+ * MediaDB events do not bubble and cannot be captured.
+ *
+ * METHODS
+ *
+ * MediaDB defines the following methods:
+ *
+ * - addEventListener(): register a function to call when an event is fired
+ *
+ * - removeEventListener(): unregister an event listener function
+ *
+ * - enumerate(): for each file that MediaDB knows about, pass its database
+ * record object to the specified callback. By default, records are returned
+ * in alphabetical order by name, but optional arguments allow you to
+ * specify a database index, a key range, and a sort direction.
+ *
+ * - cancelEnumeration(): stops an enumeration in progress. Pass the object
+ * returned by enumerate().
+ *
+ * - getFile(): given a filename and a callback, this method looks up the
+ * named file in DeviceStorage and passes it (a Blob) to the callback.
+ * An error callback is available as an optional third argument.
+ *
+ * - count(): count the number of records in the database and pass the value
+ * to the specified callback. Like enumerate(), this method allows you
+ * to specify the name of an index and a key range if you only want to
+ * count some of the records.
+ *
+ * - updateMetadata(): updates the metadata associated with a named file
+ *
+ * - addFile(): given a filename and a blob this method saves the blob as a
+ * named file to device storage.
+ *
+ * - deleteFile(): deletes the named file from device storage and the database
+ *
+ * - close(): closes the IndexedDB connections and stops listening to
+ * DeviceStorage events. This permanently puts the MediaDB object into
+ * the MediaDB.CLOSED state in which it is unusable.
+ *
+ * - freeSpace(): call the DeviceStorage freeSpace() method and pass the
+ * result to the specified callback
+ */
+var MediaDB = (function() {
+
+ function MediaDB(mediaType, metadataParser, options) {
+ this.mediaType = mediaType;
+ this.metadataParser = metadataParser;
+ if (!options)
+ options = {};
+ this.indexes = options.indexes || [];
+ this.version = options.version || 1;
+ this.directory = options.directory || '';
+ this.mimeTypes = options.mimeTypes;
+ this.autoscan = (options.autoscan !== undefined) ? options.autoscan : true;
+ this.state = MediaDB.OPENING;
+ this.scanning = false; // becomes true while scanning
+
+ // While scanning, we attempt to send change events in batches.
+ // After finding a new or deleted file, we'll wait this long before
+ // sending events in case we find another new or deleted file right away.
+ this.batchHoldTime = options.batchHoldTime || 100;
+
+ // But we'll send a batch of changes right away if it gets this big
+ // A batch size of 0 means no maximum batch size
+ this.batchSize = options.batchSize || 0;
+
+ if (this.directory &&
+ this.directory[this.directory.length - 1] !== '/')
+ this.directory += '/';
+
+ this.dbname = 'MediaDB/' + this.mediaType + '/' + this.directory;
+
+ var media = this; // for the nested functions below
+
+ // Private implementation details in this object
+ this.details = {
+ // This maps event type -> array of listeners
+ // See addEventListener and removeEventListener
+ eventListeners: {},
+
+ // Properties for queuing up db insertions and deletions and also
+ // for queueing up notifications to be sent
+ pendingInsertions: [], // Array of filenames to insert
+ pendingDeletions: [], // Array of filenames to remove
+ whenDoneProcessing: [], // Functions to run when queue is empty
+
+ pendingCreateNotifications: [], // Array of fileinfo objects
+ pendingDeleteNotifications: [], // Ditto
+ pendingNotificationTimer: null,
+
+ // This property holds the modification date of the newest file we have.
+ // We need to know the newest file in order to look for newer ones during
+ // scanning. We initialize newestFileModTime during initialization by
+ // actually checking the database (using the date index). We also update
+ // this property every time a new record is added to the database.
+ newestFileModTime: 0
+ };
+
+ // Define a dummy metadata parser if we're not given one
+ if (!this.metadataParser) {
+ this.metadataParser = function(file, callback) {
+ setTimeout(function() { callback({}); }, 0);
+ }
+ }
+
+ // Open the database
+ // Note that the user can upgrade the version and we can upgrade the version
+ var openRequest = indexedDB.open(this.dbname,
+ this.version * MediaDB.VERSION);
+
+ // This should never happen for Gaia apps
+ openRequest.onerror = function(e) {
+ console.error('MediaDB():', openRequest.error.name);
+ };
+
+ // This should never happen for Gaia apps
+ openRequest.onblocked = function(e) {
+ console.error('indexedDB.open() is blocked in MediaDB()');
+ };
+
+ // This is where we create (or delete and recreate) the database
+ openRequest.onupgradeneeded = function(e) {
+ var db = openRequest.result;
+
+ // If there are already existing object stores, delete them all
+ // If the version number changes we just want to start over.
+ var existingStoreNames = db.objectStoreNames;
+ for (var i = 0; i < existingStoreNames.length; i++) {
+ db.deleteObjectStore(existingStoreNames[i]);
+ }
+
+ // Now build the database
+ var filestore = db.createObjectStore('files', { keyPath: 'name' });
+ // Always index the files by modification date
+ filestore.createIndex('date', 'date');
+ // And index them by any other file properties or metadata properties
+ // passed to the constructor
+ media.indexes.forEach(function(indexName) {
+ // Don't recreate indexes we've already got
+ if (indexName === 'name' || indexName === 'date')
+ return;
+ // the index name is also the keypath
+ filestore.createIndex(indexName, indexName);
+ });
+ }
+
+ // This is called when we've got the database open and ready.
+ openRequest.onsuccess = function(e) {
+ media.db = openRequest.result;
+
+ // Log any errors that propagate up to here
+ media.db.onerror = function(event) {
+ console.error('MediaDB: ',
+ event.target.error && event.target.error.name);
+ }
+
+ // Query the db to find the modification time of the newest file
+ var cursorRequest =
+ media.db.transaction('files', 'readonly')
+ .objectStore('files')
+ .index('date')
+ .openCursor(null, 'prev');
+
+ cursorRequest.onerror = function() {
+ // If anything goes wrong just display an error.
+ // If this fails, don't even attempt error recovery
+ console.error('MediaDB initialization error', cursorRequest.error);
+ };
+ cursorRequest.onsuccess = function() {
+ var cursor = cursorRequest.result;
+ if (cursor) {
+ media.details.newestFileModTime = cursor.value.date;
+ }
+ else {
+ // No files in the db yet, so use a really old time
+ media.details.newestFileModTime = 0;
+ }
+
+ // The DB is initialized, and we've got our mod time
+ // so move on and initialize device storage
+ initDeviceStorage();
+ };
+ };
+
+ function initDeviceStorage() {
+ // Set up DeviceStorage
+ // If storage is null, then there is no sdcard installed and
+ // we have to abort.
+ media.storage = navigator.getDeviceStorage(mediaType);
+
+ // Handle change notifications from device storage
+ // We set this onchange property to null in the close() method
+ // so don't use addEventListener here
+ media.storage.addEventListener('change', deviceStorageChangeHandler);
+ media.details.dsEventListener = deviceStorageChangeHandler;
+
+ // Use available() to figure out if there is actually an sdcard there
+ // and emit a ready or unavailable event
+ var availreq = media.storage.available();
+ availreq.onsuccess = function(e) {
+ switch (e.target.result) {
+ case 'available':
+ changeState(media, MediaDB.READY);
+ if (media.autoscan)
+ scan(media); // Start scanning as soon as we're ready
+ break;
+ case 'unavailable':
+ changeState(media, MediaDB.NOCARD);
+ break;
+ case 'shared':
+ changeState(media, MediaDB.UNMOUNTED);
+ break;
+ }
+ };
+ availreq.onerror = function(e) {
+ console.error('available() failed',
+ availreq.error && availreq.error.name);
+ changeState(media, MediaDB.UNMOUNTED);
+ };
+ }
+
+ function deviceStorageChangeHandler(e) {
+ var filename;
+ switch (e.reason) {
+ case 'available':
+ changeState(media, MediaDB.READY);
+ if (media.autoscan)
+ scan(media); // automatically scan every time the card comes back
+ break;
+ case 'unavailable':
+ changeState(media, MediaDB.NOCARD);
+ endscan(media);
+ break;
+ case 'shared':
+ changeState(media, MediaDB.UNMOUNTED);
+ endscan(media);
+ break;
+ case 'modified':
+ case 'deleted':
+ filename = e.path;
+ if (ignoreName(filename))
+ break;
+ if (media.directory) {
+ // Ignore changes outside of our directory
+ if (filename.substring(0, media.directory.length) !==
+ media.directory)
+ break;
+ // And strip the directory from changes inside of it
+ filename = filename.substring(media.directory.length);
+ }
+ if (e.reason === 'modified')
+ insertRecord(media, filename);
+ else
+ deleteRecord(media, filename);
+ break;
+ }
+ }
+ }
+
+ MediaDB.prototype = {
+ close: function close() {
+ // Close the database
+ this.db.close();
+
+ // There is no way to close device storage, but we at least want
+ // to stop receiving events from it.
+ this.storage.removeEventListener('change', this.details.dsEventListener);
+
+ // Change state and send out an event
+ changeState(this, MediaDB.CLOSED);
+ },
+
+ addEventListener: function addEventListener(type, listener) {
+ if (!this.details.eventListeners.hasOwnProperty(type))
+ this.details.eventListeners[type] = [];
+ var listeners = this.details.eventListeners[type];
+ if (listeners.indexOf(listener) !== -1)
+ return;
+ listeners.push(listener);
+ },
+
+ removeEventListener: function removeEventListener(type, listener) {
+ if (!this.details.eventListeners.hasOwnProperty(type))
+ return;
+ var listeners = this.details.eventListeners[type];
+ var position = listeners.indexOf(listener);
+ if (position === -1)
+ return;
+ listeners.splice(position, 1);
+ },
+
+ // Look up the specified filename in DeviceStorage and pass the
+ // resulting File object to the specified callback.
+ getFile: function getFile(filename, callback, errback) {
+ if (this.state !== MediaDB.READY)
+ throw Error('MediaDB is not ready. State: ' + this.state);
+
+ var getRequest = this.storage.get(this.directory + filename);
+ getRequest.onsuccess = function() {
+ callback(getRequest.result);
+ };
+ getRequest.onerror = function() {
+ var errmsg = getRequest.error && getRequest.error.name;
+ if (errback)
+ errback(errmsg);
+ else
+ console.error('MediaDB.getFile:', errmsg);
+ }
+ },
+
+ // Delete the named file from device storage.
+ // This will cause a device storage change event, which will cause
+ // mediadb to remove the file from the database and send out a
+ // mediadb change event, which will notify the application UI.
+ deleteFile: function deleteFile(filename) {
+ if (this.state !== MediaDB.READY)
+ throw Error('MediaDB is not ready. State: ' + this.state);
+
+ this.storage.delete(this.directory + filename).onerror = function(e) {
+ console.error('MediaDB.deleteFile(): Failed to delete', filename,
+ 'from DeviceStorage:', e.target.error);
+ };
+ },
+
+ //
+ // Save the specified blob to device storage, using the specified filename.
+ // This will cause device storage to send us an event, and that event
+ // will cause mediadb to add the file to its database, and that will
+ // send out a mediadb event to the application UI.
+ //
+ addFile: function addFile(filename, file) {
+ if (this.state !== MediaDB.READY)
+ throw Error('MediaDB is not ready. State: ' + this.state);
+
+ var media = this;
+
+ // Delete any existing file by this name, then save the file.
+ var deletereq = media.storage.delete(media.directory + filename);
+ deletereq.onsuccess = deletereq.onerror = save;
+
+ function save() {
+ var request = media.storage.addNamed(file, media.directory + filename);
+ request.onerror = function() {
+ console.error('MediaDB: Failed to store', filename,
+ 'in DeviceStorage:', storeRequest.error);
+ };
+ }
+ },
+
+ // Look up the database record for the named file, and copy the properties
+ // of the metadata object into the file's metadata, and then write the
+ // updated record back to the database. The third argument is optional. If
+ // you pass a function, it will be called when the metadata is written.
+ updateMetadata: function(filename, metadata, callback) {
+ if (this.state !== MediaDB.READY)
+ throw Error('MediaDB is not ready. State: ' + this.state);
+
+ var media = this;
+
+ // First, look up the fileinfo record in the db
+ var read = media.db.transaction('files', 'readonly')
+ .objectStore('files')
+ .get(filename);
+
+ read.onerror = function() {
+ console.error('MediaDB.updateMetadata called with unknown filename');
+ };
+
+ read.onsuccess = function() {
+ var fileinfo = read.result;
+
+ // Update the fileinfo metadata
+ Object.keys(metadata).forEach(function(key) {
+ fileinfo.metadata[key] = metadata[key];
+ });
+
+ // And write it back into the database.
+ var write = media.db.transaction('files', 'readwrite')
+ .objectStore('files')
+ .put(fileinfo);
+
+ write.onerror = function() {
+ console.error('MediaDB.updateMetadata: database write failed',
+ write.error && write.error.name);
+ };
+
+ if (callback) {
+ write.onsuccess = function() {
+ callback();
+ }
+ }
+ }
+ },
+
+ // Count the number of records in the database and pass that number to the
+ // specified callback. key is 'name', 'date' or one of the index names
+ // passed to the constructor. range is be an IDBKeyRange that defines a
+ // the range of key values to count. key and range are optional
+ // arguments. If one argument is passed, it is the callback. If two
+ // arguments are passed, they are assumed to be the range and callback.
+ count: function(key, range, callback) {
+ if (this.state !== MediaDB.READY)
+ throw Error('MediaDB is not ready. State: ' + this.state);
+
+ // range is an optional argument
+ if (arguments.length === 1) {
+ callback = key;
+ range = undefined;
+ key = undefined;
+ }
+ else if (arguments.length === 2) {
+ callback = range;
+ range = key;
+ key = undefined;
+ }
+
+ var store = this.db.transaction('files').objectStore('files');
+ if (key && key !== 'name')
+ store = store.index(key);
+
+ var countRequest = store.count(range || null);
+
+ countRequest.onerror = function() {
+ console.error('MediaDB.count() failed with', countRequest.error);
+ };
+
+ countRequest.onsuccess = function(e) {
+ callback(e.target.result);
+ };
+ },
+
+
+ // Enumerate all files in the filesystem, sorting by the specified
+ // property (which must be one of the indexes, or null for the filename).
+ // Direction is ascending or descending. Use whatever string
+ // constant IndexedDB uses. f is the function to pass each record to.
+ //
+ // Each record is an object like this:
+ //
+ // {
+ // // The basic fields are all from the File object
+ // name: // the filename
+ // type: // the file type
+ // size: // the file size
+ // date: // file mod time
+ // metadata: // whatever object the metadata parser returns
+ // }
+ //
+ // This method returns an object that you can pass to cancelEnumeration()
+ // to cancel an enumeration in progress. You can use the state property
+ // of the returned object to find out the state of the enumeration. It
+ // should be one of the strings 'enumerating', 'complete', 'cancelling'
+ // 'cancelled', or 'error'
+ //
+ enumerate: function enumerate(key, range, direction, callback) {
+ if (this.state !== MediaDB.READY)
+ throw Error('MediaDB is not ready. State: ' + this.state);
+
+ var handle = { state: 'enumerating' };
+
+ // The first three arguments are optional, but the callback
+ // is required, and we don't want to have to pass three nulls
+ if (arguments.length === 1) {
+ callback = key;
+ key = undefined;
+ }
+ else if (arguments.length === 2) {
+ callback = range;
+ range = undefined;
+ }
+ else if (arguments.length === 3) {
+ callback = direction;
+ direction = undefined;
+ }
+
+ var store = this.db.transaction('files').objectStore('files');
+
+ // If a key other than "name" is specified, then use the index for that
+ // key instead of the store.
+ if (key && key !== 'name')
+ store = store.index(key);
+
+ // Now create a cursor for the store or index.
+ var cursorRequest = store.openCursor(range || null, direction || 'next');
+
+ cursorRequest.onerror = function() {
+ console.error('MediaDB.enumerate() failed with', cursorRequest.error);
+ handle.state = 'error';
+ };
+
+ cursorRequest.onsuccess = function() {
+ // If the enumeration has been cancelled, return without
+ // calling the callback and without calling cursor.continue();
+ if (handle.state === 'cancelling') {
+ handle.state = 'cancelled';
+ return;
+ }
+
+ var cursor = cursorRequest.result;
+ if (cursor) {
+ try {
+ if (!cursor.value.fail) // if metadata parsing succeeded
+ callback(cursor.value);
+ }
+ catch (e) {
+ console.warn('MediaDB.enumerate(): callback threw', e);
+ }
+ cursor.continue();
+ }
+ else {
+ // Final time, tell the callback that there are no more.
+ handle.state = 'complete';
+ callback(null);
+ }
+ };
+
+ return handle;
+ },
+
+ // This method takes the same arguments as enumerate(), but batches
+ // the results into an array and passes them to the callback all at
+ // once when the enumeration is complete. It uses enumerate() so it
+ // is no faster than that method, but may be more convenient.
+ enumerateAll: function enumerateAll(key, range, direction, callback) {
+ var batch = [];
+
+ // The first three arguments are optional, but the callback
+ // is required, and we don't want to have to pass three nulls
+ if (arguments.length === 1) {
+ callback = key;
+ key = undefined;
+ }
+ else if (arguments.length === 2) {
+ callback = range;
+ range = undefined;
+ }
+ else if (arguments.length === 3) {
+ callback = direction;
+ direction = undefined;
+ }
+
+ return this.enumerate(key, range, direction, function(fileinfo) {
+ if (fileinfo !== null)
+ batch.push(fileinfo);
+ else
+ callback(batch);
+ });
+ },
+
+ // Cancel a pending enumeration. After calling this the callback for
+ // the specified enumeration will not be invoked again.
+ cancelEnumeration: function cancelEnumeration(handle) {
+ if (handle.state === 'enumerating')
+ handle.state = 'cancelling';
+ },
+
+ // Use the non-standard mozGetAll() function to return all of the
+ // records in the database in one big batch. The records will be
+ // sorted by filename
+ getAll: function getAll(callback) {
+ if (this.state !== MediaDB.READY)
+ throw Error('MediaDB is not ready. State: ' + this.state);
+
+ var store = this.db.transaction('files').objectStore('files');
+ var request = store.mozGetAll();
+ request.onerror = function() {
+ console.error('MediaDB.getAll() failed with', request.error);
+ };
+ request.onsuccess = function() {
+ var all = request.result; // All records in the object store
+
+ // Filter out files that failed metadata parsing
+ var good = all.filter(function(fileinfo) { return !fileinfo.fail; });
+
+ callback(good);
+ };
+ },
+
+ // Scan for new or deleted files.
+ // This is only necessary if you have explicitly disabled automatic
+ // scanning by setting autoscan:false in the options object.
+ scan: function() {
+ scan(this);
+ },
+
+ // Use the device storage freeSpace() method and pass the returned
+ // value to the callback.
+ freeSpace: function freeSpace(callback) {
+ if (this.state !== MediaDB.READY)
+ throw Error('MediaDB is not ready. State: ' + this.state);
+
+ var freereq = this.storage.freeSpace();
+ freereq.onsuccess = function() {
+ callback(freereq.result);
+ }
+ }
+ };
+
+ // This is the version number of the MediaDB schema. If we change this
+ // number it will cause existing data stores to be deleted and rebuilt,
+ // which is useful when the schema changes. Note that the user can also
+ // upgrade the version number with an option to the MediaDB constructor.
+ // The final indexedDB version number we use is the product of our version
+ // and the user's version.
+ // This is version 2 because we modified the default schema to include
+ // an index for file modification date.
+ MediaDB.VERSION = 2;
+
+ // These are the values of the state property of a MediaDB object
+ // The NOCARD, UNMOUNTED, and CLOSED values are also used as the detail
+ // property of 'unavailable' events
+ MediaDB.OPENING = 'opening'; // MediaDB is initializing itself
+ MediaDB.READY = 'ready'; // MediaDB is available and ready for use
+ MediaDB.NOCARD = 'nocard'; // Unavailable because there is no sd card
+ MediaDB.UNMOUNTED = 'unmounted'; // Unavailable because card unmounted
+ MediaDB.CLOSED = 'closed'; // Unavailalbe because MediaDB has closed
+
+ /* Details of helper functions follow */
+
+ //
+ // Return true if media db should ignore this file.
+ //
+ // If any components of the path begin with a . we'll ignore the file.
+ // The '.' prefix indicates hidden files and directories on Unix and
+ // when files are "moved to trash" during a USB Mass Storage session they
+ // are sometimes not actually deleted, but moved to a hidden directory.
+ //
+ // If an array of media types was specified when the MediaDB was created
+ // and the type of this file is not a member of that list, then ignore it.
+ //
+ function ignore(media, file) {
+ if (ignoreName(file.name))
+ return true;
+ if (media.mimeTypes && media.mimeTypes.indexOf(file.type) === -1)
+ return true;
+ return false;
+ }
+
+ // Test whether this filename is one we ignore.
+ // This is a separate function because device storage change events
+ // give us a name only, not the file object.
+ function ignoreName(filename) {
+ return (filename[0] === '.' || filename.indexOf('/.') !== -1);
+ }
+
+ // Tell the db to start a manual scan. I think we don't do
+ // this automatically from the constructor, but most apps will start
+ // a scan right after calling the constructor and then will proceed to
+ // enumerate what is already in the db. If scan performance is bad
+ // for large media collections, apps can just have the user specify
+ // when to rescan rather than doing it automatically. Until we have
+ // change event notifications, gaia apps might want to do a scan
+ // every time they are made visible.
+ //
+ // Filesystem changes discovered by a scan are generally
+ // batched. If a scan discovers 10 new files, the information
+ // about those files will generally be passed as an array to a the
+ // onchange handler rather than calling that handler once for each
+ // newly discovered file. Apps can decide whether to handle
+ // batches by processing each element individually or by just starting
+ // fresh with a new call to enumerate().
+ //
+ // Scan details are not tightly specified, but the goal is to be
+ // as efficient as possible. We'll try to do a quick date-based
+ // scan to look for new files and report those first. Following
+ // that, a full scan will be compared with a full dump of the DB
+ // to see if any files have been deleted.
+ //
+ function scan(media) {
+ media.scanning = true;
+ dispatchEvent(media, 'scanstart');
+
+ // First, scan for new files since the last scan, if there was one
+ // When the quickScan is done it will begin a full scan. If we don't
+ // have a last scan date, then the database is empty and we don't
+ // have to do a full scan, since there will be no changes or deletions.
+ quickScan(media.details.newestFileModTime);
+
+ // Do a quick scan and then follow with a full scan
+ function quickScan(timestamp) {
+ var cursor;
+ if (timestamp > 0) {
+ media.details.firstscan = false;
+ cursor = media.storage.enumerate(media.directory, {
+ // add 1 so we don't find the same newest file again
+ since: new Date(timestamp + 1)
+ });
+ }
+ else {
+ // If there is no timestamp then this is the first time we've
+ // scanned and we don't have any files in the database, which
+ // allows important optimizations during the scanning process
+ media.details.firstscan = true;
+ media.details.records = [];
+ cursor = media.storage.enumerate(media.directory);
+ }
+
+ cursor.onsuccess = function() {
+ var file = cursor.result;
+ if (file) {
+ if (!ignore(media, file))
+ insertRecord(media, file);
+ cursor.continue();
+ }
+ else {
+ // Quick scan is done. When the queue is empty, force out
+ // any batched created events and move on to the slower
+ // more thorough full scan.
+ whenDoneProcessing(media, function() {
+ sendNotifications(media);
+ if (media.details.firstscan) {
+ // If this was the first scan, then we're done
+ endscan(media);
+ }
+ else {
+ // If this was not the first scan, then we need to go
+ // ensure that all of the old files we know about are still there
+ fullScan();
+ }
+ });
+ }
+ };
+
+ cursor.onerror = function() {
+ // We can't scan if we can't read device storage.
+ // Perhaps the card was unmounted or pulled out
+ console.warning('Error while scanning', cursor.error);
+ endscan(media);
+ };
+ }
+
+ // Get a complete list of files from DeviceStorage
+ // Get a complete list of files from IndexedDB.
+ // Sort them both (the indexedDB list will already be sorted)
+ // Step through the lists noting deleted files and created files.
+ // Pay attention to files whose size or date has changed and
+ // treat those as deletions followed by insertions.
+ // Sync up the database while stepping through the lists.
+ function fullScan() {
+ if (media.state !== MediaDB.READY) {
+ endscan(media);
+ return;
+ }
+
+ // The db may be busy right about now, processing files that
+ // were found during the quick scan. So we'll start off by
+ // enumerating all files in device storage
+ var dsfiles = [];
+ var cursor = media.storage.enumerate(media.directory);
+ cursor.onsuccess = function() {
+ var file = cursor.result;
+ if (file) {
+ if (!ignore(media, file)) {
+ dsfiles.push(file);
+ }
+ cursor.continue();
+ }
+ else {
+ // We're done enumerating device storage, so get all files from db
+ getDBFiles();
+ }
+ }
+
+ cursor.onerror = function() {
+ // We can't scan if we can't read device storage.
+ // Perhaps the card was unmounted or pulled out
+ console.warning('Error while scanning', cursor.error);
+ endscan(media);
+ };
+
+ function getDBFiles() {
+ var store = media.db.transaction('files').objectStore('files');
+ var getAllRequest = store.mozGetAll();
+
+ getAllRequest.onsuccess = function() {
+ var dbfiles = getAllRequest.result; // Should already be sorted
+ compareLists(dbfiles, dsfiles);
+ };
+ }
+
+ function compareLists(dbfiles, dsfiles) {
+ // The dbfiles are sorted when we get them from the db.
+ // But the ds files are not sorted
+ dsfiles.sort(function(a, b) {
+ if (a.name < b.name)
+ return -1;
+ else
+ return 1;
+ });
+
+ // Loop through both the dsfiles and dbfiles lists
+ var dsindex = 0, dbindex = 0;
+ while (true) {
+ // Get the next DeviceStorage file or null
+ var dsfile;
+ if (dsindex < dsfiles.length)
+ dsfile = dsfiles[dsindex];
+ else
+ dsfile = null;
+
+ // Get the next DB file or null
+ var dbfile;
+ if (dbindex < dbfiles.length)
+ dbfile = dbfiles[dbindex];
+ else
+ dbfile = null;
+
+ // Case 1: both files are null. If so, we're done.
+ if (dsfile === null && dbfile === null)
+ break;
+
+ // Case 2: no more files in the db. This means that
+ // the file from ds is a new one
+ if (dbfile === null) {
+ insertRecord(media, dsfile);
+ dsindex++;
+ continue;
+ }
+
+ // Case 3: no more files in ds. This means that the db file
+ // has been deleted
+ if (dsfile === null) {
+ deleteRecord(media, dbfile.name);
+ dbindex++;
+ continue;
+ }
+
+ // Case 4: two files with the same name.
+ // 4a: date and size are the same for both: do nothing
+ // 4b: file has changed: it is both a deletion and a creation
+ if (dsfile.name === dbfile.name) {
+ var lastModified = dsfile.lastModifiedDate;
+ if ((lastModified && lastModified.getTime() !== dbfile.date) ||
+ dsfile.size !== dbfile.size) {
+ deleteRecord(media, dbfile.name);
+ insertRecord(media, dsfile);
+ }
+ dsindex++;
+ dbindex++;
+ continue;
+ }
+
+ // Case 5: the dsfile name is less than the dbfile name.
+ // This means that the dsfile is new. Like case 2
+ if (dsfile.name < dbfile.name) {
+ insertRecord(media, dsfile);
+ dsindex++;
+ continue;
+ }
+
+ // Case 6: the dsfile name is greater than the dbfile name.
+ // this means that the dbfile no longer exists on disk
+ if (dsfile.name > dbfile.name) {
+ deleteRecord(media, dbfile.name);
+ dbindex++;
+ continue;
+ }
+
+ // That should be an exhaustive set of possiblities
+ // and we should never reach this point.
+ console.error('Assertion failed');
+ }
+
+ // Push a special value onto the queue so that when it is
+ // processed we can trigger a 'scanend' event
+ insertRecord(media, null);
+ }
+ }
+ }
+
+ // Called to send out a scanend event when scanning is done.
+ // This event is sent on normal scan termination and also
+ // when something goes wrong, such as the device storage being
+ // unmounted during a scan.
+ function endscan(media) {
+ if (media.scanning) {
+ media.scanning = false;
+ dispatchEvent(media, 'scanend');
+ }
+ }
+
+ // Pass in a file, or a filename. The function queues it up for
+ // metadata parsing and insertion into the database, and will send a
+ // mediadb change event (possibly batched with other changes).
+ // Ensures that only one file is being parsed at a time, but tries
+ // to make as many db changes in one transaction as possible. The
+ // special value null indicates that scanning is complete. If the
+ // 2nd argument is a File, it should come from enumerate() so that
+ // the name property does not include the directory prefix. If it
+ // is a name, then the directory prefix must already have been
+ // stripped.
+ function insertRecord(media, fileOrName) {
+ var details = media.details;
+
+ // Add this file to the queue of files to process
+ details.pendingInsertions.push(fileOrName);
+
+ // If the queue is already being processed, just return
+ if (details.processingQueue)
+ return;
+
+ // Otherwise, start processing the queue.
+ processQueue(media);
+ }
+
+ // Delete the database record associated with filename.
+ // filename must not include the directory prefix.
+ function deleteRecord(media, filename) {
+ var details = media.details;
+
+ // Add this file to the queue of files to process
+ details.pendingDeletions.push(filename);
+
+ // If there is already a transaction in progress return now.
+ if (details.processingQueue)
+ return;
+
+ // Otherwise, start processing the queue
+ processQueue(media);
+ }
+
+ function whenDoneProcessing(media, f) {
+ var details = media.details;
+ if (details.processingQueue)
+ details.whenDoneProcessing.push(f);
+ else
+ f();
+ }
+
+ function processQueue(media) {
+ var details = media.details;
+
+ details.processingQueue = true;
+
+ // Now get one filename off a queue and store it
+ next();
+
+ // Take an item from a queue and process it.
+ // Deletions are always processed before insertions because we want
+ // to clear away non-functional parts of the UI ASAP.
+ function next() {
+ if (details.pendingDeletions.length > 0) {
+ deleteFiles();
+ }
+ else if (details.pendingInsertions.length > 0) {
+ insertFile(details.pendingInsertions.shift());
+ }
+ else {
+ details.processingQueue = false;
+ if (details.whenDoneProcessing.length > 0) {
+ var functions = details.whenDoneProcessing;
+ details.whenDoneProcessing = [];
+ functions.forEach(function(f) { f(); });
+ }
+ }
+ }
+
+ // Delete all of the pending files in a single transaction
+ function deleteFiles() {
+ var transaction = media.db.transaction('files', 'readwrite');
+ var store = transaction.objectStore('files');
+
+ deleteNextFile();
+
+ function deleteNextFile() {
+ if (details.pendingDeletions.length === 0) {
+ next();
+ return;
+ }
+ var filename = details.pendingDeletions.shift();
+ var request = store.delete(filename);
+ request.onerror = function() {
+ // This probably means that the file wasn't in the db yet
+ console.warn('MediaDB: Unknown file in deleteRecord:',
+ filename, getreq.error);
+ deleteNextFile();
+ };
+ request.onsuccess = function() {
+ // We succeeded, so remember to send out an event about it.
+ queueDeleteNotification(media, filename);
+ deleteNextFile();
+ };
+ }
+ }
+
+ // Insert a file into the db. One transaction per insertion.
+ // The argument might be a filename or a File object
+ // If it is a File, then it came from enumerate and its name
+ // property already has the directory stripped off. If it is a
+ // filename, it came from a device storage change event and we
+ // stripped of the directory before calling insertRecord.
+ function insertFile(f) {
+ // null is a special value pushed on to the queue when a scan()
+ // is complete. We use it to trigger a scanend event
+ // after all the change events from the scan are delivered
+ if (f === null) {
+ sendNotifications(media);
+ endscan(media);
+ next();
+ return;
+ }
+
+ // If we got a filename, look up the file in device storage
+ if (typeof f === 'string') {
+ var getreq = media.storage.get(media.directory + f);
+ getreq.onerror = function() {
+ console.warn('MediaDB: Unknown file in insertRecord:',
+ media.directory + f, getreq.error);
+ next();
+ };
+ getreq.onsuccess = function() {
+ parseMetadata(getreq.result, f);
+ };
+ }
+ else {
+ // otherwise f is the file we want
+ parseMetadata(f, f.name);
+ }
+ }
+
+ function parseMetadata(file, filename) {
+ if (!file.lastModifiedDate) {
+ console.warn('MediaDB: parseMetadata: no lastModifiedDate for',
+ filename,
+ 'using Date.now() until #793955 is fixed');
+ }
+
+ // Basic information about the file
+ var fileinfo = {
+ name: filename, // we can't trust file.name
+ type: file.type,
+ size: file.size,
+ date: file.lastModifiedDate ?
+ file.lastModifiedDate.getTime() :
+ Date.now()
+ };
+
+ if (fileinfo.date > details.newestFileModTime)
+ details.newestFileModTime = fileinfo.date;
+
+ // Get metadata about the file
+ media.metadataParser(file, gotMetadata, metadataError);
+ function metadataError(e) {
+ console.warn('MediaDB: error parsing metadata for',
+ filename, ':', e);
+ // If we get an error parsing the metadata, assume it is invalid
+ // and make a note in the fileinfo record that we store in the database
+ // If we don't store it in the database, we'll keep finding it
+ // on every scan. But we make sure never to return the invalid file
+ // on an enumerate call.
+ fileinfo.fail = true;
+ storeRecord(fileinfo);
+ }
+ function gotMetadata(metadata) {
+ fileinfo.metadata = metadata;
+ storeRecord(fileinfo);
+ }
+ }
+
+ function storeRecord(fileinfo) {
+ if (media.details.firstscan) {
+ // If this is the first scan then we know this is a new file and
+ // we can assume that adding it to the db will succeed.
+ // So we can just queue a notification about the new file without
+ // waiting for a db operation.
+ media.details.records.push(fileinfo);
+ if (!fileinfo.fail) {
+ queueCreateNotification(media, fileinfo);
+ }
+ // And go on to the next
+ next();
+ }
+ else {
+ // If this is not the first scan, then we may already have a db
+ // record for this new file. In that case, the call to add() above
+ // is going to fail. We need to handle that case, so we can't send
+ // out the new file notification until we get a response to the add().
+ var transaction = media.db.transaction('files', 'readwrite');
+ var store = transaction.objectStore('files');
+ var request = store.add(fileinfo);
+
+ request.onsuccess = function() {
+ // Remember to send an event about this new file
+ if (!fileinfo.fail)
+ queueCreateNotification(media, fileinfo);
+ // And go on to the next
+ next();
+ };
+ request.onerror = function(event) {
+ // If the error name is 'ConstraintError' it means that the
+ // file already exists in the database. So try again, using put()
+ // instead of add(). If that succeeds, then queue a delete
+ // notification along with the insert notification. If the
+ // second try fails, or if the error was something different
+ // then issue a warning and continue with the next.
+ if (request.error.name === 'ConstraintError') {
+ // Don't let the higher-level DB error handler report the error
+ event.stopPropagation();
+ // And don't spew a default error message to the console either
+ event.preventDefault();
+ var putrequest = store.put(fileinfo);
+ putrequest.onsuccess = function() {
+ queueDeleteNotification(media, fileinfo.name);
+ if (!fileinfo.fail)
+ queueCreateNotification(media, fileinfo);
+ next();
+ };
+ putrequest.onerror = function() {
+ // Report and move on
+ console.error('MediaDB: unexpected ConstraintError',
+ 'in insertRecord for file:', fileinfo.name);
+ next();
+ };
+ }
+ else {
+ // Something unexpected happened!
+ // All we can do is report it and move on
+ console.error('MediaDB: unexpected error in insertRecord:',
+ request.error, 'for file:', fileinfo.name);
+ next();
+ }
+ };
+ }
+ }
+ }
+
+ // Don't send out notification events right away. Wait a short time to
+ // see if others arrive that we can batch up. This is common for scanning
+ function queueCreateNotification(media, fileinfo) {
+ var creates = media.details.pendingCreateNotifications;
+ creates.push(fileinfo);
+ if (media.batchSize && creates.length >= media.batchSize)
+ sendNotifications(media);
+ else
+ resetNotificationTimer(media);
+ }
+
+ function queueDeleteNotification(media, filename) {
+ var deletes = media.details.pendingDeleteNotifications;
+ deletes.push(filename);
+ if (media.batchSize && deletes.length >= media.batchSize)
+ sendNotifications(media);
+ else
+ resetNotificationTimer(media);
+ }
+
+ function resetNotificationTimer(media) {
+ var details = media.details;
+ if (details.pendingNotificationTimer)
+ clearTimeout(details.pendingNotificationTimer);
+ details.pendingNotificationTimer =
+ setTimeout(function() { sendNotifications(media); },
+ media.batchHoldTime);
+ }
+
+ // Send out notifications for creations and deletions
+ function sendNotifications(media) {
+ var details = media.details;
+ if (details.pendingNotificationTimer) {
+ clearTimeout(details.pendingNotificationTimer);
+ details.pendingNotificationTimer = null;
+ }
+ if (details.pendingDeleteNotifications.length > 0) {
+ var deletions = details.pendingDeleteNotifications;
+ details.pendingDeleteNotifications = [];
+ dispatchEvent(media, 'deleted', deletions);
+ }
+
+ if (details.pendingCreateNotifications.length > 0) {
+
+ // If this is a first scan, and we have records that are not
+ // in the db yet, write them to the db now
+ if (details.firstscan && details.records.length > 0) {
+ var transaction = media.db.transaction('files', 'readwrite');
+ var store = transaction.objectStore('files');
+ for (var i = 0; i < details.records.length; i++)
+ store.add(details.records[i]);
+ details.records.length = 0;
+ }
+
+ var creations = details.pendingCreateNotifications;
+ details.pendingCreateNotifications = [];
+ dispatchEvent(media, 'created', creations);
+ }
+ }
+
+ function dispatchEvent(media, type, detail) {
+ var handler = media['on' + type];
+ var listeners = media.details.eventListeners[type];
+
+ // Return if there is nothing to handle the event
+ if (!handler && (!listeners || listeners.length == 0))
+ return;
+
+ // We use a fake event object
+ var event = {
+ type: type,
+ target: media,
+ currentTarget: media,
+ timestamp: Date.now(),
+ detail: detail
+ };
+
+ // Call the 'on' handler property if there is one
+ if (typeof handler === 'function') {
+ try {
+ handler.call(media, event);
+ }
+ catch (e) {
+ console.warn('MediaDB: ', 'on' + type, 'event handler threw', e);
+ }
+ }
+
+ // Now call the listeners if there are any
+ if (!listeners)
+ return;
+ for (var i = 0; i < listeners.length; i++) {
+ try {
+ var listener = listeners[i];
+ if (typeof listener === 'function') {
+ listener.call(media, event);
+ }
+ else {
+ listener.handleEvent(event);
+ }
+ }
+ catch (e) {
+ console.warn('MediaDB: ', type, 'event listener threw', e);
+ }
+ }
+ }
+
+ function changeState(media, state) {
+ if (media.state !== state) {
+ media.state = state;
+ if (state === MediaDB.READY)
+ dispatchEvent(media, 'ready');
+ else
+ dispatchEvent(media, 'unavailable', state);
+ }
+ }
+
+ return MediaDB;
+
+}());
diff --git a/shared/js/mobile_operator.js b/shared/js/mobile_operator.js
new file mode 100644
index 0000000..edd2b44
--- /dev/null
+++ b/shared/js/mobile_operator.js
@@ -0,0 +1,96 @@
+'use strict';
+
+var MobileOperator = {
+ BRAZIL_MCC: 724,
+ BRAZIL_CELLBROADCAST_CHANNEL: 50,
+
+ userFacingInfo: function mo_userFacingInfo(mobileConnection) {
+ var network = mobileConnection.voice.network;
+ var iccInfo = mobileConnection.iccInfo;
+ var operator = network.shortName || network.longName;
+
+ if (iccInfo.isDisplaySpnRequired && iccInfo.spn
+ && !mobileConnection.voice.roaming) {
+ if (iccInfo.isDisplayNetworkNameRequired) {
+ operator = operator + ' ' + iccInfo.spn;
+ } else {
+ operator = iccInfo.spn;
+ }
+ }
+
+ var carrier, region;
+ if (this.isBrazil(mobileConnection)) {
+ // We are in Brazil, It is legally required to show local info
+ // about current registered GSM network in a legally specified way.
+ var lac = mobileConnection.voice.cell.gsmLocationAreaCode % 100;
+ var carriers = MobileInfo.brazil.carriers;
+ var regions = MobileInfo.brazil.regions;
+
+ carrier = carriers[network.mnc] || (this.BRAZIL_MCC.toString() + network.mnc);
+ region = (regions[lac] ? regions[lac] + ' ' + lac : '');
+ }
+
+ return {
+ 'operator': operator,
+ 'carrier': carrier,
+ 'region': region
+ };
+ },
+
+ isBrazil: function mo_isBrazil(mobileConnection) {
+ var cell = mobileConnection.voice.cell;
+ return mobileConnection.voice.network.mcc == this.BRAZIL_MCC &&
+ cell && cell.gsmLocationAreaCode;
+ }
+};
+
+
+var MobileInfo = {
+ brazil: {
+ carriers: {
+ '0': 'NEXTEL',
+ '2': 'TIM', '3': 'TIM', '4': 'TIM',
+ '5': 'CLARO', '6': 'VIVO', '7': 'CTBC', '8': 'TIM',
+ '10': 'VIVO', '11': 'VIVO', '15': 'SERCOMTEL',
+ '16': 'OI', '23': 'VIVO', '24': 'OI', '31': 'OI',
+ '32': 'CTBC', '33': 'CTBC', '34': 'CTBC', '37': 'AEIOU'
+ },
+ regions: {
+ '11': 'SP', '12': 'SP', '13': 'SP', '14': 'SP', '15': 'SP', '16': 'SP',
+ '17': 'SP', '18': 'SP', '19': 'SP',
+ '21': 'RJ', '22': 'RJ', '24': 'RJ',
+ '27': 'ES', '28': 'ES',
+ '31': 'MG', '32': 'MG', '33': 'MG', '34': 'MG', '35': 'MG', '37': 'MG',
+ '38': 'MG',
+ '41': 'PR', '42': 'PR', '43': 'PR', '44': 'PR', '45': 'PR', '46': 'PR',
+ '47': 'SC', '48': 'SC', '49': 'SC',
+ '51': 'RS', '53': 'RS', '54': 'RS', '55': 'RS',
+ '61': 'DF',
+ '62': 'GO',
+ '63': 'TO',
+ '64': 'GO',
+ '65': 'MT', '66': 'MT',
+ '67': 'MS',
+ '68': 'AC',
+ '69': 'RO',
+ '71': 'BA', '73': 'BA', '74': 'BA', '75': 'BA', '77': 'BA',
+ '79': 'SE',
+ '81': 'PE',
+ '82': 'AL',
+ '83': 'PB',
+ '84': 'RN',
+ '85': 'CE',
+ '86': 'PI',
+ '87': 'PE',
+ '88': 'CE',
+ '89': 'PI',
+ '91': 'PA',
+ '92': 'AM',
+ '93': 'PA', '94': 'PA',
+ '95': 'RR',
+ '96': 'AP',
+ '97': 'AM',
+ '98': 'MA', '99': 'MA'
+ }
+ }
+};
diff --git a/shared/js/mouse_event_shim.js b/shared/js/mouse_event_shim.js
new file mode 100644
index 0000000..053ef7f
--- /dev/null
+++ b/shared/js/mouse_event_shim.js
@@ -0,0 +1,282 @@
+/**
+ * mouse_event_shim.js: generate mouse events from touch events.
+ *
+ * This library listens for touch events and generates mousedown, mousemove
+ * mouseup, and click events to match them. It captures and dicards any
+ * real mouse events (non-synthetic events with isTrusted true) that are
+ * send by gecko so that there are not duplicates.
+ *
+ * This library does emit mouseover/mouseout and mouseenter/mouseleave
+ * events. You can turn them off by setting MouseEventShim.trackMouseMoves to
+ * false. This means that mousemove events will always have the same target
+ * as the mousedown even that began the series. You can also call
+ * MouseEventShim.setCapture() from a mousedown event handler to prevent
+ * mouse tracking until the next mouseup event.
+ *
+ * This library does not support multi-touch but should be sufficient
+ * to do drags based on mousedown/mousemove/mouseup events.
+ *
+ * This library does not emit dblclick events or contextmenu events
+ */
+
+'use strict';
+
+(function() {
+ // Make sure we don't run more than once
+ if (MouseEventShim)
+ return;
+
+ // Bail if we're not on running on a platform that sends touch
+ // events. We don't need the shim code for mouse events.
+ try {
+ document.createEvent('TouchEvent');
+ } catch (e) {
+ return;
+ }
+
+ var starttouch; // The Touch object that we started with
+ var target; // The element the touch is currently over
+ var emitclick; // Will we be sending a click event after mouseup?
+
+ // Use capturing listeners to discard all mouse events from gecko
+ window.addEventListener('mousedown', discardEvent, true);
+ window.addEventListener('mouseup', discardEvent, true);
+ window.addEventListener('mousemove', discardEvent, true);
+ window.addEventListener('click', discardEvent, true);
+
+ function discardEvent(e) {
+ if (e.isTrusted) {
+ e.stopImmediatePropagation(); // so it goes no further
+ if (e.type === 'click')
+ e.preventDefault(); // so it doesn't trigger a change event
+ }
+ }
+
+ // Listen for touch events that bubble up to the window.
+ // If other code has called stopPropagation on the touch events
+ // then we'll never see them. Also, we'll honor the defaultPrevented
+ // state of the event and will not generate synthetic mouse events
+ window.addEventListener('touchstart', handleTouchStart);
+ window.addEventListener('touchmove', handleTouchMove);
+ window.addEventListener('touchend', handleTouchEnd);
+ window.addEventListener('touchcancel', handleTouchEnd); // Same as touchend
+
+ function handleTouchStart(e) {
+ // If we're already handling a touch, ignore this one
+ if (starttouch)
+ return;
+
+ // Ignore any event that has already been prevented
+ if (e.defaultPrevented)
+ return;
+
+ // Sometimes an unknown gecko bug causes us to get a touchstart event
+ // for an iframe target that we can't use because it is cross origin.
+ // Don't start handling a touch in that case
+ try {
+ e.changedTouches[0].target.ownerDocument;
+ }
+ catch (e) {
+ // Ignore the event if we can't see the properties of the target
+ return;
+ }
+
+ // If there is more than one simultaneous touch, ignore all but the first
+ starttouch = e.changedTouches[0];
+ target = starttouch.target;
+ emitclick = true;
+
+ // Move to the position of the touch
+ emitEvent('mousemove', target, starttouch);
+
+ // Now send a synthetic mousedown
+ var result = emitEvent('mousedown', target, starttouch);
+
+ // If the mousedown was prevented, pass that on to the touch event.
+ // And remember not to send a click event
+ if (!result) {
+ e.preventDefault();
+ emitclick = false;
+ }
+ }
+
+ function handleTouchEnd(e) {
+ if (!starttouch)
+ return;
+
+ // End a MouseEventShim.setCapture() call
+ if (MouseEventShim.capturing) {
+ MouseEventShim.capturing = false;
+ MouseEventShim.captureTarget = null;
+ }
+
+ for (var i = 0; i < e.changedTouches.length; i++) {
+ var touch = e.changedTouches[i];
+ // If the ended touch does not have the same id, skip it
+ if (touch.identifier !== starttouch.identifier)
+ continue;
+
+ emitEvent('mouseup', target, touch);
+
+ // If target is still the same element we started and the touch did not
+ // move more than the threshold and if the user did not prevent
+ // the mousedown, then send a click event, too.
+ if (emitclick)
+ emitEvent('click', starttouch.target, touch);
+
+ starttouch = null;
+ return;
+ }
+ }
+
+ function handleTouchMove(e) {
+ if (!starttouch)
+ return;
+
+ for (var i = 0; i < e.changedTouches.length; i++) {
+ var touch = e.changedTouches[i];
+ // If the ended touch does not have the same id, skip it
+ if (touch.identifier !== starttouch.identifier)
+ continue;
+
+ // Don't send a mousemove if the touchmove was prevented
+ if (e.defaultPrevented)
+ return;
+
+ // See if we've moved too much to emit a click event
+ var dx = Math.abs(touch.screenX - starttouch.screenX);
+ var dy = Math.abs(touch.screenY - starttouch.screenY);
+ if (dx > MouseEventShim.dragThresholdX ||
+ dy > MouseEventShim.dragThresholdY) {
+ emitclick = false;
+ }
+
+ var tracking = MouseEventShim.trackMouseMoves &&
+ !MouseEventShim.capturing;
+
+ if (tracking) {
+ // If the touch point moves, then the element it is over
+ // may have changed as well. Note that calling elementFromPoint()
+ // forces a layout if one is needed.
+ // XXX: how expensive is it to do this on each touchmove?
+ // Can we listen for (non-standard) touchleave events instead?
+ var oldtarget = target;
+ var newtarget = document.elementFromPoint(touch.clientX, touch.clientY);
+ if (newtarget === null) {
+ // this can happen as the touch is moving off of the screen, e.g.
+ newtarget = oldtarget;
+ }
+ if (newtarget !== oldtarget) {
+ leave(oldtarget, newtarget, touch); // mouseout, mouseleave
+ target = newtarget;
+ }
+ }
+ else if (MouseEventShim.captureTarget) {
+ target = MouseEventShim.captureTarget;
+ }
+
+ emitEvent('mousemove', target, touch);
+
+ if (tracking && newtarget !== oldtarget) {
+ enter(newtarget, oldtarget, touch); // mouseover, mouseenter
+ }
+ }
+ }
+
+ // Return true if element a contains element b
+ function contains(a, b) {
+ return (a.compareDocumentPosition(b) & 16) !== 0;
+ }
+
+ // A touch has left oldtarget and entered newtarget
+ // Send out all the events that are required
+ function leave(oldtarget, newtarget, touch) {
+ emitEvent('mouseout', oldtarget, touch, newtarget);
+
+ // If the touch has actually left oldtarget (and has not just moved
+ // into a child of oldtarget) send a mouseleave event. mouseleave
+ // events don't bubble, so we have to repeat this up the hierarchy.
+ for (var e = oldtarget; !contains(e, newtarget); e = e.parentNode) {
+ emitEvent('mouseleave', e, touch, newtarget);
+ }
+ }
+
+ // A touch has entered newtarget from oldtarget
+ // Send out all the events that are required.
+ function enter(newtarget, oldtarget, touch) {
+ emitEvent('mouseover', newtarget, touch, oldtarget);
+
+ // Emit non-bubbling mouseenter events if the touch actually entered
+ // newtarget and wasn't already in some child of it
+ for (var e = newtarget; !contains(e, oldtarget); e = e.parentNode) {
+ emitEvent('mouseenter', e, touch, oldtarget);
+ }
+ }
+
+ function emitEvent(type, target, touch, relatedTarget) {
+ var synthetic = document.createEvent('MouseEvents');
+ var bubbles = (type !== 'mouseenter' && type !== 'mouseleave');
+ var count =
+ (type === 'mousedown' || type === 'mouseup' || type === 'click') ? 1 : 0;
+
+ synthetic.initMouseEvent(type,
+ bubbles, // canBubble
+ true, // cancelable
+ window,
+ count, // detail: click count
+ touch.screenX,
+ touch.screenY,
+ touch.clientX,
+ touch.clientY,
+ false, // ctrlKey: we don't have one
+ false, // altKey: we don't have one
+ false, // shiftKey: we don't have one
+ false, // metaKey: we don't have one
+ 0, // we're simulating the left button
+ relatedTarget || null);
+
+ try {
+ return target.dispatchEvent(synthetic);
+ }
+ catch (e) {
+ console.warn('Exception calling dispatchEvent', type, e);
+ return true;
+ }
+ }
+}());
+
+var MouseEventShim = {
+ // It is a known gecko bug that synthetic events have timestamps measured
+ // in microseconds while regular events have timestamps measured in
+ // milliseconds. This utility function returns a the timestamp converted
+ // to milliseconds, if necessary.
+ getEventTimestamp: function(e) {
+ if (e.isTrusted) // XXX: Are real events always trusted?
+ return e.timeStamp;
+ else
+ return e.timeStamp / 1000;
+ },
+
+ // Set this to false if you don't care about mouseover/out events
+ // and don't want the target of mousemove events to follow the touch
+ trackMouseMoves: true,
+
+ // Call this function from a mousedown event handler if you want to guarantee
+ // that the mousemove and mouseup events will go to the same element
+ // as the mousedown even if they leave the bounds of the element. This is
+ // like setting trackMouseMoves to false for just one drag. It is a
+ // substitute for event.target.setCapture(true)
+ setCapture: function(target) {
+ this.capturing = true; // Will be set back to false on mouseup
+ if (target)
+ this.captureTarget = target;
+ },
+
+ capturing: false,
+
+ // Keep these in sync with ui.dragThresholdX and ui.dragThresholdY prefs.
+ // If a touch ever moves more than this many pixels from its starting point
+ // then we will not synthesize a click event when the touch ends.
+ dragThresholdX: 25,
+ dragThresholdY: 25
+};
diff --git a/shared/js/notification_helper.js b/shared/js/notification_helper.js
new file mode 100644
index 0000000..384fbfc
--- /dev/null
+++ b/shared/js/notification_helper.js
@@ -0,0 +1,68 @@
+/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- /
+/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
+
+'use strict';
+
+/**
+ * Keeping a reference on all active notifications to avoid weird GC issues.
+ * See https://bugzilla.mozilla.org/show_bug.cgi?id=755402
+ */
+
+var NotificationHelper = {
+ _referencesArray: [],
+
+ getIconURI: function nc_getIconURI(app, entryPoint) {
+ var icons = app.manifest.icons;
+
+ if (entryPoint) {
+ icons = app.manifest.entry_points[entryPoint].icons;
+ }
+
+ if (!icons)
+ return null;
+
+ var sizes = Object.keys(icons).map(function parse(str) {
+ return parseInt(str, 10);
+ });
+ sizes.sort(function(x, y) { return y - x; });
+
+ var HVGA = document.documentElement.clientWidth < 480;
+ var index = sizes[HVGA ? sizes.length - 1 : 0];
+ return app.installOrigin + icons[index];
+ },
+
+ send: function nc_send(title, body, icon, clickCB, closeCB) {
+ if (!('mozNotification' in navigator))
+ return;
+
+ var notification = navigator.mozNotification.createNotification(title,
+ body, icon);
+
+ notification.onclick = (function() {
+ if (clickCB)
+ clickCB();
+
+ this._forget(notification);
+ }).bind(this);
+
+ notification.onclose = (function() {
+ if (closeCB)
+ closeCB();
+
+ this._forget(notification);
+ }).bind(this);
+
+ notification.show();
+ this._keep(notification);
+ },
+
+ _keep: function nc_keep(notification) {
+ this._referencesArray.push(notification);
+ },
+ _forget: function nc_forget(notification) {
+ this._referencesArray.splice(
+ this._referencesArray.indexOf(notification), 1
+ );
+ }
+};
+
diff --git a/shared/js/phoneNumberJS/PhoneNumber.js b/shared/js/phoneNumberJS/PhoneNumber.js
new file mode 100644
index 0000000..16f9e80
--- /dev/null
+++ b/shared/js/phoneNumberJS/PhoneNumber.js
@@ -0,0 +1,335 @@
+/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
+
+var PhoneNumber = (function (dataBase) {
+ // Use strict in our context only - users might not want it
+ 'use strict';
+
+ const UNICODE_DIGITS = /[\uFF10-\uFF19\u0660-\u0669\u06F0-\u06F9]/g;
+ const ALPHA_CHARS = /[a-zA-Z]/g;
+ const NON_ALPHA_CHARS = /[^a-zA-Z]/g;
+ const NON_DIALABLE_CHARS = /[^+\*\d]/g;
+ const PLUS_CHARS = /^[+\uFF0B]+/;
+ const BACKSLASH = /\\/g;
+ const COMMACOMMA = /,,/g;
+ const COMMABRACKET = /,]/g;
+ const SPLIT_FIRST_GROUP = /^(\d+)(.*)$/;
+
+ // Format of the string encoded meta data. If the name contains "^" or "$"
+ // we will generate a regular expression from the value, with those special
+ // characters as prefix/suffix.
+ const META_DATA_ENCODING = ["region",
+ "^internationalPrefix",
+ "nationalPrefix",
+ "^nationalPrefixForParsing",
+ "nationalPrefixTransformRule",
+ "nationalPrefixFormattingRule",
+ "^possiblePattern$",
+ "^nationalPattern$",
+ "formats"];
+
+ const FORMAT_ENCODING = ["^pattern$",
+ "nationalFormat",
+ "^leadingDigits",
+ "nationalPrefixFormattingRule",
+ "internationalFormat"];
+
+ var regionCache = Object.create(null);
+
+ // Parse an array of strings into a convenient object. We store meta
+ // data as arrays since thats much more compact than JSON.
+ function ParseArray(array, encoding, obj) {
+ for (var n = 0; n < encoding.length; ++n) {
+ var value = array[n];
+ if (!value)
+ continue;
+ var field = encoding[n];
+ var fieldAlpha = field.replace(NON_ALPHA_CHARS, "");
+ if (field != fieldAlpha)
+ value = new RegExp(field.replace(fieldAlpha, value));
+ obj[fieldAlpha] = value;
+ }
+ return obj;
+ }
+
+ // Parse string encoded meta data into a convenient object
+ // representation.
+ function ParseMetaData(countryCode, md) {
+ var array = JSON.parse(md.replace(BACKSLASH, "\\\\").replace(COMMACOMMA, ', null,').replace(COMMACOMMA, ', null,').replace(COMMABRACKET, ', null]'));
+ md = ParseArray(array,
+ META_DATA_ENCODING,
+ { countryCode: countryCode });
+ regionCache[md.region] = md;
+ return md;
+ }
+
+ // Parse string encoded format data into a convenient object
+ // representation.
+ function ParseFormat(md) {
+ var formats = md.formats;
+ // Bail if we already parsed the format definitions.
+ if (!(Array.isArray(formats[0])))
+ return;
+ for (var n = 0; n < formats.length; ++n) {
+ formats[n] = ParseArray(formats[n],
+ FORMAT_ENCODING,
+ {});
+ }
+ }
+
+ // Search for the meta data associated with a region identifier ("US") in
+ // our database, which is indexed by country code ("1"). Since we have
+ // to walk the entire database for this, we cache the result of the lookup
+ // for future reference.
+ function FindMetaDataForRegion(region) {
+ // Check in the region cache first. This will find all entries we have
+ // already resolved (parsed from a string encoding).
+ var md = regionCache[region];
+ if (md)
+ return md;
+ for (var countryCode in dataBase) {
+ var entry = dataBase[countryCode];
+ // Each entry is a string encoded object of the form '["US..', or
+ // an array of strings. We don't want to parse the string here
+ // to save memory, so we just substring the region identifier
+ // and compare it. For arrays, we compare against all region
+ // identifiers with that country code. We skip entries that are
+ // of type object, because they were already resolved (parsed into
+ // an object), and their country code should have been in the cache.
+ if (Array.isArray(entry)) {
+ for (var n = 0; n < entry.length; ++n) {
+ if (typeof entry[n] == "string" && entry[n].substr(2,2) == region)
+ return entry[n] = ParseMetaData(countryCode, entry[n]);
+ }
+ continue;
+ }
+ if (typeof entry == "string" && entry.substr(2,2) == region)
+ return dataBase[countryCode] = ParseMetaData(countryCode, entry);
+ }
+ }
+
+ // Format a national number for a given region. The boolean flag "intl"
+ // indicates whether we want the national or international format.
+ function FormatNumber(regionMetaData, number, intl) {
+ // We lazily parse the format description in the meta data for the region,
+ // so make sure to parse it now if we haven't already done so.
+ ParseFormat(regionMetaData);
+ var formats = regionMetaData.formats;
+ for (var n = 0; n < formats.length; ++n) {
+ var format = formats[n];
+ // The leading digits field is optional. If we don't have it, just
+ // use the matching pattern to qualify numbers.
+ if (format.leadingDigits && !format.leadingDigits.test(number))
+ continue;
+ if (!format.pattern.test(number))
+ continue;
+ if (intl) {
+ // If there is no international format, just fall back to the national
+ // format.
+ var internationalFormat = format.internationalFormat;
+ if (!internationalFormat)
+ internationalFormat = format.nationalFormat;
+ // Some regions have numbers that can't be dialed from outside the
+ // country, indicated by "NA" for the international format of that
+ // number format pattern.
+ if (internationalFormat == "NA")
+ return null;
+ // Prepend "+" and the country code.
+ number = "+" + regionMetaData.countryCode + " " +
+ number.replace(format.pattern, internationalFormat);
+ } else {
+ number = number.replace(format.pattern, format.nationalFormat);
+ // The region has a national prefix formatting rule, and it can be overwritten
+ // by each actual number format rule.
+ var nationalPrefixFormattingRule = regionMetaData.nationalPrefixFormattingRule;
+ if (format.nationalPrefixFormattingRule)
+ nationalPrefixFormattingRule = format.nationalPrefixFormattingRule;
+ if (nationalPrefixFormattingRule) {
+ // The prefix formatting rule contains two magic markers, "$NP" and "$FG".
+ // "$NP" will be replaced by the national prefix, and "$FG" with the
+ // first group of numbers.
+ var match = number.match(SPLIT_FIRST_GROUP);
+ var firstGroup = match[1];
+ var rest = match[2];
+ var prefix = nationalPrefixFormattingRule;
+ prefix = prefix.replace("$NP", regionMetaData.nationalPrefix);
+ prefix = prefix.replace("$FG", firstGroup);
+ number = prefix + rest;
+ }
+ }
+ return (number == "NA") ? null : number;
+ }
+ return null;
+ }
+
+ function NationalNumber(regionMetaData, number) {
+ this.region = regionMetaData.region;
+ this.regionMetaData = regionMetaData;
+ this.nationalNumber = number;
+ }
+
+ // NationalNumber represents the result of parsing a phone number. We have
+ // three getters on the prototype that format the number in national and
+ // international format. Once called, the getters put a direct property
+ // onto the object, caching the result.
+ NationalNumber.prototype = {
+ // +1 949-726-2896
+ get internationalFormat() {
+ var value = FormatNumber(this.regionMetaData, this.nationalNumber, true);
+ Object.defineProperty(this, "internationalFormat", { value: value, enumerable: true });
+ return value;
+ },
+ // (949) 726-2896
+ get nationalFormat() {
+ var value = FormatNumber(this.regionMetaData, this.nationalNumber, false);
+ Object.defineProperty(this, "nationalFormat", { value: value, enumerable: true });
+ return value;
+ },
+ // +19497262896
+ get internationalNumber() {
+ var value = this.internationalFormat.replace(NON_DIALABLE_CHARS, "");
+ Object.defineProperty(this, "nationalNumber", { value: value, enumerable: true });
+ return value;
+ }
+ };
+
+ // Normalize a number by converting unicode numbers and symbols to their
+ // ASCII equivalents and removing all non-dialable characters.
+ function NormalizeNumber(number) {
+ number = number.replace(UNICODE_DIGITS,
+ function (ch) {
+ return String.fromCharCode(48 + (ch.charCodeAt(0) & 0xf));
+ });
+ number = number.replace(ALPHA_CHARS,
+ function (ch) {
+ return (ch.toLowerCase().charCodeAt(0) - 97)/3+2 | 0;
+ });
+ number = number.replace(PLUS_CHARS, "+");
+ number = number.replace(NON_DIALABLE_CHARS, "");
+ return number;
+ }
+
+ // Check whether the number is valid for the given region.
+ function IsValidNumber(number, md) {
+ return md.possiblePattern.test(number);
+ }
+
+ // Check whether the number is a valid national number for the given region.
+ function IsNationalNumber(number, md) {
+ return IsValidNumber(number, md) && md.nationalPattern.test(number);
+ }
+
+ // Determine the country code a number starts with, or return null if
+ // its not a valid country code.
+ function ParseCountryCode(number) {
+ for (var n = 1; n <= 3; ++n) {
+ var cc = number.substr(0,n);
+ if (dataBase[cc])
+ return cc;
+ }
+ return null;
+ }
+
+ // Parse an international number that starts with the country code. Return
+ // null if the number is not a valid international number.
+ function ParseInternationalNumber(number) {
+ var ret;
+
+ // Parse and strip the country code.
+ var countryCode = ParseCountryCode(number);
+ if (!countryCode)
+ return null;
+ number = number.substr(countryCode.length);
+
+ // Lookup the meta data for the region (or regions) and if the rest of
+ // the number parses for that region, return the parsed number.
+ var entry = dataBase[countryCode];
+ if (Array.isArray(entry)) {
+ for (var n = 0; n < entry.length; ++n) {
+ if (typeof entry[n] == "string")
+ entry[n] = ParseMetaData(countryCode, entry[n]);
+ if (ret = ParseNationalNumber(number, entry[n]))
+ return ret;
+ }
+ return null;
+ }
+ if (typeof entry == "string")
+ entry = dataBase[countryCode] = ParseMetaData(countryCode, entry);
+ return ParseNationalNumber(number, entry);
+ }
+
+ // Parse a national number for a specific region. Return null if the
+ // number is not a valid national number (it might still be a possible
+ // number for parts of that region).
+ function ParseNationalNumber(number, md) {
+ if (!md.possiblePattern.test(number) ||
+ !md.nationalPattern.test(number)) {
+ return null;
+ }
+ // Success.
+ return new NationalNumber(md, number);
+ }
+
+ // Parse a number and transform it into the national format, removing any
+ // international dial prefixes and country codes.
+ function ParseNumber(number, defaultRegion) {
+ var ret;
+
+ // Remove formating characters and whitespace.
+ number = NormalizeNumber(number);
+
+ // Detect and strip leading '+'.
+ if (number[0] === '+')
+ return ParseInternationalNumber(number.replace(PLUS_CHARS, ""));
+
+ // Lookup the meta data for the given region.
+ var md = FindMetaDataForRegion(defaultRegion.toUpperCase());
+
+ // See if the number starts with an international prefix, and if the
+ // number resulting from stripping the code is valid, then remove the
+ // prefix and flag the number as international.
+ if (md.internationalPrefix.test(number)) {
+ var possibleNumber = number.replace(md.internationalPrefix, "");
+ if (ret = ParseInternationalNumber(possibleNumber))
+ return ret;
+ }
+
+ // This is not an international number. See if its a national one for
+ // the current region. National numbers can start with the national
+ // prefix, or without.
+ if (md.nationalPrefixForParsing) {
+ // Some regions have specific national prefix parse rules. Apply those.
+ var withoutPrefix = number.replace(md.nationalPrefixForParsing,
+ md.nationalPrefixTransformRule);
+ if (ret = ParseNationalNumber(withoutPrefix, md))
+ return ret;
+ } else {
+ // If there is no specific national prefix rule, just strip off the
+ // national prefix from the beginning of the number (if there is one).
+ var nationalPrefix = md.nationalPrefix;
+ if (nationalPrefix && number.indexOf(nationalPrefix) == 0 &&
+ (ret = ParseNationalNumber(number.substr(nationalPrefix.length), md))) {
+ return ret;
+ }
+ }
+ if (ret = ParseNationalNumber(number, md))
+ return ret;
+
+ // If the number matches the possible numbers of the current region,
+ // return it as a possible number.
+ if (md.possiblePattern.test(number))
+ return new NationalNumber(md, number);
+
+ // Now lets see if maybe its an international number after all, but
+ // without '+' or the international prefix.
+ if (ret = ParseInternationalNumber(number))
+ return ret;
+
+ // We couldn't parse the number at all.
+ return null;
+ }
+
+ return {
+ Parse: ParseNumber
+ };
+})(PHONE_NUMBER_META_DATA);
diff --git a/shared/js/phoneNumberJS/PhoneNumberMetaData.js b/shared/js/phoneNumberJS/PhoneNumberMetaData.js
new file mode 100644
index 0000000..a901a78
--- /dev/null
+++ b/shared/js/phoneNumberJS/PhoneNumberMetaData.js
@@ -0,0 +1,218 @@
+/* Automatically generated. Do not edit. */
+const PHONE_NUMBER_META_DATA = {
+"46": '["SE","00","0",,,"$NP$FG","\\d{5,10}","[1-9]\\d{6,9}",[["(8)(\\d{2,3})(\\d{2,3})(\\d{2})","$1-$2 $3 $4","8",,"$1 $2 $3 $4"],["([1-69]\\d)(\\d{2,3})(\\d{2})(\\d{2})","$1-$2 $3 $4","1[013689]|2[0136]|3[1356]|4[0246]|54|6[03]|90",,"$1 $2 $3 $4"],["([1-69]\\d)(\\d{3})(\\d{2})","$1-$2 $3","1[13689]|2[136]|3[1356]|4[0246]|54|6[03]|90",,"$1 $2 $3"],["(\\d{3})(\\d{2})(\\d{2})(\\d{2})","$1-$2 $3 $4","1[2457]|2[2457-9]|3[0247-9]|4[1357-9]|5[0-35-9]|6[124-9]|9(?:[125-8]|3[0-5]|4[0-3])",,"$1 $2 $3 $4"],["(\\d{3})(\\d{2,3})(\\d{2})","$1-$2 $3","1[2457]|2[2457-9]|3[0247-9]|4[1357-9]|5[0-35-9]|6[124-9]|9(?:[125-8]|3[0-5]|4[0-3])",,"$1 $2 $3"],["(7\\d)(\\d{3})(\\d{2})(\\d{2})","$1-$2 $3 $4","7",,"$1 $2 $3 $4"],["(20)(\\d{2,3})(\\d{2})","$1-$2 $3","20",,"$1 $2 $3"],["(9[034]\\d)(\\d{2})(\\d{2})(\\d{3})","$1-$2 $3 $4","9[034]",,"$1 $2 $3 $4"]]]',
+"299": '["GL","00",,,,,"\\d{6}","[1-689]\\d{5}",[["(\\d{2})(\\d{2})(\\d{2})","$1 $2 $3",,,]]]',
+"385": '["HR","00","0",,,"$NP$FG","\\d{6,12}","[1-7]\\d{5,8}|[89]\\d{6,11}",[["(1)(\\d{4})(\\d{3})","$1 $2 $3","1",,],["(6[09])(\\d{4})(\\d{3})","$1 $2 $3","6[09]",,],["(62)(\\d{3})(\\d{3,4})","$1 $2 $3","62",,],["([2-5]\\d)(\\d{3})(\\d{3})","$1 $2 $3","[2-5]",,],["(9\\d)(\\d{3})(\\d{3,4})","$1 $2 $3","9",,],["(9\\d)(\\d{4})(\\d{4})","$1 $2 $3","9",,],["(9\\d)(\\d{3,4})(\\d{3})(\\d{3})","$1 $2 $3 $4","9",,],["(\\d{2})(\\d{2})(\\d{2,3})","$1 $2 $3","6[145]|7",,],["(\\d{2})(\\d{3,4})(\\d{3})","$1 $2 $3","6[145]|7",,],["(80[01])(\\d{2})(\\d{2,3})","$1 $2 $3","8",,],["(80[01])(\\d{3,4})(\\d{3})","$1 $2 $3","8",,]]]',
+"670": '["TL","00",,,,,"\\d{7,8}","[2-489]\\d{6}|7\\d{6,7}",[["(\\d{3})(\\d{4})","$1 $2","[2-489]",,],["(\\d{4})(\\d{4})","$1 $2","7",,]]]',
+"258": '["MZ","00",,,,,"\\d{8,9}","[28]\\d{7,8}",[["([28]\\d)(\\d{3})(\\d{3,4})","$1 $2 $3","2|8[246]",,],["(80\\d)(\\d{3})(\\d{3})","$1 $2 $3","80",,]]]',
+"359": '["BG","00","0",,,"$NP$FG","\\d{5,9}","[23567]\\d{5,7}|[489]\\d{6,8}",[["(2)(\\d{5})","$1 $2","29",,],["(2)(\\d{3})(\\d{3,4})","$1 $2 $3","2",,],["(\\d{3})(\\d{4})","$1 $2","43[124-7]|70[1-9]",,],["(\\d{3})(\\d{3})(\\d{2})","$1 $2 $3","43[124-7]|70[1-9]",,],["(\\d{3})(\\d{2})(\\d{3})","$1 $2 $3","[78]00",,],["(\\d{2})(\\d{3})(\\d{2,3})","$1 $2 $3","[356]|7[1-9]|8[1-6]|9[1-7]",,],["(\\d{2})(\\d{3})(\\d{3,4})","$1 $2 $3","48|8[7-9]|9[08]",,]]]',
+"682": '["CK","00",,,,,"\\d{5}","[2-57]\\d{4}",[["(\\d{2})(\\d{3})","$1 $2",,,]]]',
+"852": '["HK","00",,,,,"\\d{5,11}","[235-7]\\d{7}|8\\d{7,8}|9\\d{4,10}",[["(\\d{4})(\\d{4})","$1 $2","[235-7]|[89](?:0[1-9]|[1-9])",,],["(800)(\\d{3})(\\d{3})","$1 $2 $3","800",,],["(900)(\\d{2})(\\d{3})(\\d{3})","$1 $2 $3 $4","900",,],["(900)(\\d{2,5})","$1 $2","900",,]]]',
+"998": '["UZ","810","8",,,"$NP $FG","\\d{7,9}","[679]\\d{8}",[["([679]\\d)(\\d{3})(\\d{2})(\\d{2})","$1 $2 $3 $4",,,]]]',
+"291": '["ER","00","0",,,"$NP$FG","\\d{6,7}","[178]\\d{6}",[["(\\d)(\\d{3})(\\d{3})","$1 $2 $3",,,]]]',
+"95": '["MM","00","0",,,"$NP$FG","\\d{5,10}","[124-8]\\d{5,7}|9(?:[25689]|4\\d{1,2}|7\\d)\\d{6}",[["(1)(\\d{3})(\\d{3})","$1 $2 $3","1",,],["(1)(3)(33\\d)(\\d{3})","$1 $2 $3 $4","133",,],["(2)(\\d{2})(\\d{3})","$1 $2 $3","2",,],["(\\d{2})(\\d{3})(\\d{3})","$1 $2 $3","67|81",,],["(\\d{2})(\\d{2})(\\d{3})","$1 $2 $3","[4-8]",,],["(9)(\\d{3})(\\d{4,5})","$1 $2 $3","9(?:[25-9]|4[1349])",,],["(9)(4\\d{4})(\\d{4})","$1 $2 $3","94[0256]",,]]]',
+"266": '["LS","00",,,,,"\\d{8}","[2568]\\d{7}",[["(\\d{4})(\\d{4})","$1 $2",,,]]]',
+"245": '["GW","00",,,,,"\\d{7}","[3567]\\d{6}",[["(\\d{3})(\\d{4})","$1 $2",,,]]]',
+"374": '["AM","00","0",,,"($NP$FG)","\\d{5,8}","[1-9]\\d{7}",[["(\\d{2})(\\d{6})","$1 $2","1|47",,],["(\\d{2})(\\d{6})","$1 $2","[5-7]|9[1-9]","$NP$FG",],["(\\d{3})(\\d{5})","$1 $2","[23]",,],["(\\d{3})(\\d{2})(\\d{3})","$1 $2 $3","8|90","$NP $FG",]]]',
+"379": '["VA","00",,,,,"\\d{10}","06\\d{8}",[["(06)(\\d{4})(\\d{4})","$1 $2 $3",,,]]]',
+"61": ['["AU","(?:14(?:1[14]|34|4[17]|[56]6|7[47]|88))?001[14-689]","0",,,,"\\d{6,10}","[1-578]\\d{5,9}",[["([2378])(\\d{4})(\\d{4})","$1 $2 $3","[2378]","($NP$FG)",],["(\\d{3})(\\d{3})(\\d{3})","$1 $2 $3","[45]|14","$NP$FG",],["(16)(\\d{3})(\\d{2,4})","$1 $2 $3","16","$NP$FG",],["(1[389]\\d{2})(\\d{3})(\\d{3})","$1 $2 $3","1(?:[38]0|90)","$FG",],["(180)(2\\d{3})","$1 $2","180","$FG",],["(19\\d)(\\d{3})","$1 $2","19[13]","$FG",],["(19\\d{2})(\\d{4})","$1 $2","19[67]","$FG",],["(13)(\\d{2})(\\d{2})","$1 $2 $3","13[1-9]","$FG",]]]','["CC","(?:14(?:1[14]|34|4[17]|[56]6|7[47]|88))?001[14-689]","0",,,,"\\d{6,10}","[1458]\\d{5,9}",]','["CX","(?:14(?:1[14]|34|4[17]|[56]6|7[47]|88))?001[14-689]","0",,,,"\\d{6,10}","[1458]\\d{5,9}",]'],
+"500": '["FK","00",,,,,"\\d{5}","[2-7]\\d{4}",]',
+"261": '["MG","00","0",,,"$NP$FG","\\d{7,9}","[23]\\d{8}",[["([23]\\d)(\\d{2})(\\d{3})(\\d{2})","$1 $2 $3 $4",,,]]]',
+"92": '["PK","00","0",,,"($NP$FG)","\\d{6,12}","1\\d{8}|[2-8]\\d{5,11}|9(?:[013-9]\\d{4,9}|2\\d(?:111\\d{6}|\\d{3,7}))",[["(\\d{2})(111)(\\d{3})(\\d{3})","$1 $2 $3 $4","(?:2[125]|4[0-246-9]|5[1-35-7]|6[1-8]|7[14]|8[16]|91)1",,],["(\\d{3})(111)(\\d{3})(\\d{3})","$1 $2 $3 $4","2[349]|45|54|60|72|8[2-5]|9[2-9]",,],["(\\d{2})(\\d{7,8})","$1 $2","(?:2[125]|4[0-246-9]|5[1-35-7]|6[1-8]|7[14]|8[16]|91)[2-9]",,],["(\\d{3})(\\d{6,7})","$1 $2","2[349]|45|54|60|72|8[2-5]|9[2-9]",,],["(3\\d{2})(\\d{7})","$1 $2","3","$NP$FG",],["([15]\\d{3})(\\d{5,6})","$1 $2","58[12]|1",,],["(586\\d{2})(\\d{5})","$1 $2","586",,],["([89]00)(\\d{3})(\\d{2})","$1 $2 $3","[89]00","$NP$FG",]]]',
+"234": '["NG","009","0",,,"$NP$FG","\\d{5,14}","[1-69]\\d{5,8}|[78]\\d{5,13}",[["([129])(\\d{3})(\\d{3,4})","$1 $2 $3","[129]",,],["([3-8]\\d)(\\d{3})(\\d{2,3})","$1 $2 $3","[3-6]|7(?:[1-79]|0[1-9])|8[2-9]",,],["([78]\\d{2})(\\d{3})(\\d{3,4})","$1 $2 $3","70|8[01]",,],["([78]00)(\\d{4})(\\d{4,5})","$1 $2 $3","[78]00",,],["([78]00)(\\d{5})(\\d{5,6})","$1 $2 $3","[78]00",,],["(78)(\\d{2})(\\d{3})","$1 $2 $3","78",,]]]',
+"350": '["GI","00",,,,,"\\d{8}","[2568]\\d{7}",]',
+"45": '["DK","00",,,,,"\\d{8}","[2-9]\\d{7}",[["(\\d{2})(\\d{2})(\\d{2})(\\d{2})","$1 $2 $3 $4",,,]]]',
+"963": '["SY","00","0",,,"$NP$FG","\\d{6,9}","[1-59]\\d{7,8}",[["(\\d{2})(\\d{3})(\\d{3,4})","$1 $2 $3","[1-5]",,],["(9\\d{2})(\\d{3})(\\d{3})","$1 $2 $3","9",,]]]',
+"226": '["BF","00",,,,,"\\d{8}","[24-7]\\d{7}",[["(\\d{2})(\\d{2})(\\d{2})(\\d{2})","$1 $2 $3 $4",,,]]]',
+"974": '["QA","00",,,,,"\\d{7,8}","[2-8]\\d{6,7}",[["([28]\\d{2})(\\d{4})","$1 $2","[28]",,],["([3-7]\\d{3})(\\d{4})","$1 $2","[3-7]",,]]]',
+"218": '["LY","00","0",,,"$NP$FG","\\d{7,9}","[25679]\\d{8}",[["([25679]\\d)(\\d{7})","$1-$2",,,]]]',
+"51": '["PE","19(?:1[124]|77|90)00","0",,,"($NP$FG)","\\d{6,9}","[14-9]\\d{7,8}",[["(1)(\\d{7})","$1 $2","1",,],["([4-8]\\d)(\\d{6})","$1 $2","[4-7]|8[2-4]",,],["(\\d{3})(\\d{5})","$1 $2","80",,],["(9\\d{2})(\\d{3})(\\d{3})","$1 $2 $3","9","$FG",]]]',
+"62": '["ID","0(?:0[1789]|10(?:00|1[67]))","0",,,"$NP$FG","\\d{5,11}","[1-9]\\d{6,10}",[["(\\d{2})(\\d{7,8})","$1 $2","2[124]|[36]1","($NP$FG)",],["(\\d{3})(\\d{5,7})","$1 $2","[4579]|2[035-9]|[36][02-9]","($NP$FG)",],["(8\\d{2})(\\d{3,4})(\\d{3,4})","$1-$2-$3","8[1-35-9]",,],["(177)(\\d{6,8})","$1 $2","1",,],["(800)(\\d{5,7})","$1 $2","800",,],["(809)(\\d)(\\d{3})(\\d{3})","$1 $2 $3 $4","809",,]]]',
+"298": '["FO","00",,"(10(?:01|[12]0|88))",,,"\\d{6}","[2-9]\\d{5}",[["(\\d{6})","$1",,,]]]',
+"381": '["RS","00","0",,,"$NP$FG","\\d{5,12}","[126-9]\\d{4,11}|3(?:[0-79]\\d{3,10}|8[2-9]\\d{2,9})",[["([23]\\d{2})(\\d{4,9})","$1 $2","(?:2[389]|39)0",,],["([1-3]\\d)(\\d{5,10})","$1 $2","1|2(?:[0-24-7]|[389][1-9])|3(?:[0-8]|9[1-9])",,],["(6\\d)(\\d{6,8})","$1 $2","6",,],["([89]\\d{2})(\\d{3,9})","$1 $2","[89]",,],["(7[26])(\\d{4,9})","$1 $2","7[26]",,],["(7[08]\\d)(\\d{4,9})","$1 $2","7[08]",,]]]',
+"975": '["BT","00",,,,,"\\d{6,8}","[1-8]\\d{6,7}",[["([17]7)(\\d{2})(\\d{2})(\\d{2})","$1 $2 $3 $4","1|77",,],["([2-8])(\\d{3})(\\d{3})","$1 $2 $3","[2-68]|7[246]",,]]]',
+"34": '["ES","00",,,,,"\\d{9}","[5-9]\\d{8}",[["([5-9]\\d{2})(\\d{2})(\\d{2})(\\d{2})","$1 $2 $3 $4",,,]]]',
+"881": '["001",,,,,,"\\d{9}","[67]\\d{8}",[["(\\d)(\\d{3})(\\d{5})","$1 $2 $3","[67]",,]]]',
+"855": '["KH","00[14-9]","0",,,,"\\d{6,10}","[1-9]\\d{7,9}",[["(\\d{2})(\\d{3})(\\d{3,4})","$1 $2 $3","1\\d[1-9]|[2-9]","$NP$FG",],["(1[89]00)(\\d{3})(\\d{3})","$1 $2 $3","1[89]0",,]]]',
+"420": '["CZ","00",,,,,"\\d{9,12}","[2-8]\\d{8}|9\\d{8,11}",[["([2-9]\\d{2})(\\d{3})(\\d{3})","$1 $2 $3","[2-8]|9[015-7]",,],["(96\\d)(\\d{3})(\\d{3})(\\d{3})","$1 $2 $3 $4","96",,],["(9\\d)(\\d{3})(\\d{3})(\\d{3})","$1 $2 $3 $4","9[36]",,]]]',
+"216": '["TN","00",,,,,"\\d{8}","[2-57-9]\\d{7}",[["(\\d{2})(\\d{3})(\\d{3})","$1 $2 $3",,,]]]',
+"673": '["BN","00",,,,,"\\d{7}","[2-578]\\d{6}",[["([2-578]\\d{2})(\\d{4})","$1 $2",,,]]]',
+"290": '["SH","00",,,,,"\\d{4}","[2-9]\\d{3}",]',
+"882": '["001",,,,,,"\\d{7,12}","[13]\\d{6,11}",[["(\\d{2})(\\d{4})(\\d{3})","$1 $2 $3","3[23]",,],["(\\d{2})(\\d{5})","$1 $2","16|342",,],["(\\d{2})(\\d{4})(\\d{4})","$1 $2 $3","34[57]",,],["(\\d{3})(\\d{4})(\\d{4})","$1 $2 $3","348",,],["(\\d{2})(\\d{2})(\\d{4})","$1 $2 $3","1",,],["(\\d{2})(\\d{3,4})(\\d{4})","$1 $2 $3","16",,],["(\\d{2})(\\d{4,5})(\\d{5})","$1 $2 $3","16",,]]]',
+"267": '["BW","00",,,,,"\\d{7,8}","[2-79]\\d{6,7}",[["(\\d{3})(\\d{4})","$1 $2","[2-6]",,],["(7\\d)(\\d{3})(\\d{3})","$1 $2 $3","7",,],["(90)(\\d{5})","$1 $2","9",,]]]',
+"94": '["LK","00","0",,,"$NP$FG","\\d{7,9}","[1-9]\\d{8}",[["(\\d{2})(\\d{1})(\\d{6})","$1 $2 $3","[1-689]",,],["(\\d{2})(\\d{3})(\\d{4})","$1 $2 $3","7",,]]]',
+"356": '["MT","00",,,,,"\\d{8}","[2579]\\d{7}",[["(\\d{4})(\\d{4})","$1 $2",,,]]]',
+"375": '["BY","810","8","80?",,,"\\d{7,11}","[1-4]\\d{8}|[89]\\d{9,10}",[["([1-4]\\d)(\\d{3})(\\d{4})","$1 $2 $3","[1-4]","$NP 0$FG",],["([89]\\d{2})(\\d{3})(\\d{4})","$1 $2 $3","8[01]|9","$NP $FG",],["(8\\d{2})(\\d{4})(\\d{4})","$1 $2 $3","82","$NP $FG",]]]',
+"690": '["TK","00",,,,,"\\d{4}","[2-5]\\d{3}",]',
+"507": '["PA","00",,,,,"\\d{7,8}","[1-9]\\d{6,7}",[["(\\d{3})(\\d{4})","$1-$2","[1-57-9]",,],["(\\d{4})(\\d{4})","$1-$2","6",,]]]',
+"692": '["MH","011","1",,,,"\\d{7}","[2-6]\\d{6}",[["(\\d{3})(\\d{4})","$1-$2",,,]]]',
+"250": '["RW","00","0",,,,"\\d{8,9}","[027-9]\\d{7,8}",[["(2\\d{2})(\\d{3})(\\d{3})","$1 $2 $3","2","$FG",],["([7-9]\\d{2})(\\d{3})(\\d{3})","$1 $2 $3","[7-9]","$NP$FG",],["(0\\d)(\\d{2})(\\d{2})(\\d{2})","$1 $2 $3 $4","0",,]]]',
+"81": '["JP","010","0",,,"$NP$FG","\\d{7,16}","[1-9]\\d{8,9}|0(?:[36]\\d{7,14}|7\\d{5,7}|8\\d{7})",[["(\\d{3})(\\d{3})(\\d{3})","$1-$2-$3","(?:12|57|99)0",,],["(\\d{3})(\\d{3})(\\d{4})","$1-$2-$3","800",,],["(\\d{3})(\\d{4})","$1-$2","077",,],["(\\d{3})(\\d{2})(\\d{3,4})","$1-$2-$3","077",,],["(\\d{3})(\\d{2})(\\d{4})","$1-$2-$3","088",,],["(\\d{3})(\\d{3})(\\d{3,4})","$1-$2-$3","0(?:37|66)",,],["(\\d{3})(\\d{4})(\\d{4,5})","$1-$2-$3","0(?:37|66)",,],["(\\d{3})(\\d{5})(\\d{5,6})","$1-$2-$3","0(?:37|66)",,],["(\\d{3})(\\d{6})(\\d{6,7})","$1-$2-$3","0(?:37|66)",,],["(\\d{2})(\\d{4})(\\d{4})","$1-$2-$3","[2579]0|80[1-9]",,],["(\\d{4})(\\d)(\\d{4})","$1-$2-$3","1(?:26|3[79]|4[56]|5[4-68]|6[3-5])|5(?:76|97)|499|746|8(?:3[89]|63|47|51)|9(?:49|80|9[16])",,],["(\\d{3})(\\d{2})(\\d{4})","$1-$2-$3","1(?:2[3-6]|3[3-9]|4[2-6]|5[2-8]|[68][2-7]|7[2-689]|9[1-578])|2(?:2[03-689]|3[3-58]|4[0-468]|5[04-8]|6[013-8]|7[06-9]|8[02-57-9]|9[13])|4(?:2[28]|3[689]|6[035-7]|7[05689]|80|9[3-5])|5(?:3[1-36-9]|4[4578]|5[013-8]|6[1-9]|7[2-8]|8[14-7]|9[4-9])|7(?:2[15]|3[5-9]|4[02-9]|6[135-8]|7[0-4689]|9[014-9])|8(?:2[49]|3[3-8]|4[5-8]|5[2-9]|6[35-9]|7[579]|8[03-579]|9[2-8])|9(?:[23]0|4[02-46-9]|5[024-79]|6[4-9]|7[2-47-9]|8[02-7]|9[3-7])",,],["(\\d{2})(\\d{3})(\\d{4})","$1-$2-$3","1|2(?:2[37]|5[5-9]|64|78|8[39]|91)|4(?:2[2689]|64|7[347])|5(?:[2-589]|39)|60|8(?:[46-9]|3[279]|2[124589])|9(?:[235-8]|93)",,],["(\\d{3})(\\d{2})(\\d{4})","$1-$2-$3","2(?:9[14-79]|74|[34]7|[56]9)|82|993",,],["(\\d)(\\d{4})(\\d{4})","$1-$2-$3","3|4(?:2[09]|7[01])|6[1-9]",,],["(\\d{2})(\\d{3})(\\d{4})","$1-$2-$3","[2479][1-9]",,]]]',
+"237": '["CM","00",,,,,"\\d{8}","[237-9]\\d{7}",[["([237-9]\\d)(\\d{2})(\\d{2})(\\d{2})","$1 $2 $3 $4","[2379]|88",,],["(800)(\\d{2})(\\d{3})","$1 $2 $3","80",,]]]',
+"351": '["PT","00",,,,,"\\d{9}","[2-46-9]\\d{8}",[["([2-46-9]\\d{2})(\\d{3})(\\d{3})","$1 $2 $3",,,]]]',
+"246": '["IO","00",,,,,"\\d{7}","3\\d{6}",[["(\\d{3})(\\d{4})","$1 $2",,,]]]',
+"227": '["NE","00",,,,,"\\d{8}","[029]\\d{7}",[["([029]\\d)(\\d{2})(\\d{2})(\\d{2})","$1 $2 $3 $4","[29]|09",,],["(08)(\\d{3})(\\d{3})","$1 $2 $3","08",,]]]',
+"27": '["ZA","00","0",,,"$NP$FG","\\d{5,9}","[1-5]\\d{8}|(?:7\\d{4,8}|8[1-5789]\\d{3,7})|8[06]\\d{7}",[["(860)(\\d{3})(\\d{3})","$1 $2 $3","860",,],["([1-578]\\d)(\\d{3})(\\d{4})","$1 $2 $3","[1-57]|8(?:[0-57-9]|6[1-9])",,],["(\\d{2})(\\d{3,4})","$1 $2","7|8[1-5789]",,],["(\\d{2})(\\d{3})(\\d{2,3})","$1 $2 $3","7|8[1-5789]",,]]]',
+"962": '["JO","00","0",,,"$NP$FG","\\d{7,9}","[235-9]\\d{7,8}",[["(\\d)(\\d{3})(\\d{4})","$1 $2 $3","[2356]|87","($NP$FG)",],["(7)(\\d{4})(\\d{4})","$1 $2 $3","7[457-9]",,],["(\\d{3})(\\d{5,6})","$1 $2","70|8[0158]|9",,]]]',
+"387": '["BA","00","0",,,"$NP$FG","\\d{6,9}","[3-9]\\d{7,8}",[["(\\d{2})(\\d{3})(\\d{3})","$1 $2-$3","[3-5]",,],["(\\d{2})(\\d{3})(\\d{3})","$1 $2 $3","6[1-356]|[7-9]",,],["(\\d{2})(\\d{2})(\\d{2})(\\d{3})","$1 $2 $3 $4","6[047]",,]]]',
+"33": '["FR","[04579]0","0",,,"$NP$FG","\\d{4}(?:\\d{5})?","[124-9]\\d{8}|3\\d{3}(?:\\d{5})?",[["([1-79])(\\d{2})(\\d{2})(\\d{2})(\\d{2})","$1 $2 $3 $4 $5","[1-79]",,],["(8\\d{2})(\\d{2})(\\d{2})(\\d{2})","$1 $2 $3 $4","8","$NP $FG",]]]',
+"972": '["IL","0(?:0|1[2-9])","0",,,"$FG","\\d{4,10}","[17]\\d{6,9}|[2-589]\\d{3}(?:\\d{3,6})?|6\\d{3}",[["([2-489])(\\d{3})(\\d{4})","$1-$2-$3","[2-489]","$NP$FG",],["([57]\\d)(\\d{3})(\\d{4})","$1-$2-$3","[57]","$NP$FG",],["(1)([7-9]\\d{2})(\\d{3})(\\d{3})","$1-$2-$3-$4","1[7-9]",,],["(1255)(\\d{3})","$1-$2","125",,],["(1200)(\\d{3})(\\d{3})","$1-$2-$3","120",,],["(1212)(\\d{2})(\\d{2})","$1-$2-$3","121",,],["(1599)(\\d{6})","$1-$2","15",,],["(\\d{4})","*$1","[2-689]",,]]]',
+"248": '["SC","0[0-2]",,,,,"\\d{6,7}","[24689]\\d{5,6}",[["(\\d{3})(\\d{3})","$1 $2","[89]",,],["(\\d)(\\d{3})(\\d{3})","$1 $2 $3","[246]",,]]]',
+"297": '["AW","00",,,,,"\\d{7}","[25-9]\\d{6}",[["(\\d{3})(\\d{4})","$1 $2",,,]]]',
+"421": '["SK","00","0",,,"$NP$FG","\\d{9}","[2-689]\\d{8}",[["(2)(\\d{3})(\\d{3})(\\d{2})","$1/$2 $3 $4","2",,],["([3-5]\\d)(\\d{3})(\\d{2})(\\d{2})","$1/$2 $3 $4","[3-5]",,],["([689]\\d{2})(\\d{3})(\\d{3})","$1 $2 $3","[689]",,]]]',
+"672": '["NF","00",,,,,"\\d{5,6}","[13]\\d{5}",[["(\\d{2})(\\d{4})","$1 $2","1",,],["(\\d)(\\d{5})","$1 $2","3",,]]]',
+"870": '["001",,,,,,"\\d{9}","[35-7]\\d{8}",[["(\\d{3})(\\d{3})(\\d{3})","$1 $2 $3",,,]]]',
+"883": '["001",,,,,,"\\d{9}(?:\\d{3})?","51\\d{7}(?:\\d{3})?",[["(\\d{3})(\\d{3})(\\d{3})","$1 $2 $3",,,],["(\\d{3})(\\d{3})(\\d{3})(\\d{3})","$1 $2 $3 $4",,,]]]',
+"264": '["NA","00","0",,,"$NP$FG","\\d{8,9}","[68]\\d{7,8}",[["(8\\d)(\\d{3})(\\d{4})","$1 $2 $3","8[1235]",,],["(6\\d)(\\d{2,3})(\\d{4})","$1 $2 $3","6",,],["(88)(\\d{3})(\\d{3})","$1 $2 $3","88",,],["(870)(\\d{3})(\\d{3})","$1 $2 $3","870",,]]]',
+"878": '["001",,,,,,"\\d{12}","1\\d{11}",[["(\\d{2})(\\d{5})(\\d{5})","$1 $2 $3",,,]]]',
+"239": '["ST","00",,,,,"\\d{7}","[29]\\d{6}",[["(\\d{3})(\\d{4})","$1 $2",,,]]]',
+"357": '["CY","00",,,,,"\\d{8}","[257-9]\\d{7}",[["(\\d{2})(\\d{6})","$1 $2",,,]]]',
+"240": '["GQ","00",,,,,"\\d{9}","[23589]\\d{8}",[["(\\d{3})(\\d{3})(\\d{3})","$1 $2 $3","[235]",,],["(\\d{3})(\\d{6})","$1 $2","[89]",,]]]',
+"506": '["CR","00",,"(19(?:0[0-2468]|19|66|77))",,,"\\d{8,10}","[24-9]\\d{7,9}",[["(\\d{4})(\\d{4})","$1 $2","[24-7]|8[3-9]",,],["(\\d{3})(\\d{3})(\\d{4})","$1-$2-$3","[89]0",,]]]',
+"86": '["CN","(1[1279]\\d{3})?00","0","(1[1279]\\d{3})|0",,,"\\d{4,12}","[1-79]\\d{7,11}|8[0-357-9]\\d{6,9}",[["(80\\d{2})(\\d{4})","$1 $2","80[2678]","$NP$FG",],["([48]00)(\\d{3})(\\d{4})","$1 $2 $3","[48]00",,],["(\\d{3,4})(\\d{4})","$1 $2","[2-9]",,"NA"],["(21)(\\d{4})(\\d{4,6})","$1 $2 $3","21","$NP$FG",],["([12]\\d)(\\d{4})(\\d{4})","$1 $2 $3","10[1-9]|2[02-9]","$NP$FG",],["(\\d{3})(\\d{4})(\\d{4})","$1 $2 $3","3(?:11|7[179])|4(?:[15]1|3[12])|5(?:1|2[37]|3[12]|7[13-79]|9[15])|7(?:31|5[457]|6[09]|91)|898","$NP$FG",],["(\\d{3})(\\d{3})(\\d{4})","$1 $2 $3","3(?:1[02-9]|35|49|5|7[02-68]|9[1-68])|4(?:1[02-9]|2[179]|[35][2-9]|6[4789]|7\\d|8[23])|5(?:3[03-9]|4[36]|5|6[1-6]|7[028]|80|9[2-46-9])|6(?:3[1-5]|6[0238]|9[12])|7(?:01|[1579]|2[248]|3[04-9]|4[3-6]|6[2368])|8(?:1[236-8]|2[5-7]|[37]|5[1-9]|8[3678]|9[1-7])|9(?:0[1-3689]|1[1-79]|[379]|4[13]|5[1-5])","$NP$FG",],["(1[3-58]\\d)(\\d{4})(\\d{4})","$1 $2 $3","1[3-58]",,],["(10800)(\\d{3})(\\d{4})","$1 $2 $3","108",,]]]',
+"257": '["BI","00",,,,,"\\d{8}","[27]\\d{7}",[["([27]\\d)(\\d{2})(\\d{2})(\\d{2})","$1 $2 $3 $4",,,]]]',
+"683": '["NU","00",,,,,"\\d{4}","[1-5]\\d{3}",]',
+"43": '["AT","00","0",,,"$NP$FG","\\d{3,13}","[1-9]\\d{3,12}",[["(1)(\\d{3,12})","$1 $2","1",,],["(5\\d)(\\d{3,5})","$1 $2","5[079]",,],["(5\\d)(\\d{3})(\\d{3,4})","$1 $2 $3","5[079]",,],["(5\\d)(\\d{4})(\\d{4,7})","$1 $2 $3","5[079]",,],["(\\d{3})(\\d{3,10})","$1 $2","316|46|51|732|6(?:44|5[0-3579]|[6-9])|7(?:1|[28]0)|[89]",,],["(\\d{4})(\\d{3,9})","$1 $2","2|3(?:1[1-578]|[3-8])|4[2378]|5[2-6]|6(?:[12]|4[1-35-9]|5[468])|7(?:2[1-8]|35|4[1-8]|[57-9])",,]]]',
+"247": '["AC","00",,,,,"\\d{4}","[2-467]\\d{3}",]',
+"675": '["PG","00",,,,,"\\d{7,8}","[1-9]\\d{6,7}",[["(\\d{3})(\\d{4})","$1 $2","[1-689]",,],["(7[1-36]\\d)(\\d{2})(\\d{3})","$1 $2 $3","7[1-36]",,]]]',
+"376": '["AD","00",,,,,"\\d{6,8}","(?:[346-9]|180)\\d{5}",[["(\\d{3})(\\d{3})","$1 $2","[346-9]",,],["(180[02])(\\d{4})","$1 $2","1",,]]]',
+"63": '["PH","00","0",,,,"\\d{7,13}","[2-9]\\d{7,9}|1800\\d{7,9}",[["(2)(\\d{3})(\\d{4})","$1 $2 $3","2","($NP$FG)",],["(\\d{4})(\\d{5})","$1 $2","3(?:23|39|46)|4(?:2[3-6]|[35]9|4[26]|76)|5(?:22|44)|642|8(?:62|8[245])","($NP$FG)",],["(\\d{5})(\\d{4})","$1 $2","346|4(?:27|9[35])|883","($NP$FG)",],["([3-8]\\d)(\\d{3})(\\d{4})","$1 $2 $3","[3-8]","($NP$FG)",],["(9\\d{2})(\\d{3})(\\d{4})","$1 $2 $3","9","$NP$FG",],["(1800)(\\d{3})(\\d{4})","$1 $2 $3","1",,],["(1800)(\\d{1,2})(\\d{3})(\\d{4})","$1 $2 $3 $4","1",,]]]',
+"236": '["CF","00",,,,,"\\d{8}","[278]\\d{7}",[["(\\d{2})(\\d{2})(\\d{2})(\\d{2})","$1 $2 $3 $4",,,]]]',
+"590": ['["GP","00","0",,,"$NP$FG","\\d{9}","[56]\\d{8}",[["([56]90)(\\d{2})(\\d{4})","$1 $2-$3",,,]]]','["BL","00","0",,,,"\\d{9}","[56]\\d{8}",]','["MF","00","0",,,,"\\d{9}","[56]\\d{8}",]'],
+"53": '["CU","119","0",,,"($NP$FG)","\\d{4,8}","[2-57]\\d{5,7}",[["(\\d)(\\d{6,7})","$1 $2","7",,],["(\\d{2})(\\d{4,6})","$1 $2","[2-4]",,],["(\\d)(\\d{7})","$1 $2","5","$NP$FG",]]]',
+"64": '["NZ","0(?:0|161)","0",,,"$NP$FG","\\d{7,11}","6[235-9]\\d{6}|[2-57-9]\\d{7,10}",[["([34679])(\\d{3})(\\d{4})","$1-$2 $3","[3467]|9[1-9]",,],["(24099)(\\d{3})","$1 $2","240",,],["(\\d{2})(\\d{3})(\\d{3})","$1 $2 $3","21",,],["(\\d{2})(\\d{3})(\\d{3,4})","$1 $2 $3","2(?:1[1-9]|[69]|7[0-35-9])|86",,],["(2\\d)(\\d{3,4})(\\d{4})","$1 $2 $3","2[028]",,],["(\\d{3})(\\d{3})(\\d{3,4})","$1 $2 $3","2(?:10|74)|5|[89]0",,]]]',
+"965": '["KW","00",,,,,"\\d{7,8}","[12569]\\d{6,7}",[["(\\d{4})(\\d{3,4})","$1 $2","[1269]",,],["(5[015]\\d)(\\d{5})","$1 $2","5",,]]]',
+"224": '["GN","00",,,,,"\\d{8,9}","[23567]\\d{7,8}",[["(\\d{2})(\\d{2})(\\d{2})(\\d{2})","$1 $2 $3 $4","[23567]",,],["(\\d{3})(\\d{2})(\\d{2})(\\d{2})","$1 $2 $3 $4","62",,]]]',
+"973": '["BH","00",,,,,"\\d{8}","[136-9]\\d{7}",[["(\\d{4})(\\d{4})","$1 $2",,,]]]',
+"32": '["BE","00","0",,,"$NP$FG","\\d{8,9}","[1-9]\\d{7,8}",[["(4[6-9]\\d)(\\d{2})(\\d{2})(\\d{2})","$1 $2 $3 $4","4[6-9]",,],["([2-49])(\\d{3})(\\d{2})(\\d{2})","$1 $2 $3 $4","[23]|[49][23]",,],["([15-8]\\d)(\\d{2})(\\d{2})(\\d{2})","$1 $2 $3 $4","[156]|7[0178]|8(?:0[1-9]|[1-79])",,],["([89]\\d{2})(\\d{2})(\\d{3})","$1 $2 $3","(?:80|9)0",,]]]',
+"249": '["SD","00","0",,,"$NP$FG","\\d{9}","[19]\\d{8}",[["(\\d{2})(\\d{3})(\\d{4})","$1 $2 $3",,,]]]',
+"678": '["VU","00",,,,,"\\d{5,7}","[2-57-9]\\d{4,6}",[["(\\d{3})(\\d{4})","$1 $2","[579]",,]]]',
+"52": '["MX","0[09]","01","0[12]|04[45](\\d{10})","1$1","$NP $FG","\\d{7,11}","[1-9]\\d{9,10}",[["([358]\\d)(\\d{4})(\\d{4})","$1 $2 $3","33|55|81",,],["(\\d{3})(\\d{3})(\\d{4})","$1 $2 $3","[2467]|3[12457-9]|5[89]|8[02-9]|9[0-35-9]",,],["(1)([358]\\d)(\\d{4})(\\d{4})","044 $2 $3 $4","1(?:33|55|81)","$FG","$1 $2 $3 $4"],["(1)(\\d{3})(\\d{3})(\\d{4})","044 $2 $3 $4","1(?:[2467]|3[12457-9]|5[89]|8[2-9]|9[1-35-9])","$FG","$1 $2 $3 $4"]]]',
+"968": '["OM","00",,,,,"\\d{7,9}","(?:2[2-6]|5|9[1-9])\\d{6}|800\\d{5,6}",[["(2\\d)(\\d{6})","$1 $2","2",,],["(9\\d{3})(\\d{4})","$1 $2","9",,],["([58]00)(\\d{4,6})","$1 $2","[58]",,]]]',
+"599": ['["CW","00",,,,,"\\d{7,8}","[169]\\d{6,7}",[["(\\d{3})(\\d{4})","$1 $2","[13-7]",,],["(9)(\\d{3})(\\d{4})","$1 $2 $3","9",,]]]','["BQ","00",,,,,"\\d{7}","[347]\\d{6}",]'],
+"800": '["001",,,,,,"\\d{8}","\\d{8}",[["(\\d{4})(\\d{4})","$1 $2",,,]]]',
+"386": '["SI","00","0",,,"$NP$FG","\\d{5,8}","[1-7]\\d{6,7}|[89]\\d{4,7}",[["(\\d)(\\d{3})(\\d{2})(\\d{2})","$1 $2 $3 $4","[12]|3[4-8]|4[24-8]|5[2-8]|7[3-8]","($NP$FG)",],["([3-7]\\d)(\\d{3})(\\d{3})","$1 $2 $3","[37][01]|4[019]|51|6",,],["([89][09])(\\d{3,6})","$1 $2","[89][09]",,],["([58]\\d{2})(\\d{5})","$1 $2","59|8[1-3]",,]]]',
+"679": '["FJ","0(?:0|52)",,,,,"\\d{7}(?:\\d{4})?","[36-9]\\d{6}|0\\d{10}",[["(\\d{3})(\\d{4})","$1 $2","[36-9]",,],["(\\d{4})(\\d{3})(\\d{4})","$1 $2 $3","0",,]]]',
+"238": '["CV","0",,,,,"\\d{7}","[259]\\d{6}",[["(\\d{3})(\\d{2})(\\d{2})","$1 $2 $3",,,]]]',
+"691": '["FM","00",,,,,"\\d{7}","[39]\\d{6}",[["(\\d{3})(\\d{4})","$1 $2",,,]]]',
+"262": ['["RE","00","0",,,"$NP$FG","\\d{9}","[268]\\d{8}",[["([268]\\d{2})(\\d{2})(\\d{2})(\\d{2})","$1 $2 $3 $4",,,]]]','["YT","00","0",,,"$NP$FG","\\d{9}","[268]\\d{8}",]'],
+"241": '["GA","00","0",,,,"\\d{7,8}","[01]\\d{6,7}",[["(1)(\\d{2})(\\d{2})(\\d{2})","$1 $2 $3 $4","1","$NP$FG",],["(0\\d)(\\d{2})(\\d{2})(\\d{2})","$1 $2 $3 $4","0",,]]]',
+"370": '["LT","00","8",,,"($NP-$FG)","\\d{8}","[3-9]\\d{7}",[["([34]\\d)(\\d{6})","$1 $2","37|4(?:1|5[45]|6[2-4])",,],["([3-6]\\d{2})(\\d{5})","$1 $2","3[148]|4(?:[24]|6[09])|528|6",,],["([7-9]\\d{2})(\\d{2})(\\d{3})","$1 $2 $3","[7-9]","$NP $FG",],["(5)(2\\d{2})(\\d{4})","$1 $2 $3","52[0-79]",,]]]',
+"256": '["UG","00[057]","0",,,"$NP$FG","\\d{5,9}","\\d{9}",[["(\\d{3})(\\d{6})","$1 $2","[7-9]|20(?:[013-5]|2[5-9])|4(?:6[45]|[7-9])",,],["(\\d{2})(\\d{7})","$1 $2","3|4(?:[1-5]|6[0-36-9])",,],["(2024)(\\d{5})","$1 $2","2024",,]]]',
+"677": '["SB","0[01]",,,,,"\\d{5,7}","[1-9]\\d{4,6}",[["(\\d{3})(\\d{4})","$1 $2","[7-9]",,]]]',
+"377": '["MC","00","0",,,"$NP$FG","\\d{8,9}","[4689]\\d{7,8}",[["(\\d{2})(\\d{2})(\\d{2})(\\d{2})","$1 $2 $3 $4","[89]","$FG",],["(\\d{2})(\\d{3})(\\d{3})","$1 $2 $3","4",,],["(6)(\\d{2})(\\d{2})(\\d{2})(\\d{2})","$1 $2 $3 $4 $5","6",,]]]',
+"382": '["ME","00","0",,,"$NP$FG","\\d{6,9}","[2-9]\\d{7,8}",[["(\\d{2})(\\d{3})(\\d{3})","$1 $2 $3","[2-57-9]|6[3789]",,],["(67)(9)(\\d{3})(\\d{3})","$1 $2 $3 $4","679",,]]]',
+"231": '["LR","00","0",,,"$NP$FG","\\d{7,9}","(?:[29]\\d|[4-6]|7\\d{1,2}|[38]\\d{2})\\d{6}",[["([279]\\d)(\\d{3})(\\d{3})","$1 $2 $3","[279]",,],["(7\\d{2})(\\d{3})(\\d{3})","$1 $2 $3","7",,],["([4-6])(\\d{3})(\\d{3})","$1 $2 $3","[4-6]",,],["(\\d{2})(\\d{3})(\\d{4})","$1 $2 $3","[38]",,]]]',
+"591": '["BO","00(1\\d)?","0","0(1\\d)?",,,"\\d{7,8}","[23467]\\d{7}",[["([234])(\\d{7})","$1 $2","[234]",,],["([67]\\d{7})","$1","[67]",,]]]',
+"808": '["001",,,,,,"\\d{8}","\\d{8}",[["(\\d{4})(\\d{4})","$1 $2",,,]]]',
+"964": '["IQ","00","0",,,"$NP$FG","\\d{6,10}","[1-7]\\d{7,9}",[["(1)(\\d{3})(\\d{4})","$1 $2 $3","1",,],["([2-6]\\d)(\\d{3})(\\d{3,4})","$1 $2 $3","[2-6]",,],["(7\\d{2})(\\d{3})(\\d{4})","$1 $2 $3","7",,]]]',
+"225": '["CI","00",,,,,"\\d{8}","[02-6]\\d{7}",[["(\\d{2})(\\d{2})(\\d{2})(\\d{2})","$1 $2 $3 $4",,,]]]',
+"992": '["TJ","810","8",,,"($NP) $FG","\\d{3,9}","[3-59]\\d{8}",[["([349]\\d{2})(\\d{2})(\\d{4})","$1 $2 $3","[34]7|91[78]",,],["([459]\\d)(\\d{3})(\\d{4})","$1 $2 $3","4[48]|5|9(?:1[59]|[0235-9])",,],["(331700)(\\d)(\\d{2})","$1 $2 $3","331",,],["(\\d{4})(\\d)(\\d{4})","$1 $2 $3","3[1-5]",,]]]',
+"55": '["BR","00(?:1[45]|2[135]|[34]1|43)","0","0(?:(1[245]|2[135]|[34]1)(\\d{10,11}))?","$2",,"\\d{8,11}","[1-46-9]\\d{7,10}|5\\d{8,9}",[["(\\d{2})(\\d{5})(\\d{4})","$1 $2-$3","119","($FG)",],["(\\d{2})(\\d{4})(\\d{4})","$1 $2-$3","[1-9][1-9]","($FG)",],["([34]00\\d)(\\d{4})","$1-$2","[34]00",,],["([3589]00)(\\d{2,3})(\\d{4})","$1 $2 $3","[3589]00","$NP$FG",]]]',
+"674": '["NR","00",,,,,"\\d{7}","[458]\\d{6}",[["(\\d{3})(\\d{4})","$1 $2",,,]]]',
+"967": '["YE","00","0",,,"$NP$FG","\\d{6,9}","[1-7]\\d{6,8}",[["([1-7])(\\d{3})(\\d{3,4})","$1 $2 $3","[1-6]|7[24-68]",,],["(7\\d{2})(\\d{3})(\\d{3})","$1 $2 $3","7[0137]",,]]]',
+"49": '["DE","00","0",,,"$NP$FG","\\d{2,15}","[1-35-9]\\d{3,14}|4(?:[0-8]\\d{4,12}|9(?:[0-37]\\d|4[1-8]|5\\d{1,2}|6[1-8]\\d?)\\d{2,7})",[["(\\d{2})(\\d{4,11})","$1 $2","3[02]|40|[68]9",,],["(\\d{3})(\\d{3,11})","$1 $2","2(?:\\d1|0[2389]|1[24]|28|34)|3(?:[3-9][15]|40)|[4-8][1-9]1|9(?:06|[1-9]1)",,],["(\\d{4})(\\d{2,11})","$1 $2","[24-6]|[7-9](?:\\d[1-9]|[1-9]\\d)|3(?:[3569][02-46-9]|4[2-4679]|7[2-467]|8[2-46-8])",,],["(\\d{5})(\\d{1,10})","$1 $2","3",,],["(1\\d{2})(\\d{7,8})","$1 $2","1[5-7]",,],["(177)(99)(\\d{7,8})","$1 $2 $3","177",,],["(8\\d{2})(\\d{7,10})","$1 $2","800",,],["(\\d{3})(\\d)(\\d{4,10})","$1 $2 $3","(?:18|90)0",,],["(1\\d{2})(\\d{5,11})","$1 $2","181",,],["(18\\d{3})(\\d{6})","$1 $2","185",,],["(18\\d{2})(\\d{7})","$1 $2","18[68]",,],["(18\\d)(\\d{8})","$1 $2","18[2-579]",,],["(700)(\\d{4})(\\d{4})","$1 $2 $3","700",,]]]',
+"31": '["NL","00","0",,,"$NP$FG","\\d{5,10}","1\\d{4,8}|[2-7]\\d{8}|[89]\\d{6,9}",[["([1-578]\\d)(\\d{3})(\\d{4})","$1 $2 $3","1[035]|2[0346]|3[03568]|4[0356]|5[0358]|7|8[458]",,],["([1-5]\\d{2})(\\d{3})(\\d{3})","$1 $2 $3","1[16-8]|2[259]|3[124]|4[17-9]|5[124679]",,],["(6)(\\d{8})","$1 $2","6[0-57-9]",,],["(66)(\\d{7})","$1 $2","66",,],["(14)(\\d{3,4})","$1 $2","14","$FG",],["([89]0\\d)(\\d{4,7})","$1 $2","80|9",,]]]',
+"970": '["PS","00","0",,,"$NP$FG","\\d{4,10}","[24589]\\d{7,8}|1(?:[78]\\d{8}|[49]\\d{2,3})",[["([2489])(2\\d{2})(\\d{4})","$1 $2 $3","[2489]",,],["(5[69]\\d)(\\d{3})(\\d{3})","$1 $2 $3","5",,],["(1[78]00)(\\d{3})(\\d{3})","$1 $2 $3","1[78]","$FG",]]]',
+"58": '["VE","00","0","(1\\d{2})|0",,"$NP$FG","\\d{7,10}","[24589]\\d{9}",[["(\\d{3})(\\d{7})","$1-$2",,,]]]',
+"856": '["LA","00","0",,,"$NP$FG","\\d{6,10}","[2-8]\\d{7,9}",[["(20)(\\d{2})(\\d{3})(\\d{3})","$1 $2 $3 $4","20",,],["([2-8]\\d)(\\d{3})(\\d{3})","$1 $2 $3","2[13]|[3-8]",,]]]',
+"354": '["IS","00",,,,,"\\d{7,9}","[4-9]\\d{6}|38\\d{7}",[["(\\d{3})(\\d{4})","$1 $2","[4-9]",,],["(3\\d{2})(\\d{3})(\\d{3})","$1 $2 $3","3",,]]]',
+"242": '["CG","00",,,,,"\\d{9}","[028]\\d{8}",[["(\\d{2})(\\d{3})(\\d{4})","$1 $2 $3","[02]",,],["(\\d)(\\d{4})(\\d{4})","$1 $2 $3","8",,]]]',
+"423": '["LI","00","0",,,,"\\d{7,9}","6\\d{8}|[23789]\\d{6}",[["(\\d{3})(\\d{2})(\\d{2})","$1 $2 $3","[23]|7[3-57-9]|87",,],["(6\\d)(\\d{3})(\\d{3})","$1 $2 $3","6",,],["(6[567]\\d)(\\d{3})(\\d{3})","$1 $2 $3","6[567]",,],["(69)(7\\d{2})(\\d{4})","$1 $2 $3","697",,],["([7-9]0\\d)(\\d{2})(\\d{2})","$1 $2 $3","[7-9]0",,],["([89]0\\d)(\\d{2})(\\d{2})(\\d{2})","$1 $2 $3 $4","[89]0","$NP$FG",]]]',
+"213": '["DZ","00","0",,,"$NP$FG","\\d{8,9}","(?:[1-4]|[5-9]\\d)\\d{7}",[["([1-4]\\d)(\\d{2})(\\d{2})(\\d{2})","$1 $2 $3 $4","[1-4]",,],["([5-8]\\d{2})(\\d{2})(\\d{2})(\\d{2})","$1 $2 $3 $4","[5-8]",,],["(9\\d)(\\d{3})(\\d{2})(\\d{2})","$1 $2 $3 $4","9",,]]]',
+"371": '["LV","00",,,,,"\\d{8}","[2689]\\d{7}",[["([2689]\\d)(\\d{3})(\\d{3})","$1 $2 $3",,,]]]',
+"503": '["SV","00",,,,,"\\d{7,8}|\\d{11}","[267]\\d{7}|[89]\\d{6}(?:\\d{4})?",[["(\\d{4})(\\d{4})","$1 $2","[267]",,],["(\\d{3})(\\d{4})","$1 $2","[89]",,],["(\\d{3})(\\d{4})(\\d{4})","$1 $2 $3","[89]",,]]]',
+"685": '["WS","0",,,,,"\\d{5,7}","[2-8]\\d{4,6}",[["(8\\d{2})(\\d{3,4})","$1 $2","8",,],["(7\\d)(\\d{5})","$1 $2","7",,]]]',
+"880": '["BD","00[12]?","0",,,"$NP$FG","\\d{6,10}","[2-79]\\d{5,9}|1\\d{9}|8[0-7]\\d{4,8}",[["(2)(\\d{7})","$1 $2","2",,],["(\\d{2})(\\d{4,6})","$1 $2","[3-79]1",,],["(\\d{3})(\\d{3,7})","$1 $2","[3-79][2-9]|8",,],["(\\d{4})(\\d{6})","$1 $2","1",,]]]',
+"265": '["MW","00","0",,,"$NP$FG","\\d{7,9}","(?:1(?:\\d{2})?|[2789]\\d{2})\\d{6}",[["(\\d)(\\d{3})(\\d{3})","$1 $2 $3","1",,],["(2\\d{2})(\\d{3})(\\d{3})","$1 $2 $3","2",,],["(\\d{3})(\\d{2})(\\d{2})(\\d{2})","$1 $2 $3 $4","[1789]",,]]]',
+"65": '["SG","0[0-3][0-9]",,,,,"\\d{8,11}","[36]\\d{7}|[17-9]\\d{7,10}",[["([3689]\\d{3})(\\d{4})","$1 $2","[369]|8[1-9]",,],["(1[89]00)(\\d{3})(\\d{4})","$1 $2 $3","1[89]",,],["(7000)(\\d{4})(\\d{3})","$1 $2 $3","70",,],["(800)(\\d{3})(\\d{4})","$1 $2 $3","80",,]]]',
+"504": '["HN","00",,,,,"\\d{8}","[237-9]\\d{7}",[["(\\d{4})(\\d{4})","$1-$2",,,]]]',
+"688": '["TV","00",,,,,"\\d{5,6}","[29]\\d{4,5}",]',
+"84": '["VN","00","0",,,"$NP$FG","\\d{7,10}","[17]\\d{6,9}|[2-69]\\d{7,9}|8\\d{6,8}",[["([17]99)(\\d{4})","$1 $2","[17]99",,],["([48])(\\d{4})(\\d{4})","$1 $2 $3","[48]",,],["([235-7]\\d)(\\d{4})(\\d{3})","$1 $2 $3","2[025-79]|3[0136-9]|5[2-9]|6[0-46-8]|7[02-79]",,],["(80)(\\d{5})","$1 $2","80",,],["(69\\d)(\\d{4,5})","$1 $2","69",,],["([235-7]\\d{2})(\\d{4})(\\d{3})","$1 $2 $3","2[1348]|3[25]|5[01]|65|7[18]",,],["(9\\d)(\\d{3})(\\d{2})(\\d{2})","$1 $2 $3 $4","9",,],["(1[2689]\\d)(\\d{3})(\\d{4})","$1 $2 $3","1(?:[26]|8[68]|99)",,],["(1[89]00)(\\d{4,6})","$1 $2","1[89]0","$FG",]]]',
+"255": '["TZ","00[056]","0",,,"$NP$FG","\\d{7,9}","\\d{9}",[["([24]\\d)(\\d{3})(\\d{4})","$1 $2 $3","[24]",,],["([67]\\d{2})(\\d{3})(\\d{3})","$1 $2 $3","[67]",,],["([89]\\d{2})(\\d{2})(\\d{4})","$1 $2 $3","[89]",,]]]',
+"222": '["MR","00",,,,,"\\d{8}","[2-48]\\d{7}",[["([2-48]\\d)(\\d{2})(\\d{2})(\\d{2})","$1 $2 $3 $4",,,]]]',
+"230": '["MU","0(?:[2-7]0|33)",,,,,"\\d{7}","[2-9]\\d{6}",[["([2-9]\\d{2})(\\d{4})","$1 $2",,,]]]',
+"592": '["GY","001",,,,,"\\d{7}","[2-4679]\\d{6}",[["(\\d{3})(\\d{4})","$1 $2",,,]]]',
+"41": '["CH","00","0",,,"$NP$FG","\\d{9}(?:\\d{3})?","[2-9]\\d{8}|860\\d{9}",[["([2-9]\\d)(\\d{3})(\\d{2})(\\d{2})","$1 $2 $3 $4","[2-7]|[89]1",,],["([89]\\d{2})(\\d{3})(\\d{3})","$1 $2 $3","8[047]|90",,],["(\\d{3})(\\d{2})(\\d{3})(\\d{2})(\\d{2})","$1 $2 $3 $4 $5","860",,]]]',
+"39": '["IT","00",,,,,"\\d{6,11}","[01589]\\d{5,10}|3(?:[12457-9]\\d{8}|[36]\\d{7,9})",[["(\\d{2})(\\d{3,4})(\\d{4})","$1 $2 $3","0[26]|55",,],["(0[26])(\\d{4})(\\d{5})","$1 $2 $3","0[26]",,],["(0[26])(\\d{4,6})","$1 $2","0[26]",,],["(0\\d{2})(\\d{3,4})(\\d{4})","$1 $2 $3","0[13-57-9][0159]",,],["(\\d{3})(\\d{3,6})","$1 $2","0[13-57-9][0159]|8(?:03|4[17]|9[245])",,],["(0\\d{3})(\\d{3})(\\d{4})","$1 $2 $3","0[13-57-9][2-46-8]",,],["(0\\d{3})(\\d{2,6})","$1 $2","0[13-57-9][2-46-8]",,],["(\\d{3})(\\d{3})(\\d{3,4})","$1 $2 $3","[13]|8(?:00|4[08]|9[59])",,],["(\\d{4})(\\d{4})","$1 $2","894",,],["(\\d{3})(\\d{4})(\\d{4})","$1 $2 $3","3",,]]]',
+"993": '["TM","810","8",,,"($NP $FG)","\\d{8}","[1-6]\\d{7}",[["(\\d{2})(\\d{2})(\\d{2})(\\d{2})","$1 $2-$3-$4","12",,],["(\\d{2})(\\d{6})","$1 $2","6","$NP $FG",],["(\\d{3})(\\d)(\\d{2})(\\d{2})","$1 $2-$3-$4","13|[2-5]",,]]]',
+"888": '["001",,,,,,"\\d{11}","\\d{11}",[["(\\d{3})(\\d{3})(\\d{5})","$1 $2 $3",,,]]]',
+"353": '["IE","00","0",,,"($NP$FG)","\\d{5,10}","[124-9]\\d{6,9}",[["(1)(\\d{3,4})(\\d{4})","$1 $2 $3","1",,],["(\\d{2})(\\d{5})","$1 $2","2[2-9]|4[347]|5[2-58]|6[2-47-9]|9[3-9]",,],["(\\d{3})(\\d{5})","$1 $2","40[24]|50[45]",,],["(48)(\\d{4})(\\d{4})","$1 $2 $3","48",,],["(818)(\\d{3})(\\d{3})","$1 $2 $3","81",,],["(\\d{2})(\\d{3})(\\d{3,4})","$1 $2 $3","[24-69]|7[14]",,],["([78]\\d)(\\d{3,4})(\\d{4})","$1 $2 $3","76|8[35-9]","$NP$FG",],["(700)(\\d{3})(\\d{3})","$1 $2 $3","70","$NP$FG",],["(\\d{4})(\\d{3})(\\d{3})","$1 $2 $3","1(?:8[059]|5)","$FG",]]]',
+"966": '["SA","00","0",,,"$NP$FG","\\d{7,10}","(?:[1-467]|92)\\d{7}|5\\d{8}|8\\d{9}",[["([1-467])(\\d{3})(\\d{4})","$1 $2 $3","[1-467]",,],["(5\\d)(\\d{3})(\\d{4})","$1 $2 $3","5",,],["(9200)(\\d{5})","$1 $2","9","$FG",],["(800)(\\d{3})(\\d{4})","$1 $2 $3","80","$FG",],["(8111)(\\d{3})(\\d{3})","$1 $2 $3","81",,]]]',
+"380": '["UA","00","0",,,"$NP$FG","\\d{5,9}","[3-689]\\d{8}",[["([3-69]\\d)(\\d{3})(\\d{4})","$1 $2 $3","39|4(?:[45][0-5]|87)|5(?:0|6[37]|7[37])|6[36-8]|9[1-9]",,],["([3-689]\\d{2})(\\d{3})(\\d{3})","$1 $2 $3","3[1-8]2|4[1378]2|5(?:[12457]2|6[24])|6(?:[49]2|[12][29]|5[24])|8|90",,],["([3-6]\\d{3})(\\d{5})","$1 $2","3(?:5[013-9]|[1-46-8])|4(?:[137][013-9]|6|[45][6-9]|8[4-6])|5(?:[1245][013-9]|6[0135-9]|3|7[4-6])|6(?:[49][013-9]|5[0135-9]|[12][13-8])",,]]]',
+"98": '["IR","00","0",,,"$NP$FG","\\d{4,10}","[2-6]\\d{4,9}|9(?:[1-4]\\d{8}|9\\d{2,8})|[178]\\d{9}",[["(21)(\\d{3,5})","$1 $2","21",,],["(21)(\\d{3})(\\d{3,4})","$1 $2 $3","21",,],["(21)(\\d{4})(\\d{4})","$1 $2 $3","21",,],["(\\d{3})(\\d{3})(\\d{3,4})","$1 $2 $3","[13-9]|2[02-9]",,]]]',
+"971": '["AE","00","0",,,"$NP$FG","\\d{5,12}","[2-79]\\d{7,8}|800\\d{2,9}",[["([2-4679])(\\d{3})(\\d{4})","$1 $2 $3","[2-4679][2-8]",,],["(5[0256])(\\d{3})(\\d{4})","$1 $2 $3","5",,],["([4679]00)(\\d)(\\d{5})","$1 $2 $3","[4679]0","$FG",],["(800)(\\d{2,9})","$1 $2","8","$FG",]]]',
+"30": '["GR","00",,,,,"\\d{10}","[26-9]\\d{9}",[["([27]\\d)(\\d{4})(\\d{4})","$1 $2 $3","21|7",,],["(\\d{3})(\\d{3})(\\d{4})","$1 $2 $3","2[2-9]1|[689]",,],["(2\\d{3})(\\d{6})","$1 $2","2[2-9][02-9]",,]]]',
+"228": '["TG","00",,,,,"\\d{8}","[29]\\d{7}",[["(\\d{2})(\\d{2})(\\d{2})(\\d{2})","$1 $2 $3 $4",,,]]]',
+"48": '["PL","00",,,,,"\\d{6,9}","[1-58]\\d{6,8}|9\\d{8}|[67]\\d{5,8}",[["(\\d{2})(\\d{3})(\\d{2})(\\d{2})","$1 $2 $3 $4","[124]|3[2-4]|5[24-689]|6[1-3578]|7[14-7]|8[1-79]|9[145]",,],["(\\d{2})(\\d{4,6})","$1 $2","[124]|3[2-4]|5[24-689]|6[1-3578]|7[14-7]|8[1-7]",,],["(\\d{3})(\\d{3})(\\d{3})","$1 $2 $3","39|5[013]|6[0469]|7[0289]|8[08]",,],["(\\d{3})(\\d{2})(\\d{2,3})","$1 $2 $3","64",,],["(\\d{3})(\\d{3})","$1 $2","64",,]]]',
+"886": '["TW","0(?:0[25679]|19)","0",,,"$NP$FG","\\d{8,9}","[2-9]\\d{7,8}",[["([2-8])(\\d{3,4})(\\d{4})","$1 $2 $3","[2-7]|8[1-9]",,],["([89]\\d{2})(\\d{3})(\\d{3})","$1 $2 $3","80|9",,]]]',
+"212": '["MA","00","0",,,"$NP$FG","\\d{9}","[5689]\\d{8}",[["([56]\\d{2})(\\d{6})","$1-$2","5(?:2[015-7]|3[0-4])|6",,],["([58]\\d{3})(\\d{5})","$1-$2","5(?:2[2-489]|3[5-9])|892",,],["(5\\d{4})(\\d{4})","$1-$2","5(?:29|38)",,],["(8[09])(\\d{7})","$1-$2","8(?:0|9[013-9])",,]]]',
+"372": '["EE","00",,,,,"\\d{4,10}","1\\d{3,4}|[3-9]\\d{6,7}|800\\d{6,7}",[["([3-79]\\d{2})(\\d{4})","$1 $2","[369]|4[3-8]|5(?:[0-2]|5[0-478]|6[45])|7[1-9]",,],["(70)(\\d{2})(\\d{4})","$1 $2 $3","70",,],["(8000)(\\d{3})(\\d{3})","$1 $2 $3","800",,],["([458]\\d{3})(\\d{3,4})","$1 $2","40|5|8(?:00|[1-5])",,]]]',
+"598": '["UY","0(?:1[3-9]\\d|0)","0",,,,"\\d{7,8}","[2489]\\d{6,7}",[["(\\d{4})(\\d{4})","$1 $2","[24]",,],["(\\d{2})(\\d{3})(\\d{3})","$1 $2 $3","9[1-9]","$NP$FG",],["(\\d{3})(\\d{4})","$1 $2","[89]0","$NP$FG",]]]',
+"502": '["GT","00",,,,,"\\d{8}(?:\\d{3})?","[2-7]\\d{7}|1[89]\\d{9}",[["(\\d{4})(\\d{4})","$1 $2","[2-7]",,],["(\\d{4})(\\d{3})(\\d{4})","$1 $2 $3","1",,]]]',
+"82": '["KR","00(?:[124-68]|[37]\\d{2})","0","0(8[1-46-8]|85\\d{2})?",,"$NP$FG","\\d{4,10}","[1-7]\\d{3,9}|8\\d{8}",[["(\\d{2})(\\d{4})(\\d{4})","$1-$2-$3","1(?:0|1[19]|[69]9|5[458])|[57]0",,],["(\\d{2})(\\d{3,4})(\\d{4})","$1-$2-$3","1(?:[169][2-8]|[78]|5[1-4])|[68]0|[3-6][1-9][2-9]",,],["(\\d{3})(\\d)(\\d{4})","$1-$2-$3","131",,],["(\\d{3})(\\d{2})(\\d{4})","$1-$2-$3","131",,],["(\\d{3})(\\d{3})(\\d{4})","$1-$2-$3","13[2-9]",,],["(\\d{2})(\\d{2})(\\d{3})(\\d{4})","$1-$2-$3-$4","30",,],["(\\d)(\\d{3,4})(\\d{4})","$1-$2-$3","2[2-9]",,],["(\\d)(\\d{3,4})","$1-$2","21[0-46-9]",,],["(\\d{2})(\\d{3,4})","$1-$2","[3-6][1-9]1",,],["(\\d{4})(\\d{4})","$1-$2","1(?:5[46-9]|6[04678])","$FG",]]]',
+"253": '["DJ","00",,,,,"\\d{8}","[27]\\d{7}",[["(\\d{2})(\\d{2})(\\d{2})(\\d{2})","$1 $2 $3 $4",,,]]]',
+"91": '["IN","00","0",,,"$NP$FG","\\d{6,13}","1\\d{7,12}|[2-9]\\d{9,10}",[["(\\d{2})(\\d{2})(\\d{6})","$1 $2 $3","7(?:2[0579]|3[057-9]|4[0-389]|5[024-9]|6[0-35-9]|7[03469]|8[0-4679])|8(?:0[01589]|1[0-479]|2[236-9]|3[0-57-9]|[45]|6[0245789]|7[1-69]|8[0124-9]|9[02-9])|9",,],["(\\d{2})(\\d{4})(\\d{4})","$1 $2 $3","11|2[02]|33|4[04]|79|80[2-46]",,],["(\\d{3})(\\d{3})(\\d{4})","$1 $2 $3","1(?:2[0-249]|3[0-25]|4[145]|[569][14]|7[1257]|8[1346]|[68][1-9])|2(?:1[257]|3[013]|4[01]|5[0137]|6[0158]|78|8[1568]|9[14])|3(?:26|4[1-3]|5[34]|6[01489]|7[02-46]|8[159])|4(?:1[36]|2[1-47]|3[15]|5[12]|6[126-9]|7[0-24-9]|8[013-57]|9[014-7])|5(?:[136][25]|22|4[28]|5[12]|[78]1|9[15])|6(?:12|[2345]1|57|6[13]|7[14]|80)",,],["(\\d{3})(\\d{3})(\\d{4})","$1 $2 $3","7(?:12|2[14]|3[134]|4[47]|5[15]|[67]1|88)",,],["(\\d{3})(\\d{3})(\\d{4})","$1 $2 $3","8(?:16|2[014]|3[126]|6[136]|7[078]|8[34]|91)",,],["(\\d{4})(\\d{3})(\\d{3})","$1 $2 $3","1(?:[2-579]|[68][1-9])|[2-8]",,],["(1600)(\\d{2})(\\d{4})","$1 $2 $3","160","$FG",],["(1800)(\\d{4,5})","$1 $2","180","$FG",],["(18[06]0)(\\d{2,4})(\\d{4})","$1 $2 $3","18[06]","$FG",],["(\\d{4})(\\d{3})(\\d{4})(\\d{2})","$1 $2 $3 $4","18[06]","$FG",]]]',
+"389": '["MK","00","0",,,"$NP$FG","\\d{8}","[2-578]\\d{7}",[["(2)(\\d{3})(\\d{4})","$1 $2 $3","2",,],["([347]\\d)(\\d{3})(\\d{3})","$1 $2 $3","[347]",,],["([58]\\d{2})(\\d)(\\d{2})(\\d{2})","$1 $2 $3 $4","[58]",,]]]',
+"1": ['["US","011","1",,,,"\\d{7}(?:\\d{3})?","[2-9]\\d{9}",[["(\\d{3})(\\d{4})","$1-$2",,,"NA"],["(\\d{3})(\\d{3})(\\d{4})","($1) $2-$3",,,"$1-$2-$3"]]]','["AI","011","1",,,,"\\d{7}(?:\\d{3})?","[2589]\\d{9}",]','["AS","011","1",,,,"\\d{7}(?:\\d{3})?","[5689]\\d{9}",]','["BB","011","1",,,,"\\d{7}(?:\\d{3})?","[2589]\\d{9}",]','["BM","011","1",,,,"\\d{7}(?:\\d{3})?","[4589]\\d{9}",]','["BS","011","1",,,,"\\d{7}(?:\\d{3})?","[2589]\\d{9}",]','["CA","011","1",,,,"\\d{7}(?:\\d{3})?","[2-9]\\d{9}|3\\d{6}",]','["DM","011","1",,,,"\\d{7}(?:\\d{3})?","[57-9]\\d{9}",]','["DO","011","1",,,,"\\d{7}(?:\\d{3})?","[589]\\d{9}",]','["GD","011","1",,,,"\\d{7}(?:\\d{3})?","[4589]\\d{9}",]','["GU","011","1",,,,"\\d{7}(?:\\d{3})?","[5689]\\d{9}",]','["JM","011","1",,,,"\\d{7}(?:\\d{3})?","[589]\\d{9}",]','["KN","011","1",,,,"\\d{7}(?:\\d{3})?","[589]\\d{9}",]','["KY","011","1",,,,"\\d{7}(?:\\d{3})?","[3589]\\d{9}",]','["LC","011","1",,,,"\\d{7}(?:\\d{3})?","[5789]\\d{9}",]','["MP","011","1",,,,"\\d{7}(?:\\d{3})?","[5689]\\d{9}",]','["MS","011","1",,,,"\\d{7}(?:\\d{3})?","[5689]\\d{9}",]','["PR","011","1",,,,"\\d{7}(?:\\d{3})?","[5789]\\d{9}",]','["SX","011","1",,,,"\\d{7}(?:\\d{3})?","[5789]\\d{9}",]','["TC","011","1",,,,"\\d{7}(?:\\d{3})?","[5689]\\d{9}",]','["TT","011","1",,,,"\\d{7}(?:\\d{3})?","[589]\\d{9}",]','["AG","011","1",,,,"\\d{7}(?:\\d{3})?","[2589]\\d{9}",]','["VC","011","1",,,,"\\d{7}(?:\\d{3})?","[5789]\\d{9}",]','["VG","011","1",,,,"\\d{7}(?:\\d{3})?","[2589]\\d{9}",]','["VI","011","1",,,,"\\d{7}(?:\\d{3})?","[3589]\\d{9}",]'],
+"60": '["MY","00","0",,,,"\\d{6,10}","[13-9]\\d{7,9}",[["([4-79])(\\d{3})(\\d{4})","$1-$2 $3","[4-79]","$NP$FG",],["(3)(\\d{4})(\\d{4})","$1-$2 $3","3","$NP$FG",],["([18]\\d)(\\d{3})(\\d{3,4})","$1-$2 $3","1[02-46-9][1-9]|8","$NP$FG",],["(1)([36-8]00)(\\d{2})(\\d{4})","$1-$2-$3-$4","1[36-8]0",,],["(11)(\\d{4})(\\d{4})","$1-$2 $3","11","$NP$FG",],["(154)(\\d{3})(\\d{4})","$1-$2 $3","15","$NP$FG",]]]',
+"355": '["AL","00","0",,,"$NP$FG","\\d{5,9}","[2-57]\\d{7}|6\\d{8}|8\\d{5,7}|9\\d{5}",[["(4)(\\d{3})(\\d{4})","$1 $2 $3","4[0-6]",,],["(6[6-9])(\\d{3})(\\d{4})","$1 $2 $3","6",,],["(\\d{2})(\\d{3})(\\d{3})","$1 $2 $3","[2358][2-5]|4[7-9]",,],["(\\d{3})(\\d{3,5})","$1 $2","[235][16-9]|8[016-9]|[79]",,]]]',
+"254": '["KE","000","0",,,"$NP$FG","\\d{5,10}","20\\d{6,7}|[4-9]\\d{6,9}",[["(\\d{2})(\\d{4,7})","$1 $2","[24-6]",,],["(\\d{3})(\\d{6,7})","$1 $2","7",,],["(\\d{3})(\\d{3})(\\d{3,4})","$1 $2 $3","[89]",,]]]',
+"223": '["ML","00",,,,,"\\d{8}","[246-8]\\d{7}",[["([246-8]\\d)(\\d{2})(\\d{2})(\\d{2})","$1 $2 $3 $4",,,]]]',
+"686": '["KI","00",,"0",,,"\\d{5}","[2-689]\\d{4}",]',
+"994": '["AZ","00","0",,,"($NP$FG)","\\d{7,9}","[1-9]\\d{8}",[["(\\d{2})(\\d{3})(\\d{2})(\\d{2})","$1 $2 $3 $4","(?:1[28]|2(?:[45]2|[0-36])|365)",,],["(\\d{2})(\\d{3})(\\d{2})(\\d{2})","$1 $2 $3 $4","[4-8]","$NP$FG",],["(\\d{3})(\\d{2})(\\d{2})(\\d{2})","$1 $2 $3 $4","9","$NP$FG",]]]',
+"979": '["001",,,,,,"\\d{9}","\\d{9}",[["(\\d)(\\d{4})(\\d{4})","$1 $2 $3",,,]]]',
+"66": '["TH","00","0",,,"$NP$FG","\\d{4}|\\d{8,10}","[2-9]\\d{7,8}|1\\d{3}(?:\\d{6})?",[["(2)(\\d{3})(\\d{4})","$1 $2 $3","2",,],["([3-9]\\d)(\\d{3})(\\d{3,4})","$1 $2 $3","[3-9]",,],["(1[89]00)(\\d{3})(\\d{3})","$1 $2 $3","1","$FG",]]]',
+"233": '["GH","00","0",,,"$NP$FG","\\d{7,9}","[235]\\d{8}|8\\d{7}",[["(\\d{2})(\\d{3})(\\d{4})","$1 $2 $3","[235]",,],["(\\d{3})(\\d{5})","$1 $2","8",,]]]',
+"593": '["EC","00","0",,,"($NP$FG)","\\d{7,11}","1\\d{9,10}|[2-8]\\d{7}|9\\d{8}",[["(\\d)(\\d{3})(\\d{4})","$1 $2-$3","[247]|[356][2-8]",,"$1-$2-$3"],["(\\d{2})(\\d{3})(\\d{4})","$1 $2 $3","9","$NP$FG",],["(1800)(\\d{3})(\\d{3,4})","$1 $2 $3","1","$FG",]]]',
+"509": '["HT","00",,,,,"\\d{8}","[2-489]\\d{7}",[["(\\d{2})(\\d{2})(\\d{4})","$1 $2 $3",,,]]]',
+"54": '["AR","00","0","0?(?:(11|2(?:2(?:02?|[13]|2[13-79]|4[1-6]|5[2457]|6[124-8]|7[1-4]|8[13-6]|9[1267])|3(?:02?|1[467]|2[03-6]|3[13-8]|[49][2-6]|5[2-8]|[67])|4(?:7[3-578]|9)|6(?:[0136]|2[24-6]|4[6-8]?|5[15-8])|80|9(?:0[1-3]|[19]|2\\d|3[1-6]|4[02568]?|5[2-4]|6[2-46]|72?|8[23]?))|3(?:3(?:2[79]|6|8[2578])|4(?:0[124-9]|[12]|3[5-8]?|4[24-7]|5[4-68]?|6[02-9]|7[126]|8[2379]?|9[1-36-8])|5(?:1|2[1245]|3[237]?|4[1-46-9]|6[2-4]|7[1-6]|8[2-5]?)|6[24]|7(?:1[1568]|2[15]|3[145]|4[13]|5[14-8]|[069]|7[2-57]|8[126])|8(?:[01]|2[15-7]|3[2578]?|4[13-6]|5[4-8]?|6[1-357-9]|7[36-8]?|8[5-8]?|9[124])))15)?","9$1","$NP$FG","\\d{6,11}","[1-368]\\d{9}|9\\d{10}",[["([68]\\d{2})(\\d{3})(\\d{4})","$1-$2-$3","[68]",,],["(9)(11)(\\d{4})(\\d{4})","$2 15-$3-$4","911",,"$1 $2 $3-$4"],["(9)(\\d{3})(\\d{3})(\\d{4})","$2 15-$3-$4","9(?:2[234689]|3[3-8])",,"$1 $2 $3-$4"],["(9)(\\d{4})(\\d{3})(\\d{3})","$2 15-$3-$4","93[58]",,],["(9)(\\d{4})(\\d{2})(\\d{4})","$2 15-$3-$4","9[23]",,"$1 $2 $3-$4"],["(11)(\\d{4})(\\d{4})","$1 $2-$3","1",,],["(\\d{3})(\\d{3})(\\d{4})","$1 $2-$3","2(?:2[013]|3[067]|49|6[01346]|80|9[147-9])|3(?:36|4[12358]|5[138]|6[24]|7[069]|8[013578])",,],["(\\d{4})(\\d{3})(\\d{3})","$1 $2-$3","3(?:53|8[78])",,],["(\\d{4})(\\d{2})(\\d{4})","$1 $2-$3","[23]",,]]]',
+"57": '["CO","00[579]|#555|#999","0","0([3579]|4(?:44|56))?",,,"\\d{7,11}","(?:[13]\\d{0,3}|[24-8])\\d{7}",[["(\\d)(\\d{7})","$1 $2","1(?:8[2-9]|9[0-3]|[2-7])|[24-8]","($FG)",],["(\\d{3})(\\d{7})","$1 $2","3",,],["(1)(\\d{3})(\\d{7})","$1-$2-$3","1(?:80|9[04])","$NP$FG","$1 $2 $3"]]]',
+"597": '["SR","00",,,,,"\\d{6,7}","[2-8]\\d{5,6}",[["(\\d{3})(\\d{3})","$1-$2","[2-4]|5[2-58]",,],["(\\d{2})(\\d{2})(\\d{2})","$1-$2-$3","56",,],["(\\d{3})(\\d{4})","$1-$2","[6-8]",,]]]',
+"676": '["TO","00",,,,,"\\d{5,7}","[02-8]\\d{4,6}",[["(\\d{2})(\\d{3})","$1-$2","[1-6]|7[0-4]|8[05]",,],["(\\d{3})(\\d{4})","$1 $2","7[5-9]|8[7-9]",,],["(\\d{4})(\\d{3})","$1 $2","0",,]]]',
+"505": '["NI","00",,,,,"\\d{8}","[128]\\d{7}",[["(\\d{4})(\\d{4})","$1 $2",,,]]]',
+"850": '["KP","00|99","0",,,"$NP$FG","\\d{6,8}|\\d{10}","1\\d{9}|[28]\\d{7}",[["(\\d{3})(\\d{3})(\\d{4})","$1 $2 $3","1",,],["(\\d)(\\d{3})(\\d{4})","$1 $2 $3","2",,],["(\\d{2})(\\d{3})(\\d{3})","$1 $2 $3","8",,]]]',
+"7": ['["RU","810","8",,,"$NP ($FG)","\\d{10}","[3489]\\d{9}",[["(\\d{3})(\\d{2})(\\d{2})","$1-$2-$3","[1-79]","$FG","NA"],["([3489]\\d{2})(\\d{3})(\\d{2})(\\d{2})","$1 $2-$3-$4","[34689]",,],["(7\\d{2})(\\d{3})(\\d{4})","$1 $2 $3","7",,]]]','["KZ","810","8",,,,"\\d{10}","(?:33\\d|7\\d{2}|80[09])\\d{7}",]'],
+"268": '["SZ","00",,,,,"\\d{8}","[027]\\d{7}",[["(\\d{4})(\\d{4})","$1 $2","[027]",,]]]',
+"501": '["BZ","00",,,,,"\\d{7}(?:\\d{4})?","[2-8]\\d{6}|0\\d{10}",[["(\\d{3})(\\d{4})","$1-$2","[2-8]",,],["(0)(800)(\\d{4})(\\d{3})","$1-$2-$3-$4","0",,]]]',
+"252": '["SO","00","0",,,,"\\d{7,9}","[1-79]\\d{6,8}",[["(\\d)(\\d{6})","$1 $2","2[0-79]|[13-5]",,],["(\\d)(\\d{7})","$1 $2","24|[67]",,],["(\\d{2})(\\d{5,7})","$1 $2","15|28|6[178]|9",,],["(69\\d)(\\d{6})","$1 $2","69",,]]]',
+"229": '["BJ","00",,,,,"\\d{4,8}","[2689]\\d{7}|7\\d{3}",[["(\\d{2})(\\d{2})(\\d{2})(\\d{2})","$1 $2 $3 $4",,,]]]',
+"680": '["PW","01[12]",,,,,"\\d{7}","[2-8]\\d{6}",[["(\\d{3})(\\d{4})","$1 $2",,,]]]',
+"263": '["ZW","00","0",,,"$NP$FG","\\d{3,10}","2(?:[012457-9]\\d{3,8}|6\\d{3,6})|[13-79]\\d{4,8}|86\\d{8}",[["([49])(\\d{3})(\\d{2,5})","$1 $2 $3","4|9[2-9]",,],["([179]\\d)(\\d{3})(\\d{3,4})","$1 $2 $3","[19]1|7",,],["(86\\d{2})(\\d{3})(\\d{3})","$1 $2 $3","86[24]",,],["([1-356]\\d)(\\d{3,5})","$1 $2","1[3-9]|2(?:[1-469]|0[0-35-9]|[45][0-79])|3(?:0[0-79]|1[0-689]|[24-69]|3[0-69])|5(?:[02-46-9]|[15][0-69])|6(?:[0145]|[29][0-79]|3[0-689]|[68][0-69])",,],["([1-356]\\d)(\\d{3})(\\d{3})","$1 $2 $3","1[3-9]|2(?:[1-469]|0[0-35-9]|[45][0-79])|3(?:0[0-79]|1[0-689]|[24-69]|3[0-69])|5(?:[02-46-9]|[15][0-69])|6(?:[0145]|[29][0-79]|3[0-689]|[68][0-69])",,],["([2356]\\d{2})(\\d{3,5})","$1 $2","2(?:[278]|0[45]|48)|3(?:08|17|3[78]|[78])|5[15][78]|6(?:[29]8|37|[68][78])",,],["([2356]\\d{2})(\\d{3})(\\d{3})","$1 $2 $3","2(?:[278]|0[45]|48)|3(?:08|17|3[78]|[78])|5[15][78]|6(?:[29]8|37|[68][78])",,],["([25]\\d{3})(\\d{3,5})","$1 $2","(?:25|54)8",,],["([25]\\d{3})(\\d{3})(\\d{3})","$1 $2 $3","(?:25|54)8",,],["(8\\d{3})(\\d{6})","$1 $2","86[1389]",,]]]',
+"90": '["TR","00","0",,,,"\\d{7,10}","[2-589]\\d{9}|444\\d{4}",[["(\\d{3})(\\d{3})(\\d{4})","$1 $2 $3","[23]|4(?:[0-35-9]|4[0-35-9])","($NP$FG)",],["(\\d{3})(\\d{3})(\\d{4})","$1 $2 $3","[589]","$NP$FG",],["(444)(\\d{1})(\\d{3})","$1 $2 $3","444",,]]]',
+"352": '["LU","00",,"(15(?:0[06]|1[12]|35|4[04]|55|6[26]|77|88|99)\\d)",,,"\\d{4,11}","[24-9]\\d{3,10}|3(?:[0-46-9]\\d{2,9}|5[013-9]\\d{1,8})",[["(\\d{2})(\\d{3})","$1 $2","[2-5]|7[1-9]|[89](?:[1-9]|0[2-9])",,],["(\\d{2})(\\d{2})(\\d{2})","$1 $2 $3","[2-5]|7[1-9]|[89](?:[1-9]|0[2-9])",,],["(\\d{2})(\\d{2})(\\d{3})","$1 $2 $3","20",,],["(\\d{2})(\\d{2})(\\d{2})(\\d{1,2})","$1 $2 $3 $4","2(?:[0367]|4[3-8])",,],["(\\d{2})(\\d{2})(\\d{2})(\\d{3})","$1 $2 $3 $4","20",,],["(\\d{2})(\\d{2})(\\d{2})(\\d{2})(\\d{1,2})","$1 $2 $3 $4 $5","2(?:[0367]|4[3-8])",,],["(\\d{2})(\\d{2})(\\d{2})(\\d{1,4})","$1 $2 $3 $4","2(?:[12589]|4[12])|[3-5]|7[1-9]|[89](?:[1-9]|0[2-9])",,],["(\\d{3})(\\d{2})(\\d{3})","$1 $2 $3","[89]0[01]|70",,],["(\\d{3})(\\d{3})(\\d{3})","$1 $2 $3","6",,]]]',
+"47": ['["NO","00",,,,,"\\d{5}(?:\\d{3})?","0\\d{4}|[2-9]\\d{7}",[["([489]\\d{2})(\\d{2})(\\d{3})","$1 $2 $3","[489]",,],["([235-7]\\d)(\\d{2})(\\d{2})(\\d{2})","$1 $2 $3 $4","[235-7]",,]]]','["SJ","00",,,,,"\\d{5}(?:\\d{3})?","0\\d{4}|[4789]\\d{7}",]'],
+"243": '["CD","00","0",,,"$NP$FG","\\d{7,9}","[1-6]\\d{6}|8\\d{6,8}|9\\d{8}",[["([89]\\d{2})(\\d{3})(\\d{3})","$1 $2 $3","8[0-259]|9",,],["(\\d{2})(\\d{2})(\\d{3})","$1 $2 $3","8[48]",,],["(\\d{2})(\\d{5})","$1 $2","[1-6]",,]]]',
+"220": '["GM","00",,,,,"\\d{7}","[2-9]\\d{6}",[["(\\d{3})(\\d{4})","$1 $2",,,]]]',
+"687": '["NC","00",,,,,"\\d{6}","[2-47-9]\\d{5}",[["(\\d{2})(\\d{2})(\\d{2})","$1.$2.$3",,,]]]',
+"995": '["GE","810","8",,,,"\\d{6,9}","[3458]\\d{8}",[["(\\d{3})(\\d{2})(\\d{2})(\\d{2})","$1 $2 $3 $4","[348]","$NP $FG",],["(\\d{3})(\\d{2})(\\d{2})(\\d{2})","$1 $2 $3 $4","5","$FG",]]]',
+"961": '["LB","00","0",,,,"\\d{7,8}","[13-9]\\d{6,7}",[["(\\d)(\\d{3})(\\d{3})","$1 $2 $3","[13-6]|7(?:[2-579]|62|8[0-7])|[89][2-9]","$NP$FG",],["([7-9]\\d)(\\d{3})(\\d{3})","$1 $2 $3","[89][01]|7(?:[01]|6[013-9]|8[89]|91)",,]]]',
+"40": '["RO","00","0",,,"$NP$FG","\\d{6,9}","2\\d{5,8}|[37-9]\\d{8}",[["([237]\\d)(\\d{3})(\\d{4})","$1 $2 $3","[23]1|7",,],["(21)(\\d{4})","$1 $2","21",,],["(\\d{3})(\\d{3})(\\d{3})","$1 $2 $3","[23][3-7]|[89]",,],["(2\\d{2})(\\d{3})","$1 $2","2[3-6]",,]]]',
+"232": '["SL","00","0",,,"($NP$FG)","\\d{6,8}","[2-578]\\d{7}",[["(\\d{2})(\\d{6})","$1 $2",,,]]]',
+"594": '["GF","00","0",,,"$NP$FG","\\d{9}","[56]\\d{8}",[["(\\d{3})(\\d{2})(\\d{2})(\\d{2})","$1 $2 $3 $4",,,]]]',
+"976": '["MN","001","0",,,"$NP$FG","\\d{6,10}","[12]\\d{7,9}|[57-9]\\d{7}",[["([12]\\d)(\\d{2})(\\d{4})","$1 $2 $3","[12]1",,],["([12]2\\d)(\\d{5,6})","$1 $2","[12]2[1-3]",,],["([12]\\d{3})(\\d{5})","$1 $2","[12](?:27|[3-5])",,],["(\\d{4})(\\d{4})","$1 $2","[57-9]","$FG",],["([12]\\d{4})(\\d{4,5})","$1 $2","[12](?:27|[3-5])",,]]]',
+"20": '["EG","00","0",,,"$NP$FG","\\d{5,10}","1\\d{4,9}|[2456]\\d{8}|3\\d{7}|[89]\\d{8,9}",[["(\\d)(\\d{7,8})","$1 $2","[23]",,],["(\\d{3})(\\d{3})(\\d{4})","$1 $2 $3","1[012]|[89]00",,],["(\\d{2})(\\d{6,7})","$1 $2","1(?:3|5[23])|[4-6]|[89][2-9]",,]]]',
+"689": '["PF","00",,,,,"\\d{6}","[2-9]\\d{5}",[["(\\d{2})(\\d{2})(\\d{2})","$1 $2 $3",,,]]]',
+"56": '["CL","(?:0|1(?:1[0-69]|2[0-57]|5[13-58]|69|7[0167]|8[018]))0","0","0|(1(?:1[0-69]|2[0-57]|5[13-58]|69|7[0167]|8[018]))",,"$NP$FG","\\d{6,11}","(?:[2-9]|600|123)\\d{7,8}",[["(2)(\\d{3})(\\d{4})","$1 $2 $3","2","($FG)",],["(\\d{2})(\\d{2,3})(\\d{4})","$1 $2 $3","[357]|4[1-35]|6[13-57]","($FG)",],["(9)([5-9]\\d{3})(\\d{4})","$1 $2 $3","9",,],["(44)(\\d{3})(\\d{4})","$1 $2 $3","44",,],["([68]00)(\\d{3})(\\d{3,4})","$1 $2 $3","60|8","$FG",],["(600)(\\d{3})(\\d{2})(\\d{3})","$1 $2 $3 $4","60","$FG",],["(1230)(\\d{3})(\\d{4})","$1 $2 $3","1","$FG",]]]',
+"596": '["MQ","00","0",,,"$NP$FG","\\d{9}","[56]\\d{8}",[["(\\d{3})(\\d{2})(\\d{2})(\\d{2})","$1 $2 $3 $4",,,]]]',
+"508": '["PM","00","0",,,"$NP$FG","\\d{6}","[45]\\d{5}",[["([45]\\d)(\\d{2})(\\d{2})","$1 $2 $3",,,]]]',
+"269": '["KM","00",,,,,"\\d{7}","[379]\\d{6}",[["(\\d{3})(\\d{2})(\\d{2})","$1 $2 $3",,,]]]',
+"358": ['["FI","00|99[049]","0",,,"$NP$FG","\\d{5,12}","1\\d{4,11}|[2-9]\\d{4,10}",[["(\\d{3})(\\d{3,7})","$1 $2","(?:[1-3]00|[6-8]0)",,],["(\\d{2})(\\d{4,10})","$1 $2","2[09]|[14]|50|7[135]",,],["(\\d)(\\d{4,11})","$1 $2","[25689][1-8]|3",,]]]','["AX","00|99[049]","0",,,"$NP$FG","\\d{5,12}","[135]\\d{5,9}|[27]\\d{4,9}|4\\d{5,10}|6\\d{7,8}|8\\d{6,9}",]'],
+"251": '["ET","00","0",,,"$NP$FG","\\d{7,9}","[1-59]\\d{8}",[["([1-59]\\d)(\\d{3})(\\d{4})","$1 $2 $3",,,]]]',
+"681": '["WF","00",,,,,"\\d{6}","[5-7]\\d{5}",[["(\\d{2})(\\d{2})(\\d{2})","$1 $2 $3",,,]]]',
+"853": '["MO","00",,,,,"\\d{8}","[268]\\d{7}",[["([268]\\d{3})(\\d{4})","$1 $2",,,]]]',
+"44": ['["GB","00","0",,,"$NP$FG","\\d{4,10}","\\d{7,10}",[["(\\d{2})(\\d{4})(\\d{4})","$1 $2 $3","2|5[56]|7(?:0|6[013-9])",,],["(\\d{3})(\\d{3})(\\d{4})","$1 $2 $3","1(?:1|\\d1)|3|9[018]",,],["(\\d{5})(\\d{4,5})","$1 $2","1(?:38|5[23]|69|76|94)",,],["(1\\d{3})(\\d{5,6})","$1 $2","1",,],["(7\\d{3})(\\d{6})","$1 $2","7(?:[1-5789]|62)",,],["(800)(\\d{4})","$1 $2","800",,],["(845)(46)(4\\d)","$1 $2 $3","845",,],["(8\\d{2})(\\d{3})(\\d{4})","$1 $2 $3","8(?:4[2-5]|7[0-3])",,],["(80\\d)(\\d{3})(\\d{4})","$1 $2 $3","80",,],["([58]00)(\\d{6})","$1 $2","[58]00",,]]]','["GG","00","0",,,"$NP$FG","\\d{6,10}","[135789]\\d{6,9}",]','["IM","00","0",,,"$NP$FG","\\d{6,10}","[135789]\\d{6,9}",]','["JE","00","0",,,"$NP$FG","\\d{6,10}","[135789]\\d{6,9}",]'],
+"244": '["AO","00",,,,,"\\d{9}","[29]\\d{8}",[["(\\d{3})(\\d{3})(\\d{3})","$1 $2 $3",,,]]]',
+"211": '["SS","00","0",,,,"\\d{9}","[19]\\d{8}",[["(\\d{3})(\\d{3})(\\d{3})","$1 $2 $3",,"$NP$FG",]]]',
+"373": '["MD","00","0",,,"$NP$FG","\\d{8}","[235-9]\\d{7}",[["(\\d{2})(\\d{3})(\\d{3})","$1 $2 $3","22|3",,],["([25-7]\\d{2})(\\d{2})(\\d{3})","$1 $2 $3","2[13-79]|[5-7]",,],["([89]\\d{2})(\\d{5})","$1 $2","[89]",,]]]',
+"996": '["KG","00","0",,,"$NP$FG","\\d{5,10}","[35-8]\\d{8,9}",[["(\\d{3})(\\d{3})(\\d{3})","$1 $2 $3","31[25]|[5-7]",,],["(\\d{4})(\\d{5})","$1 $2","3(?:1[36]|[2-9])",,],["(\\d{3})(\\d{3})(\\d)(\\d{3})","$1 $2 $3 $4","8",,]]]',
+"93": '["AF","00","0",,,"$NP$FG","\\d{7,9}","[2-7]\\d{8}",[["([2-7]\\d)(\\d{3})(\\d{4})","$1 $2 $3",,,]]]',
+"260": '["ZM","00","0",,,"$NP$FG","\\d{9}","[289]\\d{8}",[["([29]\\d)(\\d{7})","$1 $2","[29]",,],["(800)(\\d{3})(\\d{3})","$1 $2 $3","8",,]]]',
+"378": '["SM","00",,"(?:0549)?([89]\\d{5})","0549$1",,"\\d{6,10}","[05-7]\\d{7,9}",[["(\\d{2})(\\d{2})(\\d{2})(\\d{2})","$1 $2 $3 $4","[5-7]",,],["(0549)(\\d{6})","$1 $2","0",,"($1) $2"],["(\\d{6})","0549 $1","[89]",,"(0549) $1"]]]',
+"235": '["TD","00|16",,,,,"\\d{8}","[2679]\\d{7}",[["(\\d{2})(\\d{2})(\\d{2})(\\d{2})","$1 $2 $3 $4",,,]]]',
+"960": '["MV","0(?:0|19)",,,,,"\\d{7,10}","[3467]\\d{6}|9(?:00\\d{7}|\\d{6})",[["(\\d{3})(\\d{4})","$1-$2","[3467]|9(?:[1-9]|0[1-9])",,],["(\\d{3})(\\d{3})(\\d{4})","$1 $2 $3","900",,]]]',
+"221": '["SN","00",,,,,"\\d{9}","[37]\\d{8}",[["(\\d{2})(\\d{3})(\\d{2})(\\d{2})","$1 $2 $3 $4",,,]]]',
+"595": '["PY","00","0",,,,"\\d{5,9}","5[0-5]\\d{4,7}|[2-46-9]\\d{5,8}",[["(\\d{2})(\\d{5,7})","$1 $2","(?:[26]1|3[289]|4[124678]|7[123]|8[1236])","($FG)",],["(\\d{3})(\\d{3,6})","$1 $2","[2-9]0","$NP$FG",],["(\\d{3})(\\d{6})","$1 $2","9[1-9]","$NP$FG",],["(\\d{2})(\\d{3})(\\d{4})","$1 $2 $3","8700",,],["(\\d{3})(\\d{4,6})","$1 $2","[2-8][1-9]","($FG)",]]]',
+"977": '["NP","00","0",,,"$NP$FG","\\d{6,10}","[1-8]\\d{7}|9(?:[1-69]\\d{6}|7[2-6]\\d{5,7}|8\\d{8})",[["(1)(\\d{7})","$1-$2","1[2-6]",,],["(\\d{2})(\\d{6})","$1-$2","1[01]|[2-8]|9(?:[1-69]|7[15-9])",,],["(9\\d{2})(\\d{7})","$1-$2","9(?:7[45]|8)",,]]]',
+"36": '["HU","00","06",,,"($FG)","\\d{6,9}","[1-9]\\d{7,8}",[["(1)(\\d{3})(\\d{4})","$1 $2 $3","1",,],["(\\d{2})(\\d{3})(\\d{3,4})","$1 $2 $3","[2-9]",,]]]',
+};
diff --git a/shared/js/phoneNumberJS/mcc_iso3166_table.js b/shared/js/phoneNumberJS/mcc_iso3166_table.js
new file mode 100644
index 0000000..4119856
--- /dev/null
+++ b/shared/js/phoneNumberJS/mcc_iso3166_table.js
@@ -0,0 +1,242 @@
+// MCC(Mobile Country Codes) and country name(ISO3166-1) mapping table.
+// Reference Data from:
+// http://en.wikipedia.org/wiki/List_of_mobile_country_codes
+
+MCC_ISO3166_TABLE = {
+412:'AF',
+276:'AL',
+603:'DZ',
+544:'AS',
+213:'AD',
+631:'AO',
+365:'AI',
+344:'AG',
+722:'AR',
+283:'AM',
+363:'AW',
+505:'AU',
+232:'AT',
+400:'AZ',
+364:'BS',
+426:'BH',
+470:'BD',
+342:'BB',
+257:'BY',
+206:'BE',
+702:'BZ',
+616:'BJ',
+350:'BM',
+402:'BT',
+736:'BO',
+218:'BA',
+652:'BW',
+724:'BR',
+348:'VG',
+528:'BN',
+284:'BG',
+613:'BF',
+642:'BI',
+456:'KH',
+624:'CM',
+302:'CA',
+625:'CV',
+346:'KY',
+623:'CF',
+622:'TD',
+730:'CL',
+460:'CN',
+461:'CN',
+732:'CO',
+654:'KM',
+629:'CG',
+548:'CK',
+712:'CR',
+612:'CI',
+219:'HR',
+368:'CU',
+362:'CW',
+280:'CY',
+230:'CZ',
+630:'CD',
+238:'DK',
+638:'DJ',
+366:'DM',
+370:'DO',
+514:'TL',
+740:'EC',
+602:'EG',
+706:'SV',
+627:'GQ',
+657:'ER',
+248:'EE',
+636:'ET',
+750:'FK',
+288:'FO',
+542:'FJ',
+244:'FI',
+208:'FR',
+742:'GF',
+547:'PF',
+628:'GA',
+607:'GM',
+282:'GE',
+262:'DE',
+620:'GH',
+266:'GI',
+202:'GR',
+290:'GL',
+352:'GD',
+340:'GP',
+535:'GU',
+704:'GT',
+611:'GN',
+632:'GW',
+738:'GY',
+372:'HT',
+708:'HN',
+454:'HK',
+216:'HU',
+274:'IS',
+404:'IN',
+405:'IN',
+406:'IN',
+510:'ID',
+432:'IR',
+418:'IQ',
+272:'IE',
+425:'IL',
+222:'IT',
+338:'JM',
+441:'JP',
+440:'JP',
+416:'JO',
+401:'KZ',
+639:'KE',
+545:'KI',
+467:'KP',
+450:'KR',
+419:'KW',
+437:'KG',
+457:'LA',
+247:'LV',
+415:'LB',
+651:'LS',
+618:'LR',
+606:'LY',
+295:'LI',
+246:'LT',
+270:'LU',
+455:'MO',
+294:'MK',
+646:'MG',
+650:'MW',
+502:'MY',
+472:'MV',
+610:'ML',
+278:'MT',
+551:'MH',
+340:'MQ',
+609:'MR',
+617:'MU',
+334:'MX',
+550:'FM',
+259:'MD',
+212:'MC',
+428:'MN',
+297:'ME',
+354:'MS',
+604:'MA',
+643:'MZ',
+414:'MM',
+649:'NA',
+536:'NR',
+429:'NP',
+204:'NL',
+546:'NC',
+530:'NZ',
+710:'NI',
+614:'NE',
+621:'NG',
+555:'NU',
+534:'MP',
+242:'NO',
+422:'OM',
+410:'PK',
+552:'PW',
+425:'PS',
+714:'PA',
+537:'PG',
+744:'PY',
+716:'PE',
+515:'PH',
+260:'PL',
+268:'PT',
+330:'PR',
+427:'QA',
+647:'RE',
+226:'RO',
+250:'RU',
+635:'RW',
+356:'KN',
+358:'LC',
+308:'PM',
+360:'VC',
+549:'WS',
+292:'SM',
+626:'ST',
+420:'SA',
+608:'SN',
+220:'RS',
+633:'SC',
+619:'SL',
+525:'SG',
+231:'SK',
+293:'SI',
+540:'SB',
+637:'SO',
+655:'ZA',
+214:'ES',
+413:'LK',
+634:'SD',
+746:'SR',
+653:'SZ',
+240:'SE',
+228:'CH',
+417:'SY',
+466:'TW',
+436:'TJ',
+640:'TZ',
+520:'TH',
+615:'TG',
+539:'TO',
+374:'TT',
+605:'TN',
+286:'TR',
+438:'TM',
+376:'TC',
+641:'UG',
+255:'UA',
+424:'AE',
+430:'AE',
+431:'AE',
+235:'GB',
+234:'GB',
+310:'US',
+311:'US',
+312:'US',
+313:'US',
+314:'US',
+315:'US',
+316:'US',
+332:'VI',
+748:'UY',
+434:'UZ',
+541:'VU',
+225:'VA',
+734:'VE',
+452:'VN',
+543:'WF',
+421:'YE',
+645:'ZM',
+648:'ZW'
+}
diff --git a/shared/js/settings_listener.js b/shared/js/settings_listener.js
new file mode 100644
index 0000000..272729c
--- /dev/null
+++ b/shared/js/settings_listener.js
@@ -0,0 +1,69 @@
+/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- /
+/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
+
+'use strict';
+
+var SettingsListener = {
+ /* Timer to remove the lock. */
+ _timer: null,
+
+ /* lock stores here */
+ _lock: null,
+
+ /**
+ * getSettingsLock: create a lock or retrieve one that we saved.
+ * mozSettings.createLock() is expensive and lock should be reused
+ * whenever possible.
+ */
+
+ getSettingsLock: function sl_getSettingsLock() {
+ // Each time there is a getSettingsLock call, we pospone the removal
+ clearTimeout(this._timer);
+ this._timer = setTimeout((function removeLock() {
+ this._lock = null;
+ }).bind(this), 0);
+
+ // If there is a lock present we return that
+ if (this._lock) {
+ return this._lock;
+ }
+
+ // If there isn't we return one.
+ var settings = window.navigator.mozSettings;
+
+ return (this._lock = settings.createLock());
+ },
+
+ observe: function sl_observe(name, defaultValue, callback) {
+ var settings = window.navigator.mozSettings;
+ if (!settings) {
+ window.setTimeout(function() { callback(defaultValue); });
+ return;
+ }
+
+ var req;
+ try {
+ req = this.getSettingsLock().get(name);
+ } catch (e) {
+ // It is possible (but rare) for getSettingsLock() to return
+ // a SettingsLock object that is no longer valid.
+ // Until https://bugzilla.mozilla.org/show_bug.cgi?id=793239
+ // is fixed, we just catch the resulting exception and try
+ // again with a fresh lock
+ console.warn('Stale lock in settings_listener.js.',
+ 'See https://bugzilla.mozilla.org/show_bug.cgi?id=793239');
+ this._lock = null;
+ req = this.getSettingsLock().get(name);
+ }
+
+ req.addEventListener('success', (function onsuccess() {
+ callback(typeof(req.result[name]) != 'undefined' ?
+ req.result[name] : defaultValue);
+ }));
+
+ settings.addObserver(name, function settingChanged(evt) {
+ callback(evt.settingValue);
+ });
+ }
+};
+
diff --git a/shared/js/simple_phone_matcher.js b/shared/js/simple_phone_matcher.js
new file mode 100644
index 0000000..249c551
--- /dev/null
+++ b/shared/js/simple_phone_matcher.js
@@ -0,0 +1,366 @@
+/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- /
+/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
+
+'use strict';
+
+/**
+ * A simple lib to match internaional phone number with local
+ * and user formatted phone numbers.
+ *
+ * Adding this feature to gecko is discussed here:
+ * https://bugzilla.mozilla.org/show_bug.cgi?id=743363
+ *
+ */
+
+var SimplePhoneMatcher = {
+ mcc: '724', // Assuming a Brazilian mcc by default, can be changed.
+
+ // Used to remove all the formatting from a phone number.
+ sanitizedNumber: function spm_sanitizedNumber(number) {
+ var join = this._formattingChars.join('|\\');
+ var regexp = new RegExp('(\\' + join + ')', 'g');
+ return number.replace(regexp, '');
+ },
+
+ // Generate variants of a phone number (with prefix, without...).
+ // The variants are sorted from the shortest to the longest.
+ generateVariants: function spm_generateVariants(number) {
+ var sanitizedNumber = this.sanitizedNumber(number);
+
+ var variants = [];
+
+ variants = variants.concat(this._internationalPrefixes(sanitizedNumber),
+ this._trunkPrefixes(sanitizedNumber),
+ this._carrierPrefixes(sanitizedNumber),
+ this._areaPrefixes(sanitizedNumber));
+
+ return variants.sort(function shortestFirst(a, b) {
+ return a.length > b.length;
+ });
+ },
+
+ // Find the best (ie longest) match between the variants for a number
+ // and matches.
+ // |matches| is an array of arrays
+ // This way we can easily go trough the results of a mozContacts request:
+ // array (contacts) of arrays (phone numbers).
+ // => {
+ // bestMatchIndex: i,
+ // localIndex: j
+ // }
+ // ie. bestMatchIndex will be the index in the contact arrays, localIndex
+ // the index in the phone numbers array of this contact
+ bestMatch: function spm_bestMatchIndex(variants, matches) {
+ var bestMatchIndex = null;
+ var bestLocalIndex = null;
+ var bestMatchLength = 0;
+
+ matches.forEach(function(match, matchIndex) {
+ match.forEach(function(number, localIndex) {
+ var sanitizedNumber = this.sanitizedNumber(number);
+
+ variants.forEach(function match(variant) {
+ if (variant.indexOf(sanitizedNumber) !== -1 ||
+ sanitizedNumber.indexOf(variant) !== -1) {
+ var length = sanitizedNumber.length;
+
+ if (length > bestMatchLength) {
+ bestMatchLength = length;
+ bestMatchIndex = matchIndex;
+ bestLocalIndex = localIndex;
+ }
+ }
+ });
+ }, this);
+ }, this);
+
+ return {
+ bestMatchIndex: bestMatchIndex,
+ localIndex: bestLocalIndex
+ };
+ },
+
+ _formattingChars: ['\s', '-', '.', '(', ')'],
+
+ _mccWith00Prefix: ['208', '214', '234', '235', '724'],
+ _mccWith011Prefix: ['310', '311', '312', '313', '314', '315', '316'],
+
+ // https://en.wikipedia.org/wiki/Country_calling_code
+ // https://en.wikipedia.org/wiki/Trunk_code
+ // This is an array of objects composed by { p: <prefix>, t: <true if it can be trunk> }
+ _countryPrefixes: [
+ // North American Numbering Plan countries and territories
+ // US, CA, AG, AI, AS, BB, BM, BS, DM, DO, GD, GU, JM, KN, KY,
+ // LC, MP, MS, PR, SX, TC, TT, VC, VG, VI
+ {p: '1', t: false},
+
+ // BS BB AI
+ {p: '1242', t: false}, {p: '1246', t: false}, {p: '1264', t: false},
+ // AG VG VI KY BM
+ {p: '1268', t: false}, {p: '1284', t: false}, {p: '1340', t: false}, {p: '1345', t: false}, {p: '1441', t: false},
+ // GD TC MS MP GU
+ {p: '1473', t: false}, {p: '1649', t: false}, {p: '1664', t: false}, {p: '1670', t: false}, {p: '1671', t: false},
+ // AS SX LC DM VC
+ {p: '1684', t: false}, {p: '1721', t: false}, {p: '1758', t: false}, {p: '1767', t: false}, {p: '1784', t: false},
+ // PR DO DO DO TT
+ {p: '1787', t: false}, {p: '1809', t: false}, {p: '1829', t: false}, {p: '1849', t: false}, {p: '1868', t: false},
+ // KN JM PR
+ {p: '1869', t: false}, {p: '1876', t: false}, {p: '1939', t: false},
+
+ // EG -- SS
+ {p: '20', t: false}, {p: '210', t: false}, {p: '211', t: false},
+ // MA,EH DZ -- -- TN
+ {p: '212', t: false}, {p: '213', t: false}, {p: '214', t: false}, {p: '215', t: false}, {p: '216', t: false},
+ // -- LY -- GM SN
+ {p: '217', t: false}, {p: '218', t: false}, {p: '219', t: false}, {p: '220', t: false}, {p: '221', t: false},
+ // MR ML GN CI BF
+ {p: '222', t: false}, {p: '223', t: false}, {p: '224', t: false}, {p: '225', t: false}, {p: '226', t: false},
+ // NE TG BJ MU LR
+ {p: '227', t: false}, {p: '228', t: false}, {p: '229', t: false}, {p: '230', t: false}, {p: '231', t: false},
+ // SL GH NG TD CF
+ {p: '232', t: false}, {p: '233', t: false}, {p: '234', t: false}, {p: '235', t: false}, {p: '236', t: false},
+ // CM CV ST GQ GA
+ {p: '237', t: false}, {p: '238', t: false}, {p: '239', t: false}, {p: '240', t: false}, {p: '241', t: false},
+ // CG CD AO GW IO
+ {p: '242', t: false}, {p: '243', t: false}, {p: '244', t: false}, {p: '245', t: false}, {p: '246', t: false},
+ // AC SC SD RW ET
+ {p: '247', t: false}, {p: '248', t: false}, {p: '249', t: false}, {p: '250', t: false}, {p: '251', t: false},
+ // SO,QS DJ KE TZ UG
+ {p: '252', t: false}, {p: '253', t: false}, {p: '254', t: false}, {p: '255', t: false}, {p: '256', t: false},
+ // BI MZ -- ZM MG
+ {p: '257', t: false}, {p: '258', t: false}, {p: '259', t: false}, {p: '260', t: false}, {p: '261', t: false},
+ // RE,YT ZW NA MW LS
+ {p: '262', t: false}, {p: '263', t: false}, {p: '264', t: false}, {p: '265', t: false}, {p: '266', t: false},
+ // BW SZ KM ZA --
+ {p: '267', t: false}, {p: '268', t: false}, {p: '269', t: false}, {p: '27', t: false}, {p: '28', t: false},
+ // SH,TA ER -- -- --
+ {p: '290', t: false}, {p: '291', t: false}, {p: '292', t: false}, {p: '293', t: false}, {p: '294', t: false},
+ // -- -- AW FO GL
+ {p: '295', t: false}, {p: '296', t: false}, {p: '297', t: false}, {p: '298', t: false}, {p: '299', t: false},
+
+ // GR NL BE
+ {p: '30', t: false}, {p: '31', t: true}, {p: '32', t: true},
+ // FR ES GI PT LU
+ {p: '33', t: true}, {p: '34', t: false}, {p: '350', t: false}, {p: '351', t: true}, {p: '352', t: false},
+ // IE IS AL MT CY
+ {p: '353', t: false}, {p: '354', t: false}, {p: '355', t: true}, {p: '356', t: false}, {p: '357', t: false},
+ // FI,AX BG HU LT LV
+ {p: '358', t: false}, {p: '359', t: true}, {p: '36', t: true}, {p: '370', t: true}, {p: '371', t: false},
+ // EE MD AM,QN BY AD
+ {p: '372', t: false}, {p: '373', t: true}, {p: '374', t: false}, {p: '375', t: true}, {p: '376', t: false},
+ // MC SM VA UA RS
+ {p: '377', t: false}, {p: '378', t: false}, {p: '379', t: false}, {p: '380', t: true}, {p: '381', t: true},
+ // ME -- -- HR SI
+ {p: '382', t: true}, {p: '383', t: false}, {p: '384', t: false}, {p: '385', t: true}, {p: '386', t: false},
+ // BA EU MK IT,VA
+ {p: '387', t: true}, {p: '388', t: false}, {p: '389', t: true}, {p: '39', t: false},
+
+ // RO CH CZ
+ {p: '40', t: true}, {p: '41', t: true}, {p: '420', t: false},
+ // SK -- LI -- --
+ {p: '421', t: false}, {p: '422', t: false}, {p: '423', t: false}, {p: '424', t: false}, {p: '425', t: false},
+ // -- -- -- -- AT
+ {p: '426', t: false}, {p: '427', t: false}, {p: '428', t: false}, {p: '429', t: false}, {p: '43', t: true},
+ // GB/UK,GG,IM,JE DK SE NO,SJ PL
+ {p: '44', t: true}, {p: '45', t: false}, {p: '46', t: true}, {p: '47', t: false}, {p: '48', t: true},
+ // DE
+ {p: '49', t: true},
+
+ // FK BZ GT
+ {p: '500', t: false}, {p: '501', t: false}, {p: '502', t: false},
+ // SV HN NI CR PA
+ {p: '503', t: false}, {p: '504', t: false}, {p: '505', t: false}, {p: '506', t: false}, {p: '507', t: false},
+ // PM HT PE MX CU
+ {p: '508', t: false}, {p: '509', t: false}, {p: '51', t: true}, {p: '52', t: false}, {p: '53', t: false},
+ // AR BR CL CO VE
+ {p: '54', t: true}, {p: '55', t: true}, {p: '56', t: false}, {p: '57', t: false}, {p: '58', t: false},
+ // GP,BL,MF BO GY EC GF
+ {p: '590', t: false}, {p: '591', t: true}, {p: '592', t: false}, {p: '593', t: false}, {p: '594', t: false},
+ // PY MQ SR UY BQ,CW
+ {p: '595', t: false}, {p: '596', t: false}, {p: '597', t: false}, {p: '598', t: false}, {p: '599', t: true},
+
+ // MY AU,CX,CC ID
+ {p: '60', t: true}, {p: '61', t: true}, {p: '62', t: true},
+ // PH NZ SG TH TL
+ {p: '63', t: true}, {p: '64', t: false}, {p: '65', t: true}, {p: '66', t: true}, {p: '670', t: false},
+ // -- NF,AQ BN NR PG
+ {p: '671', t: false}, {p: '672', t: false}, {p: '673', t: true}, {p:'674', t: false}, {p: '675', t: false},
+ // TO SB VU FJ PW
+ {p: '676', t: false}, {p: '677', t: false}, {p: '678', t: false}, {p:'679', t: false}, {p: '680', t: false},
+ // WF CK NU -- WS
+ {p: '681', t: false}, {p: '682', t: false}, {p: '683', t: false}, {p:'684', t: false}, {p: '685', t: false},
+ // KI NC TV PF TK
+ {p: '686', t: false}, {p: '687', t: false}, {p: '688', t: false}, {p:'689', t: false}, {p: '690', t: false},
+ // FM MH -- -- --
+ {p: '691', t: false}, {p: '692', t: false}, {p: '693', t: false}, {p: '694', t: false}, {p: '695', t: false},
+ // -- -- -- -- --
+ {p: '696', t: false}, {p: '697', t: false}, {p: '698', t: false}, {p: '699', t: false}, {p: '699', t: false},
+
+ // RU,KZ
+ {p: '7', t: true},
+
+ // KZ KZ Abkhazia Abkhazia
+ {p: '76', t: true}, {p: '77', t: true}, {p: '7840', t: false}, {p: '7940', t: false},
+
+ // XT -- --
+ {p: '800', t: false}, {p: '801', t: false}, {p: '802', t: false},
+ // -- -- -- -- --
+ {p: '803', t: false}, {p: '804', t: false}, {p: '805', t: false}, {p: '806', t: false}, {p: '807', t: false},
+ // XS -- JP KR --
+ {p: '808', t: false}, {p: '809', t: false}, {p: '81', t: false}, {p: '82', t: true}, {p: '83', t: false},
+ // VN KP -- HK MO
+ {p: '84', t: true}, {p: '850', t: true}, {p: '851', t: false}, {p: '852', t: false}, {p: '853', t: false},
+ // -- KH LA -- --
+ {p: '854', t: false}, {p: '855', t: true}, {p: '856', t: true}, {p: '857', t: false}, {p: '858', t: false},
+ // -- CN XN -- --
+ {p: '859', t: false}, {p: '86', t: true}, {p: '870', t: false}, {p: '871', t: false}, {p: '872', t: false},
+ // -- -- -- -- --
+ {p: '873', t: false}, {p: '874', t: false}, {p: '875', t: false}, {p: '876', t: false}, {p: '877', t: false},
+ // XP -- BD XG XV
+ {p: '878', t: false}, {p: '879', t: false}, {p: '880', t: true}, {p: '881', t: false}, {p: '882', t: false},
+ // XV -- -- TW --
+ {p: '883', t: false}, {p: '884', t: false}, {p: '885', t: false}, {p: '886', t: true}, {p: '887', t: false},
+ // XD -- --
+ {p: '888', t: false}, {p: '889', t: false}, {p: '89', t: false},
+
+ // TR,QY IN PK
+ {p: '90', t: false}, {p: '91', t: true}, {p: '92', t: true},
+ // AF LK MM MV LB
+ {p: '93', t: true}, {p: '94', t: false}, {p: '95', t: true}, {p: '960', t: false}, {p: '961', t: false},
+ // JO SY IQ KW SA
+ {p: '962', t: false}, {p: '963', t: false}, {p: '964', t: false}, {p: '965', t: false}, {p: '966', t: false},
+ // YE OM -- PS AE
+ {p: '967', t: false}, {p: '968', t: false}, {p: '969', t: false}, {p: '970', t: false}, {p: '971', t: false},
+ // IL BH QA BT MN
+ {p: '972', t: false}, {p: '973', t: false}, {p: '974', t: false}, {p: '975', t: true}, {p: '976', t: true},
+ // NP -- XR IR --
+ {p: '977', t: true}, {p: '978', t: false}, {p: '979', t: false}, {p: '98', t: false}, {p: '990', t: false},
+ // XC TJ TM AZ GE
+ {p: '991', t: false}, {p: '992', t: false}, {p: '993', t: true}, {p: '994', t: true}, {p: '995', t: true},
+ // KG -- UZ --
+ {p: '996', t: false}, {p: '997', t: false}, {p: '998', t: true}, {p: '999', t: false}
+ ],
+ _trunkCodes: ['0'],
+
+ // https://en.wikipedia.org/wiki/List_of_dialling_codes_in_Brazil
+ // https://en.wikipedia.org/wiki/Telephone_numbers_in_the_United_Kingdom
+ // https://en.wikipedia.org/wiki/Telephone_numbering_plan
+ // country code -> length of the area code
+ _areaCodeSwipe: {
+ '55': 2,
+ '44': 3,
+ '1': 3
+ },
+
+ _internationalPrefixes: function spm_internatialPrefixes(number) {
+ var variants = [number];
+
+ var internationalPrefix = '';
+ if (this._mccWith00Prefix.indexOf(this.mcc) !== -1) {
+ internationalPrefix = '00';
+ }
+ if (this._mccWith011Prefix.indexOf(this.mcc) !== -1) {
+ internationalPrefix = '011';
+ }
+
+ var plusRegexp = new RegExp('^\\+');
+ if (number.match(plusRegexp)) {
+ variants.push(number.replace(plusRegexp, internationalPrefix));
+ }
+
+ var ipRegexp = new RegExp('^' + internationalPrefix);
+ if (number.match(ipRegexp)) {
+ variants.push(number.replace(ipRegexp, '+'));
+ }
+
+ return variants;
+ },
+
+ _trunkPrefixes: function spm_trunkPrefixes(number) {
+ var variants = [];
+
+ var prefixesWithTrunk0 = [];
+ var prefixesWithNoTrunk = [];
+ this._countryPrefixes.forEach(function(prefix) {
+ if (prefix.t) {
+ prefixesWithTrunk0.push(prefix.p);
+ } else {
+ prefixesWithNoTrunk.push(prefix.p);
+ }
+ });
+
+ var trunk0Join = prefixesWithTrunk0.join('|')
+ var trunk0Regexp = new RegExp('^\\+(' + trunk0Join + ')');
+ this._internationalPrefixes(number).some(function match(variant) {
+ var match = variant.match(trunk0Regexp);
+
+ if (match) {
+ variants.push(variant.replace(trunk0Regexp, '0'));
+ variants.push(variant.replace(trunk0Regexp, ''));
+ }
+
+ return match;
+ });
+
+ var noTrunkJoin = prefixesWithNoTrunk.join('|');
+ var noTrunkRegexp = new RegExp('^\\+(' + noTrunkJoin + ')');
+ this._internationalPrefixes(number).some(function match(variant) {
+ var match = variant.match(noTrunkRegexp);
+
+ if (match) {
+ variants.push(variant.replace(noTrunkRegexp, ''));
+ }
+
+ return match;
+ });
+
+ // If the number has a trunk prefix already we need a variant without it
+ var withTrunkRegexp = new RegExp('^(' + this._trunkCodes.join('|') + ')');
+ if (number.match(withTrunkRegexp)) {
+ variants.push(number.replace(withTrunkRegexp, ''));
+ }
+
+ return variants;
+ },
+
+ _areaPrefixes: function spm_areaPrefixes(number) {
+ var variants = [];
+
+ Object.keys(this._areaCodeSwipe).forEach(function(country) {
+ var re = new RegExp('^\\+' + country);
+
+ this._internationalPrefixes(number).some(function match(variant) {
+ var match = variant.match(re);
+
+ if (match) {
+ var afterArea = 1 + country.length + this._areaCodeSwipe[country];
+ variants.push(variant.substring(afterArea));
+ }
+
+ return match;
+ }, this);
+ }, this);
+
+ return variants;
+ },
+
+ // http://thebrazilbusiness.com/article/telephone-system-in-brazil
+ _carrierPrefixes: function spm_carrierPrefix(number) {
+ if (this.mcc != '724') {
+ return [];
+ }
+
+ var variants = [];
+ var withTrunk = new RegExp('^0');
+
+ // A number with carrier prefix will have a trunk code and at
+ // lest 13 digits
+ if (number.length >= 13 && number.match(withTrunk)) {
+ var afterCarrier = 3;
+ variants.push(number.substring(afterCarrier));
+ }
+
+ return variants;
+ }
+};
+
diff --git a/shared/js/tz_select.js b/shared/js/tz_select.js
new file mode 100644
index 0000000..720df43
--- /dev/null
+++ b/shared/js/tz_select.js
@@ -0,0 +1,155 @@
+/* -*- Mode: js; js-indent-level: 2; indent-tabs-mode: nil -*- */
+/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
+
+'use strict';
+
+function tzSelect(regionSelector, citySelector, onchange) {
+ var TIMEZONE_FILE = '/shared/resources/tz.json';
+
+
+ /**
+ * Activate a timezone selector UI
+ */
+
+ function newTZSelector(onchangeTZ, currentID) {
+ var gRegion = currentID.replace(/\/.*/, '');
+ var gCity = currentID.replace(/.*?\//, '');
+ var gTZ = null;
+
+ function loadTZ(callback) {
+ var xhr = new XMLHttpRequest();
+ xhr.open('GET', TIMEZONE_FILE, true);
+ xhr.responseType = 'json';
+ xhr.onreadystatechange = function() {
+ if (xhr.readyState == 4) {
+ if (xhr.status == 200 || xhr.status === 0) {
+ gTZ = xhr.response;
+ }
+ callback();
+ }
+ };
+ xhr.send();
+ }
+
+ function fillSelectElement(selector, options) {
+ selector.innerHTML = '';
+ options.sort(function(a, b) {
+ return (a.text > b.text);
+ });
+ for (var i = 0; i < options.length; i++) {
+ var option = document.createElement('option');
+ option.textContent = options[i].text;
+ option.selected = options[i].selected;
+ option.value = options[i].value;
+ selector.appendChild(option);
+ }
+ }
+
+ function getSelectedText(selector) {
+ var options = selector.querySelectorAll('option');
+ return options[selector.selectedIndex].textContent;
+ }
+
+ function fillRegions() {
+ var _ = navigator.mozL10n.get;
+ var options = [];
+ for (var c in gTZ) {
+ options.push({
+ text: _('tzRegion-' + c) || c,
+ value: c,
+ selected: (c == gRegion)
+ });
+ }
+ fillSelectElement(regionSelector, options);
+ fillCities();
+ }
+
+ function fillCities() {
+ gRegion = regionSelector.value;
+ var list = gTZ[gRegion];
+ var options = [];
+ for (var i = 0; i < list.length; i++) {
+ options.push({
+ text: list[i].name || list[i].city.replace(/_/g, ' '),
+ value: i,
+ selected: (list[i].city == gCity)
+ });
+ }
+ fillSelectElement(citySelector, options);
+ setTimezone();
+ }
+
+ function setTimezone() {
+ var res = gTZ[gRegion][citySelector.value];
+ gCity = res.city;
+ var offset = res.offset.split(',');
+ onchangeTZ({
+ id: res.id || gRegion + '/' + res.city,
+ region: getSelectedText(regionSelector),
+ city: getSelectedText(citySelector),
+ cc: res.cc,
+ utcOffset: offset[0],
+ dstOffset: offset[1]
+ });
+ }
+
+ regionSelector.onchange = fillCities;
+ citySelector.onchange = setTimezone;
+ loadTZ(fillRegions);
+ }
+
+
+ /**
+ * Monitor time.timezone changes
+ */
+
+ function newTZObserver() {
+ var settings = window.navigator.mozSettings;
+ if (!settings)
+ return;
+
+ settings.addObserver('time.timezone', function(event) {
+ setTimezoneDescription(event.settingValue);
+ });
+
+ var reqTimezone = settings.createLock().get('time.timezone');
+ reqTimezone.onsuccess = function dt_getStatusSuccess() {
+ var lastMozSettingValue = reqTimezone.result['time.timezone'];
+ if (!lastMozSettingValue) {
+ lastMozSettingValue = 'Pacific/Pago_Pago';
+ }
+
+ setTimezoneDescription(lastMozSettingValue);
+
+ // initialize the timezone selector with the initial TZ setting
+ newTZSelector(function updateTZ(tz) {
+ var req = settings.createLock().set({ 'time.timezone': tz.id });
+ if (onchange) {
+ req.onsuccess = function updateTZ_callback() {
+ // Wait until the timezone is actually set
+ // before calling the callback.
+ window.addEventListener('moztimechange', function timeChanged() {
+ window.removeEventListener('moztimechange', timeChanged);
+ onchange(tz);
+ });
+ }
+ }
+ }, lastMozSettingValue);
+
+ console.log('Initial TZ value: ' + lastMozSettingValue);
+ };
+
+ function setTimezoneDescription(timezoneID) {
+ regionSelector.value = timezoneID.replace(/\/.*/, '');
+ citySelector.value = timezoneID.replace(/.*?\//, '');
+ }
+ }
+
+
+ /**
+ * Startup -- make sure webL10n is ready before using tzSelect()
+ */
+
+ newTZObserver();
+}
+
diff --git a/shared/js/visibility_monitor.js b/shared/js/visibility_monitor.js
new file mode 100644
index 0000000..5a8ba1f
--- /dev/null
+++ b/shared/js/visibility_monitor.js
@@ -0,0 +1,494 @@
+/*
+ * visibility_monitor.js
+ *
+ * Given a scrolling container element (with overflow-y: scroll set,
+ * e.g.), monitorChildVisibility() listens for scroll events in order to
+ * determine which child elements are visible within the element and
+ * which are not (assuming that the element itself is visible).
+ *
+ * When a child scrolls onscreen, it is passed to the onscreen callback.
+ *
+ * When a child scrolls offscreen, it is passed to the offscreen callback.
+ *
+ * This class also listens for DOM modification events so that it can handle
+ * children being added to or removed from the scrolling element. It also
+ * handles resize events.
+ *
+ * Note that this class only pays attention to the direct children of
+ * the container element, not all ancestors.
+ *
+ * When you insert a new child into the container, you should create it in
+ * its offscreen state. If it is inserted offscreen nothing will happen.
+ * If you insert it onscreen, it will immediately be passed to the onscreen
+ * callback function
+ *
+ * The scrollmargin argument specifies a number of pixels. Elements
+ * that are within this many pixels of being onscreen are considered
+ * onscreen.
+ *
+ * By specifing proper onscreen and offscreen functions you can use this
+ * class to (for example) remove the background-image style of elements
+ * that are not visible, allowing gecko to free up image memory.
+ * In that sense, this class can be used to workaround
+ * https://bugzilla.mozilla.org/show_bug.cgi?id=689623
+ *
+ * The return value of this function is an object that has a stop() method.
+ * calling the stop method stops visiblity monitoring. If you want to restart
+ * call monitorChildVisiblity() again.
+ *
+ * monitorChildVisiblity() makes the following assumptions. If your program
+ * violates them, the function may not work correctly:
+ *
+ * Child elements of the container element flow left to right and
+ * top to bottom. I.e. the nextSibling of a child element never has a
+ * smaller clientTop value. They are not absolutely positioned and don't
+ * move on their own.
+ *
+ * The children of the container element are themselves all elements;
+ * there are no text nodes or comments cluttering things up.
+ *
+ * Children don't change size, either spontaneously or in response to
+ * onscreen and offscreen callbacks. Don't set display:none on an element
+ * when it goes offscreen, for example.
+ *
+ * Children aren't added or removed to the container while the container
+ * or any of its ancestors is hidden with display:none or is removed from
+ * the tree. The mutation observer that responds to additions and deletions
+ * needs the container and its children to have valid layout data in order
+ * to figure out what is onscreen and what is offscreen. Use visiblity:hidden
+ * instead of display:none if you need to add or remove children while
+ * the container is hidden.
+ *
+ * DocumentFragments are not used to add multiple children at once to
+ * the container, and multiple children are not deleted at once by
+ * setting innerHTML or innerText to ''.
+ *
+ * The container element only changes size when there is a resize event
+ * on the window.
+ */
+'use strict';
+
+function monitorChildVisibility(container, scrollmargin,
+ onscreenCallback, offscreenCallback)
+{
+ // The onscreen region is represented by these two elements
+ var firstOnscreen = null, lastOnscreen = null;
+
+ // This is the last onscreen region that we have notified the client about
+ var firstNotifiedOnscreen = null, lastNotifiedOnscreen = null;
+
+ // The timer used by deferCallbacks()
+ var pendingCallbacks = null;
+
+ // Update the onscreen region whenever we scroll
+ container.addEventListener('scroll', scrollHandler);
+
+ // Update the onscreen region when the window changes size
+ window.addEventListener('resize', resizeHandler);
+
+ // Update the onscreen region when children are added or removed
+ var observer = new MutationObserver(mutationHandler);
+ observer.observe(container, { childList: true });
+
+ // Now determine the initial onscreen region
+ adjustBounds();
+
+ // Call the onscreenCallback for the initial onscreen elements
+ callCallbacks();
+
+ // Return an object that allows the caller to stop monitoring
+ return {
+ stop: function stop() {
+ // Unregister our event handlers and stop the mutation observer.
+ container.removeEventListener('scroll', scrollHandler);
+ window.removeEventListener('resize', resizeHandler);
+ observer.disconnect();
+ }
+ };
+
+ // Adjust the onscreen element range and synchronously call onscreen
+ // and offscreen callbacks as needed.
+ function resizeHandler() {
+ // If we are triggered with 0 height, ignore the event. If this happens
+ // we don't have any layout data and we'll end up thinking that all
+ // of the children are onscreen. Better to do nothing at all here and
+ // just wait until the container becomes visible again.
+ if (container.clientHeight === 0) {
+ return;
+ }
+ adjustBounds();
+ callCallbacks();
+ }
+
+ // This gets called when children are added or removed from the container.
+ // Adding and removing nodes can change the position of other elements
+ // so changes may extend beyond just the ones added or removed
+ function mutationHandler(mutations) {
+ // Ignore any mutations while we are not displayed because
+ // none of our calculations will be right
+ if (container.clientHeight === 0) {
+ return;
+ }
+
+ // If there are any pending callbacks, call them now before handling
+ // the mutations so that we start off in sync, with the onscreen range
+ // equal to the notified range.
+ if (pendingCallbacks)
+ callCallbacks();
+
+ for (var i = 0; i < mutations.length; i++) {
+ var mutation = mutations[i];
+ if (mutation.addedNodes) {
+ for (var j = 0; j < mutation.addedNodes.length; j++) {
+ var child = mutation.addedNodes[j];
+ if (child.nodeType === Node.ELEMENT_NODE)
+ childAdded(child);
+ }
+ }
+
+ if (mutation.removedNodes) {
+ for (var j = 0; j < mutation.removedNodes.length; j++) {
+ var child = mutation.removedNodes[j];
+ if (child.nodeType === Node.ELEMENT_NODE)
+ childRemoved(child,
+ mutation.previousSibling,
+ mutation.nextSibling);
+ }
+ }
+ }
+ }
+
+ // If the new child is onscreen, call the onscreen callback for it.
+ // Adjust the onscreen element range and synchronously call
+ // onscreen and offscreen callbacks as needed.
+ function childAdded(child) {
+ // If the added child is after the last onscreen child, and we're
+ // not filling in the first page of content then this insertion
+ // doesn't affect us at all.
+ if (lastOnscreen &&
+ after(child, lastOnscreen) &&
+ child.offsetTop > container.clientHeight + scrollmargin)
+ return;
+
+ // Otherwise, if this is the first element added or if it is after
+ // the first onscreen element, then it is onscreen and we need to
+ // call the onscreen callback for it.
+ if (!firstOnscreen || after(child, firstOnscreen)) {
+ // Invoke the onscreen callback for this child
+ try {
+ onscreenCallback(child);
+ }
+ catch (e) {
+ console.warn('monitorChildVisiblity: Exception in onscreenCallback:',
+ e, e.stack);
+ }
+ }
+
+ // Now adjust the first and last onscreen element and
+ // send a synchronous notification
+ adjustBounds();
+ callCallbacks();
+ }
+
+ // If the removed element was after the last onscreen element just return.
+ // Otherwise adjust the onscreen element range and synchronously call
+ // onscreen and offscreen callbacks as needed. Note, however that there
+ // are some special cases when the last element is deleted or when the
+ // first or last onscreen element is deleted.
+ function childRemoved(child, previous, next) {
+ // If there aren't any elements left revert back to initial state
+ if (container.firstElementChild === null) {
+ firstOnscreen = lastOnscreen = null;
+ firstNotifiedOnscreen = lastNotifiedOnscreen = null;
+ }
+ else {
+ // If the removed child was after the last onscreen child, then
+ // this removal doesn't affect us at all.
+ if (previous !== null && after(previous, lastOnscreen))
+ return;
+
+ // If the first onscreen element was the one removed
+ // use the next or previous element as a starting point instead.
+ // We know that there is at least one element left, so one of these
+ // two must be defined.
+ if (child === firstOnscreen) {
+ firstOnscreen = firstNotifiedOnscreen = next || previous;
+ }
+
+ // And similarly for the last onscreen element
+ if (child === lastOnscreen) {
+ lastOnscreen = lastNotifiedOnscreen = previous || next;
+ }
+
+ // Find the new bounds after the deletion
+ adjustBounds();
+ }
+
+ // Synchronously call the callbacks
+ callCallbacks();
+ }
+
+ // Adjust the onscreen element range and asynchronously call
+ // onscreen and offscreen callbacks as needed. We do this
+ // asynchronously so that if we get lots of scroll events in
+ // rapid succession and can't keep up, we can skip some of
+ // the notifications.
+ function scrollHandler() {
+ // Ignore scrolls while we are not displayed because
+ // none of our calculations will be right
+ if (container.clientHeight === 0) {
+ return;
+ }
+
+ // Adjust the first and last onscreen element
+ adjustBounds();
+
+ // We may get a lot of scroll events in quick succession, so
+ // don't call the callbacks synchronously. Instead defer so that
+ // we can handle any other queued scroll events.
+ deferCallbacks();
+ }
+
+ // Return true if node a is before node b and false otherwise
+ function before(a, b) {
+ return !!(a.compareDocumentPosition(b) & Node.DOCUMENT_POSITION_FOLLOWING);
+ }
+
+ // Return true if node a is after node b and false otherwise
+ function after(a, b) {
+ return !!(a.compareDocumentPosition(b) & Node.DOCUMENT_POSITION_PRECEDING);
+ }
+
+ // This function recomputes the range of onscreen elements. Normally it
+ // just needs to do small amounts of nextElementSibling
+ // or previousElementSibling iteration to find the range. But it can also
+ // start from an unknown state and search the entire container to find
+ // the range of child elements that are onscreen.
+ function adjustBounds() {
+ // If the container has no children, the bounds are null
+ if (container.firstElementChild === null) {
+ firstOnscreen = lastOnscreen = null;
+ return;
+ }
+
+ // Compute the visible region of the screen, including scroll margin
+ var scrollTop = container.scrollTop;
+ var screenTop = scrollTop - scrollmargin;
+ var screenBottom = scrollTop + container.clientHeight + scrollmargin;
+
+ // This utility function returns ON if the child is onscreen,
+ // BEFORE if it offscreen before the visible elements and AFTER if
+ // it is offscreen aafter the visible elements
+ var BEFORE = -1, ON = 0, AFTER = 1;
+ function position(child) {
+ var childTop = child.offsetTop;
+ var childBottom = childTop + child.offsetHeight;
+ if (childBottom < screenTop)
+ return BEFORE;
+ if (childTop > screenBottom)
+ return AFTER;
+ return ON;
+ }
+
+ // If we don't have a first onscreen element yet, start with the first.
+ if (!firstOnscreen)
+ firstOnscreen = container.firstElementChild;
+
+ // Check the position of the top
+ var toppos = position(firstOnscreen);
+
+ // If the first element is onscreen, see if there are earlier ones
+ if (toppos === ON) {
+ var prev = firstOnscreen.previousElementSibling;
+ while (prev && position(prev) === ON) {
+ firstOnscreen = prev;
+ prev = prev.previousElementSibling;
+ }
+ }
+ else if (toppos === BEFORE) {
+ // The screen is below us, so find the next element that is visible.
+ var e = firstOnscreen.nextElementSibling;
+ while (e && position(e) !== ON) {
+ e = e.nextElementSibling;
+ }
+ firstOnscreen = e;
+ }
+ else {
+ // We've scrolled a lot or things have moved so much that the
+ // entire visible region is now above the first element.
+ // So scan backwards to find the new lastOnscreen and firstOnscreen
+ // elements. Note that if we get here, we can return since we
+ // will have updated both bounds
+
+ // Loop until we find an onscreen element
+ lastOnscreen = firstOnscreen.previousElementSibling;
+ while (lastOnscreen && position(lastOnscreen) !== ON)
+ lastOnscreen = lastOnscreen.previousElementSibling;
+
+ // Now loop from there to find the first onscreen element
+ firstOnscreen = lastOnscreen;
+ prev = firstOnscreen.previousElementSibling;
+ while (prev && position(prev) === ON) {
+ firstOnscreen = prev;
+ prev = prev.previousElementSibling;
+ }
+ return;
+ }
+
+ // Now make the same adjustment on the bottom of the onscreen region
+ // If we don't have a lastOnscreen value to start with, use the newly
+ // computed firstOnscreen value.
+ if (lastOnscreen === null)
+ lastOnscreen = firstOnscreen;
+
+ var bottompos = position(lastOnscreen);
+ if (bottompos === ON) {
+ // If the last element is onscreen, see if there are more below it.
+ var next = lastOnscreen.nextElementSibling;
+ while (next && position(next) === ON) {
+ lastOnscreen = next;
+ next = next.nextElementSibling;
+ }
+ }
+ else if (bottompos === AFTER) {
+ // the last element is now below the visible part of the screen
+ lastOnscreen = lastOnscreen.previousElementSibling;
+ while (position(lastOnscreen) !== ON)
+ lastOnscreen = lastOnscreen.previousElementSibling;
+ }
+ else {
+ // First and last are now both above the visible portion of the screen
+ // So loop down to find their new positions
+ firstOnscreen = lastOnscreen.nextElementSibling;
+ while (firstOnscreen && position(firstOnscreen) !== ON) {
+ firstOnscreen = firstOnscreen.nextElementSibling;
+ }
+
+ lastOnscreen = firstOnscreen;
+ var next = lastOnscreen.nextElementSibling;
+ while (next && position(next) === ON) {
+ lastOnscreen = next;
+ next = next.nextElementSibling;
+ }
+ }
+ }
+
+ // Call the callCallbacks() function after any pending events are processed
+ // We use this for asynchronous notification after scroll events.
+ function deferCallbacks() {
+ if (pendingCallbacks) {
+ // XXX: or we could just return here, which would defer for less time.
+ clearTimeout(pendingCallbacks);
+ }
+ pendingCallbacks = setTimeout(callCallbacks, 0);
+ }
+
+ // Synchronously call the callbacks to notify the client of the new set
+ // of onscreen elements. This only calls the onscreen and offscreen
+ // callbacks for elements that have come onscreen or gone offscreen since
+ // the last time it was called.
+ function callCallbacks() {
+ // If there is a pending call to this function (or if this was the pending
+ // call) clear it now, since we are sending the callbacks
+ if (pendingCallbacks) {
+ clearTimeout(pendingCallbacks);
+ pendingCallbacks = null;
+ }
+
+ // Call the onscreen callback for element from and its siblings
+ // up to, but not including to.
+ function onscreen(from, to) {
+ var e = from;
+ while (e && e !== to) {
+ try {
+ onscreenCallback(e);
+ }
+ catch (ex) {
+ console.warn('monitorChildVisibility: Exception in onscreenCallback:',
+ ex, ex.stack);
+ }
+ e = e.nextElementSibling;
+ }
+ }
+
+ // Call the offscreen callback for element from and its siblings
+ // up to, but not including to.
+ function offscreen(from, to) {
+ var e = from;
+ while (e && e !== to) {
+ try {
+ offscreenCallback(e);
+ }
+ catch (ex) {
+ console.warn('monitorChildVisibility: ' +
+ 'Exception in offscreenCallback:',
+ ex, ex.stack);
+ }
+ e = e.nextElementSibling;
+ }
+ }
+
+ // If the two ranges are the same, return immediately
+ if (firstOnscreen === firstNotifiedOnscreen &&
+ lastOnscreen === lastNotifiedOnscreen)
+ return;
+
+ // If the last notified range is null, then we just add the new range
+ if (firstNotifiedOnscreen === null) {
+ onscreen(firstOnscreen, lastOnscreen.nextElementSibling);
+ }
+
+ // If the new range is null, this means elements have been removed.
+ // We don't need to call offscreen for elements that are not in the
+ // container anymore, so we don't do anything in this case
+ else if (firstOnscreen === null) {
+ // Nothing to do here
+ }
+
+ // If the new range and the old range are disjoint, call the onscreen
+ // callback for the new range first and then call the offscreen callback
+ // for the old.
+ else if (before(lastOnscreen, firstNotifiedOnscreen) ||
+ after(firstOnscreen, lastNotifiedOnscreen)) {
+ // Mark the new ones onscreen
+ onscreen(firstOnscreen, lastOnscreen.nextElementSibling);
+
+ // Mark the old range offscreen
+ offscreen(firstNotifiedOnscreen,
+ lastNotifiedOnscreen.nextElementSibling);
+ }
+
+ // Otherwise if new elements are visible at the top, call those callbacks
+ // If new elements are visible at the bottom, call those.
+ // If elements have gone offscreen at the top, call those callbacks
+ // If elements have gone offscreen at the bottom, call those.
+ else {
+ // Are there new onscreen elements at the top?
+ if (before(firstOnscreen, firstNotifiedOnscreen)) {
+ onscreen(firstOnscreen, firstNotifiedOnscreen);
+ }
+
+ // Are there new onscreen elements at the bottom?
+ if (after(lastOnscreen, lastNotifiedOnscreen)) {
+ onscreen(lastNotifiedOnscreen.nextElementSibling,
+ lastOnscreen.nextElementSibling);
+ }
+
+ // Have elements gone offscreen at the top?
+ if (after(firstOnscreen, firstNotifiedOnscreen)) {
+ offscreen(firstNotifiedOnscreen, firstOnscreen);
+ }
+
+ // Have elements gone offscreen at the bottom?
+ if (before(lastOnscreen, lastNotifiedOnscreen)) {
+ offscreen(lastOnscreen.nextElementSibling,
+ lastNotifiedOnscreen.nextElementSibling);
+ }
+ }
+
+ // Now the notified onscreen range is in sync with the actual
+ // onscreen range.
+ firstNotifiedOnscreen = firstOnscreen;
+ lastNotifiedOnscreen = lastOnscreen;
+ }
+}
diff --git a/shared/resources/apn.json b/shared/resources/apn.json
new file mode 100644
index 0000000..023c61f
--- /dev/null
+++ b/shared/resources/apn.json
@@ -0,0 +1,3175 @@
+{
+"202": {
+ "1": [
+ {"carrier":"Cosmote Wireless Internet","apn":"internet","type":["default","supl"]},
+ {"voicemail":"123","type":["operatorvariant"]},
+ {"carrier":"Cosmote Mms","apn":"mms","mmsc":"http://mmsc.cosmote.gr:8002","mmsproxy":"10.10.10.20","mmsport":"8080","type":["mms"]}
+ ],
+ "5": [
+ {"carrier":"Vf Mobile Internet","apn":"internet.vodafone.gr","type":["default","supl","dun"]},
+ {"voicemail":"121","type":["operatorvariant"]},
+ {"carrier":"Vf MMS","apn":"mms.vodafone.net","user":"user","password":"pass","mmsc":"http://mms.vodafone.gr","mmsproxy":"213.249.19.49","mmsport":"5080","type":["mms"]}
+ ],
+ "9": [
+ {"carrier":"Q-Telecom MMS GPRS","apn":"q-mms.myq.gr","mmsc":"http://mms.myq.gr","mmsproxy":"192.168.80.134","mmsport":"8080","type":["mms"]},
+ {"voicemail":"122","type":["operatorvariant"]}
+ ],
+ "10": [
+ {"carrier":"Wind Internet","apn":"gint.b-online.gr","type":["default","supl"]},
+ {"voicemail":"122","type":["operatorvariant"]},
+ {"carrier":"Wind MMS","apn":"mnet.b-online.gr","mmsc":"http://192.168.200.95/servlets/mms","mmsproxy":"192.168.200.11","mmsport":"9401","type":["mms"]}
+ ]
+},
+"204": {
+ "4": [
+ {"carrier":"Vodafone NL","apn":"live.vodafone.com","user":"vodafone","password":"vodafone","mmsc":"http://mmsc.mms.vodafone.nl","mmsproxy":"192.168.251.150","mmsport":"8799","type":["default","supl","mms"]}
+ ],
+ "8": [
+ {"carrier":"KPN Internet","apn":"portalmmm.nl","mmsc":"http://mp.mobiel.kpn/mmsc","mmsproxy":"10.10.100.20","mmsport":"5080","type":["default","supl","mms"]}
+ ],
+ "12": [
+ {"carrier":"Telfort Internet","apn":"internet","mmsc":"http://mms","mmsproxy":"193.113.200.195","mmsport":"8080","type":["default","supl","mms"]}
+ ],
+ "16": [
+ {"carrier":"T-Mobile Internet","apn":"internet","type":["default","supl"]},
+ {"carrier":"T-Mobile MMS","apn":"mms","user":"tmobilemms","password":"tmobilemms","mmsc":"http://t-mobilemms","mmsproxy":"10.10.10.11","mmsport":"8080","type":["mms"]}
+ ],
+ "20": [
+ {"carrier":"Rabo Mobiel","apn":"rabo.plus","type":["default","supl"]}
+ ]
+},
+"206": {
+ "1": [
+ {"carrier":"Proximus MMS","apn":"event.proximus.be","user":"mms","password":"mms","mmsc":"http://mmsc.proximus.be/mms","mmsproxy":"10.55.14.75","mmsport":"8080","type":["mms"]},
+ {"voicemail":"1230","type":["operatorvariant"]},
+ {"carrier":"Proximus Internet","apn":"internet.proximus.be","type":["default","supl"]},
+ {"carrier":"Telenet Internet","apn":"telenetwap.be","type":["default","supl"]},
+ {"carrier":"Telenet MMS","apn":"mms.be","mmsc":"http://mmsc.telenet.be","mmsproxy":"195.130.149.100","mmsport":"80","type":["mms"]}
+ ],
+ "5": [
+ {"carrier":"Telenet","apn":"telenetwap.be","type":["default","supl"]},
+ {"carrier":"Telenet MMS","apn":"mms.be","mmsc":"http://mmsc.telenet.be","mmsproxy":"195.130.149.100","mmsport":"80","type":["mms"]}
+ ],
+ "10": [
+ {"carrier":"Mobistar MMS","apn":"mms.be","mmsc":"http://mmsc.mobistar.be","mmsproxy":"212.65.63.143","mmsport":"8080","type":["mms"]},
+ {"voicemail":"5555","type":["operatorvariant"]},
+ {"carrier":"Mobistar","apn":"mworld.be","user":"mobistar","password":"mobistar","proxy":"212.65.63.143","port":"8080","type":["default","supl"]}
+ ],
+ "20": [
+ {"carrier":"Base","apn":"gprs.base.be","user":"base","password":"base","proxy":"172.31.198.37","port":"5080","type":["default","supl"]},
+ {"voicemail":"1933","type":["operatorvariant"]},
+ {"carrier":"BASE MMS","apn":"mms.base.be","user":"base","password":"base","mmsc":"http://mmsc.base.be","mmsproxy":"217.72.235.1","mmsport":"8080","type":["mms"]}
+ ]
+},
+"208": {
+ "1": [
+ {"carrier":"Orange World","apn":"orange","user":"orange","password":"orange","authtype":"2","type":["default","supl"]},
+ {"voicemail":"888","type":["operatorvariant"]},
+ {"carrier":"Orange MMS","apn":"orange.acte","user":"orange","password":"orange","mmsc":"http://mms.orange.fr","mmsproxy":"192.168.10.200","mmsport":"8080","authtype":"2","type":["mms"]},
+ {"carrier":"Orange Entreprise","apn":"orange-mib","proxy":"172.16.2.8","port":"8000","user":"orange","password":"orange","authtype":"2","type":["default"]},
+ {"carrier":"VM WAP","apn":"ofnew.fr","user":"orange","password":"orange","type":["default","supl"]},
+ {"carrier":"VM MMS","apn":"orange.acte","user":"orange","password":"orange","mmsc":"http://mms.orange.fr","mmsproxy":"192.168.10.200","mmsport":"8080","type":["mms"]},
+ {"carrier":"Tele2 WAP","apn":"ofnew.fr","user":"orange","password":"orange","type":["default","supl"]},
+ {"carrier":"Tele2 MMS","apn":"orange.acte","user":"orange","password":"orange","mmsc":"http://mms.orange.fr","mmsproxy":"192.168.10.200","mmsport":"8080","type":["mms"]},
+ {"carrier":"Carrefour WAP","apn":"ofnew.fr","user":"orange","password":"orange","type":["default","supl"]},
+ {"carrier":"Carrefour MMS","apn":"orange.acte","user":"orange","password":"orange","mmsc":"http://mms.orange.fr","mmsproxy":"192.168.10.200","mmsport":"8080","type":["mms"]},
+ {"carrier":"NRJWEB","apn":"ofnew.fr","user":"orange","password":"orange","type":["default","supl"]},
+ {"carrier":"NRJMMS","apn":"orange.acte","user":"orange","password":"orange","mmsc":"http://mms.orange.fr","mmsproxy":"192.168.10.200","mmsport":"8080","type":["mms"]}
+ ],
+ "10": [
+ {"carrier":"SFR webphone","apn":"sl2sfr","type":["default","supl"]},
+ {"voicemail":"123","type":["operatorvariant"]},
+ {"carrier":"SFR Mobile","apn":"wapsfr","type":["default","supl"]},
+ {"carrier":"MMS","apn":"mmssfr","mmsc":"http://mms1","mmsproxy":"10.151.0.1","mmsport":"8080","type":["mms"]},
+ {"carrier":"NRJWEB","apn":"fnetnrj","type":["default","supl"]},
+ {"carrier":"NRJMMS","mmsc":"http://mmsnrj","mmsproxy":"10.143.156.5","mmsport":"8080","apn":"mmsnrj","type":["mms"]},
+ {"carrier":"Auchan WAP","proxy":"192.168.21.8","port":"8080","apn":"wap65","type":["default","supl"]},
+ {"carrier":"Auchan MMS","mmsc":"http://mms65","mmsproxy":"10.143.156.8","mmsport":"8080","apn":"mms65","type":["mms"]},
+ {"carrier":"WAP Simplicime","proxy":"192.168.21.3","port":"8080","apn":"wapdebitel","type":["default","supl"]},
+ {"carrier":"MMS Simplicime","mmsc":"http://mmsdebitel","mmsproxy":"10.143.156.3","mmsport":"8080","apn":"mmsdebitel","type":["mms"]},
+ {"carrier":"WAP LeclercMobile","proxy":"192.168.21.9","port":"8080","apn":"wap66","type":["default","supl"]},
+ {"carrier":"MMS LeclercMobile","mmsc":"http://mms66","mmsproxy":"10.143.156.9","mmsport":"8080","apn":"mms66","type":["mms"]},
+ {"carrier":"Coriolis WAP","proxy":"192.168.21.6","port":"8080","apn":"wapcoriolis","type":["default","supl"]},
+ {"carrier":"Coriolis MMS","mmsc":"http://mmscoriolis","mmsproxy":"10.143.156.6","mmsport":"8080","apn":"mmscoriolis","type":["mms"]},
+ {"carrier":"Keyyo Mobile Internet","apn":"internet68","type":["default","supl"]},
+ {"carrier":"Keyyo Mobile MMS","mmsc":"http://mms68","mmsproxy":"10.143.156.11","mmsport":"8080","apn":"mms68","type":["mms"]},
+ {"carrier":"WEB La Poste Mobile","proxy":"192.168.21.3","port":"8080","apn":"wapdebitel","type":["default","supl"]},
+ {"carrier":"MMS La Poste Mobile","mmsc":"http://mmsdebitel","mmsproxy":"10.143.156.3","mmsport":"8080","apn":"mmsdebitel","type":["mms"]}
+ ],
+ "20": [
+ {"carrier":"Bouygues Telecom","apn":"mmsbouygtel.com","mmsc":"http://mms.bouyguestelecom.fr/mms/wapenc","mmsproxy":"62.201.129.226","mmsport":"8080","type":["default","supl","mms"]},
+ {"voicemail":"660","type":["operatorvariant"]}
+ ],
+ "15": [
+ {"carrier":"Free","apn":"free","type":["default","supl"]},
+ {"carrier":"Free MMS","mmsc":"http://mms.free.fr","apn":"mmsfree","type":["mms"]}
+ ]
+},
+"214": {
+ "1": [
+ {"carrier":"Internet movil","apn":"airtelwap.es","user":"wap@wap","password":"wap125","type":["default","supl"]},
+ {"enableStrict7BitEncodingForSms":true,"type":["operatorvariant"]},
+ {"carrier":"MMS VODAFONE","apn":"mms.vodafone.net","user":"wap@wap","password":"wap125","mmsc":"http://mmsc.vodafone.es/servlets/mms","mmsproxy":"212.73.32.10","mmsport":"80","type":["mms"]}
+ ],
+ "3": [
+ {"carrier":"Orange Internet Móvil","apn":"orangeworld","user":"orange","password":"orange","authtype":"1","type":["default"]},
+ {"voicemail":"242","enableStrict7BitEncodingForSms":true,"type":["operatorvariant"]},
+ {"carrier":"Orange MMS","apn":"orangemms","proxy":"172.22.188.25","port":"8080","user":"orange","password":"orange","mmsc":"http://mms.orange.es","mmsproxy":"172.22.188.25","mmsport":"8080","authtype":"2","type":["mms"]}
+ ],
+ "4": [
+ {"carrier":"Yoigo Navegador","apn":"internet","type":["default","supl"]},
+ {"voicemail":"633","enableStrict7BitEncodingForSms":true,"type":["operatorvariant"]},
+ {"carrier":"Yoigo MMS","apn":"mms","mmsc":"http://mmss/","mmsproxy":"193.209.134.141","mmsport":"80","type":["mms"]}
+ ],
+ "6": [
+ {"carrier":"INTERNET GPRS","apn":"airtelnet.es","user":"vodafone","password":"vodafone","type":["default","supl"]},
+ {"enableStrict7BitEncodingForSms":true,"type":["operatorvariant"]},
+ {"carrier":"Euskaltel MMS","apn":"euskaltelmms.euskaltel.mobi","user":"MMS","password":"EUSKALTEL","mmsc":"http://mms.euskaltel.mobi","mmsproxy":"172.16.18.74","mmsport":"8080","type":["mms"]},
+ {"carrier":"Euskaltel Internet","apn":"internet.euskaltel.mobi","user":"CLIENTE","password":"EUSKALTEL","type":["default","supl"]},
+ {"carrier":"Internet R","apn":"internet.mundo-r.com","type":["default","supl"]},
+ {"carrier":"TeleCable Internet","apn":"internet.telecable.es","user":"telecable","password":"telecable","type":["default","supl"]},
+ {"carrier":"MMS R","apn":"euskaltelmms.euskaltel.mobi","mmsc":"http://mms.mundo-r.com","mmsproxy":"10.0.157.169","mmsport":"8080","type":["mms"]},
+ {"carrier":"TeleCable MMS","apn":"mms.telecable.es","user":"telecable","password":"telecable","mmsc":"http://mms.telecable.es/mms/","mmsproxy":"212.89.0.84","mmsport":"8080","type":["mms"]},
+ {"carrier":"MMS Vodafone","apn":"mms.vodafone.net","user":"wap@wap","password":"wap125","mmsc":"http://mmsc.vodafone.es/servlets/mms","mmsproxy":"212.73.32.10","mmsport":"80","type":["mms"]}
+ ],
+ "7": [
+ {"carrier":"Movistar MMS","apn":"telefonica.es","user":"telefonica","password":"telefonica","mmsc":"http://mms.movistar.com","mmsproxy":"10.138.255.5","mmsport":"8080","type":["mms"]},
+ {"voicemail":"123","enableStrict7BitEncodingForSms":true,"type":["operatorvariant"]},
+ {"carrier":"Movistar","apn":"telefonica.es","user":"telefonica","password":"telefonica","proxy":"10.138.255.133","port":"8080","type":["default","supl"]}
+ ],
+ "8": [
+ {"carrier":"Euskaltel MMS","apn":"euskaltelmms.euskaltel.mobi","user":"MMS","password":"EUSKALTEL","mmsc":"http://mms.euskaltel.mobi","mmsproxy":"172.16.18.74","mmsport":"8080","type":["mms"]},
+ {"enableStrict7BitEncodingForSms":true,"type":["operatorvariant"]},
+ {"carrier":"Euskaltel Internet","apn":"internet.euskaltel.mobi","user":"CLIENTE","password":"EUSKALTEL","type":["default","supl"]}
+ ],
+ "16": [
+ {"carrier":"TeleCable Internet","apn":"internet.telecable.es","user":"telecable","password":"telecable","type":["default","supl"]},
+ {"enableStrict7BitEncodingForSms":true,"type":["operatorvariant"]},
+ {"carrier":"TeleCable MMS","apn":"mms.telecable.es","user":"telecable","password":"telecable","mmsc":"http://mms.telecable.es/mms/","mmsproxy":"212.89.0.84","mmsport":"8080","type":["mms"]}
+ ]
+},
+"216": {
+ "1": [
+ {"carrier":"Telenor MMS","apn":"mms","mmsc":"http://mmsc.telenor.hu/","mmsproxy":"84.225.255.1","mmsport":"8080","type":["mms"]},
+ {"carrier":"Telenor Online","apn":"online","type":["default","supl"]}
+ ],
+ "30": [
+ {"carrier":"T-Mobile MMS","apn":"mms","user":"mms","password":"mms","mmsc":"http://mms.t-mobile.hu/servlets/mms","mmsproxy":"212.51.126.10","mmsport":"8080","type":["mms"]},
+ {"carrier":"Web","apn":"wnw","type":["default","supl"]}
+ ],
+ "70": [
+ {"carrier":"VF internet","apn":"internet.vodafone.net","type":["default","supl"]},
+ {"carrier":"Vodafone MMS","apn":"mms.vodafone.net","mmsc":"http://mms.vodafone.hu/servlets/mms","mmsproxy":"80.244.97.2","mmsport":"8080","type":["mms"]},
+ {"carrier":"VMAX Internet","apn":"vitamax.internet.vodafone.net","type":["default","supl"]}
+ ]
+},
+"218": {
+ "3": [
+ {"carrier":"HT Eronet WAP","apn":"wap.eronet.ba","proxy":"10.12.3.10","port":"8080","type":["default","supl"]},
+ {"carrier":"HT Eronet GPRS","apn":"gprs.eronet.ba","type":["default","supl"]},
+ {"carrier":"Ht Eronet MMS","apn":"mms.eronet.ba","mmsc":"http://mms.gprs.eronet.ba/mms/wapenc","mmsproxy":"10.12.3.11","mmsport":"8080","type":["mms"]}
+ ],
+ "5": [
+ {"carrier":"m:tel","apn":"3g1","proxy":"192.168.61.10","port":"80","type":["default","supl"]},
+ {"carrier":"mtelmms","apn":"mtelmms","mmsc":"http://mmsc.mtel.ba/mms/wapenc","mmsproxy":"192.168.61.11","mmsport":"8080","type":["mms"]}
+ ],
+ "90": [
+ {"carrier":"Bh Mobile","apn":"active.bhmobile.ba","type":["default","supl"]},
+ {"carrier":"BHMobileMMS","apn":"mms.bhmobile.ba","mmsc":"http://mms.bhmobile.ba/cmmsc/post","mmsproxy":"195.222.56.041","mmsport":"8080","type":["mms"]}
+ ]
+},
+"219": {
+ "1": [
+ {"carrier":"T-Mobile MMS","apn":"mms.htgprs","mmsc":"http://mms.t-mobile.hr/servlets/mms","mmsproxy":"10.12.0.4","mmsport":"8080","type":["mms"]},
+ {"carrier":"T-Mobile","apn":"web.htgprs","type":["default","supl"]}
+ ],
+ "2": [
+ {"carrier":"Tele2","apn":"internet.tele2.hr","mmsc":"http://mmsc.tele2.hr","mmsproxy":"193.12.40.66","mmsport":"8080","type":["default","supl","mms"]}
+ ],
+ "10": [
+ {"carrier":"Broadband","apn":"data.vip.hr","user":"38591","password":"38591","type":["default","supl"]},
+ {"carrier":"VIP.mms","apn":"mms.vipnet.hr","user":"38591","password":"38591","mmsc":"http://mms.vipnet.hr/servlets/mms","mmsproxy":"212.91.99.91","mmsport":"8080","type":["mms"]}
+ ]
+},
+"220": {
+ "1": [
+ {"carrier":"Telenor WAP","apn":"internet","user":"telenor","password":"gprs","proxy":"217.65.192.33","port":"8080","type":["default","supl"]},
+ {"carrier":"Telenor MMS","apn":"mms","mmsc":"http://mms.telenor.rs/servlets/mms","mmsproxy":"217.65.192.33","mmsport":"8080","type":["mms"]}
+ ],
+ "2": [
+ {"carrier":"Telenor MNE internet","apn":"internet","user":"gprs","password":"gprs","proxy":"192.168.246.5","port":"8080","type":["default","supl"]},
+ {"carrier":"Telenor MNE mms","apn":"mms","user":"mms","password":"mms","mmsc":"http://mm.vor.telenor.me","mmsproxy":"192.168.246.5","mmsport":"8080","type":["mms"]}
+ ],
+ "3": [
+ {"carrier":"mt:s wap","apn":"gprswap","user":"mts","password":"064","proxy":"172.17.88.198","port":"8080","type":["default","supl"]},
+ {"carrier":"mt:s mms","apn":"mms","user":"mts","password":"064","mmsc":"http://mms.mts064.telekom.rs/mms/wapenc","mmsproxy":"172.17.85.131","mmsport":"8080","type":["mms"]}
+ ],
+ "4": [
+ {"carrier":"T-Mobile MMS","apn":"mms","user":"38267","password":"38267","mmsc":"http://192.168.180.100/servlets/mms","mmsproxy":"10.0.5.19","mmsport":"8080","type":["mms"]},
+ {"carrier":"T-Mobile Internet","apn":"tmcg-wnw","user":"38267","password":"38267","proxy":"10.0.5.19","port":"8080","type":["default","supl"]}
+ ],
+ "5": [
+ {"carrier":"Vip GPRS","apn":"vipmobile","user":"vipmobile","password":"vipmobile","proxy":"212.15.182.82","port":"8080","type":["default","supl"]},
+ {"carrier":"Vip MMS","apn":"vipmobile.mms","user":"vipmobile","password":"vipmobile","mmsc":"http://mmsc.vipmobile.rs","mmsproxy":"212.15.182.82","mmsport":"8080","type":["mms"]}
+ ]
+},
+"222": {
+ "1": [
+ {"carrier":"TIM WAP","apn":"wap.tim.it","user":"WAPTIM","password":"WAPTIM","authtype":"1","type":["default","supl"]},
+ {"carrier":"TIM WEB","apn":"ibox.tim.it","type":["default","supl"]},
+ {"carrier":"TIM MMS","apn":"unico.tim.it","mmsc":"http://mms.tim.it/servlets/mms","mmsproxy":"213.230.130.89","mmsport":"80","type":["mms"]}
+ ],
+ "10": [
+ {"carrier":"MMS Vodafone","apn":"mms.vodafone.it","mmsc":"http://mms.vodafone.it/servlets/mms","mmsproxy":"10.128.224.10","mmsport":"80","type":["mms"]},
+ {"carrier":"Acc. Internet da cell","apn":"mobile.vodafone.it","type":["default","supl"]}
+ ],
+ "88": [
+ {"carrier":"WIND WEB","apn":"internet.wind","type":["default","supl"]},
+ {"carrier":"WIND BIZ WEB","apn":"internet.wind.biz","type":["default","supl"]},
+ {"carrier":"WIND MMS","apn":"mms.wind","mmsc":"http://mms.wind.it","mmsproxy":"212.245.244.100","mmsport":"8080","type":["mms"]}
+ ],
+ "99": [
+ {"carrier":"3","apn":"tre.it","mmsc":"http://10.216.59.240:10021/mmsc","mmsproxy":"62.13.171.3","mmsport":"8799","type":["default","supl","mms"]},
+ {"carrier":"Fastweb WEB","apn":"apn.fastweb.it","mmsc":"http://mms.fastweb.it/mms/wapenc","mmsproxy":"10.0.65.9","mmsport":"8080","type":["default","supl","mms"]}
+ ]
+},
+"226": {
+ "1": [
+ {"carrier":"Vodafone live!","apn":"live.vodafone.com","user":"live","password":"vodafone","proxy":"193.230.161.231","port":"8080","type":["default","supl"]},
+ {"carrier":"Vodafone live! PRE","apn":"live.pre.vodafone.com","user":"live","password":"vodafone","proxy":"193.230.161.231","port":"8080","type":["default","supl"]},
+ {"carrier":"Vodafone MMS","apn":"mms.vodafone.ro","user":"mms","password":"vodafone","mmsc":"http://multimedia/servlets/mms","mmsproxy":"193.230.161.231","mmsport":"8080","type":["mms"]},
+ {"carrier":"Vodafone MMS PRE","apn":"mms.pre.vodafone.ro","user":"mms","password":"vodafone","mmsc":"http://multimedia/servlets/mms","mmsproxy":"193.230.161.231","mmsport":"8080","type":["mms"]}
+ ],
+ "3": [
+ {"carrier":"Cosmote Internet","apn":"internet","type":["default","supl"]},
+ {"carrier":"Cosmote MMS","apn":"mms","user":"mms","password":"mms","mmsc":"http://mmsc1.mms.cosmote.ro:8002","mmsproxy":"10.252.1.62","mmsport":"8080","type":["mms"]},
+ {"carrier":"web'n'walk","apn":"wnw","user":"wnw","password":"wnw","proxy":"10.252.1.62","port":"8080","type":["default","supl"]}
+ ],
+ "6": [
+ {"carrier":"Cosmote MMS","apn":"mms","user":"mms","password":"mms","mmsc":"http://mmsc1.mms.cosmote.ro:8002","mmsproxy":"10.252.1.62","mmsport":"8080","type":["mms"]},
+ {"carrier":"web'n'walk","apn":"wnw","user":"wnw","password":"wnw","proxy":"10.252.1.62","port":"8080","type":["default","supl"]}
+ ],
+ "10": [
+ {"carrier":"Orange MMS","apn":"mms","mmsc":"http://wap.mms.orange.ro:8002","mmsproxy":"62.217.247.252","mmsport":"9201","type":["mms"]},
+ {"carrier":"Orange WAP","apn":"wap","proxy":"62.217.247.252","port":"8799","type":["default"]},
+ {"carrier":"Orange Internet","apn":"net","type":["default"]}
+ ]
+},
+"228": {
+ "1": [
+ {"carrier":"Swisscom MMS","apn":"event.swisscom.ch","mmsc":"http://mms.natel.ch:8079","mmsproxy":"192.168.210.2","mmsport":"8080","type":["mms"]},
+ {"carrier":"Swisscom Services","apn":"gprs.swisscom.ch","proxy":"192.168.210.1","port":"8080","type":["default","supl"]}
+ ],
+ "2": [
+ {"carrier":"Sunrise live","apn":"internet","type":["default","supl"]},
+ {"carrier":"Sunrise MMS","apn":"mms.sunrise.ch","mmsc":"http://mmsc.sunrise.ch","mmsproxy":"212.35.34.75","mmsport":"8080","type":["mms"]}
+ ],
+ "3": [
+ {"carrier":"Orange Internet","apn":"internet","type":["default","supl"]},
+ {"carrier":"Orange MMS","apn":"mms","mmsc":"http://192.168.151.3:8002","mmsproxy":"192.168.151.2","mmsport":"8080","type":["mms"]}
+ ]
+},
+"230": {
+ "1": [
+ {"carrier":"T-Mobile CZ","apn":"internet.t-mobile.cz","user":"wap","password":"wap","type":["default","supl"]},
+ {"carrier":"T-Mobile MMS","apn":"mms.t-mobile.cz","user":"mms","password":"mms","mmsc":"http://mms","mmsproxy":"10.0.0.10","mmsport":"80","type":["mms"]}
+ ],
+ "2": [
+ {"carrier":"O2 internet","apn":"internet","type":["default","supl"]},
+ {"carrier":"O2 MMS","apn":"mms","mmsc":"http://mms.o2active.cz:8002","mmsproxy":"160.218.160.218","mmsport":"8080","type":["mms"]}
+ ],
+ "3": [
+ {"carrier":"GPRS Web","apn":"internet","type":["default","supl"]},
+ {"carrier":"Vodafone MMS","apn":"mms","user":"mms","password":"mms","mmsc":"http://mms","mmsproxy":"10.11.10.111","mmsport":"80","type":["mms"]}
+ ]
+},
+"231": {
+ "1": [
+ {"carrier":"Orange SK","apn":"internet","type":["default"]},
+ {"carrier":"Orange SK MMS","apn":"mms","user":"wap","password":"wap","mmsc":"http://imms.orange.sk","mmsproxy":"213.151.208.145","mmsport":"8799","type":["mms"]},
+ {"carrier":"Orange World","apn":"orangewap","user":"wap","password":"wap","proxy":"213.151.208.156","port":"8799","type":["default","supl"]}
+ ],
+ "2": [
+ {"carrier":"T-Mobile internet","apn":"internet","type":["default","supl"]},
+ {"carrier":"T-Mobile MMS","apn":"mms","user":"mms","password":"mms","mmsc":"http://mms","mmsproxy":"192.168.1.1","mmsport":"8080","type":["mms"]}
+ ],
+ "6": [
+ {"carrier":"O2 internet","apn":"o2internet","type":["default","supl"]},
+ {"carrier":"O2 MMS","apn":"o2mms","mmsc":"http://mms.o2world.sk:8002","mmsproxy":"10.97.1.11","mmsport":"8080","type":["mms"]}
+ ]
+},
+"232": {
+ "1": [
+ {"carrier":"A1","apn":"a1.net","user":"ppp@a1plus.at","password":"ppp","type":["default","supl"]},
+ {"carrier":"aonMobil","apn":"aon.at","user":"mobil@aon.at","password":"ppp","type":["default","supl"]},
+ {"carrier":"A1 MMS","apn":"free.a1.net","user":"ppp@a1plus.at","password":"ppp","mmsc":"http://mmsc.a1.net","mmsproxy":"194.48.124.71","mmsport":"8001","type":["mms"]},
+ {"carrier":"aonMobil MMS","apn":"mms.aon.at","user":"mobil@aon.at","password":"ppp","mmsc":"http://mmsc.aon.at","mmsproxy":"194.48.124.134","mmsport":"8001","type":["mms"]}
+ ],
+ "3": [
+ {"carrier":"T-Mobile Internet","apn":"gprsinternet","user":"t-mobile","password":"tm","type":["default","supl"]},
+ {"carrier":"T-Mobile MMS","apn":"gprsmms","user":"t-mobile","password":"tm","mmsc":"http://mmsc.t-mobile.at/servlets/mms","mmsproxy":"10.12.0.20","mmsport":"80","type":["mms"]}
+ ],
+ "5": [
+ {"carrier":"Orange MMS","apn":"orange.mms","user":"mms","password":"mms","mmsc":"http://mmsc.orange.at/mms/wapenc","mmsproxy":"194.24.128.118","mmsport":"8080","type":["mms"]},
+ {"carrier":"Orange Smartphone","apn":"orange.smartphone","user":"web","password":"web","type":["default","supl"]}
+ ],
+ "7": [
+ {"carrier":"tele.ring mms","apn":"mms","user":"wap@telering.at","password":"wap","mmsc":"http://relay.mms.telering.at","mmsproxy":"212.95.31.50","mmsport":"80","type":["mms"]},
+ {"carrier":"tele.ring web","apn":"web","user":"web@telering.at","password":"web","type":["default","supl"]}
+ ],
+ "10": [
+ {"carrier":"Planet3","apn":"drei.at","mmsc":"http://mmsc","mmsproxy":"213.94.78.133","mmsport":"8799","type":["default","supl","mms"]}
+ ],
+ "11": [
+ {"carrier":"data.bob","apn":"bob.at","user":"data@bob.at","password":"ppp","type":["default","supl"]},
+ {"carrier":"data.bob MMS","apn":"mms.bob.at","user":"data@bob.at","password":"ppp","mmsc":"http://mmsc.bob.at","mmsproxy":"194.48.124.7","mmsport":"8001","type":["mms"]}
+ ],
+ "12": [
+ {"carrier":"YESSS! GPRS","apn":"web.yesss.at","type":["default","supl"]}
+ ]
+},
+"234": {
+ "1": [
+ {"carrier":"UBIQUISYS","apn":"internet","type":["default","supl","mms"]}
+ ],
+ "2": [
+ {"carrier":"O2 MOBILE WEB","apn":"mobile.o2.co.uk","user":"O2web","password":"O2web","type":["default","supl"]},
+ {"voicemail":"901","type":["operatorvariant"]},
+ {"carrier":"O2 MMS Prepay","apn":"payandgo.o2.co.uk","user":"payandgo","password":"password","mmsc":"http://mmsc.mms.o2.co.uk:8002","mmsproxy":"82.132.254.1","mmsport":"8080","type":["mms"]},
+ {"carrier":"O2 MMS Postpay","apn":"wap.o2.co.uk","user":"o2wap","password":"password","mmsc":"http://mmsc.mms.o2.co.uk:8002","mmsproxy":"82.132.254.1","mmsport":"8080","type":["mms"]}
+ ],
+ "10": [
+ {"carrier":"O2 Mobile Web","apn":"mobile.o2.co.uk","user":"o2web","password":"password","type":["default","supl"]},
+ {"voicemail":"901","type":["operatorvariant"]},
+ {"carrier":"O2 MMS","apn":"wap.o2.co.uk","user":"o2wap","password":"password","proxy":"193.113.200.195","port":"8080","mmsc":"http://mmsc.mms.o2.co.uk:8002","mmsproxy":"82.132.254.1","mmsport":"8080","type":["mms"]},
+ {"carrier":"O2 PREPAY","apn":"payandgo.o2.co.uk","proxy":"193.113.200.195","port":"8080","user":"payandgo","password":"password","mmsc":"http://mmsc.mms.o2.co.uk:8002","mmsproxy":"82.132.254.1","mmsport":"8080","type":["default","supl","mms"]},
+ {"carrier":"TESCO","apn":"prepay.tesco-mobile.com","user":"tescowap","password":"password","mmsc":"http://mmsc.mms.o2.co.uk:8002","mmsproxy":"82.132.254.1","mmsport":"8080","type":["default","supl","mms"]}
+ ],
+ "11": [
+ {"carrier":"O2 MOBILE WEB","apn":"mobile.o2.co.uk","user":"O2web","password":"O2web","type":["default","supl"]},
+ {"voicemail":"901","type":["operatorvariant"]},
+ {"carrier":"O2 MMS Prepay","apn":"payandgo.o2.co.uk","user":"payandgo","password":"password","mmsc":"http://mmsc.mms.o2.co.uk:8002","mmsproxy":"82.132.254.1","mmsport":"8080","type":["mms"]},
+ {"carrier":"O2 MMS Postpay","apn":"wap.o2.co.uk","user":"o2wap","password":"password","mmsc":"http://mmsc.mms.o2.co.uk:8002","mmsproxy":"82.132.254.1","mmsport":"8080","type":["mms"]},
+ {"carrier":"O2 MOBILE WEB","apn":"mobile.o2.co.uk","user":"O2web","password":"O2web","type":["default","supl"]},
+ {"carrier":"O2 MMS Prepay","apn":"payandgo.o2.co.uk","user":"payandgo","password":"password","mmsc":"http://mmsc.mms.02.co.uk:8002","mmsproxy":"82.132.254.1","mmsport":"8080","type":["mms"]},
+ {"carrier":"O2 MMS Postpay","apn":"wap.o2.co.uk","user":"o2wap","password":"password","mmsc":"http://mmsc.mms.02.co.uk:8002","mmsproxy":"82.132.254.1","mmsport":"8080","type":["mms"]}
+ ],
+ "15": [
+ {"carrier":"Vodafone UK","apn":"wap.vodafone.co.uk","user":"wap","password":"wap","mmsc":"http://mms.vodafone.co.uk/servlets/mms","mmsproxy":"212.183.137.12","mmsport":"8799","type":["default","supl","mms"]},
+ {"voicemail":"121","type":["operatorvariant"]},
+ {"carrier":"Vodafone UK Prepay","apn":"pp.vodafone.co.uk","user":"wap","password":"wap","mmsc":"http://mms.vodafone.co.uk/servlets/mms","mmsproxy":"212.183.137.12","mmsport":"8799","type":["default","supl","mms"]}
+ ],
+ "20": [
+ {"carrier":"3","apn":"three.co.uk","mmsc":"http://mms.um.three.co.uk:10021/mmsc","mmsproxy":"mms.three.co.uk","mmsport":"8799","type":["default","supl","mms"]}
+ ],
+ "30": [
+ {"carrier":"T-Mobile UK","apn":"general.t-mobile.uk","user":"t-mobile","password":"tm","mmsc":"http://mmsc.t-mobile.co.uk:8002","mmsproxy":"149.254.201.135","mmsport":"8080","type":["default","supl","mms"]},
+ {"voicemail":"222","type":["operatorvariant"]},
+ {"carrier":"Virgin Media","apn":"goto.virginmobile.uk","user":"user","mmsc":"http://mms.virginmobile.co.uk:8002","mmsproxy":"193.30.166.2","mmsport":"8080","type":["default","supl","mms"]}
+ ],
+ "31": [
+ {"carrier":"T-Mobile Internet","apn":"general.t-mobile.uk","user":"t-mobile","password":"tm","type":["default","supl"]},
+ {"carrier":"T-Mobile Picture Message","apn":"general.t-mobile.uk","user":"t-mobile","password":"tm","mmsc":"http://mmsc.t-mobile.co.uk:8002","mmsproxy":"149.254.201.135","mmsport":"8080","type":["mms"]}
+ ],
+ "32": [
+ {"carrier":"T-Mobile Internet","apn":"general.t-mobile.uk","user":"t-mobile","password":"tm","type":["default","supl"]},
+ {"carrier":"T-Mobile Picture Message","apn":"general.t-mobile.uk","user":"t-mobile","password":"tm","mmsc":"http://mmsc.t-mobile.co.uk:8002","mmsproxy":"149.254.201.135","mmsport":"8080","type":["mms"]}
+ ],
+ "33": [
+ {"carrier":"Orange Internet","apn":"orangeinternet","authtype":"2","type":["default"]},
+ {"voicemail":"123","type":["operatorvariant"]},
+ {"carrier":"Orange MMS","apn":"orangemms","mmsc":"http://mms.orange.co.uk/","mmsproxy":"192.168.224.10","mmsport":"8080","authtype":"2","type":["mms"]}
+ ],
+ "34": [
+ {"carrier":"Orange internet","apn":"orangeinternet","type":["default","supl"]},
+ {"voicemail":"123","type":["operatorvariant"]},
+ {"carrier":"Orange MMS","apn":"orangemms","mmsc":"http://mms.orange.co.uk/","mmsproxy":"192.168.224.10","mmsport":"8080","type":["mms"]}
+ ],
+ "50": [
+ {"carrier":"Jersey Telecom","apn":"mms","user":"mms","password":"mms","mmsc":"http://mms.surfmail.com/mmsc","mmsproxy":"212.9.19.199","mmsport":"3130","type":["mms"]},
+ {"carrier":"pepperWEB (Jersey)","apn":"pepper","type":["default","supl"]}
+ ],
+ "55": [
+ {"carrier":"C&W Guernsey Internet","apn":"internet","type":["default","supl"]},
+ {"carrier":"Sure Picture Messaging","apn":"mms","mmsc":"http://mmsc.gprs.cw.com/","mmsproxy":"10.0.3.101","mmsport":"80","type":["mms"]}
+ ],
+ "58": [
+ {"carrier":"3G HSDPA","apn":"3gpronto","type":["default","supl"]},
+ {"carrier":"Manx Telecom Contract MMS","apn":"mms.manxpronto.net","user":"mms","password":"mms","mmsc":"http://mms.manxpronto.net:8002","mmsproxy":"195.10.99.46","mmsport":"8080","type":["mms"]},
+ {"carrier":"Manx Telecom Prepay MMS","apn":"mms.prontogo.net","user":"mmsgo","password":"mmsgo","mmsc":"http://mms.manxpronto.net:8002","mmsproxy":"195.10.99.41","mmsport":"8080","type":["mms"]},
+ {"carrier":"Manx Telecom Contract WEB","apn":"web.manxpronto.net","user":"gprs","password":"gprs","type":["default","supl"]}
+ ],
+ "86": [
+ {"carrier":"Orange internet","apn":"orangeinternet","type":["default","supl"]},
+ {"carrier":"Orange MMS","apn":"orangemms","mmsc":"http://mms.orange.co.uk/","mmsproxy":"192.168.224.10","mmsport":"8080","type":["mms"]}
+ ]
+},
+"238": {
+ "1": [
+ {"carrier":"TDC","apn":"internet","type":["default","supl"]},
+ {"voicemail":"20171717","type":["operatorvariant"]},
+ {"carrier":"TDC MMS","apn":"mms","mmsc":"http://mmsc.tdc.dk:8002","mmsproxy":"194.182.251.15","mmsport":"8080","type":["mms"]}
+ ],
+ "2": [
+ {"carrier":"Telenor Internet","apn":"Internet","type":["default","supl"]},
+ {"voicemail":"20171717","type":["operatorvariant"]},
+ {"carrier":"Telenor MMS","apn":"telenor","mmsc":"http://mms.telenor.dk","mmsproxy":"212.88.64.8","mmsport":"8080","type":["mms"]}
+ ],
+ "6": [
+ {"carrier":"3","apn":"data.tre.dk","mmsc":"http://mms.3.dk/","mmsproxy":"mmsproxy.3.dk","mmsport":"8799","type":["default","supl","mms"]}
+ ],
+ "20": [
+ {"carrier":"Telia Internet","apn":"www.internet.mtelia.dk","type":["default","supl"]},
+ {"carrier":"Telia MMS","apn":"www.mms.mtelia.dk","mmsc":"http://mms.telia.dk","mmsproxy":"193.209.134.131","mmsport":"8080","type":["mms"]}
+ ],
+ "77": [
+ {"carrier":"Telenor Internet","apn":"Internet","type":["default","supl"]},
+ {"voicemail":"20171717","type":["operatorvariant"]},
+ {"carrier":"Telenor MMS","apn":"telenor","mmsc":"http://mms.telenor.dk","mmsproxy":"212.88.64.8","mmsport":"8080","type":["mms"]}
+ ]
+},
+"240": {
+ "1": [
+ {"carrier":"Telia SE MMS","apn":"mms.telia.se","mmsc":"http://mmss/","mmsproxy":"193.209.134.132","mmsport":"80","type":["mms"]},
+ {"voicemail":"*133#","type":["operatorvariant"]},
+ {"carrier":"Telia SE WAP","apn":"online.telia.se","proxy":"10.254.254.254","port":"8080","type":["default","supl"]}
+ ],
+ "17": [
+ {"carrier":"Halebop Internet","apn":"halebop.telia.se","type":["default","supl"]},
+ {"carrier":"Halebop MMS","apn":"mms.telia.se","user":"mms","password":"telia","mmsc":"http://mmss","mmsproxy":"193.209.134.132","mmsport":"9201","type":["mms"]}
+ ],
+ "2": [
+ {"carrier":"3","apn":"data.tre.se","mmsc":"http://mms.tre.se","mmsproxy":"mmsproxy.tre.se","mmsport":"8799","type":["default","supl","mms"]}
+ ],
+ "4": [
+ {"carrier":"Telenor Mobilsurf","apn":"services.telenor.se","proxy":"172.30.253.241","port":"8799","mmsc":"http://mms","mmsproxy":"172.30.253.241","mmsport":"8799","type":["default","supl","mms"]},
+ {"voicemail":"888","type":["operatorvariant"]}
+ ],
+ "6": [
+ {"carrier":"Telenor MMS","apn":"services.telenor.se","mmsc":"http://mms","mmsproxy":"173.30.253.241","mmsport":"8799","type":["mms"]},
+ {"voicemail":"888","type":["operatorvariant"]},
+ {"carrier":"Telenor Mobilsurf","apn":"services.telenor.se","proxy":"173.30.253.241","port":"8799","type":["default","supl"]}
+ ],
+ "7": [
+ {"carrier":"Tele2","apn":"internet.tele2.se","mmsc":"http://mmsc.tele2.se","mmsproxy":"130.244.202.30","mmsport":"8080","type":["default","supl","mms"]},
+ {"voicemail":"222","type":["operatorvariant"]}
+ ],
+ "8": [
+ {"carrier":"Telenor Mobilsurf","apn":"services.telenor.se","proxy":"172.30.253.241","port":"8799","type":["default","supl"]},
+ {"voicemail":"888","type":["operatorvariant"]},
+ {"carrier":"Telenor MMS","apn":"services.telenor.se","mmsc":"http://mms","mmsproxy":"172.30.253.241","mmsport":"8799","type":["mms"]}
+ ],
+ "9": [
+ {"carrier":"Telenor MMS","apn":"services.telenor.se","mmsc":"http://mms","mmsproxy":"173.30.253.241","mmsport":"8799","type":["mms"]},
+ {"carrier":"TelenorMobilsurf","apn":"services.telenor.se","proxy":"173.30.253.241","port":"8799","type":["default","supl"]}
+ ],
+ "10": [
+ {"carrier":"Spring data","apn":"data.springmobil.se","type":["default","supl"]},
+ {"carrier":"Spring MMS","apn":"mms.springmobil.se","mmsc":"http://mms.springmobil.se","mmsproxy":"213.88.184.37","mmsport":"8080","type":["mms"]}
+ ]
+},
+"242": {
+ "1": [
+ {"carrier":"Ventelo Internett","apn":"internet.ventelo.no","type":["default","supl"]},
+ {"voicemail":"91500002","type":["operatorvariant"]},
+ {"carrier":"Ventelo MMS","apn":"mms.ventelo.no","user":"ventelo","password":"1111","mmsc":"http://mmsc/","mmsproxy":"10.10.10.11","mmsport":"8080","type":["mms"]},
+ {"carrier":"Telenor","apn":"telenor","mmsc":"http://mmsc","mmsproxy":"10.10.10.11","mmsport":"8080","type":["default","supl","mms"]}
+ ],
+ "2": [
+ {"carrier":"NetCom MMS","apn":"mms.netcom.no","mmsc":"http://mm/","mmsproxy":"212.169.66.4","mmsport":"8080","type":["mms"]},
+ {"voicemail":"47230000","type":["operatorvariant"]},
+ {"carrier":"NetCom Internett","apn":"wap","type":["default","supl"]}
+ ],
+ "4": [
+ {"carrier":"Tele2 Internett","apn":"internet.tele2.no","mmsc":"http://mmsc.tele2.no","mmsproxy":"193.12.40.14","mmsport":"8080","type":["default","supl","mms"]},
+ {"voicemail":"47230000","type":["operatorvariant"]}
+ ],
+ "5": [
+ {"carrier":"NwN Internet","apn":"internet","type":["default","supl"]},
+ {"carrier":"NwN MMS","apn":"mms","mmsc":"http://mms.nwn.no","mmsproxy":"188.149.250.10","mmsport":"80","type":["mms"]}
+ ]
+},
+"244": {
+ "3": [
+ {"carrier":"DNA Internet","apn":"internet","type":["default","supl"]},
+ {"carrier":"DNA MMS","apn":"mms","mmsc":"http://mmsc.dnafinland.fi/","mmsproxy":"10.1.1.2","mmsport":"8080","type":["mms"]}
+ ],
+ "4": [
+ {"carrier":"DNA Internet","apn":"internet","type":["default","supl"]},
+ {"carrier":"DNA MMS","apn":"mms","user":"dna","password":"mms","mmsc":"http://mmsc.dnafinland.fi/","mmsproxy":"10.1.1.2","mmsport":"8080","type":["mms"]}
+ ],
+ "5": [
+ {"carrier":"Elisa Internet","apn":"internet","type":["default","supl"]},
+ {"voicemail":"777","type":["operatorvariant"]},
+ {"carrier":"Elisa MMS","apn":"mms","mmsc":"http://mms.elisa.fi","mmsproxy":"213.161.41.57","mmsport":"80","type":["mms"]}
+ ],
+ "10": [
+ {"carrier":"TDC Internet","apn":"internet.song.fi","user":"song@internet","password":"songnet","type":["default","supl"]},
+ {"carrier":"TDC MMS","apn":"mms.song.fi","mmsc":"http://mms.song.fi","mmsproxy":"213.161.41.58","mmsport":"80","type":["mms"]}
+ ],
+ "12": [
+ {"carrier":"DNA Internet","apn":"internet","type":["default","supl"]},
+ {"carrier":"DNA MMS","apn":"mms","mmsc":"http://mmsc.dnafinland.fi/","mmsproxy":"10.1.1.2","mmsport":"8080","type":["mms"]},
+ {"carrier":"DNA Pro Internet","apn":"dnapro.fi","authtype":"1","type":["default","supl"]},
+ {"carrier":"DNA Pro MMS","apn":"mms.dnapro.fi","mmsc":"http://mmsc.dnapro.fi/","mmsproxy":"10.1.1.21","mmsport":"8080","authtype":"1","type":["mms"]},
+ {"carrier":"TDC Internet Finland","apn":"inet.tdc.fi","authtype":"1","type":["default","supl"]},
+ {"carrier":"TDC MMS Finland","apn":"mms.tdc.fi","mmsc":"http://mmsc.tdc.fi","mmsproxy":"10.1.12.2","mmsport":"8080","authtype":"1","type":["mms"]}
+ ],
+ "13": [
+ {"carrier":"DNA Internet","apn":"internet","type":["default","supl"]},
+ {"carrier":"DNA MMS","apn":"mms","user":"dna","password":"mms","mmsc":"http://mmsc.dnafinland.fi/","mmsproxy":"10.1.1.2","mmsport":"8080","type":["mms"]}
+ ],
+ "21": [
+ {"carrier":"Saunalahti Internet","apn":"internet.saunalahti","type":["default","supl"]},
+ {"carrier":"Saunalahti MMS","apn":"mms.saunalahti.fi","mmsc":"http://mms.saunalahti.fi:8002/","mmsproxy":"62.142.4.197","mmsport":"8080","type":["mms"]}
+ ],
+ "91": [
+ {"carrier":"SONERA Internet","apn":"internet","type":["default","supl"]},
+ {"carrier":"SONERA MMS","apn":"wap.sonera.net","mmsc":"http://mms.sonera.fi:8002","mmsproxy":"195.156.25.33","mmsport":"80","type":["mms"]}
+ ]
+},
+"246": {
+ "1": [
+ {"carrier":"Omnitel MMS","apn":"gprs.mms.lt","user":"mms","password":"mms","mmsc":"http://mms.omnitel.net:8002/","mmsproxy":"194.176.32.149","mmsport":"8080","type":["mms"]},
+ {"carrier":"Omnitel Internet","apn":"omnitel","user":"omni","password":"omni","type":["default","supl"]}
+ ],
+ "2": [
+ {"carrier":"Bite LT Internet","apn":"internet","user":"mms@mms","password":"mms","mmsc":"http://mmsc/servlets/mms","mmsproxy":"192.168.150.2","mmsport":"8080","type":["default","supl","mms"]}
+ ],
+ "3": [
+ {"carrier":"Tele2 Internet LT","apn":"internet.tele2.lt","user":"wap","password":"wap","proxy":"130.244.196.90","port":"8080","mmsc":"http://mmsc.tele2.lt/","mmsproxy":"193.12.40.29","mmsport":"8080","type":["default","supl","mms"]}
+ ]
+},
+"247": {
+ "1": [
+ {"carrier":"LMT Internet","apn":"internet.lmt.lv","type":["default","supl"]},
+ {"carrier":"LMT MMS","apn":"mms.lmt.lv","mmsc":"http://mmsc.lmt.lv/mmsc","mmsproxy":"212.93.97.201","mmsport":"80","type":["mms"]}
+ ],
+ "2": [
+ {"carrier":"Tele2 LV Internet","apn":"internet.tele2.lv","user":"wap","password":"wap","type":["default","supl"]},
+ {"carrier":"Tele2 LV MMS","apn":"mms.tele2.lv","user":"wap","password":"wap","mmsc":"http://mmsc.tele2.lv/","mmsproxy":"193.12.40.38","mmsport":"8080","type":["mms"]}
+ ],
+ "5": [
+ {"carrier":"Bite LV Internet","apn":"internet","type":["default","supl"]},
+ {"carrier":"Bite LV MMS","apn":"mms","user":"mms@mms","password":"mms","mmsc":"http://mmsc/servlets/mms","mmsproxy":"192.168.150.2","mmsport":"8080","type":["mms"]},
+ {"carrier":"Bite LV WAP","apn":"wap","proxy":"213.226.131.133","port":"8080","type":["default","supl"]}
+ ]
+},
+"248": {
+ "1": [
+ {"carrier":"EMT Internet","apn":"internet.emt.ee","type":["default","supl"]},
+ {"carrier":"EMT MMS","apn":"mms.emt.ee","mmsc":"http://mms.emt.ee/servlets/mms","mmsproxy":"217.71.32.82","mmsport":"8080","type":["mms"]},
+ {"carrier":"EMT WAP","apn":"wap.emt.ee","proxy":"217.71.32.236","port":"8080","type":["default","supl"]}
+ ],
+ "2": [
+ {"carrier":"Elisa Internet","apn":"internet","type":["default","supl"]},
+ {"carrier":"Elisa MMS","apn":"mms","mmsc":"http://194.204.2.10","mmsproxy":"194.204.2.6","mmsport":"8000","type":["mms"]},
+ {"carrier":"Elisa WAP","apn":"wap","proxy":"194.204.2.6","port":"8000","type":["default","supl"]}
+ ],
+ "3": [
+ {"carrier":"Tele2 Internet","apn":"internet.tele2.ee","type":["default","supl"]},
+ {"carrier":"Tele2 MMS","apn":"mms.tele2.ee","mmsc":"http://mmsc.tele2.ee","mmsproxy":"193.12.40.6","mmsport":"8080","type":["mms"]}
+ ]
+},
+"250": {
+ "1": [
+ {"carrier":"MTS Internet","apn":"internet.mts.ru","user":"mts","password":"mts","type":["default","supl"]},
+ {"carrier":"MTS MMS","apn":"mms.mts.ru","user":"mts","password":"mts","mmsc":"http://mmsc","mmsproxy":"192.168.192.192","mmsport":"9201","type":["mms"]}
+ ],
+ "2": [
+ {"carrier":"Megafon Internet","apn":"internet","user":"gdata","password":"gdata","type":["default","supl"]},
+ {"carrier":"Megafon MMS","apn":"mms","user":"mms","password":"mms","mmsc":"http://mmsc:8002","mmsproxy":"10.10.10.10","mmsport":"8080","type":["mms"]}
+ ],
+ "99": [
+ {"carrier":"Beeline Internet","apn":"internet.beeline.ru","user":"beeline","password":"beeline","type":["default","supl"]},
+ {"carrier":"Beeline MMS","apn":"mms.beeline.ru","user":"beeline","password":"beeline","mmsc":"http://mms/","mmsproxy":"192.168.94.23","mmsport":"8080","type":["mms"]}
+ ],
+ "20": [
+ {"carrier":"TELE2 Internet","apn":"internet.tele2.ru","type":["default","supl"]},
+ {"carrier":"TELE2 MMS","apn":"mms.tele2.ru","mmsc":"http://mmsc.tele2.ru","mmsproxy":"193.12.40.65","mmsport":"8080","type":["mms"]}
+ ]
+},
+"255": {
+ "1": [
+ {"carrier":"Jeans MMS","apn":"mms.jeans.ua","mmsc":"http://mmsc:8002/","mmsproxy":"192.168.10.10","mmsport":"8080","type":["mms"]},
+ {"carrier":"MTS MMS","apn":"mms","mmsc":"http://mms/","mmsproxy":"192.168.10.10","mmsport":"8080","type":["mms"]},
+ {"carrier":"MTS-internet","apn":"internet","type":["default","supl"]}
+ ],
+ "2": [
+ {"carrier":"Beeline-internet","apn":"internet.beeline.ua","type":["default","supl"]},
+ {"carrier":"Beeline MMS","apn":"mms.beeline.ua","mmsc":"http://mms/","mmsproxy":"172.29.18.192","mmsport":"8080","type":["mms"]}
+ ],
+ "3": [
+ {"carrier":"Djuice MMS","apn":"mms.djuice.com.ua","user":"djuice","password":"mms","mmsc":"http://mms.kyivstar.net","mmsproxy":"10.10.10.10","mmsport":"8080","type":["mms"]},
+ {"carrier":"Kyivstar MMS","apn":"mms.kyivstar.net","user":"mms","password":"mms","mmsc":"http://mms.kyivstar.net","mmsproxy":"10.10.10.10","mmsport":"8080","type":["mms"]},
+ {"carrier":"Kyivstar Internet","apn":"www.kyivstar.net","type":["default","supl"]}
+ ],
+ "6": [
+ {"carrier":"life:) internet","apn":"internet","type":["default","supl"]},
+ {"carrier":"life:) MMS","apn":"mms","mmsc":"http://mms.life.com.ua/cmmsc/post","mmsproxy":"212.58.162.230","mmsport":"8080","type":["mms"]}
+ ],
+ "7": [
+ {"carrier":"Utel Internet","apn":"3g.utel.ua","type":["default","supl"]},
+ {"carrier":"Utel MMS","apn":"3g.utel.ua","mmsc":"http://10.212.1.4/mms/wapenc","mmsproxy":"10.212.3.148","mmsport":"8080","type":["mms"]}
+ ]
+},
+"259": {
+ "1": [
+ {"carrier":"Orange MMS","apn":"mms.orange.md","mmsc":"http://mms/mms","mmsproxy":"192.168.127.125","mmsport":"3128","type":["mms"]},
+ {"carrier":"Orange IMO","apn":"wap.orange.md","proxy":"192.168.127.124","mmsport":"3128","type":["mms"]}
+ ]
+},
+"260": {
+ "1": [
+ {"carrier":"Plus Internet","apn":"internet","type":["default","supl"]},
+ {"voicemail":"2222","type":["operatorvariant"]},
+ {"carrier":"Plus MMS","apn":"mms","mmsc":"http://mms.plusgsm.pl:8002","mmsproxy":"212.2.96.16","mmsport":"8080","type":["mms"]}
+ ],
+ "2": [
+ {"carrier":"T-mobile.pl","apn":"internet","type":["default","supl"]},
+ {"voicemail":"602950","type":["operatorvariant"]},
+ {"carrier":"T-mobile.pl","apn":"mms","mmsc":"http://mms/servlets/mms","mmsproxy":"213.158.194.226","mmsport":"8080","type":["mms"]},
+ {"carrier":"heyahinternet","apn":"heyah.pl","type":["default","supl"]},
+ {"carrier":"heyahmms","apn":"heyahmms","mmsc":"http://mms.heyah.pl/servlets/mms","mmsproxy":"213.158.194.226","mmsport":"8080","type":["mms"]}
+ ],
+ "3": [
+ {"carrier":"Internet","apn":"Internet","user":"internet","password":"internet","type":["default"]},
+ {"voicemail":"*501","type":["operatorvariant"]},
+ {"carrier":"MMS Orange","apn":"mms","user":"mms","password":"mms","mmsc":"http://mms.orange.pl","mmsproxy":"192.168.6.104","mmsport":"8080","type":["mms"]}
+ ],
+ "6": [
+ {"carrier":"P4 Internet","apn":"internet","type":["default","supl"]},
+ {"carrier":"P4 MMS","apn":"mms","mmsc":"http://10.10.28.164/mms/wapenc","mmsproxy":"10.10.25.5","mmsport":"8080","type":["mms"]}
+ ]
+},
+"262": {
+ "1": [
+ {"carrier":"T-Mobile Internet","apn":"internet.t-mobile","user":"t-mobile","password":"tm","mmsc":"http://mms.t-mobile.de/servlets/mms","mmsproxy":"172.28.23.131","mmsport":"8008","type":["default","supl","mms"]}
+ ],
+ "2": [
+ {"carrier":"Vodafone DE-MMS","apn":"event.vodafone.de","mmsc":"http://139.7.24.1/servlets/mms","mmsproxy":"139.7.29.17","mmsport":"80","type":["mms"]},
+ {"voicemail":"5500","type":["operatorvariant"]},
+ {"carrier":"Vodafone DE","apn":"web.vodafone.de","type":["default","supl"]}
+ ],
+ "3": [
+ {"carrier":"E-Plus Web GPRS","apn":"internet.eplus.de","user":"eplus","password":"internet","type":["default","supl"]},
+ {"voicemail":"9911","type":["operatorvariant"]},
+ {"carrier":"E-Plus MMS","apn":"mms.eplus.de","user":"mms","password":"eplus","mmsc":"http://mms/eplus/","mmsproxy":"212.23.97.153","mmsport":"5080","type":["mms"]}
+ ],
+ "7": [
+ {"carrier":"o2","apn":"internet","mmsc":"http://10.81.0.7:8002","mmsproxy":"82.113.100.5","mmsport":"8080","type":["default","supl","mms"]},
+ {"voicemail":"333","type":["operatorvariant"]},
+ {"carrier":"o2 Internet Prepaid","apn":"pinternet.interkom.de","proxy":"82.113.100.6","port":"8080","mmsc":"http://10.81.0.7:8002","mmsproxy":"82.113.100.6","mmsport":"8080","type":["default","supl","mms"]}
+ ]
+},
+"268": {
+ "1": [
+ {"carrier":"Vodafone Net2","apn":"net2.vodafone.pt","user":"vodafone","password":"vodafone","proxy":"iproxy.vodafone.pt","port":"80","mmsc":"http://mms.vodafone.pt/servlets/mms","mmsproxy":"iproxy.vodafone.pt","mmsport":"80","type":["default","supl","mms"]}
+ ],
+ "3": [
+ {"carrier":"Optimus Internet","apn":"umts","type":["default"]},
+ {"carrier":"Optimus MMS","apn":"mms","mmsc":"http://mmsc:10021/mmsc","mmsproxy":"62.169.66.1","mmsport":"8799","type":["mms"]}
+ ],
+ "6": [
+ {"carrier":"tmn internet","apn":"internet","type":["default","supl"]},
+ {"carrier":"mms tmn","apn":"mmsc.tmn.pt","user":"tmn","password":"tmnnet","mmsc":"http://mmsc/","mmsproxy":"10.111.2.16","mmsport":"8080","type":["mms"]}
+ ]
+},
+"270": {
+ "1": [
+ {"carrier":"LUXGSM MMS","apn":"mms.pt.lu","user":"mms","password":"mms","mmsc":"http://mmsc.pt.lu","mmsproxy":"194.154.192.88","mmsport":"8080","type":["mms"]},
+ {"carrier":"LUXGSM WAP","apn":"wap.pt.lu","user":"wap","password":"wap","proxy":"194.154.192.98","port":"8080","type":["default","supl"]}
+ ],
+ "77": [
+ {"carrier":"Tango WAP","apn":"internet","user":"tango","password":"tango","proxy":"130.244.196.90","port":"8080","type":["default","supl"]},
+ {"carrier":"Tango MMS","apn":"mms","user":"tango","password":"tango","mmsc":"http://mms.tango.lu","mmsproxy":"212.66.75.3","mmsport":"8080","type":["mms"]}
+ ],
+ "99": [
+ {"carrier":"Vox Mobile","apn":"vox.lu","mmsc":"http://mms.vox.lu","mmsproxy":"212.88.139.44","mmsport":"8080","type":["default","supl","mms"]}
+ ]
+},
+"272": {
+ "1": [
+ {"carrier":"Vodafone IE","apn":"live.vodafone.com","type":["default","supl"]},
+ {"carrier":"Vodafone IE-MMS","apn":"mms.vodafone.net","mmsc":"http://www.vodafone.ie/mms","mmsproxy":"10.24.59.200","mmsport":"80","type":["mms"]}
+ ],
+ "2": [
+ {"carrier":"O2 Ireland","apn":"internet","proxy":"62.40.32.40","port":"8080","mmsc":"http://mmsc.mms.o2.ie:8002","mmsproxy":"62.40.32.40","mmsport":"8080","type":["default","supl","mms"]}
+ ],
+ "3": [
+ {"carrier":"Meteor Data","apn":"data.mymeteor.ie","type":["default","supl"]},
+ {"carrier":"Meteor","apn":"mms.mymeteor.ie","user":"my","password":"wap","mmsc":"http://mms.mymeteor.ie","mmsproxy":"10.85.85.85","mmsport":"8799","type":["default","supl","mms"]}
+ ],
+ "5": [
+ {"carrier":"3","apn":"3ireland.ie","mmsc":"http://mms.um.3ireland.ie:10021/mmsc","mmsproxy":"mms.3ireland.ie","mmsport":"8799","type":["default","supl","mms"]}
+ ],
+ "11": [
+ {"carrier":"Tesco","apn":"tescomobile.liffeytelecom.com","mmsc":"http://10.1.11.68/servlets/mms","mmsproxy":"10.1.11.19","mmsport":"8080","type":["default","supl","mms"]}
+ ]
+},
+"274": {
+ "1": [
+ {"carrier":"Siminn Internet","apn":"internet","proxy":"213.167.138.200","port":"8080","type":["default","supl"]},
+ {"carrier":"Siminn MMS","apn":"mms.simi.is","mmsc":"http://mms.simi.is/servlets/mms","mmsproxy":"213.167.138.200","mmsport":"8080","type":["mms"]}
+ ],
+ "2": [
+ {"carrier":"Vodafone Internet","apn":"gprs.is","type":["default","supl"]},
+ {"carrier":"Vodafone MMS","apn":"mms.gprs.is","mmsc":"http://mmsc.vodafone.is","mmsproxy":"10.22.0.10","mmsport":"8080","type":["mms"]}
+ ],
+ "11": [
+ {"carrier":"MMS Nova","apn":"mms.nova.is","mmsc":"http://mmsc.nova.is","mmsproxy":"10.10.2.60","mmsport":"8080","type":["mms"]},
+ {"carrier":"Net Nova","apn":"net.nova.is","proxy":"10.10.2.60","port":"8080","type":["default","supl"]}
+ ]
+},
+"278": {
+ "1": [
+ {"carrier":"Vodafone MT","apn":"internet","user":"internet","password":"internet","type":["default","supl"]},
+ {"carrier":"Vodafone MT-MMS","apn":"mms.vodafone.com.mt","mmsc":"http://mms.vodafone.com.mt/servlets/mms","mmsproxy":"10.12.0.3","mmsport":"8080","type":["mms"]}
+ ]
+},
+"280": {
+ "1": [
+ {"carrier":"CYTA","apn":"cytamobile","user":"user","password":"pass","mmsc":"http://mmsc.cyta.com.cy","mmsproxy":"212.31.96.161","mmsport":"8080","type":["default","supl","mms"]}
+ ],
+ "10": [
+ {"carrier":"MTN MMS","apn":"mms","user":"mms","password":"mms","mmsc":"http://mms.mtn.com.cy/mmsc","mmsproxy":"172.24.97.1","mmsport":"3130","type":["mms"]},
+ {"carrier":"MTN Wap","apn":"wap","user":"wap","password":"wap","proxy":"172.24.97.3","port":"3130","type":["default","supl"]}
+ ]
+},
+"283": {
+ "10": [
+ {"carrier":"Orange Armenia MMS","apn":"mms","mmsc":"http://mms/","mmsproxy":"192.168.220.251","mmsport":"3128","type":["mms"],"authtype":"1"},
+ {"carrier":"Orange Armenia Internet","apn":"Internet","type":["default"],"authtype":"1"}
+ ]
+},
+"284": {
+ "1": [
+ {"carrier":"M-Tel","apn":"wap-gprs.mtel.bg","type":["default","supl"]},
+ {"voicemail":"131","type":["operatorvariant"]},
+ {"carrier":"M-Tel MMS","apn":"mms-gprs.mtel.bg","user":"mtel","password":"mtel","mmsc":"http://mmsc/","mmsproxy":"10.150.0.33","mmsport":"8080","type":["mms"]}
+ ],
+ "3": [
+ {"carrier":"Vivacom WAP","apn":"wap.vivacom.bg","user":"wap","password":"wap","proxy":"192.168.123.123","port":"8080","type":["default","supl"]},
+ {"voicemail":"110","type":["operatorvariant"]},
+ {"carrier":"Vivacom MMS","apn":"mms.vivacom.bg","user":"mms","password":"mms","mmsc":"http://mmsc.vivacom.bg","mmsproxy":"192.168.123.123","mmsport":"8080","type":["mms"]}
+ ],
+ "5": [
+ {"carrier":"GPRS GLOBUL","apn":"globul","type":["default","supl"]},
+ {"voicemail":"120","type":["operatorvariant"]},
+ {"carrier":"GLOBUL MMS GPRS","apn":"mms.globul.bg","user":"mms","mmsc":"http://mmsc1.mms.globul.bg:8002","mmsproxy":"192.168.87.11","mmsport":"8004","type":["mms"]}
+ ]
+},
+"286": {
+ "1": [
+ {"carrier":"Turkcell Internet","apn":"internet","user":"gprs","password":"gprs","type":["default","supl"]},
+ {"carrier":"Turkcell MMS","apn":"mms","user":"mms","password":"mms","mmsc":"http://mms.turkcell.com.tr/servlets/mms","mmsproxy":"212.252.169.217","mmsport":"8080","type":["mms"]}
+ ],
+ "2": [
+ {"carrier":"Vodafone Internet","apn":"internet","user":"vodafone","password":"vodafone","type":["default","supl"]},
+ {"carrier":"Vodafone MMS","apn":"mms","mmsc":"http://217.31.233.18:6001/MM1Servlet","mmsproxy":"217.31.233.18","mmsport":"9401","type":["mms"]}
+ ],
+ "3": [
+ {"carrier":"Avea Internet","apn":"internet","user":"wap","password":"wap","type":["default","supl"]},
+ {"carrier":"Avea MMS","apn":"mms","user":"mms","password":"mms","mmsc":"http://mms.avea.com.tr/servlets/mms","mmsproxy":"213.161.151.201","mmsport":"8080","type":["mms"]}
+ ]
+},
+"290": {
+ "1": [
+ {"carrier":"Tele Internet","apn":"internet","type":["default","supl"]},
+ {"carrier":"Tele MMS","apn":"mms","mmsc":"http://mms.tele.gl/mms/wapenc","mmsproxy":"10.112.222.37","mmsport":"8080","type":["mms"]}
+ ]
+},
+"293": {
+ "40": [
+ {"carrier":"Vodafone live!","apn":"internet.simobil.si","user":"simobil","password":"internet","proxy":"80.95.224.17","port":"9201","type":["default","supl"]},
+ {"carrier":"Si.mobil MMS","apn":"mms.simobil.si","user":"simobil","password":"internet","mmsc":"http://mmc","mmsproxy":"80.95.224.46","mmsport":"9201","type":["mms"]}
+ ],
+ "41": [
+ {"carrier":"Planet","apn":"internet","user":"mobitel","password":"internet","proxy":"213.229.249.40","port":"8080","type":["default","supl"]},
+ {"carrier":"Mobitel MMS","apn":"internet","user":"mobitel","password":"internet","mmsc":"http://mms.mobitel.si/servlets/mms","mmsproxy":"213.229.249.40","mmsport":"8080","type":["mms"]}
+ ]
+},
+"294": {
+ "1": [
+ {"carrier":"T-Mobile MK Internet","apn":"internet","user":"internet","password":"t-mobile","type":["default","supl"]},
+ {"carrier":"T-Mobile MK MMS","apn":"mms","user":"mms","password":"mms","mmsc":"http://mms.t-mobile.com.mk","mmsproxy":"62.162.155.227","mmsport":"8080","type":["mms"]}
+ ],
+ "2": [
+ {"carrier":"Cosmofon","apn":"Internet","user":"Internet","password":"Internet","proxy":"http://wap.planet.mk","port":"8080","type":["default","supl"]},
+ {"carrier":"Cosmofon MMS","apn":"mms","mmsc":"http://195.167.65.220:8002","mmsproxy":"10.10.10.20","mmsport":"8080","type":["mms"]}
+ ],
+ "3": [
+ {"carrier":"Internet","apn":"vipoperator","user":"vipoperator","password":"vipoperator","proxy":"78.40.0.1","port":"8080","type":["default","supl"]},
+ {"carrier":"MMS","apn":"vipoperator.mms","user":"vipoperator","password":"vipoperator","mmsc":"http://mmsc.vipoperator.com.mk","mmsproxy":"78.40.0.1","mmsport":"8080","type":["mms"]}
+ ]
+},
+"297": {
+ "2": [
+ {"carrier":"T-Mobile MMS","apn":"mms","user":"38267","password":"38267","mmsc":"http://192.168.180.100/servlets/mms","mmsproxy":"10.0.5.19","mmsport":"8080","type":["mms"]},
+ {"carrier":"T-Mobile Internet","apn":"tmcg-wnw","user":"38267","password":"38267","proxy":"10.0.5.19","port":"8080","type":["default","supl"]}
+ ]
+},
+"302": {
+ "220": [
+ {"carrier":"TELUS","apn":"sp.telus.com","mmsc":"http://aliasredirect.net/proxy/mmsc","mmsproxy":"74.49.0.18","mmsport":"80","type":["default","supl","mms"]},
+ {"carrier":"Koodo","apn":"sp.koodo.com","proxy":"74.49.0.18","port":"80","mmsc":"http://aliasredirect.net/proxy/koodo/mmsc","mmsproxy":"74.49.0.18","mmsport":"80","type":["default","supl","mms"]}
+ ],
+ "320": [
+ {"carrier":"MOWAP","apn":"wap.davewireless.com","proxy":"10.100.3.4","port":"8080","type":["default","supl"]},
+ {"carrier":"MOMMS","apn":"mms.davewireless.com","mmsc":"http://mms.mobilicity.net","mmsproxy":"10.100.3.4","mmsport":"8080","type":["mms"]}
+ ],
+ "270": [
+ {"carrier":"MMS","apn":"mms.mobi.eastlink.ca","mmsc":"http://mmss.mobi.eastlink.ca","mmsproxy":"10.232.12.49","mmsport":"8080","type":["mms"]},
+ {"carrier":"Internet","apn":"wisp.mobi.eastlink.ca","type":["default","supl"]}
+ ],
+ "370": [
+ {"carrier":"Fido Internet","apn":"fido-core-appl1.apn","mmsc":"http://mms.fido.ca","mmsproxy":"205.151.11.13","mmsport":"80","type":["default","supl","mms"]},
+ {"carrier":"MTS-Roaming","apn":"sp.mts","mmsc":"http://mmsc2.mts.net/","mmsproxy":"209.4.229.90","mmsport":"9401","type":["default","supl","mms"]}
+ ],
+ "490": [
+ {"carrier":"Internet","apn":"internet.windmobile.ca","type":["default","supl"]},
+ {"carrier":"MMS","apn":"mms.windmobile.ca","mmsc":"http://mms.windmobile.ca","mmsproxy":"74.115.197.70","mmsport":"8080","type":["mms"]}
+ ],
+ "500": [
+ {"carrier":"Videotron","apn":"media.videotron","mmsc":"http://media.videotron.com","type":["default","supl","mms"]}
+ ],
+ "610": [
+ {"carrier":"Bell Internet","apn":"pda.bell.ca","mmsc":"http://mms.bell.ca/mms/wapenc","mmsproxy":"web.wireless.bell.ca","mmsport":"80","type":["default","supl","mms"]}
+ ],
+ "660": [
+ {"carrier":"MTS","apn":"sp.mts","mmsc":"http://mmsc2.mts.net/","mmsproxy":"209.4.229.90","mmsport":"9401","type":["default","supl","mms"]}
+ ],
+ "720": [
+ {"carrier":"Rogers","apn":"rogers-core-appl1.apn","mmsc":"http://mms.gprs.rogers.com","mmsproxy":"10.128.1.69","mmsport":"80","type":["default","supl","mms"]},
+ {"carrier":"Chatr Internet","apn":"chatrweb.apn","mmsc":"http://mms.chatrwireless.com","mmsproxy":"205.151.11.11","mmsport":"80","type":["default","mms"]},
+ {"carrier":"SpeakOut","apn":"goam.com","user":"wapuser1","password":"wap","proxy":"10.128.1.69","port":"80","mmsc":"http://mms.gprs.rogers.com","mmsproxy":"10.128.1.69","mmsport":"80","type":["default","mms"]}
+ ],
+ "780": [
+ {"carrier":"SaskTel","apn":"pda.stm.sk.ca","mmsc":"http://mms.sasktel.com","mmsproxy":"mig.sasktel.com","mmsport":"80","type":["default","supl","mms"]}
+ ]
+},
+"310": {
+ "30": [
+ {"carrier":"myBlue Pix","apn":"mmswap.centennialwireless.com","mmsc":"http://mms.myblue.com/servlets/mms","mmsproxy":"63.99.231.135","mmsport":"8080","type":["mms"]},
+ {"carrier":"Internet","apn":"private.centennialwireless.com","user":"privuser","password":"priv","type":["default","supl"]}
+ ],
+ "80": [
+ {"carrier":"CorrMMS","apn":"corrmms","mmsc":"http://mms.iot1.com/corr/mms.php","mmsproxy":"66.255.55.23","mmsport":"80","type":["mms"]}
+ ],
+ "90": [
+ {"carrier":"Internet","apn":"isp","type":["default","supl"]},
+ {"carrier":"MMS","apn":"mms","mmsc":"http://mms.edgemobile.net/mmsc","mmsproxy":"12.108.12.13","mmsport":"3128","type":["mms"]},
+ {"carrier":"Edge MMS Prepay","apn":"ppmms","mmsc":"http://mms.edgemobile.net/mmsc","mmsproxy":"12.108.12.13","mmsport":"3128","type":["mms"]}
+ ],
+ "100": [
+ {"carrier":"PLAT-OTA-MMS","apn":"plateaumms","password":"mmsc","mmsc":"208.254.124.11:8514","mmsproxy":"208.254.124.11","mmsport":"8080","type":["mms"]},
+ {"carrier":"PLATWEB","apn":"plateauweb","type":["default","supl"]}
+ ],
+ "130": [
+ {"carrier":"My Multi Media","apn":"mms.c1.ama","user":"cell1mms","password":"cell1","mmsc":"http://mms.iot1.com/amarillo/mms.php","type":["mms"]}
+ ],
+ "160": [
+ {"carrier":"T-Mobile US 160","apn":"epc.tmobile.com","mmsc":"http://mms.msg.eng.t-mobile.com/mms/wapenc","type":["default","supl","mms"]},
+ {"voicemail":"123","type":["operatorvariant"]}
+ ],
+ "170": [
+ {"carrier":"DataConnect","apn":"isp.cingular","type":["default","supl"]},
+ {"carrier":"Cingular MMS","apn":"wap.cingular","user":"WAP@CINGULARGPRS.COM","password":"CINGULAR1","mmsc":"http://mmsc.cingular.com","mmsproxy":"66.209.11.32","mmsport":"8080","type":["mms"]}
+ ],
+ "200": [
+ {"carrier":"T-Mobile US 200","apn":"epc.tmobile.com","mmsc":"http://mms.msg.eng.t-mobile.com/mms/wapenc","type":["default","supl","mms"]},
+ {"voicemail":"123","type":["operatorvariant"]}
+ ],
+ "210": [
+ {"carrier":"T-Mobile US 210","apn":"epc.tmobile.com","mmsc":"http://mms.msg.eng.t-mobile.com/mms/wapenc","type":["default","supl","mms"]},
+ {"voicemail":"123","type":["operatorvariant"]}
+ ],
+ "220": [
+ {"carrier":"T-Mobile US 220","apn":"epc.tmobile.com","mmsc":"http://mms.msg.eng.t-mobile.com/mms/wapenc","type":["default","supl","mms"]},
+ {"voicemail":"123","type":["operatorvariant"]}
+ ],
+ "230": [
+ {"carrier":"T-Mobile US 230","apn":"epc.tmobile.com","mmsc":"http://mms.msg.eng.t-mobile.com/mms/wapenc","type":["default","supl","mms"]},
+ {"voicemail":"123","type":["operatorvariant"]}
+ ],
+ "240": [
+ {"carrier":"T-Mobile US 240","apn":"epc.tmobile.com","mmsc":"http://mms.msg.eng.t-mobile.com/mms/wapenc","type":["default","supl","mms"]},
+ {"voicemail":"123","type":["operatorvariant"]}
+ ],
+ "250": [
+ {"carrier":"T-Mobile US 250","apn":"epc.tmobile.com","mmsc":"http://mms.msg.eng.t-mobile.com/mms/wapenc","type":["default","supl","mms"]},
+ {"voicemail":"123","type":["operatorvariant"]}
+ ],
+ "260": [
+ {"carrier":"T-Mobile US","apn":"epc.tmobile.com","mmsc":"http://mms.msg.eng.t-mobile.com/mms/wapenc","type":["default","supl","mms"]},
+ {"voicemail":"123","type":["operatorvariant"]}
+ ],
+ "270": [
+ {"carrier":"T-Mobile US 270","apn":"epc.tmobile.com","mmsc":"http://mms.msg.eng.t-mobile.com/mms/wapenc","type":["default","supl","mms"]},
+ {"voicemail":"123","type":["operatorvariant"]}
+ ],
+ "310": [
+ {"carrier":"T-Mobile US 310","apn":"epc.tmobile.com","mmsc":"http://mms.msg.eng.t-mobile.com/mms/wapenc","type":["default","supl","mms"]},
+ {"voicemail":"123","type":["operatorvariant"]}
+ ],
+ "380": [
+ {"carrier":"Cingular 380 ATT","apn":"proxy","mmsc":"http://mmsc.cingular.com/","mmsproxy":"wireless.cingular.com","type":["default","supl","mms"]},
+ {"carrier":"AWS MMS","apn":"proxy","mmsc":"http://mmsc.mymmode.com","mmsproxy":"10.250.250.55","mmsport":"8080","type":["mms"]}
+ ],
+ "390": [
+ {"carrier":"Celloneet MMS","apn":"mms.celloneet.com","user":"user1@mms.celloneet.com","password":"celloneet","mmsc":"http://mms.celloneet.com/servlets/mms","mmsproxy":"63.99.231.135","mmsport":"8080","type":["mms"]}
+ ],
+ "410": [
+ {"carrier":"Cingular 410","apn":"wap.cingular","user":"WAP@CINGULARGPRS.COM","password":"CINGULAR1","server":"cingulargprs.com","mmsc":"http://mmsc.cingular.com/","mmsproxy":"wireless.cingular.com","type":["default","supl","mms"]}
+ ],
+ "420": [
+ {"carrier":"CBW MMS","apn":"wap.gocbw.com","mmsc":"http://mms.gocbw.com:8088/mms","mmsproxy":"216.68.79.202","mmsport":"80","type":["mms"]}
+ ],
+ "470": [
+ {"carrier":"DataConnect","apn":"isp.cingular","type":["default","supl"]},
+ {"carrier":"MediaNet","apn":"wap.cingular","user":"WAP@CINGULARGPRS.COM","password":"CINGULAR1","mmsc":"http://mmsc.cingular.com","mmsproxy":"66.209.11.32","mmsport":"8080","type":["default","supl","mms"]}
+ ],
+ "480": [
+ {"carrier":"DataConnect","apn":"isp.cingular","type":["default","supl"]},
+ {"carrier":"MediaNet","apn":"wap.cingular","user":"WAP@CINGULARGPRS.COM","password":"CINGULAR1","mmsc":"http://mmsc.cingular.com","mmsproxy":"66.209.11.32","mmsport":"8080","type":["default","supl","mms"]}
+ ],
+ "490": [
+ {"carrier":"T-Mobile US 490","apn":"epc.tmobile.com","mmsc":"http://mms.msg.eng.t-mobile.com/mms/wapenc","type":["default","supl","mms"]},
+ {"voicemail":"123","type":["operatorvariant"]},
+ {"carrier":"GoodCall Picture Message","apn":"good.call","mmsc":"http://mms.suncom.net:8088/mms","mmsproxy":"66.150.33.125","mmsport":"8080","type":["mms"]},
+ {"carrier":"Suncom MMS","apn":"mms","mmsc":"http://mms.suncom.net:8088/mms","mmsproxy":"66.150.33.125","mmsport":"8080","type":["mms"]}
+ ],
+ "560": [
+ {"carrier":"DobsonMMS","apn":"dobsoncellularwap","mmsc":"http://mmsc","mmsproxy":"172.23.1.252","mmsport":"8799","type":["mms"]}
+ ],
+ "570": [
+ {"carrier":"ChinookMMS","apn":"wapgw.chinookwireless.net","mmsc":"http://mms.cellonenation.net/mms/","mmsproxy":"204.181.155.195","mmsport":"8080","type":["mms"]}
+ ],
+ "580": [
+ {"carrier":"T-Mobile US 580","apn":"epc.tmobile.com","mmsc":"http://mms.msg.eng.t-mobile.com/mms/wapenc","type":["default","supl","mms"]},
+ {"voicemail":"123","type":["operatorvariant"]}
+ ],
+ "590": [
+ {"carrier":"CellularOne MMS","apn":"cellular1wap","mmsc":"http://mmsc","mmsproxy":"172.23.1.252","mmsport":"8799","type":["mms"]}
+ ],
+ "610": [
+ {"carrier":"EpicINT","apn":"internet.epictouch","type":["default","supl"]},
+ {"carrier":"EpicMMS","apn":"mms.epictouch","mmsc":"http://mmsc.westlinkcom.com/servlets/mms","mmsproxy":"63.99.231.135","mmsport":"8080","type":["mms"]}
+ ],
+ "660": [
+ {"carrier":"T-Mobile US 660","apn":"epc.tmobile.com","mmsc":"http://mms.msg.eng.t-mobile.com/mms/wapenc","type":["default","supl","mms"]},
+ {"voicemail":"123","type":["operatorvariant"]}
+ ],
+ "770": [
+ {"carrier":"WEB 2","apn":"i2.iwireless.com","type":["default","supl"]},
+ {"carrier":"Picture Messaging","apn":"wap1.iwireless.com","mmsc":"http://mmsc.iwireless.dataonair.net:6672","mmsproxy":"209.4.229.31","mmsport":"9401","type":["mms"]},
+ {"carrier":"PIAPicture Messaging","apn":"wap9.iwireless.com","mmsc":"http://mmsc.iwireless.dataonair.net:6672","mmsproxy":"209.4.229.32","mmsport":"9401","type":["mms"]}
+ ],
+ "800": [
+ {"carrier":"T-Mobile US 800","apn":"epc.tmobile.com","mmsc":"http://mms.msg.eng.t-mobile.com/mms/wapenc","type":["default","supl","mms"]},
+ {"voicemail":"123","type":["operatorvariant"]}
+ ],
+ "840": [
+ {"carrier":"Internet","apn":"isp","type":["default","supl"]},
+ {"carrier":"MMS","apn":"mms","mmsc":"http://mms.edgemobile.net/mmsc","mmsproxy":"12.108.12.13","mmsport":"3128","type":["mms"]},
+ {"carrier":"Edge MMS Prepay","apn":"ppmms","mmsc":"http://mms.edgemobile.net/mmsc","mmsproxy":"12.108.12.13","mmsport":"3128","type":["mms"]}
+ ],
+ "880": [
+ {"carrier":"DTC MMS","apn":"mms.adv.com","mmsc":"http://mms.iot1.com/advantage/mms.php","type":["mms"]}
+ ],
+ "910": [
+ {"carrier":"WOW_WAP","apn":"wap.firstcellular.com","mmsc":"mms.firstcellular.net/mmsc","mmsproxy":"10.101.1.5","mmsport":"3128","type":["default","supl","mms"]}
+ ]
+},
+"311": {
+ "190": [
+ {"carrier":"MMS","apn":"wap.cellular1.net","mmsc":"http://mms.cellular1.net/ecit/mms.php","type":["mms"]}
+ ],
+ "210": [
+ {"carrier":"Farmers GPRS","apn":"internet.farmerswireless.com","type":["default","supl"]},
+ {"carrier":"Farmers MMS","apn":"mms.farmers.com","mmsc":"172.16.0.37:8514","type":["mms"]}
+ ]
+},
+"330": {
+ "110": [
+ {"carrier":"INTERNET CLARO","apn":"internet.claropr.com","user":"","password":"","authtype":"1","type":["default","supl","dun"]},
+ {"carrier":"MMS CLARO","apn":"mms.claropr.com","user":"","password":"","mmsproxy":"10.50.38.3","mmsport":"8799","mmsc":"http://mmsg.claropr.com:10021/mmsc","authtype":"1","type":["mms"]}
+ ]
+},
+"334": {
+ "20": [
+ {"carrier":"Internet","apn":"internet.itelcel.com","user":"webgprs","password":"webgprs2002","authtype":"1","type":["default","supl","dun"]},
+ {"enableStrict7BitEncodingForSms":true,"type":["operatorvariant"]},
+ {"carrier":"Mensajes Multimedia","apn":"mms.itelcel.com","user":"mmsgprs","password":"mmsgprs2003","mmsc":"http://mms.itelcel.com/servlets/mms","mmsproxy":"148.233.151.240","mmsport":"8080","authtype":"1","type":["mms"]}
+ ],
+ "3": [
+ {"carrier":"movistar Internet","apn":"internet.movistar.mx","user":"movistar","password":"movistar","authtype":"1","type":["default","supl","dun"]},
+ {"voicemail":"*86","enableStrict7BitEncodingForSms":true,"type":["operatorvariant"]},
+ {"carrier":"movistar MMS","apn":"mms.movistar.mx","user":"movistar","password":"movistar","mmsc":"http://mms.movistar.mx","mmsproxy":"10.2.20.1","mmsport":"80","authtype":"1","type":["mms"]}
+ ],
+ "30": [
+ {"carrier":"movistar Internet","apn":"internet.movistar.mx","user":"movistar","password":"movistar","authtype":"1","type":["default","supl","dun"]},
+ {"enableStrict7BitEncodingForSms":true,"type":["operatorvariant"]},
+ {"carrier":"movistar MMS","apn":"mms.movistar.mx","user":"movistar","password":"movistar","mmsc":"http://mms.movistar.mx","mmsproxy":"10.2.20.1","mmsport":"80","authtype":"1","type":["mms"]}
+ ],
+ "50": [
+ {"carrier":"Iusacell Internet","apn":"web.iusacellgsm.mx","user":"Iusacellgsm","password":"Iusacellgsm","authtype":"0","type":["default","supl","dun"]},
+ {"enableStrict7BitEncodingForSms":true,"type":["operatorvariant"]},
+ {"carrier":"Iusacell MMS","apn":"mms.iusacellgsm.mx","user":"mmsiusacellgsm","password":"mmsiusacellgsm","mmsc":"http://mms.iusacell3g.com/","mmsproxy":"192.200.1.40","mmsport":"80","authtype":"0","type":["mms"]}
+ ]
+},
+"338": {
+ "50": [
+ {"carrier":"INTERNET Digicel","apn":"web.digiceljamaica.com","authtype":"1","type":["default","supl","dun"]},
+ {"carrier":"MMS Digicel","apn":"wap","user":"","password":"","mmsproxy":"172.16.7.12","mmsport":"8080","mmsc":"http://mms.digicelgroup.com","authtype":"1","type":["mms"]}
+ ],
+ "18": [
+ {"carrier":"Lime Internet Postpaid","apn":"internet","user":"","password":"","authtype":"1","type":["default","supl","dun"]},
+ {"carrier":"Lime Postpaid MMS","apn":"multimedia","user":"","password":"","mmsproxy":"10.20.5.34","mmsport":"8799","mmsc":"http://mmsc","authtype":"1","type":["mms"]}
+ ],
+ "70": [
+ {"carrier":"Claro Web","apn":"internet.ideasclaro.com.jm","user":"","password":"","authtype":"1","type":["default","supl","dun"]},
+ {"carrier":"Claro MMS","apn":"mms.ideasclaro.com.jm","user":"","password":"","mmsproxy":"190.80.147.118","mmsport":"8080","mmsc":"http://mms.ideasclaro.com.jm/mms/wapenc","authtype":"1","type":["mms"]}
+ ],
+ "180": [
+ {"carrier":"Lime Internet Postpaid","apn":"internet","user":"","password":"","authtype":"1","type":["default","supl","dun"]},
+ {"carrier":"Lime Postpaid MMS","apn":"multimedia","user":"","password":"","mmsproxy":"10.20.5.34","mmsport":"8799","mmsc":"http://mmsc","authtype":"1","type":["mms"]}
+ ]
+},
+"340": {
+ "1": [
+ {"carrier":"Orange World Caraïbe","apn":"orangewap","user":"orange","password":"wap","proxy":"10.0.0.10","port":"8082","type":["default","supl"]},
+ {"carrier":"Orange MMS Caraïbe","apn":"orangewap","user":"orange","password":"orange","mmsc":"http://193.251.160.246/servlets/mms","mmsproxy":"10.0.0.10","mmsport":"8082","type":["mms"]},
+ {"carrier":"Orangeweb","apn":"orangeweb","user":"orange","password":"orange","type":["default"]}
+ ],
+ "20": [
+ {"carrier":"Digicel FR","apn":"wap.digicelfr.com","user":"wap","password":"wap","mmsc":"http://mmc.digicelfr.com/servlets/mms","mmsproxy":"172.20.6.12","mmsport":"8080","type":["default","supl","mms"]}
+ ]
+},
+"342": {
+ "60": [
+ {"carrier":"Lime Internet Postpaid","apn":"internet","user":"","password":"","type":["default","supl","dun"]},
+ {"carrier":"Lime Postpaid MMS","apn":"multimedia","user":"","password":"","mmsproxy":"10.20.5.34","mmsport":"8799","mmsc":"http://mmsc","type":["mms"]}
+ ]
+},
+"344": {
+ "92": [
+ {"carrier":"Lime Internet Postpaid","apn":"internet","user":"","password":"","type":["default","supl","dun"]},
+ {"carrier":"Lime Postpaid MMS","apn":"multimedia","user":"","password":"","mmsproxy":"10.20.5.34","mmsport":"8799","mmsc":"http://mmsc","type":["mms"]}
+ ]
+},
+"346": {
+ "14": [
+ {"carrier":"Lime Internet Postpaid","apn":"internet","user":"","password":"","type":["default","supl","dun"]},
+ {"carrier":"Lime Postpaid MMS","apn":"multimedia","user":"","password":"","mmsproxy":"10.20.5.34","mmsport":"8799","mmsc":"http://mmsc","type":["mms"]}
+ ]
+},
+"348": {
+ "17": [
+ {"carrier":"Lime Internet Postpaid","apn":"internet","user":"","password":"","type":["default","supl","dun"]},
+ {"carrier":"Lime Postpaid MMS","apn":"multimedia","user":"","password":"","mmsproxy":"10.20.5.34","mmsport":"8799","mmsc":"http://mmsc","type":["mms"]}
+ ]
+},
+"352": {
+ "11": [
+ {"carrier":"Lime Internet Postpaid","apn":"internet","user":"","password":"","type":["default","supl","dun"]},
+ {"carrier":"Lime Postpaid MMS","apn":"multimedia","user":"","password":"","mmsproxy":"10.20.5.34","mmsport":"8799","mmsc":"http://mmsc","type":["mms"]}
+ ]
+},
+"354": {
+ "86": [
+ {"carrier":"Lime Internet Postpaid","apn":"internet","user":"","password":"","type":["default","supl","dun"]},
+ {"carrier":"Lime Postpaid MMS","apn":"multimedia","user":"","password":"","mmsproxy":"10.20.5.34","mmsport":"8799","mmsc":"http://mmsc","type":["mms"]}
+ ]
+},
+"356": {
+ "11": [
+ {"carrier":"Lime Internet Postpaid","apn":"internet","user":"","password":"","type":["default","supl","dun"]},
+ {"carrier":"Lime Postpaid MMS","apn":"multimedia","user":"","password":"","mmsproxy":"10.20.5.34","mmsport":"8799","mmsc":"http://mmsc","type":["mms"]}
+ ]
+},
+"358": {
+ "11": [
+ {"carrier":"Lime Internet Postpaid","apn":"internet","user":"","password":"","type":["default","supl","dun"]},
+ {"carrier":"Lime Postpaid MMS","apn":"multimedia","user":"","password":"","mmsproxy":"10.20.5.34","mmsport":"8799","mmsc":"http://mmsc","type":["mms"]}
+ ]
+},
+"360": {
+ "11": [
+ {"carrier":"Lime Internet Postpaid","apn":"internet","user":"","password":"","type":["default","supl","dun"]},
+ {"carrier":"Lime Postpaid MMS","apn":"multimedia","user":"","password":"","mmsproxy":"10.20.5.34","mmsport":"8799","mmsc":"http://mmsc","type":["mms"]}
+ ]
+},
+"363": {
+ "2": [
+ {"carrier":"INTERNET Aruba","apn":"web.digicelaruba.com","user":"","password":"","authtype":"1","type":["default","supl","dun"]},
+ {"carrier":"MMS Digicel","apn":"wap","user":"","password":"","mmsproxy":"172.16.7.12","mmsport":"8080","mmsc":"http://mms.digicelgroup.com","authtype":"1","type":["mms"]}
+ ],
+ "20": [
+ {"carrier":"INTERNET Aruba","apn":"web.digicelaruba.com","user":"","password":"","authtype":"1","type":["default","supl","dun"]},
+ {"carrier":"MMS Digicel","apn":"wap","user":"","password":"","mmsproxy":"172.16.7.12","mmsport":"8080","mmsc":"http://mms.digicelgroup.com","authtype":"1","type":["mms"]}
+ ]
+},
+"365": {
+ "84": [
+ {"carrier":"Lime Internet Postpaid","apn":"internet","user":"","password":"","type":["default","supl","dun"]},
+ {"carrier":"Lime Postpaid MMS","apn":"multimedia","user":"","password":"","mmsproxy":"10.20.5.34","mmsport":"8799","mmsc":"http://mmsc","type":["mms"]}
+ ]
+},
+"366": {
+ "11": [
+ {"carrier":"Lime Internet Postpaid","apn":"internet","user":"","password":"","type":["default","supl","dun"]},
+ {"carrier":"Lime Postpaid MMS","apn":"multimedia","user":"","password":"","mmsproxy":"10.20.5.34","mmsport":"8799","mmsc":"http://mmsc","type":["mms"]}
+ ]
+},
+"368": {
+ "1": [
+ {"carrier":"Internet","apn":"internet","authtype":"1","type":["default","supl","dun"]},
+ {"carrier":"Cubacel MMS","apn":"mms","mmsproxy":"200.13.145.52","mmsport":"8080","mmsc":"http://mms.cubacel.cu/","authtype":"1","type":["mms"]}
+ ]
+},
+"370": {
+ "1": [
+ {"carrier":"Orange net","apn":"orangenet.com.do","user":"","password":"","authtype":"1","type":["default","supl","dun"]},
+ {"voicemail":"*777","type":["operatorvariant"]},
+ {"carrier":"Orange MMS","apn":"orangeworld","user":"orange","password":"orange","mmsproxy":"172.16.126.70","mmsport":"8080","mmsc":"http://mms.orange.com.do/servlets/mms","authtype":"1","type":["mms"]},
+ {"carrier":"Orange DO MMS","apn":"orangeworld","mmsc":"http://mmr.orangewi.com/servlets/mms","mmsproxy":"172.16.126.70","mmsport":"8080","type":["mms"]},
+ {"carrier":"Orange DO","apn":"orangenet.com.do","type":["default"]}
+ ],
+ "2": [
+ {"carrier":"INTERNET CLARO","apn":"internet.ideasclaro.com.do","user":"","password":"","authtype":"1","type":["default","supl","dun"]},
+ {"voicemail":"*99","type":["operatorvariant"]},
+ {"carrier":"MMS CLARO","apn":"mms.ideasclaro.com.do","user":"","password":"","mmsproxy":"190.80.147.8","mmsport":"8080","mmsc":"http://mms.ideasclaro.com.do/mms/wapenc","authtype":"1","type":["mms"]}
+ ],
+ "4": [
+ {"carrier":"Viva Edge","apn":"edge.viva.net.do","proxy":"192.168.16.10","port":"9401","user":"viva","password":"viva","authtype":"1","type":["default","supl","dun"]},
+ {"carrier":"Viva MMS","apn":"mms.viva.net.do","user":"viva","password":"viva","mmsproxy":"192.168.16.10","mmsport":"9401","mmsc":"http://10.200.16.4/mms/wapenc","authtype":"1","type":["mms"]}
+ ]
+},
+"374": {
+ "12": [
+ {"carrier":"Bmobile internet","apn":"internet","user":"","password":"","authtype":"1","type":["default","supl","dun"]},
+ {"carrier":"Bmobile mms","apn":"mms","user":"","password":"","mmsproxy":"192.168.210.104","mmsport":"8080","mmsc":"http://192.168.210.104/mmrelay.app","authtype":"1","type":["mms"]}
+ ],
+ "120": [
+ {"carrier":"Bmobile internet","apn":"internet","user":"","password":"","authtype":"1","type":["default","supl","dun"]},
+ {"carrier":"Bmobile mms","apn":"mms","user":"","password":"","mmsproxy":"192.168.210.104","mmsport":"8080","mmsc":"http://192.168.210.104/mmrelay.app","authtype":"1","type":["mms"]}
+ ],
+ "121": [
+ {"carrier":"Bmobile internet","apn":"internet","user":"","password":"","authtype":"1","type":["default","supl","dun"]},
+ {"carrier":"Bmobile mms","apn":"mms","user":"","password":"","mmsproxy":"192.168.210.104","mmsport":"8080","mmsc":"http://192.168.210.104/mmrelay.app","authtype":"1","type":["mms"]}
+ ],
+ "122": [
+ {"carrier":"Bmobile internet","apn":"internet","user":"","password":"","authtype":"1","type":["default","supl","dun"]},
+ {"carrier":"Bmobile mms","apn":"mms","user":"","password":"","mmsproxy":"192.168.210.104","mmsport":"8080","mmsc":"http://192.168.210.104/mmrelay.app","authtype":"1","type":["mms"]}
+ ],
+ "123": [
+ {"carrier":"Bmobile internet","apn":"internet","user":"","password":"","authtype":"1","type":["default","supl","dun"]},
+ {"carrier":"Bmobile mms","apn":"mms","user":"","password":"","mmsproxy":"192.168.210.104","mmsport":"8080","mmsc":"http://192.168.210.104/mmrelay.app","authtype":"1","type":["mms"]}
+ ],
+ "124": [
+ {"carrier":"Bmobile internet","apn":"internet","user":"","password":"","authtype":"1","type":["default","supl","dun"]},
+ {"carrier":"Bmobile mms","apn":"mms","user":"","password":"","mmsproxy":"192.168.210.104","mmsport":"8080","mmsc":"http://192.168.210.104/mmrelay.app","authtype":"1","type":["mms"]}
+ ],
+ "125": [
+ {"carrier":"Bmobile internet","apn":"internet","user":"","password":"","authtype":"1","type":["default","supl","dun"]},
+ {"carrier":"Bmobile mms","apn":"mms","user":"","password":"","mmsproxy":"192.168.210.104","mmsport":"8080","mmsc":"http://192.168.210.104/mmrelay.app","authtype":"1","type":["mms"]}
+ ],
+ "126": [
+ {"carrier":"Bmobile internet","apn":"internet","user":"","password":"","authtype":"1","type":["default","supl","dun"]},
+ {"carrier":"Bmobile mms","apn":"mms","user":"","password":"","mmsproxy":"192.168.210.104","mmsport":"8080","mmsc":"http://192.168.210.104/mmrelay.app","authtype":"1","type":["mms"]}
+ ],
+ "127": [
+ {"carrier":"Bmobile internet","apn":"internet","user":"","password":"","authtype":"1","type":["default","supl","dun"]},
+ {"carrier":"Bmobile mms","apn":"mms","user":"","password":"","mmsproxy":"192.168.210.104","mmsport":"8080","mmsc":"http://192.168.210.104/mmrelay.app","authtype":"1","type":["mms"]}
+ ],
+ "128": [
+ {"carrier":"Bmobile internet","apn":"internet","user":"","password":"","authtype":"1","type":["default","supl","dun"]},
+ {"carrier":"Bmobile mms","apn":"mms","user":"","password":"","mmsproxy":"192.168.210.104","mmsport":"8080","mmsc":"http://192.168.210.104/mmrelay.app","authtype":"1","type":["mms"]}
+ ],
+ "129": [
+ {"carrier":"Bmobile internet","apn":"internet","user":"","password":"","authtype":"1","type":["default","supl","dun"]},
+ {"carrier":"Bmobile mms","apn":"mms","user":"","password":"","mmsproxy":"192.168.210.104","mmsport":"8080","mmsc":"http://192.168.210.104/mmrelay.app","authtype":"1","type":["mms"]}
+ ],
+ "13": [
+ {"carrier":"INTERNET Trinidad","apn":"web.digiceltt.com","user":"","password":"","authtype":"1","type":["default","supl","dun"]},
+ {"carrier":"MMS Trinidad","apn":"wap.digiceltt.com","user":"wap","password":"wap","mmsproxy":"172.20.6.12","mmsport":"8080","mmsc":"http://mmc.digiceltt.com/servlets/mms","authtype":"1","type":["mms"]}
+ ],
+ "130": [
+ {"carrier":"INTERNET Trinidad","apn":"web.digiceltt.com","user":"","password":"","authtype":"1","type":["default","supl","dun"]},
+ {"carrier":"MMS Trinidad","apn":"wap.digiceltt.com","user":"wap","password":"wap","mmsproxy":"172.20.6.12","mmsport":"8080","mmsc":"http://mmc.digiceltt.com/servlets/mms","authtype":"1","type":["mms"]}
+ ]
+},
+"376": {
+ "35": [
+ {"carrier":"Lime Internet Postpaid","apn":"internet","user":"","password":"","type":["default","supl","dun"]},
+ {"carrier":"Lime Postpaid MMS","apn":"multimedia","user":"","password":"","mmsproxy":"10.20.5.34","mmsport":"8799","mmsc":"http://mmsc","type":["mms"]}
+ ]
+},
+"401": {
+ "1": [
+ {"carrier":"Beeline Internet","apn":"internet.beeline.kz","user":"@internet.beeline","password":"beeline","type":["default","supl"]},
+ {"carrier":"Beeline MMS","apn":"mms.beeline.kz","user":"@mms.beeline","password":"beeline","mmsc":"http://mms.beeline.kz/mms/wapenc","mmsproxy":"172.27.6.93","mmsport":"8080","type":["mms"]}
+ ],
+ "2": [
+ {"carrier":"Kcell Internet","apn":"internet","type":["default","supl"]},
+ {"carrier":"Kcell MMS","apn":"mms","mmsc":"http://mms.kcell.kz/post","mmsproxy":"195.47.255.7","mmsport":"8080","type":["mms"]}
+ ],
+ "77": [
+ {"carrier":"Tele2 Internet","apn":"internet","type":["default","supl"]},
+ {"carrier":"Tele2 MMS","apn":"mms","mmsc":"http://mms.neogsm.kz/mms/wapenc","mmsproxy":"10.1.26.10","mmsport":"8080","type":["mms"]}
+ ]
+},
+"404": {
+ "1": [
+ {"carrier":"Vodafone MMS","apn":"portalnmms","mmsc":"http://mms1.live.vodafone.in/mms/","mmsproxy":"10.10.1.100","mmsport":"9401","type":["mms"]},
+ {"carrier":"Vodafone Connect","apn":"www","type":["default","supl"]},
+ {"carrier":"VodafoneLive!","apn":"portalnmms","proxy":"10.10.1.100","port":"9401","type":["default","supl"]}
+ ],
+ "2": [
+ {"carrier":"Airtel","apn":"airtelgprs.com","type":["default","supl"]},
+ {"carrier":"Airtel MMS","apn":"airtelmms.com","mmsc":"http://100.1.201.171:10021/mmsc","mmsproxy":"100.1.201.172","mmsport":"8799","type":["mms"]}
+ ],
+ "3": [
+ {"carrier":"Airtel","apn":"airtelgprs.com","type":["default","supl"]},
+ {"carrier":"Airtel MMS","apn":"airtelmms.com","mmsc":"http://100.1.201.171:10021/mmsc","mmsproxy":"100.1.201.172","mmsport":"8799","type":["mms"]}
+ ],
+ "4": [
+ {"carrier":"IDEA","apn":"internet","type":["default","supl"]},
+ {"carrier":"IDEA MMS","apn":"mmsc","mmsc":"http://10.4.42.21:8002/","mmsproxy":"10.4.42.15","mmsport":"8080","type":["mms"]}
+ ],
+ "5": [
+ {"carrier":"Vodafone MMS","apn":"portalnmms","mmsc":"http://mms1.live.vodafone.in/mms/","mmsproxy":"10.10.1.100","mmsport":"9401","type":["mms"]},
+ {"carrier":"Vodafone Connect","apn":"www","type":["default","supl"]},
+ {"carrier":"VodafoneLive!","apn":"portalnmms","proxy":"10.10.1.100","port":"9401","type":["default","supl"]}
+ ],
+ "7": [
+ {"carrier":"IDEA","apn":"internet","type":["default","supl"]},
+ {"carrier":"IDEA MMS","apn":"mmsc","mmsc":"http://10.4.42.21:8002/","mmsproxy":"10.4.42.15","mmsport":"8080","type":["mms"]}
+ ],
+ "9": [
+ {"carrier":"Reliance RTel MMS","apn":"MMS","mmsc":"http://10.239.221.47/mms/","mmsproxy":"10.239.221.7","mmsport":"8080","type":["mms"]},
+ {"carrier":"Reliance RTel","apn":"SMARTNET","type":["default","supl"]}
+ ],
+ "10": [
+ {"carrier":"Airtel","apn":"airtelgprs.com","type":["default","supl"]},
+ {"carrier":"Airtel MMS","apn":"airtelmms.com","mmsc":"http://100.1.201.171:10021/mmsc","mmsproxy":"100.1.201.172","mmsport":"8799","type":["mms"]}
+ ],
+ "11": [
+ {"carrier":"Vodafone MMS","apn":"portalnmms","mmsc":"http://mms1.live.vodafone.in/mms/","mmsproxy":"10.10.1.100","mmsport":"9401","type":["mms"]},
+ {"carrier":"Vodafone Connect","apn":"www","type":["default","supl"]},
+ {"carrier":"VodafoneLive!","apn":"portalnmms","proxy":"10.10.1.100","port":"9401","type":["default","supl"]}
+ ],
+ "12": [
+ {"carrier":"IDEA","apn":"internet","type":["default","supl"]},
+ {"carrier":"IDEA MMS","apn":"mmsc","mmsc":"http://10.4.42.21:8002/","mmsproxy":"10.4.42.15","mmsport":"8080","type":["mms"]}
+ ],
+ "13": [
+ {"carrier":"Vodafone MMS","apn":"portalnmms","mmsc":"http://mms1.live.vodafone.in/mms/","mmsproxy":"10.10.1.100","mmsport":"9401","type":["mms"]},
+ {"carrier":"Vodafone Connect","apn":"www","type":["default","supl"]},
+ {"carrier":"VodafoneLive!","apn":"portalnmms","proxy":"10.10.1.100","port":"9401","type":["default","supl"]}
+ ],
+ "14": [
+ {"carrier":"IDEA Punjab MMS","apn":"mmsc","mmsc":"http://10.11.12.180/","mmsproxy":"10.11.12.13","mmsport":"9401","type":["mms"]},
+ {"carrier":"IDEA Punjab","apn":"internet","type":["default","supl"]}
+ ],
+ "15": [
+ {"carrier":"Vodafone MMS","apn":"portalnmms","mmsc":"http://mms1.live.vodafone.in/mms/","mmsproxy":"10.10.1.100","mmsport":"9401","type":["mms"]},
+ {"carrier":"Vodafone Connect","apn":"www","type":["default","supl"]},
+ {"carrier":"VodafoneLive!","apn":"portalnmms","proxy":"10.10.1.100","port":"9401","type":["default","supl"]}
+ ],
+ "16": [
+ {"carrier":"Airtel","apn":"airtelgprs.com","type":["default","supl"]},
+ {"carrier":"Airtel MMS","apn":"airtelmms.com","mmsc":"http://100.1.201.171:10021/mmsc","mmsproxy":"100.1.201.172","mmsport":"8799","type":["mms"]}
+ ],
+ "17": [
+ {"carrier":"AIRCEL NE MMS","apn":"aircelmms","mmsc":"http://10.50.1.166/servlets/mms","mmsproxy":"172.17.83.69","mmsport":"8080","type":["mms"]},
+ {"carrier":"AIRCEL NE","apn":"aircelwebpost","type":["default","supl"]}
+ ],
+ "18": [
+ {"carrier":"Reliance RTel MMS","apn":"MMS","mmsc":"http://10.239.221.47/mms/","mmsproxy":"10.239.221.7","mmsport":"8080","type":["mms"]},
+ {"carrier":"Reliance RTel","apn":"SMARTNET","type":["default","supl"]}
+ ],
+ "19": [
+ {"carrier":"IDEA","apn":"internet","type":["default","supl"]},
+ {"carrier":"IDEA MMS","apn":"mmsc","mmsc":"http://10.4.42.21:8002/","mmsproxy":"10.4.42.15","mmsport":"8080","type":["mms"]}
+ ],
+ "20": [
+ {"carrier":"Vodafone MMS","apn":"portalnmms","mmsc":"http://mms1.live.vodafone.in/mms/","mmsproxy":"10.10.1.100","mmsport":"9401","type":["mms"]},
+ {"carrier":"Vodafone Connect","apn":"www","type":["default","supl"]},
+ {"carrier":"VodafoneLive!","apn":"portalnmms","proxy":"10.10.1.100","port":"9401","type":["default","supl"]}
+ ],
+ "21": [
+ {"carrier":"Loop Mobile MMS","apn":"mizone","password":"mmsc","mmsc":"http://mms.loopmobile.in:8080","mmsproxy":"10.0.0.10","mmsport":"9401","type":["mms"]},
+ {"carrier":"Loop Mobile","apn":"www","type":["default","supl"]}
+ ],
+ "22": [
+ {"carrier":"IDEA","apn":"internet","type":["default","supl"]},
+ {"carrier":"IDEA MMS","apn":"mmsc","mmsc":"http://10.4.42.21:8002/","mmsproxy":"10.4.42.15","mmsport":"8080","type":["mms"]}
+ ],
+ "24": [
+ {"carrier":"IDEA","apn":"internet","type":["default","supl"]},
+ {"carrier":"IDEA MMS","apn":"mmsc","mmsc":"http://10.4.42.21:8002/","mmsproxy":"10.4.42.15","mmsport":"8080","type":["mms"]}
+ ],
+ "25": [
+ {"carrier":"AIRCEL NE MMS","apn":"aircelmms","mmsc":"http://10.50.1.166/servlets/mms","mmsproxy":"172.17.83.69","mmsport":"8080","type":["mms"]},
+ {"carrier":"AIRCEL NE","apn":"aircelwebpost","type":["default","supl"]}
+ ],
+ "27": [
+ {"carrier":"Vodafone MMS","apn":"portalnmms","mmsc":"http://mms1.live.vodafone.in/mms/","mmsproxy":"10.10.1.100","mmsport":"9401","type":["mms"]},
+ {"carrier":"Vodafone Connect","apn":"www","type":["default","supl"]},
+ {"carrier":"VodafoneLive!","apn":"portalnmms","proxy":"10.10.1.100","port":"9401","type":["default","supl"]}
+ ],
+ "28": [
+ {"carrier":"AIRCEL NE MMS","apn":"aircelmms","mmsc":"http://10.50.1.166/servlets/mms","mmsproxy":"172.17.83.69","mmsport":"8080","type":["mms"]},
+ {"carrier":"AIRCEL NE","apn":"aircelwebpost","type":["default","supl"]}
+ ],
+ "29": [
+ {"carrier":"AIRCEL NE MMS","apn":"aircelmms","mmsc":"http://10.50.1.166/servlets/mms","mmsproxy":"172.17.83.69","mmsport":"8080","type":["mms"]},
+ {"carrier":"AIRCEL NE","apn":"aircelwebpost","type":["default","supl"]}
+ ],
+ "30": [
+ {"carrier":"Vodafone MMS","apn":"portalnmms","mmsc":"http://mms1.live.vodafone.in/mms/","mmsproxy":"10.10.1.100","mmsport":"9401","type":["mms"]},
+ {"carrier":"Vodafone Connect","apn":"www","type":["default","supl"]},
+ {"carrier":"VodafoneLive!","apn":"portalnmms","proxy":"10.10.1.100","port":"9401","type":["default","supl"]}
+ ],
+ "31": [
+ {"carrier":"Airtel","apn":"airtelgprs.com","type":["default","supl"]},
+ {"carrier":"Airtel MMS","apn":"airtelmms.com","mmsc":"http://100.1.201.171:10021/mmsc","mmsproxy":"100.1.201.172","mmsport":"8799","type":["mms"]}
+ ],
+ "33": [
+ {"carrier":"AIRCEL NE MMS","apn":"aircelmms","mmsc":"http://10.50.1.166/servlets/mms","mmsproxy":"172.17.83.69","mmsport":"8080","type":["mms"]},
+ {"carrier":"AIRCEL NE","apn":"aircelwebpost","type":["default","supl"]}
+ ],
+ "34": [
+ {"carrier":"Cellone_North MMS","apn":"bsnlmms","mmsc":"http://bsnlmmsc.in","mmsproxy":"10.210.10.11","mmsport":"8080","type":["mms"]},
+ {"carrier":"Cellone_North","apn":"bsnlnet","type":["default","supl"]}
+ ],
+ "35": [
+ {"carrier":"AIRCEL NE MMS","apn":"aircelmms","mmsc":"http://10.50.1.166/servlets/mms","mmsproxy":"172.17.83.69","mmsport":"8080","type":["mms"]},
+ {"carrier":"AIRCEL NE","apn":"aircelwebpost","type":["default","supl"]}
+ ],
+ "36": [
+ {"carrier":"Reliance RTel MMS","apn":"MMS","mmsc":"http://10.239.221.47/mms/","mmsproxy":"10.239.221.7","mmsport":"8080","type":["mms"]},
+ {"carrier":"Reliance RTel","apn":"SMARTNET","type":["default","supl"]}
+ ],
+ "37": [
+ {"carrier":"AIRCEL NE MMS","apn":"aircelmms","mmsc":"http://10.50.1.166/servlets/mms","mmsproxy":"172.17.83.69","mmsport":"8080","type":["mms"]},
+ {"carrier":"AIRCEL NE","apn":"aircelwebpost","type":["default","supl"]}
+ ],
+ "38": [
+ {"carrier":"CellOne_Kolkata MMS","apn":"bsnlmms","mmsc":"http://bsnlmmsc.in:8514","mmsproxy":"10.210.10.11","mmsport":"8080","type":["mms"]},
+ {"carrier":"CellOne_Kolkata","apn":"bsnlnet","type":["default","supl"]}
+ ],
+ "40": [
+ {"carrier":"Airtel","apn":"airtelgprs.com","type":["default","supl"]},
+ {"carrier":"Airtel MMS","apn":"airtelmms.com","mmsc":"http://100.1.201.171:10021/mmsc","mmsproxy":"100.1.201.172","mmsport":"8799","type":["mms"]}
+ ],
+ "41": [
+ {"carrier":"AIRCEL TN","apn":"aircelgprs.po","type":["default","supl"]},
+ {"carrier":"AIRCEL TN MMS","apn":"aircelmms.po","mmsc":"http://mmsc/mmrelay.app","mmsproxy":"192.168.35.196","mmsport":"8081","type":["mms"]}
+ ],
+ "42": [
+ {"carrier":"AIRCEL TN","apn":"aircelgprs.po","type":["default","supl"]},
+ {"carrier":"AIRCEL TN MMS","apn":"aircelmms.po","mmsc":"http://mmsc/mmrelay.app","mmsproxy":"192.168.35.196","mmsport":"8081","type":["mms"]}
+ ],
+ "43": [
+ {"carrier":"Vodafone MMS","apn":"portalnmms","mmsc":"http://mms1.live.vodafone.in/mms/","mmsproxy":"10.10.1.100","mmsport":"9401","type":["mms"]},
+ {"carrier":"Vodafone Connect","apn":"www","type":["default","supl"]},
+ {"carrier":"VodafoneLive!","apn":"portalnmms","proxy":"10.10.1.100","port":"9401","type":["default","supl"]}
+ ],
+ "44": [
+ {"carrier":"IDEA Karnataka","apn":"internet","type":["default","supl"]},
+ {"carrier":"IDEA Karnataka MMS","apn":"mmsc","mmsc":"http://10.4.42.21:8002","mmsproxy":"10.4.42.15","mmsport":"8080","type":["mms"]}
+ ],
+ "45": [
+ {"carrier":"Airtel","apn":"airtelgprs.com","type":["default","supl"]},
+ {"carrier":"Airtel MMS","apn":"airtelmms.com","mmsc":"http://100.1.201.171:10021/mmsc","mmsproxy":"100.1.201.172","mmsport":"8799","type":["mms"]}
+ ],
+ "46": [
+ {"carrier":"Vodafone MMS","apn":"portalnmms","mmsc":"http://mms1.live.vodafone.in/mms/","mmsproxy":"10.10.1.100","mmsport":"9401","type":["mms"]},
+ {"carrier":"Vodafone Connect","apn":"www","type":["default","supl"]},
+ {"carrier":"VodafoneLive!","apn":"portalnmms","proxy":"10.10.1.100","port":"9401","type":["default","supl"]}
+ ],
+ "49": [
+ {"carrier":"Airtel","apn":"airtelgprs.com","type":["default","supl"]},
+ {"carrier":"Airtel MMS","apn":"airtelmms.com","mmsc":"http://100.1.201.171:10021/mmsc","mmsproxy":"100.1.201.172","mmsport":"8799","type":["mms"]}
+ ],
+ "50": [
+ {"carrier":"Reliance RTel MMS","apn":"MMS","mmsc":"http://10.239.221.47/mms/","mmsproxy":"10.239.221.7","mmsport":"8080","type":["mms"]},
+ {"carrier":"Reliance RTel","apn":"SMARTNET","type":["default","supl"]}
+ ],
+ "51": [
+ {"carrier":"Cellone_North MMS","apn":"bsnlmms","mmsc":"http://bsnlmmsc.in","mmsproxy":"10.210.10.11","mmsport":"8080","type":["mms"]},
+ {"carrier":"Cellone_North","apn":"bsnlnet","type":["default","supl"]}
+ ],
+ "52": [
+ {"carrier":"Reliance RTel MMS","apn":"MMS","mmsc":"http://10.239.221.47/mms/","mmsproxy":"10.239.221.7","mmsport":"8080","type":["mms"]},
+ {"carrier":"Reliance RTel","apn":"SMARTNET","type":["default","supl"]}
+ ],
+ "53": [
+ {"carrier":"Cellone_North MMS","apn":"bsnlmms","mmsc":"http://bsnlmmsc.in","mmsproxy":"10.210.10.11","mmsport":"8080","type":["mms"]},
+ {"carrier":"Cellone_North","apn":"bsnlnet","type":["default","supl"]}
+ ],
+ "54": [
+ {"carrier":"Cellone_North MMS","apn":"bsnlmms","mmsc":"http://bsnlmmsc.in","mmsproxy":"10.210.10.11","mmsport":"8080","type":["mms"]},
+ {"carrier":"Cellone_North","apn":"bsnlnet","type":["default","supl"]}
+ ],
+ "55": [
+ {"carrier":"Cellone_North MMS","apn":"bsnlmms","mmsc":"http://bsnlmmsc.in","mmsproxy":"10.210.10.11","mmsport":"8080","type":["mms"]},
+ {"carrier":"Cellone_North","apn":"bsnlnet","type":["default","supl"]}
+ ],
+ "56": [
+ {"carrier":"IDEA","apn":"internet","type":["default","supl"]},
+ {"carrier":"IDEA MMS","apn":"mmsc","mmsc":"http://10.4.42.21:8002/","mmsproxy":"10.4.42.15","mmsport":"8080","type":["mms"]}
+ ],
+ "57": [
+ {"carrier":"Cellone_West MMS","apn":"bsnlmms","mmsc":"http://bsnlmmsc.in","mmsproxy":"10.210.10.11","mmsport":"8080","type":["mms"]},
+ {"carrier":"Cellone_West","apn":"bsnlnet","type":["default","supl"]}
+ ],
+ "58": [
+ {"carrier":"Cellone_West MMS","apn":"bsnlmms","mmsc":"http://bsnlmmsc.in","mmsproxy":"10.210.10.11","mmsport":"8080","type":["mms"]},
+ {"carrier":"Cellone_West","apn":"bsnlnet","type":["default","supl"]}
+ ],
+ "59": [
+ {"carrier":"Cellone_North MMS","apn":"bsnlmms","mmsc":"http://bsnlmmsc.in","mmsproxy":"10.210.10.11","mmsport":"8080","type":["mms"]},
+ {"carrier":"Cellone_North","apn":"bsnlnet","type":["default","supl"]}
+ ],
+ "60": [
+ {"carrier":"Vodafone MMS","apn":"portalnmms","mmsc":"http://mms1.live.vodafone.in/mms/","mmsproxy":"10.10.1.100","mmsport":"9401","type":["mms"]},
+ {"carrier":"Vodafone Connect","apn":"www","type":["default","supl"]},
+ {"carrier":"VodafoneLive!","apn":"portalnmms","proxy":"10.10.1.100","port":"9401","type":["default","supl"]}
+ ],
+ "62": [
+ {"carrier":"Cellone_North MMS","apn":"bsnlmms","mmsc":"http://bsnlmmsc.in","mmsproxy":"10.210.10.11","mmsport":"8080","type":["mms"]},
+ {"carrier":"Cellone_North","apn":"bsnlnet","type":["default","supl"]}
+ ],
+ "64": [
+ {"carrier":"CellOne_South MMS","apn":"bsnlmms","password":"mmsc","mmsc":"http://bsnlmmsc.in:8514","mmsproxy":"10.210.10.11","mmsport":"8080","type":["mms"]},
+ {"carrier":"CellOne_South","apn":"bsnlnet","type":["default","supl"]}
+ ],
+ "66": [
+ {"carrier":"Cellone_West MMS","apn":"bsnlmms","mmsc":"http://bsnlmmsc.in","mmsproxy":"10.210.10.11","mmsport":"8080","type":["mms"]},
+ {"carrier":"Cellone_West","apn":"bsnlnet","type":["default","supl"]}
+ ],
+ "67": [
+ {"carrier":"Reliance RTel MMS","apn":"MMS","mmsc":"http://10.239.221.47/mms/","mmsproxy":"10.239.221.7","mmsport":"8080","type":["mms"]},
+ {"carrier":"Reliance RTel","apn":"SMARTNET","type":["default","supl"]}
+ ],
+ "68": [
+ {"carrier":"Dolphin_Delhi MMS","apn":"gprsmtnldel","user":"mtnl","password":"mtnl123","mmsc":"http://172.16.31.136/mms/","mmsproxy":"172.16.31.10","mmsport":"80","type":["mms"]},
+ {"carrier":"Dolphin_Delhi","apn":"gprsmtnldel","user":"mtnl","password":"mtnl123","type":["default","supl"]},
+ {"carrier":"Dolphin_Delhi_3G MMS","apn":"mtnl3g","user":"mtnl","password":"mtnl123","mmsc":"http://172.16.31.165/mms/","mmsproxy":"172.16.31.10","mmsport":"9401","type":["mms"]},
+ {"carrier":"Dolphin_Delhi_3G","apn":"mtnl3g","user":"mtnl","password":"mtnl123","type":["default","supl"]}
+ ],
+ "69": [
+ {"carrier":"Dolphin_Mumbai MMS","apn":"gprsmtnlmum","user":"mtnl","password":"mtnl123","mmsc":"http://172.16.39.140/mms/","mmsproxy":"172.16.39.10","mmsport":"80","type":["mms"]},
+ {"carrier":"Dolphin_Mumbai","apn":"gprsmtnlmum","user":"mtnl","password":"mtnl123","type":["default","supl"]},
+ {"carrier":"Dolphin_Mumbai_3G MMS","apn":"mtnl3g","user":"mtnl","password":"mtnl123","mmsc":"http://172.16.31.165/mms/","mmsproxy":"172.16.39.10","mmsport":"9401","type":["mms"]},
+ {"carrier":"Dolphin_Mumbai_3G","apn":"mtnl3g","user":"mtnl","password":"mtnl123","type":["default","supl"]}
+ ],
+ "70": [
+ {"carrier":"Airtel","apn":"airtelgprs.com","type":["default","supl"]},
+ {"carrier":"Airtel MMS","apn":"airtelmms.com","mmsc":"http://100.1.201.171:10021/mmsc","mmsproxy":"100.1.201.172","mmsport":"8799","type":["mms"]}
+ ],
+ "71": [
+ {"carrier":"CellOne_South MMS","apn":"bsnlmms","password":"mmsc","mmsc":"http://bsnlmmsc.in:8514","mmsproxy":"10.210.10.11","mmsport":"8080","type":["mms"]},
+ {"carrier":"CellOne_South","apn":"bsnlnet","type":["default","supl"]}
+ ],
+ "72": [
+ {"carrier":"CellOne_South MMS","apn":"bsnlmms","password":"mmsc","mmsc":"http://bsnlmmsc.in:8514","mmsproxy":"10.210.10.11","mmsport":"8080","type":["mms"]},
+ {"carrier":"CellOne_South","apn":"bsnlnet","type":["default","supl"]}
+ ],
+ "73": [
+ {"carrier":"CellOne_South MMS","apn":"bsnlmms","password":"mmsc","mmsc":"http://bsnlmmsc.in:8514","mmsproxy":"10.210.10.11","mmsport":"8080","type":["mms"]},
+ {"carrier":"CellOne_South","apn":"bsnlnet","type":["default","supl"]}
+ ],
+ "74": [
+ {"carrier":"CellOne_Kolkata MMS","apn":"bsnlmms","mmsc":"http://bsnlmmsc.in:8514","mmsproxy":"10.210.10.11","mmsport":"8080","type":["mms"]},
+ {"carrier":"CellOne_Kolkata","apn":"bsnlnet","type":["default","supl"]}
+ ],
+ "75": [
+ {"carrier":"CellOne_Kolkata MMS","apn":"bsnlmms","mmsc":"http://bsnlmmsc.in:8514","mmsproxy":"10.210.10.11","mmsport":"8080","type":["mms"]},
+ {"carrier":"CellOne_Kolkata","apn":"bsnlnet","type":["default","supl"]}
+ ],
+ "76": [
+ {"carrier":"CellOne_Kolkata MMS","apn":"bsnlmms","mmsc":"http://bsnlmmsc.in:8514","mmsproxy":"10.210.10.11","mmsport":"8080","type":["mms"]},
+ {"carrier":"CellOne_Kolkata","apn":"bsnlnet","type":["default","supl"]}
+ ],
+ "77": [
+ {"carrier":"CellOne_Kolkata MMS","apn":"bsnlmms","mmsc":"http://bsnlmmsc.in:8514","mmsproxy":"10.210.10.11","mmsport":"8080","type":["mms"]},
+ {"carrier":"CellOne_Kolkata","apn":"bsnlnet","type":["default","supl"]}
+ ],
+ "78": [
+ {"carrier":"IDEA","apn":"internet","type":["default","supl"]},
+ {"carrier":"IDEA MMS","apn":"mmsc","mmsc":"http://10.4.42.21:8002/","mmsproxy":"10.4.42.15","mmsport":"8080","type":["mms"]}
+ ],
+ "79": [
+ {"carrier":"CellOne_South MMS","apn":"bsnlmms","password":"mmsc","mmsc":"http://bsnlmmsc.in:8514","mmsproxy":"10.210.10.11","mmsport":"8080","type":["mms"]},
+ {"carrier":"CellOne_South","apn":"bsnlnet","type":["default","supl"]}
+ ],
+ "80": [
+ {"carrier":"CellOne_South MMS","apn":"bsnlmms","password":"mmsc","mmsc":"http://bsnlmmsc.in:8514","mmsproxy":"10.210.10.11","mmsport":"8080","type":["mms"]},
+ {"carrier":"CellOne_South","apn":"bsnlnet","type":["default","supl"]}
+ ],
+ "81": [
+ {"carrier":"CellOne_Kolkata MMS","apn":"bsnlmms","mmsc":"http://bsnlmmsc.in:8514","mmsproxy":"10.210.10.11","mmsport":"8080","type":["mms"]},
+ {"carrier":"CellOne_Kolkata","apn":"bsnlnet","type":["default","supl"]}
+ ],
+ "82": [
+ {"carrier":"IDEA","apn":"internet","type":["default","supl"]},
+ {"carrier":"IDEA MMS","apn":"mmsc","mmsc":"http://10.4.42.21:8002/","mmsproxy":"10.4.42.15","mmsport":"8080","type":["mms"]}
+ ],
+ "83": [
+ {"carrier":"Reliance RTel MMS","apn":"MMS","mmsc":"http://10.239.221.47/mms/","mmsproxy":"10.239.221.7","mmsport":"8080","type":["mms"]},
+ {"carrier":"Reliance RTel","apn":"SMARTNET","type":["default","supl"]}
+ ],
+ "84": [
+ {"carrier":"Vodafone MMS","apn":"portalnmms","mmsc":"http://mms1.live.vodafone.in/mms/","mmsproxy":"10.10.1.100","mmsport":"9401","type":["mms"]},
+ {"carrier":"Vodafone Connect","apn":"www","type":["default","supl"]},
+ {"carrier":"VodafoneLive!","apn":"portalnmms","proxy":"10.10.1.100","port":"9401","type":["default","supl"]}
+ ],
+ "85": [
+ {"carrier":"Reliance RTel MMS","apn":"MMS","mmsc":"http://10.239.221.47/mms/","mmsproxy":"10.239.221.7","mmsport":"8080","type":["mms"]},
+ {"carrier":"Reliance RTel","apn":"SMARTNET","type":["default","supl"]}
+ ],
+ "86": [
+ {"carrier":"Vodafone MMS","apn":"portalnmms","mmsc":"http://mms1.live.vodafone.in/mms/","mmsproxy":"10.10.1.100","mmsport":"9401","type":["mms"]},
+ {"carrier":"Vodafone Connect","apn":"www","type":["default","supl"]},
+ {"carrier":"VodafoneLive!","apn":"portalnmms","proxy":"10.10.1.100","port":"9401","type":["default","supl"]}
+ ],
+ "87": [
+ {"carrier":"IDEA","apn":"internet","type":["default","supl"]},
+ {"carrier":"IDEA MMS","apn":"mmsc","mmsc":"http://10.4.42.21:8002/","mmsproxy":"10.4.42.15","mmsport":"8080","type":["mms"]}
+ ],
+ "88": [
+ {"carrier":"Vodafone MMS","apn":"portalnmms","mmsc":"http://mms1.live.vodafone.in/mms/","mmsproxy":"10.10.1.100","mmsport":"9401","type":["mms"]},
+ {"carrier":"Vodafone Connect","apn":"www","type":["default","supl"]},
+ {"carrier":"VodafoneLive!","apn":"portalnmms","proxy":"10.10.1.100","port":"9401","type":["default","supl"]}
+ ],
+ "89": [
+ {"carrier":"IDEA","apn":"internet","type":["default","supl"]},
+ {"carrier":"IDEA MMS","apn":"mmsc","mmsc":"http://10.4.42.21:8002/","mmsproxy":"10.4.42.15","mmsport":"8080","type":["mms"]}
+ ],
+ "90": [
+ {"carrier":"Airtel","apn":"airtelgprs.com","type":["default","supl"]},
+ {"carrier":"Airtel MMS","apn":"airtelmms.com","mmsc":"http://100.1.201.171:10021/mmsc","mmsproxy":"100.1.201.172","mmsport":"8799","type":["mms"]}
+ ],
+ "91": [
+ {"carrier":"AIRCEL NE MMS","apn":"aircelmms","mmsc":"http://10.50.1.166/servlets/mms","mmsproxy":"172.17.83.69","mmsport":"8080","type":["mms"]},
+ {"carrier":"AIRCEL NE","apn":"aircelwebpost","type":["default","supl"]}
+ ],
+ "92": [
+ {"carrier":"Airtel","apn":"airtelgprs.com","type":["default","supl"]},
+ {"carrier":"Airtel MMS","apn":"airtelmms.com","mmsc":"http://100.1.201.171:10021/mmsc","mmsproxy":"100.1.201.172","mmsport":"8799","type":["mms"]}
+ ],
+ "93": [
+ {"carrier":"Airtel","apn":"airtelgprs.com","type":["default","supl"]},
+ {"carrier":"Airtel MMS","apn":"airtelmms.com","mmsc":"http://100.1.201.171:10021/mmsc","mmsproxy":"100.1.201.172","mmsport":"8799","type":["mms"]}
+ ],
+ "94": [
+ {"carrier":"Airtel","apn":"airtelgprs.com","type":["default","supl"]},
+ {"carrier":"Airtel MMS","apn":"airtelmms.com","mmsc":"http://100.1.201.171:10021/mmsc","mmsproxy":"100.1.201.172","mmsport":"8799","type":["mms"]}
+ ],
+ "95": [
+ {"carrier":"Airtel","apn":"airtelgprs.com","type":["default","supl"]},
+ {"carrier":"Airtel MMS","apn":"airtelmms.com","mmsc":"http://100.1.201.171:10021/mmsc","mmsproxy":"100.1.201.172","mmsport":"8799","type":["mms"]}
+ ],
+ "96": [
+ {"carrier":"Airtel","apn":"airtelgprs.com","type":["default","supl"]},
+ {"carrier":"Airtel MMS","apn":"airtelmms.com","mmsc":"http://100.1.201.171:10021/mmsc","mmsproxy":"100.1.201.172","mmsport":"8799","type":["mms"]}
+ ],
+ "97": [
+ {"carrier":"Airtel","apn":"airtelgprs.com","type":["default","supl"]},
+ {"carrier":"Airtel MMS","apn":"airtelmms.com","mmsc":"http://100.1.201.171:10021/mmsc","mmsproxy":"100.1.201.172","mmsport":"8799","type":["mms"]}
+ ],
+ "98": [
+ {"carrier":"Airtel","apn":"airtelgprs.com","type":["default","supl"]},
+ {"carrier":"Airtel MMS","apn":"airtelmms.com","mmsc":"http://100.1.201.171:10021/mmsc","mmsproxy":"100.1.201.172","mmsport":"8799","type":["mms"]}
+ ]
+},
+"405": {
+ "1": [
+ {"carrier":"Reliance RCOM MMS","apn":"rcommms","mmsc":"http://mmsc.rcom.co.in/mms/","mmsproxy":"10.239.221.5","mmsport":"8080","type":["mms"]},
+ {"carrier":"Reliance RCOM","apn":"rcomnet","type":["default","supl"]}
+ ],
+ "3": [
+ {"carrier":"Reliance RCOM MMS","apn":"rcommms","mmsc":"http://mmsc.rcom.co.in/mms/","mmsproxy":"10.239.221.5","mmsport":"8080","type":["mms"]},
+ {"carrier":"Reliance RCOM","apn":"rcomnet","type":["default","supl"]}
+ ],
+ "4": [
+ {"carrier":"Reliance RCOM MMS","apn":"rcommms","mmsc":"http://mmsc.rcom.co.in/mms/","mmsproxy":"10.239.221.5","mmsport":"8080","type":["mms"]},
+ {"carrier":"Reliance RCOM","apn":"rcomnet","type":["default","supl"]}
+ ],
+ "5": [
+ {"carrier":"Reliance RCOM MMS","apn":"rcommms","mmsc":"http://mmsc.rcom.co.in/mms/","mmsproxy":"10.239.221.5","mmsport":"8080","type":["mms"]},
+ {"carrier":"Reliance RCOM","apn":"rcomnet","type":["default","supl"]}
+ ],
+ "6": [
+ {"carrier":"Reliance RCOM MMS","apn":"rcommms","mmsc":"http://mmsc.rcom.co.in/mms/","mmsproxy":"10.239.221.5","mmsport":"8080","type":["mms"]},
+ {"carrier":"Reliance RCOM","apn":"rcomnet","type":["default","supl"]}
+ ],
+ "7": [
+ {"carrier":"Reliance RCOM MMS","apn":"rcommms","mmsc":"http://mmsc.rcom.co.in/mms/","mmsproxy":"10.239.221.5","mmsport":"8080","type":["mms"]},
+ {"carrier":"Reliance RCOM","apn":"rcomnet","type":["default","supl"]}
+ ],
+ "8": [
+ {"carrier":"Reliance RCOM MMS","apn":"rcommms","mmsc":"http://mmsc.rcom.co.in/mms/","mmsproxy":"10.239.221.5","mmsport":"8080","type":["mms"]},
+ {"carrier":"Reliance RCOM","apn":"rcomnet","type":["default","supl"]}
+ ],
+ "9": [
+ {"carrier":"Reliance RCOM MMS","apn":"rcommms","mmsc":"http://mmsc.rcom.co.in/mms/","mmsproxy":"10.239.221.5","mmsport":"8080","type":["mms"]},
+ {"carrier":"Reliance RCOM","apn":"rcomnet","type":["default","supl"]}
+ ],
+ "10": [
+ {"carrier":"Reliance RCOM MMS","apn":"rcommms","mmsc":"http://mmsc.rcom.co.in/mms/","mmsproxy":"10.239.221.5","mmsport":"8080","type":["mms"]},
+ {"carrier":"Reliance RCOM","apn":"rcomnet","type":["default","supl"]}
+ ],
+ "11": [
+ {"carrier":"Reliance RCOM MMS","apn":"rcommms","mmsc":"http://mmsc.rcom.co.in/mms/","mmsproxy":"10.239.221.5","mmsport":"8080","type":["mms"]},
+ {"carrier":"Reliance RCOM","apn":"rcomnet","type":["default","supl"]}
+ ],
+ "12": [
+ {"carrier":"Reliance RCOM MMS","apn":"rcommms","mmsc":"http://mmsc.rcom.co.in/mms/","mmsproxy":"10.239.221.5","mmsport":"8080","type":["mms"]},
+ {"carrier":"Reliance RCOM","apn":"rcomnet","type":["default","supl"]}
+ ],
+ "13": [
+ {"carrier":"Reliance RCOM MMS","apn":"rcommms","mmsc":"http://mmsc.rcom.co.in/mms/","mmsproxy":"10.239.221.5","mmsport":"8080","type":["mms"]},
+ {"carrier":"Reliance RCOM","apn":"rcomnet","type":["default","supl"]}
+ ],
+ "14": [
+ {"carrier":"Reliance RCOM MMS","apn":"rcommms","mmsc":"http://mmsc.rcom.co.in/mms/","mmsproxy":"10.239.221.5","mmsport":"8080","type":["mms"]},
+ {"carrier":"Reliance RCOM","apn":"rcomnet","type":["default","supl"]}
+ ],
+ "15": [
+ {"carrier":"Reliance RCOM MMS","apn":"rcommms","mmsc":"http://mmsc.rcom.co.in/mms/","mmsproxy":"10.239.221.5","mmsport":"8080","type":["mms"]},
+ {"carrier":"Reliance RCOM","apn":"rcomnet","type":["default","supl"]}
+ ],
+ "17": [
+ {"carrier":"Reliance RCOM MMS","apn":"rcommms","mmsc":"http://mmsc.rcom.co.in/mms/","mmsproxy":"10.239.221.5","mmsport":"8080","type":["mms"]},
+ {"carrier":"Reliance RCOM","apn":"rcomnet","type":["default","supl"]}
+ ],
+ "18": [
+ {"carrier":"Reliance RCOM MMS","apn":"rcommms","mmsc":"http://mmsc.rcom.co.in/mms/","mmsproxy":"10.239.221.5","mmsport":"8080","type":["mms"]},
+ {"carrier":"Reliance RCOM","apn":"rcomnet","type":["default","supl"]}
+ ],
+ "19": [
+ {"carrier":"Reliance RCOM MMS","apn":"rcommms","mmsc":"http://mmsc.rcom.co.in/mms/","mmsproxy":"10.239.221.5","mmsport":"8080","type":["mms"]},
+ {"carrier":"Reliance RCOM","apn":"rcomnet","type":["default","supl"]}
+ ],
+ "20": [
+ {"carrier":"Reliance RCOM MMS","apn":"rcommms","mmsc":"http://mmsc.rcom.co.in/mms/","mmsproxy":"10.239.221.5","mmsport":"8080","type":["mms"]},
+ {"carrier":"Reliance RCOM","apn":"rcomnet","type":["default","supl"]}
+ ],
+ "21": [
+ {"carrier":"Reliance RCOM MMS","apn":"rcommms","mmsc":"http://mmsc.rcom.co.in/mms/","mmsproxy":"10.239.221.5","mmsport":"8080","type":["mms"]},
+ {"carrier":"Reliance RCOM","apn":"rcomnet","type":["default","supl"]}
+ ],
+ "22": [
+ {"carrier":"Reliance RCOM MMS","apn":"rcommms","mmsc":"http://mmsc.rcom.co.in/mms/","mmsproxy":"10.239.221.5","mmsport":"8080","type":["mms"]},
+ {"carrier":"Reliance RCOM","apn":"rcomnet","type":["default","supl"]}
+ ],
+ "23": [
+ {"carrier":"Reliance RCOM MMS","apn":"rcommms","mmsc":"http://mmsc.rcom.co.in/mms/","mmsproxy":"10.239.221.5","mmsport":"8080","type":["mms"]},
+ {"carrier":"Reliance RCOM","apn":"rcomnet","type":["default","supl"]}
+ ],
+ "25": [
+ {"carrier":"TataDOCOMO","apn":"TATA.DOCOMO.INTERNET","type":["default","supl"]},
+ {"carrier":"TataDOCOMO MMS","apn":"TATA.DOCOMO.MMS","mmsc":"http://mmsc/","mmsproxy":"10.124.26.94","mmsport":"8799","type":["mms"]}
+ ],
+ "26": [
+ {"carrier":"TataDOCOMO","apn":"TATA.DOCOMO.INTERNET","type":["default","supl"]},
+ {"carrier":"TataDOCOMO MMS","apn":"TATA.DOCOMO.MMS","mmsc":"http://mmsc/","mmsproxy":"10.124.26.94","mmsport":"8799","type":["mms"]}
+ ],
+ "27": [
+ {"carrier":"TataDOCOMO","apn":"TATA.DOCOMO.INTERNET","type":["default","supl"]},
+ {"carrier":"TataDOCOMO MMS","apn":"TATA.DOCOMO.MMS","mmsc":"http://mmsc/","mmsproxy":"10.124.26.94","mmsport":"8799","type":["mms"]}
+ ],
+ "28": [
+ {"carrier":"TataDOCOMO","apn":"TATA.DOCOMO.INTERNET","type":["default","supl"]},
+ {"carrier":"TataDOCOMO MMS","apn":"TATA.DOCOMO.MMS","mmsc":"http://mmsc/","mmsproxy":"10.124.26.94","mmsport":"8799","type":["mms"]}
+ ],
+ "29": [
+ {"carrier":"TataDOCOMO","apn":"TATA.DOCOMO.INTERNET","type":["default","supl"]},
+ {"carrier":"TataDOCOMO MMS","apn":"TATA.DOCOMO.MMS","mmsc":"http://mmsc/","mmsproxy":"10.124.26.94","mmsport":"8799","type":["mms"]}
+ ],
+ "30": [
+ {"carrier":"TataDOCOMO","apn":"TATA.DOCOMO.INTERNET","type":["default","supl"]},
+ {"carrier":"TataDOCOMO MMS","apn":"TATA.DOCOMO.MMS","mmsc":"http://mmsc/","mmsproxy":"10.124.26.94","mmsport":"8799","type":["mms"]}
+ ],
+ "31": [
+ {"carrier":"TataDOCOMO","apn":"TATA.DOCOMO.INTERNET","type":["default","supl"]},
+ {"carrier":"TataDOCOMO MMS","apn":"TATA.DOCOMO.MMS","mmsc":"http://mmsc/","mmsproxy":"10.124.26.94","mmsport":"8799","type":["mms"]}
+ ],
+ "32": [
+ {"carrier":"TataDOCOMO","apn":"TATA.DOCOMO.INTERNET","type":["default","supl"]},
+ {"carrier":"TataDOCOMO MMS","apn":"TATA.DOCOMO.MMS","mmsc":"http://mmsc/","mmsproxy":"10.124.26.94","mmsport":"8799","type":["mms"]}
+ ],
+ "33": [
+ {"carrier":"TataDOCOMO","apn":"TATA.DOCOMO.INTERNET","type":["default","supl"]},
+ {"carrier":"TataDOCOMO MMS","apn":"TATA.DOCOMO.MMS","mmsc":"http://mmsc/","mmsproxy":"10.124.26.94","mmsport":"8799","type":["mms"]}
+ ],
+ "34": [
+ {"carrier":"TataDOCOMO","apn":"TATA.DOCOMO.INTERNET","type":["default","supl"]},
+ {"carrier":"TataDOCOMO MMS","apn":"TATA.DOCOMO.MMS","mmsc":"http://mmsc/","mmsproxy":"10.124.26.94","mmsport":"8799","type":["mms"]}
+ ],
+ "35": [
+ {"carrier":"TataDOCOMO","apn":"TATA.DOCOMO.INTERNET","type":["default","supl"]},
+ {"carrier":"TataDOCOMO MMS","apn":"TATA.DOCOMO.MMS","mmsc":"http://mmsc/","mmsproxy":"10.124.26.94","mmsport":"8799","type":["mms"]}
+ ],
+ "36": [
+ {"carrier":"TataDOCOMO","apn":"TATA.DOCOMO.INTERNET","type":["default","supl"]},
+ {"carrier":"TataDOCOMO MMS","apn":"TATA.DOCOMO.MMS","mmsc":"http://mmsc/","mmsproxy":"10.124.26.94","mmsport":"8799","type":["mms"]}
+ ],
+ "37": [
+ {"carrier":"TataDOCOMO","apn":"TATA.DOCOMO.INTERNET","type":["default","supl"]},
+ {"carrier":"TataDOCOMO MMS","apn":"TATA.DOCOMO.MMS","mmsc":"http://mmsc/","mmsproxy":"10.124.26.94","mmsport":"8799","type":["mms"]}
+ ],
+ "38": [
+ {"carrier":"TataDOCOMO","apn":"TATA.DOCOMO.INTERNET","type":["default","supl"]},
+ {"carrier":"TataDOCOMO MMS","apn":"TATA.DOCOMO.MMS","mmsc":"http://mmsc/","mmsproxy":"10.124.26.94","mmsport":"8799","type":["mms"]}
+ ],
+ "39": [
+ {"carrier":"TataDOCOMO","apn":"TATA.DOCOMO.INTERNET","type":["default","supl"]},
+ {"carrier":"TataDOCOMO MMS","apn":"TATA.DOCOMO.MMS","mmsc":"http://mmsc/","mmsproxy":"10.124.26.94","mmsport":"8799","type":["mms"]}
+ ],
+ "40": [
+ {"carrier":"TataDOCOMO","apn":"TATA.DOCOMO.INTERNET","type":["default","supl"]},
+ {"carrier":"TataDOCOMO MMS","apn":"TATA.DOCOMO.MMS","mmsc":"http://mmsc/","mmsproxy":"10.124.26.94","mmsport":"8799","type":["mms"]}
+ ],
+ "41": [
+ {"carrier":"TataDOCOMO","apn":"TATA.DOCOMO.INTERNET","type":["default","supl"]},
+ {"carrier":"TataDOCOMO MMS","apn":"TATA.DOCOMO.MMS","mmsc":"http://mmsc/","mmsproxy":"10.124.26.94","mmsport":"8799","type":["mms"]}
+ ],
+ "42": [
+ {"carrier":"TataDOCOMO","apn":"TATA.DOCOMO.INTERNET","type":["default","supl"]},
+ {"carrier":"TataDOCOMO MMS","apn":"TATA.DOCOMO.MMS","mmsc":"http://mmsc/","mmsproxy":"10.124.26.94","mmsport":"8799","type":["mms"]}
+ ],
+ "43": [
+ {"carrier":"TataDOCOMO","apn":"TATA.DOCOMO.INTERNET","type":["default","supl"]},
+ {"carrier":"TataDOCOMO MMS","apn":"TATA.DOCOMO.MMS","mmsc":"http://mmsc/","mmsproxy":"10.124.26.94","mmsport":"8799","type":["mms"]}
+ ],
+ "44": [
+ {"carrier":"TataDOCOMO","apn":"TATA.DOCOMO.INTERNET","type":["default","supl"]},
+ {"carrier":"TataDOCOMO MMS","apn":"TATA.DOCOMO.MMS","mmsc":"http://mmsc/","mmsproxy":"10.124.26.94","mmsport":"8799","type":["mms"]}
+ ],
+ "45": [
+ {"carrier":"TataDOCOMO","apn":"TATA.DOCOMO.INTERNET","type":["default","supl"]},
+ {"carrier":"TataDOCOMO MMS","apn":"TATA.DOCOMO.MMS","mmsc":"http://mmsc/","mmsproxy":"10.124.26.94","mmsport":"8799","type":["mms"]}
+ ],
+ "46": [
+ {"carrier":"TataDOCOMO","apn":"TATA.DOCOMO.INTERNET","type":["default","supl"]},
+ {"carrier":"TataDOCOMO MMS","apn":"TATA.DOCOMO.MMS","mmsc":"http://mmsc/","mmsproxy":"10.124.26.94","mmsport":"8799","type":["mms"]}
+ ],
+ "47": [
+ {"carrier":"TataDOCOMO","apn":"TATA.DOCOMO.INTERNET","type":["default","supl"]},
+ {"carrier":"TataDOCOMO MMS","apn":"TATA.DOCOMO.MMS","mmsc":"http://mmsc/","mmsproxy":"10.124.26.94","mmsport":"8799","type":["mms"]}
+ ],
+ "51": [
+ {"carrier":"Airtel","apn":"airtelgprs.com","type":["default","supl"]},
+ {"carrier":"Airtel MMS","apn":"airtelmms.com","mmsc":"http://100.1.201.171:10021/mmsc","mmsproxy":"100.1.201.172","mmsport":"8799","type":["mms"]}
+ ],
+ "52": [
+ {"carrier":"Airtel","apn":"airtelgprs.com","type":["default","supl"]},
+ {"carrier":"Airtel MMS","apn":"airtelmms.com","mmsc":"http://100.1.201.171:10021/mmsc","mmsproxy":"100.1.201.172","mmsport":"8799","type":["mms"]}
+ ],
+ "53": [
+ {"carrier":"Airtel","apn":"airtelgprs.com","type":["default","supl"]},
+ {"carrier":"Airtel MMS","apn":"airtelmms.com","mmsc":"http://100.1.201.171:10021/mmsc","mmsproxy":"100.1.201.172","mmsport":"8799","type":["mms"]}
+ ],
+ "54": [
+ {"carrier":"Airtel","apn":"airtelgprs.com","type":["default","supl"]},
+ {"carrier":"Airtel MMS","apn":"airtelmms.com","mmsc":"http://100.1.201.171:10021/mmsc","mmsproxy":"100.1.201.172","mmsport":"8799","type":["mms"]}
+ ],
+ "55": [
+ {"carrier":"Airtel","apn":"airtelgprs.com","type":["default","supl"]},
+ {"carrier":"Airtel MMS","apn":"airtelmms.com","mmsc":"http://100.1.201.171:10021/mmsc","mmsproxy":"100.1.201.172","mmsport":"8799","type":["mms"]}
+ ],
+ "56": [
+ {"carrier":"Airtel","apn":"airtelgprs.com","type":["default","supl"]},
+ {"carrier":"Airtel MMS","apn":"airtelmms.com","mmsc":"http://100.1.201.171:10021/mmsc","mmsproxy":"100.1.201.172","mmsport":"8799","type":["mms"]}
+ ],
+ "66": [
+ {"carrier":"Vodafone MMS","apn":"portalnmms","mmsc":"http://mms1.live.vodafone.in/mms/","mmsproxy":"10.10.1.100","mmsport":"9401","type":["mms"]},
+ {"carrier":"Vodafone Connect","apn":"www","type":["default","supl"]},
+ {"carrier":"VodafoneLive!","apn":"portalnmms","proxy":"10.10.1.100","port":"9401","type":["default","supl"]}
+ ],
+ "67": [
+ {"carrier":"Vodafone MMS","apn":"portalnmms","mmsc":"http://mms1.live.vodafone.in/mms/","mmsproxy":"10.10.1.100","mmsport":"9401","type":["mms"]},
+ {"carrier":"Vodafone Connect","apn":"www","type":["default","supl"]},
+ {"carrier":"VodafoneLive!","apn":"portalnmms","proxy":"10.10.1.100","port":"9401","type":["default","supl"]}
+ ],
+ "70": [
+ {"carrier":"IDEA","apn":"internet","type":["default","supl"]},
+ {"carrier":"IDEA MMS","apn":"mmsc","mmsc":"http://10.4.42.21:8002/","mmsproxy":"10.4.42.15","mmsport":"8080","type":["mms"]}
+ ],
+ "750": [
+ {"carrier":"Vodafone MMS","apn":"vodafonelivejk","mmsc":"http://mms1.live.vodafone.in/mms/","mmsproxy":"10.10.1.100","mmsport":"9401","type":["mms"]},
+ {"carrier":"Vodafone Connect","apn":"jkgprs","type":["default","supl"]},
+ {"carrier":"VodafoneLive!","apn":"vodafonelivejk","proxy":"10.10.1.100","port":"9411","type":["default","supl"]}
+ ],
+ "751": [
+ {"carrier":"Vodafone MMS","apn":"portalnmms","mmsc":"http://mms1.live.vodafone.in/mms/","mmsproxy":"10.10.1.100","mmsport":"9401","type":["mms"]},
+ {"carrier":"Vodafone Connect","apn":"www","type":["default","supl"]},
+ {"carrier":"VodafoneLive!","apn":"portalnmms","proxy":"10.10.1.100","port":"9401","type":["default","supl"]}
+ ],
+ "752": [
+ {"carrier":"Vodafone MMS","apn":"portalnmms","mmsc":"http://mms1.live.vodafone.in/mms/","mmsproxy":"10.10.1.100","mmsport":"9401","type":["mms"]},
+ {"carrier":"Vodafone Connect","apn":"www","type":["default","supl"]},
+ {"carrier":"VodafoneLive!","apn":"portalnmms","proxy":"10.10.1.100","port":"9401","type":["default","supl"]}
+ ],
+ "753": [
+ {"carrier":"Vodafone MMS","apn":"portalnmms","mmsc":"http://mms1.live.vodafone.in/mms/","mmsproxy":"10.10.1.100","mmsport":"9401","type":["mms"]},
+ {"carrier":"Vodafone Connect","apn":"www","type":["default","supl"]},
+ {"carrier":"VodafoneLive!","apn":"portalnmms","proxy":"10.10.1.100","port":"9401","type":["default","supl"]}
+ ],
+ "754": [
+ {"carrier":"Vodafone MMS","apn":"portalnmms","mmsc":"http://mms1.live.vodafone.in/mms/","mmsproxy":"10.10.1.100","mmsport":"9401","type":["mms"]},
+ {"carrier":"Vodafone Connect","apn":"www","type":["default","supl"]},
+ {"carrier":"VodafoneLive!","apn":"portalnmms","proxy":"10.10.1.100","port":"9401","type":["default","supl"]}
+ ],
+ "755": [
+ {"carrier":"Vodafone MMS","apn":"portalnmms","mmsc":"http://mms1.live.vodafone.in/mms/","mmsproxy":"10.10.1.100","mmsport":"9401","type":["mms"]},
+ {"carrier":"Vodafone Connect","apn":"www","type":["default","supl"]},
+ {"carrier":"VodafoneLive!","apn":"portalnmms","proxy":"10.10.1.100","port":"9401","type":["default","supl"]}
+ ],
+ "756": [
+ {"carrier":"Vodafone MMS","apn":"portalnmms","mmsc":"http://mms1.live.vodafone.in/mms/","mmsproxy":"10.10.1.100","mmsport":"9401","type":["mms"]},
+ {"carrier":"Vodafone Connect","apn":"www","type":["default","supl"]},
+ {"carrier":"VodafoneLive!","apn":"portalnmms","proxy":"10.10.1.100","port":"9401","type":["default","supl"]}
+ ],
+ "799": [
+ {"carrier":"IDEA","apn":"internet","type":["default","supl"]},
+ {"carrier":"IDEA MMS","apn":"mmsc","mmsc":"http://10.4.42.21:8002/","mmsproxy":"10.4.42.15","mmsport":"8080","type":["mms"]}
+ ],
+ "800": [
+ {"carrier":"AIRCEL ROI","apn":"aircelgprs","type":["default","supl"]},
+ {"carrier":"AIRCEL ROI MMS","apn":"aircelmms","mmsc":"http://10.50.1.166/servlets/mms","mmsproxy":"172.17.83.69","mmsport":"8080","type":["mms"]}
+ ],
+ "801": [
+ {"carrier":"AIRCEL South","apn":"aircelgprs","type":["default","supl"]},
+ {"carrier":"AIRCEL South MMS","apn":"aircelmms","mmsc":"http://mmsc/mmrelay.app","mmsproxy":"192.168.35.196","mmsport":"8081","type":["mms"]}
+ ],
+ "802": [
+ {"carrier":"AIRCEL Central","apn":"aircelgprs","type":["default","supl"]},
+ {"carrier":"AIRCEL Central MMS","apn":"aircelmms","mmsc":"http://10.50.1.166/servlets/mms","mmsproxy":"172.17.83.69","mmsport":"8080","type":["mms"]}
+ ],
+ "803": [
+ {"carrier":"AIRCEL South","apn":"aircelgprs","type":["default","supl"]},
+ {"carrier":"AIRCEL South MMS","apn":"aircelmms","mmsc":"http://mmsc/mmrelay.app","mmsproxy":"192.168.35.196","mmsport":"8081","type":["mms"]}
+ ],
+ "804": [
+ {"carrier":"AIRCEL ROI","apn":"aircelgprs","type":["default","supl"]},
+ {"carrier":"AIRCEL ROI MMS","apn":"aircelmms","mmsc":"http://10.50.1.166/servlets/mms","mmsproxy":"172.17.83.69","mmsport":"8080","type":["mms"]}
+ ],
+ "805": [
+ {"carrier":"AIRCEL ROI","apn":"aircelgprs","type":["default","supl"]},
+ {"carrier":"AIRCEL ROI MMS","apn":"aircelmms","mmsc":"http://10.50.1.166/servlets/mms","mmsproxy":"172.17.83.69","mmsport":"8080","type":["mms"]}
+ ],
+ "806": [
+ {"carrier":"AIRCEL Central","apn":"aircelgprs","type":["default","supl"]},
+ {"carrier":"AIRCEL Central MMS","apn":"aircelmms","mmsc":"http://10.50.1.166/servlets/mms","mmsproxy":"172.17.83.69","mmsport":"8080","type":["mms"]}
+ ],
+ "807": [
+ {"carrier":"AIRCEL Central","apn":"aircelgprs","type":["default","supl"]},
+ {"carrier":"AIRCEL Central MMS","apn":"aircelmms","mmsc":"http://10.50.1.166/servlets/mms","mmsproxy":"172.17.83.69","mmsport":"8080","type":["mms"]}
+ ],
+ "808": [
+ {"carrier":"AIRCEL Central","apn":"aircelgprs","type":["default","supl"]},
+ {"carrier":"AIRCEL Central MMS","apn":"aircelmms","mmsc":"http://10.50.1.166/servlets/mms","mmsproxy":"172.17.83.69","mmsport":"8080","type":["mms"]}
+ ],
+ "809": [
+ {"carrier":"AIRCEL South","apn":"aircelgprs","type":["default","supl"]},
+ {"carrier":"AIRCEL South MMS","apn":"aircelmms","mmsc":"http://mmsc/mmrelay.app","mmsproxy":"192.168.35.196","mmsport":"8081","type":["mms"]}
+ ],
+ "810": [
+ {"carrier":"AIRCEL ROI","apn":"aircelgprs","type":["default","supl"]},
+ {"carrier":"AIRCEL ROI MMS","apn":"aircelmms","mmsc":"http://10.50.1.166/servlets/mms","mmsproxy":"172.17.83.69","mmsport":"8080","type":["mms"]}
+ ],
+ "811": [
+ {"carrier":"AIRCEL ROI","apn":"aircelgprs","type":["default","supl"]},
+ {"carrier":"AIRCEL ROI MMS","apn":"aircelmms","mmsc":"http://10.50.1.166/servlets/mms","mmsproxy":"172.17.83.69","mmsport":"8080","type":["mms"]}
+ ],
+ "812": [
+ {"carrier":"AIRCEL Central","apn":"aircelgprs","type":["default","supl"]},
+ {"carrier":"AIRCEL Central MMS","apn":"aircelmms","mmsc":"http://10.50.1.166/servlets/mms","mmsproxy":"172.17.83.69","mmsport":"8080","type":["mms"]}
+ ],
+ "813": [
+ {"carrier":"Uninor GPRS","apn":"uninor","type":["default","supl"]},
+ {"carrier":"Uninor MMS","apn":"uninor","mmsc":"http://10.58.2.120","mmsproxy":"10.58.10.59","mmsport":"8080","type":["mms"]}
+ ],
+ "814": [
+ {"carrier":"Uninor GPRS","apn":"uninor","type":["default","supl"]},
+ {"carrier":"Uninor MMS","apn":"uninor","mmsc":"http://10.58.2.120","mmsproxy":"10.58.10.59","mmsport":"8080","type":["mms"]}
+ ],
+ "815": [
+ {"carrier":"Uninor GPRS","apn":"uninor","type":["default","supl"]},
+ {"carrier":"Uninor MMS","apn":"uninor","mmsc":"http://10.58.2.120","mmsproxy":"10.58.10.59","mmsport":"8080","type":["mms"]}
+ ],
+ "816": [
+ {"carrier":"Uninor GPRS","apn":"uninor","type":["default","supl"]},
+ {"carrier":"Uninor MMS","apn":"uninor","mmsc":"http://10.58.2.120","mmsproxy":"10.58.10.59","mmsport":"8080","type":["mms"]}
+ ],
+ "817": [
+ {"carrier":"Uninor GPRS","apn":"uninor","type":["default","supl"]},
+ {"carrier":"Uninor MMS","apn":"uninor","mmsc":"http://10.58.2.120","mmsproxy":"10.58.10.59","mmsport":"8080","type":["mms"]}
+ ],
+ "818": [
+ {"carrier":"Uninor GPRS","apn":"uninor","type":["default","supl"]},
+ {"carrier":"Uninor MMS","apn":"uninor","mmsc":"http://10.58.2.120","mmsproxy":"10.58.10.59","mmsport":"8080","type":["mms"]}
+ ],
+ "819": [
+ {"carrier":"Uninor GPRS","apn":"uninor","type":["default","supl"]},
+ {"carrier":"Uninor MMS","apn":"uninor","mmsc":"http://10.58.2.120","mmsproxy":"10.58.10.59","mmsport":"8080","type":["mms"]}
+ ],
+ "820": [
+ {"carrier":"Uninor GPRS","apn":"uninor","type":["default","supl"]},
+ {"carrier":"Uninor MMS","apn":"uninor","mmsc":"http://10.58.2.120","mmsproxy":"10.58.10.59","mmsport":"8080","type":["mms"]}
+ ],
+ "821": [
+ {"carrier":"Uninor GPRS","apn":"uninor","type":["default","supl"]},
+ {"carrier":"Uninor MMS","apn":"uninor","mmsc":"http://10.58.2.120","mmsproxy":"10.58.10.59","mmsport":"8080","type":["mms"]}
+ ],
+ "822": [
+ {"carrier":"Uninor GPRS","apn":"uninor","type":["default","supl"]},
+ {"carrier":"Uninor MMS","apn":"uninor","mmsc":"http://10.58.2.120","mmsproxy":"10.58.10.59","mmsport":"8080","type":["mms"]}
+ ],
+ "823": [
+ {"carrier":"Videocon MMS","apn":"vgprs.com","mmsc":"http://10.202.4.119:10021/mmsc/","mmsproxy":"10.202.5.145","mmsport":"8799","type":["mms"]},
+ {"carrier":"Videocon","apn":"vinternet.com","type":["default","supl"]}
+ ],
+ "824": [
+ {"carrier":"Videocon MMS","apn":"vgprs.com","mmsc":"http://10.202.4.119:10021/mmsc/","mmsproxy":"10.202.5.145","mmsport":"8799","type":["mms"]},
+ {"carrier":"Videocon","apn":"vinternet.com","type":["default","supl"]}
+ ],
+ "825": [
+ {"carrier":"Videocon MMS","apn":"vgprs.com","mmsc":"http://10.202.4.119:10021/mmsc/","mmsproxy":"10.202.5.145","mmsport":"8799","type":["mms"]},
+ {"carrier":"Videocon","apn":"vinternet.com","type":["default","supl"]}
+ ],
+ "826": [
+ {"carrier":"Videocon MMS","apn":"vgprs.com","mmsc":"http://10.202.4.119:10021/mmsc/","mmsproxy":"10.202.5.145","mmsport":"8799","type":["mms"]},
+ {"carrier":"Videocon","apn":"vinternet.com","type":["default","supl"]}
+ ],
+ "827": [
+ {"carrier":"Videocon MMS","apn":"vgprs.com","mmsc":"http://10.202.4.119:10021/mmsc/","mmsproxy":"10.202.5.145","mmsport":"8799","type":["mms"]},
+ {"carrier":"Videocon","apn":"vinternet.com","type":["default","supl"]}
+ ],
+ "828": [
+ {"carrier":"Videocon MMS","apn":"vgprs.com","mmsc":"http://10.202.4.119:10021/mmsc/","mmsproxy":"10.202.5.145","mmsport":"8799","type":["mms"]},
+ {"carrier":"Videocon","apn":"vinternet.com","type":["default","supl"]}
+ ],
+ "829": [
+ {"carrier":"Videocon MMS","apn":"vgprs.com","mmsc":"http://10.202.4.119:10021/mmsc/","mmsproxy":"10.202.5.145","mmsport":"8799","type":["mms"]},
+ {"carrier":"Videocon","apn":"vinternet.com","type":["default","supl"]}
+ ],
+ "830": [
+ {"carrier":"Videocon MMS","apn":"vgprs.com","mmsc":"http://10.202.4.119:10021/mmsc/","mmsproxy":"10.202.5.145","mmsport":"8799","type":["mms"]},
+ {"carrier":"Videocon","apn":"vinternet.com","type":["default","supl"]}
+ ],
+ "831": [
+ {"carrier":"Videocon MMS","apn":"vgprs.com","mmsc":"http://10.202.4.119:10021/mmsc/","mmsproxy":"10.202.5.145","mmsport":"8799","type":["mms"]},
+ {"carrier":"Videocon","apn":"vinternet.com","type":["default","supl"]}
+ ],
+ "832": [
+ {"carrier":"Videocon MMS","apn":"vgprs.com","mmsc":"http://10.202.4.119:10021/mmsc/","mmsproxy":"10.202.5.145","mmsport":"8799","type":["mms"]},
+ {"carrier":"Videocon","apn":"vinternet.com","type":["default","supl"]}
+ ],
+ "833": [
+ {"carrier":"Videocon MMS","apn":"vgprs.com","mmsc":"http://10.202.4.119:10021/mmsc/","mmsproxy":"10.202.5.145","mmsport":"8799","type":["mms"]},
+ {"carrier":"Videocon","apn":"vinternet.com","type":["default","supl"]}
+ ],
+ "834": [
+ {"carrier":"Videocon MMS","apn":"vgprs.com","mmsc":"http://10.202.4.119:10021/mmsc/","mmsproxy":"10.202.5.145","mmsport":"8799","type":["mms"]},
+ {"carrier":"Videocon","apn":"vinternet.com","type":["default","supl"]}
+ ],
+ "835": [
+ {"carrier":"Videocon MMS","apn":"vgprs.com","mmsc":"http://10.202.4.119:10021/mmsc/","mmsproxy":"10.202.5.145","mmsport":"8799","type":["mms"]},
+ {"carrier":"Videocon","apn":"vinternet.com","type":["default","supl"]}
+ ],
+ "836": [
+ {"carrier":"Videocon MMS","apn":"vgprs.com","mmsc":"http://10.202.4.119:10021/mmsc/","mmsproxy":"10.202.5.145","mmsport":"8799","type":["mms"]},
+ {"carrier":"Videocon","apn":"vinternet.com","type":["default","supl"]}
+ ],
+ "837": [
+ {"carrier":"Videocon MMS","apn":"vgprs.com","mmsc":"http://10.202.4.119:10021/mmsc/","mmsproxy":"10.202.5.145","mmsport":"8799","type":["mms"]},
+ {"carrier":"Videocon","apn":"vinternet.com","type":["default","supl"]}
+ ],
+ "838": [
+ {"carrier":"Videocon MMS","apn":"vgprs.com","mmsc":"http://10.202.4.119:10021/mmsc/","mmsproxy":"10.202.5.145","mmsport":"8799","type":["mms"]},
+ {"carrier":"Videocon","apn":"vinternet.com","type":["default","supl"]}
+ ],
+ "839": [
+ {"carrier":"Videocon MMS","apn":"vgprs.com","mmsc":"http://10.202.4.119:10021/mmsc/","mmsproxy":"10.202.5.145","mmsport":"8799","type":["mms"]},
+ {"carrier":"Videocon","apn":"vinternet.com","type":["default","supl"]}
+ ],
+ "840": [
+ {"carrier":"Videocon MMS","apn":"vgprs.com","mmsc":"http://10.202.4.119:10021/mmsc/","mmsproxy":"10.202.5.145","mmsport":"8799","type":["mms"]},
+ {"carrier":"Videocon","apn":"vinternet.com","type":["default","supl"]}
+ ],
+ "841": [
+ {"carrier":"Videocon MMS","apn":"vgprs.com","mmsc":"http://10.202.4.119:10021/mmsc/","mmsproxy":"10.202.5.145","mmsport":"8799","type":["mms"]},
+ {"carrier":"Videocon","apn":"vinternet.com","type":["default","supl"]}
+ ],
+ "842": [
+ {"carrier":"Videocon MMS","apn":"vgprs.com","mmsc":"http://10.202.4.119:10021/mmsc/","mmsproxy":"10.202.5.145","mmsport":"8799","type":["mms"]},
+ {"carrier":"Videocon","apn":"vinternet.com","type":["default","supl"]}
+ ],
+ "843": [
+ {"carrier":"Videocon MMS","apn":"vgprs.com","mmsc":"http://10.202.4.119:10021/mmsc/","mmsproxy":"10.202.5.145","mmsport":"8799","type":["mms"]},
+ {"carrier":"Videocon","apn":"vinternet.com","type":["default","supl"]}
+ ],
+ "844": [
+ {"carrier":"Uninor GPRS","apn":"uninor","type":["default","supl"]},
+ {"carrier":"Uninor MMS","apn":"uninor","mmsc":"http://10.58.2.120","mmsproxy":"10.58.10.59","mmsport":"8080","type":["mms"]}
+ ],
+ "845": [
+ {"carrier":"IDEA","apn":"internet","type":["default","supl"]},
+ {"carrier":"IDEA MMS","apn":"mmsc","mmsc":"http://10.4.42.21:8002/","mmsproxy":"10.4.42.15","mmsport":"8080","type":["mms"]}
+ ],
+ "846": [
+ {"carrier":"IDEA","apn":"internet","type":["default","supl"]},
+ {"carrier":"IDEA MMS","apn":"mmsc","mmsc":"http://10.4.42.21:8002/","mmsproxy":"10.4.42.15","mmsport":"8080","type":["mms"]}
+ ],
+ "847": [
+ {"carrier":"IDEA","apn":"internet","type":["default","supl"]},
+ {"carrier":"IDEA MMS","apn":"mmsc","mmsc":"http://10.4.42.21:8002/","mmsproxy":"10.4.42.15","mmsport":"8080","type":["mms"]}
+ ],
+ "848": [
+ {"carrier":"IDEA","apn":"internet","type":["default","supl"]},
+ {"carrier":"IDEA MMS","apn":"mmsc","mmsc":"http://10.4.42.21:8002/","mmsproxy":"10.4.42.15","mmsport":"8080","type":["mms"]}
+ ],
+ "849": [
+ {"carrier":"IDEA","apn":"internet","type":["default","supl"]},
+ {"carrier":"IDEA MMS","apn":"mmsc","mmsc":"http://10.4.42.21:8002/","mmsproxy":"10.4.42.15","mmsport":"8080","type":["mms"]}
+ ],
+ "850": [
+ {"carrier":"IDEA","apn":"internet","type":["default","supl"]},
+ {"carrier":"IDEA MMS","apn":"mmsc","mmsc":"http://10.4.42.21:8002/","mmsproxy":"10.4.42.15","mmsport":"8080","type":["mms"]}
+ ],
+ "851": [
+ {"carrier":"IDEA","apn":"internet","type":["default","supl"]},
+ {"carrier":"IDEA MMS","apn":"mmsc","mmsc":"http://10.4.42.21:8002/","mmsproxy":"10.4.42.15","mmsport":"8080","type":["mms"]}
+ ],
+ "852": [
+ {"carrier":"IDEA","apn":"internet","type":["default","supl"]},
+ {"carrier":"IDEA MMS","apn":"mmsc","mmsc":"http://10.4.42.21:8002/","mmsproxy":"10.4.42.15","mmsport":"8080","type":["mms"]}
+ ],
+ "853": [
+ {"carrier":"IDEA","apn":"internet","type":["default","supl"]},
+ {"carrier":"IDEA MMS","apn":"mmsc","mmsc":"http://10.4.42.21:8002/","mmsproxy":"10.4.42.15","mmsport":"8080","type":["mms"]}
+ ],
+ "875": [
+ {"carrier":"Uninor GPRS","apn":"uninor","type":["default","supl"]},
+ {"carrier":"Uninor MMS","apn":"uninor","mmsc":"http://10.58.2.120","mmsproxy":"10.58.10.59","mmsport":"8080","type":["mms"]}
+ ],
+ "876": [
+ {"carrier":"Uninor GPRS","apn":"uninor","type":["default","supl"]},
+ {"carrier":"Uninor MMS","apn":"uninor","mmsc":"http://10.58.2.120","mmsproxy":"10.58.10.59","mmsport":"8080","type":["mms"]}
+ ],
+ "877": [
+ {"carrier":"Uninor GPRS","apn":"uninor","type":["default","supl"]},
+ {"carrier":"Uninor MMS","apn":"uninor","mmsc":"http://10.58.2.120","mmsproxy":"10.58.10.59","mmsport":"8080","type":["mms"]}
+ ],
+ "878": [
+ {"carrier":"Uninor GPRS","apn":"uninor","type":["default","supl"]},
+ {"carrier":"Uninor MMS","apn":"uninor","mmsc":"http://10.58.2.120","mmsproxy":"10.58.10.59","mmsport":"8080","type":["mms"]}
+ ],
+ "879": [
+ {"carrier":"Uninor GPRS","apn":"uninor","type":["default","supl"]},
+ {"carrier":"Uninor MMS","apn":"uninor","mmsc":"http://10.58.2.120","mmsproxy":"10.58.10.59","mmsport":"8080","type":["mms"]}
+ ],
+ "880": [
+ {"carrier":"Uninor GPRS","apn":"uninor","type":["default","supl"]},
+ {"carrier":"Uninor MMS","apn":"uninor","mmsc":"http://10.58.2.120","mmsproxy":"10.58.10.59","mmsport":"8080","type":["mms"]}
+ ],
+ "881": [
+ {"carrier":"STEL","apn":"gprs.stel.in","type":["default","supl"]}
+ ],
+ "882": [
+ {"carrier":"STEL","apn":"gprs.stel.in","type":["default","supl"]}
+ ],
+ "883": [
+ {"carrier":"STEL","apn":"gprs.stel.in","type":["default","supl"]}
+ ],
+ "884": [
+ {"carrier":"STEL","apn":"gprs.stel.in","type":["default","supl"]}
+ ],
+ "885": [
+ {"carrier":"STEL","apn":"gprs.stel.in","type":["default","supl"]}
+ ],
+ "886": [
+ {"carrier":"STEL","apn":"gprs.stel.in","type":["default","supl"]}
+ ],
+ "908": [
+ {"carrier":"IDEA","apn":"internet","type":["default","supl"]},
+ {"carrier":"IDEA MMS","apn":"mmsc","mmsc":"http://10.4.42.21:8002/","mmsproxy":"10.4.42.15","mmsport":"8080","type":["mms"]}
+ ],
+ "909": [
+ {"carrier":"IDEA","apn":"internet","type":["default","supl"]},
+ {"carrier":"IDEA MMS","apn":"mmsc","mmsc":"http://10.4.42.21:8002/","mmsproxy":"10.4.42.15","mmsport":"8080","type":["mms"]}
+ ],
+ "910": [
+ {"carrier":"IDEA","apn":"internet","type":["default","supl"]},
+ {"carrier":"IDEA MMS","apn":"mmsc","mmsc":"http://10.4.42.21:8002/","mmsproxy":"10.4.42.15","mmsport":"8080","type":["mms"]}
+ ],
+ "911": [
+ {"carrier":"IDEA","apn":"internet","type":["default","supl"]},
+ {"carrier":"IDEA MMS","apn":"mmsc","mmsc":"http://10.4.42.21:8002/","mmsproxy":"10.4.42.15","mmsport":"8080","type":["mms"]}
+ ],
+ "912": [
+ {"carrier":"Cheers","apn":"internet","type":["default","supl"]}
+ ],
+ "913": [
+ {"carrier":"Cheers","apn":"internet","type":["default","supl"]}
+ ],
+ "914": [
+ {"carrier":"Cheers","apn":"internet","type":["default","supl"]}
+ ],
+ "915": [
+ {"carrier":"Cheers","apn":"internet","type":["default","supl"]}
+ ],
+ "916": [
+ {"carrier":"Cheers","apn":"internet","type":["default","supl"]}
+ ],
+ "917": [
+ {"carrier":"Cheers","apn":"internet","type":["default","supl"]}
+ ],
+ "918": [
+ {"carrier":"Cheers","apn":"internet","type":["default","supl"]}
+ ],
+ "919": [
+ {"carrier":"Cheers","apn":"internet","type":["default","supl"]}
+ ],
+ "920": [
+ {"carrier":"Cheers","apn":"internet","type":["default","supl"]}
+ ],
+ "921": [
+ {"carrier":"Cheers","apn":"internet","type":["default","supl"]}
+ ],
+ "922": [
+ {"carrier":"Cheers","apn":"internet","type":["default","supl"]}
+ ],
+ "923": [
+ {"carrier":"Cheers","apn":"internet","type":["default","supl"]}
+ ],
+ "924": [
+ {"carrier":"Cheers","apn":"internet","type":["default","supl"]}
+ ],
+ "925": [
+ {"carrier":"Uninor GPRS","apn":"uninor","type":["default","supl"]},
+ {"carrier":"Uninor MMS","apn":"uninor","mmsc":"http://10.58.2.120","mmsproxy":"10.58.10.59","mmsport":"8080","type":["mms"]}
+ ],
+ "926": [
+ {"carrier":"Uninor GPRS","apn":"uninor","type":["default","supl"]},
+ {"carrier":"Uninor MMS","apn":"uninor","mmsc":"http://10.58.2.120","mmsproxy":"10.58.10.59","mmsport":"8080","type":["mms"]}
+ ],
+ "927": [
+ {"carrier":"Uninor GPRS","apn":"uninor","type":["default","supl"]},
+ {"carrier":"Uninor MMS","apn":"uninor","mmsc":"http://10.58.2.120","mmsproxy":"10.58.10.59","mmsport":"8080","type":["mms"]}
+ ],
+ "928": [
+ {"carrier":"Uninor GPRS","apn":"uninor","type":["default","supl"]},
+ {"carrier":"Uninor MMS","apn":"uninor","mmsc":"http://10.58.2.120","mmsproxy":"10.58.10.59","mmsport":"8080","type":["mms"]}
+ ],
+ "929": [
+ {"carrier":"Uninor GPRS","apn":"uninor","type":["default","supl"]},
+ {"carrier":"Uninor MMS","apn":"uninor","mmsc":"http://10.58.2.120","mmsproxy":"10.58.10.59","mmsport":"8080","type":["mms"]}
+ ],
+ "930": [
+ {"carrier":"Cheers","apn":"internet","type":["default","supl"]}
+ ],
+ "931": [
+ {"carrier":"Cheers","apn":"internet","type":["default","supl"]}
+ ],
+ "932": [
+ {"carrier":"Videocon MMS","apn":"vgprs.com","mmsc":"http://10.202.4.119:10021/mmsc/","mmsproxy":"10.202.5.145","mmsport":"8799","type":["mms"]},
+ {"carrier":"Videocon","apn":"vinternet.com","type":["default","supl"]}
+ ]
+},
+"410": {
+ "1": [
+ {"carrier":"Mobilink WAP GPRS","apn":"connect.mobilinkworld.com","user":"Mobilink","password":"Mobilink","type":["default","supl"]},
+ {"carrier":"Mobilink MMS","apn":"mms.mobilinkworld.com","user":"Mobilink","password":"Mobilink","mmsc":"http://mms/","mmsproxy":"172.25.20.12","mmsport":"8080","type":["mms"]}
+ ],
+ "3": [
+ {"carrier":"Ufone WAP","apn":"Ufone.internet","type":["default","supl"]},
+ {"carrier":"Ufone MMS","apn":"Ufone.mms","mmsc":"http://www.ufonemms.com:80/","mmsproxy":"172.16.13.27","mmsport":"8080","type":["mms"]}
+ ],
+ "4": [
+ {"carrier":"ZONG WAP","apn":"zonginternet","type":["default","supl"]},
+ {"carrier":"ZONG MMS","apn":"zongmms","mmsc":"http://10.81.6.11:8080","mmsproxy":"10.81.6.33","mmsport":"8000","type":["mms"]}
+ ],
+ "6": [
+ {"carrier":"Telenor WAP","apn":"internet","type":["default","supl"]},
+ {"carrier":"Telenor MMS","apn":"mms","user":"Telenor","password":"Telenor","mmsc":"http://mmstelenor","mmsproxy":"172.18.19.11","mmsport":"8080","type":["mms"]}
+ ],
+ "7": [
+ {"carrier":"Warid WAP","apn":"Wap.warid","type":["default","supl"]},
+ {"carrier":"Warid MMS","apn":"mms.warid","mmsc":"http://10.4.0.132/servlets/MMS","mmsproxy":"10.4.2.1","mmsport":"8080","type":["mms"]}
+ ]
+},
+"415": {
+ "1": [
+ {"carrier":"Alfa Internet","user":"mic1","password":"mic1","apn":"internet.mic1.com.lb","type":["default","supl"]},
+ {"carrier":"Alfa MMS","user":"mic1","password":"mic1","apn":"mms.mic1.com.lb","mmsc":"http://mms.mic1.com.lb","mmsproxy":"192.168.23.51","mmsport":"80","type":["mms"]}
+ ],
+ "3": [
+ {"carrier":"MTC Touch GPRS","apn":"gprs.mtctouch.com.lb","type":["default","supl"]},
+ {"carrier":"MTC MMS","user":"mtctouch","apn":"mms.mtctouch.com.lb","mmsc":"http://mms:8080/mms/","mmsproxy":"192.168.4.103","mmsport":"80","type":["mms"]}
+ ]
+},
+"416": {
+ "1": [
+ {"carrier":"Zain JO Internet Postpaid","apn":"internet","type":["default","supl"]},
+ {"carrier":"Zain JO Internet Prepaid","apn":"internetpre","user":"zain","password":"zain","type":["default","supl"]},
+ {"carrier":"Zain JO MMS","apn":"mms","user":"mms","password":"mms","mmsc":"http://mms.jo.zain.com","mmsproxy":"192.168.55.10","mmsport":"80","type":["mms"]}
+ ],
+ "3": [
+ {"carrier":"umniahinternet Postpaid","apn":"internet","type":["default","supl"]},
+ {"carrier":"umniahmms","apn":"mms","mmsc":"http://mms.umniah.com","mmsproxy":"10.1.1.10","mmsport":"8080","type":["mms"]},
+ {"carrier":"umniah Prepaid","apn":"net","proxy":"0.0.0.0","port":"80","type":["default","supl"]}
+ ],
+ "77": [
+ {"carrier":"Orange MMS","apn":"mms.orange.jo","user":"mmc","password":"mmc","mmsc":"http://172.16.1.96/servlets/mms","mmsproxy":"172.16.1.2","mmsport":"8080","type":["mms"]},
+ {"carrier":"Orange Internet","apn":"net.orange.jo","user":"net","password":"net","type":["default","supl"]}
+ ]
+},
+"419": {
+ "2": [
+ {"carrier":"ZAIN MMS","apn":"pps","user":"annyway","password":"online","mmsc":"http://mms.zain","mmsproxy":"176.0.0.65","mmsport":"8080","type":["mms"]},
+ {"carrier":"ZAIN MI","apn":"pps","user":"pps","password":"pps","type":["default","supl"]}
+ ],
+ "3": [
+ {"carrier":"WATANIYA MMS","apn":"mms.wataniya.com","mmsc":"http://action.wataniya.com","mmsproxy":"194.126.53.64","mmsport":"8080","type":["mms"]},
+ {"carrier":"INTERNET ACTION","apn":"action.wataniya.com","type":["default","supl"]}
+ ],
+ "4": [
+ {"carrier":"VIVA MMS","apn":"VIVA","mmsc":"http://172.16.128.80:38090/was","mmsproxy":"172.16.128.228","mmsport":"8080","type":["mms"]},
+ {"carrier":"VIVA Internet","apn":"VIVA","type":["default","supl"]}
+ ]
+},
+"420": {
+ "1": [
+ {"carrier":"JAWALNet","apn":"jawalnet.com.sa","type":["default","supl"]},
+ {"carrier":"ALJAWAL MMS","apn":"mms.net.sa","mmsc":"http://mms.net.sa:8002","mmsproxy":"10.1.1.1","mmsport":"8080","type":["mms"]}
+ ],
+ "3": [
+ {"carrier":"mobily Net Prepaid","apn":"web2","type":["default","supl"]},
+ {"carrier":"mobily MMS Prepaid","apn":"mms2","mmsc":"http://10.3.3.133:9090/was","mmsproxy":"10.3.2.133","mmsport":"8080","type":["mms"]},
+ {"carrier":"mobily Net Postpaid","apn":"web1","type":["default","supl"]},
+ {"carrier":"mobily MMS Postpaid","apn":"mms1","mmsc":"http://10.3.3.133:9090/was","mmsproxy":"10.3.2.133","mmsport":"8080","type":["mms"]}
+ ],
+ "4": [
+ {"carrier":"zain-gprs","apn":"zain","type":["default","supl"]},
+ {"carrier":"zain-mms","apn":"zain","mmsc":"http://10.122.200.12:8002/","mmsproxy":"10.122.200.10","mmsport":"8080","type":["mms"]}
+ ]
+},
+"422": {
+ "2": [
+ {"carrier":"Oman Mobile MMS","apn":"MMS","user":"MMS","password":"MMS","mmsc":"http://mmsc.omanmobile.om:10021/mmsc","mmsproxy":"192.168.203.35","mmsport":"8080","type":["mms"]},
+ {"carrier":"Oman Mobile Internet","apn":"taif","user":"taif","password":"taif","type":["default","supl"]}
+ ],
+ "3": [
+ {"carrier":"Nawras MMS","apn":"mms.nawras.com.om","user":"test","password":"test","mmsc":"http://10.128.240.16/servlets/mms","mmsproxy":"10.128.240.19","mmsport":"8080","type":["mms"]},
+ {"carrier":"Nawras GPRS","apn":"isp.nawras.com.om","type":["default","supl"]}
+ ]
+},
+"424": {
+ "2": [
+ {"carrier":"Etisalat MMS","apn":"etisalat","mmsc":"http://mms/servlets/mms","mmsproxy":"10.12.0.32","mmsport":"8080","type":["mms"]},
+ {"carrier":"DATA Package","apn":"etisalat.ae","type":["default","supl"]}
+ ],
+ "3": [
+ {"carrier":"du","apn":"du","type":["default","supl"]},
+ {"carrier":"du MMS","apn":"du","mmsc":"http://mms.du.ae","mmsproxy":"10.19.18.4","mmsport":"8080","type":["mms"]}
+ ]
+},
+"425": {
+ "1": [
+ {"carrier":"3G Portal","apn":"uwap.orange.co.il","port":"8080","type":["default","supl"]},
+ {"carrier":"MMS 3G","apn":"uwap.orange.co.il","mmsc":"http://192.168.220.15/servlets/mms","mmsport":"8080","type":["mms"]},
+ {"carrier":"Internet 3G","apn":"modem.orange.net.il","type":["default","supl"]}
+ ],
+ "2": [
+ {"carrier":"Cellcom Internet","apn":"Sphone","type":["default","supl"]},
+ {"carrier":"Cellcom MMS","apn":"mms","mmsc":"http://mms.cellcom.co.il","mmsproxy":"172.31.29.38","mmsport":"8080","type":["mms"]}
+ ],
+ "3": [
+ {"carrier":"Multimedia Pelephone","apn":"mms.pelephone.net.il","user":"pcl@3g","password":"pcl","mmsc":"http://mmsu.pelephone.net.il","mmsproxy":"10.170.252.104","mmsport":"9093","type":["mms"]},
+ {"carrier":"Sphone Pelephone","apn":"sphone.pelephone.net.il","user":"pcl@3g","password":"pcl","type":["default","supl"]}
+ ]
+},
+"426": {
+ "1": [
+ {"carrier":"Internet","apn":"internet.batelco.com","type":["default","supl"]},
+ {"carrier":"BAT MMS","apn":"mms.batelco.com","mmsc":"http://192.168.36.10/servlets/mms","mmsproxy":"192.168.1.2","mmsport":"80","type":["mms"]}
+ ],
+ "2": [
+ {"carrier":"Zain Internet","apn":"connect.mobilinkworld.com","user":"Mobilink","password":"Mobilink","type":["default","supl"]},
+ {"carrier":"Zain MMS","apn":"mms","user":"mms","password":"mms","mmsc":"http://172.18.83.129","mmsproxy":"172.18.85.34","mmsport":"80","type":["mms"]}
+ ],
+ "4": [
+ {"carrier":"VIVAGPRS","apn":"viva.bh","type":["default","supl"]},
+ {"carrier":"VIVAMMS","apn":"vivawap.bh","mmsc":"http://mms.viva.com.bh:38090","mmsproxy":"172.18.142.36","mmsport":"8080","type":["mms"]}
+ ]
+},
+"427": {
+ "1": [
+ {"carrier":"Qtel MMS","apn":"mms.qtel","user":"mms","password":"mms","mmsc":"http://mmsr.qtelmms.qa","mmsproxy":"10.23.8.3","mmsport":"8080","type":["mms"]},
+ {"carrier":"Qtel GPRS","apn":"gprs.qtel","user":"gprs","password":"gprs","proxy":"10.23.8.3","port":"8080","type":["default","supl"]}
+ ],
+ "2": [
+ {"carrier":"VFQ MMS","apn":"vodafone.com.qa","mmsc":"http://mms.vodafone.com.qa/mmsc","mmsproxy":"10.101.97.102","mmsport":"80","type":["mms"]},
+ {"carrier":"VFQ Mobile Internet","apn":"web.vodafone.com.qa","type":["default","supl"]}
+ ]
+},
+"440": {
+ "10": [
+ {"carrier":"spモード","apn":"spmode.ne.jp","user":"","server":"","password":"","mmsc":""},
+ {"carrier":"mopera U(スマートフォン定額)","apn":"mpr2.bizho.net","user":"","server":"","password":"","mmsc":""},
+ {"carrier":"mopera U設定","apn":"0120.mopera.net","user":"","server":"","password":"","mmsc":""}
+ ]
+},
+"450": {
+ "5": [
+ {"carrier":"SK Telecom","apn":"web.sktelecom.com","mmsc":"http://omms.nate.com:9082/oma_mms","mmsproxy":"smart.nate.com","mmsport":"9093","type":["default","supl","mms"]},
+ {"carrier":"SK Telecom (Roaming)","apn":"roaming.sktelecom.com","mmsc":"http://omms.nate.com:9082/oma_mms","mmsproxy":"smart.nate.com","mmsport":"9093","type":["default","supl","mms"]}
+ ],
+ "8": [
+ {"carrier":"KT-HSDPA","apn":"alwayson-r6.ktfwing.com","mmsc":"http://mmsc.ktfwing.com:9082","type":["default","supl","mms"]}
+ ]
+},
+"452": {
+ "1": [
+ {"carrier":"VN MOBIFONE MMS","apn":"m-i090","user":"mms","password":"mms","mmsc":"http://203.162.21.114/mmsc","mmsproxy":"203.162.21.114","mmsport":"3130","type":["mms"]},
+ {"carrier":"VN MOBIFONE Email","apn":"m-wap","user":"mms","password":"mms","type":["default","supl"]}
+ ],
+ "2": [
+ {"carrier":"Vinaphone MMS","apn":"m3-mms","user":"mms","password":"mms","mmsc":"http://mms.vinaphone.com.vn","mmsproxy":"10.1.10.46","mmsport":"8000","type":["mms"]},
+ {"carrier":"Vinaphone Email","apn":"m3-world","user":"mms","password":"mms","type":["default","supl"]}
+ ],
+ "4": [
+ {"carrier":"Viettel Email","apn":"v-internet","type":["default","supl"]},
+ {"carrier":"Viettel MMS","apn":"v-mms","mmsc":"http://mms.viettelmobile.com.vn/mms/wapenc","mmsproxy":"192.168.233.10","mmsport":"8080","type":["mms"]}
+ ],
+ "5": [
+ {"carrier":"Vietnamobile Email","apn":"internet","type":["default","supl"]},
+ {"carrier":"Vietnamobile MMS","apn":"mms","mmsc":"http://10.10.128.58/servlets/mms","mmsproxy":"10.10.128.44","mmsport":"8080","type":["mms"]}
+ ],
+ "7": [
+ {"carrier":"BEELINE Email","apn":"internet","type":["default","supl"]},
+ {"carrier":"BEELINE MMS","apn":"mms","user":"mms","password":"mms","mmsc":"http://mms","mmsproxy":"10.16.70.199","mmsport":"8080","type":["mms"]}
+ ],
+ "8": [
+ {"carrier":"EVNTelecom Email","apn":"e-internet","type":["default","supl"]},
+ {"carrier":"EVNTelecom MMS","apn":"e-wap","mmsc":"http://10.18.2.172:38090","mmsproxy":"10.18.2.183","mmsport":"8080","type":["mms"]}
+ ]
+},
+"454": {
+ "0": [
+ {"carrier":"CSL Data","apn":"hkcsl","mmsc":"http://192.168.58.171:8002","mmsproxy":"192.168.59.51","mmsport":"8080","type":["default","supl","mms"]}
+ ],
+ "2": [
+ {"carrier":"CSL Data","apn":"hkcsl","mmsc":"http://192.168.58.171:8002","mmsproxy":"192.168.59.51","mmsport":"8080","type":["default","supl","mms"]}
+ ],
+ "3": [
+ {"carrier":"3","apn":"mobile.three.com.hk","mmsc":"http://mms.um.three.com.hk:10021/mmsc","mmsproxy":"172.20.97.116","mmsport":"8799","type":["default","supl","mms"]}
+ ],
+ "4": [
+ {"carrier":"3-DB-MMS","apn":"mms-g.three.com.hk","mmsc":"http://10.30.15.51:10021/mmsc","mmsproxy":"10.30.15.53","mmsport":"8080","type":["mms"]},
+ {"carrier":"3-DB-GPRS","apn":"web-g.three.com.hk","proxy":"10.30.15.53","port":"8080","type":["default","supl"]}
+ ],
+ "6": [
+ {"carrier":"SmarTone HK","apn":"SmarTone","mmsc":"http://mms.smartone.com/server","mmsproxy":"10.9.9.9","mmsport":"8080","type":["default","supl","mms"]}
+ ],
+ "10": [
+ {"carrier":"CSL Data","apn":"hkcsl","mmsc":"http://192.168.58.171:8002","mmsproxy":"192.168.59.51","mmsport":"8080","type":["default","supl","mms"]}
+ ],
+ "12": [
+ {"carrier":"CMHK MMS","apn":"peoples.mms","mmsc":"http://mms.hk.chinamobile.com/mms","mmsproxy":"172.31.31.36","mmsport":"8080","type":["mms"]},
+ {"carrier":"CMHK GPRS","apn":"peoples.net","proxy":"172.31.31.36","port":"8080","type":["default","supl"]}
+ ],
+ "16": [
+ {"carrier":"PCCW_GPRS","apn":"pccwdata","proxy":"10.131.2.8","port":"8080","type":["default","supl"]},
+ {"carrier":"PCCW_MMS","apn":"pccwmms","mmsc":"http://mmsc.mms.pccwmobile.com:8002/","mmsproxy":"10.131.2.8","mmsport":"8080","type":["mms"]}
+ ],
+ "18": [
+ {"carrier":"CSL Data","apn":"hkcsl","mmsc":"http://192.168.58.171:8002","mmsproxy":"192.168.59.51","mmsport":"8080","type":["default","supl","mms"]}
+ ],
+ "19": [
+ {"carrier":"PCCW 3G","apn":"pccw","mmsc":"http://3gmms.pccwmobile.com:8080/was","mmsproxy":"10.140.14.10","mmsport":"8080","type":["default","supl","mms"]}
+ ]
+},
+"455": {
+ "0": [
+ {"carrier":"SmarTone MAC","apn":"smartgprs","proxy":"10.9.9.29","port":"8080","mmsc":"http://mms.smartone.com.mo/dmog/mo","mmsproxy":"10.9.9.29","mmsport":"8080","type":["default","supl","mms"]}
+ ],
+ "1": [
+ {"carrier":"CTM","apn":"ctm-mobile","proxy":"192.168.99.2","port":"8080","type":["default","supl"]},
+ {"carrier":"CTM MMS","apn":"ctmmms","mmsc":"http://mms.wap.ctm.net:8002","mmsproxy":"192.168.99.3","mmsport":"8080","type":["mms"]}
+ ],
+ "3": [
+ {"carrier":"3 Macau MMS","apn":"mms.hutchisonmacau.com","mmsc":"http://10.30.15.51:10021/mmsc","mmsproxy":"10.30.15.53","mmsport":"8080","type":["mms"]},
+ {"carrier":"3 Macau","apn":"web-g.three.com.hk","user":"hutchison","password":"1234","type":["default","supl"]}
+ ],
+ "4": [
+ {"carrier":"CTM","apn":"ctm-mobile","proxy":"192.168.99.2","port":"8080","type":["default","supl"]},
+ {"carrier":"CTM MMS","apn":"ctmmms","mmsc":"http://mms.wap.ctm.net:8002","mmsproxy":"192.168.99.3","mmsport":"8080","type":["mms"]}
+ ]
+},
+"460": {
+ "0": [
+ {"carrier":"中国移动 (China Mobile) GPRS","apn":"cmnet","type":["default","supl"]},
+ {"carrier":"中国移动 (China Mobile) WAP","apn":"cmwap","proxy":"10.0.0.172","port":"80","type":["default","supl"]},
+ {"carrier":"中国移动彩信 (China Mobile)","apn":"cmwap","proxy":"10.0.0.172","port":"80","mmsc":"http://mmsc.monternet.com","mmsproxy":"10.0.0.172","mmsport":"80","type":["mms"]}
+ ],
+ "1": [
+ {"carrier":"沃3G连接互联网 (China Unicom)","apn":"3gnet","type":["default","supl"]},
+ {"carrier":"沃3G手机上网 (China Unicom)","apn":"3gwap","proxy":"10.0.0.172","port":"80","type":["default","supl"]},
+ {"carrier":"联通2GNET上网 (China Unicom)","apn":"uninet","type":["default","supl"]},
+ {"carrier":"联通彩信 (China Unicom)","apn":"3gwap","mmsc":"http://mmsc.myuni.com.cn","mmsproxy":"10.0.0.172","mmsport":"80","type":["mms"]},
+ {"carrier":"联通2g彩信 (China Unicom)","apn":"uniwap","mmsc":"http://mmsc.myuni.com.cn","mmsproxy":"10.0.0.172","mmsport":"80","type":["mms"]}
+ ],
+ "2": [
+ {"carrier":"中国移动 (China Mobile) GPRS","apn":"cmnet","type":["default","supl"]},
+ {"carrier":"中国移动 (China Mobile) WAP","apn":"cmwap","proxy":"10.0.0.172","port":"80","type":["default","supl"]},
+ {"carrier":"中国移动彩信 (China Mobile)","apn":"cmwap","proxy":"10.0.0.172","port":"80","mmsc":"http://mmsc.monternet.com","mmsproxy":"10.0.0.172","mmsport":"80","type":["mms"]}
+ ],
+ "7": [
+ {"carrier":"中国移动 (China Mobile) GPRS","apn":"cmnet","type":["default","supl"]},
+ {"carrier":"中国移动 (China Mobile) WAP","apn":"cmwap","proxy":"10.0.0.172","port":"80","type":["default","supl"]},
+ {"carrier":"中国移动彩信 (China Mobile)","apn":"cmwap","proxy":"10.0.0.172","port":"80","mmsc":"http://mmsc.monternet.com","mmsproxy":"10.0.0.172","mmsport":"80","type":["mms"]}
+ ]
+},
+"466": {
+ "1": [
+ {"carrier":"遠傳電信(Far EasTone) (MMS)","apn":"fetnet01","mmsc":"http://mms","mmsproxy":"210.241.199.199","mmsport":"9201","type":["mms"]},
+ {"carrier":"遠傳電信(Far EasTone) (Internet)","apn":"internet","type":["default","supl"]}
+ ],
+ "88": [
+ {"carrier":"和信電訊(KGT-Online) (Internet)","apn":"internet","type":["default","supl"]},
+ {"carrier":"和信電訊(KGT-Online) (MMS)","apn":"kgtmms","mmsc":"http://mms.kgtmms.net.tw/mms/wapenc","mmsproxy":"172.28.33.5","mmsport":"8080","type":["mms"]}
+ ],
+ "89": [
+ {"carrier":"威寶電信(VIBO)","apn":"vibo","mmsc":"http://mms","mmsproxy":"172.24.128.36","mmsport":"8080","type":["default","supl","mms"]}
+ ],
+ "92": [
+ {"carrier":"中華電信(Chunghwa) (Internet)","apn":"internet","type":["default","supl"]},
+ {"carrier":"中華電信(Chunghwa)","apn":"emome","mmsc":"http://mms.emome.net:8002","mmsproxy":"10.1.1.1","mmsport":"8080","type":["default","supl","mms"]}
+ ],
+ "93": [
+ {"carrier":"台灣大哥大(TW Mobile) (Internet)","apn":"internet","type":["default","supl"]},
+ {"carrier":"台灣大哥大(TW Mobile) (twm)","apn":"TWM","type":["default","supl"]},
+ {"carrier":"台灣大哥大(TW Mobile) (MMS)","apn":"mms","mmsc":"http://mms.catch.net.tw","mmsproxy":"10.1.1.2","mmsport":"80","type":["mms"]}
+ ],
+ "97": [
+ {"carrier":"台灣大哥大(TW Mobile) (Internet)","apn":"internet","type":["default","supl"]},
+ {"carrier":"台灣大哥大(TW Mobile) (twm)","apn":"TWM","type":["default","supl"]},
+ {"carrier":"台灣大哥大(TW Mobile) (MMS)","apn":"mms","mmsc":"http://mms.catch.net.tw","mmsproxy":"10.1.1.2","mmsport":"80","type":["mms"]}
+ ],
+ "99": [
+ {"carrier":"台灣大哥大(TW Mobile) (Internet)","apn":"internet","type":["default","supl"]},
+ {"carrier":"台灣大哥大(TW Mobile) (twm)","apn":"TWM","type":["default","supl"]},
+ {"carrier":"台灣大哥大(TW Mobile) (MMS)","apn":"mms","mmsc":"http://mms.catch.net.tw","mmsproxy":"10.1.1.2","mmsport":"80","type":["mms"]}
+ ]
+},
+"502": {
+ "12": [
+ {"carrier":"Maxis MMS","apn":"net","user":"maxis","password":"wap","mmsc":"http://172.16.74.100:10021/mmsc","mmsproxy":"202.75.133.49","mmsport":"80","type":["mms"]},
+ {"carrier":"Maxis Internet","apn":"net","user":"maxis","password":"wap","type":["default","supl"]},
+ {"carrier":"Maxis 3G MMS","apn":"unet","user":"maxis","password":"wap","mmsc":"http://172.16.74.100:10021/mmsc","mmsproxy":"202.75.133.49","mmsport":"80","type":["mms"]},
+ {"carrier":"Maxis 3G Internet","apn":"unet","user":"maxis","password":"wap","type":["default","supl"]}
+ ],
+ "16": [
+ {"carrier":"DiGi MMS","apn":"digimms","user":"mms","password":"mms","mmsc":"http://mms.digi.com.my/servlets/mms","mmsproxy":"203.92.128.160","mmsport":"80","type":["mms"]},
+ {"carrier":"DiGi Internet","apn":"diginet","type":["default","supl"]}
+ ],
+ "18": [
+ {"carrier":"U Mobile MMS","apn":"my3g","mmsc":"http://10.30.3.11/servlets/mms","mmsproxy":"10.30.5.11","mmsport":"8080","type":["mms"]},
+ {"carrier":"U Mobile Internet","apn":"my3g","type":["default","supl"]}
+ ],
+ "19": [
+ {"carrier":"Celcom Internet","apn":"celcom.net.my","type":["default","supl"]},
+ {"carrier":"Celcom 3G MMS","apn":"celcom3g","mmsc":"http://mms.celcom.net.my","mmsproxy":"10.128.1.242","mmsport":"8080","type":["mms"]},
+ {"carrier":"Celcom 3G Internet","apn":"celcom3g","type":["default","supl"]},
+ {"carrier":"Celcom MMS","apn":"mms.celcom.net.my","mmsc":"http://mms.celcom.net.my","mmsproxy":"10.128.1.242","mmsport":"8080","type":["mms"]}
+ ]
+},
+"505": {
+ "1": [
+ {"carrier":"Telstra MMS","apn":"telstra.mms","mmsc":"http://mmsc.telstra.com:8002","mmsproxy":"10.1.1.180","mmsport":"80","type":["mms"]},
+ {"carrier":"Telstra Internet","apn":"telstra.wap","type":["default","supl"]}
+ ],
+ "2": [
+ {"carrier":"Optus Yes Internet","apn":"yesinternet","type":["default","supl"]},
+ {"carrier":"Optus MMS","apn":"mms","mmsc":"http://mmsc.optus.com.au:8002/","mmsproxy":"61.88.190.10","mmsport":"8070","type":["mms"]}
+ ],
+ "3": [
+ {"carrier":"Vodafone live!","apn":"live.vodafone.com","mmsc":"http://pxt.vodafone.net.au/pxtsend","mmsproxy":"10.202.2.60","mmsport":"8080","type":["default","supl","mms"]}
+ ],
+ "6": [
+ {"carrier":"Planet 3","apn":"3services","mmsc":"http://mmsc.three.net.au:10021/mmsc","mmsproxy":"10.176.57.25","mmsport":"8799","type":["default","supl","mms"]}
+ ],
+ "7": [
+ {"carrier":"VF AU PXT","apn":"live.vodafone.com","mmsc":"http://pxt.vodafone.net.au/pxtsend","mmsproxy":"10.202.2.60","mmsport":"8080","type":["mms"]},
+ {"carrier":"VF Internet","apn":"vfinternet.au","type":["default","supl"]}
+ ],
+ "11": [
+ {"carrier":"Telstra MMS","apn":"Telstra.mms","mmsc":"http://mmsc.telstra.com:8002","mmsproxy":"10.1.1.180","mmsport":"80","type":["mms"]},
+ {"carrier":"Telstra Internet","apn":"Telstra.wap","type":["default","supl"]}
+ ],
+ "12": [
+ {"carrier":"3Internet","apn":"3netaccess","type":["default","supl"]},
+ {"carrier":"3","apn":"3services","mmsc":"http://mmsc.three.net.au:10021/mmsc","mmsproxy":"10.176.57.25","mmsport":"8799","type":["default","supl","mms"]}
+ ],
+ "71": [
+ {"carrier":"Telstra MMS","apn":"Telstra.mms","mmsc":"http://mmsc.telstra.com:8002","mmsproxy":"10.1.1.180","mmsport":"80","type":["mms"]},
+ {"carrier":"Telstra Internet","apn":"Telstra.wap","type":["default","supl"]}
+ ],
+ "72": [
+ {"carrier":"Telstra MMS","apn":"Telstra.mms","mmsc":"http://mmsc.telstra.com:8002","mmsproxy":"10.1.1.180","mmsport":"80","type":["mms"]},
+ {"carrier":"Telstra Internet","apn":"Telstra.wap","type":["default","supl"]}
+ ],
+ "88": [
+ {"carrier":"VF AU PXT","apn":"live.vodafone.com","mmsc":"http://pxt.vodafone.net.au/pxtsend","mmsproxy":"10.202.2.60","mmsport":"8080","type":["mms"]},
+ {"carrier":"VF Internet","apn":"vfinternet.au","type":["default","supl"]}
+ ],
+ "90": [
+ {"carrier":"Optus Internet","apn":"internet","type":["default","supl"]},
+ {"carrier":"Optus MMS","apn":"mms","mmsc":"http://mmsc.optus.com.au:8002/","mmsproxy":"61.88.190.10","mmsport":"8070","type":["mms"]}
+ ]
+},
+"510": {
+ "1": [
+ {"carrier":"ISAT BB INTERNET","apn":"indosat3g","user":"indosat","password":"indosat","proxy":"10.19.19.19","port":"8080","type":["default","supl"]},
+ {"carrier":"ISAT WAP","apn":"indosatgprs","user":"indosat","password":"indosat","type":["default","supl"]},
+ {"carrier":"ISAT MMS","apn":"indosatmms","user":"indosat","password":"indosat","mmsc":"http://mmsc.indosat.com","mmsproxy":"10.19.19.19","mmsport":"8080","type":["mms"]},
+ {"carrier":"ISAT BB MMS","apn":"indosatmms","user":"indosat","password":"indosat","mmsc":"http://mmsc.indosat.com","mmsproxy":"10.19.19.19","mmsport":"8080","type":["mms"]}
+ ],
+ "8": [
+ {"carrier":"AXISinternet","apn":"AXIS","user":"AXIS","password":"123456","type":["default","supl"]},
+ {"carrier":"AXIS MMS","apn":"axismms","user":"axis","password":"123456","mmsc":"http://mmsc.axis","mmsproxy":"10.8.3.8","mmsport":"8080","type":["mms"]}
+ ],
+ "10": [
+ {"carrier":"Telkomsel GPRS WEB","apn":"internet","user":"wap","password":"wap123","type":["default","supl"]},
+ {"carrier":"TSEL MMS","apn":"mms","user":"wap","password":"wap123","mmsc":"http://mms.telkomsel.com","mmsproxy":"10.1.89.150","mmsport":"8000","type":["mms"]}
+ ],
+ "11": [
+ {"carrier":"XL GPRS","apn":"www.xlgprs.net","user":"xlgprs","password":"proxl","type":["default","supl"]},
+ {"carrier":"XL MMS","apn":"www.xlmms.net","user":"xlgprs","password":"proxl","mmsc":"http://mmc.xl.net.id/servlets/mms","mmsproxy":"202.152.240.50","mmsport":"8080","type":["mms"]},
+ {"carrier":"XL Unlimited","apn":"xlunlimited","type":["default","supl"]}
+ ],
+ "21": [
+ {"carrier":"ISAT M3 INTERNET","apn":"indosatgprs","user":"indosat","password":"indosat","type":["default","supl"]}
+ ],
+ "89": [
+ {"carrier":"3 GPRS","apn":"3gprs","user":"3gprs","password":"3gprs","type":["default","supl"]},
+ {"carrier":"3 MMS","apn":"3mms","user":"3mms","password":"3mms","mmsc":"http://mms.three.co.id","mmsproxy":"10.4.0.10","mmsport":"3128","type":["mms"]}
+ ]
+},
+"515": {
+ "2": [
+ {"carrier":"myGlobe Internet Postpaid","apn":"internet.globe.com.ph","type":["default","supl"]},
+ {"carrier":"myGlobe Internet Prepaid","apn":"http.globe.com.ph","type":["default","supl"]},
+ {"carrier":"myGlobe MMS","apn":"mms.globe.com.ph","mmsc":"http://192.40.100.22:10021/mmsc","mmsproxy":"203.177.42.214","mmsport":"8080","type":["mms"]}
+ ],
+ "3": [
+ {"carrier":"Smart Internet","apn":"internet","type":["default","supl"]},
+ {"carrier":"SmartMMS","apn":"mms","mmsc":"http://10.102.61.238:8002","mmsproxy":"10.102.61.46","mmsport":"8080","type":["mms"]}
+ ],
+ "5": [
+ {"carrier":"SUN INTERNET","apn":"minternet","type":["default","supl"]},
+ {"carrier":"SUN MMS","apn":"mms","mmsc":"http://mmscenter.suncellular.com.ph","mmsproxy":"202.138.159.78","mmsport":"8080","type":["mms"]}
+ ],
+ "18": [
+ {"carrier":"RED INTERNET","apn":"redinternet","type":["default","supl"]},
+ {"carrier":"RED MMS","apn":"redmms","mmsc":"http://10.102.61.193:8002/mmsc","mmsproxy":"10.138.3.35","mmsport":"8080","type":["mms"]}
+ ]
+},
+"520": {
+ "1": [
+ {"carrier":"AIS Internet","apn":"internet","type":["default","supl"]},
+ {"carrier":"AIS MMS","apn":"multimedia","mmsc":"http://mms.mobilelife.co.th","mmsproxy":"203.170.229.34","mmsport":"8080","type":["mms"]}
+ ],
+ "18": [
+ {"carrier":"DTAC MMS","apn":"mms","mmsc":"http://mms.dtac.co.th:8002/","mmsproxy":"203.155.200.133","mmsport":"8080","type":["mms"]},
+ {"carrier":"DTAC Internet","apn":"www.dtac.co.th","type":["default","supl"]}
+ ],
+ "99": [
+ {"carrier":"True GPRS Inet","apn":"internet","user":"true","password":"true","type":["default","supl"]},
+ {"carrier":"True GPRS MMS","apn":"mms","user":"true","password":"true","mmsc":"http://mms.trueworld.net:8002/","mmsproxy":"10.4.7.39","mmsport":"8080","type":["mms"]}
+ ]
+},
+"525": {
+ "1": [
+ {"carrier":"IDEAS E-mail","apn":"e-ideas","mmsc":"http://mms.singtel.com:10021/mmsc","mmsproxy":"165.21.42.84","mmsport":"8080","type":["default","supl","mms"]}
+ ],
+ "2": [
+ {"carrier":"IDEAS E-mail","apn":"e-ideas","mmsc":"http://mms.singtel.com:10021/mmsc","mmsproxy":"165.21.42.84","mmsport":"8080","type":["default","supl","mms"]}
+ ],
+ "3": [
+ {"carrier":"M1 MMS","apn":"miworld","user":"65","password":"user123","mmsc":"http://mmsgw:8002","mmsproxy":"172.16.14.10","mmsport":"8080","type":["mms"]},
+ {"carrier":"M1 E-mail","apn":"sunsurf","user":"65","type":["default","supl"]}
+ ],
+ "5": [
+ {"carrier":"StarHub","apn":"internet","type":["default","supl"]},
+ {"carrier":"Gee! MMS","apn":"shmms","user":"star","password":"hub","mmsc":"http://mms.starhubgee.com.sg:8002","mmsproxy":"10.12.1.80","mmsport":"80","type":["mms"]},
+ {"carrier":"Gee!","apn":"shwap","user":"star","password":"hub","proxy":"10.12.1.2","port":"80","type":["default","supl"]},
+ {"carrier":"StarHub E-mail","apn":"shwap","type":["default","supl"]}
+ ]
+},
+"530": {
+ "1": [
+ {"carrier":"VFNZ PXT","apn":"live.vodafone.com","mmsc":"http://pxt.vodafone.net.nz/pxtsend","mmsproxy":"172.30.38.3","mmsport":"8080","type":["mms"]},
+ {"carrier":"VFNZ Internet","apn":"www.vodafone.net.nz","type":["default","supl"]}
+ ],
+ "5": [
+ {"carrier":"TelecomDefault","apn":"wap.telecom.co.nz","type":["default","supl"]},
+ {"carrier":"Telecom MMS","apn":"wap.telecom.co.nz","mmsc":"http://lsmmsc.xtra.co.nz","mmsproxy":"210.55.11.73","mmsport":"8080","type":["mms"]}
+ ],
+ "24": [
+ {"carrier":"2degrees Internet","apn":"internet","type":["default","supl"]},
+ {"carrier":"2degrees MMS","apn":"mms","mmsc":"http://mms.2degreesmobile.net.nz:48090","mmsproxy":"118.148.1.118","mmsport":"8080","type":["mms"]}
+ ]
+},
+"602": {
+ "1": [
+ {"carrier":"Mobinil MMS","apn":"mobinilmms","mmsc":"http://10.7.13.24:8002","mmsproxy":"http://62.241.155.45","mmsport":"8205","type":["mms"]},
+ {"carrier":"Mobinil Internet","apn":"mobinilweb","type":["default"]},
+ {"carrier":"Mobinil Web","apn":"MobinilWeb","type":["default","supl"]},
+ {"carrier":"Mobinil MMS","apn":"mobinilmms","mmsc":"http://10.7.13.24:8002/","mmsproxy":"62.241.155.45","mmsport":"8080","type":["mms"]}
+ ],
+ "2": [
+ {"carrier":"VF internet","apn":"internet.vodafone.net","user":"internet","password":"internet","type":["default","supl"]},
+ {"carrier":"VF MMS","apn":"mms.vodafone.com.eg","user":"mms","password":"mms","mmsc":"http://mms.vodafone.com.eg/servlets/mms","mmsproxy":"163.121.178.2","mmsport":"8080","type":["mms"]}
+ ],
+ "3": [
+ {"carrier":"Etisalat internet","apn":"Etisalat","type":["default","supl"]},
+ {"carrier":"Etisalat MMS","apn":"Etisalat","mmsc":"http://10.71.131.7:38090","mmsproxy":"10.71.130.29","mmsport":"8080","type":["mms"]}
+ ]
+},
+"605": {
+ "1": [
+ {"carrier":"weborange","apn":"weborange","type":["default","supl"]},
+ {"carrier":"Orange MMS","apn":"mms.otun","mmsc":"http://mms.orange.tn","mmsproxy":"10.12.1.52","mmsport":"8080","type":["mms"]}
+ ],
+ "2": [
+ {"carrier":"mobinet","apn":"gprs.tn","user":"gprs","password":"gprs","type":["default","supl"]},
+ {"carrier":"MMS","apn":"mms.tn","user":"mms@tt1","password":"mms","mmsc":"http://","mmsproxy":"192.168.0.2","mmsport":"8080","type":["mms"]}
+ ],
+ "3": [
+ {"carrier":"internet Tunisiana","apn":"internet.tunisiana.com","user":"internet","password":"internet","proxy":"10.3.2.99","port":"80","type":["default","supl"]},
+ {"carrier":"MMS Tunisiana","apn":"mms.tunisiana.com","user":"mms","password":"mms","mmsc":"http://mmsc.tunisiana.com","mmsproxy":"10.3.2.100","mmsport":"80","type":["mms"]}
+ ]
+},
+"608": {
+ "1": [
+ {"carrier":"Orange MMS SN","apn":"mms","user":"mms","password":"mms","mmsc":"http://mmsalize/servlets/mms","mmsproxy":"172.16.30.9","mmsport":"8080","type":["mms"]},
+ {"carrier":"Orange Wap SN","apn":"wap","user":"wap","password":"wap","proxy":"172.16.30.9","port":"8080","type":["default"]},
+ {"carrier":"Orange Web SN","apn":"internet","user":"internet","password":"internet","type":["default"]}
+ ]
+},
+"610": {
+ "2": [
+ {"carrier":"Orange ML MMS","apn":"mms","user":"mms","password":"mms","mmsc":"http://10.109.6.2/servlets/mms","mmsproxy":"10.109.4.35","mmsport":"8080","type":["mms"]},
+ {"carrier":"Orange Wap ML","apn":"wap","user":"wap","password":"wap","proxy":"10.109.4.35","port":"8080","type":["default"]}
+ ]
+},
+"612": {
+ "3": [
+ {"carrier":"Omms CI","apn":"orangecimms","user":"mms","password":"mms","mmsc":"http://172.20.6.1/servlets/mms","mmsproxy":"172.20.4.33","mmsport":"8080","type":["mms"]},
+ {"carrier":"OWORLD CI","apn":"orangeciwap","user":"wap","password":"wap","proxy":"172.20.4.33","port":"8080","type":["default"]}
+ ]
+},
+"614": {
+ "4": [
+ {"carrier":"Orange MMS","apn":"orange.mms","user":"orange","password":"orange","mmsc":"http://10.10.10.35:38090/was","mmsproxy":"10.10.10.36","mmsport":"8080","type":["mms"]},
+ {"carrier":"Orange Internet","apn":"orange.ne","type":["default"]}
+ ]
+},
+"617": {
+ "1": [
+ {"carrier":"Orange MMS","apn":"orangemms","user":"mmsc","password":"mmsc","mmsc":"http://10.2.1.20:8514","mmsproxy":"10.2.1.20","mmsport":"8080","type":["mms"]},
+ {"carrier":"Orange Internet","apn":"orange","type":["default"]}
+ ]
+},
+"621": {
+ "20": [
+ {"carrier":"Zain Mobile Internet","apn":"internet.ng.Zain.com","type":["default","supl"]},
+ {"carrier":"Zain MMS","apn":"mms.ng.zain.com","user":"mms","password":"mms","mmsc":"http://172.23.1.12/mms/wapenc","mmsproxy":"172.18.254.5","mmsport":"8080","type":["mms"]}
+ ],
+ "30": [
+ {"carrier":"MTN GPRS","apn":"web.gprs.mtnnigeria.net","user":"web","password":"web","proxy":"10.199.212.2","port":"8080","type":["default","supl"]},
+ {"carrier":"MTN MMS","apn":"web.gprs.mtnnigeria.net","user":"web","password":"web","mmsc":"http://10.199.212.8/servlets/mms","mmsproxy":"10.199.212.2","mmsport":"8080","type":["mms"]}
+ ],
+ "50": [
+ {"carrier":"glo direct","apn":"glosecure","user":"gprs","password":"gprs","type":["default","supl"]},
+ {"carrier":"glo mms","apn":"glomms","user":"mms","password":"mms","mmsc":"http://mms.gloworld.com/mmsc","mmsproxy":"10.100.82.4","mmsport":"9201","type":["mms"]}
+ ],
+ "60": [
+ {"carrier":"Etisalat Internet","apn":"etisalat","type":["default","supl"]},
+ {"carrier":"Etisalat MMS","apn":"etisalat","mmsc":"http://10.71.170.30:38090/was","mmsproxy":"10.71.170.5","mmsport":"8080","type":["mms"]}
+ ]
+},
+"624": {
+ "2": [
+ {"carrier":"Orange CM","apn":"orangecmgprs","user":"orange","password":"orange","proxy":"192.168.122.101","port":"8080","mmsc":"http://mms.orange.cm","mmsproxy":"192.168.122.101","mmsport":"8080","type":["default","mms"]}
+ ]
+},
+"639": {
+ "2": [
+ {"carrier":"SafaricomBrowse","apn":"Safaricom","user":"saf","password":"data","proxy":"172.22.2.38","port":"8080","type":["default","supl"]},
+ {"carrier":"SafaricomMMS","apn":"Safaricom","user":"saf","password":"data","mmsc":"http://mms.gprs.safaricom.com","type":["mms"]}
+ ],
+ "3": [
+ {"carrier":"Zain WAP","apn":"wap.ke.celtel.com","user":"wap","password":"wap","type":["default","supl"]},
+ {"carrier":"Zain MMS","apn":"mms.ke.celtel.com","user":"wap","password":"wap","mmsc":"http://mms.ke.celtel.com/servlets/mms","type":["mms"]}
+ ],
+ "7": [
+ {"carrier":"Orange Internet","apn":"wap.orange.co.ke","type":["default","supl"]},
+ {"carrier":"Orange MMS","apn":"mms.orange.co.ke","mmsproxy":"10.36.16.5","mmsport":"8080","mmsc":"http://10.36.16.5/servlets/mms","type":["mms"]},
+ {"carrier":"Orange net KE","apn":"bew.orange.co.ke","type":["default"]}
+ ]
+},
+"640": {
+ "4": [
+ {"carrier":"Vodacom WAP","apn":"Wap","proxy":"10.154.0.8","port":"9401","type":["default","supl"]},
+ {"carrier":"Vodacom MMS","apn":"mms","mmsc":"http://10.154.0.12/mms/","type":["mms"]}
+ ]
+},
+"641": {
+ "14": [
+ {"carrier":"Orange Internet UG","apn":"orange.ug","type":["default"]},
+ {"carrier":"Orange MMS","apn":"orangemms","mmsc":"http://mms/","type":["mms"]}
+ ]
+},
+"646": {
+ "2": [
+ {"carrier":"Orange MG MMS","apn":"orangemms","user":"mms","password":"orange","mmsc":"http://10.152.10.70.38090","mmsproxy":"10.150.0.115","mmsport":"8080","type":["mms"]},
+ {"carrier":"Orange World MG","apn":"orangeworld","user":"world","password":"orange","proxy":"10.150.0.115","port":"8080","type":["default"]}
+ ]
+},
+"647": {
+ "0": [
+ {"carrier":"Orange World re","apn":"orangerun","user":"orange","password":"orange","type":["default","supl"]},
+ {"carrier":"Orange MMS Réunion","apn":"orangerun.acte","user":"orange","password":"orange","mmsc":"http://mms.orange.re","mmsproxy":"192.168.10.200","mmsport":"8080","type":["mms"]},
+ {"carrier":"Orange RE","apn":"orangerun","user":"orange","password":"orange","type":["default"]}
+ ],
+ "10": [
+ {"carrier":"MMS","apn":"mmssfr","user":"mms","password":"mms","mmsc":"http://mms","mmsproxy":"10.0.224.145","mmsport":"8080","type":["mms"]},
+ {"carrier":"GPRS SRR","apn":"wapsfr","user":"wap","password":"wap","proxy":"10.0.224.161","port":"8080","type":["default","supl"]}
+ ]
+},
+"651": {
+ "1": [
+ {"carrier":"VCL Internet GPRS","apn":"internet","type":["default","supl"]},
+ {"carrier":"VCL MMS GPRS","apn":"mms","proxy":"10.113.63.11","port":"8080","mmsc":"http://mmsc.vodacom4me.co.ls","mmsproxy":"10.113.63.11","mmsport":"8080","type":["mms"]}
+ ]
+},
+"652": {
+ "2": [
+ {"carrier":"Orange BW MMS","apn":"mms.orange.co.bw","mmsc":"http://10.0.0.242/servlets/mms","mmsproxy":"10.0.0.226","mmsport":"8080","type":["mms"]},
+ {"carrier":"Orange WAP BW","apn":"internet.orange.co.bw","proxy":"10.0.0.226","port":"8080","type":["default"]}
+ ]
+},
+"655": {
+ "1": [
+ {"carrier":"Vodacom","apn":"internet","proxy":"196.6.128.12","port":"8080","type":["default","supl"]},
+ {"carrier":"MMS.Vodacom","apn":"mms.vodacom.net","proxy":"196.6.128.13","port":"8080","mmsc":"http://mmsc.vodacom4me.co.za/","mmsproxy":"196.6.128.13","mmsport":"8080","type":["mms"]}
+ ],
+ "2": [
+ {"carrier":"8.ta internet","apn":"internet","type":["default","supl"]},
+ {"carrier":"8.ta mms","apn":"mms","proxy":"41.151.254.162","port":"8080","mmsc":"http://mms.8ta.com:38090/was","mmsproxy":"41.151.254.162","mmsport":"8080","type":["mms"]}
+ ],
+ "7": [
+ {"carrier":"Smartdata","apn":"internet","proxy":"196.31.116.250","port":"8080","type":["default","supl"]},
+ {"carrier":"Cell C MMS","apn":"mms","proxy":"196.31.116.250","port":"8080","mmsc":"http://mms.cmobile.co.za","mmsproxy":"196.31.116.250","mmsport":"8080","type":["mms"]},
+ {"carrier":"virgin_internet","apn":"vdata","proxy":"196.31.116.241","port":"8080","type":["default","supl"]},
+ {"carrier":"virgin_mms","apn":"vmms","proxy":"196.31.116.242","port":"8080","mmsc":"http://mms.virginmobile.co.za","mmsproxy":"196.31.116.242","mmsport":"8080","type":["mms"]}
+ ],
+ "10": [
+ {"carrier":"MTN Data","apn":"myMTN","user":"mtn","password":"mtn","mmsc":"http://mms.mtn.co.za/mms/wapenc","mmsproxy":"196.11.240.241","mmsport":"8080","type":["default","supl","mms"]}
+ ]
+},
+"704": {
+ "1": [
+ {"carrier":"INTERNET CLARO","apn":"internet.ideasclaro","user":"","password":"","authtype":"1","type":["default","supl","dun"]},
+ {"enableStrict7BitEncodingForSms":true,"type":["operatorvariant"]},
+ {"carrier":"MMS CLARO","apn":"mms.ideasclaro","user":"","password":"","mmsproxy":"216.230.133.66","mmsport":"8080","mmsc":"http://mms.ideasclaro.com:8002","authtype":"1","type":["mms"]}
+ ],
+ "2": [
+ {"carrier":"BROADBAND TIGO","apn":"broadband.tigo.gt","user":"","password":"","authtype":"1","type":["default","supl","dun"]},
+ {"enableStrict7BitEncodingForSms":true,"type":["operatorvariant"]},
+ {"carrier":"MMS TIGO","apn":"mms.tigo.gt","user":"","password":"","mmsproxy":"10.16.17.12","mmsport":"8888","mmsc":"http://mms","authtype":"1","type":["mms"]}
+ ],
+ "3": [
+ {"carrier":"Internet GT","apn":"internet.movistar.gt","user":"movistargt","password":"movistargt","authtype":"1","type":["default","supl","dun"]},
+ {"enableStrict7BitEncodingForSms":true,"type":["operatorvariant"]},
+ {"carrier":"MMS2 GPRS","apn":"mms.movistar.gt","user":"movistargt","password":"movistargt","mmsproxy":"10.12.22.1","mmsport":"80","mmsc":"http://mms.movistar.gt","authtype":"1","type":["mms"]}
+ ],
+ "30": [
+ {"carrier":"Internet GT","apn":"internet.movistar.gt","user":"movistargt","password":"movistargt","authtype":"1","type":["default","supl","dun"]},
+ {"enableStrict7BitEncodingForSms":true,"type":["operatorvariant"]},
+ {"carrier":"MMS2 GPRS","apn":"mms.movistar.gt","user":"movistargt","password":"movistargt","mmsproxy":"10.12.22.1","mmsport":"80","mmsc":"http://mms.movistar.gt","authtype":"1","type":["mms"]}
+ ]
+},
+"706": {
+ "1": [
+ {"carrier":"INTERNET CLARO","apn":"internet.ideasclaro","user":"","password":"","authtype":"1","type":["default","supl","dun"]},
+ {"enableStrict7BitEncodingForSms":true,"type":["operatorvariant"]},
+ {"carrier":"MMS CLARO","apn":"mms.ideasclaro","user":"","password":"","mmsproxy":"216.230.133.66","mmsport":"8080","mmsc":"http://mms.ideasclaro.com:8002","authtype":"1","type":["mms"]}
+ ],
+ "2": [
+ {"carrier":"El Salvaldor Digicel","apn":"web.digicelsv.com","user":"","password":"","authtype":"1","type":["default","supl","dun"]},
+ {"enableStrict7BitEncodingForSms":true,"type":["operatorvariant"]},
+ {"carrier":"MMS El Salvador","apn":"wap.digicelsv.com","user":"","password":"","mmsproxy":"172.26.5.12","mmsport":"8080","mmsc":"http://172.26.5.132/servlets/mms","authtype":"1","type":["mms"]}
+ ],
+ "3": [
+ {"carrier":"BROADBAND TIGO","apn":"broadband.tigo.sv","user":"","password":"","authtype":"1","type":["default","supl","dun"]},
+ {"enableStrict7BitEncodingForSms":true,"type":["operatorvariant"]},
+ {"carrier":"MMS TIGO","apn":"mms.tigo.sv","user":"","password":"","mmsproxy":"10.16.17.12","mmsport":"8888","mmsc":"http://mms","authtype":"1","type":["mms"]}
+ ],
+ "4": [
+ {"carrier":"Internet SV","apn":"internet.movistar.sv","user":"movistarsv","password":"movistarsv","authtype":"1","type":["default","supl","dun"]},
+ {"enableStrict7BitEncodingForSms":true,"type":["operatorvariant"]},
+ {"carrier":"MMS2 GPRS","apn":"mms.movistar.sv","user":"movistarsv","password":"movistarsv","mmsproxy":"10.12.20.1","mmsport":"80","mmsc":"http://mms.movistar.sv","authtype":"1","type":["mms"]}
+ ],
+ "40": [
+ {"carrier":"Internet SV","apn":"internet.movistar.sv","user":"movistarsv","password":"movistarsv","authtype":"1","type":["default","supl","dun"]},
+ {"enableStrict7BitEncodingForSms":true,"type":["operatorvariant"]},
+ {"carrier":"MMS2 GPRS","apn":"mms.movistar.sv","user":"movistarsv","password":"movistarsv","mmsproxy":"10.12.20.1","mmsport":"80","mmsc":"http://mms.movistar.sv","authtype":"1","type":["mms"]}
+ ]
+},
+"708": {
+ "1": [
+ {"carrier":"Internet Claro","apn":"web.megatel.hn","user":"webmegatel","password":"webmegatel","authtype":"1","type":["default","supl","dun"]},
+ {"carrier":"MMS Claro","apn":"mms.megatel.hn","user":"mmsmegatel","password":"mmsmegatel","mmsproxy":"10.6.32.2","mmsport":"8080","mmsc":"http://10.6.32.27/servlets/mms","authtype":"1","type":["mms"]}
+ ],
+ "2": [
+ {"carrier":"INTERNET TIGO","apn":"internet.tigo.hn","user":"","password":"","authtype":"1","type":["default","supl","dun"]},
+ {"carrier":"MMS TIGO","apn":"mms.tigo.hn","user":"","password":"","mmsproxy":"10.16.17.12","mmsport":"8888","mmsc":"http://mms","authtype":"1","type":["mms"]}
+ ],
+ "20": [
+ {"carrier":"INTERNET TIGO","apn":"internet.tigo.hn","user":"","password":"","authtype":"1","type":["default","supl","dun"]},
+ {"carrier":"MMS TIGO","apn":"mms.tigo.hn","user":"","password":"","mmsproxy":"10.16.17.12","mmsport":"8888","mmsc":"http://mms","authtype":"1","type":["mms"]}
+ ]
+},
+"710": {
+ "21": [
+ {"carrier":"INTERNET","apn":"web.emovil","user":"webemovil","password":"webemovil","authtype":"1","type":["default","supl","dun"]},
+ {"enableStrict7BitEncodingForSms":true,"type":["operatorvariant"]},
+ {"carrier":"MMS","apn":"mms.emovil","user":"mmsemovil","password":"mmsemovil","mmsproxy":"10.6.32.2","mmsport":"8080","mmsc":"http://10.6.32.27/servlets/mms","authtype":"1","type":["mms"]}
+ ],
+ "300": [
+ {"carrier":"Internet GPRS","apn":"internet.movistar.ni","user":"movistarni","password":"movistarni","authtype":"1","type":["default","supl","dun"]},
+ {"enableStrict7BitEncodingForSms":true,"type":["operatorvariant"]},
+ {"carrier":"movistar MMS","apn":"mms.movistar.ni","user":"movistarni","password":"movistarni","mmsproxy":"10.12.23.1","mmsport":"80","mmsc":"http://mms.movistar.ni","authtype":"1","type":["mms"]}
+ ],
+ "730": [
+ {"carrier":"INTERNET","apn":"web.emovil","user":"webemovil","password":"webemovil","authtype":"1","type":["default","supl","dun"]},
+ {"enableStrict7BitEncodingForSms":true,"type":["operatorvariant"]},
+ {"carrier":"MMS","apn":"mms.emovil","user":"mmsemovil","password":"mmsemovil","mmsproxy":"10.6.32.2","mmsport":"8080","mmsc":"http://10.6.32.27/servlets/mms","authtype":"1","type":["mms"]}
+ ]
+},
+"712": {
+ "1": [
+ {"carrier":"Kolbi","apn":"kolbi3g","user":"","password":"","authtype":"1","type":["default","supl","dun"]},
+ {"carrier":"Multimedia","apn":"kolbi3g","user":"","password":"","mmsproxy":"10.184.202.24","mmsport":"8080","mmsc":"http://mmsice","authtype":"1","type":["mms"]}
+ ],
+ "2": [
+ {"carrier":"Kolbi","apn":"kolbi3g","user":"","password":"","authtype":"1","type":["default","supl","dun"]},
+ {"carrier":"Multimedia","apn":"kolbi3g","user":"","password":"","mmsproxy":"10.184.202.24","mmsport":"8080","mmsc":"http://mmsice","authtype":"1","type":["mms"]}
+ ],
+ "3": [
+ {"carrier":"INTERNET CLARO","apn":"internet.ideasclaro","user":"","password":"","authtype":"1","type":["default","supl","dun"]},
+ {"voicemail":"190","type":["operatorvariant"]},
+ {"carrier":"MMS CLARO","apn":"mms.ideasclaro","user":"","password":"","mmsproxy":"216.230.133.66","mmsport":"8080","mmsc":"http://mms.ideasclaro.com:8002","authtype":"1","type":["mms"]}
+ ],
+ "4": [
+ {"carrier":"movistar INTERNET","apn":"internet.movistar.cr","user":"movistarcr","password":"movistarcr","authtype":"1","type":["default","supl","dun"]},
+ {"carrier":"movistar MMS","apn":"mms.movistar.cr","user":"movistarcr","password":"movistarcr","mmsproxy":"10.221.79.83","mmsport":"80","mmsc":"http://mms.movistar.cr","authtype":"1","type":["mms"]}
+ ]
+},
+"714": {
+ "1": [
+ {"carrier":"Wap","apn":"apn01.cwpanama.com.pa","proxy":"172.25.3.5","port":"8080","user":"","password":"","authtype":"1","type":["default","supl","dun"]},
+ {"enableStrict7BitEncodingForSms":true,"type":["operatorvariant"]},
+ {"carrier":"Mms","apn":"apn02.cwpanama.com.pa","user":"","password":"","mmsproxy":"172.25.3.5","mmsport":"8080","mmsc":"http://mms.zonamovil.com.pa","authtype":"1","type":["mms"]}
+ ],
+ "20": [
+ {"carrier":"movistar INTERNET","apn":"internet.movistar.pa","user":"movistarpa","password":"movistarpa","authtype":"1","type":["default","supl","dun"]},
+ {"enableStrict7BitEncodingForSms":true,"type":["operatorvariant"]},
+ {"carrier":"movistar MMS","apn":"mms.movistar.pa","user":"movistarpamms","password":"movistarpa","mmsproxy":"10.12.21.1","mmsport":"80","mmsc":"http://mms.movistar.pa","authtype":"1","type":["mms"]}
+ ],
+ "3": [
+ {"carrier":"WEB Claro","apn":"web.claro.com.pa","user":"CLAROWEB","password":"CLAROWEB","authtype":"1","type":["default","supl","dun"]},
+ {"enableStrict7BitEncodingForSms":true,"type":["operatorvariant"]},
+ {"carrier":"MMS Claro","apn":"mms.claro.com.pa","user":"CLAROMMS","password":"CLAROMMS","mmsproxy":"10.240.3.129","mmsport":"8799","mmsc":"http://www.claro.com.pa/mms/","authtype":"1","type":["mms"]}
+ ],
+ "4": [
+ {"carrier":"INTERNET Panama","apn":"web.digicelpanama.com","user":"","password":"","authtype":"1","type":["default","supl","dun"]},
+ {"enableStrict7BitEncodingForSms":true,"type":["operatorvariant"]},
+ {"carrier":"MMS Panama","apn":"wap.digicelpanama.com","user":"","password":"","mmsproxy":"172.27.99.99","mmsport":"8080","mmsc":"http://mmc.digicelpanama.com/servlets/mms","authtype":"1","type":["mms"]}
+ ]
+},
+"716": {
+ "6": [
+ {"carrier":"movistar Internet","apn":"movistar.pe","user":"movistar@datos","password":"movistar","authtype":"1","type":["default","supl","dun"]},
+ {"enableStrict7BitEncodingForSms":true,"type":["operatorvariant"]},
+ {"carrier":"movistar MMS","apn":"mms.movistar.pe","user":"movistar@mms","password":"movistar","mmsproxy":"200.4.196.118","mmsport":"8080","mmsc":"http://mmsc.telefonicamovistar.com.pe:8088/mms/","authtype":"1","type":["mms"]}
+ ],
+ "10": [
+ {"carrier":"CLARO DATOS","apn":"claro.pe","user":"claro","password":"claro","authtype":"1","type":["default","supl","dun"]},
+ {"enableStrict7BitEncodingForSms":true,"type":["operatorvariant"]},
+ {"carrier":"CLARO MMS","apn":"mms.claro.pe","user":"claro","password":"claro","mmsproxy":"192.168.231.30","mmsport":"80","mmsc":"http://claro/servlets/mms","authtype":"1","type":["mms"]}
+ ]
+},
+"722": {
+ "7": [
+ {"carrier":"Movistar Emoción","apn":"wap.gprs.unifon.com.ar","user":"wap","password":"wap","proxy":"200.5.68.10","port":"8080","authtype":"1","type":["default","supl","dun"]},
+ {"enableStrict7BitEncodingForSms":true,"type":["operatorvariant"]},
+ {"carrier":"Movistar MMS","apn":"mms.gprs.unifon.com.ar","user":"mms","password":"mms","mmsproxy":"200.68.32.239","mmsport":"8080","mmsc":"http://mms.movistar.com.ar","authtype":"1","type":["mms"]}
+ ],
+ "31": [
+ {"carrier":"Claro AR","apn":"igprs.claro.com.ar","user":"ctigprs","password":"ctigprs999","authtype":"1","type":["default","supl","dun"]},
+ {"enableStrict7BitEncodingForSms":true,"type":["operatorvariant"]},
+ {"carrier":"MMS GPRS AR","apn":"mms.ctimovil.com.ar","user":"ctimms","password":"ctimms999","mmsproxy":"170.51.255.240","mmsport":"8080","mmsc":"http://mms.ctimovil.com.ar","authtype":"1","type":["mms"]}
+ ],
+ "310": [
+ {"carrier":"Claro AR","apn":"igprs.claro.com.ar","user":"ctigprs","password":"ctigprs999","authtype":"1","type":["default","supl","dun"]},
+ {"enableStrict7BitEncodingForSms":true,"type":["operatorvariant"]},
+ {"carrier":"MMS GPRS AR","apn":"mms.ctimovil.com.ar","user":"ctimms","password":"ctimms999","mmsproxy":"170.51.255.240","mmsport":"8080","mmsc":"http://mms.ctimovil.com.ar","authtype":"1","type":["mms"]}
+ ],
+ "34": [
+ {"carrier":"Personal Datos","apn":"datos.personal.com","user":"gprs","password":"adgj","authtype":"1","type":["default","supl","dun"]},
+ {"enableStrict7BitEncodingForSms":true,"type":["operatorvariant"]},
+ {"carrier":"Personal MMS","apn":"mms","user":"mms","password":"mms","mmsproxy":"172.25.7.31","mmsport":"8080","mmsc":"http://mms.personal.com","authtype":"1","type":["mms"]}
+ ],
+ "341": [
+ {"carrier":"Personal Datos","apn":"datos.personal.com","user":"gprs","password":"adgj","authtype":"1","type":["default","supl","dun"]},
+ {"enableStrict7BitEncodingForSms":true,"type":["operatorvariant"]},
+ {"carrier":"Personal MMS","apn":"mms","user":"mms","password":"mms","mmsproxy":"172.25.7.31","mmsport":"8080","mmsc":"http://mms.personal.com","authtype":"1","type":["mms"]}
+ ]
+},
+"724": {
+ "2": [
+ {"carrier":"TIM Connect","apn":"timbrasil.br","user":"tim","password":"tim","mmsc":"http://mms.tim.br","mmsproxy":"200.179.66.242","mmsport":"8080","authtype":"1","type":["default","supl","mms"]},
+ {"voicemail":"*100","enableStrict7BitEncodingForSms":true,"type":["operatorvariant"]}
+ ],
+ "3": [
+ {"carrier":"TIM Connect","apn":"timbrasil.br","user":"tim","password":"tim","mmsc":"http://mms.tim.br","mmsproxy":"200.179.66.242","mmsport":"8080","authtype":"1","type":["default","supl","mms"]},
+ {"voicemail":"*100","enableStrict7BitEncodingForSms":true,"type":["operatorvariant"]}
+ ],
+ "4": [
+ {"carrier":"TIM Connect","apn":"timbrasil.br","user":"tim","password":"tim","mmsc":"http://mms.tim.br","mmsproxy":"200.179.66.242","mmsport":"8080","authtype":"1","type":["default","supl","mms"]},
+ {"voicemail":"*100","enableStrict7BitEncodingForSms":true,"type":["operatorvariant"]}
+ ],
+ "5": [
+ {"carrier":"Java Session","apn":"java.claro.com.br","user":"claro","password":"claro","authtype":"1","type":["default","supl"]},
+ {"voicemail":"*100","enableStrict7BitEncodingForSms":true,"type":["operatorvariant"]},
+ {"carrier":"Claro Foto","apn":"mms.claro.com.br","user":"claro","password":"claro","mmsc":"http://mms.claro.com.br","mmsproxy":"200.169.126.10","mmsport":"8799","authtype":"1","type":["mms"]}
+ ],
+ "6": [
+ {"carrier":"Vivo MMS","apn":"mms.vivo.com.br","user":"vivo","password":"vivo","mmsc":"http://termnat.vivomms.com.br:8088/mms","mmsproxy":"200.142.130.104","mmsport":"80","authtype":"1","type":["mms"]},
+ {"voicemail":"*555","enableStrict7BitEncodingForSms":true,"cellBroadcastSearchList":"50","type":["operatorvariant"]},
+ {"carrier":"Vivo Internet","apn":"zap.vivo.com.br","user":"vivo","password":"vivo","authtype":"1","type":["default","supl"]}
+ ],
+ "7": [
+ {"carrier":"SCTL MMS","apn":"mms.sercomtel.com.br","user":"sercomtel","password":"sercomtel","mmsc":"http://mms.claro.com.br","mmsproxy":"200.169.126.10","mmsport":"8799","type":["mms"]},
+ {"enableStrict7BitEncodingForSms":true,"type":["operatorvariant"]},
+ {"carrier":"SCTL GPRS","apn":"sercomtel.com.br","user":"sercomtel","password":"sercomtel","type":["default","supl"]}
+ ],
+ "10": [
+ {"carrier":"Vivo Internet","apn":"zap.vivo.com.br","user":"vivo","password":"vivo","type":["default","supl"]},
+ {"voicemail":"*555","enableStrict7BitEncodingForSms":true,"cellBroadcastSearchList":"50","type":["operatorvariant"]},
+ {"carrier":"Vivo MMS","apn":"mms.vivo.com.br","user":"vivo","password":"vivo","mmsc":"http://termnat.vivomms.com.br:8088/mms","mmsproxy":"200.142.130.104","mmsport":"80","authtype":"1","type":["mms"]}
+ ],
+ "11": [
+ {"carrier":"Vivo MMS","apn":"mms.vivo.com.br","user":"vivo","password":"vivo","mmsc":"http://termnat.vivomms.com.br:8088/mms","mmsproxy":"200.142.130.104","mmsport":"80","authtype":"1","type":["mms"]},
+ {"voicemail":"*555","enableStrict7BitEncodingForSms":true,"cellBroadcastSearchList":"50","type":["operatorvariant"]},
+ {"carrier":"Vivo Internet","apn":"zap.vivo.com.br","user":"vivo","password":"vivo","authtype":"1","type":["default","supl"]}
+ ],
+ "16": [
+ {"carrier":"BrT Modem","apn":"brt.br","user":"brt","password":"brt","authtype":"1","type":["default","supl"]},
+ {"voicemail":"*100","enableStrict7BitEncodingForSms":true,"type":["operatorvariant"]},
+ {"carrier":"BrT MMS","apn":"mms.brt.br","user":"brt","password":"brt","mmsc":"http://mms.brasiltelecom.com.br/","mmsproxy":"200.96.8.29","mmsport":"8080","authtype":"1","type":["mms"]}
+ ],
+ "19": [
+ {"carrier":"TelemigC GPRS","apn":"gprs.telemigcelular.com.br","user":"celular","password":"celular","type":["default","supl"]},
+ {"enableStrict7BitEncodingForSms":true,"type":["operatorvariant"]},
+ {"carrier":"MMS Telemig","apn":"mmsgprs.telemigcelular.com.br","user":"celular","password":"celular","mmsc":"http://mms.telemigcelular.com.br","mmsproxy":"200.192.230.142","mmsport":"8080","type":["mms"]}
+ ],
+ "23": [
+ {"carrier":"Vivo Internet","apn":"zap.vivo.com.br","user":"vivo","password":"vivo","authtype":"1","type":["default","supl"]},
+ {"voicemail":"*555","enableStrict7BitEncodingForSms":true,"cellBroadcastSearchList":"50","type":["operatorvariant"]},
+ {"carrier":"Vivo MMS","apn":"mms.vivo.com.br","user":"vivo","password":"vivo","mmsc":"http://termnat.vivomms.com.br:8088/mms","mmsproxy":"200.142.130.104","mmsport":"80","authtype":"1","type":["mms"]}
+ ],
+ "24": [
+ {"carrier":"AmazôniaC GPRS","apn":"gprs.amazoniacelular.com.br","user":"celular","password":"celular","authtype":"1","type":["default","supl","dun"]},
+ {"voicemail":"*100","enableStrict7BitEncodingForSms":true,"type":["operatorvariant"]},
+ {"carrier":"AmazôniaC MMS","apn":"mmsgprs.amazoniacelular.com.br","user":"celular","password":"celular","mmsc":"http://mms.amazoniacelular.com.br","mmsproxy":"200.192.230.142","mmsport":"8080","authtype":"1","type":["mms"]}
+ ],
+ "31": [
+ {"carrier":"OI GPRS","apn":"gprs.oi.com.br","authtype":"1","type":["default","supl"]},
+ {"voicemail":"*100","enableStrict7BitEncodingForSms":true,"type":["operatorvariant"]},
+ {"carrier":"OI MMS","apn":"mmsgprs.oi.com.br","user":"oimms","password":"oimms","mmsc":"http://200.222.42.204:8002","mmsproxy":"192.168.10.50","mmsport":"3128","authtype":"1","type":["mms"]}
+ ]
+},
+"730": {
+ "1": [
+ {"carrier":"Internet Movil","apn":"bam.entelpcs.cl","user":"entelpcs","password":"entelpcs","authtype":"1","type":["default","supl","dun"]},
+ {"enableStrict7BitEncodingForSms":true,"type":["operatorvariant"]},
+ {"carrier":"MMS Entel","apn":"mms.entelpcs.cl","user":"entelpcs","password":"entelpcs","mmsproxy":"10.99.0.10","mmsport":"8080","mmsc":"http://mmsc.entelpcs.cl","authtype":"1","type":["mms"]}
+ ],
+ "10": [
+ {"carrier":"Internet Movil","apn":"bam.entelpcs.cl","user":"entelpcs","password":"entelpcs","authtype":"1","type":["default","supl","dun"]},
+ {"enableStrict7BitEncodingForSms":true,"type":["operatorvariant"]},
+ {"carrier":"MMS Entel","apn":"mms.entelpcs.cl","user":"entelpcs","password":"entelpcs","mmsproxy":"10.99.0.10","mmsport":"8080","mmsc":"http://mmsc.entelpcs.cl","authtype":"1","type":["mms"]},
+ {"carrier":"Internet Movil","apn":"bam.entelpcs.cl","user":"entelpcs","password":"entelpcs","authtype":"1","type":["default","supl","dun"]},
+ {"carrier":"MMS Entel PCS","apn":"mms.entelpcs.cl","user":"entelpcs","password":"entelpcs","mmsc":"http://mmsc.entelpcs.cl","mmsproxy":"10.99.0.10","mmsport":"8080","authtype":"1","type":["mms"]}
+ ],
+ "2": [
+ {"carrier":"NEM","apn":"wap.tmovil.cl","proxy":"172.17.8.11","port":"8080","user":"wap","password":"wap","authtype":"1","type":["default","supl","dun"]},
+ {"enableStrict7BitEncodingForSms":true,"type":["operatorvariant"]},
+ {"carrier":"MMS","apn":"mms.tmovil.cl","user":"mms","password":"mms","mmsproxy":"172.17.8.10","mmsport":"8080","mmsc":"http://mms.movistar.cl","authtype":"1","type":["mms"]}
+ ],
+ "3": [
+ {"carrier":"BAM Claro","apn":"bam.clarochile.cl","user":"clarochile","password":"clarochile","authtype":"1","type":["default","supl","dun"]},
+ {"enableStrict7BitEncodingForSms":true,"type":["operatorvariant"]},
+ {"carrier":"MMS Claro","apn":"mms.clarochile.cl","user":"clarochile","password":"clarochile","mmsproxy":"172.23.200.200","mmsport":"8080","mmsc":"http://mms.clarochile.cl","authtype":"1","type":["mms"]}
+ ],
+ "7": [
+ {"carrier":"web","apn":"web.gtdmovil.cl","user":"webgtd","password":"webgtd","authtype":"1","type":["default","supl","dun"]},
+ {"enableStrict7BitEncodingForSms":true,"type":["operatorvariant"]}
+ ],
+ "8": [
+ {"carrier":"Internet","apn":"movil.vtr.com","user":"vtrmovil","password":"vtrmovil","authtype":"2","type":["default","supl","dun"]},
+ {"enableStrict7BitEncodingForSms":true,"type":["operatorvariant"]},
+ {"carrier":"Wap","apn":"wap.vtr.com","proxy":"192.168.94.210","port":"9028","user":"","password":"","authtype":"0","type":["default","supl","dun"]},
+ {"carrier":"Bam","apn":"bam.vtr.com","user":"vtr","password":"vtr","authtype":"2","type":["default","supl","dun"]},
+ {"carrier":"MMS","apn":"mms.vtr.com","user":"mms","password":"","mmsc":"http://192.168.94.162:19090/was","mmsproxy":"192.168.94.210","mmsport":"9028","authtype":"0","type":["mms"]}
+ ]
+},
+"732": {
+ "101": [
+ {"carrier":"WEB Comcel 3GSM","apn":"internet.comcel.com.co","user":"COMCELWEB","password":"COMCELWEB","authtype":"1","type":["default","supl","dun"]},
+ {"enableStrict7BitEncodingForSms":true,"type":["operatorvariant"]},
+ {"carrier":"MMS Comcel 3GSM","apn":"mms.comcel.com.co","user":"COMCELMMS","password":"COMCELMMS","mmsproxy":"198.228.90.225","mmsport":"8799","mmsc":"http://www.comcel.com.co/mms/","authtype":"1","type":["mms"]}
+ ],
+ "103": [
+ {"carrier":"TIGO WEB","apn":"web.colombiamovil.com.co","user":"","password":"","authtype":"1","type":["default","supl","dun"]},
+ {"enableStrict7BitEncodingForSms":true,"type":["operatorvariant"]},
+ {"carrier":"TIGO Multimedia","apn":"mms.colombiamovil.com.co","user":"mms-cm1900","password":"mms-cm1900","mmsproxy":"190.102.206.48","mmsport":"8080","mmsc":"http://mms.ola.com.co","authtype":"1","type":["mms"]}
+ ],
+ "111": [