Merge mozilla-central to mozilla-inbound

This commit is contained in:
Carsten "Tomcat" Book 2016-01-12 11:56:10 +01:00
commit b0e2b7fa3c
219 changed files with 791 additions and 29062 deletions

View File

@ -201,6 +201,7 @@ toolkit/content/contentAreaUtils.js
toolkit/content/widgets/videocontrols.xml
toolkit/components/jsdownloads/src/DownloadIntegration.jsm
toolkit/components/search/nsSearchService.js
toolkit/components/telemetry/healthreport-prefs.js
toolkit/components/url-classifier/**
toolkit/components/urlformatter/nsURLFormatter.js
toolkit/identity/FirefoxAccounts.jsm

View File

@ -62,6 +62,11 @@ body.content-loaded > #installing {
animation-timing-function: linear;
}
#titlebar-buttonbox {
margin: 6px 7px;
-moz-appearance: -moz-window-button-box;
}
@keyframes throbber{
from {
transform: scale(0);

View File

@ -58,6 +58,7 @@
<h1 id="placeholder">wtf mac os!</h1>
#endif
#else
<div id="titlebar-buttonbox"></div>
<div id="installing">
<div class="throbber"></div>
<div class="message"></div>

View File

@ -0,0 +1,195 @@
<?xml version="1.0" ?><manifest>
<!--
Remotes
-->
<!--original fetch url was https://android.googlesource.com/-->
<remote fetch="https://git.mozilla.org/external/aosp" name="aosp"/>
<!--original fetch url was git://github.com/apitrace/-->
<remote fetch="https://git.mozilla.org/external/apitrace" name="apitrace"/>
<!--original fetch url was git://github.com/mozilla-b2g/-->
<remote fetch="https://git.mozilla.org/b2g" name="b2g"/>
<!--original fetch url was https://git.mozilla.org/b2g-->
<remote fetch="https://git.mozilla.org/b2g" name="b2gmozilla"/>
<!--original fetch url was git://codeaurora.org/-->
<remote fetch="https://git.mozilla.org/external/caf" name="caf"/>
<!--original fetch url was http://android.git.linaro.org/git-ro/-->
<remote fetch="https://git.mozilla.org/external/linaro" name="linaro"/>
<!--original fetch url was git://github.com/mozilla/-->
<remote fetch="https://git.mozilla.org/b2g" name="mozilla"/>
<!--original fetch url was https://git.mozilla.org/releases-->
<remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
<!--
B2G repositories for all targets
-->
<project name="gaia" path="gaia" remote="mozillaorg" revision="3c97d6a8ac5a69662e1e2c22a84ea59bf50c305e"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="4a962bdab532e18f53e9d2d114c349983262c6b7"/>
<project name="moztt" path="external/moztt" remote="b2g" revision="99c333dab00ed79baff9e1cf76b320aee8e1c123"/>
<project name="platform_hardware_libhardware_moz" path="hardware/libhardware_moz" remote="b2g" revision="fdf3a143dc777e5f9d33a88373af7ea161d3b440"/>
<!-- B2G specific things. -->
<project name="platform_build" path="build" remote="b2g" revision="be4b291a90b371b41b62ade68c31ad173bb87baa">
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
<project name="fake-qemu-kernel" path="prebuilts/qemu-kernel" remote="b2g" revision="939b377d55a2f081d94029a30a75d05e5a20daf3"/>
<project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
<project name="valgrind" path="external/valgrind" remote="b2g" revision="5f931350fbc87c3df9db8b0ceb37734b8b471593"/>
<project name="vex" path="external/VEX" remote="b2g" revision="48d8c7c950745f1b166b42125e6f0d3293d71636"/>
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="8b76e208f3ba77f26017122fe9661d2221028f7a"/>
<!-- Stock Android things -->
<project groups="pdk,linux" name="platform/prebuilts/clang/linux-x86/host/3.5" path="prebuilts/clang/linux-x86/host/3.5" revision="0f86914b89cf8a069533e66b218533a17bad6b43"/>
<project groups="pdk,linux,arm" name="platform/prebuilts/gcc/linux-x86/aarch64/aarch64-linux-android-4.8" path="prebuilts/gcc/linux-x86/aarch64/aarch64-linux-android-4.8" revision="6b1fb5b730b1299f99f9194c1fcf088579cc7977"/>
<project groups="pdk,linux,arm" name="platform/prebuilts/gcc/linux-x86/arm/arm-eabi-4.8" path="prebuilts/gcc/linux-x86/arm/arm-eabi-4.8" revision="755656d9a7c79c6463920ad13f95d71e45e21397"/>
<project groups="pdk,linux,arm" name="platform/prebuilts/gcc/linux-x86/arm/arm-linux-androideabi-4.8" path="prebuilts/gcc/linux-x86/arm/arm-linux-androideabi-4.8" revision="ad086223028d281b2ea95f0f42f23ff4435917dd"/>
<project groups="pdk,linux" name="platform/prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.11-4.6" path="prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.11-4.6" revision="94d88f335cdc90bf43471bacf243006e99cff908"/>
<project groups="pdk,linux" name="platform/prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.11-4.8" path="prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.11-4.8" revision="ac160a43fddd833d4a0bc430f44f8b1956bac1e9"/>
<project groups="pdk,linux,x86" name="platform/prebuilts/gcc/linux-x86/x86/x86_64-linux-android-4.8" path="prebuilts/gcc/linux-x86/x86/x86_64-linux-android-4.8" revision="966dbeff106bf36966d4c80a4f2c58a464ae314e"/>
<project groups="linux" name="platform/prebuilts/python/linux-x86/2.7.5" path="prebuilts/python/linux-x86/2.7.5" revision="accd992f11a8ec0a0ec52cdc891302dc54e2941e"/>
<project name="device/common" path="device/common" revision="cf1543ff569188c1df9f4e9c7fa53b75393c49e3"/>
<project name="device/sample" path="device/sample" revision="3bbddd699b3e093a664aae7d9a63032b798b561b"/>
<project name="platform/abi/cpp" path="abi/cpp" revision="870adeafa39f8cb4bec108e35638bb1c36fc648a"/>
<project name="platform/bionic" path="bionic" revision="060309e1369e7aa394d720fbc0703d5e20a60391"/>
<project name="platform/bootable/recovery" path="bootable/recovery" revision="ac9bafa97733238b0f8538d1f28e9a01d2931fa1"/>
<project name="platform/external/aac" path="external/aac" revision="925c65e2186ed3f33d26e85d1471a1b5f18b1bfa"/>
<project name="platform/external/bison" path="external/bison" revision="ba6887d58129c3cba6f181942943709f6250471f"/>
<project name="platform/external/bluetooth/bluedroid" path="external/bluetooth/bluedroid" revision="7456378c0ebfac007506decf0d187b747b70affd"/>
<project name="platform/external/bsdiff" path="external/bsdiff" revision="ef2f916f26550b6d2684c5f96ef404deb24060e8"/>
<project name="platform/external/bzip2" path="external/bzip2" revision="34b2cdaab634bcd1bd2f01130573a7833771df70"/>
<project name="platform/external/checkpolicy" path="external/checkpolicy" revision="cb2e70cd69a05cdfffa00825ed2f944789bbf95f"/>
<project name="platform/external/clang" path="external/clang" revision="eeb154f1b498998e77261475909c28aa6f61377e"/>
<project name="platform/external/compiler-rt" path="external/compiler-rt" revision="c87fcb2428a26ae69a2f7110c73ac08bafb652d7"/>
<project name="platform/external/dhcpcd" path="external/dhcpcd" revision="ffae58641836bb1c19b340b70ed4c625e0171a5d"/>
<project name="platform/external/dnsmasq" path="external/dnsmasq" revision="9d46bf92e0953655ca81c8b02ae5007dc67bd920"/>
<project name="platform/external/e2fsprogs" path="external/e2fsprogs" revision="c31df77fe0064ca3d16676be2cfa04362c14a01d"/>
<project name="platform/external/elfutils" path="external/elfutils" revision="a4e81ae7683f1b49f35f4cf3936ad9a9e4a54231"/>
<project name="platform/external/expat" path="external/expat" revision="f5fef24959803e65738cf96a91b4153cddcd3bf6"/>
<project name="platform/external/f2fs-tools" path="external/f2fs-tools" revision="fc24ac8347630a14d62ffafa93a27ec3b81cc44b"/>
<project name="platform/external/fdlibm" path="external/fdlibm" revision="0447a94e59aaad84ba9606bd7ef5bab525c356c3"/>
<project name="platform/external/flac" path="external/flac" revision="e15b8e8f7ee9af14f048c8bee8875583453a4259"/>
<project name="platform/external/freetype" path="external/freetype" revision="8276a1e4822384a763ee5a239352e815858802e9"/>
<project name="platform/external/gcc-demangle" path="external/gcc-demangle" revision="408f3ac7a9a3716c8198a04305d0132816ba02ad"/>
<project name="platform/external/genext2fs" path="external/genext2fs" revision="d1420472b31fab33fb23dd16197fdc6c1a056d4c"/>
<project name="platform/external/giflib" path="external/giflib" revision="c06fa3137c3c990f281400007f1426789e882a6f"/>
<project name="platform/external/gtest" path="external/gtest" revision="4b4c07cf16bc2a0e915be8acbd1947d4cca4dcab"/>
<project name="platform/external/harfbuzz_ng" path="external/harfbuzz_ng" revision="001e8c6dd3a1aa77d4449b66b5cd5e00a158481b"/>
<project name="platform/external/icu" path="external/icu" revision="3667975ba9c170a65c66666c019c8256ccc047bf"/>
<project name="platform/external/iproute2" path="external/iproute2" revision="2b9c711e59817efcf487b790cd95d441ce434b45"/>
<project name="platform/external/ipsec-tools" path="external/ipsec-tools" revision="8c1adaedd11540df5647f19c9ab5bc81fa614548"/>
<project name="platform/external/iptables" path="external/iptables" revision="38e301e24878e82cf6a4f0fbcd920cbe7270ff13"/>
<project name="platform/external/jack" path="external/jack" revision="72d913b877f04598204bfac6cce1ab96a193d058"/>
<project name="platform/external/jemalloc" path="external/jemalloc" revision="768ad4c9555dca6dafd147c7c00920eedddd3969"/>
<project name="platform/external/jhead" path="external/jhead" revision="e55f218242ffd89d0a76ba1dee11a2a439d233c5"/>
<project name="platform/external/jpeg" path="external/jpeg" revision="06ff2ef8790692b2a8d11eceb145d8723c77b622"/>
<project name="platform/external/jsmn" path="external/jsmn" revision="7af4b7e6369d6afc276da699bd41251d2398e350"/>
<project name="platform/external/jsoncpp" path="external/jsoncpp" revision="6e813c30f660b10f430e7f600799ae1379a287dc"/>
<project name="platform/external/junit" path="external/junit" revision="61fcf385c1b65ef439c5664adda77b9cfa0e8118"/>
<project name="platform/external/libcxxabi" path="external/libcxxabi" revision="96c4e7bce0f0e710cf5c0c6f2557f142a6ebed27"/>
<project name="platform/external/libcxx" path="external/libcxx" revision="3c2d8389b6c83fc5d88111aae64c527e903e73c7"/>
<project name="platform/external/libgsm" path="external/libgsm" revision="f107553887fbb8ed03b7bd8f6318d1bc4489fee4"/>
<project name="platform/external/liblzf" path="external/liblzf" revision="c57cd8d9318259f8f85fa5ceaa6327ec9c7b0296"/>
<project name="platform/external/libnfc-nxp" path="external/libnfc-nxp" revision="057646515e2d6dffec80bbbabddc706689fb12e3"/>
<project name="platform/external/libnl" path="external/libnl" revision="103740e4486e7aa783323f4b9b1b3f596c858b30"/>
<project name="platform/external/libogg" path="external/libogg" revision="921a239234786023cdade5d6b222a7c88068b61d"/>
<project name="platform/external/libopus" path="external/libopus" revision="2b6e63f8fe19a481e3577d6de764855198493ea4"/>
<project name="platform/external/libpcap" path="external/libpcap" revision="f391e622e30509cd1731152aab61511e82969236"/>
<project name="platform/external/libpng" path="external/libpng" revision="72d906de7515609342498f5e45c81282b1302448"/>
<project name="platform/external/libselinux" path="external/libselinux" revision="6472342ee6c171882891d2f644b88eef55c9ebc3"/>
<project name="platform/external/libsepol" path="external/libsepol" revision="9eb69f1e70aa80a1ccc00242c634fcf2a424b06a"/>
<project name="platform/external/libunwind" path="external/libunwind" revision="989888043cdaf966b8b86853910aa19165e5194e"/>
<project name="platform/external/libvpx" path="external/libvpx" revision="052edf77a2149d108bfa5e6ca88adf26c6950bd7"/>
<project name="platform/external/llvm" path="external/llvm" revision="00b16d2275a2305137a2afca1479cc4077aad2e5"/>
<project name="platform/external/mdnsresponder" path="external/mdnsresponder" revision="49f160bbaea5d855bddfa22a7d61a29fb6e736f9"/>
<project name="platform/external/mksh" path="external/mksh" revision="be73b929515f8e4a59e979a5372bc192e1324ece"/>
<project name="platform/external/netcat" path="external/netcat" revision="5bc44d564f09695b89e713afaeee09fc07ecd182"/>
<project name="platform/external/openssl" path="external/openssl" revision="d8a69d6b9f960c068ce5d375875e3c8609a1f405"/>
<project name="platform/external/pcre" path="external/pcre" revision="74befec51e20f2e31ae4a11f7e992dbeb6be8bd9"/>
<project name="platform/external/protobuf" path="external/protobuf" revision="8cd15d4980ba1b4dc2a05cc3e5bf48bfabaa14ea"/>
<project name="platform/external/safe-iop" path="external/safe-iop" revision="ba3ac916708940cc761318c57e8efe4cd73af400"/>
<project name="platform/external/scrypt" path="external/scrypt" revision="4059b5c88404bc81350bb37b23fca35ea3c97179"/>
<project name="platform/external/sfntly" path="external/sfntly" revision="9656ee1e46a5ad61c569c2e70e94187be52cebe6"/>
<project name="platform/external/skia" path="external/skia" revision="14cd31a0bafabdc222e9beaccfb6162a312d6e90"/>
<project name="platform/external/sonivox" path="external/sonivox" revision="dc512141dae4ef69e470a64d0cfbf412277aa0b3"/>
<project name="platform/external/speex" path="external/speex" revision="f26fe68d1400a503a71b55728e9ba43b5bf8f65f"/>
<project name="platform/external/sqlite" path="external/sqlite" revision="8bf2eb40ff5dd0a6724d493eb997d99a2e07e20f"/>
<project name="platform/external/stlport" path="external/stlport" revision="76e06e090244f1960bdd48f1109613ea4cf05884"/>
<project name="platform/external/strace" path="external/strace" revision="b2dc0fc24db579b0fedf26e89a1a466b2c9a32e2"/>
<project name="platform/external/svox" path="external/svox" revision="7c3164643a2115617581d34bb9f9612f0e0173bf"/>
<project name="platform/external/tagsoup" path="external/tagsoup" revision="7d489e3f8f2c5463d334de9cc74bdce271a1886d"/>
<project name="platform/external/tcpdump" path="external/tcpdump" revision="75b33a817a0c62278c6099c47985f8704e7d9232"/>
<project name="platform/external/tinyalsa" path="external/tinyalsa" revision="8fbb1a592beafe2de3ddbdcac78ffcd96df5e383"/>
<project name="platform/external/tinycompress" path="external/tinycompress" revision="82c0b2c653987ff4280be7b14d1f0c1d89eba4c9"/>
<project name="platform/external/tinyxml2" path="external/tinyxml2" revision="de52ec2164bb14fba7ae01ac8f0167212b8c741f"/>
<project name="platform/external/tinyxml" path="external/tinyxml" revision="0d3f3eb780888334c9d87d22d5595f313395f8d6"/>
<project name="platform/external/tremolo" path="external/tremolo" revision="1d89e41830bf268fca6b4a797ea30a96d84a84fb"/>
<project name="platform/external/webp" path="external/webp" revision="5ac894de5df4345a26e006d4f73acb422ea9d814"/>
<project name="platform/external/webrtc" path="external/webrtc" revision="333fb8884c0467f06ad956b3e52ff1169b822eb0"/>
<project name="platform/external/yaffs2" path="external/yaffs2" revision="0f5965cfdf082b9e329d543e2a49d229dccf0050"/>
<project name="platform/external/zlib" path="external/zlib" revision="d0f3ac473d9d1356ce175cffc3c2a33c2fbda006"/>
<project name="platform/external/zopfli" path="external/zopfli" revision="546647ec998acbaa06124ee57b826257b6530905"/>
<project name="platform/frameworks/native" path="frameworks/native" revision="0951e44a56a7721c26d82fdc1d4c8c05f83f6496"/>
<project name="platform/frameworks/opt/emoji" path="frameworks/opt/emoji" revision="d9f0fe12f016cd8105dfd110cd2209db725e035a"/>
<project name="platform/hardware/libhardware_legacy" path="hardware/libhardware_legacy" revision="f5cec8be8143538003c4cdc84a3bc4973e70adec"/>
<project name="platform/libcore" path="libcore" revision="42e5c65efd528e3d360ecf147016af8f51abe679"/>
<project name="platform/libnativehelper" path="libnativehelper" revision="1c07d779580f3db10a7f96fba387968bf2783e28"/>
<project name="platform/ndk" path="ndk" revision="2f78ff38973c8e8e89dc28915fe637ea185b102e"/>
<project name="platform_prebuilts_misc" path="prebuilts/misc" remote="b2g" revision="25b96077aeae7bd0e3a5e7c284fb636664337013"/>
<project name="platform/prebuilts/ndk" path="prebuilts/ndk" revision="3881c90cec9a89b1b1e13c97af537647dcf63c71"/>
<project name="platform/prebuilts/sdk" path="prebuilts/sdk" revision="a259bbcab27bb60bb75b16bce581549c724954cc"/>
<project name="platform/prebuilts/tools" path="prebuilts/tools" revision="a6dda1861aee67e18947fee510e4e85f8c1ffeb7"/>
<project name="platform_system_bluetoothd" path="system/bluetoothd" remote="b2g" revision="bb356d6505f914347690c8143dbd03af427dd07e"/>
<project name="platform/system/extras" path="system/extras" revision="7eb50bad98466762626ae1dd31df2acd77202c06"/>
<project name="platform_system_libfdio" path="system/libfdio" remote="b2g" revision="34adfb400e031f3dd3353d92413572db5e3a7376"/>
<project name="platform/system/media" path="system/media" revision="9de34d557017fe115051fbbd49f05564de6ba3de"/>
<project name="platform/system/netd" path="system/netd" revision="313c71603fbfa643ff5f62f4fb3c26aa4696435c"/>
<project name="platform/system/security" path="system/security" revision="7c9f4ee469a965f119e302a39f00004d8c59bc6b"/>
<project name="platform/system/vold" path="system/vold" revision="d4435cf658bc391a5dc85d4973b38e5b9ada92fc"/>
<project name="platform_frameworks_av" path="frameworks/av" remote="b2g" revision="479a404164986b3e95212eecdae7e67da4fba9ed"/>
<project name="platform_frameworks_base" path="frameworks/base" remote="b2g" revision="10592803994aa01fa3dc0d0bd36d0d29f006d779"/>
<project name="platform_frameworks_wilhelm" path="frameworks/wilhelm" remote="b2g" revision="174bb44bb9af7583e6337e1e1b6cc18d0217ae82"/>
<project name="platform_system_core" path="system/core" remote="b2g" revision="1b8322b228f717ff2a4d48fa8b44240d8e3f62bc"/>
<project name="platform_external_sepolicy" path="external/sepolicy" remote="b2g" revision="246c603d9fe181fa8893af7293dbc63e870fe5e0"/>
<default remote="caf" revision="refs/tags/android-5.1.1_r29" sync-j="4"/>
<!-- Platform common things -->
<project name="platform/external/libxml2" path="external/libxml2" revision="d0fea31601c5a47c1327515a1ed1d81c4d3586cf"/>
<project name="platform/external/wpa_supplicant_8" path="external/wpa_supplicant_8" revision="60a2b840daa7e877d7aec9c08909a17b72095d10"/>
<project name="platform/hardware/broadcom/wlan" path="hardware/broadcom/wlan" revision="0e5baccde90f60ab248a1aedd1f50bff0ba224d9"/>
<project name="platform/hardware/qcom/bt" path="hardware/qcom/bt" remote="caf" revision="d2b071c3683bace40c9ed666107367dac5a5bb45"/>
<project name="platform/hardware/qcom/audio" path="hardware/qcom/audio" revision="4225df42ce215c0b46c7bd5a59a03f0c85c1d06c"/>
<project name="platform/hardware/qcom/gps" path="hardware/qcom/gps" revision="b299e214bf810b2b29a3cdcf872f8de09595845f"/>
<project name="platform/hardware/qcom/media" path="hardware/qcom/media" revision="0a774fd3bfc78cf03ca10d436255d0120c49a068"/>
<project name="platform/hardware/qcom/wlan" path="hardware/qcom/wlan" revision="82d8c482d59926ff27c53f1fcc8a0ce3554e5491"/>
<project name="platform/hardware/ril" path="hardware/ril" revision="4782b037af55a8d61078305df6a4c31c1424623c"/>
<project name="platform_external_libnfc-nci" path="external/libnfc-nci" remote="b2g" revision="cac2be832f7bf748d8f0de7eac93009ef7c2eea9"/>
<project name="hardware_qcom_display" path="hardware/qcom/display" remote="b2g" revision="288427e047fcdadba1b907fc886bf1142e1f646e"/>
<project name="platform_hardware_broadcom_libbt" path="hardware/broadcom/libbt" remote="b2g" revision="c927f10590b39bfe35976e01aed0969afeed3ba8"/>
<project name="platform_hardware_libhardware" path="hardware/libhardware" remote="b2g" revision="c4bdd7888f2ab3069f6f8853915a806ebd390320"/>
<project name="platform_system_nfcd" path="system/nfcd" remote="b2g" revision="5f4b68c799927b6e078f987b12722c3a6ccd4a45"/>
<!--
<project name="platform/hardware/qcom/camera" path="hardware/qcom/camera" revision="l_LNX.LA.3.6"/>
-->
<!-- Sony platform specific things -->
<project name="codeaurora_kernel_msm" path="kernel" remote="b2g" revision="831789c04366336dd35ce0678f02656c1476d6d7"/>
<project name="device-qcom-sepolicy" path="device/qcom/sepolicy" remote="b2g" revision="e0a1cce6a8559ccc88e15da148531d36e18ae5f7"/>
<project name="android_external_busybox" path="external/busybox" remote="b2g" revision="757a15c84e7bf9897b908058f2e529fcffea73b8"/>
<project groups="device" name="init_sh" path="hardware/sony/init_sh" remote="b2g" revision="58d94ebe3a2e9a1af4bc8c003cd6837e53cc4867"/>
<project groups="device" name="macaddrsetup" path="hardware/sony/macaddrsetup" remote="b2g" revision="e7ee149283eaec32a525ee39b2ecdca46d502e77"/>
<project groups="device" name="mkqcdtbootimg" path="hardware/sony/mkqcdtbootimg" remote="b2g" revision="4badb288cefe7cc708450c02fd4f6cab84c3ed98"/>
<project groups="device" name="thermanager" path="hardware/sony/thermanager" remote="b2g" revision="ec9a2b6a7fe8254ecc3c2c4781e7304f3822e390"/>
<project groups="device" name="timekeep" path="hardware/sony/timekeep" remote="b2g" revision="4cbb0abc00681f116f043584661307b5c4855a31"/>
<!-- Shinano platform specific things -->
<project groups="device" name="device-sony-shinano" path="device/sony/shinano" remote="b2g" revision="fdc22f74b2271dfcfba1e7191b83257b5a2b1343"/>
<project groups="device" name="device-sony-aries" path="device/sony/aries" remote="b2g" revision="56f665a19e35623d923949014bc15013d292fa37"/>
<project groups="device" name="device-sony-leo" path="device/sony/leo" remote="b2g" revision="3fdec3f02117be6b57d2f9c5946119f179119a51"/>
<project groups="device" name="device-sony-scorpion" path="device/sony/scorpion" remote="b2g" revision="b0cd32cb805637d2afda61b67d0421e2c39b2ffa"/>
<project groups="device" name="device-sony-sirius" path="device/sony/sirius" remote="b2g" revision="5e5c19c6cd7c042bcac41a2b30b3ad3eeb66e7ab"/>
<!-- Rhine platform specific things -->
<project groups="device" name="device-sony-rhine" path="device/sony/rhine" remote="b2g" revision="236bfcab94371ca7056dab2dab15b77a6f9fa1b3"/>
<project groups="device" name="device-sony-honami" path="device/sony/honami" remote="b2g" revision="f8a9d6d3a2ce73d336453b71db591a82f72a2120"/>
<project groups="device" name="device-sony-amami" path="device/sony/amami" remote="b2g" revision="9f05cdc34e60df19fcd9189ff8a14f752dcf07b9"/>
<!-- Yukon platform specific things -->
<project name="device-sony-yukon" path="device/sony/yukon" remote="b2g" revision="054306b53d6a9c85c6276fea625c3dfb55ac38c3"/>
<project name="device-sony-tianchi" path="device/sony/tianchi" remote="b2g" revision="571afbafe2abf52e031c2484f38ca5f6f81a114a"/>
<project name="device-sony-eagle" path="device/sony/eagle" remote="b2g" revision="b253fa75f0eeea10a1fa747fd98deb80ec71a654"/>
<project name="device-sony-flamingo" path="device/sony/flamingo" remote="b2g" revision="44fb20f16a9b1a44af6109dbfad2a975ca29fece"/>
<project name="device-sony-seagull" path="device/sony/seagull" remote="b2g" revision="666810a3b77df8cd7a5e68e3d041052b6dd8cd22"/>
</manifest>

View File

@ -21,7 +21,7 @@
<!--
B2G repositories for all targets
-->
<project name="gaia" path="gaia" remote="mozillaorg" revision="260e51a4262f75341e037e583dfc8f6835b5ab31"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="3c97d6a8ac5a69662e1e2c22a84ea59bf50c305e"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="4a962bdab532e18f53e9d2d114c349983262c6b7"/>
<project name="moztt" path="external/moztt" remote="b2g" revision="99c333dab00ed79baff9e1cf76b320aee8e1c123"/>
<project name="platform_hardware_libhardware_moz" path="hardware/libhardware_moz" remote="b2g" revision="fdf3a143dc777e5f9d33a88373af7ea161d3b440"/>
@ -35,7 +35,7 @@
<project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
<project name="valgrind" path="external/valgrind" remote="b2g" revision="5f931350fbc87c3df9db8b0ceb37734b8b471593"/>
<project name="vex" path="external/VEX" remote="b2g" revision="48d8c7c950745f1b166b42125e6f0d3293d71636"/>
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="5e96ed318db1ba8037eb402724bc052240ac9e05"/>
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="8b76e208f3ba77f26017122fe9661d2221028f7a"/>
<!-- Stock Android things -->
<project groups="linux" name="platform/prebuilts/gcc/linux-x86/host/i686-linux-glibc2.7-4.6" path="prebuilts/gcc/linux-x86/host/i686-linux-glibc2.7-4.6" revision="95bb5b66b3ec5769c3de8d3f25d681787418e7d2"/>
<project groups="linux" name="platform/prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.7-4.6" path="prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.7-4.6" revision="ebdad82e61c16772f6cd47e9f11936bf6ebe9aa0"/>

View File

@ -21,7 +21,7 @@
<!--
B2G repositories for all targets
-->
<project name="gaia" path="gaia" remote="mozillaorg" revision="260e51a4262f75341e037e583dfc8f6835b5ab31"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="3c97d6a8ac5a69662e1e2c22a84ea59bf50c305e"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="4a962bdab532e18f53e9d2d114c349983262c6b7"/>
<project name="moztt" path="external/moztt" remote="b2g" revision="99c333dab00ed79baff9e1cf76b320aee8e1c123"/>
<project name="platform_hardware_libhardware_moz" path="hardware/libhardware_moz" remote="b2g" revision="fdf3a143dc777e5f9d33a88373af7ea161d3b440"/>
@ -35,7 +35,7 @@
<project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
<project name="valgrind" path="external/valgrind" remote="b2g" revision="5f931350fbc87c3df9db8b0ceb37734b8b471593"/>
<project name="vex" path="external/VEX" remote="b2g" revision="48d8c7c950745f1b166b42125e6f0d3293d71636"/>
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="5e96ed318db1ba8037eb402724bc052240ac9e05"/>
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="8b76e208f3ba77f26017122fe9661d2221028f7a"/>
<!-- Stock Android things -->
<project groups="linux" name="platform/prebuilts/gcc/linux-x86/host/i686-linux-glibc2.7-4.6" path="prebuilts/gcc/linux-x86/host/i686-linux-glibc2.7-4.6" revision="95bb5b66b3ec5769c3de8d3f25d681787418e7d2"/>
<project groups="linux" name="platform/prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.7-4.6" path="prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.7-4.6" revision="ebdad82e61c16772f6cd47e9f11936bf6ebe9aa0"/>

View File

@ -21,7 +21,7 @@
<!--
B2G repositories for all targets
-->
<project name="gaia" path="gaia" remote="mozillaorg" revision="260e51a4262f75341e037e583dfc8f6835b5ab31"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="3c97d6a8ac5a69662e1e2c22a84ea59bf50c305e"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="4a962bdab532e18f53e9d2d114c349983262c6b7"/>
<project name="moztt" path="external/moztt" remote="b2g" revision="99c333dab00ed79baff9e1cf76b320aee8e1c123"/>
<project name="platform_hardware_libhardware_moz" path="hardware/libhardware_moz" remote="b2g" revision="fdf3a143dc777e5f9d33a88373af7ea161d3b440"/>

View File

@ -21,7 +21,7 @@
<!--
B2G repositories for all targets
-->
<project name="gaia" path="gaia" remote="mozillaorg" revision="260e51a4262f75341e037e583dfc8f6835b5ab31"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="3c97d6a8ac5a69662e1e2c22a84ea59bf50c305e"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="4a962bdab532e18f53e9d2d114c349983262c6b7"/>
<project name="moztt" path="external/moztt" remote="b2g" revision="99c333dab00ed79baff9e1cf76b320aee8e1c123"/>
<project name="platform_hardware_libhardware_moz" path="hardware/libhardware_moz" remote="b2g" revision="fdf3a143dc777e5f9d33a88373af7ea161d3b440"/>
@ -31,7 +31,7 @@
</project>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="5e96ed318db1ba8037eb402724bc052240ac9e05"/>
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="8b76e208f3ba77f26017122fe9661d2221028f7a"/>
<project name="valgrind" path="external/valgrind" remote="b2g" revision="5f931350fbc87c3df9db8b0ceb37734b8b471593"/>
<project name="vex" path="external/VEX" remote="b2g" revision="48d8c7c950745f1b166b42125e6f0d3293d71636"/>
<!-- Stock Android things -->

View File

@ -21,7 +21,7 @@
<!--
B2G repositories for all targets
-->
<project name="gaia" path="gaia" remote="mozillaorg" revision="260e51a4262f75341e037e583dfc8f6835b5ab31"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="3c97d6a8ac5a69662e1e2c22a84ea59bf50c305e"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="4a962bdab532e18f53e9d2d114c349983262c6b7"/>
<project name="moztt" path="external/moztt" remote="b2g" revision="99c333dab00ed79baff9e1cf76b320aee8e1c123"/>
<project name="platform_hardware_libhardware_moz" path="hardware/libhardware_moz" remote="b2g" revision="fdf3a143dc777e5f9d33a88373af7ea161d3b440"/>
@ -34,7 +34,7 @@
<project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
<project name="valgrind" path="external/valgrind" remote="b2g" revision="5f931350fbc87c3df9db8b0ceb37734b8b471593"/>
<project name="vex" path="external/VEX" remote="b2g" revision="48d8c7c950745f1b166b42125e6f0d3293d71636"/>
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="5e96ed318db1ba8037eb402724bc052240ac9e05"/>
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="8b76e208f3ba77f26017122fe9661d2221028f7a"/>
<!-- Stock Android things -->
<project groups="linux" name="platform/prebuilts/gcc/linux-x86/host/i686-linux-glibc2.7-4.6" path="prebuilts/gcc/linux-x86/host/i686-linux-glibc2.7-4.6" revision="f92a936f2aa97526d4593386754bdbf02db07a12"/>
<project groups="linux" name="platform/prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.7-4.6" path="prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.7-4.6" revision="6e47ff2790f5656b5b074407829ceecf3e6188c4"/>

View File

@ -21,7 +21,7 @@
<!--
B2G repositories for all targets
-->
<project name="gaia" path="gaia" remote="mozillaorg" revision="260e51a4262f75341e037e583dfc8f6835b5ab31"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="3c97d6a8ac5a69662e1e2c22a84ea59bf50c305e"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="4a962bdab532e18f53e9d2d114c349983262c6b7"/>
<project name="moztt" path="external/moztt" remote="b2g" revision="99c333dab00ed79baff9e1cf76b320aee8e1c123"/>
<project name="platform_hardware_libhardware_moz" path="hardware/libhardware_moz" remote="b2g" revision="fdf3a143dc777e5f9d33a88373af7ea161d3b440"/>
@ -34,7 +34,7 @@
<project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
<project name="valgrind" path="external/valgrind" remote="b2g" revision="5f931350fbc87c3df9db8b0ceb37734b8b471593"/>
<project name="vex" path="external/VEX" remote="b2g" revision="48d8c7c950745f1b166b42125e6f0d3293d71636"/>
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="5e96ed318db1ba8037eb402724bc052240ac9e05"/>
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="8b76e208f3ba77f26017122fe9661d2221028f7a"/>
<!-- Stock Android things -->
<project groups="pdk,linux" name="platform/prebuilts/clang/linux-x86/host/3.5" path="prebuilts/clang/linux-x86/host/3.5" revision="ffc05a232799fe8fcb3e47b7440b52b1fb4244c0"/>
<project groups="pdk,linux,arm" name="platform/prebuilts/gcc/linux-x86/aarch64/aarch64-linux-android-4.8" path="prebuilts/gcc/linux-x86/aarch64/aarch64-linux-android-4.8" revision="337e0ef5e40f02a1ae59b90db0548976c70a7226"/>

View File

@ -21,7 +21,7 @@
<!--
B2G repositories for all targets
-->
<project name="gaia" path="gaia" remote="mozillaorg" revision="260e51a4262f75341e037e583dfc8f6835b5ab31"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="3c97d6a8ac5a69662e1e2c22a84ea59bf50c305e"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="4a962bdab532e18f53e9d2d114c349983262c6b7"/>
<project name="moztt" path="external/moztt" remote="b2g" revision="99c333dab00ed79baff9e1cf76b320aee8e1c123"/>
<project name="platform_hardware_libhardware_moz" path="hardware/libhardware_moz" remote="b2g" revision="fdf3a143dc777e5f9d33a88373af7ea161d3b440"/>

View File

@ -21,7 +21,7 @@
<!--
B2G repositories for all targets
-->
<project name="gaia" path="gaia" remote="mozillaorg" revision="260e51a4262f75341e037e583dfc8f6835b5ab31"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="3c97d6a8ac5a69662e1e2c22a84ea59bf50c305e"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="4a962bdab532e18f53e9d2d114c349983262c6b7"/>
<project name="moztt" path="external/moztt" remote="b2g" revision="99c333dab00ed79baff9e1cf76b320aee8e1c123"/>
<project name="platform_hardware_libhardware_moz" path="hardware/libhardware_moz" remote="b2g" revision="fdf3a143dc777e5f9d33a88373af7ea161d3b440"/>
@ -35,7 +35,7 @@
<project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
<project name="valgrind" path="external/valgrind" remote="b2g" revision="5f931350fbc87c3df9db8b0ceb37734b8b471593"/>
<project name="vex" path="external/VEX" remote="b2g" revision="48d8c7c950745f1b166b42125e6f0d3293d71636"/>
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="5e96ed318db1ba8037eb402724bc052240ac9e05"/>
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="8b76e208f3ba77f26017122fe9661d2221028f7a"/>
<!-- Stock Android things -->
<project groups="linux" name="platform/prebuilts/gcc/linux-x86/host/i686-linux-glibc2.7-4.6" path="prebuilts/gcc/linux-x86/host/i686-linux-glibc2.7-4.6" revision="95bb5b66b3ec5769c3de8d3f25d681787418e7d2"/>
<project groups="linux" name="platform/prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.7-4.6" path="prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.7-4.6" revision="ebdad82e61c16772f6cd47e9f11936bf6ebe9aa0"/>

View File

@ -1,9 +1,9 @@
{
"git": {
"git_revision": "260e51a4262f75341e037e583dfc8f6835b5ab31",
"git_revision": "3c97d6a8ac5a69662e1e2c22a84ea59bf50c305e",
"remote": "https://git.mozilla.org/releases/gaia.git",
"branch": ""
},
"revision": "1af9f0deab127d830bbd4e29651fed46614104f8",
"revision": "39dd39c9b2ff6718c79af1617577366fa9510603",
"repo_path": "integration/gaia-central"
}

View File

@ -21,7 +21,7 @@
<!--
B2G repositories for all targets
-->
<project name="gaia" path="gaia" remote="mozillaorg" revision="260e51a4262f75341e037e583dfc8f6835b5ab31"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="3c97d6a8ac5a69662e1e2c22a84ea59bf50c305e"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="4a962bdab532e18f53e9d2d114c349983262c6b7"/>
<project name="moztt" path="external/moztt" remote="b2g" revision="99c333dab00ed79baff9e1cf76b320aee8e1c123"/>
<project name="platform_hardware_libhardware_moz" path="hardware/libhardware_moz" remote="b2g" revision="fdf3a143dc777e5f9d33a88373af7ea161d3b440"/>
@ -35,7 +35,7 @@
<project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
<project name="valgrind" path="external/valgrind" remote="b2g" revision="5f931350fbc87c3df9db8b0ceb37734b8b471593"/>
<project name="vex" path="external/VEX" remote="b2g" revision="48d8c7c950745f1b166b42125e6f0d3293d71636"/>
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="5e96ed318db1ba8037eb402724bc052240ac9e05"/>
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="8b76e208f3ba77f26017122fe9661d2221028f7a"/>
<!-- Stock Android things -->
<project groups="linux" name="platform/prebuilts/gcc/linux-x86/host/i686-linux-glibc2.7-4.6" path="prebuilts/gcc/linux-x86/host/i686-linux-glibc2.7-4.6" revision="f92a936f2aa97526d4593386754bdbf02db07a12"/>
<project groups="linux" name="platform/prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.7-4.6" path="prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.7-4.6" revision="6e47ff2790f5656b5b074407829ceecf3e6188c4"/>

View File

@ -21,7 +21,7 @@
<!--
B2G repositories for all targets
-->
<project name="gaia" path="gaia" remote="mozillaorg" revision="260e51a4262f75341e037e583dfc8f6835b5ab31"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="3c97d6a8ac5a69662e1e2c22a84ea59bf50c305e"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="4a962bdab532e18f53e9d2d114c349983262c6b7"/>
<project name="moztt" path="external/moztt" remote="b2g" revision="99c333dab00ed79baff9e1cf76b320aee8e1c123"/>
<project name="platform_hardware_libhardware_moz" path="hardware/libhardware_moz" remote="b2g" revision="fdf3a143dc777e5f9d33a88373af7ea161d3b440"/>
@ -32,7 +32,7 @@
<project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
<project name="fake-qemu-kernel" path="prebuilts/qemu-kernel" remote="b2g" revision="939b377d55a2f081d94029a30a75d05e5a20daf3"/>
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="5e96ed318db1ba8037eb402724bc052240ac9e05"/>
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="8b76e208f3ba77f26017122fe9661d2221028f7a"/>
<project name="valgrind" path="external/valgrind" remote="b2g" revision="5f931350fbc87c3df9db8b0ceb37734b8b471593"/>
<project name="vex" path="external/VEX" remote="b2g" revision="48d8c7c950745f1b166b42125e6f0d3293d71636"/>
<!-- Stock Android things -->

View File

@ -21,7 +21,7 @@
<!--
B2G repositories for all targets
-->
<project name="gaia" path="gaia" remote="mozillaorg" revision="260e51a4262f75341e037e583dfc8f6835b5ab31"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="3c97d6a8ac5a69662e1e2c22a84ea59bf50c305e"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="4a962bdab532e18f53e9d2d114c349983262c6b7"/>
<project name="moztt" path="external/moztt" remote="b2g" revision="99c333dab00ed79baff9e1cf76b320aee8e1c123"/>
<project name="platform_hardware_libhardware_moz" path="hardware/libhardware_moz" remote="b2g" revision="fdf3a143dc777e5f9d33a88373af7ea161d3b440"/>
@ -35,7 +35,7 @@
<project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
<project name="valgrind" path="external/valgrind" remote="b2g" revision="5f931350fbc87c3df9db8b0ceb37734b8b471593"/>
<project name="vex" path="external/VEX" remote="b2g" revision="48d8c7c950745f1b166b42125e6f0d3293d71636"/>
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="5e96ed318db1ba8037eb402724bc052240ac9e05"/>
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="8b76e208f3ba77f26017122fe9661d2221028f7a"/>
<!-- Stock Android things -->
<project groups="pdk,linux" name="platform/prebuilts/clang/linux-x86/host/3.5" path="prebuilts/clang/linux-x86/host/3.5" revision="ffc05a232799fe8fcb3e47b7440b52b1fb4244c0"/>
<project groups="pdk,linux,arm" name="platform/prebuilts/gcc/linux-x86/aarch64/aarch64-linux-android-4.8" path="prebuilts/gcc/linux-x86/aarch64/aarch64-linux-android-4.8" revision="337e0ef5e40f02a1ae59b90db0548976c70a7226"/>

View File

@ -19,7 +19,6 @@ MOZ_OFFICIAL_BRANDING_DIRECTORY=b2g/branding/official
MOZ_SAFE_BROWSING=1
MOZ_SERVICES_COMMON=1
MOZ_SERVICES_METRICS=1
MOZ_WEBSMS_BACKEND=1
MOZ_NO_SMART_CARDS=1

View File

@ -26,7 +26,6 @@ MOZ_OFFICIAL_BRANDING_DIRECTORY=b2g/branding/official
MOZ_SAFE_BROWSING=1
MOZ_SERVICES_COMMON=1
MOZ_SERVICES_METRICS=1
MOZ_CAPTIVEDETECT=1
MOZ_WEBSMS_BACKEND=1

View File

@ -627,10 +627,6 @@
#endif
@RESPATH@/components/servicesComponents.manifest
@RESPATH@/components/cryptoComponents.manifest
#ifdef MOZ_SERVICES_HEALTHREPORT
@RESPATH@/components/HealthReportComponents.manifest
@RESPATH@/components/HealthReportService.js
#endif
@RESPATH@/components/CaptivePortalDetectComponents.manifest
@RESPATH@/components/captivedetect.js
@RESPATH@/components/TelemetryStartup.js

View File

@ -1548,7 +1548,8 @@ pref("experiments.supported", true);
pref("media.gmp-provider.enabled", true);
#ifdef NIGHTLY_BUILD
pref("browser.polaris.enabled", false);
pref("privacy.trackingprotection.ui.enabled", true);
#else
pref("privacy.trackingprotection.ui.enabled", false);
#endif
pref("privacy.trackingprotection.introCount", 0);

View File

@ -23,10 +23,6 @@ var healthReportWrapper = {
let iframe = document.getElementById("remote-report");
iframe.addEventListener("load", healthReportWrapper.initRemotePage, false);
iframe.src = this._getReportURI().spec;
iframe.onload = () => {
MozSelfSupport.getHealthReportPayload().then(this.updatePayload,
this.handleInitFailure);
};
prefs.observe("uploadEnabled", this.updatePrefState, healthReportWrapper);
},
@ -103,15 +99,6 @@ var healthReportWrapper = {
});
},
refreshPayload: function () {
MozSelfSupport.getHealthReportPayload().then(this.updatePayload,
this.handlePayloadFailure);
},
updatePayload: function (payload) {
healthReportWrapper.injectData("payload", JSON.stringify(payload));
},
injectData: function (type, content) {
let report = this._getReportURI();
@ -139,9 +126,6 @@ var healthReportWrapper = {
case "RequestCurrentPrefs":
this.updatePrefState();
break;
case "RequestCurrentPayload":
this.refreshPayload();
break;
case "RequestTelemetryPingList":
this.sendTelemetryPingList();
break;

View File

@ -2,6 +2,9 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
const LOGGER_NAME = "Toolkit.Telemetry";
const LOGGER_PREFIX = "DataNotificationInfoBar::";
/**
* Represents an info bar that shows a data submission notification.
*/
@ -21,7 +24,7 @@ var gDataNotificationInfoBar = {
get _log() {
let Log = Cu.import("resource://gre/modules/Log.jsm", {}).Log;
delete this._log;
return this._log = Log.repository.getLogger("Services.DataReporting.InfoBar");
return this._log = Log.repository.getLoggerWithMessagePrefix(LOGGER_NAME, LOGGER_PREFIX);
},
init: function() {

View File

@ -3633,7 +3633,7 @@ const BrowserSearch = {
loadSearchFromContext: function (terms) {
let engine = BrowserSearch._loadSearch(terms, true, "contextmenu");
if (engine) {
BrowserSearch.recordSearchInHealthReport(engine, "contextmenu");
BrowserSearch.recordSearchInTelemetry(engine, "contextmenu");
}
},
@ -3657,10 +3657,26 @@ const BrowserSearch = {
openUILinkIn(searchEnginesURL, where);
},
_getSearchEngineId: function (engine) {
if (!engine) {
return "other";
}
if (engine.identifier) {
return engine.identifier;
}
if (!("name" in engine) || engine.name === undefined) {
return "other";
}
return "other-" + engine.name;
},
/**
* Helper to record a search with Firefox Health Report.
* Helper to record a search with Telemetry.
*
* FHR records only search counts and nothing pertaining to the search itself.
* Telemetry records only search counts and nothing pertaining to the search itself.
*
* @param engine
* (nsISearchEngine) The engine handling the search.
@ -3672,45 +3688,7 @@ const BrowserSearch = {
* the search was a suggested search, this indicates where the
* item was in the suggestion list and how the user selected it.
*/
recordSearchInHealthReport: function (engine, source, selection) {
BrowserUITelemetry.countSearchEvent(source, null, selection);
this.recordSearchInTelemetry(engine, source);
let reporter = AppConstants.MOZ_SERVICES_HEALTHREPORT
? Cc["@mozilla.org/datareporting/service;1"]
.getService()
.wrappedJSObject
.healthReporter
: null;
// This can happen if the FHR component of the data reporting service is
// disabled. This is controlled by a pref that most will never use.
if (!reporter) {
return;
}
reporter.onInit().then(function record() {
try {
reporter.getProvider("org.mozilla.searches").recordSearch(engine, source);
} catch (ex) {
Cu.reportError(ex);
}
});
},
_getSearchEngineId: function (engine) {
if (!engine) {
return "other";
}
if (engine.identifier) {
return engine.identifier;
}
return "other-" + engine.name;
},
recordSearchInTelemetry: function (engine, source) {
recordSearchInTelemetry: function (engine, source, selection) {
const SOURCES = [
"abouthome",
"contextmenu",
@ -3719,6 +3697,8 @@ const BrowserSearch = {
"urlbar",
];
BrowserUITelemetry.countSearchEvent(source, null, selection);
if (SOURCES.indexOf(source) == -1) {
Cu.reportError("Unknown source for search: " + source);
return;

View File

@ -286,8 +286,6 @@ skip-if = e10s # Bug 1094510 - test hits the network in e10s mode only
[browser_contextSearchTabPosition.js]
skip-if = os == "mac" || e10s # bug 967013; e10s: bug 1094761 - test hits the network in e10s, causing next test to crash
[browser_ctrlTab.js]
[browser_datareporting_notification.js]
skip-if = !datareporting
[browser_datachoices_notification.js]
skip-if = !datareporting
[browser_devedition.js]
@ -480,7 +478,6 @@ skip-if = os == "linux" # Bug 1073339 - Investigate autocomplete test unreliabil
[browser_urlbarStop.js]
[browser_urlbarTrimURLs.js]
[browser_urlbar_autoFill_backspaced.js]
[browser_urlbar_search_healthreport.js]
[browser_urlbar_searchsettings.js]
[browser_utilityOverlay.js]
[browser_viewSourceInTabOnViewSource.js]

View File

@ -1,154 +1,143 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/
*/
XPCOMUtils.defineLazyModuleGetter(this, "Promise",
"resource://gre/modules/Promise.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Task",
"resource://gre/modules/Task.jsm");
const CHROME_BASE = "chrome://mochitests/content/browser/browser/base/content/test/general/";
const HTTPS_BASE = "https://example.com/browser/browser/base/content/test/general/";
const TELEMETRY_LOG_PREF = "toolkit.telemetry.log.level";
const telemetryOriginalLogPref = Preferences.get(TELEMETRY_LOG_PREF, null);
const originalReportUrl = Services.prefs.getCharPref("datareporting.healthreport.about.reportUrl");
const originalReportUrlUnified = Services.prefs.getCharPref("datareporting.healthreport.about.reportUrlUnified");
registerCleanupFunction(function() {
// Ensure we don't pollute prefs for next tests.
if (telemetryOriginalLogPref) {
Preferences.set(TELEMETRY_LOG_PREF, telemetryOriginalLogPref);
} else {
Preferences.reset(TELEMETRY_LOG_PREF);
}
try {
Services.prefs.setCharPref("datareporting.healthreport.about.reportUrl", originalReportUrl);
Services.prefs.setCharPref("datareporting.healthreport.about.reportUrlUnified", originalReportUrlUnified);
let policy = Cc["@mozilla.org/datareporting/service;1"]
.getService(Ci.nsISupports)
.wrappedJSObject
.policy;
policy.recordHealthReportUploadEnabled(true,
"Resetting after tests.");
} catch (ex) {}
});
function fakeTelemetryNow(...args) {
let date = new Date(...args);
let scope = {};
const modules = [
Cu.import("resource://gre/modules/TelemetrySession.jsm", scope),
Cu.import("resource://gre/modules/TelemetryEnvironment.jsm", scope),
Cu.import("resource://gre/modules/TelemetryController.jsm", scope),
];
for (let m of modules) {
m.Policy.now = () => new Date(date);
}
return date;
}
function setupPingArchive() {
let scope = {};
Cu.import("resource://gre/modules/TelemetryController.jsm", scope);
Cc["@mozilla.org/moz/jssubscript-loader;1"].getService(Ci.mozIJSSubScriptLoader)
.loadSubScript(CHROME_BASE + "healthreport_pingData.js", scope);
for (let p of scope.TEST_PINGS) {
fakeTelemetryNow(p.date);
p.id = yield scope.TelemetryController.submitExternalPing(p.type, p.payload);
}
}
var gTests = [
{
desc: "Test the remote commands",
setup: Task.async(function*()
{
Preferences.set(TELEMETRY_LOG_PREF, "Trace");
yield setupPingArchive();
Preferences.set("datareporting.healthreport.about.reportUrl",
HTTPS_BASE + "healthreport_testRemoteCommands.html");
Preferences.set("datareporting.healthreport.about.reportUrlUnified",
HTTPS_BASE + "healthreport_testRemoteCommands.html");
}),
run: function (iframe)
{
let deferred = Promise.defer();
let policy = Cc["@mozilla.org/datareporting/service;1"]
.getService(Ci.nsISupports)
.wrappedJSObject
.policy;
let results = 0;
try {
iframe.contentWindow.addEventListener("FirefoxHealthReportTestResponse", function evtHandler(event) {
let data = event.detail.data;
if (data.type == "testResult") {
ok(data.pass, data.info);
results++;
}
else if (data.type == "testsComplete") {
is(results, data.count, "Checking number of results received matches the number of tests that should have run");
iframe.contentWindow.removeEventListener("FirefoxHealthReportTestResponse", evtHandler, true);
deferred.resolve();
}
}, true);
} catch(e) {
ok(false, "Failed to get all commands");
deferred.reject();
}
return deferred.promise;
}
},
]; // gTests
function test()
{
waitForExplicitFinish();
// xxxmpc leaving this here until we resolve bug 854038 and bug 854060
requestLongerTimeout(10);
Task.spawn(function () {
for (let test of gTests) {
info(test.desc);
yield test.setup();
let iframe = yield promiseNewTabLoadEvent("about:healthreport");
yield test.run(iframe);
gBrowser.removeCurrentTab();
}
finish();
});
}
function promiseNewTabLoadEvent(aUrl, aEventType="load")
{
let deferred = Promise.defer();
let tab = gBrowser.selectedTab = gBrowser.addTab(aUrl);
tab.linkedBrowser.addEventListener(aEventType, function load(event) {
tab.linkedBrowser.removeEventListener(aEventType, load, true);
let iframe = tab.linkedBrowser.contentDocument.getElementById("remote-report");
iframe.addEventListener("load", function frameLoad(e) {
if (iframe.contentWindow.location.href == "about:blank" ||
e.target != iframe) {
return;
}
iframe.removeEventListener("load", frameLoad, false);
deferred.resolve(iframe);
}, false);
}, true);
return deferred.promise;
}
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/
*/
XPCOMUtils.defineLazyModuleGetter(this, "Promise",
"resource://gre/modules/Promise.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Task",
"resource://gre/modules/Task.jsm");
const CHROME_BASE = "chrome://mochitests/content/browser/browser/base/content/test/general/";
const HTTPS_BASE = "https://example.com/browser/browser/base/content/test/general/";
const TELEMETRY_LOG_PREF = "toolkit.telemetry.log.level";
const telemetryOriginalLogPref = Preferences.get(TELEMETRY_LOG_PREF, null);
const originalReportUrl = Services.prefs.getCharPref("datareporting.healthreport.about.reportUrl");
const originalReportUrlUnified = Services.prefs.getCharPref("datareporting.healthreport.about.reportUrlUnified");
registerCleanupFunction(function() {
// Ensure we don't pollute prefs for next tests.
if (telemetryOriginalLogPref) {
Preferences.set(TELEMETRY_LOG_PREF, telemetryOriginalLogPref);
} else {
Preferences.reset(TELEMETRY_LOG_PREF);
}
try {
Services.prefs.setCharPref("datareporting.healthreport.about.reportUrl", originalReportUrl);
Services.prefs.setCharPref("datareporting.healthreport.about.reportUrlUnified", originalReportUrlUnified);
Services.prefs.setBoolPref("datareporting.healthreport.uploadEnabled", true);
} catch (ex) {}
});
function fakeTelemetryNow(...args) {
let date = new Date(...args);
let scope = {};
const modules = [
Cu.import("resource://gre/modules/TelemetrySession.jsm", scope),
Cu.import("resource://gre/modules/TelemetryEnvironment.jsm", scope),
Cu.import("resource://gre/modules/TelemetryController.jsm", scope),
];
for (let m of modules) {
m.Policy.now = () => new Date(date);
}
return date;
}
function setupPingArchive() {
let scope = {};
Cu.import("resource://gre/modules/TelemetryController.jsm", scope);
Cc["@mozilla.org/moz/jssubscript-loader;1"].getService(Ci.mozIJSSubScriptLoader)
.loadSubScript(CHROME_BASE + "healthreport_pingData.js", scope);
for (let p of scope.TEST_PINGS) {
fakeTelemetryNow(p.date);
p.id = yield scope.TelemetryController.submitExternalPing(p.type, p.payload);
}
}
var gTests = [
{
desc: "Test the remote commands",
setup: Task.async(function*()
{
Preferences.set(TELEMETRY_LOG_PREF, "Trace");
yield setupPingArchive();
Preferences.set("datareporting.healthreport.about.reportUrl",
HTTPS_BASE + "healthreport_testRemoteCommands.html");
Preferences.set("datareporting.healthreport.about.reportUrlUnified",
HTTPS_BASE + "healthreport_testRemoteCommands.html");
}),
run: function (iframe)
{
let deferred = Promise.defer();
let results = 0;
try {
iframe.contentWindow.addEventListener("FirefoxHealthReportTestResponse", function evtHandler(event) {
let data = event.detail.data;
if (data.type == "testResult") {
ok(data.pass, data.info);
results++;
}
else if (data.type == "testsComplete") {
is(results, data.count, "Checking number of results received matches the number of tests that should have run");
iframe.contentWindow.removeEventListener("FirefoxHealthReportTestResponse", evtHandler, true);
deferred.resolve();
}
}, true);
} catch(e) {
ok(false, "Failed to get all commands");
deferred.reject();
}
return deferred.promise;
}
},
]; // gTests
function test()
{
waitForExplicitFinish();
// xxxmpc leaving this here until we resolve bug 854038 and bug 854060
requestLongerTimeout(10);
Task.spawn(function () {
for (let test of gTests) {
info(test.desc);
yield test.setup();
let iframe = yield promiseNewTabLoadEvent("about:healthreport");
yield test.run(iframe);
gBrowser.removeCurrentTab();
}
finish();
});
}
function promiseNewTabLoadEvent(aUrl, aEventType="load")
{
let deferred = Promise.defer();
let tab = gBrowser.selectedTab = gBrowser.addTab(aUrl);
tab.linkedBrowser.addEventListener(aEventType, function load(event) {
tab.linkedBrowser.removeEventListener(aEventType, load, true);
let iframe = tab.linkedBrowser.contentDocument.getElementById("remote-report");
iframe.addEventListener("load", function frameLoad(e) {
if (iframe.contentWindow.location.href == "about:blank" ||
e.target != iframe) {
return;
}
iframe.removeEventListener("load", frameLoad, false);
deferred.resolve(iframe);
}, false);
}, true);
return deferred.promise;
}

View File

@ -78,24 +78,11 @@ var gTests = [
}
},
// Disabled on Linux for intermittent issues with FHR, see Bug 945667.
{
desc: "Check that performing a search fires a search event and records to " +
"Firefox Health Report.",
"Telemetry.",
setup: function () { },
run: function* () {
// Skip this test on Linux.
if (navigator.platform.indexOf("Linux") == 0) {
return Promise.resolve();
}
try {
let cm = Cc["@mozilla.org/categorymanager;1"].getService(Ci.nsICategoryManager);
cm.getCategoryEntry("healthreport-js-provider-default", "SearchesProvider");
} catch (ex) {
// Health Report disabled, or no SearchesProvider.
return Promise.resolve();
}
let engine = yield promiseNewEngine("searchSuggestionEngine.xml");
// Make this actually work in healthreport by giving it an ID:
@ -113,23 +100,32 @@ var gTests = [
is(engine.name, engineName, "Engine name in DOM should match engine we just added");
// Get the current number of recorded searches.
let searchStr = "a search";
getNumberOfSearchesInFHR(engineName, "abouthome").then(num => {
numSearchesBefore = num;
let histogramKey = engine.identifier + ".abouthome";
try {
let hs = Services.telemetry.getKeyedHistogramById("SEARCH_COUNTS").snapshot();
if (histogramKey in hs) {
numSearchesBefore = hs[histogramKey].sum;
}
} catch (ex) {
// No searches performed yet, not a problem, |numSearchesBefore| is 0.
}
info("Perform a search.");
doc.getElementById("searchText").value = searchStr;
doc.getElementById("searchSubmit").click();
});
// Perform a search to increase the SEARCH_COUNT histogram.
let searchStr = "a search";
info("Perform a search.");
doc.getElementById("searchText").value = searchStr;
doc.getElementById("searchSubmit").click();
let expectedURL = Services.search.currentEngine.
getSubmission(searchStr, null, "homepage").
uri.spec;
let loadPromise = waitForDocLoadAndStopIt(expectedURL).then(() => {
getNumberOfSearchesInFHR(engineName, "abouthome").then(num => {
is(num, numSearchesBefore + 1, "One more search recorded.");
searchEventDeferred.resolve();
});
// Make sure the SEARCH_COUNTS histogram has the right key and count.
let hs = Services.telemetry.getKeyedHistogramById("SEARCH_COUNTS").snapshot();
Assert.ok(histogramKey in hs, "histogram with key should be recorded");
Assert.equal(hs[histogramKey].sum, numSearchesBefore + 1,
"histogram sum should be incremented");
searchEventDeferred.resolve();
});
try {

View File

@ -2,15 +2,34 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
function test() {
waitForExplicitFinish();
add_task(function* test() {
// Will need to be changed if Google isn't the default search engine.
// Note: geoSpecificDefaults are disabled for mochitests, so this is the
// non-US en-US default.
let histogramKey = "google.contextmenu";
let numSearchesBefore = 0;
try {
let hs = Services.telemetry.getKeyedHistogramById("SEARCH_COUNTS").snapshot();
if (histogramKey in hs) {
numSearchesBefore = hs[histogramKey].sum;
}
} catch (ex) {
// No searches performed yet, not a problem, |numSearchesBefore| is 0.
}
let tabs = [];
let tabsLoadedDeferred = Promise.defer();
function tabAdded(event) {
let tab = event.target;
tabs.push(tab);
}
let tabs = [];
// We wait for the blank tab and the two context searches tabs to open.
if (tabs.length == 3) {
tabsLoadedDeferred.resolve();
}
}
let container = gBrowser.tabContainer;
container.addEventListener("TabOpen", tabAdded, false);
@ -19,6 +38,9 @@ function test() {
BrowserSearch.loadSearchFromContext("mozilla");
BrowserSearch.loadSearchFromContext("firefox");
// Wait for all the tabs to open.
yield tabsLoadedDeferred.promise;
is(tabs[0], gBrowser.tabs[3], "blank tab has been pushed to the end");
is(tabs[1], gBrowser.tabs[1], "first search tab opens next to the current tab");
is(tabs[2], gBrowser.tabs[2], "second search tab opens next to the first search tab");
@ -26,45 +48,9 @@ function test() {
container.removeEventListener("TabOpen", tabAdded, false);
tabs.forEach(gBrowser.removeTab, gBrowser);
try {
let cm = Components.classes["@mozilla.org/categorymanager;1"]
.getService(Components.interfaces.nsICategoryManager);
cm.getCategoryEntry("healthreport-js-provider-default", "SearchesProvider");
} catch (ex) {
// Health Report disabled, or no SearchesProvider.
finish();
return;
}
let reporter = Components.classes["@mozilla.org/datareporting/service;1"]
.getService()
.wrappedJSObject
.healthReporter;
// reporter should always be available in automation.
ok(reporter, "Health Reporter available.");
reporter.onInit().then(function onInit() {
let provider = reporter.getProvider("org.mozilla.searches");
ok(provider, "Searches provider is available.");
let m = provider.getMeasurement("counts", 3);
m.getValues().then(function onValues(data) {
let now = new Date();
ok(data.days.hasDay(now), "Have data for today.");
let day = data.days.getDay(now);
// Will need to be changed if Google isn't the default search engine.
// Note: geoSpecificDefaults are disabled for mochitests, so this is the
// non-US en-US default.
let defaultProviderID = "google";
let field = defaultProviderID + ".contextmenu";
ok(day.has(field), "Have search recorded for context menu.");
// If any other mochitests perform a context menu search, this will fail.
// The solution will be to look up count at test start and ensure it is
// incremented by two.
is(day.get(field), 2, "2 searches recorded in FHR.");
finish();
});
});
}
// Make sure that the context searches are correctly recorded.
let hs = Services.telemetry.getKeyedHistogramById("SEARCH_COUNTS").snapshot();
Assert.ok(histogramKey in hs, "The histogram must contain the correct key");
Assert.equal(hs[histogramKey].sum, numSearchesBefore + 2,
"The histogram must contain the correct search count");
});

View File

@ -10,13 +10,7 @@ var Preferences = Cu.import("resource://gre/modules/Preferences.jsm", {}).Prefer
var TelemetryReportingPolicy =
Cu.import("resource://gre/modules/TelemetryReportingPolicy.jsm", {}).TelemetryReportingPolicy;
XPCOMUtils.defineLazyGetter(this, "gDatareportingService",
() => Cc["@mozilla.org/datareporting/service;1"]
.getService(Ci.nsISupports)
.wrappedJSObject);
const PREF_BRANCH = "datareporting.policy.";
const PREF_DRS_ENABLED = "datareporting.healthreport.service.enabled";
const PREF_BYPASS_NOTIFICATION = PREF_BRANCH + "dataSubmissionPolicyBypassNotification";
const PREF_CURRENT_POLICY_VERSION = PREF_BRANCH + "currentPolicyVersion";
const PREF_ACCEPTED_POLICY_VERSION = PREF_BRANCH + "dataSubmissionPolicyAcceptedVersion";
@ -103,31 +97,21 @@ var checkInfobarButton = Task.async(function* (aNotification) {
});
add_task(function* setup(){
const drsEnabled = Preferences.get(PREF_DRS_ENABLED, true);
const bypassNotification = Preferences.get(PREF_BYPASS_NOTIFICATION, true);
const currentPolicyVersion = Preferences.get(PREF_CURRENT_POLICY_VERSION, 1);
// Register a cleanup function to reset our preferences.
registerCleanupFunction(() => {
Preferences.set(PREF_DRS_ENABLED, drsEnabled);
Preferences.set(PREF_BYPASS_NOTIFICATION, bypassNotification);
Preferences.set(PREF_CURRENT_POLICY_VERSION, currentPolicyVersion);
// Start polling again.
gDatareportingService.policy.startPolling();
return closeAllNotifications();
});
// Disable Healthreport/Data reporting service.
Preferences.set(PREF_DRS_ENABLED, false);
// Don't skip the infobar visualisation.
Preferences.set(PREF_BYPASS_NOTIFICATION, false);
// Set the current policy version.
Preferences.set(PREF_CURRENT_POLICY_VERSION, TEST_POLICY_VERSION);
// Stop the polling to make sure no policy gets displayed by FHR.
gDatareportingService.policy.stopPolling();
});
function clearAcceptedPolicy() {

View File

@ -1,213 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
var originalPolicy = null;
/**
* Display a datareporting notification to the user.
*
* @param {String} name
*/
function sendNotifyRequest(name) {
let ns = {};
Cu.import("resource://gre/modules/services/datareporting/policy.jsm", ns);
Cu.import("resource://gre/modules/Preferences.jsm", ns);
let service = Cc["@mozilla.org/datareporting/service;1"]
.getService(Ci.nsISupports)
.wrappedJSObject;
ok(service.healthReporter, "Health Reporter instance is available.");
Cu.import("resource://gre/modules/Promise.jsm", ns);
let deferred = ns.Promise.defer();
if (!originalPolicy) {
originalPolicy = service.policy;
}
let policyPrefs = new ns.Preferences("testing." + name + ".");
ok(service._prefs, "Health Reporter prefs are available.");
let hrPrefs = service._prefs;
let policy = new ns.DataReportingPolicy(policyPrefs, hrPrefs, service);
policy.dataSubmissionPolicyBypassNotification = false;
service.policy = policy;
policy.firstRunDate = new Date(Date.now() - 24 * 60 * 60 * 1000);
service.healthReporter.onInit().then(function onSuccess () {
is(policy.ensureUserNotified(), false, "User not notified about data policy on init.");
ok(policy._userNotifyPromise, "_userNotifyPromise defined.");
policy._userNotifyPromise.then(
deferred.resolve.bind(deferred),
deferred.reject.bind(deferred)
);
}.bind(this), deferred.reject.bind(deferred));
return [policy, deferred.promise];
}
var dumpAppender, rootLogger;
function test() {
registerCleanupFunction(cleanup);
waitForExplicitFinish();
let ns = {};
Components.utils.import("resource://gre/modules/Log.jsm", ns);
rootLogger = ns.Log.repository.rootLogger;
dumpAppender = new ns.Log.DumpAppender();
dumpAppender.level = ns.Log.Level.All;
rootLogger.addAppender(dumpAppender);
closeAllNotifications().then(function onSuccess () {
let notification = document.getElementById("global-notificationbox");
notification.addEventListener("AlertActive", function active() {
notification.removeEventListener("AlertActive", active, true);
is(notification.allNotifications.length, 1, "Notification Displayed.");
executeSoon(function afterNotification() {
waitForNotificationClose(notification.currentNotification, function onClose() {
is(notification.allNotifications.length, 0, "No notifications remain.");
is(policy.dataSubmissionPolicyAcceptedVersion, 1, "Version pref set.");
ok(policy.dataSubmissionPolicyNotifiedDate.getTime() > -1, "Date pref set.");
test_multiple_windows();
});
notification.currentNotification.close();
});
}, true);
let [policy, promise] = sendNotifyRequest("single_window_notified");
is(policy.dataSubmissionPolicyAcceptedVersion, 0, "No version should be set on init.");
is(policy.dataSubmissionPolicyNotifiedDate.getTime(), 0, "No date should be set on init.");
is(policy.userNotifiedOfCurrentPolicy, false, "User not notified about datareporting policy.");
promise.then(function () {
is(policy.dataSubmissionPolicyAcceptedVersion, 1, "Policy version set.");
is(policy.dataSubmissionPolicyNotifiedDate.getTime() > 0, true, "Policy date set.");
is(policy.userNotifiedOfCurrentPolicy, true, "User notified about datareporting policy.");
}.bind(this), function (err) {
throw err;
});
}.bind(this), function onError (err) {
throw err;
});
}
function test_multiple_windows() {
// Ensure we see the notification on all windows and that action on one window
// results in dismiss on every window.
let window2 = OpenBrowserWindow();
whenDelayedStartupFinished(window2, function onWindow() {
let notification1 = document.getElementById("global-notificationbox");
let notification2 = window2.document.getElementById("global-notificationbox");
ok(notification2, "2nd window has a global notification box.");
let [policy, promise] = sendNotifyRequest("multiple_window_behavior");
let displayCount = 0;
let prefWindowOpened = false;
let mutationObserversRemoved = false;
function onAlertDisplayed() {
displayCount++;
if (displayCount != 2) {
return;
}
ok(true, "Data reporting info bar displayed on all open windows.");
// We register two independent observers and we need both to clean up
// properly. This handles gating for test completion.
function maybeFinish() {
if (!prefWindowOpened) {
dump("Not finishing test yet because pref pane hasn't yet appeared.\n");
return;
}
if (!mutationObserversRemoved) {
dump("Not finishing test yet because mutation observers haven't been removed yet.\n");
return;
}
window2.close();
dump("Finishing multiple window test.\n");
rootLogger.removeAppender(dumpAppender);
dumpAppender = null;
rootLogger = null;
finish();
}
let closeCount = 0;
function onAlertClose() {
closeCount++;
if (closeCount != 2) {
return;
}
ok(true, "Closing info bar on one window closed them on all.");
is(policy.userNotifiedOfCurrentPolicy, true, "Data submission policy accepted.");
is(notification1.allNotifications.length, 0, "No notifications remain on main window.");
is(notification2.allNotifications.length, 0, "No notifications remain on 2nd window.");
mutationObserversRemoved = true;
maybeFinish();
}
waitForNotificationClose(notification1.currentNotification, onAlertClose);
waitForNotificationClose(notification2.currentNotification, onAlertClose);
// While we're here, we dual purpose this test to check that pressing the
// button does the right thing.
let buttons = notification2.currentNotification.getElementsByTagName("button");
is(buttons.length, 1, "There is 1 button in the data reporting notification.");
let button = buttons[0];
// Add an observer to ensure the "advanced" pane opened (but don't bother
// closing it - we close the entire window when done.)
Services.obs.addObserver(function observer(prefWin, topic, data) {
Services.obs.removeObserver(observer, "advanced-pane-loaded");
ok(true, "Advanced preferences opened on info bar button press.");
executeSoon(function soon() {
prefWindowOpened = true;
maybeFinish();
});
}, "advanced-pane-loaded", false);
button.click();
}
notification1.addEventListener("AlertActive", function active1() {
notification1.removeEventListener("AlertActive", active1, true);
executeSoon(onAlertDisplayed);
}, true);
notification2.addEventListener("AlertActive", function active2() {
notification2.removeEventListener("AlertActive", active2, true);
executeSoon(onAlertDisplayed);
}, true);
promise.then(null, function onError(err) {
throw err;
});
});
}
function cleanup () {
// In case some test fails.
if (originalPolicy) {
let service = Cc["@mozilla.org/datareporting/service;1"]
.getService(Ci.nsISupports)
.wrappedJSObject;
service.policy = originalPolicy;
}
return closeAllNotifications();
}

View File

@ -103,7 +103,6 @@ function* compareCounts(clickCallback) {
// FHR -- first make sure the engine has an identifier so that FHR is happy.
Object.defineProperty(engine.wrappedJSObject, "identifier",
{ value: engineID });
let fhrCount = yield getNumberOfSearchesInFHR(engine.name, "urlbar");
gURLBar.focus();
yield clickCallback();
@ -126,10 +125,6 @@ function* compareCounts(clickCallback) {
Assert.ok(histogramKey in snapshot, "histogram with key should be recorded");
Assert.equal(snapshot[histogramKey].sum, histogramCount + 1,
"histogram sum should be incremented");
// FHR
let newFHRCount = yield getNumberOfSearchesInFHR(engine.name, "urlbar");
Assert.equal(newFHRCount, fhrCount + 1, "should be recorded in FHR");
}
/**

View File

@ -1,85 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
add_task(function* test_healthreport_search_recording() {
try {
let cm = Cc["@mozilla.org/categorymanager;1"].getService(Ci.nsICategoryManager);
cm.getCategoryEntry("healthreport-js-provider-default", "SearchesProvider");
} catch (ex) {
// Health Report disabled, or no SearchesProvider.
ok(true, "Firefox Health Report is not enabled.");
return;
}
let reporter = Cc["@mozilla.org/datareporting/service;1"]
.getService()
.wrappedJSObject
.healthReporter;
ok(reporter, "Health Reporter available.");
yield reporter.onInit();
let provider = reporter.getProvider("org.mozilla.searches");
ok(provider, "Searches provider is available.");
let m = provider.getMeasurement("counts", 3);
let data = yield m.getValues();
let now = new Date();
let oldCount = 0;
// This will to be need changed if default search engine is not Google.
// Note: geoSpecificDefaults are disabled for mochitests, so this is the
// non-US en-US default.
let defaultEngineID = "google";
let field = defaultEngineID + ".urlbar";
if (data.days.hasDay(now)) {
let day = data.days.getDay(now);
if (day.has(field)) {
oldCount = day.get(field);
}
}
let tab = gBrowser.addTab("about:blank");
yield BrowserTestUtils.browserLoaded(tab.linkedBrowser);
gBrowser.selectedTab = tab;
let searchStr = "firefox health report";
let expectedURL = Services.search.currentEngine.
getSubmission(searchStr, "", "keyword").uri.spec;
// Expect the search URL to load but stop it as soon as it starts.
let docLoadPromise = waitForDocLoadAndStopIt(expectedURL);
// Trigger the search.
gURLBar.value = searchStr;
gURLBar.handleCommand();
yield docLoadPromise;
data = yield m.getValues();
ok(data.days.hasDay(now), "We have a search measurement for today.");
let day = data.days.getDay(now);
ok(day.has(field), "Have a search count for the urlbar.");
let newCount = day.get(field);
is(newCount, oldCount + 1, "We recorded one new search.");
// We should record the default search engine if Telemetry is enabled.
let oldTelemetry = Services.prefs.getBoolPref("toolkit.telemetry.enabled");
Services.prefs.setBoolPref("toolkit.telemetry.enabled", true);
m = provider.getMeasurement("engines", 2);
yield provider.collectDailyData();
data = yield m.getValues();
ok(data.days.hasDay(now), "Have engines data when Telemetry is enabled.");
day = data.days.getDay(now);
ok(day.has("default"), "We have default engine data.");
is(day.get("default"), defaultEngineID, "The default engine is reported properly.");
// Restore.
Services.prefs.setBoolPref("toolkit.telemetry.enabled", oldTelemetry);
gBrowser.removeTab(tab);
});

View File

@ -1202,61 +1202,3 @@ function promiseCrashReport(expectedExtra) {
}
});
}
/**
* Retrieves the number of searches recorded in FHR for the current day.
*
* @param aEngineName
* name of the setup search engine.
* @param aSource
* The FHR "source" name for the search, like "abouthome" or "urlbar".
*
* @return {Promise} Returns a promise resolving to the number of searches.
*/
function getNumberOfSearchesInFHR(aEngineName, aSource) {
let reporter = Components.classes["@mozilla.org/datareporting/service;1"]
.getService()
.wrappedJSObject
.healthReporter;
ok(reporter, "Health Reporter instance available.");
return reporter.onInit().then(function onInit() {
let provider = reporter.getProvider("org.mozilla.searches");
ok(provider, "Searches provider is available.");
let m = provider.getMeasurement("counts", 3);
return m.getValues().then(data => {
let now = new Date();
let yday = new Date(now);
yday.setDate(yday.getDate() - 1);
// Add the number of searches recorded yesterday to the number of searches
// recorded today. This makes the test not fail intermittently when it is
// run at midnight and we accidentally compare the number of searches from
// different days. Tests are always run with an empty profile so there
// are no searches from yesterday, normally. Should the test happen to run
// past midnight we make sure to count them in as well.
return getNumberOfSearchesInFHRByDate(aEngineName, aSource, data, now) +
getNumberOfSearchesInFHRByDate(aEngineName, aSource, data, yday);
});
});
}
/**
* Helper for getNumberOfSearchesInFHR. You probably don't want to call this
* directly.
*/
function getNumberOfSearchesInFHRByDate(aEngineName, aSource, aData, aDate) {
if (aData.days.hasDay(aDate)) {
let id = Services.search.getEngineByName(aEngineName).identifier;
let day = aData.days.getDay(aDate);
let field = id + "." + aSource;
if (day.has(field)) {
return day.get(field) || 0;
}
}
return 0; // No records found.
}

View File

@ -7,35 +7,14 @@
<script type="application/javascript;version=1.7">
function init() {
window.addEventListener("message", function process(e) {
// The init function of abouthealth.js schedules an initial payload event,
// which will be sent after the payload data has been collected. This extra
// event can cause unexpected successes/failures in this test, so we wait
// for the extra event to arrive here before progressing with the actual
// test.
if (e.data.type == "payload") {
window.removeEventListener("message", process, false);
window.addEventListener("message", doTest, false);
doTest();
}
}, false);
window.addEventListener("message", doTest, false);
doTest();
}
function checkSubmissionValue(payload, expectedValue) {
return payload.enabled == expectedValue;
}
function validatePayload(payload) {
payload = JSON.parse(payload);
// xxxmpc - this is some pretty low-bar validation, but we have plenty of tests of that API elsewhere
if (!payload.thisPingDate)
return false;
return true;
}
function isArray(arg) {
return Object.prototype.toString.call(arg) === '[object Array]';
}
@ -141,11 +120,11 @@ var tests = [
},
},
{
info: "Verifying we can get a payload while submission is disabled",
event: "RequestCurrentPayload",
payloadType: "payload",
info: "Verifying that we can get the current ping data while submission is disabled",
event: "RequestCurrentPingData",
payloadType: "telemetry-current-ping-data",
validateResponse: function(payload) {
return validatePayload(payload);
return validateCurrentTelemetryPingData(payload);
},
},
{
@ -164,14 +143,6 @@ var tests = [
return checkSubmissionValue(payload, true);
},
},
{
info: "Verifying we can get a payload after re-enabling",
event: "RequestCurrentPayload",
payloadType: "payload",
validateResponse: function(payload) {
return validatePayload(payload);
},
},
{
info: "Verifying that we can get the current Telemetry environment data",
event: "RequestCurrentEnvironment",

View File

@ -454,7 +454,7 @@ file, You can obtain one at http://mozilla.org/MPL/2.0/.
<body><![CDATA[
let engine =
Services.search.getEngineByName(action.params.engineName);
BrowserSearch.recordSearchInHealthReport(engine, "urlbar");
BrowserSearch.recordSearchInTelemetry(engine, "urlbar");
let query = action.params.searchSuggestion ||
action.params.searchQuery;
let submission = engine.getSubmission(query, null, "keyword");
@ -948,15 +948,12 @@ file, You can obtain one at http://mozilla.org/MPL/2.0/.
<method name="handleDelete">
<body><![CDATA[
// When UnifiedComplete is enabled, we arrange for the popup to
// always have a "special" first item that's always selected. The
// autocomplete controller's handleDelete() implementation will
// remove the selected entry from the popup in that case.
// So when our first special item is selected, we call handleText
// instead so it acts as a delete on the text value instead of
// removing that item.
if (Services.prefs.getBoolPref("browser.urlbar.unifiedcomplete") &&
this.popup.selectedIndex == 0) {
// If the heuristic result is selected, then the autocomplete
// controller's handleDelete implementation will remove it, which is
// not what we want. So in that case, call handleText so it acts as
// a backspace on the text value instead of removing the result.
if (this.popup.selectedIndex == 0 &&
this.popup._isFirstResultHeuristic) {
return this.mController.handleText();
}
return this.mController.handleDelete();
@ -1264,13 +1261,9 @@ file, You can obtain one at http://mozilla.org/MPL/2.0/.
// ie, hitting page-down will only cause is to wrap if we're already
// at one end of the list.
// Do not allow the selection to be removed if UnifiedComplete is
// enabled and the popup's first result is a heuristic result.
if (!Services.prefs.getBoolPref("browser.urlbar.unifiedcomplete") ||
(this.input.mController.matchCount > 0 &&
this.input.mController
.getStyleAt(0)
.split(/\s+/).indexOf("heuristic") == -1)) {
// Allow the selection to be removed if the first result is not a
// heuristic result.
if (!this._isFirstResultHeuristic) {
if (reverse && index == -1 || newIndex > maxRow && index != maxRow)
newIndex = maxRow;
else if (!reverse && index == -1 || newIndex < 0 && index != 0)
@ -1282,16 +1275,30 @@ file, You can obtain one at http://mozilla.org/MPL/2.0/.
return newIndex;
}
// Otherwise do not allow the selection to be removed.
if (newIndex < 0) {
newIndex = index > 0 ? 0 : maxRow;
} else if (newIndex > maxRow) {
newIndex = index < maxRow ? maxRow : 0;
}
return newIndex;
]]></body>
</method>
<property name="_isFirstResultHeuristic" readonly="true">
<getter>
<![CDATA[
// The popup usually has a special "heuristic" first result (added
// by UnifiedComplete.js) that is automatically selected when the
// popup opens.
return this.input.mController.matchCount > 0 &&
this.input.mController
.getStyleAt(0)
.split(/\s+/).indexOf("heuristic") > 0;
]]>
</getter>
</property>
<property name="maxResults" readonly="true">
<getter>
<![CDATA[
@ -1526,20 +1533,17 @@ file, You can obtain one at http://mozilla.org/MPL/2.0/.
// If nothing is selected yet, select the first result if it is a
// pre-selected "heuristic" result. (See UnifiedComplete.js.)
if (this._matchCount > 0 && this.selectedIndex == -1) {
let styles = this.input.mController.getStyleAt(0).split(/\s+/);
if (styles.indexOf("heuristic") >= 0) {
// Don't handle this as a user-initiated action.
this._ignoreNextSelect = true;
if (this.selectedIndex == -1 && this._isFirstResultHeuristic) {
// Don't handle this as a user-initiated action.
this._ignoreNextSelect = true;
// Don't fire DOMMenuItemActive so that screen readers still see
// the input as being focused.
this.richlistbox.suppressMenuItemEvent = true;
// Don't fire DOMMenuItemActive so that screen readers still see
// the input as being focused.
this.richlistbox.suppressMenuItemEvent = true;
this.selectedIndex = 0;
this.richlistbox.suppressMenuItemEvent = false;
this._ignoreNextSelect = false;
}
this.selectedIndex = 0;
this.richlistbox.suppressMenuItemEvent = false;
this._ignoreNextSelect = false;
}
this.input.gotResultForCurrentQuery = true;

View File

@ -9,7 +9,6 @@ const Cr = Components.results;
const Cu = Components.utils;
const XULNS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
const POLARIS_ENABLED = "browser.polaris.enabled";
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");
@ -431,7 +430,7 @@ BrowserGlue.prototype = {
Cu.reportError(ex);
}
let win = RecentWindow.getMostRecentBrowserWindow();
win.BrowserSearch.recordSearchInHealthReport(engine, "urlbar");
win.BrowserSearch.recordSearchInTelemetry(engine, "urlbar");
break;
case "browser-search-engine-modified":
// Ensure we cleanup the hiddenOneOffs pref when removing
@ -447,23 +446,6 @@ BrowserGlue.prototype = {
hiddenList.join(","));
}
break;
#ifdef NIGHTLY_BUILD
case "nsPref:changed":
if (data == POLARIS_ENABLED) {
let enabled = Services.prefs.getBoolPref(POLARIS_ENABLED);
if (enabled) {
Services.prefs.setBoolPref("privacy.donottrackheader.enabled", enabled);
Services.prefs.setBoolPref("privacy.trackingprotection.enabled", enabled);
Services.prefs.setBoolPref("privacy.trackingprotection.ui.enabled", enabled);
} else {
// Don't reset DNT because its visible pref is independent of
// Polaris and may have been previously set.
Services.prefs.clearUserPref("privacy.trackingprotection.enabled");
Services.prefs.clearUserPref("privacy.trackingprotection.ui.enabled");
}
}
break;
#endif
case "flash-plugin-hang":
this._handleFlashHang();
break;
@ -634,9 +616,6 @@ BrowserGlue.prototype = {
os.removeObserver(this, "keyword-search");
#endif
os.removeObserver(this, "browser-search-engine-modified");
#ifdef NIGHTLY_BUILD
Services.prefs.removeObserver(POLARIS_ENABLED, this);
#endif
os.removeObserver(this, "flash-plugin-hang");
os.removeObserver(this, "xpi-signature-changed");
os.removeObserver(this, "autocomplete-did-enter-text");
@ -821,10 +800,6 @@ BrowserGlue.prototype = {
SelfSupportBackend.init();
#ifdef NIGHTLY_BUILD
Services.prefs.addObserver(POLARIS_ENABLED, this, false);
#endif
#ifndef RELEASE_BUILD
let themeName = gBrowserBundle.GetStringFromName("deveditionTheme.name");
let vendorShortName = gBrandBundle.GetStringFromName("vendorShortName");

View File

@ -7,6 +7,8 @@ Components.utils.import("resource://gre/modules/DownloadUtils.jsm");
Components.utils.import("resource://gre/modules/LoadContextInfo.jsm");
Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
const PREF_UPLOAD_ENABLED = "datareporting.healthreport.uploadEnabled";
var gAdvancedPane = {
_inited: false,
@ -289,38 +291,23 @@ var gAdvancedPane = {
initSubmitHealthReport: function () {
this._setupLearnMoreLink("datareporting.healthreport.infoURL", "FHRLearnMore");
let policy = Components.classes["@mozilla.org/datareporting/service;1"]
.getService(Components.interfaces.nsISupports)
.wrappedJSObject
.policy;
let checkbox = document.getElementById("submitHealthReportBox");
if (!policy || policy.healthReportUploadLocked) {
if (Services.prefs.prefIsLocked(PREF_UPLOAD_ENABLED)) {
checkbox.setAttribute("disabled", "true");
return;
}
checkbox.checked = policy.healthReportUploadEnabled;
checkbox.checked = Services.prefs.getBoolPref(PREF_UPLOAD_ENABLED);
this.setTelemetrySectionEnabled(checkbox.checked);
},
/**
* Update the health report policy acceptance with state from checkbox.
* Update the health report preference with state from checkbox.
*/
updateSubmitHealthReport: function () {
let policy = Components.classes["@mozilla.org/datareporting/service;1"]
.getService(Components.interfaces.nsISupports)
.wrappedJSObject
.policy;
if (!policy) {
return;
}
let checkbox = document.getElementById("submitHealthReportBox");
policy.recordHealthReportUploadEnabled(checkbox.checked,
"Checkbox from preferences pane");
Services.prefs.setBoolPref(PREF_UPLOAD_ENABLED, checkbox.checked);
this.setTelemetrySectionEnabled(checkbox.checked);
},
#endif

View File

@ -8,7 +8,7 @@ browser.jar:
content/browser/preferences/in-content/subdialogs.js
* content/browser/preferences/in-content/main.js
* content/browser/preferences/in-content/privacy.js
content/browser/preferences/in-content/privacy.js
* content/browser/preferences/in-content/advanced.js
* content/browser/preferences/in-content/applications.js
content/browser/preferences/in-content/content.js

View File

@ -16,7 +16,6 @@ var gPrivacyPane = {
*/
_shouldPromptForRestart: true,
#ifdef NIGHTLY_BUILD
/**
* Show the Tracking Protection UI depending on the
* privacy.trackingprotection.ui.enabled pref, and linkify its Learn More link
@ -35,7 +34,6 @@ var gPrivacyPane = {
document.getElementById("trackingprotectionbox").hidden = false;
document.getElementById("trackingprotectionpbmbox").hidden = true;
},
#endif
/**
* Linkify the Learn More link of the Private Browsing Mode Tracking
@ -83,9 +81,7 @@ var gPrivacyPane = {
this.updateHistoryModePane();
this.updatePrivacyMicroControls();
this.initAutoStartPrivateBrowsingReverter();
#ifdef NIGHTLY_BUILD
this._initTrackingProtection();
#endif
this._initTrackingProtectionPBM();
this._initAutocomplete();
@ -510,7 +506,7 @@ var gPrivacyPane = {
acceptThirdPartyLabel.disabled = acceptThirdPartyMenu.disabled = !acceptCookies;
keepUntil.disabled = menu.disabled = this._autoStartPrivateBrowsing || !acceptCookies;
return acceptCookies;
},
@ -529,7 +525,7 @@ var gPrivacyPane = {
return accept.checked ? 0 : 2;
},
/**
* Converts between network.cookie.cookieBehavior and the third-party cookie UI
*/
@ -550,7 +546,7 @@ var gPrivacyPane = {
return undefined;
}
},
writeAcceptThirdPartyCookies: function ()
{
var accept = document.getElementById("acceptThirdPartyMenu").selectedItem;

View File

@ -18,7 +18,7 @@ skip-if = os != "win" # This test tests the windows-specific app selection dialo
[browser_connection_bug388287.js]
[browser_cookies_exceptions.js]
[browser_healthreport.js]
skip-if = true || !healthreport || (os == 'linux' && debug) # Bug 1185403 for the "true"
skip-if = true || !healthreport # Bug 1185403 for the "true"
[browser_homepages_filter_aboutpreferences.js]
[browser_notifications_do_not_disturb.js]
[browser_permissions_urlFieldHidden.js]

View File

@ -3,6 +3,8 @@
"use strict";
const FHR_UPLOAD_ENABLED = "datareporting.healthreport.uploadEnabled";
function runPaneTest(fn) {
open_preferences((win) => {
let doc = win.document;
@ -10,14 +12,7 @@ function runPaneTest(fn) {
let advancedPrefs = doc.getElementById("advancedPrefs");
let tab = doc.getElementById("dataChoicesTab");
advancedPrefs.selectedTab = tab;
let policy = Components.classes["@mozilla.org/datareporting/service;1"]
.getService(Components.interfaces.nsISupports)
.wrappedJSObject
.policy;
ok(policy, "Policy object is defined.");
fn(win, doc, policy);
fn(win, doc);
});
}
@ -28,8 +23,9 @@ function test() {
runPaneTest(testBasic);
}
function testBasic(win, doc, policy) {
is(policy.healthReportUploadEnabled, true, "Health Report upload enabled on app first run.");
function testBasic(win, doc) {
is(Services.prefs.getBoolPref(FHR_UPLOAD_ENABLED), true,
"Health Report upload enabled on app first run.");
let checkbox = doc.getElementById("submitHealthReportBox");
ok(checkbox);
@ -37,28 +33,30 @@ function testBasic(win, doc, policy) {
checkbox.checked = false;
checkbox.doCommand();
is(policy.healthReportUploadEnabled, false, "Unchecking checkbox opts out of FHR upload.");
is(Services.prefs.getBoolPref(FHR_UPLOAD_ENABLED), false,
"Unchecking checkbox opts out of FHR upload.");
checkbox.checked = true;
checkbox.doCommand();
is(policy.healthReportUploadEnabled, true, "Checking checkbox allows FHR upload.");
is(Services.prefs.getBoolPref(FHR_UPLOAD_ENABLED), true,
"Checking checkbox allows FHR upload.");
win.close();
Services.prefs.lockPref("datareporting.healthreport.uploadEnabled");
Services.prefs.lockPref(FHR_UPLOAD_ENABLED);
runPaneTest(testUploadDisabled);
}
function testUploadDisabled(win, doc, policy) {
ok(policy.healthReportUploadLocked, "Upload enabled flag is locked.");
function testUploadDisabled(win, doc) {
ok(Services.prefs.prefIsLocked(FHR_UPLOAD_ENABLED), "Upload enabled flag is locked.");
let checkbox = doc.getElementById("submitHealthReportBox");
is(checkbox.getAttribute("disabled"), "true", "Checkbox is disabled if upload flag is locked.");
policy._healthReportPrefs.unlock("uploadEnabled");
Services.prefs.unlockPref(FHR_UPLOAD_ENABLED);
win.close();
finish();
}
function resetPreferences() {
Services.prefs.clearUserPref("datareporting.healthreport.uploadEnabled");
Services.prefs.clearUserPref(FHR_UPLOAD_ENABLED);
}

View File

@ -420,7 +420,7 @@
if (telemetrySearchDetails && telemetrySearchDetails.index == -1) {
telemetrySearchDetails = null;
}
BrowserSearch.recordSearchInHealthReport(engine, "searchbar", telemetrySearchDetails);
BrowserSearch.recordSearchInTelemetry(engine, "searchbar", telemetrySearchDetails);
// null parameter below specifies HTML response for search
let params = {
postData: submission.postData,

View File

@ -3,75 +3,50 @@
"use strict";
var Preferences = Cu.import("resource://gre/modules/Preferences.jsm", {}).Preferences;
function test() {
requestLongerTimeout(2);
waitForExplicitFinish();
resetPreferences();
try {
let cm = Components.classes["@mozilla.org/categorymanager;1"]
.getService(Components.interfaces.nsICategoryManager);
cm.getCategoryEntry("healthreport-js-provider-default", "SearchesProvider");
} catch (ex) {
// Health Report disabled, or no SearchesProvider.
// We need a test or else we'll be marked as failure.
ok(true, "Firefox Health Report is not enabled.");
finish();
return;
}
function testTelemetry() {
// Find the right bucket for the "Foo" engine.
let engine = Services.search.getEngineByName("Foo");
let histogramKey = (engine.identifier || "other-Foo") + ".searchbar";
let numSearchesBefore = 0;
try {
let hs = Services.telemetry.getKeyedHistogramById("SEARCH_COUNTS").snapshot();
if (histogramKey in hs) {
numSearchesBefore = hs[histogramKey].sum;
}
} catch (ex) {
// No searches performed yet, not a problem, |numSearchesBefore| is 0.
}
function testFHR() {
let reporter = Components.classes["@mozilla.org/datareporting/service;1"]
.getService()
.wrappedJSObject
.healthReporter;
ok(reporter, "Health Reporter available.");
reporter.onInit().then(function onInit() {
let provider = reporter.getProvider("org.mozilla.searches");
let m = provider.getMeasurement("counts", 3);
// Now perform a search and ensure the count is incremented.
let tab = gBrowser.addTab();
gBrowser.selectedTab = tab;
let searchBar = BrowserSearch.searchBar;
m.getValues().then(function onData(data) {
let now = new Date();
let oldCount = 0;
searchBar.value = "firefox health report";
searchBar.focus();
function afterSearch() {
searchBar.value = "";
gBrowser.removeTab(tab);
// Make sure that the context searches are correctly recorded.
let hs = Services.telemetry.getKeyedHistogramById("SEARCH_COUNTS").snapshot();
Assert.ok(histogramKey in hs, "The histogram must contain the correct key");
Assert.equal(hs[histogramKey].sum, numSearchesBefore + 1,
"Performing a search increments the related SEARCH_COUNTS key by 1.");
// Find the right bucket for the "Foo" engine.
let engine = Services.search.getEngineByName("Foo");
let field = (engine.identifier || "other-Foo") + ".searchbar";
Services.search.removeEngine(engine);
}
if (data.days.hasDay(now)) {
let day = data.days.getDay(now);
if (day.has(field)) {
oldCount = day.get(field);
}
}
// Now perform a search and ensure the count is incremented.
let tab = gBrowser.addTab();
gBrowser.selectedTab = tab;
let searchBar = BrowserSearch.searchBar;
searchBar.value = "firefox health report";
searchBar.focus();
function afterSearch() {
searchBar.value = "";
gBrowser.removeTab(tab);
m.getValues().then(function onData(data) {
ok(data.days.hasDay(now), "Have data for today.");
let day = data.days.getDay(now);
is(day.get(field), oldCount + 1, "Performing a search increments FHR count by 1.");
let engine = Services.search.getEngineByName("Foo");
Services.search.removeEngine(engine);
});
}
EventUtils.synthesizeKey("VK_RETURN", {});
executeSoon(() => executeSoon(afterSearch));
});
});
EventUtils.synthesizeKey("VK_RETURN", {});
executeSoon(() => executeSoon(afterSearch));
}
function observer(subject, topic, data) {
@ -84,7 +59,7 @@ function test() {
case "engine-current":
is(Services.search.currentEngine.name, "Foo", "Current engine is Foo");
testFHR();
testTelemetry();
break;
case "engine-removed":
@ -101,9 +76,6 @@ function test() {
}
function resetPreferences() {
let service = Components.classes["@mozilla.org/datareporting/service;1"]
.getService(Components.interfaces.nsISupports)
.wrappedJSObject;
service.policy._prefs.resetBranch("datareporting.policy.");
service.policy.dataSubmissionPolicyBypassNotification = true;
Preferences.resetBranch("datareporting.policy.");
Preferences.set("datareporting.policy.dataSubmissionPolicyBypassNotification", true);
}

View File

@ -12,24 +12,6 @@ Cu.import("resource://gre/modules/Preferences.jsm");
const PREF_FHR_UPLOAD_ENABLED = "datareporting.healthreport.uploadEnabled";
XPCOMUtils.defineLazyGetter(this, "gPolicy", () => {
try {
return Cc["@mozilla.org/datareporting/service;1"]
.getService(Ci.nsISupports)
.wrappedJSObject
.policy;
} catch (e) {
return undefined;
}
});
XPCOMUtils.defineLazyGetter(this, "reporter", () => {
return Cc["@mozilla.org/datareporting/service;1"]
.getService(Ci.nsISupports)
.wrappedJSObject
.healthReporter;
});
XPCOMUtils.defineLazyModuleGetter(this, "TelemetryArchive",
"resource://gre/modules/TelemetryArchive.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "TelemetryEnvironment",
@ -53,45 +35,13 @@ MozSelfSupportInterface.prototype = {
},
get healthReportDataSubmissionEnabled() {
if (gPolicy) {
return gPolicy.healthReportUploadEnabled;
}
// The datareporting service is unavailable or disabled.
return Preferences.get(PREF_FHR_UPLOAD_ENABLED, false);
},
set healthReportDataSubmissionEnabled(enabled) {
if (gPolicy) {
let reason = "Self-support interface sent " +
(enabled ? "opt-in" : "opt-out") +
" command.";
gPolicy.recordHealthReportUploadEnabled(enabled, reason);
return;
}
// The datareporting service is unavailable or disabled.
Preferences.set(PREF_FHR_UPLOAD_ENABLED, enabled);
},
getHealthReportPayload: function () {
return new this._window.Promise(function (aResolve, aReject) {
if (reporter) {
let resolvePayload = function () {
reporter.collectAndObtainJSONPayload(true).then(aResolve, aReject);
};
if (reporter.initialized) {
resolvePayload();
} else {
reporter.onInit().then(resolvePayload, aReject);
}
} else {
aReject(new Error("No reporter"));
}
}.bind(this));
},
resetPref: function(name) {
Services.prefs.clearUserPref(name);
},

View File

@ -2,5 +2,3 @@
[browser_bug538331.js]
skip-if = e10s # Bug ?????? - child process crash, but only when run as part of the suite (ie, probably not actually this tests fault!?)
[browser_polaris_prefs.js]

View File

@ -1,89 +0,0 @@
const POLARIS_ENABLED = "browser.polaris.enabled";
const PREF_DNT = "privacy.donottrackheader.enabled";
const PREF_TP = "privacy.trackingprotection.enabled";
const PREF_TPUI = "privacy.trackingprotection.ui.enabled";
var prefs = [PREF_DNT, PREF_TP, PREF_TPUI];
function spinEventLoop() {
return new Promise((resolve) => executeSoon(resolve));
};
// Spin event loop before checking so that polaris pref observer can set
// dependent prefs.
function* assertPref(pref, enabled) {
yield spinEventLoop();
let prefEnabled = Services.prefs.getBoolPref(pref);
Assert.equal(prefEnabled, enabled, "Checking state of pref " + pref + ".");
};
function* testPrefs(test) {
for (let pref of prefs) {
yield test(pref);
}
}
function isNightly() {
return Services.appinfo.version.includes("a1");
}
add_task(function* test_default_values() {
if (!isNightly()) {
ok(true, "Skipping test, not Nightly")
return;
}
Assert.ok(!Services.prefs.getBoolPref(POLARIS_ENABLED), POLARIS_ENABLED + " is disabled by default.");
Assert.ok(!Services.prefs.getBoolPref(PREF_TPUI), PREF_TPUI + "is disabled by default.");
});
add_task(function* test_changing_pref_changes_tracking() {
if (!isNightly()) {
ok(true, "Skipping test, not Nightly")
return;
}
// Register a cleanup function for all the prefs affected by this entire test file.
registerCleanupFunction(function () {
Services.prefs.clearUserPref(POLARIS_ENABLED);
for (let pref of prefs) {
Services.prefs.clearUserPref(pref);
}
});
function* testPref(pref) {
Services.prefs.setBoolPref(POLARIS_ENABLED, true);
yield assertPref(pref, true);
Services.prefs.setBoolPref(POLARIS_ENABLED, false);
// We don't clear the DNT pref if Polaris is disabled.
if (pref != PREF_DNT) {
yield assertPref(pref, false);
} else {
yield assertPref(pref, true);
}
Services.prefs.setBoolPref(POLARIS_ENABLED, true);
yield assertPref(pref, true);
}
yield testPrefs(testPref);
});
add_task(function* test_prefs_can_be_changed_individually() {
if (!isNightly()) {
ok(true, "Skipping test, not Nightly")
return;
}
function* testPref(pref) {
Services.prefs.setBoolPref(POLARIS_ENABLED, true);
yield assertPref(pref, true);
Services.prefs.setBoolPref(pref, false);
yield assertPref(pref, false);
yield assertPref(POLARIS_ENABLED, true);
Services.prefs.setBoolPref(POLARIS_ENABLED, false);
yield assertPref(pref, false);
Services.prefs.setBoolPref(pref, true);
yield assertPref(pref, true);
yield assertPref(POLARIS_ENABLED, false);
}
yield testPrefs(testPref);
});

View File

@ -31,7 +31,6 @@ MOZ_SAFE_BROWSING=1
MOZ_SERVICES_COMMON=1
MOZ_SERVICES_CRYPTO=1
MOZ_SERVICES_HEALTHREPORT=1
MOZ_SERVICES_METRICS=1
MOZ_SERVICES_SYNC=1
MOZ_SERVICES_CLOUDSYNC=1
MOZ_APP_VERSION=$FIREFOX_VERSION

View File

@ -19,7 +19,6 @@ XPCOMUtils.defineLazyModuleGetter(this, "CommonUtils",
const PREF_EXPERIMENTS_ENABLED = "experiments.enabled";
const PREF_ACTIVE_EXPERIMENT = "experiments.activeExperiment"; // whether we have an active experiment
const PREF_HEALTHREPORT_ENABLED = "datareporting.healthreport.service.enabled";
const PREF_TELEMETRY_ENABLED = "toolkit.telemetry.enabled";
const PREF_TELEMETRY_UNIFIED = "toolkit.telemetry.unified";
const DELAY_INIT_MS = 30 * 1000;
@ -38,8 +37,7 @@ XPCOMUtils.defineLazyGetter(
// We can enable experiments if either unified Telemetry or FHR is on, and the user
// has opted into Telemetry.
return gPrefs.get(PREF_EXPERIMENTS_ENABLED, false) &&
(gPrefs.get(PREF_HEALTHREPORT_ENABLED, false) || IS_UNIFIED_TELEMETRY) &&
gPrefs.get(PREF_TELEMETRY_ENABLED, false);
IS_UNIFIED_TELEMETRY && gPrefs.get(PREF_TELEMETRY_ENABLED, false);
});
XPCOMUtils.defineLazyGetter(

View File

@ -14,7 +14,7 @@ FIREFOX_PREFERENCES = {
"devtools.debugger.prompt-connection": False,
"devtools.debugger.remote-enabled": True,
"media.volume_scale": "0",
"loop.gettingStarted.latestFTUVersion": 0,
"loop.gettingStarted.latestFTUVersion": 1,
# this dialog is fragile, and likely to introduce intermittent failures
"media.navigator.permission.disabled": True,

View File

@ -263,41 +263,41 @@ class Test1BrowserCall(MarionetteTestCase):
def test_1_browser_call(self):
self.switch_to_panel()
# self.local_start_a_conversation()
self.local_start_a_conversation()
# # Check the self video in the conversation window
# self.local_check_room_self_video()
# Check the self video in the conversation window
self.local_check_room_self_video()
# # make sure that the media start time is not initialized
# self.local_check_media_start_time_uninitialized()
# make sure that the media start time is not initialized
self.local_check_media_start_time_uninitialized()
# room_url = self.local_get_and_verify_room_url()
room_url = self.local_get_and_verify_room_url()
# # load the link clicker interface into the current content browser
# self.standalone_load_and_join_room(room_url)
# load the link clicker interface into the current content browser
self.standalone_load_and_join_room(room_url)
# # Check we get the video streams
# self.standalone_check_remote_video()
# self.local_check_remote_video()
# Check we get the video streams
self.standalone_check_remote_video()
self.local_check_remote_video()
# # Check text messaging
# self.check_text_messaging()
# Check text messaging
self.check_text_messaging()
# # since bi-directional media is connected, make sure we've set
# # the start time
# self.local_check_media_start_time_initialized()
# since bi-directional media is connected, make sure we've set
# the start time
self.local_check_media_start_time_initialized()
# # Check that screenshare was automatically started
# self.standalone_check_remote_screenshare()
# Check that screenshare was automatically started
self.standalone_check_remote_screenshare()
# # We hangup on the remote (standalone) side, because this also leaves
# # the local chatbox with the local publishing media still connected,
# # which means that the local_check_connection_length below
# # verifies that the connection is noted at the time the remote media
# # drops, rather than waiting until the window closes.
# self.remote_leave_room()
# We hangup on the remote (standalone) side, because this also leaves
# the local chatbox with the local publishing media still connected,
# which means that the local_check_connection_length below
# verifies that the connection is noted at the time the remote media
# drops, rather than waiting until the window closes.
self.remote_leave_room()
# self.local_check_connection_length_noted()
self.local_check_connection_length_noted()
def tearDown(self):
self.loop_test_servers.shutdown()

View File

@ -498,12 +498,7 @@
@RESPATH@/components/nsINIProcessor.js
@RESPATH@/components/nsPrompter.manifest
@RESPATH@/components/nsPrompter.js
#ifdef MOZ_DATA_REPORTING
@RESPATH@/components/DataReporting.manifest
@RESPATH@/components/DataReportingService.js
#endif
#ifdef MOZ_SERVICES_HEALTHREPORT
@RESPATH@/components/HealthReportComponents.manifest
@RESPATH@/browser/components/SelfSupportService.manifest
@RESPATH@/browser/components/SelfSupportService.js
#endif

View File

@ -303,8 +303,8 @@ this.ContentSearch = {
};
win.openUILinkIn(submission.uri.spec, where, params);
}
win.BrowserSearch.recordSearchInHealthReport(engine, data.healthReportKey,
data.selection || null);
win.BrowserSearch.recordSearchInTelemetry(engine, data.healthReportKey,
data.selection || null);
return Promise.resolve();
},

View File

@ -23,8 +23,6 @@ XPCOMUtils.defineLazyModuleGetter(this, "HiddenFrame",
const PREF_ENABLED = "browser.selfsupport.enabled";
// Url to open in the Self Support browser, in the urlFormatter service format.
const PREF_URL = "browser.selfsupport.url";
// FHR status.
const PREF_FHR_ENABLED = "datareporting.healthreport.service.enabled";
// Unified Telemetry status.
const PREF_TELEMETRY_UNIFIED = "toolkit.telemetry.unified";
// UITour status.
@ -84,8 +82,8 @@ var SelfSupportBackendInternal = {
Preferences.observe(PREF_BRANCH_LOG, this._configureLogging, this);
// Only allow to use SelfSupport if either FHR or Unified Telemetry is enabled.
let reportingEnabled = Preferences.get(PREF_FHR_ENABLED, false) || IS_UNIFIED_TELEMETRY;
// Only allow to use SelfSupport if Unified Telemetry is enabled.
let reportingEnabled = IS_UNIFIED_TELEMETRY;
if (!reportingEnabled) {
this._log.config("init - Disabling SelfSupport because FHR and Unified Telemetry are disabled.");
return;

View File

@ -10,7 +10,7 @@ var toolbox;
add_task(function* themeRegistration() {
let tab = yield addTab("data:text/html,test");
let target = TargetFactory.forTab(tab);
toolbox = yield gDevTools.showToolbox(target);
toolbox = yield gDevTools.showToolbox(target, "options");
let themeId = yield new Promise(resolve => {
gDevTools.once("theme-registered", (e, themeId) => {
@ -31,9 +31,6 @@ add_task(function* themeRegistration() {
});
add_task(function* themeInOptionsPanel() {
yield toolbox.selectTool("options");
let panel = toolbox.getCurrentPanel();
let panelWin = toolbox.getCurrentPanel().panelWin;
let doc = panelWin.frameElement.contentDocument;

View File

@ -65,15 +65,6 @@
}
let oldThemeDef = gDevTools.getThemeDefinition(oldTheme);
// Unload all theme stylesheets related to the old theme.
if (oldThemeDef) {
for (let sheet of devtoolsStyleSheets.get(oldThemeDef) || []) {
sheet.remove();
}
}
// Load all stylesheets associated with the new theme.
let newThemeDef = gDevTools.getThemeDefinition(newTheme);
// The theme might not be available anymore (e.g. uninstalled)
@ -110,28 +101,35 @@
forceStyle();
}
if (oldThemeDef) {
for (let name of oldThemeDef.classList) {
documentElement.classList.remove(name);
Promise.all(loadEvents).then(() => {
// Unload all stylesheets and classes from the old theme.
if (oldThemeDef) {
for (let name of oldThemeDef.classList) {
documentElement.classList.remove(name);
}
for (let sheet of devtoolsStyleSheets.get(oldThemeDef) || []) {
sheet.remove();
}
if (oldThemeDef.onUnapply) {
oldThemeDef.onUnapply(window, newTheme);
}
}
if (oldThemeDef.onUnapply) {
oldThemeDef.onUnapply(window, newTheme);
// Load all stylesheets and classes from the new theme.
for (let name of newThemeDef.classList) {
documentElement.classList.add(name);
}
}
for (let name of newThemeDef.classList) {
documentElement.classList.add(name);
}
if (newThemeDef.onApply) {
newThemeDef.onApply(window, oldTheme);
}
if (newThemeDef.onApply) {
newThemeDef.onApply(window, oldTheme);
}
// Final notification for further theme-switching related logic.
gDevTools.emit("theme-switched", window, newTheme, oldTheme);
Promise.all(loadEvents).then(notifyWindow, console.error.bind(console));
// Final notification for further theme-switching related logic.
gDevTools.emit("theme-switched", window, newTheme, oldTheme);
notifyWindow();
}, console.error.bind(console));
}
function handlePrefChange(event, data) {

View File

@ -73,6 +73,15 @@ var inputTests = [
inspectorIcon: true
},
{
input: "testLotsOfAttributes()",
output: '<p n="" m="" l="" k="" j="" i="" h="" g="" f="" e="" d="" c="" b="" a="" id="lots-of-attributes">',
printOutput: "[object HTMLParagraphElement]",
inspectable: true,
noClick: true,
inspectorIcon: true
},
{
input: "testDocumentFragment()",
output: "DocumentFragment [ <span.foo>, <div#fragdiv> ]",
@ -99,15 +108,6 @@ var inputTests = [
noClick: true,
inspectorIcon: false
},
{
input: "testLotsOfAttributes()",
output: '<p n="" m="" l="" k="" j="" i="" h="" g="" f="" e="" d="" c="" b="" a="" id="lots-of-attributes">',
printOutput: "[object HTMLParagraphElement]",
inspectable: true,
noClick: true,
inspectorIcon: true
}
];
function test() {

View File

@ -222,6 +222,7 @@ MeasuringToolHighlighter.prototype = {
pageListenerTarget.removeEventListener("mouseup", this);
pageListenerTarget.removeEventListener("scroll", this);
pageListenerTarget.removeEventListener("pagehide", this);
pageListenerTarget.removeEventListener("mouseleave", this);
this.markup.destroy();

View File

@ -51,7 +51,7 @@ LoadContext::LoadContext(nsIPrincipal* aPrincipal,
#endif
{
PrincipalOriginAttributes poa = BasePrincipal::Cast(aPrincipal)->OriginAttributesRef();
mOriginAttributes = DocShellOriginAttributes(poa.mAppId, poa.mInBrowser);
mOriginAttributes.InheritFromDocToChildDocShell(poa);
if (!aOptionalBase) {
return;

View File

@ -1288,7 +1288,7 @@ BluetoothServiceBluedroid::PinReplyInternal(
ENSURE_BLUETOOTH_IS_ENABLED_VOID(aRunnable);
if (aAccept) {
if (aAccept && aPinCode.mLength) {
sBtCoreInterface->PinReply(aDeviceAddress, aAccept, aPinCode,
new PinReplyResultHandler(aRunnable));
} else {

View File

@ -20,26 +20,6 @@ interface MozSelfSupport
*/
attribute boolean healthReportDataSubmissionEnabled;
/**
* Retrieves the FHR payload object, which is of the form:
*
* {
* version: Number,
* clientID: String,
* clientIDVersion: Number,
* thisPingDate: String,
* geckoAppInfo: Object,
* data: Object
* }
*
* Refer to the getJSONPayload function in healthreporter.jsm for more
* information.
*
* @return Promise<Object>
* Resolved when the FHR payload data has been collected.
*/
Promise<object> getHealthReportPayload();
/**
* Retrieve a list of the archived Telemetry pings.
* This contains objects with ping info, which are of the form:

View File

@ -12,7 +12,6 @@ MOZ_PLACES=1
MOZ_EXTENSIONS_DEFAULT=" gio"
MOZ_SERVICES_COMMON=1
MOZ_SERVICES_CRYPTO=1
MOZ_SERVICES_METRICS=1
MOZ_SERVICES_SYNC=1
MOZ_MEDIA_NAVIGATOR=1
MOZ_SERVICES_HEALTHREPORT=1

View File

@ -428,11 +428,6 @@
@BINPATH@/components/PeerConnection.manifest
#endif
#ifdef MOZ_SERVICES_HEALTHREPORT
@BINPATH@/components/HealthReportComponents.manifest
@BINPATH@/components/HealthReportService.js
#endif
@BINPATH@/components/CaptivePortalDetectComponents.manifest
@BINPATH@/components/captivedetect.js

View File

@ -16,7 +16,6 @@
android:targetSdkVersion="22"/>
#include ../services/manifests/FxAccountAndroidManifest_permissions.xml.in
#include ../services/manifests/HealthReportAndroidManifest_permissions.xml.in
#include ../services/manifests/SyncAndroidManifest_permissions.xml.in
#ifdef MOZ_ANDROID_SEARCH_ACTIVITY
@ -357,7 +356,6 @@
</receiver>
#include ../services/manifests/FxAccountAndroidManifest_activities.xml.in
#include ../services/manifests/HealthReportAndroidManifest_activities.xml.in
#include ../services/manifests/SyncAndroidManifest_activities.xml.in
#ifdef MOZ_ANDROID_SEARCH_ACTIVITY
#include ../search/manifests/SearchAndroidManifest_activities.xml.in
@ -465,7 +463,6 @@
#include ../services/manifests/FxAccountAndroidManifest_services.xml.in
#include ../services/manifests/HealthReportAndroidManifest_services.xml.in
#include ../services/manifests/SyncAndroidManifest_services.xml.in
<service

View File

@ -765,10 +765,6 @@ sync_thirdparty_java_files = [
sync_java_files = [TOPSRCDIR + '/mobile/android/services/src/main/java/org/mozilla/gecko/' + x for x in [
'background/BackgroundService.java',
'background/bagheera/BagheeraClient.java',
'background/bagheera/BagheeraRequestDelegate.java',
'background/bagheera/BoundedByteArrayEntity.java',
'background/bagheera/DeflateHelper.java',
'background/common/DateUtils.java',
'background/common/EditorBranch.java',
'background/common/GlobalConstants.java',
@ -805,31 +801,6 @@ sync_java_files = [TOPSRCDIR + '/mobile/android/services/src/main/java/org/mozil
'background/fxa/profile/FxAccountProfileClient10.java',
'background/fxa/QuickPasswordStretcher.java',
'background/fxa/SkewHandler.java',
'background/healthreport/AndroidConfigurationProvider.java',
'background/healthreport/Environment.java',
'background/healthreport/EnvironmentBuilder.java',
'background/healthreport/EnvironmentV1.java',
'background/healthreport/EnvironmentV2.java',
'background/healthreport/HealthReportBroadcastReceiver.java',
'background/healthreport/HealthReportBroadcastService.java',
'background/healthreport/HealthReportConstants.java',
'background/healthreport/HealthReportDatabases.java',
'background/healthreport/HealthReportDatabaseStorage.java',
'background/healthreport/HealthReportExportedBroadcastReceiver.java',
'background/healthreport/HealthReportGenerator.java',
'background/healthreport/HealthReportProvider.java',
'background/healthreport/HealthReportStorage.java',
'background/healthreport/HealthReportUtils.java',
'background/healthreport/ProfileInformationCache.java',
'background/healthreport/prune/HealthReportPruneService.java',
'background/healthreport/prune/PrunePolicy.java',
'background/healthreport/prune/PrunePolicyDatabaseStorage.java',
'background/healthreport/prune/PrunePolicyStorage.java',
'background/healthreport/upload/AndroidSubmissionClient.java',
'background/healthreport/upload/HealthReportUploadService.java',
'background/healthreport/upload/ObsoleteDocumentTracker.java',
'background/healthreport/upload/SubmissionClient.java',
'background/healthreport/upload/SubmissionPolicy.java',
'background/nativecode/NativeCrypto.java',
'background/preferences/PreferenceFragment.java',
'background/preferences/PreferenceManagerCompat.java',

View File

@ -30,8 +30,6 @@ import org.mozilla.gecko.firstrun.FirstrunAnimationContainer;
import org.mozilla.gecko.gfx.DynamicToolbarAnimator;
import org.mozilla.gecko.gfx.ImmutableViewportMetrics;
import org.mozilla.gecko.gfx.LayerView;
import org.mozilla.gecko.health.BrowserHealthRecorder;
import org.mozilla.gecko.health.BrowserHealthReporter;
import org.mozilla.gecko.health.HealthRecorder;
import org.mozilla.gecko.health.SessionInformation;
import org.mozilla.gecko.home.BrowserSearch;
@ -256,8 +254,6 @@ public class BrowserApp extends GeckoApp
private OrderedBroadcastHelper mOrderedBroadcastHelper;
private BrowserHealthReporter mBrowserHealthReporter;
private ReadingListHelper mReadingListHelper;
private AccountsHelper mAccountsHelper;
@ -700,7 +696,6 @@ public class BrowserApp extends GeckoApp
JavaAddonManager.getInstance().init(appContext);
mSharedPreferencesHelper = new SharedPreferencesHelper(appContext);
mOrderedBroadcastHelper = new OrderedBroadcastHelper(appContext);
mBrowserHealthReporter = new BrowserHealthReporter();
mReadingListHelper = new ReadingListHelper(appContext, getProfile(), this);
mAccountsHelper = new AccountsHelper(appContext, getProfile());
@ -1306,11 +1301,6 @@ public class BrowserApp extends GeckoApp
mOrderedBroadcastHelper = null;
}
if (mBrowserHealthReporter != null) {
mBrowserHealthReporter.uninit();
mBrowserHealthReporter = null;
}
if (mReadingListHelper != null) {
mReadingListHelper.uninit();
mReadingListHelper = null;
@ -2340,16 +2330,16 @@ public class BrowserApp extends GeckoApp
* {@link BrowserHealthRecorder#SEARCH_LOCATIONS}.
*/
private static void recordSearch(SearchEngine engine, String where) {
try {
String identifier = (engine == null) ? "other" : engine.getEngineIdentifier();
JSONObject message = new JSONObject();
message.put("type", BrowserHealthRecorder.EVENT_SEARCH);
message.put("location", where);
message.put("identifier", identifier);
EventDispatcher.getInstance().dispatchEvent(message, null);
} catch (Exception e) {
Log.e(LOGTAG, "Error recording search.", e);
}
//try {
// String identifier = (engine == null) ? "other" : engine.getEngineIdentifier();
// JSONObject message = new JSONObject();
// message.put("type", BrowserHealthRecorder.EVENT_SEARCH);
// message.put("location", where);
// message.put("identifier", identifier);
// EventDispatcher.getInstance().dispatchEvent(message, null);
//} catch (Exception e) {
// Log.e(LOGTAG, "Error recording search.", e);
//}
}
/**
@ -3865,22 +3855,6 @@ public class BrowserApp extends GeckoApp
mDynamicToolbar.setTemporarilyVisible(false, VisibilityTransition.IMMEDIATE);
}
@Override
protected HealthRecorder createHealthRecorder(final Context context,
final String profilePath,
final EventDispatcher dispatcher,
final String osLocale,
final String appLocale,
final SessionInformation previousSession) {
return new BrowserHealthRecorder(context,
GeckoSharedPrefs.forApp(context),
profilePath,
dispatcher,
osLocale,
appLocale,
previousSession);
}
public static interface Refreshable {
public void refresh();
}

View File

@ -2201,7 +2201,7 @@ public abstract class GeckoApp
final HealthRecorder rec = mHealthRecorder;
mHealthRecorder = null;
if (rec != null && rec.isEnabled()) {
// Closing a BrowserHealthRecorder could incur a write.
// Closing a HealthRecorder could incur a write.
ThreadUtils.postToBackgroundThread(new Runnable() {
@Override
public void run() {
@ -2793,7 +2793,7 @@ public abstract class GeckoApp
/**
* Use BrowserLocaleManager to change our persisted and current locales,
* and poke HealthRecorder to tell it of our changed state.
* and poke the system to tell it of our changed state.
*/
protected void setLocale(final String locale) {
if (locale == null) {

View File

@ -1,157 +0,0 @@
/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package org.mozilla.gecko.health;
import org.json.JSONException;
import org.json.JSONObject;
import org.mozilla.gecko.EventDispatcher;
import org.mozilla.gecko.GeckoAppShell;
import org.mozilla.gecko.GeckoEvent;
import org.mozilla.gecko.GeckoProfile;
import org.mozilla.gecko.background.common.GlobalConstants;
import org.mozilla.gecko.background.healthreport.AndroidConfigurationProvider;
import org.mozilla.gecko.background.healthreport.EnvironmentBuilder;
import org.mozilla.gecko.background.healthreport.EnvironmentBuilder.ConfigurationProvider;
import org.mozilla.gecko.background.healthreport.HealthReportConstants;
import org.mozilla.gecko.background.healthreport.HealthReportDatabaseStorage;
import org.mozilla.gecko.background.healthreport.HealthReportGenerator;
import org.mozilla.gecko.util.GeckoEventListener;
import org.mozilla.gecko.util.ThreadUtils;
import android.content.ContentProviderClient;
import android.content.Context;
import android.util.Log;
/**
* BrowserHealthReporter is the browser's interface to the Firefox Health
* Report report generator.
*
* Each instance registers Gecko event listeners, so keep a single instance
* around for the life of the browser. Java callers should use this globally
* available singleton.
*/
public class BrowserHealthReporter implements GeckoEventListener {
private static final String LOGTAG = "GeckoHealthRep";
public static final String EVENT_REQUEST = "HealthReport:Request";
public static final String EVENT_RESPONSE = "HealthReport:Response";
protected final Context context;
public BrowserHealthReporter() {
EventDispatcher.getInstance().registerGeckoThreadListener(this, EVENT_REQUEST);
context = GeckoAppShell.getContext();
if (context == null) {
throw new IllegalStateException("Null Gecko context");
}
}
public void uninit() {
EventDispatcher.getInstance().unregisterGeckoThreadListener(this, EVENT_REQUEST);
}
/**
* Generate a new Health Report.
*
* This method performs IO, so call it from a background thread.
*
* @param since timestamp of first day to report (milliseconds since epoch).
* @param lastPingTime timestamp when last health report was uploaded
* (milliseconds since epoch).
* @param profilePath path of the profile to generate report for.
* @throws JSONException if JSON generation fails.
* @throws IllegalStateException if the environment does not allow to generate a report.
* @return non-null report.
*/
public JSONObject generateReport(long since, long lastPingTime, String profilePath) throws JSONException {
// We abuse the life-cycle of an Android ContentProvider slightly by holding
// onto a ContentProviderClient while we generate a payload. This keeps
// our database storage alive, while also allowing us to share a database
// connection with BrowserHealthRecorder and the uploader.
// The ContentProvider owns all underlying Storage instances, so we don't
// need to explicitly close them.
ContentProviderClient client = EnvironmentBuilder.getContentProviderClient(context);
if (client == null) {
throw new IllegalStateException("Could not fetch Health Report content provider.");
}
try {
// Storage instance is owned by HealthReportProvider, so we don't need
// to close it.
HealthReportDatabaseStorage storage = EnvironmentBuilder.getStorage(client, profilePath);
if (storage == null) {
throw new IllegalStateException("No storage in Health Reporter.");
}
HealthReportGenerator generator = new HealthReportGenerator(storage);
ConfigurationProvider configProvider = new AndroidConfigurationProvider(context);
JSONObject report = generator.generateDocument(since, lastPingTime, profilePath, configProvider);
if (report == null) {
throw new IllegalStateException("Not enough profile information to generate report.");
}
return report;
} finally {
client.release();
}
}
/**
* Get last time a health report was successfully uploaded.
*
* This is read from shared preferences, so call it from a background
* thread. Bug 882182 tracks making this work with multiple profiles.
*
* @return milliseconds since the epoch, or 0 if never uploaded.
*/
protected long getLastUploadLocalTime() {
return context
.getSharedPreferences(HealthReportConstants.PREFS_BRANCH, 0)
.getLong(HealthReportConstants.PREF_LAST_UPLOAD_LOCAL_TIME, 0L);
}
/**
* Generate a new Health Report for the current Gecko profile.
*
* This method performs IO, so call it from a background thread.
*
* @throws JSONException if JSON generation fails.
* @throws IllegalStateException if the environment does not allow to generate a report.
* @return non-null Health Report.
*/
public JSONObject generateReport() throws JSONException {
GeckoProfile profile = GeckoAppShell.getGeckoInterface().getProfile();
String profilePath = profile.getDir().getAbsolutePath();
long since = System.currentTimeMillis() - GlobalConstants.MILLISECONDS_PER_SIX_MONTHS;
long lastPingTime = Math.max(getLastUploadLocalTime(), HealthReportConstants.EARLIEST_LAST_PING);
return generateReport(since, lastPingTime, profilePath);
}
@Override
public void handleMessage(String event, JSONObject message) {
try {
ThreadUtils.postToBackgroundThread(new Runnable() {
@Override
public void run() {
JSONObject report = null;
try {
report = generateReport(); // non-null if it returns.
} catch (Exception e) {
Log.e(LOGTAG, "Generating report failed; responding with empty report.", e);
report = new JSONObject();
}
GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent(EVENT_RESPONSE, report.toString()));
}
});
} catch (Exception e) {
Log.e(LOGTAG, "Exception handling message \"" + event + "\":", e);
}
}
}

View File

@ -29,7 +29,6 @@ import org.mozilla.gecko.Telemetry;
import org.mozilla.gecko.TelemetryContract;
import org.mozilla.gecko.TelemetryContract.Method;
import org.mozilla.gecko.background.common.GlobalConstants;
import org.mozilla.gecko.background.healthreport.HealthReportConstants;
import org.mozilla.gecko.db.BrowserContract.SuggestedSites;
import org.mozilla.gecko.restrictions.Restrictable;
import org.mozilla.gecko.tabqueue.TabQueueHelper;
@ -981,10 +980,10 @@ OnSharedPreferenceChangeListener
* <code>PREFS_HEALTHREPORT_UPLOAD_ENABLED</code> pref.
*/
public static void broadcastHealthReportUploadPref(final Context context, final boolean value) {
broadcastPrefAction(context,
HealthReportConstants.ACTION_HEALTHREPORT_UPLOAD_PREF,
PREFS_HEALTHREPORT_UPLOAD_ENABLED,
value);
//broadcastPrefAction(context,
// HealthReportConstants.ACTION_HEALTHREPORT_UPLOAD_PREF,
// PREFS_HEALTHREPORT_UPLOAD_ENABLED,
// value);
}
/**
@ -992,13 +991,13 @@ OnSharedPreferenceChangeListener
* <code>PREFS_HEALTHREPORT_UPLOAD_ENABLED</code> pref.
*/
public static void broadcastHealthReportUploadPref(final Context context) {
final boolean value = getBooleanPref(context, PREFS_HEALTHREPORT_UPLOAD_ENABLED, true);
broadcastHealthReportUploadPref(context, value);
//final boolean value = getBooleanPref(context, PREFS_HEALTHREPORT_UPLOAD_ENABLED, true);
//broadcastHealthReportUploadPref(context, value);
}
public static void broadcastHealthReportPrune(final Context context) {
final Intent intent = new Intent(HealthReportConstants.ACTION_HEALTHREPORT_PRUNE);
broadcastAction(context, intent);
//final Intent intent = new Intent(HealthReportConstants.ACTION_HEALTHREPORT_PRUNE);
//broadcastAction(context, intent);
}
/**

View File

@ -52,9 +52,11 @@ final class UnusedResourcesUtil {
};
public static final int[] USED_IN_SUGGESTEDSITES = {
R.drawable.suggestedsites_fxaddons,
R.drawable.suggestedsites_fxsupport,
R.drawable.suggestedsites_mozilla,
R.drawable.suggestedsites_amazon,
R.drawable.suggestedsites_facebook,
R.drawable.suggestedsites_twitter,
R.drawable.suggestedsites_wikipedia,
R.drawable.suggestedsites_youtube,
};
public static final int[] USED_IN_BOOKMARKDEFAULTS = {

View File

@ -352,8 +352,6 @@ gbjar.sources += ['java/org/mozilla/gecko/' + x for x in [
'gfx/VirtualLayer.java',
'GlobalHistory.java',
'GuestSession.java',
'health/BrowserHealthRecorder.java',
'health/BrowserHealthReporter.java',
'health/HealthRecorder.java',
'health/SessionInformation.java',
'health/StubbedHealthRecorder.java',

View File

@ -492,7 +492,6 @@ var BrowserApp = {
NativeWindow.init();
FormAssistant.init();
IndexedDB.init();
HealthReportStatusListener.init();
XPInstallObserver.init();
CharacterEncoding.init();
ActivityObserver.init();
@ -4691,17 +4690,14 @@ Tab.prototype = {
}
this.contentDocumentIsDisplayed = true;
if (contentDocument instanceof Ci.nsIImageDocument) {
contentDocument.shrinkToFit();
}
let zoom = this.restoredSessionZoom();
if (zoom) {
this.setResolution(zoom, true);
}
if (!this.restoredSessionZoom() && contentDocument.mozSyntheticDocument) {
let fitZoom = Math.min(gScreenWidth / contentDocument.body.scrollWidth,
gScreenHeight / contentDocument.body.scrollHeight);
this.setResolution(fitZoom, false);
this.sendViewportUpdate(); // recompute displayport
}
}
break;
}
@ -5688,192 +5684,6 @@ var FormAssistant = {
}
};
/**
* An object to watch for Gecko status changes -- add-on installs, pref changes
* -- and reflect them back to Java.
*/
var HealthReportStatusListener = {
PREF_ACCEPT_LANG: "intl.accept_languages",
PREF_BLOCKLIST_ENABLED: "extensions.blocklist.enabled",
PREF_TELEMETRY_ENABLED: AppConstants.MOZ_TELEMETRY_REPORTING ?
"toolkit.telemetry.enabled" :
null,
init: function () {
try {
AddonManager.addAddonListener(this);
} catch (ex) {
dump("Failed to initialize add-on status listener. FHR cannot report add-on state. " + ex);
}
dump("Adding HealthReport:RequestSnapshot observer.");
Services.obs.addObserver(this, "HealthReport:RequestSnapshot", false);
Services.prefs.addObserver(this.PREF_ACCEPT_LANG, this, false);
Services.prefs.addObserver(this.PREF_BLOCKLIST_ENABLED, this, false);
if (this.PREF_TELEMETRY_ENABLED) {
Services.prefs.addObserver(this.PREF_TELEMETRY_ENABLED, this, false);
}
},
observe: function (aSubject, aTopic, aData) {
switch (aTopic) {
case "HealthReport:RequestSnapshot":
HealthReportStatusListener.sendSnapshotToJava();
break;
case "nsPref:changed":
let response = {
type: "Pref:Change",
pref: aData,
isUserSet: Services.prefs.prefHasUserValue(aData),
};
switch (aData) {
case this.PREF_ACCEPT_LANG:
response.value = Services.prefs.getCharPref(aData);
break;
case this.PREF_TELEMETRY_ENABLED:
case this.PREF_BLOCKLIST_ENABLED:
response.value = Services.prefs.getBoolPref(aData);
break;
default:
console.log("Unexpected pref in HealthReportStatusListener: " + aData);
return;
}
Messaging.sendRequest(response);
break;
}
},
MILLISECONDS_PER_DAY: 24 * 60 * 60 * 1000,
COPY_FIELDS: [
"blocklistState",
"userDisabled",
"appDisabled",
"version",
"type",
"scope",
"foreignInstall",
"hasBinaryComponents",
],
// Add-on types for which full details are recorded in FHR.
// All other types are ignored.
FULL_DETAIL_TYPES: [
"plugin",
"extension",
"service",
],
/**
* Return true if the add-on is not of a type for which we report full details.
* These add-ons will still make it over to Java, but will be filtered out.
*/
_shouldIgnore: function (aAddon) {
return this.FULL_DETAIL_TYPES.indexOf(aAddon.type) == -1;
},
_dateToDays: function (aDate) {
return Math.floor(aDate.getTime() / this.MILLISECONDS_PER_DAY);
},
jsonForAddon: function (aAddon) {
let o = {};
if (aAddon.installDate) {
o.installDay = this._dateToDays(aAddon.installDate);
}
if (aAddon.updateDate) {
o.updateDay = this._dateToDays(aAddon.updateDate);
}
for (let field of this.COPY_FIELDS) {
o[field] = aAddon[field];
}
return o;
},
notifyJava: function (aAddon, aNeedsRestart, aAction="Addons:Change") {
let json = this.jsonForAddon(aAddon);
if (this._shouldIgnore(aAddon)) {
json.ignore = true;
}
Messaging.sendRequest({ type: aAction, id: aAddon.id, json: json });
},
// Add-on listeners.
onEnabling: function (aAddon, aNeedsRestart) {
this.notifyJava(aAddon, aNeedsRestart);
},
onDisabling: function (aAddon, aNeedsRestart) {
this.notifyJava(aAddon, aNeedsRestart);
},
onInstalling: function (aAddon, aNeedsRestart) {
this.notifyJava(aAddon, aNeedsRestart);
},
onUninstalling: function (aAddon, aNeedsRestart) {
this.notifyJava(aAddon, aNeedsRestart, "Addons:Uninstalling");
},
onPropertyChanged: function (aAddon, aProperties) {
this.notifyJava(aAddon);
},
onOperationCancelled: function (aAddon) {
this.notifyJava(aAddon);
},
sendSnapshotToJava: function () {
AddonManager.getAllAddons(function (aAddons) {
let jsonA = {};
if (aAddons) {
for (let i = 0; i < aAddons.length; ++i) {
let addon = aAddons[i];
try {
let addonJSON = HealthReportStatusListener.jsonForAddon(addon);
if (HealthReportStatusListener._shouldIgnore(addon)) {
addonJSON.ignore = true;
}
jsonA[addon.id] = addonJSON;
} catch (e) {
// Just skip this add-on.
}
}
}
// Now add prefs.
let jsonP = {};
for (let pref of [this.PREF_BLOCKLIST_ENABLED, this.PREF_TELEMETRY_ENABLED]) {
if (!pref) {
// This will be the case for PREF_TELEMETRY_ENABLED in developer builds.
continue;
}
jsonP[pref] = {
pref: pref,
value: Services.prefs.getBoolPref(pref),
isUserSet: Services.prefs.prefHasUserValue(pref),
};
}
for (let pref of [this.PREF_ACCEPT_LANG]) {
jsonP[pref] = {
pref: pref,
value: Services.prefs.getCharPref(pref),
isUserSet: Services.prefs.prefHasUserValue(pref),
};
}
console.log("Sending snapshot message.");
Messaging.sendRequest({
type: "HealthReport:Snapshot",
json: {
addons: jsonA,
prefs: jsonP,
},
});
}.bind(this));
},
};
var XPInstallObserver = {
init: function() {
Services.obs.addObserver(this, "addon-install-origin-blocked", false);

View File

@ -203,8 +203,7 @@ function updateBanner(messages) {
icon: message.icon,
weight: message.weight,
onclick: function() {
let parentId = gChromeWin.BrowserApp.selectedTab.id;
gChromeWin.BrowserApp.addTab(message.url, { parentId: parentId });
gChromeWin.BrowserApp.loadURI(message.url);
UITelemetry.addEvent("action.1", "banner", null, message.id);
},
ondismiss: function() {

View File

@ -11,7 +11,6 @@ import org.mozilla.gecko.Telemetry;
import org.mozilla.gecko.TelemetryContract;
import org.mozilla.gecko.db.BrowserContract.SearchHistory;
import org.mozilla.gecko.distribution.Distribution;
import org.mozilla.gecko.health.BrowserHealthRecorder;
import org.mozilla.search.autocomplete.SearchBar;
import org.mozilla.search.autocomplete.SuggestionsFragment;
import org.mozilla.search.providers.SearchEngine;
@ -255,7 +254,7 @@ public class SearchActivity extends Locales.LocaleAwareFragmentActivity
storeQuery(query);
try {
BrowserHealthRecorder.recordSearchDelayed("activity", engine.getIdentifier());
//BrowserHealthRecorder.recordSearchDelayed("activity", engine.getIdentifier());
} catch (Exception e) {
// This should never happen: it'll only throw if the
// search location is wrong. But let's not tempt fate.

View File

@ -1,41 +0,0 @@
<provider android:name="org.mozilla.gecko.background.healthreport.HealthReportProvider"
android:authorities="@ANDROID_PACKAGE_NAME@.health"
android:exported="false">
</provider>
<!-- HealthReportBroadcastReceiver$ExportedReceiver is a thin receiver
whose purpose is to start the background service in response to
system events. It's exported so that it can receive system events.
Such events cannot specify Health Report settings.
-->
<receiver
android:name="org.mozilla.gecko.background.healthreport.HealthReportExportedBroadcastReceiver"
android:exported="true">
<intent-filter>
<!-- Startup. -->
<action android:name="android.intent.action.BOOT_COMPLETED" />
</intent-filter>
<intent-filter>
<!-- SD card remounted. -->
<action android:name="android.intent.action.EXTERNAL_APPLICATIONS_AVAILABLE" />
</intent-filter>
</receiver>
<!-- HealthReportBroadcastReceiver is a thin receiver whose purpose is
to start the background service in response to events internal to
Health Report. Such events can specify Health Report settings, so
these intents must come from a trusted source; hence, this receiver
is not exported.
-->
<receiver
android:name="org.mozilla.gecko.background.healthreport.HealthReportBroadcastReceiver"
android:exported="false">
<intent-filter >
<!-- Toggle Health Report upload service alarm (based on preferences value) -->
<action android:name="@ANDROID_PACKAGE_NAME@.HEALTHREPORT_UPLOAD_PREF" />
</intent-filter>
<intent-filter >
<!-- Enable Health Report prune service alarm -->
<action android:name="@ANDROID_PACKAGE_NAME@.HEALTHREPORT_PRUNE" />
</intent-filter>
</receiver>

View File

@ -1,5 +0,0 @@
<!-- So we can start our service. -->
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<!-- So we can receive messages from Fennec. -->
<uses-permission android:name="@ANDROID_PACKAGE_NAME@.permission.PER_ANDROID_PACKAGE" />

View File

@ -1,17 +0,0 @@
<!-- BroadcastService responds to external events and starts
the other background services. We don't export any of
these services, since they are only started by components
internal to the Fennec package.
-->
<service
android:exported="false"
android:name="org.mozilla.gecko.background.healthreport.HealthReportBroadcastService" >
</service>
<service
android:exported="false"
android:name="org.mozilla.gecko.background.healthreport.upload.HealthReportUploadService" >
</service>
<service
android:exported="false"
android:name="org.mozilla.gecko.background.healthreport.prune.HealthReportPruneService" >
</service>

View File

@ -29,3 +29,5 @@
<permission
android:name="@ANDROID_PACKAGE_NAME@.permission.PER_ANDROID_PACKAGE"
android:protectionLevel="signature"/>
<uses-permission android:name="@ANDROID_PACKAGE_NAME@.permission.PER_ANDROID_PACKAGE" />

View File

@ -1,258 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package org.mozilla.gecko.background.bagheera;
import java.io.IOException;
import java.net.URISyntaxException;
import java.security.GeneralSecurityException;
import java.util.Collection;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import java.util.regex.Pattern;
import org.mozilla.gecko.sync.Utils;
import org.mozilla.gecko.sync.net.BaseResource;
import org.mozilla.gecko.sync.net.BaseResourceDelegate;
import org.mozilla.gecko.sync.net.Resource;
import ch.boye.httpclientandroidlib.HttpEntity;
import ch.boye.httpclientandroidlib.HttpResponse;
import ch.boye.httpclientandroidlib.client.ClientProtocolException;
import ch.boye.httpclientandroidlib.client.methods.HttpRequestBase;
import ch.boye.httpclientandroidlib.impl.client.DefaultHttpClient;
import ch.boye.httpclientandroidlib.protocol.HTTP;
/**
* Provides encapsulated access to a Bagheera document server.
* The two permitted operations are:
* * Delete a document.
* * Upload a document, optionally deleting an expired document.
*/
public class BagheeraClient {
protected final String serverURI;
protected final Executor executor;
protected static final Pattern URI_PATTERN = Pattern.compile("^[a-zA-Z0-9_-]+$");
protected static String PROTOCOL_VERSION = "1.0";
protected static String SUBMIT_PATH = "/submit/";
/**
* Instantiate a new client pointing at the provided server.
* {@link #deleteDocument(String, String, BagheeraRequestDelegate)} and
* {@link #uploadJSONDocument(String, String, String, String, BagheeraRequestDelegate)}
* both accept delegate arguments; the {@link Executor} provided to this
* constructor will be used to invoke callbacks on those delegates.
*
* @param serverURI
* the destination server URI.
* @param executor
* the executor which will be used to invoke delegate callbacks.
*/
public BagheeraClient(final String serverURI, final Executor executor) {
if (serverURI == null) {
throw new IllegalArgumentException("Must provide a server URI.");
}
if (executor == null) {
throw new IllegalArgumentException("Must provide a non-null executor.");
}
this.serverURI = serverURI.endsWith("/") ? serverURI : serverURI + "/";
this.executor = executor;
}
/**
* Instantiate a new client pointing at the provided server.
* Delegate callbacks will be invoked on a new background thread.
*
* See {@link #BagheeraClient(String, Executor)} for more details.
*
* @param serverURI
* the destination server URI.
*/
public BagheeraClient(final String serverURI) {
this(serverURI, Executors.newSingleThreadExecutor());
}
/**
* Delete the specified document from the server.
* The delegate's callbacks will be invoked by the BagheeraClient's executor.
*/
public void deleteDocument(final String namespace,
final String id,
final BagheeraRequestDelegate delegate) throws URISyntaxException {
if (namespace == null) {
throw new IllegalArgumentException("Must provide namespace.");
}
if (id == null) {
throw new IllegalArgumentException("Must provide id.");
}
final BaseResource resource = makeResource(namespace, id);
resource.delegate = new BagheeraResourceDelegate(resource, namespace, id, delegate);
resource.delete();
}
/**
* Upload a JSON document to a Bagheera server. The delegate's callbacks will
* be invoked in tasks run by the client's executor.
*
* @param namespace
* the namespace, such as "test"
* @param id
* the document ID, which is typically a UUID.
* @param payload
* a document, typically JSON-encoded.
* @param oldIDs
* an optional collection of IDs which denote documents to supersede. Can be null or empty.
* @param delegate
* the delegate whose methods should be invoked on success or
* failure.
*/
public void uploadJSONDocument(final String namespace,
final String id,
final String payload,
Collection<String> oldIDs,
final BagheeraRequestDelegate delegate) throws URISyntaxException {
if (namespace == null) {
throw new IllegalArgumentException("Must provide namespace.");
}
if (id == null) {
throw new IllegalArgumentException("Must provide id.");
}
if (payload == null) {
throw new IllegalArgumentException("Must provide payload.");
}
final BaseResource resource = makeResource(namespace, id);
final HttpEntity deflatedBody = DeflateHelper.deflateBody(payload);
resource.delegate = new BagheeraUploadResourceDelegate(resource, namespace, id, oldIDs, delegate);
resource.post(deflatedBody);
}
public static boolean isValidURIComponent(final String in) {
return URI_PATTERN.matcher(in).matches();
}
protected BaseResource makeResource(final String namespace, final String id) throws URISyntaxException {
if (!isValidURIComponent(namespace)) {
throw new URISyntaxException(namespace, "Illegal namespace name. Must be alphanumeric + [_-].");
}
if (!isValidURIComponent(id)) {
throw new URISyntaxException(id, "Illegal id value. Must be alphanumeric + [_-].");
}
final String uri = this.serverURI + PROTOCOL_VERSION + SUBMIT_PATH +
namespace + "/" + id;
return new BaseResource(uri);
}
public class BagheeraResourceDelegate extends BaseResourceDelegate {
private static final int DEFAULT_SOCKET_TIMEOUT_MSEC = 5 * 60 * 1000; // Five minutes.
protected final BagheeraRequestDelegate delegate;
protected final String namespace;
protected final String id;
public BagheeraResourceDelegate(final Resource resource,
final String namespace,
final String id,
final BagheeraRequestDelegate delegate) {
super(resource);
this.namespace = namespace;
this.id = id;
this.delegate = delegate;
}
@Override
public String getUserAgent() {
return delegate.getUserAgent();
}
@Override
public int socketTimeout() {
return DEFAULT_SOCKET_TIMEOUT_MSEC;
}
@Override
public void handleHttpResponse(HttpResponse response) {
final int status = response.getStatusLine().getStatusCode();
switch (status) {
case 200:
case 201:
invokeHandleSuccess(status, response);
return;
default:
invokeHandleFailure(status, response);
}
}
protected void invokeHandleError(final Exception e) {
executor.execute(new Runnable() {
@Override
public void run() {
delegate.handleError(e);
}
});
}
protected void invokeHandleFailure(final int status, final HttpResponse response) {
executor.execute(new Runnable() {
@Override
public void run() {
delegate.handleFailure(status, namespace, response);
}
});
}
protected void invokeHandleSuccess(final int status, final HttpResponse response) {
executor.execute(new Runnable() {
@Override
public void run() {
delegate.handleSuccess(status, namespace, id, response);
}
});
}
@Override
public void handleHttpProtocolException(final ClientProtocolException e) {
invokeHandleError(e);
}
@Override
public void handleHttpIOException(IOException e) {
invokeHandleError(e);
}
@Override
public void handleTransportException(GeneralSecurityException e) {
invokeHandleError(e);
}
}
public final class BagheeraUploadResourceDelegate extends BagheeraResourceDelegate {
private static final String HEADER_OBSOLETE_DOCUMENT = "X-Obsolete-Document";
private static final String COMPRESSED_CONTENT_TYPE = "application/json+zlib; charset=utf-8";
protected final Collection<String> obsoleteDocumentIDs;
public BagheeraUploadResourceDelegate(Resource resource,
String namespace,
String id,
Collection<String> obsoleteDocumentIDs,
BagheeraRequestDelegate delegate) {
super(resource, namespace, id, delegate);
this.obsoleteDocumentIDs = obsoleteDocumentIDs;
}
@Override
public void addHeaders(HttpRequestBase request, DefaultHttpClient client) {
super.addHeaders(request, client);
request.setHeader(HTTP.CONTENT_TYPE, COMPRESSED_CONTENT_TYPE);
if (this.obsoleteDocumentIDs != null && this.obsoleteDocumentIDs.size() > 0) {
request.addHeader(HEADER_OBSOLETE_DOCUMENT, Utils.toCommaSeparatedString(this.obsoleteDocumentIDs));
}
}
}
}

View File

@ -1,15 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package org.mozilla.gecko.background.bagheera;
import ch.boye.httpclientandroidlib.HttpResponse;
public interface BagheeraRequestDelegate {
void handleSuccess(int status, String namespace, String id, HttpResponse response);
void handleError(Exception e);
void handleFailure(int status, String namespace, HttpResponse response);
public String getUserAgent();
}

View File

@ -1,88 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package org.mozilla.gecko.background.bagheera;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import ch.boye.httpclientandroidlib.entity.AbstractHttpEntity;
import ch.boye.httpclientandroidlib.entity.ByteArrayEntity;
/**
* An entity that acts like {@link ByteArrayEntity}, but exposes a window onto
* the byte array that is a subsection of the array. The purpose of this is to
* allow a smaller entity to be created without having to resize the source
* array.
*/
public class BoundedByteArrayEntity extends AbstractHttpEntity implements
Cloneable {
protected final byte[] content;
protected final int start;
protected final int end;
protected final int length;
/**
* Create a new entity that behaves exactly like a {@link ByteArrayEntity}
* created with a copy of <code>b</code> truncated to (
* <code>end - start</code>) bytes, starting at <code>start</code>.
*
* @param b the byte array to use.
* @param start the start index.
* @param end the end index.
*/
public BoundedByteArrayEntity(final byte[] b, final int start, final int end) {
if (b == null) {
throw new IllegalArgumentException("Source byte array may not be null.");
}
if (end < start ||
start < 0 ||
end < 0 ||
start > b.length ||
end > b.length) {
throw new IllegalArgumentException("Bounds out of range.");
}
this.content = b;
this.start = start;
this.end = end;
this.length = end - start;
}
@Override
public boolean isRepeatable() {
return true;
}
@Override
public long getContentLength() {
return this.length;
}
@Override
public InputStream getContent() {
return new ByteArrayInputStream(this.content, this.start, this.length);
}
@Override
public void writeTo(final OutputStream outstream) throws IOException {
if (outstream == null) {
throw new IllegalArgumentException("Output stream may not be null.");
}
outstream.write(this.content);
outstream.flush();
}
@Override
public boolean isStreaming() {
return false;
}
@Override
public Object clone() throws CloneNotSupportedException {
return super.clone();
}
}

View File

@ -1,77 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package org.mozilla.gecko.background.bagheera;
import java.io.UnsupportedEncodingException;
import java.util.zip.Deflater;
import ch.boye.httpclientandroidlib.HttpEntity;
public class DeflateHelper {
/**
* Conservative upper bound for zlib size, equivalent to the first few lines
* in zlib's deflateBound function.
*
* Includes zlib header.
*
* @param sourceLen
* the number of bytes to compress.
* @return the number of bytes to allocate for the compressed output.
*/
public static int deflateBound(final int sourceLen) {
return sourceLen + ((sourceLen + 7) >> 3) + ((sourceLen + 63) >> 6) + 5 + 6;
}
/**
* Deflate the input into the output array, returning the number of bytes
* written to output.
*/
public static int deflate(byte[] input, byte[] output) {
final Deflater deflater = new Deflater();
deflater.setInput(input);
deflater.finish();
final int length = deflater.deflate(output);
deflater.end();
return length;
}
/**
* Deflate the input, returning an HttpEntity that offers an accurate window
* on the output.
*
* Note that this method does not trim the output array. (Test code can use
* TestDeflation#deflateTrimmed(byte[]).)
*
* Trimming would be more efficient for long-term space use, but we expect this
* entity to be transient.
*
* Note also that deflate can require <b>more</b> space than the input.
* {@link #deflateBound(int)} tells us the most it will use.
*
* @param bytes the input to deflate.
* @return the deflated input as an entity.
*/
public static HttpEntity deflateBytes(final byte[] bytes) {
// We would like to use DeflaterInputStream here, but it's minSDK=9, and we
// still target 8. It would also force us to use chunked Transfer-Encoding,
// so perhaps it's for the best!
final byte[] out = new byte[deflateBound(bytes.length)];
final int outLength = deflate(bytes, out);
return new BoundedByteArrayEntity(out, 0, outLength);
}
public static HttpEntity deflateBody(final String payload) {
final byte[] bytes;
try {
bytes = payload.getBytes("UTF-8");
} catch (UnsupportedEncodingException ex) {
// This will never happen. Thanks, Java!
throw new RuntimeException(ex);
}
return deflateBytes(bytes);
}
}

View File

@ -23,15 +23,6 @@ public class GlobalConstants {
public static final int SHARED_PREFERENCES_MODE = 0;
// These are used to ask Fennec (via reflection) to send
// us a pref notification. This avoids us having to guess
// Fennec's prefs branch and pref name.
// Eventually Fennec might listen to startup notifications and
// do this automatically, but this will do for now. See Bug 800244.
public static String GECKO_PREFERENCES_CLASS = "org.mozilla.gecko.preferences.GeckoPreferences";
public static String GECKO_BROADCAST_HEALTHREPORT_UPLOAD_PREF_METHOD = "broadcastHealthReportUploadPref";
public static String GECKO_BROADCAST_HEALTHREPORT_PRUNE_METHOD = "broadcastHealthReportPrune";
// Common time values.
public static final long MILLISECONDS_PER_DAY = 24 * 60 * 60 * 1000;
public static final long MILLISECONDS_PER_SIX_MONTHS = 180 * MILLISECONDS_PER_DAY;

View File

@ -1,76 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package org.mozilla.gecko.background.healthreport;
import org.mozilla.gecko.background.healthreport.Environment.UIType;
import org.mozilla.gecko.background.healthreport.EnvironmentBuilder.ConfigurationProvider;
import org.mozilla.gecko.util.HardwareUtils;
import android.content.Context;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.util.DisplayMetrics;
public class AndroidConfigurationProvider implements ConfigurationProvider {
private static final float MILLIMETERS_PER_INCH = 25.4f;
private final Configuration configuration;
private final DisplayMetrics displayMetrics;
public AndroidConfigurationProvider(final Context context) {
final Resources resources = context.getResources();
this.configuration = resources.getConfiguration();
this.displayMetrics = resources.getDisplayMetrics();
HardwareUtils.init(context);
}
@Override
public boolean hasHardwareKeyboard() {
return configuration.keyboard != Configuration.KEYBOARD_NOKEYS;
}
@Override
public UIType getUIType() {
if (HardwareUtils.isLargeTablet()) {
return UIType.LARGE_TABLET;
}
if (HardwareUtils.isSmallTablet()) {
return UIType.SMALL_TABLET;
}
return UIType.DEFAULT;
}
@Override
public int getUIModeType() {
return configuration.uiMode & Configuration.UI_MODE_TYPE_MASK;
}
@Override
public int getScreenLayoutSize() {
return configuration.screenLayout & Configuration.SCREENLAYOUT_SIZE_MASK;
}
/**
* Calculate screen horizontal width, in millimeters.
* This is approximate, will be wrong on some devices, and
* most likely doesn't include screen area that the app doesn't own.
* http://stackoverflow.com/questions/2193457/is-there-a-way-to-determine-android-physical-screen-height-in-cm-or-inches
*/
@Override
public int getScreenXInMM() {
return Math.round((displayMetrics.widthPixels / displayMetrics.xdpi) * MILLIMETERS_PER_INCH);
}
/**
* @see #getScreenXInMM() for caveats.
*/
@Override
public int getScreenYInMM() {
return Math.round((displayMetrics.heightPixels / displayMetrics.ydpi) * MILLIMETERS_PER_INCH);
}
}

View File

@ -1,98 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package org.mozilla.gecko.background.healthreport;
/**
* This captures all of the details that define an 'environment' for FHR's purposes.
* Whenever this format changes, it'll be changing with a build ID, so no migration
* of values is needed.
*
* Unless you remove the build descriptors from the set, of course.
*
* Or store these in a database.
*
* Instances of this class should be considered "effectively immutable": control their
* scope such that clear creation/sharing boundaries exist. Once you've populated and
* registered an <code>Environment</code>, don't do so again; start from scratch.
*
*/
public abstract class Environment extends EnvironmentV2 {
// Version 2 adds osLocale, appLocale, acceptLangSet, and distribution.
// Version 3 adds device characteristics.
public static final int CURRENT_VERSION = 3;
public static enum UIType {
// Corresponds to the typical phone interface.
DEFAULT("default"),
// Corresponds to a device for which Fennec is displaying the large tablet UI.
LARGE_TABLET("largetablet"),
// Corresponds to a device for which Fennec is displaying the small tablet UI.
SMALL_TABLET("smalltablet");
private final String label;
private UIType(final String label) {
this.label = label;
}
public String toString() {
return this.label;
}
public static UIType fromLabel(final String label) {
for (UIType type : UIType.values()) {
if (type.label.equals(label)) {
return type;
}
}
throw new IllegalArgumentException("Bad enum value: " + label);
}
}
public UIType uiType = UIType.DEFAULT;
/**
* Mask of Configuration#uiMode. E.g., UI_MODE_TYPE_CAR.
*/
public int uiMode = 0; // UI_MODE_TYPE_UNDEFINED = 0
/**
* Computed physical dimensions in millimeters.
*/
public int screenXInMM;
public int screenYInMM;
/**
* One of the Configuration#SCREENLAYOUT_SIZE_* constants.
*/
public int screenLayout = 0; // SCREENLAYOUT_SIZE_UNDEFINED = 0
public boolean hasHardwareKeyboard;
public Environment() {
this(Environment.HashAppender.class);
}
public Environment(Class<? extends EnvironmentAppender> appenderClass) {
super(appenderClass);
version = CURRENT_VERSION;
}
@Override
protected void appendHash(EnvironmentAppender appender) {
super.appendHash(appender);
// v3.
appender.append(hasHardwareKeyboard ? 1 : 0);
appender.append(uiType.toString());
appender.append(uiMode);
appender.append(screenLayout);
appender.append(screenXInMM);
appender.append(screenYInMM);
}
}

View File

@ -1,189 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package org.mozilla.gecko.background.healthreport;
import java.util.Iterator;
import org.json.JSONObject;
import org.mozilla.gecko.AppConstants;
import org.mozilla.gecko.SysInfo;
import org.mozilla.gecko.background.common.GlobalConstants;
import org.mozilla.gecko.background.common.log.Logger;
import org.mozilla.gecko.background.healthreport.Environment.UIType;
import android.content.ContentProvider;
import android.content.ContentProviderClient;
import android.content.ContentResolver;
import android.content.Context;
/**
* Construct a HealthReport environment from the current running system.
*/
public class EnvironmentBuilder {
private static final String LOG_TAG = "GeckoEnvBuilder";
public static ContentProviderClient getContentProviderClient(Context context) {
ContentResolver cr = context.getContentResolver();
return cr.acquireContentProviderClient(HealthReportConstants.HEALTH_AUTHORITY);
}
/**
* Fetch the storage object associated with the provided
* {@link ContentProviderClient}. If no storage instance can be found --
* perhaps because the {@link ContentProvider} is running in a different
* process -- returns <code>null</code>. On success, the returned
* {@link HealthReportDatabaseStorage} instance is owned by the underlying
* {@link HealthReportProvider} and thus does not need to be closed by the
* caller.
*
* If the provider is not a {@link HealthReportProvider}, throws a
* {@link ClassCastException}, because that would be disastrous.
*/
public static HealthReportDatabaseStorage getStorage(ContentProviderClient cpc,
String profilePath) {
ContentProvider pr = cpc.getLocalContentProvider();
if (pr == null) {
Logger.error(LOG_TAG, "Unable to retrieve local content provider. Running in a different process?");
return null;
}
try {
return ((HealthReportProvider) pr).getProfileStorage(profilePath);
} catch (ClassCastException ex) {
Logger.error(LOG_TAG, "ContentProvider not a HealthReportProvider!", ex);
throw ex;
}
}
public static interface ProfileInformationProvider {
public boolean isBlocklistEnabled();
public boolean isTelemetryEnabled();
public boolean isAcceptLangUserSet();
public long getProfileCreationTime();
public String getDistributionString();
public String getOSLocale();
public String getAppLocale();
public JSONObject getAddonsJSON();
}
public static interface ConfigurationProvider {
public boolean hasHardwareKeyboard();
public UIType getUIType();
public int getUIModeType();
public int getScreenLayoutSize();
public int getScreenXInMM();
public int getScreenYInMM();
}
protected static void populateEnvironment(Environment e,
ProfileInformationProvider info,
ConfigurationProvider config) {
e.cpuCount = SysInfo.getCPUCount();
e.memoryMB = SysInfo.getMemSize();
e.appName = AppConstants.MOZ_APP_NAME;
e.appID = AppConstants.MOZ_APP_ID;
e.appVersion = AppConstants.MOZ_APP_VERSION;
e.appBuildID = AppConstants.MOZ_APP_BUILDID;
e.updateChannel = AppConstants.MOZ_UPDATE_CHANNEL;
e.vendor = AppConstants.MOZ_APP_VENDOR;
e.platformVersion = AppConstants.MOZILLA_VERSION;
e.platformBuildID = AppConstants.MOZ_APP_BUILDID;
e.xpcomabi = AppConstants.TARGET_XPCOM_ABI;
e.os = "Android";
e.architecture = SysInfo.getArchABI(); // Not just "arm".
e.sysName = SysInfo.getName();
e.sysVersion = SysInfo.getReleaseVersion();
e.profileCreation = (int) (info.getProfileCreationTime() / GlobalConstants.MILLISECONDS_PER_DAY);
// Corresponds to Gecko pref "extensions.blocklist.enabled".
e.isBlocklistEnabled = (info.isBlocklistEnabled() ? 1 : 0);
// Corresponds to Gecko pref "toolkit.telemetry.enabled".
e.isTelemetryEnabled = (info.isTelemetryEnabled() ? 1 : 0);
e.extensionCount = 0;
e.pluginCount = 0;
e.themeCount = 0;
JSONObject addons = info.getAddonsJSON();
if (addons != null) {
@SuppressWarnings("unchecked")
Iterator<String> it = addons.keys();
while (it.hasNext()) {
String key = it.next();
try {
JSONObject addon = addons.getJSONObject(key);
String type = addon.optString("type");
Logger.pii(LOG_TAG, "Add-on " + key + " is a " + type);
if ("extension".equals(type)) {
++e.extensionCount;
} else if ("plugin".equals(type)) {
++e.pluginCount;
} else if ("theme".equals(type)) {
++e.themeCount;
} else if ("service".equals(type)) {
// Later.
} else {
Logger.debug(LOG_TAG, "Unknown add-on type: " + type);
}
} catch (Exception ex) {
Logger.warn(LOG_TAG, "Failed to process add-on " + key, ex);
}
}
}
e.addons = addons;
// v2 environment fields.
e.distribution = info.getDistributionString();
e.osLocale = info.getOSLocale();
e.appLocale = info.getAppLocale();
e.acceptLangSet = info.isAcceptLangUserSet() ? 1 : 0;
// v3 environment fields.
e.hasHardwareKeyboard = config.hasHardwareKeyboard();
e.uiType = config.getUIType();
e.uiMode = config.getUIModeType();
e.screenLayout = config.getScreenLayoutSize();
e.screenXInMM = config.getScreenXInMM();
e.screenYInMM = config.getScreenYInMM();
}
/**
* Returns an {@link Environment} not linked to a storage instance, but
* populated with current field values.
*
* @param info a source of profile data
* @return the new {@link Environment}
*/
public static Environment getCurrentEnvironment(ProfileInformationProvider info, ConfigurationProvider config) {
Environment e = new Environment() {
@Override
public int register() {
return 0;
}
};
populateEnvironment(e, info, config);
return e;
}
/**
* @return the current environment's ID in the provided storage layer
*/
public static int registerCurrentEnvironment(final HealthReportStorage storage,
final ProfileInformationProvider info,
final ConfigurationProvider config) {
Environment e = storage.getEnvironment();
populateEnvironment(e, info, config);
e.register();
Logger.debug(LOG_TAG, "Registering current environment: " + e.getHash() + " = " + e.id);
return e.id;
}
}

View File

@ -1,270 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package org.mozilla.gecko.background.healthreport;
import java.io.UnsupportedEncodingException;
import java.security.NoSuchAlgorithmException;
import java.util.Iterator;
import java.util.SortedSet;
import org.json.JSONException;
import org.json.JSONObject;
import org.mozilla.apache.commons.codec.binary.Base64;
import org.mozilla.gecko.background.common.log.Logger;
import org.mozilla.gecko.background.nativecode.NativeCrypto;
public abstract class EnvironmentV1 {
private static final String LOG_TAG = "GeckoEnvironment";
private static final int VERSION = 1;
protected final Class<? extends EnvironmentAppender> appenderClass;
protected volatile String hash = null;
protected volatile int id = -1;
public int version = VERSION;
// org.mozilla.profile.age.
public int profileCreation;
// org.mozilla.sysinfo.sysinfo.
public int cpuCount;
public int memoryMB;
public String architecture;
public String sysName;
public String sysVersion; // Kernel.
// geckoAppInfo.
public String vendor;
public String appName;
public String appID;
public String appVersion;
public String appBuildID;
public String platformVersion;
public String platformBuildID;
public String os;
public String xpcomabi;
public String updateChannel;
// appinfo.
public int isBlocklistEnabled;
public int isTelemetryEnabled;
// org.mozilla.addons.active.
public JSONObject addons = null;
// org.mozilla.addons.counts.
public int extensionCount;
public int pluginCount;
public int themeCount;
/**
* We break out this interface in order to allow for testing -- pass in your
* own appender that just records strings, for example.
*/
public static abstract class EnvironmentAppender {
public abstract void append(String s);
public abstract void append(int v);
}
public static class HashAppender extends EnvironmentAppender {
private final StringBuilder builder;
public HashAppender() throws NoSuchAlgorithmException {
builder = new StringBuilder();
}
@Override
public void append(String s) {
builder.append((s == null) ? "null" : s);
}
@Override
public void append(int profileCreation) {
append(Integer.toString(profileCreation, 10));
}
@Override
public String toString() {
// We *could* use ASCII85 but the savings would be negated by the
// inclusion of JSON-unsafe characters like double-quote.
final byte[] inputBytes;
try {
inputBytes = builder.toString().getBytes("UTF-8");
} catch (UnsupportedEncodingException e) {
Logger.warn(LOG_TAG, "Invalid charset String passed to getBytes", e);
return null;
}
// Note to the security-minded reader: we deliberately use SHA-1 here, not
// a stronger hash. These identifiers don't strictly need a cryptographic
// hash function, because there is negligible value in attacking the hash.
// We use SHA-1 because it's *shorter* -- the exact same reason that Git
// chose SHA-1.
final byte[] hash = NativeCrypto.sha1(inputBytes);
return new Base64(-1, null, false).encodeAsString(hash);
}
}
/**
* Ensure that the {@link Environment} has been registered with its
* storage layer, and can be used to annotate events.
*
* It's safe to call this method more than once, and each time you'll
* get the same ID.
*
* @return the integer ID to use in subsequent DB insertions.
*/
public abstract int register();
protected EnvironmentAppender getAppender() {
EnvironmentAppender appender = null;
try {
appender = appenderClass.newInstance();
} catch (InstantiationException | IllegalAccessException ex) {
// Should never happen, but...
Logger.warn(LOG_TAG, "Could not compute hash.", ex);
}
return appender;
}
protected void appendHash(EnvironmentAppender appender) {
appender.append(profileCreation);
appender.append(cpuCount);
appender.append(memoryMB);
appender.append(architecture);
appender.append(sysName);
appender.append(sysVersion);
appender.append(vendor);
appender.append(appName);
appender.append(appID);
appender.append(appVersion);
appender.append(appBuildID);
appender.append(platformVersion);
appender.append(platformBuildID);
appender.append(os);
appender.append(xpcomabi);
appender.append(updateChannel);
appender.append(isBlocklistEnabled);
appender.append(isTelemetryEnabled);
appender.append(extensionCount);
appender.append(pluginCount);
appender.append(themeCount);
// We need sorted values.
if (addons != null) {
appendSortedAddons(getNonIgnoredAddons(), appender);
}
}
/**
* Compute the stable hash of the configured environment.
*
* @return the hash in base34, or null if there was a problem.
*/
public String getHash() {
// It's never unset, so we only care about partial reads. volatile is enough.
if (hash != null) {
return hash;
}
EnvironmentAppender appender = getAppender();
if (appender == null) {
return null;
}
appendHash(appender);
return hash = appender.toString();
}
public EnvironmentV1(Class<? extends EnvironmentAppender> appenderClass) {
super();
this.appenderClass = appenderClass;
}
public JSONObject getNonIgnoredAddons() {
if (addons == null) {
return null;
}
JSONObject out = new JSONObject();
@SuppressWarnings("unchecked")
Iterator<String> keys = addons.keys();
while (keys.hasNext()) {
try {
final String key = keys.next();
final Object obj = addons.get(key);
if (obj != null &&
obj instanceof JSONObject &&
((JSONObject) obj).optBoolean("ignore", false)) {
continue;
}
out.put(key, obj);
} catch (JSONException ex) {
// Do nothing.
}
}
return out;
}
/**
* Take a collection of add-on descriptors, appending a consistent string
* to the provided builder.
*/
public static void appendSortedAddons(JSONObject addons, final EnvironmentAppender builder) {
final SortedSet<String> keys = HealthReportUtils.sortedKeySet(addons);
// For each add-on, produce a consistent, sorted mapping of its descriptor.
for (String key : keys) {
try {
JSONObject addon = addons.getJSONObject(key);
// Now produce the output for this add-on.
builder.append(key);
builder.append("={");
for (String addonKey : HealthReportUtils.sortedKeySet(addon)) {
builder.append(addonKey);
builder.append("==");
try {
builder.append(addon.get(addonKey).toString());
} catch (JSONException e) {
builder.append("_e_");
}
}
builder.append("}");
} catch (Exception e) {
// Muffle.
Logger.warn(LOG_TAG, "Invalid add-on for ID " + key);
}
}
}
public void setJSONForAddons(byte[] json) throws Exception {
setJSONForAddons(new String(json, "UTF-8"));
}
public void setJSONForAddons(String json) throws Exception {
if (json == null || "null".equals(json)) {
addons = null;
return;
}
addons = new JSONObject(json);
}
public void setJSONForAddons(JSONObject json) {
addons = json;
}
/**
* Includes ignored add-ons.
*/
public String getNormalizedAddonsJSON() {
// We trust that our input will already be normalized. If that assumption
// is invalidated, then we'll be sorry.
return (addons == null) ? "null" : addons.toString();
}
}

View File

@ -1,30 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package org.mozilla.gecko.background.healthreport;
public abstract class EnvironmentV2 extends EnvironmentV1 {
private static final int VERSION = 2;
public String osLocale;
public String appLocale;
public int acceptLangSet;
public String distribution;
public EnvironmentV2(Class<? extends EnvironmentAppender> appenderClass) {
super(appenderClass);
version = VERSION;
}
@Override
protected void appendHash(EnvironmentAppender appender) {
super.appendHash(appender);
// v2.
appender.append(osLocale);
appender.append(appLocale);
appender.append(acceptLangSet);
appender.append(distribution);
}
}

View File

@ -1,31 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package org.mozilla.gecko.background.healthreport;
import org.mozilla.gecko.background.common.log.Logger;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
/**
* Watch for internal notifications to start Health Report background services.
*/
public class HealthReportBroadcastReceiver extends BroadcastReceiver {
public static final String LOG_TAG = HealthReportBroadcastReceiver.class.getSimpleName();
/**
* Forward the intent (action and extras) to an IntentService to do background processing.
*/
@Override
public void onReceive(Context context, Intent intent) {
Logger.debug(LOG_TAG, "Received intent - forwarding to BroadcastService.");
Intent service = new Intent(context, HealthReportBroadcastService.class);
// It's safe to forward extras since these are internal intents.
service.putExtras(intent);
service.setAction(intent.getAction());
context.startService(service);
}
}

View File

@ -1,260 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package org.mozilla.gecko.background.healthreport;
import org.mozilla.gecko.background.BackgroundService;
import org.mozilla.gecko.background.common.GlobalConstants;
import org.mozilla.gecko.background.common.log.Logger;
import org.mozilla.gecko.background.healthreport.prune.HealthReportPruneService;
import org.mozilla.gecko.background.healthreport.upload.HealthReportUploadService;
import org.mozilla.gecko.background.healthreport.upload.ObsoleteDocumentTracker;
import android.app.AlarmManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
/**
* A service which listens to broadcast intents from the system and from the
* browser, registering or unregistering the background health report services with the
* {@link AlarmManager}.
*/
public class HealthReportBroadcastService extends BackgroundService {
public static final String LOG_TAG = HealthReportBroadcastService.class.getSimpleName();
public static final String WORKER_THREAD_NAME = LOG_TAG + "Worker";
public HealthReportBroadcastService() {
super(WORKER_THREAD_NAME);
}
protected SharedPreferences getSharedPreferences() {
return this.getSharedPreferences(HealthReportConstants.PREFS_BRANCH, GlobalConstants.SHARED_PREFERENCES_MODE);
}
public long getSubmissionPollInterval() {
return getSharedPreferences().getLong(HealthReportConstants.PREF_SUBMISSION_INTENT_INTERVAL_MSEC, HealthReportConstants.DEFAULT_SUBMISSION_INTENT_INTERVAL_MSEC);
}
public void setSubmissionPollInterval(final long interval) {
getSharedPreferences().edit().putLong(HealthReportConstants.PREF_SUBMISSION_INTENT_INTERVAL_MSEC, interval).commit();
}
public long getPrunePollInterval() {
return getSharedPreferences().getLong(HealthReportConstants.PREF_PRUNE_INTENT_INTERVAL_MSEC,
HealthReportConstants.DEFAULT_PRUNE_INTENT_INTERVAL_MSEC);
}
public void setPrunePollInterval(final long interval) {
getSharedPreferences().edit().putLong(HealthReportConstants.PREF_PRUNE_INTENT_INTERVAL_MSEC,
interval).commit();
}
/**
* Set or cancel an alarm to submit data for a profile.
*
* @param context
* Android context.
* @param profileName
* to submit data for.
* @param profilePath
* to submit data for.
* @param enabled
* whether the user has enabled submitting health report data for
* this profile.
* @param serviceEnabled
* whether submitting should be scheduled. If the user turns off
* submitting, <code>enabled</code> could be false but we could need
* to delete so <code>serviceEnabled</code> could be true.
*/
protected void toggleSubmissionAlarm(final Context context, String profileName, String profilePath,
boolean enabled, boolean serviceEnabled) {
final Class<?> serviceClass = HealthReportUploadService.class;
Logger.info(LOG_TAG, (serviceEnabled ? "R" : "Unr") + "egistering " +
serviceClass.getSimpleName() + ".");
// PendingIntents are compared without reference to their extras. Therefore
// even though we pass the profile details to the action, different
// profiles will share the *same* pending intent. In a multi-profile future,
// this will need to be addressed. See Bug 882182.
final Intent service = new Intent(context, serviceClass);
service.setAction("upload"); // PendingIntents "lose" their extras if no action is set.
service.putExtra("uploadEnabled", enabled);
service.putExtra("profileName", profileName);
service.putExtra("profilePath", profilePath);
final PendingIntent pending = PendingIntent.getService(context, 0, service, PendingIntent.FLAG_CANCEL_CURRENT);
if (!serviceEnabled) {
cancelAlarm(pending);
return;
}
final long pollInterval = getSubmissionPollInterval();
scheduleAlarm(pollInterval, pending);
}
@Override
protected void onHandleIntent(Intent intent) {
Logger.setThreadLogTag(HealthReportConstants.GLOBAL_LOG_TAG);
// Intent can be null. Bug 1025937.
if (intent == null) {
Logger.debug(LOG_TAG, "Short-circuiting on null intent.");
return;
}
// The same intent can be handled by multiple methods so do not short-circuit evaluate.
boolean handled = attemptHandleIntentForUpload(intent);
handled = attemptHandleIntentForPrune(intent) || handled;
if (!handled) {
Logger.warn(LOG_TAG, "Unhandled intent with action " + intent.getAction() + ".");
}
}
/**
* Attempts to handle the given intent for FHR document upload. If it cannot, false is returned.
*
* @param intent must be non-null.
*/
private boolean attemptHandleIntentForUpload(final Intent intent) {
if (HealthReportConstants.UPLOAD_FEATURE_DISABLED) {
Logger.debug(LOG_TAG, "Health report upload feature is compile-time disabled; not handling intent.");
return false;
}
final String action = intent.getAction();
Logger.debug(LOG_TAG, "Health report upload feature is compile-time enabled; attempting to " +
"handle intent with action " + action + ".");
if (HealthReportConstants.ACTION_HEALTHREPORT_UPLOAD_PREF.equals(action)) {
handleUploadPrefIntent(intent);
return true;
}
if (Intent.ACTION_BOOT_COMPLETED.equals(action) ||
Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE.equals(action)) {
BackgroundService.reflectContextToFennec(this,
GlobalConstants.GECKO_PREFERENCES_CLASS,
GlobalConstants.GECKO_BROADCAST_HEALTHREPORT_UPLOAD_PREF_METHOD);
return true;
}
return false;
}
/**
* Handle the intent sent by the browser when it wishes to notify us
* of the value of the user preference. Look at the value and toggle the
* alarm service accordingly.
*
* @param intent must be non-null.
*/
private void handleUploadPrefIntent(Intent intent) {
if (!intent.hasExtra("enabled")) {
Logger.warn(LOG_TAG, "Got " + HealthReportConstants.ACTION_HEALTHREPORT_UPLOAD_PREF + " intent without enabled. Ignoring.");
return;
}
final boolean enabled = intent.getBooleanExtra("enabled", true);
Logger.debug(LOG_TAG, intent.getStringExtra("branch") + "/" +
intent.getStringExtra("pref") + " = " +
(intent.hasExtra("enabled") ? enabled : ""));
String profileName = intent.getStringExtra("profileName");
String profilePath = intent.getStringExtra("profilePath");
if (profileName == null || profilePath == null) {
Logger.warn(LOG_TAG, "Got " + HealthReportConstants.ACTION_HEALTHREPORT_UPLOAD_PREF + " intent without profilePath or profileName. Ignoring.");
return;
}
Logger.pii(LOG_TAG, "Updating health report upload alarm for profile " + profileName + " at " +
profilePath + ".");
final SharedPreferences sharedPrefs = getSharedPreferences();
final ObsoleteDocumentTracker tracker = new ObsoleteDocumentTracker(sharedPrefs);
final boolean hasObsoleteIds = tracker.hasObsoleteIds();
if (!enabled) {
final Editor editor = sharedPrefs.edit();
editor.remove(HealthReportConstants.PREF_LAST_UPLOAD_DOCUMENT_ID);
if (hasObsoleteIds) {
Logger.debug(LOG_TAG, "Health report upload disabled; scheduling deletion of " + tracker.numberOfObsoleteIds() + " documents.");
tracker.limitObsoleteIds();
} else {
// Primarily intended for debugging and testing.
Logger.debug(LOG_TAG, "Health report upload disabled and no deletes to schedule: clearing prefs.");
editor.remove(HealthReportConstants.PREF_FIRST_RUN);
editor.remove(HealthReportConstants.PREF_NEXT_SUBMISSION);
}
editor.commit();
}
// The user can toggle us off or on, or we can have obsolete documents to
// remove.
final boolean serviceEnabled = hasObsoleteIds || enabled;
toggleSubmissionAlarm(this, profileName, profilePath, enabled, serviceEnabled);
}
/**
* Attempts to handle the given intent for FHR data pruning. If it cannot, false is returned.
*
* @param intent must be non-null.
*/
private boolean attemptHandleIntentForPrune(final Intent intent) {
final String action = intent.getAction();
Logger.debug(LOG_TAG, "Prune: Attempting to handle intent with action, " + action + ".");
if (HealthReportConstants.ACTION_HEALTHREPORT_PRUNE.equals(action)) {
handlePruneIntent(intent);
return true;
}
if (Intent.ACTION_BOOT_COMPLETED.equals(action) ||
Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE.equals(action)) {
BackgroundService.reflectContextToFennec(this,
GlobalConstants.GECKO_PREFERENCES_CLASS,
GlobalConstants.GECKO_BROADCAST_HEALTHREPORT_PRUNE_METHOD);
return true;
}
return false;
}
/**
* @param intent must be non-null.
*/
private void handlePruneIntent(final Intent intent) {
final String profileName = intent.getStringExtra("profileName");
final String profilePath = intent.getStringExtra("profilePath");
if (profileName == null || profilePath == null) {
Logger.warn(LOG_TAG, "Got " + HealthReportConstants.ACTION_HEALTHREPORT_PRUNE + " intent " +
"without profilePath or profileName. Ignoring.");
return;
}
final Class<?> serviceClass = HealthReportPruneService.class;
final Intent service = new Intent(this, serviceClass);
service.setAction("prune"); // Intents without actions have their extras removed.
service.putExtra("profileName", profileName);
service.putExtra("profilePath", profilePath);
final PendingIntent pending = PendingIntent.getService(this, 0, service,
PendingIntent.FLAG_CANCEL_CURRENT);
// Set a regular alarm to start PruneService. Since the various actions that PruneService can
// take occur on irregular intervals, we can be more efficient by only starting the Service
// when one of these time limits runs out. However, subsequent Service invocations must then
// be registered by the PruneService itself, which would fail if the PruneService crashes.
// Thus, we set this regular (and slightly inefficient) alarm.
Logger.info(LOG_TAG, "Registering " + serviceClass.getSimpleName() + ".");
final long pollInterval = getPrunePollInterval();
scheduleAlarm(pollInterval, pending);
}
}

View File

@ -1,128 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package org.mozilla.gecko.background.healthreport;
import org.mozilla.gecko.background.common.GlobalConstants;
import org.mozilla.gecko.AppConstants;
public class HealthReportConstants {
public static final String HEALTH_AUTHORITY = AppConstants.ANDROID_PACKAGE_NAME + ".health";
public static final String GLOBAL_LOG_TAG = "GeckoHealth";
public static final String USER_AGENT = "Firefox-Android-HealthReport/" + AppConstants.MOZ_APP_VERSION + " (" + AppConstants.MOZ_APP_UA_NAME + ")";
/**
* The earliest allowable value for the last ping time, corresponding to May 2nd 2013.
* Used for sanity checks.
*/
public static final long EARLIEST_LAST_PING = 1367500000000L;
// Not `final` so we have the option to turn this on at runtime with a magic addon.
public static boolean UPLOAD_FEATURE_DISABLED = false;
// Android SharedPreferences branch where global (not per-profile) uploader
// settings are stored.
public static final String PREFS_BRANCH = "background";
// How frequently the submission and prune policies are ticked over. This is how frequently our
// intent is scheduled to be called by the Android Alarm Manager, not how frequently we
// actually submit. These values are set as preferences rather than constants so that testing
// addons can change their values.
public static final String PREF_SUBMISSION_INTENT_INTERVAL_MSEC = "healthreport_submission_intent_interval_msec";
public static final long DEFAULT_SUBMISSION_INTENT_INTERVAL_MSEC = GlobalConstants.MILLISECONDS_PER_DAY / 24;
public static final String PREF_PRUNE_INTENT_INTERVAL_MSEC = "healthreport_prune_intent_interval_msec";
public static final long DEFAULT_PRUNE_INTENT_INTERVAL_MSEC = GlobalConstants.MILLISECONDS_PER_DAY;
public static final String ACTION_HEALTHREPORT_UPLOAD_PREF = AppConstants.ANDROID_PACKAGE_NAME + ".HEALTHREPORT_UPLOAD_PREF";
public static final String ACTION_HEALTHREPORT_PRUNE = AppConstants.ANDROID_PACKAGE_NAME + ".HEALTHREPORT_PRUNE";
public static final String PREF_MINIMUM_TIME_BETWEEN_UPLOADS = "healthreport_time_between_uploads";
public static final long DEFAULT_MINIMUM_TIME_BETWEEN_UPLOADS = GlobalConstants.MILLISECONDS_PER_DAY;
public static final String PREF_MINIMUM_TIME_BEFORE_FIRST_SUBMISSION = "healthreport_time_before_first_submission";
public static final long DEFAULT_MINIMUM_TIME_BEFORE_FIRST_SUBMISSION = GlobalConstants.MILLISECONDS_PER_DAY;
public static final String PREF_MINIMUM_TIME_AFTER_FAILURE = "healthreport_time_after_failure";
public static final long DEFAULT_MINIMUM_TIME_AFTER_FAILURE = DEFAULT_SUBMISSION_INTENT_INTERVAL_MSEC;
public static final String PREF_MAXIMUM_FAILURES_PER_DAY = "healthreport_maximum_failures_per_day";
public static final long DEFAULT_MAXIMUM_FAILURES_PER_DAY = 2;
// Authoritative.
public static final String PREF_FIRST_RUN = "healthreport_first_run";
public static final String PREF_NEXT_SUBMISSION = "healthreport_next_submission";
public static final String PREF_CURRENT_DAY_FAILURE_COUNT = "healthreport_current_day_failure_count";
public static final String PREF_CURRENT_DAY_RESET_TIME = "healthreport_current_day_reset_time";
// Forensic.
public static final String PREF_LAST_UPLOAD_REQUESTED = "healthreport_last_upload_requested";
public static final String PREF_LAST_UPLOAD_SUCCEEDED = "healthreport_last_upload_succeeded";
public static final String PREF_LAST_UPLOAD_FAILED = "healthreport_last_upload_failed";
// Preferences for deleting obsolete documents.
public static final String PREF_MINIMUM_TIME_BETWEEN_DELETES = "healthreport_time_between_deletes";
public static final long DEFAULT_MINIMUM_TIME_BETWEEN_DELETES = DEFAULT_SUBMISSION_INTENT_INTERVAL_MSEC;
public static final String PREF_OBSOLETE_DOCUMENT_IDS_TO_DELETION_ATTEMPTS_REMAINING = "healthreport_obsolete_document_ids_to_deletions_remaining";
// We don't want to try to delete forever, but we also don't want to orphan
// obsolete document IDs from devices that fail to reach the server for a few
// days. This tries to delete document IDs for at least one week (of upload
// failures). Note that if the device is really offline, no upload is
// performed and our count of attempts is not altered.
public static final long DELETION_ATTEMPTS_PER_OBSOLETE_DOCUMENT_ID = (DEFAULT_MAXIMUM_FAILURES_PER_DAY + 1) * 7;
// If we absolutely know that a document ID reached the server, we really
// don't want to orphan it. This tries to delete document IDs that will
// definitely be orphaned for at least six weeks (of upload failures). Note
// that if the device is really offline, no upload is performed and our count
// of attempts is not altered.
public static final long DELETION_ATTEMPTS_PER_KNOWN_TO_BE_ON_SERVER_DOCUMENT_ID = (DEFAULT_MAXIMUM_FAILURES_PER_DAY + 1) * 7 * 6;
// We don't want to allocate unbounded storage for obsolete IDs, but we also
// don't want to orphan obsolete document IDs from devices that fail to delete
// for a few days. This stores as many IDs as are expected to be generated in
// a month. Note that if the device is really offline, no upload is performed
// and our count of attempts is not altered.
public static final long MAXIMUM_STORED_OBSOLETE_DOCUMENT_IDS = (DEFAULT_MAXIMUM_FAILURES_PER_DAY + 1) * 30;
// Forensic.
public static final String PREF_LAST_DELETE_REQUESTED = "healthreport_last_delete_requested";
public static final String PREF_LAST_DELETE_SUCCEEDED = "healthreport_last_delete_succeeded";
public static final String PREF_LAST_DELETE_FAILED = "healthreport_last_delete_failed";
// Preferences for upload client.
public static final String PREF_LAST_UPLOAD_LOCAL_TIME = "healthreport_last_upload_local_time";
public static final String PREF_LAST_UPLOAD_DOCUMENT_ID = "healthreport_last_upload_document_id";
public static final String PREF_DOCUMENT_SERVER_URI = "healthreport_document_server_uri";
public static final String DEFAULT_DOCUMENT_SERVER_URI = "https://fhr.data.mozilla.com/";
public static final String PREF_DOCUMENT_SERVER_NAMESPACE = "healthreport_document_server_namespace";
public static final String DEFAULT_DOCUMENT_SERVER_NAMESPACE = "metrics";
// One UUID is 36 characters (like e56542e0-e4d2-11e2-a28f-0800200c9a66), so
// we limit the number of obsolete IDs passed so that each request is not a
// large upload (and therefore more likely to fail). We also don't want to
// push Bagheera to make too many deletes, since we don't know how the cluster
// will handle such API usage. This obsoletes 2 days worth of old documents
// at a time.
public static final int MAXIMUM_DELETIONS_PER_POST = ((int) DEFAULT_MAXIMUM_FAILURES_PER_DAY + 1) * 2;
public static final String PREF_PRUNE_BY_SIZE_TIME = "healthreport_prune_by_size_time";
public static final long MINIMUM_TIME_BETWEEN_PRUNE_BY_SIZE_CHECKS_MILLIS =
GlobalConstants.MILLISECONDS_PER_DAY;
public static final int MAX_ENVIRONMENT_COUNT = 50;
public static final int ENVIRONMENT_COUNT_AFTER_PRUNE = 35;
public static final int MAX_EVENT_COUNT = 10000;
public static final int EVENT_COUNT_AFTER_PRUNE = 8000;
public static final String PREF_EXPIRATION_TIME = "healthreport_expiration_time";
public static final long MINIMUM_TIME_BETWEEN_EXPIRATION_CHECKS_MILLIS = GlobalConstants.MILLISECONDS_PER_DAY * 7;
public static final long EVENT_EXISTENCE_DURATION = GlobalConstants.MILLISECONDS_PER_SIX_MONTHS;
public static final String PREF_CLEANUP_TIME = "healthreport_cleanup_time";
public static final long MINIMUM_TIME_BETWEEN_CLEANUP_CHECKS_MILLIS = GlobalConstants.MILLISECONDS_PER_DAY * 30;
}

View File

@ -1,53 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package org.mozilla.gecko.background.healthreport;
import java.io.File;
import java.util.HashMap;
import org.mozilla.gecko.background.common.log.Logger;
import android.content.Context;
/**
* Manages a set of per-profile Health Report storage helpers.
*/
public class HealthReportDatabases {
private static final String LOG_TAG = "HealthReportDatabases";
private final Context context;
private final HashMap<File, HealthReportDatabaseStorage> storages = new HashMap<File, HealthReportDatabaseStorage>();
public HealthReportDatabases(final Context context) {
this.context = context;
}
public synchronized HealthReportDatabaseStorage getDatabaseHelperForProfile(final File profileDir) {
if (profileDir == null) {
throw new IllegalArgumentException("No profile provided.");
}
if (this.storages.containsKey(profileDir)) {
return this.storages.get(profileDir);
}
final HealthReportDatabaseStorage helper;
helper = new HealthReportDatabaseStorage(this.context, profileDir);
this.storages.put(profileDir, helper);
return helper;
}
public synchronized void closeDatabaseHelpers() {
for (HealthReportDatabaseStorage helper : storages.values()) {
try {
helper.close();
} catch (Exception e) {
Logger.warn(LOG_TAG, "Failed to close database helper.", e);
}
}
storages.clear();
}
}

View File

@ -1,42 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package org.mozilla.gecko.background.healthreport;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import org.mozilla.gecko.background.common.log.Logger;
/**
* Watch for external system notifications to start Health Report background services.
*
* Some observations:
*
* From the Android documentation: "Also note that as of Android 3.0 the user
* needs to have started the application at least once before your application
* can receive android.intent.action.BOOT_COMPLETED events."
*
* We really do want to launch on BOOT_COMPLETED, since it's possible for a user
* to run Firefox, shut down the phone, then power it on again on the same day.
* We want to submit a health report in this case, even though they haven't
* launched Firefox since boot.
*/
public class HealthReportExportedBroadcastReceiver extends BroadcastReceiver {
public static final String LOG_TAG = HealthReportExportedBroadcastReceiver.class.getSimpleName();
/**
* Forward the intent action to an IntentService to do background processing.
* We intentionally do not forward extras, since there are none needed from
* external events.
*/
@Override
public void onReceive(Context context, Intent intent) {
Logger.debug(LOG_TAG, "Received intent - forwarding to BroadcastService.");
final Intent service = new Intent(context, HealthReportBroadcastService.class);
// We intentionally copy only the intent action.
service.setAction(intent.getAction());
context.startService(service);
}
}

View File

@ -1,711 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package org.mozilla.gecko.background.healthreport;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
import org.json.JSONException;
import org.json.JSONObject;
import org.mozilla.gecko.background.common.DateUtils.DateFormatter;
import org.mozilla.gecko.background.common.log.Logger;
import org.mozilla.gecko.background.healthreport.EnvironmentBuilder.ConfigurationProvider;
import org.mozilla.gecko.background.healthreport.HealthReportStorage.Field;
import android.database.Cursor;
import android.util.SparseArray;
public class HealthReportGenerator {
private static final int PAYLOAD_VERSION = 3;
private static final String LOG_TAG = "GeckoHealthGen";
private final HealthReportStorage storage;
private final DateFormatter dateFormatter;
public HealthReportGenerator(HealthReportStorage storage) {
this.storage = storage;
this.dateFormatter = new DateFormatter();
}
@SuppressWarnings("static-method")
protected long now() {
return System.currentTimeMillis();
}
/**
* Ensure that you have initialized the Locale to your satisfaction
* prior to calling this method.
*
* @return null if no environment could be computed, or else the resulting document.
* @throws JSONException if there was an error adding environment data to the resulting document.
*/
public JSONObject generateDocument(long since, long lastPingTime, String profilePath, ConfigurationProvider config) throws JSONException {
Logger.info(LOG_TAG, "Generating FHR document from " + since + "; last ping " + lastPingTime);
Logger.pii(LOG_TAG, "Generating for profile " + profilePath);
ProfileInformationCache cache = new ProfileInformationCache(profilePath);
if (!cache.restoreUnlessInitialized()) {
Logger.warn(LOG_TAG, "Not enough profile information to compute current environment.");
return null;
}
Environment current = EnvironmentBuilder.getCurrentEnvironment(cache, config);
return generateDocument(since, lastPingTime, current);
}
/**
* The document consists of:
*
*<ul>
*<li>Basic metadata: last ping time, current ping time, version.</li>
*<li>A map of environments: <code>current</code> and others named by hash. <code>current</code> is fully specified,
* and others are deltas from current.</li>
*<li>A <code>data</code> object. This includes <code>last</code> and <code>days</code>.</li>
*</ul>
*
* <code>days</code> is a map from date strings to <tt>{hash: {measurement: {_v: version, fields...}}}</tt>.
* @throws JSONException if there was an error adding environment data to the resulting document.
*/
public JSONObject generateDocument(long since, long lastPingTime, Environment currentEnvironment) throws JSONException {
final String currentHash = currentEnvironment.getHash();
Logger.debug(LOG_TAG, "Current environment hash: " + currentHash);
if (currentHash == null) {
Logger.warn(LOG_TAG, "Current hash is null; aborting.");
return null;
}
// We want to map field IDs to some strings as we go.
SparseArray<Environment> envs = storage.getEnvironmentRecordsByID();
JSONObject document = new JSONObject();
if (lastPingTime >= HealthReportConstants.EARLIEST_LAST_PING) {
document.put("lastPingDate", dateFormatter.getDateString(lastPingTime));
}
document.put("thisPingDate", dateFormatter.getDateString(now()));
document.put("version", PAYLOAD_VERSION);
document.put("environments", getEnvironmentsJSON(currentEnvironment, envs));
document.put("data", getDataJSON(currentEnvironment, envs, since));
return document;
}
protected JSONObject getDataJSON(Environment currentEnvironment,
SparseArray<Environment> envs, long since) throws JSONException {
SparseArray<Field> fields = storage.getFieldsByID();
JSONObject days = getDaysJSON(currentEnvironment, envs, fields, since);
JSONObject last = new JSONObject();
JSONObject data = new JSONObject();
data.put("days", days);
data.put("last", last);
return data;
}
protected JSONObject getDaysJSON(Environment currentEnvironment, SparseArray<Environment> envs, SparseArray<Field> fields, long since) throws JSONException {
if (Logger.shouldLogVerbose(LOG_TAG)) {
for (int i = 0; i < envs.size(); ++i) {
Logger.trace(LOG_TAG, "Days environment " + envs.keyAt(i) + ": " + envs.get(envs.keyAt(i)).getHash());
}
}
JSONObject days = new JSONObject();
Cursor cursor = storage.getRawEventsSince(since);
try {
if (!cursor.moveToFirst()) {
return days;
}
// A classic walking partition.
// Columns are "date", "env", "field", "value".
// Note that we care about the type (integer, string) and kind
// (last/counter, discrete) of each field.
// Each field will be accessed once for each date/env pair, so
// Field memoizes these facts.
// We also care about which measurement contains each field.
int lastDate = -1;
int lastEnv = -1;
JSONObject dateObject = null;
JSONObject envObject = null;
while (!cursor.isAfterLast()) {
int cEnv = cursor.getInt(1);
if (cEnv == -1 ||
(cEnv != lastEnv &&
envs.indexOfKey(cEnv) < 0)) {
Logger.warn(LOG_TAG, "Invalid environment " + cEnv + " in cursor. Skipping.");
cursor.moveToNext();
continue;
}
int cDate = cursor.getInt(0);
int cField = cursor.getInt(2);
Logger.trace(LOG_TAG, "Event row: " + cDate + ", " + cEnv + ", " + cField);
boolean dateChanged = cDate != lastDate;
boolean envChanged = cEnv != lastEnv;
if (dateChanged) {
if (dateObject != null) {
days.put(dateFormatter.getDateStringForDay(lastDate), dateObject);
}
dateObject = new JSONObject();
lastDate = cDate;
}
if (dateChanged || envChanged) {
envObject = new JSONObject();
// This is safe because we checked above that cEnv is valid.
dateObject.put(envs.get(cEnv).getHash(), envObject);
lastEnv = cEnv;
}
final Field field = fields.get(cField);
JSONObject measurement = envObject.optJSONObject(field.measurementName);
if (measurement == null) {
// We will never have more than one measurement version within a
// single environment -- to do so involves changing the build ID. And
// even if we did, we have no way to represent it. So just build the
// output object once.
measurement = new JSONObject();
measurement.put("_v", field.measurementVersion);
envObject.put(field.measurementName, measurement);
}
// How we record depends on the type of the field, so we
// break this out into a separate method for clarity.
recordMeasurementFromCursor(field, measurement, cursor);
cursor.moveToNext();
continue;
}
days.put(dateFormatter.getDateStringForDay(lastDate), dateObject);
} finally {
cursor.close();
}
return days;
}
/**
* Return the {@link JSONObject} parsed from the provided index of the given
* cursor, or {@link JSONObject#NULL} if either SQL <code>NULL</code> or
* string <code>"null"</code> is present at that index.
*/
private static Object getJSONAtIndex(Cursor cursor, int index) throws JSONException {
if (cursor.isNull(index)) {
return JSONObject.NULL;
}
final String value = cursor.getString(index);
if ("null".equals(value)) {
return JSONObject.NULL;
}
return new JSONObject(value);
}
protected static void recordMeasurementFromCursor(final Field field,
JSONObject measurement,
Cursor cursor)
throws JSONException {
if (field.isDiscreteField()) {
// Discrete counted. Increment the named counter.
if (field.isCountedField()) {
if (!field.isStringField()) {
throw new IllegalStateException("Unable to handle non-string counted types.");
}
HealthReportUtils.count(measurement, field.fieldName, cursor.getString(3));
return;
}
// Discrete string or integer. Append it.
if (field.isStringField()) {
HealthReportUtils.append(measurement, field.fieldName, cursor.getString(3));
return;
}
if (field.isJSONField()) {
HealthReportUtils.append(measurement, field.fieldName, getJSONAtIndex(cursor, 3));
return;
}
if (field.isIntegerField()) {
HealthReportUtils.append(measurement, field.fieldName, cursor.getLong(3));
return;
}
throw new IllegalStateException("Unknown field type: " + field.flags);
}
// Non-discrete -- must be LAST or COUNTER, so just accumulate the value.
if (field.isStringField()) {
measurement.put(field.fieldName, cursor.getString(3));
return;
}
if (field.isJSONField()) {
measurement.put(field.fieldName, getJSONAtIndex(cursor, 3));
return;
}
measurement.put(field.fieldName, cursor.getLong(3));
}
public static JSONObject getEnvironmentsJSON(Environment currentEnvironment,
SparseArray<Environment> envs) throws JSONException {
JSONObject environments = new JSONObject();
// Always do this, even if it hasn't recorded anything in the DB.
environments.put("current", jsonify(currentEnvironment, null));
String currentHash = currentEnvironment.getHash();
for (int i = 0; i < envs.size(); i++) {
Environment e = envs.valueAt(i);
if (currentHash.equals(e.getHash())) {
continue;
}
environments.put(e.getHash(), jsonify(e, currentEnvironment));
}
return environments;
}
public static JSONObject jsonify(Environment e, Environment current) throws JSONException {
JSONObject age = getProfileAge(e, current);
JSONObject sysinfo = getSysInfo(e, current);
JSONObject gecko = getGeckoInfo(e, current);
JSONObject appinfo = getAppInfo(e, current);
JSONObject counts = getAddonCounts(e, current);
JSONObject config = getDeviceConfig(e, current);
JSONObject out = new JSONObject();
if (age != null)
out.put("org.mozilla.profile.age", age);
if (sysinfo != null)
out.put("org.mozilla.sysinfo.sysinfo", sysinfo);
if (gecko != null)
out.put("geckoAppInfo", gecko);
if (appinfo != null)
out.put("org.mozilla.appInfo.appinfo", appinfo);
if (counts != null)
out.put("org.mozilla.addons.counts", counts);
JSONObject active = getActiveAddons(e, current);
if (active != null)
out.put("org.mozilla.addons.active", active);
if (config != null)
out.put("org.mozilla.device.config", config);
if (current == null) {
out.put("hash", e.getHash());
}
return out;
}
// v3 environment fields.
private static JSONObject getDeviceConfig(Environment e, Environment current) throws JSONException {
JSONObject config = new JSONObject();
int changes = 0;
if (e.version < 3) {
return null;
}
if (current != null && current.version < 3) {
return getDeviceConfig(e, null);
}
if (current == null || current.hasHardwareKeyboard != e.hasHardwareKeyboard) {
config.put("hasHardwareKeyboard", e.hasHardwareKeyboard);
changes++;
}
if (current == null || current.screenLayout != e.screenLayout) {
config.put("screenLayout", e.screenLayout);
changes++;
}
if (current == null || current.screenXInMM != e.screenXInMM) {
config.put("screenXInMM", e.screenXInMM);
changes++;
}
if (current == null || current.screenYInMM != e.screenYInMM) {
config.put("screenYInMM", e.screenYInMM);
changes++;
}
if (current == null || current.uiType != e.uiType) {
config.put("uiType", e.uiType.toString());
changes++;
}
if (current == null || current.uiMode != e.uiMode) {
config.put("uiMode", e.uiMode);
changes++;
}
if (current != null && changes == 0) {
return null;
}
config.put("_v", 1);
return config;
}
private static JSONObject getProfileAge(Environment e, Environment current) throws JSONException {
JSONObject age = new JSONObject();
int changes = 0;
if (current == null || current.profileCreation != e.profileCreation) {
age.put("profileCreation", e.profileCreation);
changes++;
}
if (current != null && changes == 0) {
return null;
}
age.put("_v", 1);
return age;
}
private static JSONObject getSysInfo(Environment e, Environment current) throws JSONException {
JSONObject sysinfo = new JSONObject();
int changes = 0;
if (current == null || current.cpuCount != e.cpuCount) {
sysinfo.put("cpuCount", e.cpuCount);
changes++;
}
if (current == null || current.memoryMB != e.memoryMB) {
sysinfo.put("memoryMB", e.memoryMB);
changes++;
}
if (current == null || !current.architecture.equals(e.architecture)) {
sysinfo.put("architecture", e.architecture);
changes++;
}
if (current == null || !current.sysName.equals(e.sysName)) {
sysinfo.put("name", e.sysName);
changes++;
}
if (current == null || !current.sysVersion.equals(e.sysVersion)) {
sysinfo.put("version", e.sysVersion);
changes++;
}
if (current != null && changes == 0) {
return null;
}
sysinfo.put("_v", 1);
return sysinfo;
}
private static JSONObject getGeckoInfo(Environment e, Environment current) throws JSONException {
JSONObject gecko = new JSONObject();
int changes = 0;
if (current == null || !current.vendor.equals(e.vendor)) {
gecko.put("vendor", e.vendor);
changes++;
}
if (current == null || !current.appName.equals(e.appName)) {
gecko.put("name", e.appName);
changes++;
}
if (current == null || !current.appID.equals(e.appID)) {
gecko.put("id", e.appID);
changes++;
}
if (current == null || !current.appVersion.equals(e.appVersion)) {
gecko.put("version", e.appVersion);
changes++;
}
if (current == null || !current.appBuildID.equals(e.appBuildID)) {
gecko.put("appBuildID", e.appBuildID);
changes++;
}
if (current == null || !current.platformVersion.equals(e.platformVersion)) {
gecko.put("platformVersion", e.platformVersion);
changes++;
}
if (current == null || !current.platformBuildID.equals(e.platformBuildID)) {
gecko.put("platformBuildID", e.platformBuildID);
changes++;
}
if (current == null || !current.os.equals(e.os)) {
gecko.put("os", e.os);
changes++;
}
if (current == null || !current.xpcomabi.equals(e.xpcomabi)) {
gecko.put("xpcomabi", e.xpcomabi);
changes++;
}
if (current == null || !current.updateChannel.equals(e.updateChannel)) {
gecko.put("updateChannel", e.updateChannel);
changes++;
}
if (current != null && changes == 0) {
return null;
}
gecko.put("_v", 1);
return gecko;
}
// Null-safe string comparison.
private static boolean stringsDiffer(final String a, final String b) {
if (a == null) {
return b != null;
}
return !a.equals(b);
}
@SuppressWarnings("fallthrough")
private static JSONObject getAppInfo(Environment e, Environment current) throws JSONException {
JSONObject appinfo = new JSONObject();
Logger.debug(LOG_TAG, "Generating appinfo for v" + e.version + " env " + e.hash);
// Is the environment in question newer than the diff target, or is
// there no diff target?
final boolean outdated = current == null ||
e.version > current.version;
// Is the environment in question a different version (lower or higher),
// or is there no diff target?
final boolean differ = outdated || current.version > e.version;
// Always produce an output object if there's a version mismatch or this
// isn't a diff. Otherwise, track as we go if there's any difference.
boolean changed = differ;
switch (e.version) {
// There's a straightforward correspondence between environment versions
// and appinfo versions.
case 3:
case 2:
appinfo.put("_v", 3);
break;
case 1:
appinfo.put("_v", 2);
break;
default:
Logger.warn(LOG_TAG, "Unknown environment version: " + e.version);
return appinfo;
}
switch (e.version) {
case 3:
case 2:
if (populateAppInfoV2(appinfo, e, current, outdated)) {
changed = true;
}
// Fall through.
case 1:
// There is no older version than v1, so don't check outdated.
if (populateAppInfoV1(e, current, appinfo)) {
changed = true;
}
}
if (!changed) {
return null;
}
return appinfo;
}
private static boolean populateAppInfoV1(Environment e,
Environment current,
JSONObject appinfo)
throws JSONException {
boolean changes = false;
if (current == null || current.isBlocklistEnabled != e.isBlocklistEnabled) {
appinfo.put("isBlocklistEnabled", e.isBlocklistEnabled);
changes = true;
}
if (current == null || current.isTelemetryEnabled != e.isTelemetryEnabled) {
appinfo.put("isTelemetryEnabled", e.isTelemetryEnabled);
changes = true;
}
return changes;
}
private static boolean populateAppInfoV2(JSONObject appinfo,
Environment e,
Environment current,
final boolean outdated)
throws JSONException {
boolean changes = false;
if (outdated ||
stringsDiffer(current.osLocale, e.osLocale)) {
appinfo.put("osLocale", e.osLocale);
changes = true;
}
if (outdated ||
stringsDiffer(current.appLocale, e.appLocale)) {
appinfo.put("appLocale", e.appLocale);
changes = true;
}
if (outdated ||
stringsDiffer(current.distribution, e.distribution)) {
appinfo.put("distribution", e.distribution);
changes = true;
}
if (outdated ||
current.acceptLangSet != e.acceptLangSet) {
appinfo.put("acceptLangIsUserSet", e.acceptLangSet);
changes = true;
}
return changes;
}
private static JSONObject getAddonCounts(Environment e, Environment current) throws JSONException {
JSONObject counts = new JSONObject();
int changes = 0;
if (current == null || current.extensionCount != e.extensionCount) {
counts.put("extension", e.extensionCount);
changes++;
}
if (current == null || current.pluginCount != e.pluginCount) {
counts.put("plugin", e.pluginCount);
changes++;
}
if (current == null || current.themeCount != e.themeCount) {
counts.put("theme", e.themeCount);
changes++;
}
if (current != null && changes == 0) {
return null;
}
counts.put("_v", 1);
return counts;
}
/**
* Compute the *tree* difference set between the two objects. If the two
* objects are identical, returns <code>null</code>. If <code>from</code> is
* <code>null</code>, returns <code>to</code>. If <code>to</code> is
* <code>null</code>, behaves as if <code>to</code> were an empty object.
*
* (Note that this method does not check for {@link JSONObject#NULL}, because
* by definition it can't be provided as input to this method.)
*
* This behavior is intended to simplify life for callers: a missing object
* can be viewed as (and behaves as) an empty map, to a useful extent, rather
* than throwing an exception.
*
* @param from
* a JSONObject.
* @param to
* a JSONObject.
* @param includeNull
* if true, keys present in <code>from</code> but not in
* <code>to</code> are included as {@link JSONObject#NULL} in the
* output.
*
* @return a JSONObject, or null if the two objects are identical.
* @throws JSONException
* should not occur, but...
*/
public static JSONObject diff(JSONObject from,
JSONObject to,
boolean includeNull) throws JSONException {
if (from == null) {
return to;
}
if (to == null) {
return diff(from, new JSONObject(), includeNull);
}
JSONObject out = new JSONObject();
HashSet<String> toKeys = includeNull ? new HashSet<String>(to.length())
: null;
@SuppressWarnings("unchecked")
Iterator<String> it = to.keys();
while (it.hasNext()) {
String key = it.next();
// Track these as we go if we'll need them later.
if (includeNull) {
toKeys.add(key);
}
Object value = to.get(key);
if (!from.has(key)) {
// It must be new.
out.put(key, value);
continue;
}
// Not new? Then see if it changed.
Object old = from.get(key);
// Two JSONObjects should be diffed.
if (old instanceof JSONObject && value instanceof JSONObject) {
JSONObject innerDiff = diff(((JSONObject) old), ((JSONObject) value),
includeNull);
// No change? No output.
if (innerDiff == null) {
continue;
}
// Otherwise include the diff.
out.put(key, innerDiff);
continue;
}
// A regular value, or a type change. Only skip if they're the same.
if (value.equals(old)) {
continue;
}
out.put(key, value);
}
// Now -- if requested -- include any removed keys.
if (includeNull) {
Set<String> fromKeys = HealthReportUtils.keySet(from);
fromKeys.removeAll(toKeys);
for (String notPresent : fromKeys) {
out.put(notPresent, JSONObject.NULL);
}
}
if (out.length() == 0) {
return null;
}
return out;
}
private static JSONObject getActiveAddons(Environment e, Environment current) throws JSONException {
// Just return the current add-on set, with a version annotation.
// To do so requires copying.
if (current == null) {
JSONObject out = e.getNonIgnoredAddons();
if (out == null) {
Logger.warn(LOG_TAG, "Null add-ons to return in FHR document. Returning {}.");
out = new JSONObject(); // So that we always return something.
}
out.put("_v", 1);
return out;
}
// Otherwise, return the diff.
JSONObject diff = diff(current.getNonIgnoredAddons(), e.getNonIgnoredAddons(), true);
if (diff == null) {
return null;
}
if (diff == e.addons) {
// Again, needs to copy.
return getActiveAddons(e, null);
}
diff.put("_v", 1);
return diff;
}
}

View File

@ -1,301 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package org.mozilla.gecko.background.healthreport;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
import java.util.Map.Entry;
import org.mozilla.gecko.background.healthreport.HealthReportDatabaseStorage.DatabaseEnvironment;
import org.mozilla.gecko.background.healthreport.HealthReportStorage.Field;
import org.mozilla.gecko.background.healthreport.HealthReportStorage.MeasurementFields;
import org.mozilla.gecko.background.healthreport.HealthReportStorage.MeasurementFields.FieldSpec;
import android.content.ContentProvider;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.UriMatcher;
import android.database.Cursor;
import android.net.Uri;
/**
* This is a {@link ContentProvider} wrapper around a database-backed Health
* Report storage layer.
*
* It stores environments, fields, and measurements, and events which refer to
* each of these by integer ID.
*
* Insert = daily discrete.
* content://org.mozilla.gecko.health/events/env/measurement/v/field
*
* Update = daily last or daily counter
* content://org.mozilla.gecko.health/events/env/measurement/v/field/counter
* content://org.mozilla.gecko.health/events/env/measurement/v/field/last
*
* Delete = drop today's row
* content://org.mozilla.gecko.health/events/env/measurement/v/field/
*
* Query, of course: content://org.mozilla.gecko.health/events/?since
*
* Each operation accepts an optional `time` query parameter, formatted as
* milliseconds since epoch. If omitted, it defaults to the current time.
*
* Each operation also accepts mandatory `profilePath` and `env` arguments.
*
* TODO: document measurements.
*/
public class HealthReportProvider extends ContentProvider {
private HealthReportDatabases databases;
private static final UriMatcher uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
public static final String HEALTH_AUTHORITY = HealthReportConstants.HEALTH_AUTHORITY;
// URI matches.
private static final int ENVIRONMENTS_ROOT = 10;
private static final int EVENTS_ROOT = 11;
private static final int EVENTS_RAW_ROOT = 12;
private static final int FIELDS_ROOT = 13;
private static final int MEASUREMENTS_ROOT = 14;
private static final int EVENTS_FIELD_GENERIC = 20;
private static final int EVENTS_FIELD_COUNTER = 21;
private static final int EVENTS_FIELD_LAST = 22;
private static final int ENVIRONMENT_DETAILS = 30;
private static final int FIELDS_MEASUREMENT = 31;
static {
uriMatcher.addURI(HEALTH_AUTHORITY, "environments/", ENVIRONMENTS_ROOT);
uriMatcher.addURI(HEALTH_AUTHORITY, "events/", EVENTS_ROOT);
uriMatcher.addURI(HEALTH_AUTHORITY, "rawevents/", EVENTS_RAW_ROOT);
uriMatcher.addURI(HEALTH_AUTHORITY, "fields/", FIELDS_ROOT);
uriMatcher.addURI(HEALTH_AUTHORITY, "measurements/", MEASUREMENTS_ROOT);
uriMatcher.addURI(HEALTH_AUTHORITY, "events/#/*/#/*", EVENTS_FIELD_GENERIC);
uriMatcher.addURI(HEALTH_AUTHORITY, "events/#/*/#/*/counter", EVENTS_FIELD_COUNTER);
uriMatcher.addURI(HEALTH_AUTHORITY, "events/#/*/#/*/last", EVENTS_FIELD_LAST);
uriMatcher.addURI(HEALTH_AUTHORITY, "environments/#", ENVIRONMENT_DETAILS);
uriMatcher.addURI(HEALTH_AUTHORITY, "fields/*/#", FIELDS_MEASUREMENT);
}
/**
* So we can bypass the ContentProvider layer.
*/
public HealthReportDatabaseStorage getProfileStorage(final String profilePath) {
if (profilePath == null) {
throw new IllegalArgumentException("profilePath must be provided.");
}
return databases.getDatabaseHelperForProfile(new File(profilePath));
}
private HealthReportDatabaseStorage getProfileStorageForUri(Uri uri) {
final String profilePath = uri.getQueryParameter("profilePath");
return getProfileStorage(profilePath);
}
@Override
public void onLowMemory() {
// While we could prune the database here, it wouldn't help - it would restore disk space
// rather then lower our RAM usage. Additionally, pruning the database may use even more
// memory and take too long to run in this method.
super.onLowMemory();
databases.closeDatabaseHelpers();
}
@Override
public String getType(Uri uri) {
return null;
}
@Override
public boolean onCreate() {
databases = new HealthReportDatabases(getContext());
return true;
}
@Override
public Uri insert(Uri uri, ContentValues values) {
int match = uriMatcher.match(uri);
HealthReportDatabaseStorage storage = getProfileStorageForUri(uri);
switch (match) {
case FIELDS_MEASUREMENT:
// The keys of this ContentValues are field names.
List<String> pathSegments = uri.getPathSegments();
String measurement = pathSegments.get(1);
int v = Integer.parseInt(pathSegments.get(2));
storage.ensureMeasurementInitialized(measurement, v, getFieldSpecs(values));
return uri;
case ENVIRONMENTS_ROOT:
DatabaseEnvironment environment = storage.getEnvironment();
environment.init(values);
return ContentUris.withAppendedId(uri, environment.register());
case EVENTS_FIELD_GENERIC:
long time = getTimeFromUri(uri);
int day = storage.getDay(time);
int env = getEnvironmentFromUri(uri);
Field field = getFieldFromUri(storage, uri);
if (!values.containsKey("value")) {
throw new IllegalArgumentException("Must provide ContentValues including 'value' key.");
}
Object object = values.get("value");
if (object instanceof Integer ||
object instanceof Long) {
storage.recordDailyDiscrete(env, day, field.getID(), ((Integer) object).intValue());
} else if (object instanceof String) {
storage.recordDailyDiscrete(env, day, field.getID(), (String) object);
} else {
storage.recordDailyDiscrete(env, day, field.getID(), object.toString());
}
// TODO: eventually we might want to return something more useful than
// the input URI.
return uri;
default:
throw new IllegalArgumentException("Unknown insert URI");
}
}
@Override
public int update(Uri uri, ContentValues values, String selection,
String[] selectionArgs) {
int match = uriMatcher.match(uri);
if (match != EVENTS_FIELD_COUNTER &&
match != EVENTS_FIELD_LAST) {
throw new IllegalArgumentException("Must provide operation for update.");
}
HealthReportStorage storage = getProfileStorageForUri(uri);
long time = getTimeFromUri(uri);
int day = storage.getDay(time);
int env = getEnvironmentFromUri(uri);
Field field = getFieldFromUri(storage, uri);
switch (match) {
case EVENTS_FIELD_COUNTER:
int by = values.containsKey("value") ? values.getAsInteger("value") : 1;
storage.incrementDailyCount(env, day, field.getID(), by);
return 1;
case EVENTS_FIELD_LAST:
Object object = values.get("value");
if (object instanceof Integer ||
object instanceof Long) {
storage.recordDailyLast(env, day, field.getID(), (Integer) object);
} else if (object instanceof String) {
storage.recordDailyLast(env, day, field.getID(), (String) object);
} else {
storage.recordDailyLast(env, day, field.getID(), object.toString());
}
return 1;
default:
// javac's flow control analysis sucks.
return 0;
}
}
@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
int match = uriMatcher.match(uri);
HealthReportStorage storage = getProfileStorageForUri(uri);
switch (match) {
case MEASUREMENTS_ROOT:
storage.deleteMeasurements();
return 1;
case ENVIRONMENTS_ROOT:
storage.deleteEnvironments();
return 1;
default:
throw new IllegalArgumentException();
}
// TODO: more
}
@Override
public Cursor query(Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder) {
int match = uriMatcher.match(uri);
HealthReportStorage storage = getProfileStorageForUri(uri);
switch (match) {
case EVENTS_ROOT:
return storage.getEventsSince(getTimeFromUri(uri));
case EVENTS_RAW_ROOT:
return storage.getRawEventsSince(getTimeFromUri(uri));
case MEASUREMENTS_ROOT:
return storage.getMeasurementVersions();
case FIELDS_ROOT:
return storage.getFieldVersions();
}
List<String> pathSegments = uri.getPathSegments();
switch (match) {
case ENVIRONMENT_DETAILS:
return storage.getEnvironmentRecordForID(Integer.parseInt(pathSegments.get(1), 10));
case FIELDS_MEASUREMENT:
String measurement = pathSegments.get(1);
int v = Integer.parseInt(pathSegments.get(2));
return storage.getFieldVersions(measurement, v);
default:
return null;
}
}
private static long getTimeFromUri(final Uri uri) {
String t = uri.getQueryParameter("time");
if (t == null) {
return System.currentTimeMillis();
} else {
return Long.parseLong(t, 10);
}
}
private static int getEnvironmentFromUri(final Uri uri) {
return Integer.parseInt(uri.getPathSegments().get(1), 10);
}
/**
* Assumes a URI structured like:
*
* <code>content://org.mozilla.gecko.health/events/env/measurement/v/field</code>
*
* @param uri a URI formatted as expected.
* @return a {@link Field} instance.
*/
private static Field getFieldFromUri(HealthReportStorage storage, final Uri uri) {
String measurement;
String field;
int measurementVersion;
List<String> pathSegments = uri.getPathSegments();
measurement = pathSegments.get(2);
measurementVersion = Integer.parseInt(pathSegments.get(3), 10);
field = pathSegments.get(4);
return storage.getField(measurement, measurementVersion, field);
}
private MeasurementFields getFieldSpecs(ContentValues values) {
final ArrayList<FieldSpec> specs = new ArrayList<FieldSpec>(values.size());
for (Entry<String, Object> entry : values.valueSet()) {
specs.add(new FieldSpec(entry.getKey(), (Integer) entry.getValue()));
}
return new MeasurementFields() {
@Override
public Iterable<FieldSpec> getFields() {
return specs;
}
};
}
}

View File

@ -1,238 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package org.mozilla.gecko.background.healthreport;
import org.json.JSONObject;
import android.database.Cursor;
import android.util.SparseArray;
/**
* Abstraction over storage for Firefox Health Report on Android.
*/
public interface HealthReportStorage {
// Right now we only care about the name of the field.
public interface MeasurementFields {
public class FieldSpec {
public final String name;
public final int type;
public FieldSpec(String name, int type) {
this.name = name;
this.type = type;
}
}
Iterable<FieldSpec> getFields();
}
public abstract class Field {
protected static final int UNKNOWN_TYPE_OR_FIELD_ID = -1;
protected static final int FLAG_INTEGER = 1 << 0;
protected static final int FLAG_STRING = 1 << 1;
protected static final int FLAG_JSON = 1 << 2;
protected static final int FLAG_DISCRETE = 1 << 8;
protected static final int FLAG_LAST = 1 << 9;
protected static final int FLAG_COUNTER = 1 << 10;
protected static final int FLAG_COUNTED = 1 << 14;
public static final int TYPE_INTEGER_DISCRETE = FLAG_INTEGER | FLAG_DISCRETE;
public static final int TYPE_INTEGER_LAST = FLAG_INTEGER | FLAG_LAST;
public static final int TYPE_INTEGER_COUNTER = FLAG_INTEGER | FLAG_COUNTER;
public static final int TYPE_STRING_DISCRETE = FLAG_STRING | FLAG_DISCRETE;
public static final int TYPE_STRING_LAST = FLAG_STRING | FLAG_LAST;
public static final int TYPE_JSON_DISCRETE = FLAG_JSON | FLAG_DISCRETE;
public static final int TYPE_JSON_LAST = FLAG_JSON | FLAG_LAST;
public static final int TYPE_COUNTED_STRING_DISCRETE = FLAG_COUNTED | TYPE_STRING_DISCRETE;
protected int fieldID = UNKNOWN_TYPE_OR_FIELD_ID;
protected int flags;
protected final String measurementName;
protected final String measurementVersion;
protected final String fieldName;
public Field(String mName, int mVersion, String fieldName, int type) {
this.measurementName = mName;
this.measurementVersion = Integer.toString(mVersion, 10);
this.fieldName = fieldName;
this.flags = type;
}
/**
* @return the ID for this <code>Field</code>
* @throws IllegalStateException if this field is not found in storage
*/
public abstract int getID() throws IllegalStateException;
public boolean isIntegerField() {
return (this.flags & FLAG_INTEGER) > 0;
}
public boolean isStringField() {
return (this.flags & FLAG_STRING) > 0;
}
public boolean isJSONField() {
return (this.flags & FLAG_JSON) > 0;
}
public boolean isStoredAsString() {
return (this.flags & (FLAG_JSON | FLAG_STRING)) > 0;
}
public boolean isDiscreteField() {
return (this.flags & FLAG_DISCRETE) > 0;
}
/**
* True if the accrued values are intended to be bucket-counted. For strings,
* each discrete value will name a bucket, with the number of instances per
* day being the value in the bucket.
*/
public boolean isCountedField() {
return (this.flags & FLAG_COUNTED) > 0;
}
}
/**
* Close open storage handles and otherwise finish up.
*/
public void close();
/**
* Return the day integer corresponding to the provided time.
*
* @param time
* milliseconds since Unix epoch.
* @return an integer day.
*/
public int getDay(long time);
/**
* Return the day integer corresponding to the current time.
*
* @return an integer day.
*/
public int getDay();
/**
* Return a new {@link Environment}, suitable for being populated, hashed, and
* registered.
*
* @return a new {@link Environment} instance.
*/
public Environment getEnvironment();
/**
* @return a mapping from environment IDs to hashes, suitable for use in
* payload generation.
*/
public SparseArray<String> getEnvironmentHashesByID();
/**
* @return a mapping from environment IDs to registered {@link Environment}
* records, suitable for use in payload generation.
*/
public SparseArray<Environment> getEnvironmentRecordsByID();
/**
* @param id
* the environment ID, as returned by {@link Environment#register()}.
* @return a cursor for the record.
*/
public Cursor getEnvironmentRecordForID(int id);
/**
* @param measurement
* the name of a measurement, such as "org.mozilla.appInfo.appInfo".
* @param measurementVersion
* the version of a measurement, such as '3'.
* @param fieldName
* the name of a field, such as "platformVersion".
*
* @return a {@link Field} instance corresponding to the provided values.
*/
public Field getField(String measurement, int measurementVersion,
String fieldName);
/**
* @return a mapping from field IDs to {@link Field} instances, suitable for
* use in payload generation.
*/
public SparseArray<Field> getFieldsByID();
public void recordDailyLast(int env, int day, int field, JSONObject value);
public void recordDailyLast(int env, int day, int field, String value);
public void recordDailyLast(int env, int day, int field, int value);
public void recordDailyDiscrete(int env, int day, int field, JSONObject value);
public void recordDailyDiscrete(int env, int day, int field, String value);
public void recordDailyDiscrete(int env, int day, int field, int value);
public void incrementDailyCount(int env, int day, int field, int by);
public void incrementDailyCount(int env, int day, int field);
/**
* Return true if events exist that were recorded on or after <code>time</code>.
*/
boolean hasEventSince(long time);
/**
* Obtain a cursor over events that were recorded since <code>time</code>.
* This cursor exposes 'raw' events, with integer identifiers for values.
*/
public Cursor getRawEventsSince(long time);
/**
* Obtain a cursor over events that were recorded since <code>time</code>.
*
* This cursor exposes 'friendly' events, with string names and full
* measurement metadata.
*/
public Cursor getEventsSince(long time);
/**
* Ensure that a measurement and all of its fields are registered with the DB.
* No fields will be processed if the measurement exists with the specified
* version.
*
* @param measurement
* a measurement name, such as "org.mozilla.appInfo.appInfo".
* @param version
* a version number, such as '3'.
* @param fields
* a {@link MeasurementFields} instance, consisting of a collection
* of field names.
*/
public void ensureMeasurementInitialized(String measurement,
int version,
MeasurementFields fields);
public Cursor getMeasurementVersions();
public Cursor getFieldVersions();
public Cursor getFieldVersions(String measurement, int measurementVersion);
public void deleteEverything();
public void deleteEnvironments();
public void deleteMeasurements();
/**
* Deletes all environments, addons, and events from the database before the given time.
*
* @param time milliseconds since epoch.
* @param curEnv The ID of the current environment.
* @return The number of environments and addon entries deleted.
*/
public int deleteDataBefore(final long time, final int curEnv);
public int getEventCount();
public int getEnvironmentCount();
public void pruneEvents(final int num);
public void pruneEnvironments(final int num);
public void enqueueOperation(Runnable runnable);
}

View File

@ -1,136 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package org.mozilla.gecko.background.healthreport;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;
import java.util.UUID;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.mozilla.apache.commons.codec.digest.DigestUtils;
import android.content.ContentUris;
import android.net.Uri;
public class HealthReportUtils {
public static final String LOG_TAG = HealthReportUtils.class.getSimpleName();
public static String getEnvironmentHash(final String input) {
return DigestUtils.shaHex(input);
}
/**
* Take an environment URI (one that identifies an environment) and produce an
* event URI.
*
* That this is needed is tragic.
*
* @param environmentURI
* the {@link Uri} returned by an environment operation.
* @return a {@link Uri} to which insertions can be dispatched.
*/
public static Uri getEventURI(Uri environmentURI) {
return environmentURI.buildUpon().path("/events/" + ContentUris.parseId(environmentURI) + "/").build();
}
/**
* Copy the keys from the provided {@link JSONObject} into the provided {@link Set}.
*/
private static <T extends Set<String>> T intoKeySet(T keys, JSONObject o) {
if (o == null || o == JSONObject.NULL) {
return keys;
}
@SuppressWarnings("unchecked")
Iterator<String> it = o.keys();
while (it.hasNext()) {
keys.add(it.next());
}
return keys;
}
/**
* Produce a {@link SortedSet} containing the string keys of the provided
* object.
*
* @param o a {@link JSONObject} with string keys.
* @return a sorted set.
*/
public static SortedSet<String> sortedKeySet(JSONObject o) {
return intoKeySet(new TreeSet<String>(), o);
}
/**
* Produce a {@link Set} containing the string keys of the provided object.
* @param o a {@link JSONObject} with string keys.
* @return an unsorted set.
*/
public static Set<String> keySet(JSONObject o) {
return intoKeySet(new HashSet<String>(), o);
}
/**
* Just like {@link JSONObject#accumulate(String, Object)}, but doesn't do the wrong thing for single values.
* @throws JSONException
*/
public static void append(JSONObject o, String key, Object value) throws JSONException {
if (!o.has(key)) {
JSONArray arr = new JSONArray();
arr.put(value);
o.put(key, arr);
return;
}
Object dest = o.get(key);
if (dest instanceof JSONArray) {
((JSONArray) dest).put(value);
return;
}
JSONArray arr = new JSONArray();
arr.put(dest);
arr.put(value);
o.put(key, arr);
}
/**
* Accumulate counts for how often each provided value occurs.
*
* <code>
* HealthReportUtils.count(o, "foo", "bar");
* </code>
*
* will change
*
* <pre>
* {"foo", {"bar": 1}}
* </pre>
*
* into
*
* <pre>
* {"foo", {"bar": 2}}
* </pre>
*
*/
public static void count(JSONObject o, String key,
String value) throws JSONException {
if (!o.has(key)) {
JSONObject counts = new JSONObject();
counts.put(value, 1);
o.put(key, counts);
return;
}
JSONObject dest = o.getJSONObject(key);
dest.put(value, dest.optInt(value, 0) + 1);
}
public static String generateDocumentId() {
return UUID.randomUUID().toString();
}
}

View File

@ -1,386 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package org.mozilla.gecko.background.healthreport;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.nio.charset.Charset;
import java.util.Locale;
import java.util.Scanner;
import org.json.JSONException;
import org.json.JSONObject;
import org.mozilla.gecko.background.common.log.Logger;
import org.mozilla.gecko.background.healthreport.EnvironmentBuilder.ProfileInformationProvider;
/**
* There are some parts of the FHR environment that can't be readily computed
* without a running Gecko -- add-ons, for example. In order to make this
* information available without launching Gecko, we persist it on Fennec
* startup. This class is the notepad in which we write.
*/
public class ProfileInformationCache implements ProfileInformationProvider {
private static final String LOG_TAG = "GeckoProfileInfo";
private static final String CACHE_FILE = "profile_info_cache.json";
/*
* FORMAT_VERSION history:
* -: No version number; implicit v1.
* 1: Add versioning (Bug 878670).
* 2: Bump to regenerate add-on set after landing Bug 900694 (Bug 901622).
* 3: Add distribution, osLocale, appLocale.
* 4: Add experiments as add-ons.
*/
public static final int FORMAT_VERSION = 4;
protected boolean initialized = false;
protected boolean needsWrite = false;
protected final File file;
private volatile boolean blocklistEnabled = true;
private volatile boolean telemetryEnabled = false;
private volatile boolean isAcceptLangUserSet = false;
private volatile long profileCreationTime = 0;
private volatile String distribution = "";
// There are really four kinds of locale in play:
//
// * The OS
// * The Android environment of the app (setDefault)
// * The Gecko locale
// * The requested content locale (Accept-Language).
//
// We track only the first two, assuming that the Gecko locale will typically
// be the same as the app locale.
//
// The app locale is fetched from the PIC because it can be modified at
// runtime -- it won't necessarily be what Locale.getDefaultLocale() returns
// in a fresh non-browser profile.
//
// We also track the OS locale here for the same reason -- we need to store
// the default (OS) value before the locale-switching code takes effect!
private volatile String osLocale = "";
private volatile String appLocale = "";
private volatile JSONObject addons = null;
protected ProfileInformationCache(final File f) {
file = f;
Logger.pii(LOG_TAG, "Using " + file.getAbsolutePath() + " for profile information cache.");
}
public ProfileInformationCache(final String profilePath) {
this(new File(profilePath + File.separator + CACHE_FILE));
}
public synchronized void beginInitialization() {
initialized = false;
needsWrite = true;
}
public JSONObject toJSON() {
JSONObject object = new JSONObject();
try {
object.put("version", FORMAT_VERSION);
object.put("blocklist", blocklistEnabled);
object.put("telemetry", telemetryEnabled);
object.put("isAcceptLangUserSet", isAcceptLangUserSet);
object.put("profileCreated", profileCreationTime);
object.put("osLocale", osLocale);
object.put("appLocale", appLocale);
object.put("distribution", distribution);
object.put("addons", addons);
} catch (JSONException e) {
// There isn't much we can do about this.
// Let's just quietly muffle.
return null;
}
return object;
}
/**
* Attempt to restore this object from a JSON blob. If there is a version mismatch, there has
* likely been an upgrade to the cache format. The cache can be reconstructed without data loss
* so rather than migrating, we invalidate the cache by refusing to store the given JSONObject
* and returning false.
*
* @return false if there's a version mismatch or an error, true on success.
*/
private boolean fromJSON(JSONObject object) throws JSONException {
if (object == null) {
Logger.debug(LOG_TAG, "Can't load restore PIC from null JSON object.");
return false;
}
int version = object.optInt("version", 1);
switch (version) {
case FORMAT_VERSION:
blocklistEnabled = object.getBoolean("blocklist");
telemetryEnabled = object.getBoolean("telemetry");
isAcceptLangUserSet = object.getBoolean("isAcceptLangUserSet");
profileCreationTime = object.getLong("profileCreated");
addons = object.getJSONObject("addons");
distribution = object.getString("distribution");
osLocale = object.getString("osLocale");
appLocale = object.getString("appLocale");
return true;
default:
Logger.warn(LOG_TAG, "Unable to restore from version " + version + " PIC file: expecting " + FORMAT_VERSION);
return false;
}
}
protected JSONObject readFromFile() throws FileNotFoundException, JSONException {
Scanner scanner = null;
try {
scanner = new Scanner(file, "UTF-8").useDelimiter("\\A");
if (!scanner.hasNext()) {
return null;
}
return new JSONObject(scanner.next());
} finally {
if (scanner != null) {
scanner.close();
}
}
}
protected void writeToFile(JSONObject object) throws IOException {
Logger.debug(LOG_TAG, "Writing profile information.");
Logger.pii(LOG_TAG, "Writing to file: " + file.getAbsolutePath());
FileOutputStream stream = new FileOutputStream(file);
OutputStreamWriter writer = new OutputStreamWriter(stream, Charset.forName("UTF-8"));
try {
writer.append(object.toString());
needsWrite = false;
} finally {
writer.close();
}
}
/**
* Call this <b>on a background thread</b> when you're done adding things.
* @throws IOException if there was a problem serializing or writing the cache to disk.
*/
public synchronized void completeInitialization() throws IOException {
initialized = true;
if (!needsWrite) {
Logger.debug(LOG_TAG, "No write needed.");
return;
}
JSONObject object = toJSON();
if (object == null) {
throw new IOException("Couldn't serialize JSON.");
}
writeToFile(object);
}
/**
* Call this if you're interested in reading.
*
* You should be doing so on a background thread.
*
* @return true if this object was initialized correctly.
*/
public synchronized boolean restoreUnlessInitialized() {
if (initialized) {
return true;
}
if (!file.exists()) {
return false;
}
// One-liner for file reading in Java. So sorry.
Logger.info(LOG_TAG, "Restoring ProfileInformationCache from file.");
Logger.pii(LOG_TAG, "Restoring from file: " + file.getAbsolutePath());
try {
if (!fromJSON(readFromFile())) {
// No need to blow away the file; the caller can eventually overwrite it.
return false;
}
initialized = true;
needsWrite = false;
return true;
} catch (FileNotFoundException e) {
return false;
} catch (JSONException e) {
Logger.warn(LOG_TAG, "Malformed ProfileInformationCache. Not restoring.");
return false;
}
}
private void ensureInitialized() {
if (!initialized) {
throw new IllegalStateException("Not initialized.");
}
}
@Override
public boolean isBlocklistEnabled() {
ensureInitialized();
return blocklistEnabled;
}
public void setBlocklistEnabled(boolean value) {
Logger.debug(LOG_TAG, "Setting blocklist enabled: " + value);
blocklistEnabled = value;
needsWrite = true;
}
@Override
public boolean isTelemetryEnabled() {
ensureInitialized();
return telemetryEnabled;
}
public void setTelemetryEnabled(boolean value) {
Logger.debug(LOG_TAG, "Setting telemetry enabled: " + value);
telemetryEnabled = value;
needsWrite = true;
}
@Override
public boolean isAcceptLangUserSet() {
ensureInitialized();
return isAcceptLangUserSet;
}
public void setAcceptLangUserSet(boolean value) {
Logger.debug(LOG_TAG, "Setting accept-lang as user-set: " + value);
isAcceptLangUserSet = value;
needsWrite = true;
}
@Override
public long getProfileCreationTime() {
ensureInitialized();
return profileCreationTime;
}
public void setProfileCreationTime(long value) {
Logger.debug(LOG_TAG, "Setting profile creation time: " + value);
profileCreationTime = value;
needsWrite = true;
}
@Override
public String getDistributionString() {
ensureInitialized();
return distribution;
}
/**
* Ensure that your arguments are non-null.
*/
public void setDistributionString(String distributionID, String distributionVersion) {
Logger.debug(LOG_TAG, "Setting distribution: " + distributionID + ", " + distributionVersion);
distribution = distributionID + ":" + distributionVersion;
needsWrite = true;
}
@Override
public String getAppLocale() {
ensureInitialized();
return appLocale;
}
public void setAppLocale(String value) {
if (value.equalsIgnoreCase(appLocale)) {
return;
}
Logger.debug(LOG_TAG, "Setting app locale: " + value);
appLocale = value.toLowerCase(Locale.US);
needsWrite = true;
}
@Override
public String getOSLocale() {
ensureInitialized();
return osLocale;
}
public void setOSLocale(String value) {
if (value.equalsIgnoreCase(osLocale)) {
return;
}
Logger.debug(LOG_TAG, "Setting OS locale: " + value);
osLocale = value.toLowerCase(Locale.US);
needsWrite = true;
}
/**
* Update the PIC, if necessary, to match the current locale environment.
*
* @return true if the PIC needed to be updated.
*/
public boolean updateLocales(String osLocale, String appLocale) {
if (this.osLocale.equalsIgnoreCase(osLocale) &&
(appLocale == null || this.appLocale.equalsIgnoreCase(appLocale))) {
return false;
}
this.setOSLocale(osLocale);
if (appLocale != null) {
this.setAppLocale(appLocale);
}
return true;
}
@Override
public JSONObject getAddonsJSON() {
ensureInitialized();
return addons;
}
public void updateJSONForAddon(String id, String json) throws Exception {
addons.put(id, new JSONObject(json));
needsWrite = true;
}
public void removeAddon(String id) {
if (null != addons.remove(id)) {
needsWrite = true;
}
}
/**
* Will throw if you haven't done a full update at least once.
*/
public void updateJSONForAddon(String id, JSONObject json) {
if (addons == null) {
throw new IllegalStateException("Cannot incrementally update add-ons without first initializing.");
}
try {
addons.put(id, json);
needsWrite = true;
} catch (Exception e) {
// Why would this happen?
Logger.warn(LOG_TAG, "Unexpected failure updating JSON for add-on.", e);
}
}
/**
* Update the cached set of add-ons. Throws on invalid input.
*
* @param json a valid add-ons JSON string.
*/
public void setJSONForAddons(String json) throws Exception {
addons = new JSONObject(json);
needsWrite = true;
}
public void setJSONForAddons(JSONObject json) {
addons = json;
needsWrite = true;
}
}

View File

@ -1,90 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package org.mozilla.gecko.background.healthreport.prune;
import org.mozilla.gecko.background.BackgroundService;
import org.mozilla.gecko.background.common.GlobalConstants;
import org.mozilla.gecko.background.common.log.Logger;
import org.mozilla.gecko.background.healthreport.HealthReportConstants;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.IBinder;
/**
* A <code>Service</code> to prune unnecessary or excessive health report data.
*
* We extend <code>IntentService</code>, rather than just <code>Service</code>,
* because this gives us a worker thread to avoid excessive main-thread disk access.
*/
public class HealthReportPruneService extends BackgroundService {
public static final String LOG_TAG = HealthReportPruneService.class.getSimpleName();
public static final String WORKER_THREAD_NAME = LOG_TAG + "Worker";
public HealthReportPruneService() {
super(WORKER_THREAD_NAME);
}
@Override
public IBinder onBind(Intent intent) {
return null;
}
protected SharedPreferences getSharedPreferences() {
return this.getSharedPreferences(HealthReportConstants.PREFS_BRANCH, GlobalConstants.SHARED_PREFERENCES_MODE);
}
@Override
public void onHandleIntent(Intent intent) {
Logger.setThreadLogTag(HealthReportConstants.GLOBAL_LOG_TAG);
// Intent can be null. Bug 1025937.
if (intent == null) {
Logger.debug(LOG_TAG, "Short-circuiting on null intent.");
return;
}
Logger.debug(LOG_TAG, "Handling prune intent.");
if (!isIntentValid(intent)) {
Logger.warn(LOG_TAG, "Intent not valid - returning.");
return;
}
final String profileName = intent.getStringExtra("profileName");
final String profilePath = intent.getStringExtra("profilePath");
Logger.debug(LOG_TAG, "Ticking for profile " + profileName + " at " + profilePath + ".");
final PrunePolicy policy = getPrunePolicy(profilePath);
policy.tick(System.currentTimeMillis());
}
// Generator function wraps constructor for testing purposes.
protected PrunePolicy getPrunePolicy(final String profilePath) {
final PrunePolicyStorage storage = new PrunePolicyDatabaseStorage(this, profilePath);
return new PrunePolicy(storage, getSharedPreferences());
}
/**
* @param intent must be non-null.
* @return true if the supplied intent contains both profileName and profilePath.
*/
private static boolean isIntentValid(final Intent intent) {
boolean isValid = true;
final String profileName = intent.getStringExtra("profileName");
if (profileName == null) {
Logger.warn(LOG_TAG, "Got intent without profileName.");
isValid = false;
}
final String profilePath = intent.getStringExtra("profilePath");
if (profilePath == null) {
Logger.warn(LOG_TAG, "Got intent without profilePath.");
isValid = false;
}
return isValid;
}
}

View File

@ -1,233 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package org.mozilla.gecko.background.healthreport.prune;
import org.mozilla.gecko.background.common.log.Logger;
import org.mozilla.gecko.background.healthreport.HealthReportConstants;
import android.content.SharedPreferences;
/**
* Manages scheduling of the pruning of old Firefox Health Report data.
*
* There are three main actions that take place:
* 1) Excessive storage pruning: The recorded data is taking up an unreasonable amount of space.
* 2) Expired data pruning: Data that is kept around longer than is useful.
* 3) Cleanup: To deal with storage maintenance (e.g. bloat and fragmentation)
*
* (1) and (2) are performed periodically on their own schedules. (3) will activate after a
* certain duration but only after (1) or (2) is performed.
*/
public class PrunePolicy {
public static final String LOG_TAG = PrunePolicy.class.getSimpleName();
protected final PrunePolicyStorage storage;
protected final SharedPreferences sharedPreferences;
protected final Editor editor;
public PrunePolicy(final PrunePolicyStorage storage, final SharedPreferences sharedPrefs) {
this.storage = storage;
this.sharedPreferences = sharedPrefs;
this.editor = new Editor(this.sharedPreferences.edit());
}
protected SharedPreferences getSharedPreferences() {
return this.sharedPreferences;
}
public void tick(final long time) {
try {
try {
boolean pruned = attemptPruneBySize(time);
pruned = attemptExpiration(time) || pruned;
// We only need to cleanup after a large pruning.
if (pruned) {
attemptStorageCleanup(time);
}
} catch (Exception e) {
// While catching Exception is ordinarily bad form, this Service runs in the same process
// as Fennec so if we crash, it crashes. Additionally, this Service runs regularly so
// these crashes could be regular. Thus, we choose to quietly fail instead.
Logger.error(LOG_TAG, "Got exception pruning document.", e);
} finally {
editor.commit();
}
} catch (Exception e) {
Logger.error(LOG_TAG, "Got exception committing to SharedPreferences.", e);
} finally {
storage.close();
}
}
protected boolean attemptPruneBySize(final long time) {
final long nextPrune = getNextPruneBySizeTime();
if (nextPrune < 0) {
Logger.debug(LOG_TAG, "Initializing prune-by-size time.");
editor.setNextPruneBySizeTime(time + getMinimumTimeBetweenPruneBySizeChecks());
return false;
}
// If the system clock is skewed into the past, making the time between prunes too long, reset
// the clock.
if (nextPrune > getMinimumTimeBetweenPruneBySizeChecks() + time) {
Logger.debug(LOG_TAG, "Clock skew detected - resetting prune-by-size time.");
editor.setNextPruneBySizeTime(time + getMinimumTimeBetweenPruneBySizeChecks());
return false;
}
if (nextPrune > time) {
Logger.debug(LOG_TAG, "Skipping prune-by-size - wait period has not yet elapsed.");
return false;
}
Logger.debug(LOG_TAG, "Attempting prune-by-size.");
// Prune environments first because their cascading deletions may delete some events. These
// environments are pruned in order of least-recently used first. Note that orphaned
// environments are ignored here and should be removed elsewhere.
final int environmentCount = storage.getEnvironmentCount();
if (environmentCount > getMaxEnvironmentCount()) {
final int environmentPruneCount = environmentCount - getEnvironmentCountAfterPrune();
Logger.debug(LOG_TAG, "Pruning " + environmentPruneCount + " environments.");
storage.pruneEnvironments(environmentPruneCount);
}
final int eventCount = storage.getEventCount();
if (eventCount > getMaxEventCount()) {
final int eventPruneCount = eventCount - getEventCountAfterPrune();
Logger.debug(LOG_TAG, "Pruning up to " + eventPruneCount + " events.");
storage.pruneEvents(eventPruneCount);
}
editor.setNextPruneBySizeTime(time + getMinimumTimeBetweenPruneBySizeChecks());
return true;
}
protected boolean attemptExpiration(final long time) {
final long nextPrune = getNextExpirationTime();
if (nextPrune < 0) {
Logger.debug(LOG_TAG, "Initializing expiration time.");
editor.setNextExpirationTime(time + getMinimumTimeBetweenExpirationChecks());
return false;
}
// If the system clock is skewed into the past, making the time between prunes too long, reset
// the clock.
if (nextPrune > getMinimumTimeBetweenExpirationChecks() + time) {
Logger.debug(LOG_TAG, "Clock skew detected - resetting expiration time.");
editor.setNextExpirationTime(time + getMinimumTimeBetweenExpirationChecks());
return false;
}
if (nextPrune > time) {
Logger.debug(LOG_TAG, "Skipping expiration - wait period has not yet elapsed.");
return false;
}
final long oldEventTime = time - getEventExistenceDuration();
Logger.debug(LOG_TAG, "Pruning data older than " + oldEventTime + ".");
storage.deleteDataBefore(oldEventTime);
editor.setNextExpirationTime(time + getMinimumTimeBetweenExpirationChecks());
return true;
}
protected boolean attemptStorageCleanup(final long time) {
// Cleanup if max duration since last cleanup is exceeded.
final long nextCleanup = getNextCleanupTime();
if (nextCleanup < 0) {
Logger.debug(LOG_TAG, "Initializing cleanup time.");
editor.setNextCleanupTime(time + getMinimumTimeBetweenCleanupChecks());
return false;
}
// If the system clock is skewed into the past, making the time between cleanups too long,
// reset the clock.
if (nextCleanup > getMinimumTimeBetweenCleanupChecks() + time) {
Logger.debug(LOG_TAG, "Clock skew detected - resetting cleanup time.");
editor.setNextCleanupTime(time + getMinimumTimeBetweenCleanupChecks());
return false;
}
if (nextCleanup > time) {
Logger.debug(LOG_TAG, "Skipping cleanup - wait period has not yet elapsed.");
return false;
}
editor.setNextCleanupTime(time + getMinimumTimeBetweenCleanupChecks());
Logger.debug(LOG_TAG, "Cleaning up storage.");
storage.cleanup();
return true;
}
protected static class Editor {
protected final SharedPreferences.Editor editor;
public Editor(final SharedPreferences.Editor editor) {
this.editor = editor;
}
public void commit() {
editor.commit();
}
public Editor setNextExpirationTime(final long time) {
editor.putLong(HealthReportConstants.PREF_EXPIRATION_TIME, time);
return this;
}
public Editor setNextPruneBySizeTime(final long time) {
editor.putLong(HealthReportConstants.PREF_PRUNE_BY_SIZE_TIME, time);
return this;
}
public Editor setNextCleanupTime(final long time) {
editor.putLong(HealthReportConstants.PREF_CLEANUP_TIME, time);
return this;
}
}
private long getNextExpirationTime() {
return getSharedPreferences().getLong(HealthReportConstants.PREF_EXPIRATION_TIME, -1L);
}
private long getEventExistenceDuration() {
return HealthReportConstants.EVENT_EXISTENCE_DURATION;
}
private long getMinimumTimeBetweenExpirationChecks() {
return HealthReportConstants.MINIMUM_TIME_BETWEEN_EXPIRATION_CHECKS_MILLIS;
}
private long getNextPruneBySizeTime() {
return getSharedPreferences().getLong(HealthReportConstants.PREF_PRUNE_BY_SIZE_TIME, -1L);
}
private long getMinimumTimeBetweenPruneBySizeChecks() {
return HealthReportConstants.MINIMUM_TIME_BETWEEN_PRUNE_BY_SIZE_CHECKS_MILLIS;
}
private int getMaxEnvironmentCount() {
return HealthReportConstants.MAX_ENVIRONMENT_COUNT;
}
private int getEnvironmentCountAfterPrune() {
return HealthReportConstants.ENVIRONMENT_COUNT_AFTER_PRUNE;
}
private int getMaxEventCount() {
return HealthReportConstants.MAX_EVENT_COUNT;
}
private int getEventCountAfterPrune() {
return HealthReportConstants.EVENT_COUNT_AFTER_PRUNE;
}
private long getNextCleanupTime() {
return getSharedPreferences().getLong(HealthReportConstants.PREF_CLEANUP_TIME, -1L);
}
private long getMinimumTimeBetweenCleanupChecks() {
return HealthReportConstants.MINIMUM_TIME_BETWEEN_CLEANUP_CHECKS_MILLIS;
}
}

Some files were not shown because too many files have changed in this diff Show More