mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
Merge mozilla-central to mozilla-inbound
This commit is contained in:
commit
b0e2b7fa3c
@ -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
|
||||
|
@ -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);
|
||||
|
@ -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>
|
||||
|
195
b2g/config/aries-l/sources.xml
Normal file
195
b2g/config/aries-l/sources.xml
Normal 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>
|
@ -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"/>
|
||||
|
@ -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"/>
|
||||
|
@ -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"/>
|
||||
|
@ -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 -->
|
||||
|
@ -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"/>
|
||||
|
@ -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"/>
|
||||
|
@ -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"/>
|
||||
|
@ -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"/>
|
||||
|
@ -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"
|
||||
}
|
||||
|
@ -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"/>
|
||||
|
@ -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 -->
|
||||
|
@ -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"/>
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
|
@ -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() {
|
||||
|
@ -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;
|
||||
|
@ -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]
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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");
|
||||
});
|
||||
|
@ -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() {
|
||||
|
@ -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();
|
||||
}
|
@ -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");
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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);
|
||||
});
|
@ -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.
|
||||
}
|
||||
|
@ -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",
|
||||
|
@ -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;
|
||||
|
@ -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");
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
@ -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]
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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,
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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);
|
||||
},
|
||||
|
@ -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]
|
||||
|
@ -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);
|
||||
});
|
@ -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
|
||||
|
@ -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(
|
||||
|
@ -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,
|
||||
|
@ -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()
|
||||
|
@ -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
|
||||
|
@ -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();
|
||||
},
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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) {
|
||||
|
@ -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() {
|
||||
|
@ -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();
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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 {
|
||||
|
@ -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:
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
@ -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',
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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) {
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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 = {
|
||||
|
@ -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',
|
||||
|
@ -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);
|
||||
|
@ -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() {
|
||||
|
@ -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.
|
||||
|
@ -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>
|
@ -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" />
|
@ -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>
|
@ -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" />
|
||||
|
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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;
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -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();
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
}
|
@ -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);
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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
Loading…
Reference in New Issue
Block a user