This commit is contained in:
Ryan VanderMeulen 2014-06-23 10:57:40 -04:00
Родитель 9d6e50b205 1ea076bea7
Коммит bee8ae28a0
131 изменённых файлов: 2579 добавлений и 1931 удалений

Просмотреть файл

@ -19,13 +19,13 @@
<copyfile dest="Makefile" src="core/root.mk"/> <copyfile dest="Makefile" src="core/root.mk"/>
</project> </project>
<project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/> <project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="bd5065ced020014df5fd45259fba1ac32d65673b"/> <project name="gaia.git" path="gaia" remote="mozillaorg" revision="41cc1de26e4edbe12add0009cdc0bd292f2e94fe"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="44b04243e31cd16f3baf54fcd4b5fecf9deb8b94"/> <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="bbb7659d8ea2afb396f99b3dc971ab3c42da3778"/>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/> <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
<project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="cd88d860656c31c7da7bb310d6a160d0011b0961"/> <project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="cd88d860656c31c7da7bb310d6a160d0011b0961"/>
<project name="platform_external_qemu" path="external/qemu" remote="b2g" revision="3326b51017252e09ccdd715dec6c5e12a7d1ecfe"/> <project name="platform_external_qemu" path="external/qemu" remote="b2g" revision="3326b51017252e09ccdd715dec6c5e12a7d1ecfe"/>
<project name="moztt" path="external/moztt" remote="b2g" revision="c510babaf88dfa2cfe2c202afb2649ee124569af"/> <project name="moztt" path="external/moztt" remote="b2g" revision="c510babaf88dfa2cfe2c202afb2649ee124569af"/>
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="8ba14125aba912707f44761f194339e6e59701b7"/> <project name="apitrace" path="external/apitrace" remote="apitrace" revision="23db533981ee2cd04fc5d946420402aed2792381"/>
<!-- Stock Android things --> <!-- Stock Android things -->
<project name="platform/abi/cpp" path="abi/cpp" revision="dd924f92906085b831bf1cbbc7484d3c043d613c"/> <project name="platform/abi/cpp" path="abi/cpp" revision="dd924f92906085b831bf1cbbc7484d3c043d613c"/>
<project name="platform/bionic" path="bionic" revision="c72b8f6359de7ed17c11ddc9dfdde3f615d188a9"/> <project name="platform/bionic" path="bionic" revision="c72b8f6359de7ed17c11ddc9dfdde3f615d188a9"/>

Просмотреть файл

@ -17,10 +17,10 @@
</project> </project>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/> <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/> <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="bd5065ced020014df5fd45259fba1ac32d65673b"/> <project name="gaia" path="gaia" remote="mozillaorg" revision="41cc1de26e4edbe12add0009cdc0bd292f2e94fe"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="44b04243e31cd16f3baf54fcd4b5fecf9deb8b94"/> <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="bbb7659d8ea2afb396f99b3dc971ab3c42da3778"/>
<project name="moztt" path="external/moztt" remote="b2g" revision="c510babaf88dfa2cfe2c202afb2649ee124569af"/> <project name="moztt" path="external/moztt" remote="b2g" revision="c510babaf88dfa2cfe2c202afb2649ee124569af"/>
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="8ba14125aba912707f44761f194339e6e59701b7"/> <project name="apitrace" path="external/apitrace" remote="apitrace" revision="23db533981ee2cd04fc5d946420402aed2792381"/>
<project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/> <project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>
<project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/> <project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/>
<!-- Stock Android things --> <!-- Stock Android things -->
@ -128,7 +128,7 @@
<project name="device/generic/armv7-a-neon" path="device/generic/armv7-a-neon" revision="3a9a17613cc685aa232432566ad6cc607eab4ec1"/> <project name="device/generic/armv7-a-neon" path="device/generic/armv7-a-neon" revision="3a9a17613cc685aa232432566ad6cc607eab4ec1"/>
<project name="device_generic_goldfish" path="device/generic/goldfish" remote="b2g" revision="0e31f35a2a77301e91baa8a237aa9e9fa4076084"/> <project name="device_generic_goldfish" path="device/generic/goldfish" remote="b2g" revision="0e31f35a2a77301e91baa8a237aa9e9fa4076084"/>
<project name="platform/external/libnfc-nci" path="external/libnfc-nci" revision="7d33aaf740bbf6c7c6e9c34a92b371eda311b66b"/> <project name="platform/external/libnfc-nci" path="external/libnfc-nci" revision="7d33aaf740bbf6c7c6e9c34a92b371eda311b66b"/>
<project name="platform_external_qemu" path="external/qemu" remote="b2g" revision="973367035a1f2545f3dad6e40e354463dc56a7f4"/> <project name="platform_external_qemu" path="external/qemu" remote="b2g" revision="346c7694b156a3933f3d87cbc077c657e2ce571f"/>
<project name="platform/external/wpa_supplicant_8" path="external/wpa_supplicant_8" revision="0e56e450367cd802241b27164a2979188242b95f"/> <project name="platform/external/wpa_supplicant_8" path="external/wpa_supplicant_8" revision="0e56e450367cd802241b27164a2979188242b95f"/>
<project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="832f4acaf481a19031e479a40b03d9ce5370ddee"/> <project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="832f4acaf481a19031e479a40b03d9ce5370ddee"/>
<project name="platform_system_nfcd" path="system/nfcd" remote="b2g" revision="d0aa65b140a45016975ed0ecf35f280dd336e1d3"/> <project name="platform_system_nfcd" path="system/nfcd" remote="b2g" revision="d0aa65b140a45016975ed0ecf35f280dd336e1d3"/>

Просмотреть файл

@ -15,15 +15,15 @@
<project name="platform_build" path="build" remote="b2g" revision="276ce45e78b09c4a4ee643646f691d22804754c1"> <project name="platform_build" path="build" remote="b2g" revision="276ce45e78b09c4a4ee643646f691d22804754c1">
<copyfile dest="Makefile" src="core/root.mk"/> <copyfile dest="Makefile" src="core/root.mk"/>
</project> </project>
<project name="gaia" path="gaia" remote="mozillaorg" revision="bd5065ced020014df5fd45259fba1ac32d65673b"/> <project name="gaia" path="gaia" remote="mozillaorg" revision="41cc1de26e4edbe12add0009cdc0bd292f2e94fe"/>
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/> <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="44b04243e31cd16f3baf54fcd4b5fecf9deb8b94"/> <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="bbb7659d8ea2afb396f99b3dc971ab3c42da3778"/>
<project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/> <project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>
<project name="moztt" path="external/moztt" remote="b2g" revision="c510babaf88dfa2cfe2c202afb2649ee124569af"/> <project name="moztt" path="external/moztt" remote="b2g" revision="c510babaf88dfa2cfe2c202afb2649ee124569af"/>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/> <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
<project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/> <project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>
<project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/> <project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/>
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="8ba14125aba912707f44761f194339e6e59701b7"/> <project name="apitrace" path="external/apitrace" remote="apitrace" revision="23db533981ee2cd04fc5d946420402aed2792381"/>
<!-- Stock Android things --> <!-- 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/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"/> <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"/>

Просмотреть файл

@ -19,13 +19,13 @@
<copyfile dest="Makefile" src="core/root.mk"/> <copyfile dest="Makefile" src="core/root.mk"/>
</project> </project>
<project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/> <project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="bd5065ced020014df5fd45259fba1ac32d65673b"/> <project name="gaia.git" path="gaia" remote="mozillaorg" revision="41cc1de26e4edbe12add0009cdc0bd292f2e94fe"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="44b04243e31cd16f3baf54fcd4b5fecf9deb8b94"/> <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="bbb7659d8ea2afb396f99b3dc971ab3c42da3778"/>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/> <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
<project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="cd88d860656c31c7da7bb310d6a160d0011b0961"/> <project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="cd88d860656c31c7da7bb310d6a160d0011b0961"/>
<project name="platform_external_qemu" path="external/qemu" remote="b2g" revision="3326b51017252e09ccdd715dec6c5e12a7d1ecfe"/> <project name="platform_external_qemu" path="external/qemu" remote="b2g" revision="3326b51017252e09ccdd715dec6c5e12a7d1ecfe"/>
<project name="moztt" path="external/moztt" remote="b2g" revision="c510babaf88dfa2cfe2c202afb2649ee124569af"/> <project name="moztt" path="external/moztt" remote="b2g" revision="c510babaf88dfa2cfe2c202afb2649ee124569af"/>
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="8ba14125aba912707f44761f194339e6e59701b7"/> <project name="apitrace" path="external/apitrace" remote="apitrace" revision="23db533981ee2cd04fc5d946420402aed2792381"/>
<!-- Stock Android things --> <!-- Stock Android things -->
<project name="platform/abi/cpp" path="abi/cpp" revision="dd924f92906085b831bf1cbbc7484d3c043d613c"/> <project name="platform/abi/cpp" path="abi/cpp" revision="dd924f92906085b831bf1cbbc7484d3c043d613c"/>
<project name="platform/bionic" path="bionic" revision="c72b8f6359de7ed17c11ddc9dfdde3f615d188a9"/> <project name="platform/bionic" path="bionic" revision="c72b8f6359de7ed17c11ddc9dfdde3f615d188a9"/>

Просмотреть файл

@ -17,10 +17,10 @@
</project> </project>
<project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/> <project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/> <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="bd5065ced020014df5fd45259fba1ac32d65673b"/> <project name="gaia" path="gaia" remote="mozillaorg" revision="41cc1de26e4edbe12add0009cdc0bd292f2e94fe"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="44b04243e31cd16f3baf54fcd4b5fecf9deb8b94"/> <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="bbb7659d8ea2afb396f99b3dc971ab3c42da3778"/>
<project name="moztt" path="external/moztt" remote="b2g" revision="c510babaf88dfa2cfe2c202afb2649ee124569af"/> <project name="moztt" path="external/moztt" remote="b2g" revision="c510babaf88dfa2cfe2c202afb2649ee124569af"/>
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="8ba14125aba912707f44761f194339e6e59701b7"/> <project name="apitrace" path="external/apitrace" remote="apitrace" revision="23db533981ee2cd04fc5d946420402aed2792381"/>
<project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/> <project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>
<project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/> <project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/>
<!-- Stock Android things --> <!-- Stock Android things -->

Просмотреть файл

@ -4,6 +4,6 @@
"remote": "", "remote": "",
"branch": "" "branch": ""
}, },
"revision": "387f4c0123a7e82eae4c83f88f73e81f907c00e2", "revision": "aba00cfd579caaf205e05c269f0a8100f242f39c",
"repo_path": "/integration/gaia-central" "repo_path": "/integration/gaia-central"
} }

Просмотреть файл

@ -17,12 +17,12 @@
<copyfile dest="Makefile" src="core/root.mk"/> <copyfile dest="Makefile" src="core/root.mk"/>
</project> </project>
<project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/> <project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="bd5065ced020014df5fd45259fba1ac32d65673b"/> <project name="gaia.git" path="gaia" remote="mozillaorg" revision="41cc1de26e4edbe12add0009cdc0bd292f2e94fe"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="44b04243e31cd16f3baf54fcd4b5fecf9deb8b94"/> <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="bbb7659d8ea2afb396f99b3dc971ab3c42da3778"/>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/> <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
<project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/> <project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>
<project name="moztt" path="external/moztt" remote="b2g" revision="c510babaf88dfa2cfe2c202afb2649ee124569af"/> <project name="moztt" path="external/moztt" remote="b2g" revision="c510babaf88dfa2cfe2c202afb2649ee124569af"/>
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="8ba14125aba912707f44761f194339e6e59701b7"/> <project name="apitrace" path="external/apitrace" remote="apitrace" revision="23db533981ee2cd04fc5d946420402aed2792381"/>
<!-- Stock Android things --> <!-- Stock Android things -->
<project name="platform/abi/cpp" path="abi/cpp" revision="6426040f1be4a844082c9769171ce7f5341a5528"/> <project name="platform/abi/cpp" path="abi/cpp" revision="6426040f1be4a844082c9769171ce7f5341a5528"/>
<project name="platform/bionic" path="bionic" revision="d2eb6c7b6e1bc7643c17df2d9d9bcb1704d0b9ab"/> <project name="platform/bionic" path="bionic" revision="d2eb6c7b6e1bc7643c17df2d9d9bcb1704d0b9ab"/>

Просмотреть файл

@ -15,8 +15,8 @@
<copyfile dest="Makefile" src="core/root.mk"/> <copyfile dest="Makefile" src="core/root.mk"/>
</project> </project>
<project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/> <project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="bd5065ced020014df5fd45259fba1ac32d65673b"/> <project name="gaia.git" path="gaia" remote="mozillaorg" revision="41cc1de26e4edbe12add0009cdc0bd292f2e94fe"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="44b04243e31cd16f3baf54fcd4b5fecf9deb8b94"/> <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="bbb7659d8ea2afb396f99b3dc971ab3c42da3778"/>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/> <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
<project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/> <project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>
<project name="moztt" path="external/moztt" remote="b2g" revision="c510babaf88dfa2cfe2c202afb2649ee124569af"/> <project name="moztt" path="external/moztt" remote="b2g" revision="c510babaf88dfa2cfe2c202afb2649ee124569af"/>

Просмотреть файл

@ -17,10 +17,10 @@
</project> </project>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/> <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/> <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="bd5065ced020014df5fd45259fba1ac32d65673b"/> <project name="gaia" path="gaia" remote="mozillaorg" revision="41cc1de26e4edbe12add0009cdc0bd292f2e94fe"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="44b04243e31cd16f3baf54fcd4b5fecf9deb8b94"/> <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="bbb7659d8ea2afb396f99b3dc971ab3c42da3778"/>
<project name="moztt" path="external/moztt" remote="b2g" revision="c510babaf88dfa2cfe2c202afb2649ee124569af"/> <project name="moztt" path="external/moztt" remote="b2g" revision="c510babaf88dfa2cfe2c202afb2649ee124569af"/>
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="8ba14125aba912707f44761f194339e6e59701b7"/> <project name="apitrace" path="external/apitrace" remote="apitrace" revision="23db533981ee2cd04fc5d946420402aed2792381"/>
<project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/> <project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>
<project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/> <project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/>
<!-- Stock Android things --> <!-- Stock Android things -->

Просмотреть файл

@ -17,12 +17,12 @@
<copyfile dest="Makefile" src="core/root.mk"/> <copyfile dest="Makefile" src="core/root.mk"/>
</project> </project>
<project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/> <project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="bd5065ced020014df5fd45259fba1ac32d65673b"/> <project name="gaia.git" path="gaia" remote="mozillaorg" revision="41cc1de26e4edbe12add0009cdc0bd292f2e94fe"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="44b04243e31cd16f3baf54fcd4b5fecf9deb8b94"/> <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="bbb7659d8ea2afb396f99b3dc971ab3c42da3778"/>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/> <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
<project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/> <project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>
<project name="moztt" path="external/moztt" remote="b2g" revision="c510babaf88dfa2cfe2c202afb2649ee124569af"/> <project name="moztt" path="external/moztt" remote="b2g" revision="c510babaf88dfa2cfe2c202afb2649ee124569af"/>
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="8ba14125aba912707f44761f194339e6e59701b7"/> <project name="apitrace" path="external/apitrace" remote="apitrace" revision="23db533981ee2cd04fc5d946420402aed2792381"/>
<project name="gonk-patches" path="patches" remote="b2g" revision="223a2421006e8f5da33f516f6891c87cae86b0f6"/> <project name="gonk-patches" path="patches" remote="b2g" revision="223a2421006e8f5da33f516f6891c87cae86b0f6"/>
<!-- Stock Android things --> <!-- Stock Android things -->
<project name="platform/abi/cpp" path="abi/cpp" revision="6426040f1be4a844082c9769171ce7f5341a5528"/> <project name="platform/abi/cpp" path="abi/cpp" revision="6426040f1be4a844082c9769171ce7f5341a5528"/>

Просмотреть файл

@ -1,5 +1,5 @@
<?xml version="1.0"?> <?xml version="1.0"?>
<blocklist xmlns="http://www.mozilla.org/2006/addons-blocklist" lastupdate="1402612020000"> <blocklist xmlns="http://www.mozilla.org/2006/addons-blocklist" lastupdate="1403216209000">
<emItems> <emItems>
<emItem blockID="i454" id="sqlmoz@facebook.com"> <emItem blockID="i454" id="sqlmoz@facebook.com">
<versionRange minVersion="0" maxVersion="*" severity="3"> <versionRange minVersion="0" maxVersion="*" severity="3">
@ -88,6 +88,12 @@
</versionRange> </versionRange>
<prefs> <prefs>
</prefs> </prefs>
</emItem>
<emItem blockID="i626" id="{20AD702C-661E-4534-8CE9-BA4EC9AD6ECC}">
<versionRange minVersion="0" maxVersion="*" severity="3">
</versionRange>
<prefs>
</prefs>
</emItem> </emItem>
<emItem blockID="i20" id="{AB2CE124-6272-4b12-94A9-7303C7397BD1}"> <emItem blockID="i20" id="{AB2CE124-6272-4b12-94A9-7303C7397BD1}">
<versionRange minVersion="0.1" maxVersion="5.2.0.7164" severity="1"> <versionRange minVersion="0.1" maxVersion="5.2.0.7164" severity="1">
@ -304,10 +310,8 @@
<prefs> <prefs>
</prefs> </prefs>
</emItem> </emItem>
<emItem blockID="i496" id="{ACAA314B-EEBA-48e4-AD47-84E31C44796C}"> <emItem blockID="i47" id="youtube@youtube2.com">
<versionRange minVersion="0" maxVersion="*" severity="1"> <prefs>
</versionRange>
<prefs>
</prefs> </prefs>
</emItem> </emItem>
<emItem blockID="i360" id="ytd@mybrowserbar.com"> <emItem blockID="i360" id="ytd@mybrowserbar.com">
@ -365,8 +369,8 @@
<prefs> <prefs>
</prefs> </prefs>
</emItem> </emItem>
<emItem blockID="i584" id="{52b0f3db-f988-4788-b9dc-861d016f4487}"> <emItem blockID="i624" id="/^({b95faac1-a3d7-4d69-8943-ddd5a487d966}|{ecce0073-a837-45a2-95b9-600420505f7e}|{2713b394-286f-4d7c-89ea-4174eeab9f5a}|{da7a20cf-bef4-4342-ad78-0240fdf87055})$/">
<versionRange minVersion="0" maxVersion="0.1.9999999" severity="1"> <versionRange minVersion="0" maxVersion="*" severity="1">
</versionRange> </versionRange>
<prefs> <prefs>
</prefs> </prefs>
@ -697,7 +701,7 @@
</prefs> </prefs>
</emItem> </emItem>
<emItem blockID="i620" id="{21EAF666-26B3-4A3C-ABD0-CA2F5A326744}"> <emItem blockID="i620" id="{21EAF666-26B3-4A3C-ABD0-CA2F5A326744}">
<versionRange minVersion="0" maxVersion="*" severity="1"> <versionRange minVersion="0" maxVersion="*" severity="3">
</versionRange> </versionRange>
<prefs> <prefs>
</prefs> </prefs>
@ -713,6 +717,12 @@
</versionRange> </versionRange>
<prefs> <prefs>
</prefs> </prefs>
</emItem>
<emItem blockID="i584" id="{52b0f3db-f988-4788-b9dc-861d016f4487}">
<versionRange minVersion="0" maxVersion="0.1.9999999" severity="1">
</versionRange>
<prefs>
</prefs>
</emItem> </emItem>
<emItem blockID="i370" id="happylyrics@hpyproductions.net"> <emItem blockID="i370" id="happylyrics@hpyproductions.net">
<versionRange minVersion="0" maxVersion="*" severity="1"> <versionRange minVersion="0" maxVersion="*" severity="1">
@ -1211,8 +1221,10 @@
<prefs> <prefs>
</prefs> </prefs>
</emItem> </emItem>
<emItem blockID="i47" id="youtube@youtube2.com"> <emItem blockID="i622" id="/^({ebd898f8-fcf6-4694-bc3b-eabc7271eeb1}|{46008e0d-47ac-4daa-a02a-5eb69044431a}|{213c8ed6-1d78-4d8f-8729-25006aa86a76}|{fa23121f-ee7c-4bd8-8c06-123d087282c5}|{19803860-b306-423c-bbb5-f60a7d82cde5})$/">
<prefs> <versionRange minVersion="0" maxVersion="*" severity="1">
</versionRange>
<prefs>
</prefs> </prefs>
</emItem> </emItem>
<emItem blockID="i518" id="/^({d6e79525-4524-4707-9b97-1d70df8e7e59}|{ddb4644d-1a37-4e6d-8b6e-8e35e2a8ea6c}|{e55007f4-80c5-418e-ac33-10c4d60db01e}|{e77d8ca6-3a60-4ae9-8461-53b22fa3125b}|{e89a62b7-248e-492f-9715-43bf8c507a2f}|{5ce3e0cb-aa83-45cb-a7da-a2684f05b8f3})$/"> <emItem blockID="i518" id="/^({d6e79525-4524-4707-9b97-1d70df8e7e59}|{ddb4644d-1a37-4e6d-8b6e-8e35e2a8ea6c}|{e55007f4-80c5-418e-ac33-10c4d60db01e}|{e77d8ca6-3a60-4ae9-8461-53b22fa3125b}|{e89a62b7-248e-492f-9715-43bf8c507a2f}|{5ce3e0cb-aa83-45cb-a7da-a2684f05b8f3})$/">
@ -1507,6 +1519,12 @@
</versionRange> </versionRange>
<prefs> <prefs>
</prefs> </prefs>
</emItem>
<emItem blockID="i496" id="{ACAA314B-EEBA-48e4-AD47-84E31C44796C}">
<versionRange minVersion="0" maxVersion="*" severity="1">
</versionRange>
<prefs>
</prefs>
</emItem> </emItem>
<emItem blockID="i67" id="youtube2@youtube2.com"> <emItem blockID="i67" id="youtube2@youtube2.com">
<versionRange minVersion="0" maxVersion="*"> <versionRange minVersion="0" maxVersion="*">

Просмотреть файл

@ -188,5 +188,11 @@ var gContentPane = {
document.documentElement.openWindow("Browser:TranslationExceptions", document.documentElement.openWindow("Browser:TranslationExceptions",
"chrome://browser/content/preferences/translation.xul", "chrome://browser/content/preferences/translation.xul",
"", null); "", null);
},
openTranslationProviderAttribution: function ()
{
Components.utils.import("resource:///modules/translation/Translation.jsm");
Translation.openProviderAttribution();
} }
}; };

Просмотреть файл

@ -144,10 +144,16 @@
oncommand="gContentPane.showLanguages();"/> oncommand="gContentPane.showLanguages();"/>
</row> </row>
<row id="translationBox" hidden="true"> <row id="translationBox" hidden="true">
<checkbox id="translate" preference="browser.translation.detectLanguage" flex="1" <hbox align="center">
label="&translateWebPages.label;" accesskey="&translateWebPages.accesskey;" <checkbox id="translate" preference="browser.translation.detectLanguage"
onsyncfrompreference="return gContentPane.updateButtons('translateButton', label="&translateWebPages.label;." accesskey="&translateWebPages.accesskey;"
'browser.translation.detectLanguage');"/> onsyncfrompreference="return gContentPane.updateButtons('translateButton',
'browser.translation.detectLanguage');"/>
<label>Übersetzungen von</label>
<image id="translationAttributionImage" aria-label="Microsoft Translator"
onclick="gContentPane.openTranslationProviderAttribution()"
src="chrome://browser/content/microsoft-translator-attribution.png"/>
</hbox>
<button id="translateButton" label="&translateExceptions.label;" <button id="translateButton" label="&translateExceptions.label;"
oncommand="gContentPane.showTranslationExceptions();" oncommand="gContentPane.showTranslationExceptions();"
accesskey="&translateExceptions.accesskey;"/> accesskey="&translateExceptions.accesskey;"/>

Просмотреть файл

@ -186,5 +186,11 @@ var gContentPane = {
{ {
openDialog("chrome://browser/content/preferences/translation.xul", openDialog("chrome://browser/content/preferences/translation.xul",
"Browser:TranslationExceptions", null); "Browser:TranslationExceptions", null);
},
openTranslationProviderAttribution: function ()
{
Components.utils.import("resource:///modules/translation/Translation.jsm");
Translation.openProviderAttribution();
} }
}; };

Просмотреть файл

@ -134,10 +134,17 @@
</hbox> </hbox>
<hbox id="translationBox" hidden="true"> <hbox id="translationBox" hidden="true">
<checkbox id="translate" preference="browser.translation.detectLanguage" flex="1" <hbox align="center" flex="1">
label="&translateWebPages.label;" accesskey="&translateWebPages.accesskey;" <checkbox id="translate" preference="browser.translation.detectLanguage"
onsyncfrompreference="return gContentPane.updateButtons('translateButton', label="&translateWebPages.label;." accesskey="&translateWebPages.accesskey;"
'browser.translation.detectLanguage');"/> onsyncfrompreference="return gContentPane.updateButtons('translateButton',
'browser.translation.detectLanguage');"/>
<label>Übersetzungen von</label>
<separator orient="vertical" class="thin"/>
<image id="translationAttributionImage" aria-label="Microsoft Translator"
onclick="gContentPane.openTranslationProviderAttribution()"
src="chrome://browser/content/microsoft-translator-attribution.png"/>
</hbox>
<button id="translateButton" label="&translateExceptions.label;" <button id="translateButton" label="&translateExceptions.label;"
oncommand="gContentPane.showTranslationExceptions();" oncommand="gContentPane.showTranslationExceptions();"
accesskey="&translateExceptions.accesskey;"/> accesskey="&translateExceptions.accesskey;"/>

Просмотреть файл

@ -80,6 +80,12 @@ this.Translation = {
if (trUI.shouldShowInfoBar(aBrowser.currentURI)) if (trUI.shouldShowInfoBar(aBrowser.currentURI))
trUI.showTranslationInfoBar(); trUI.showTranslationInfoBar();
},
openProviderAttribution: function() {
Cu.import("resource:///modules/RecentWindow.jsm");
RecentWindow.getMostRecentBrowserWindow().openUILinkIn(
"http://aka.ms/MicrosoftTranslatorAttribution", "tab");
} }
}; };
@ -305,6 +311,13 @@ let TranslationHealthReport = {
this._withProvider(provider => provider.recordLanguageChange(beforeFirstTranslation)); this._withProvider(provider => provider.recordLanguageChange(beforeFirstTranslation));
}, },
/**
* Record a denied translation offer.
*/
recordDeniedTranslationOffer: function () {
this._withProvider(provider => provider.recordDeniedTranslationOffer());
},
/** /**
* Retrieve the translation provider and pass it to the given function. * Retrieve the translation provider and pass it to the given function.
* *
@ -363,6 +376,7 @@ TranslationMeasurement1.prototype = Object.freeze({
pageTranslatedCountsByLanguage: DAILY_LAST_TEXT_FIELD, pageTranslatedCountsByLanguage: DAILY_LAST_TEXT_FIELD,
detectedLanguageChangedBefore: DAILY_COUNTER_FIELD, detectedLanguageChangedBefore: DAILY_COUNTER_FIELD,
detectedLanguageChangedAfter: DAILY_COUNTER_FIELD, detectedLanguageChangedAfter: DAILY_COUNTER_FIELD,
deniedTranslationOffer: DAILY_COUNTER_FIELD,
detectLanguageEnabled: DAILY_LAST_NUMERIC_FIELD, detectLanguageEnabled: DAILY_LAST_NUMERIC_FIELD,
showTranslationUI: DAILY_LAST_NUMERIC_FIELD, showTranslationUI: DAILY_LAST_NUMERIC_FIELD,
}, },
@ -499,6 +513,15 @@ TranslationProvider.prototype = Object.freeze({
}.bind(this)); }.bind(this));
}, },
recordDeniedTranslationOffer: function () {
let m = this.getMeasurement(TranslationMeasurement1.prototype.name,
TranslationMeasurement1.prototype.version);
return this._enqueueTelemetryStorageTask(function* recordTask() {
yield m.incrementDailyCounter("deniedTranslationOffer");
}.bind(this));
},
collectDailyData: function () { collectDailyData: function () {
let m = this.getMeasurement(TranslationMeasurement1.prototype.name, let m = this.getMeasurement(TranslationMeasurement1.prototype.name,
TranslationMeasurement1.prototype.version); TranslationMeasurement1.prototype.version);

Просмотреть файл

@ -3,3 +3,4 @@
# file, You can obtain one at http://mozilla.org/MPL/2.0/. # file, You can obtain one at http://mozilla.org/MPL/2.0/.
browser.jar: browser.jar:
content/browser/translation-infobar.xml content/browser/translation-infobar.xml
content/browser/microsoft-translator-attribution.png

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 3.3 KiB

Просмотреть файл

@ -196,6 +196,28 @@ add_task(function* test_record_translation() {
yield storage.close(); yield storage.close();
}); });
add_task(function* test_denied_translation_offer() {
let storage = yield Metrics.Storage("translation");
let provider = new TranslationProvider();
yield provider.init(storage);
let now = new Date();
yield provider.recordDeniedTranslationOffer();
yield provider.recordDeniedTranslationOffer();
let m = provider.getMeasurement("translation", 1);
let values = yield m.getValues();
Assert.equal(values.days.size, 1);
Assert.ok(values.days.hasDay(now));
let day = values.days.getDay(now);
Assert.ok(day.has("deniedTranslationOffer"));
Assert.equal(day.get("deniedTranslationOffer"), 2);
yield provider.shutdown();
yield storage.close();
});
add_task(function* test_collect_daily() { add_task(function* test_collect_daily() {
let storage = yield Metrics.Storage("translation"); let storage = yield Metrics.Storage("translation");
let provider = new TranslationProvider(); let provider = new TranslationProvider();
@ -253,6 +275,8 @@ add_task(function* test_healthreporter_json() {
yield provider.recordTranslationOpportunity("es", now); yield provider.recordTranslationOpportunity("es", now);
yield provider.recordTranslation("es", "en", 1000, now); yield provider.recordTranslation("es", "en", 1000, now);
yield provider.recordDeniedTranslationOffer();
yield reporter.collectMeasurements(); yield reporter.collectMeasurements();
let payload = yield reporter.getJSONPayload(true); let payload = yield reporter.getJSONPayload(true);
let today = reporter._formatDate(now); let today = reporter._formatDate(now);
@ -285,6 +309,9 @@ add_task(function* test_healthreporter_json() {
Assert.equal(translations["detectedLanguageChangedBefore"], 1); Assert.equal(translations["detectedLanguageChangedBefore"], 1);
Assert.ok("detectedLanguageChangedAfter" in translations); Assert.ok("detectedLanguageChangedAfter" in translations);
Assert.equal(translations["detectedLanguageChangedAfter"], 1); Assert.equal(translations["detectedLanguageChangedAfter"], 1);
Assert.ok("deniedTranslationOffer" in translations);
Assert.equal(translations["deniedTranslationOffer"], 1);
} finally { } finally {
reporter._shutdown(); reporter._shutdown();
} }
@ -309,6 +336,8 @@ add_task(function* test_healthreporter_json2() {
yield provider.recordTranslationOpportunity("es", now); yield provider.recordTranslationOpportunity("es", now);
yield provider.recordTranslation("es", "en", 1000, now); yield provider.recordTranslation("es", "en", 1000, now);
yield provider.recordDeniedTranslationOffer();
yield reporter.collectMeasurements(); yield reporter.collectMeasurements();
let payload = yield reporter.getJSONPayload(true); let payload = yield reporter.getJSONPayload(true);
let today = reporter._formatDate(now); let today = reporter._formatDate(now);
@ -327,6 +356,7 @@ add_task(function* test_healthreporter_json2() {
Assert.ok(!("pageTranslatedCountsByLanguage" in translations)); Assert.ok(!("pageTranslatedCountsByLanguage" in translations));
Assert.ok(!("detectedLanguageChangedBefore" in translations)); Assert.ok(!("detectedLanguageChangedBefore" in translations));
Assert.ok(!("detectedLanguageChangedAfter" in translations)); Assert.ok(!("detectedLanguageChangedAfter" in translations));
Assert.ok(!("deniedTranslationOffer" in translations));
} finally { } finally {
reporter._shutdown(); reporter._shutdown();
} }

Просмотреть файл

@ -98,7 +98,9 @@
class="translate-infobar-element options-menu-button" class="translate-infobar-element options-menu-button"
anonid="options" anonid="options"
label="&translation.options.menu;"> label="&translation.options.menu;">
<xul:menupopup onpopupshowing="document.getBindingParent(this).optionsShowing();"> <xul:menupopup class="translation-menupopup cui-widget-panel cui-widget-panelview
cui-widget-panelWithFooter PanelUI-subView"
onpopupshowing="document.getBindingParent(this).optionsShowing();">
<xul:menuitem anonid="neverForLanguage" <xul:menuitem anonid="neverForLanguage"
oncommand="document.getBindingParent(this).neverForLanguage();"/> oncommand="document.getBindingParent(this).neverForLanguage();"/>
<xul:menuitem anonid="neverForSite" <xul:menuitem anonid="neverForSite"
@ -109,6 +111,12 @@
<xul:menuitem oncommand="openPreferences('paneContent');" <xul:menuitem oncommand="openPreferences('paneContent');"
label="&translation.options.preferences.label;" label="&translation.options.preferences.label;"
accesskey="&translation.options.preferences.accesskey;"/> accesskey="&translation.options.preferences.accesskey;"/>
<xul:menuitem class="translation-attribution subviewbutton panel-subview-footer"
oncommand="document.getBindingParent(this).openProviderAttribution();">
<xul:label>Übersetzungen von</xul:label>
<xul:image src="chrome://browser/content/microsoft-translator-attribution.png"
aria-label="Microsoft Translator"/>
</xul:menuitem>
</xul:menupopup> </xul:menupopup>
</xul:button> </xul:button>
@ -310,6 +318,14 @@
</body> </body>
</method> </method>
<method name="openProviderAttribution">
<body>
<![CDATA[
Translation.openProviderAttribution();
]]>
</body>
</method>
</implementation> </implementation>
</binding> </binding>
</bindings> </bindings>

Просмотреть файл

@ -54,6 +54,12 @@ label.small {
margin: 5px; margin: 5px;
} }
/* Content Pane */
#translationAttributionImage {
width: 70px;
cursor: pointer;
}
/* Applications Pane */ /* Applications Pane */
#BrowserPreferences[animated="true"] #handlersView { #BrowserPreferences[animated="true"] #handlersView {
height: 25em; height: 25em;

Просмотреть файл

@ -140,6 +140,11 @@ caption {
border-bottom: 1px solid #ccc; border-bottom: 1px solid #ccc;
} }
#translationAttributionImage {
width: 70px;
cursor: pointer;
}
#browserUseCurrent, #browserUseCurrent,
#browserUseBookmark, #browserUseBookmark,
#browserUseBlank { #browserUseBlank {

Просмотреть файл

@ -46,3 +46,21 @@ notification[value="translation"] menulist > .menulist-dropmarker {
margin: auto; margin: auto;
padding: 5px 0; padding: 5px 0;
} }
.translation-menupopup arrowscrollbox {
padding-bottom: 0;
}
.translation-attribution {
cursor: pointer;
-moz-box-align: end;
font-size: small;
}
.translation-attribution > label {
margin-bottom: 0;
}
.translation-attribution > image {
width: 70px;
}

Просмотреть файл

@ -2443,6 +2443,10 @@ notification[value="translation"] {
-moz-image-region: rect(0px, 32px, 16px, 16px); -moz-image-region: rect(0px, 32px, 16px, 16px);
} }
.translation-menupopup {
-moz-appearance: none;
}
/* Bookmarks roots menu-items */ /* Bookmarks roots menu-items */
#subscribeToPageMenuitem:not([disabled]), #subscribeToPageMenuitem:not([disabled]),
#subscribeToPageMenupopup, #subscribeToPageMenupopup,

Просмотреть файл

@ -53,6 +53,12 @@ label.small {
margin: 6px; margin: 6px;
} }
/* Content Pane */
#translationAttributionImage {
width: 70px;
cursor: pointer;
}
/* Applications Pane */ /* Applications Pane */
#BrowserPreferences[animated="true"] #handlersView { #BrowserPreferences[animated="true"] #handlersView {
height: 25em; height: 25em;

Просмотреть файл

@ -38,6 +38,7 @@ SEARCH_PATHS = [
'config', 'config',
'dom/bindings', 'dom/bindings',
'dom/bindings/parser', 'dom/bindings/parser',
'layout/tools/reftest',
'other-licenses/ply', 'other-licenses/ply',
'xpcom/idl-parser', 'xpcom/idl-parser',
'testing', 'testing',

Просмотреть файл

@ -17,6 +17,7 @@ mozilla.pth:config
mozilla.pth:xpcom/typelib/xpt/tools mozilla.pth:xpcom/typelib/xpt/tools
mozilla.pth:dom/bindings mozilla.pth:dom/bindings
mozilla.pth:dom/bindings/parser mozilla.pth:dom/bindings/parser
mozilla.pth:layout/tools/reftest
moztreedocs.pth:tools/docs moztreedocs.pth:tools/docs
copy:build/buildconfig.py copy:build/buildconfig.py
packages.txt:testing/mozbase/packages.txt packages.txt:testing/mozbase/packages.txt

Просмотреть файл

@ -1316,6 +1316,12 @@ this.DOMApplicationRegistry = {
return; return;
} }
// Ensure we don't send additional errors for this download
app.isCanceling = true;
// Ensure this app can be downloaded again after canceling
app.downloading = false;
this._saveApps().then(() => { this._saveApps().then(() => {
this.broadcastMessage("Webapps:UpdateState", { this.broadcastMessage("Webapps:UpdateState", {
app: { app: {
@ -1339,6 +1345,7 @@ this.DOMApplicationRegistry = {
let id = this._appIdForManifestURL(aManifestURL); let id = this._appIdForManifestURL(aManifestURL);
let app = this.webapps[id]; let app = this.webapps[id];
if (!app) { if (!app) {
debug("startDownload: No app found for " + aManifestURL); debug("startDownload: No app found for " + aManifestURL);
throw new Error("NO_SUCH_APP"); throw new Error("NO_SUCH_APP");
@ -2770,6 +2777,14 @@ this.DOMApplicationRegistry = {
// initialize the progress to 0 right now // initialize the progress to 0 right now
oldApp.progress = 0; oldApp.progress = 0;
// Save the current state of the app to handle cases where we may be
// retrying a past download.
yield DOMApplicationRegistry._saveApps();
DOMApplicationRegistry.broadcastMessage("Webapps:UpdateState", {
app: oldApp,
manifestURL: aNewApp.manifestURL
});
let zipFile = yield this._getPackage(requestChannel, id, oldApp, aNewApp); let zipFile = yield this._getPackage(requestChannel, id, oldApp, aNewApp);
let hash = yield this._computeFileHash(zipFile.path); let hash = yield this._computeFileHash(zipFile.path);
@ -4083,6 +4098,15 @@ AppcacheObserver.prototype = {
let setError = function appObs_setError(aError) { let setError = function appObs_setError(aError) {
debug("Offlinecache setError to " + aError); debug("Offlinecache setError to " + aError);
app.downloading = false; app.downloading = false;
mustSave = true;
// If we are canceling the download, we already send a DOWNLOAD_CANCELED
// error.
if (app.isCanceling) {
delete app.isCanceling;
return;
}
DOMApplicationRegistry.broadcastMessage("Webapps:UpdateState", { DOMApplicationRegistry.broadcastMessage("Webapps:UpdateState", {
app: app, app: app,
error: aError, error: aError,
@ -4092,7 +4116,6 @@ AppcacheObserver.prototype = {
eventType: "downloaderror", eventType: "downloaderror",
manifestURL: app.manifestURL manifestURL: app.manifestURL
}); });
mustSave = true;
} }
switch (aState) { switch (aState) {

Просмотреть файл

@ -699,7 +699,10 @@ BluetoothAdapter::EnableDisable(bool aEnable)
} }
nsTArray<nsString> types; nsTArray<nsString> types;
types.AppendElement(NS_LITERAL_STRING("State")); BT_APPEND_ENUM_STRING(types,
BluetoothAdapterAttribute,
BluetoothAdapterAttribute::State);
DispatchAttributeEvent(types); DispatchAttributeEvent(types);
nsRefPtr<BluetoothReplyRunnable> result = nsRefPtr<BluetoothReplyRunnable> result =
@ -792,7 +795,7 @@ BluetoothAdapter::HandlePropertyChanged(const BluetoothValue& aValue)
// BluetoothAdapterAttribute properties // BluetoothAdapterAttribute properties
if (IsAdapterAttributeChanged(type, arr[i].value())) { if (IsAdapterAttributeChanged(type, arr[i].value())) {
SetPropertyByValue(arr[i]); SetPropertyByValue(arr[i]);
types.AppendElement(arr[i].name()); BT_APPEND_ENUM_STRING(types, BluetoothAdapterAttribute, type);
} }
} }

Просмотреть файл

@ -83,6 +83,18 @@ extern bool gBluetoothDebugFlag;
} \ } \
} while(0) } while(0)
/**
* Convert an enum value to string then append it to an array.
*/
#define BT_APPEND_ENUM_STRING(array, enumType, enumValue) \
do { \
uint32_t index = uint32_t(enumValue); \
nsAutoString name; \
name.AssignASCII(enumType##Values::strings[index].value, \
enumType##Values::strings[index].length); \
array.AppendElement(name); \
} while(0) \
#define BEGIN_BLUETOOTH_NAMESPACE \ #define BEGIN_BLUETOOTH_NAMESPACE \
namespace mozilla { namespace dom { namespace bluetooth { namespace mozilla { namespace dom { namespace bluetooth {
#define END_BLUETOOTH_NAMESPACE \ #define END_BLUETOOTH_NAMESPACE \

Просмотреть файл

@ -260,7 +260,9 @@ BluetoothManager::DispatchAttributeEvent()
JSAutoCompartment ac(cx, scope); JSAutoCompartment ac(cx, scope);
nsTArray<nsString> types; nsTArray<nsString> types;
types.AppendElement(NS_LITERAL_STRING("DefaultAdapter")); BT_APPEND_ENUM_STRING(types,
BluetoothManagerAttribute,
BluetoothManagerAttribute::DefaultAdapter);
if (!ToJSValue(cx, types, &value)) { if (!ToJSValue(cx, types, &value)) {
JS_ClearPendingException(cx); JS_ClearPendingException(cx);

Просмотреть файл

@ -566,7 +566,7 @@ function sendMMI(aMmi) {
* Query current voice privacy mode. * Query current voice privacy mode.
* *
* Fulfill params: * Fulfill params:
A boolean indicates the current voice privacy mode. * A boolean indicates the current voice privacy mode.
* Reject params: * Reject params:
* 'RadioNotAvailable', 'RequestNotSupported', or 'GenericFailure'. * 'RadioNotAvailable', 'RequestNotSupported', or 'GenericFailure'.
* *
@ -578,6 +578,54 @@ function sendMMI(aMmi) {
.then(() => request.result, () => { throw request.error }); .then(() => request.result, () => { throw request.error });
} }
/**
* Configures call barring options.
*
* Fulfill params: (none)
* Reject params:
* 'RadioNotAvailable', 'RequestNotSupported', 'InvalidParameter' or
* 'GenericFailure'.
*
* @return A deferred promise.
*/
function setCallBarringOption(aOptions) {
let request = mobileConnection.setCallBarringOption(aOptions);
return wrapDomRequestAsPromise(request)
.then(null, () => { throw request.error });
}
/**
* Queries current call barring status.
*
* Fulfill params:
* An object contains call barring status.
* Reject params:
* 'RadioNotAvailable', 'RequestNotSupported', 'InvalidParameter' or
* 'GenericFailure'.
*
* @return A deferred promise.
*/
function getCallBarringOption(aOptions) {
let request = mobileConnection.getCallBarringOption(aOptions);
return wrapDomRequestAsPromise(request)
.then(() => request.result, () => { throw request.error });
}
/**
* Change call barring facility password.
*
* Fulfill params: (none)
* Reject params:
* 'RadioNotAvailable', 'RequestNotSupported', or 'GenericFailure'.
*
* @return A deferred promise.
*/
function changeCallBarringPassword(aOptions) {
let request = mobileConnection.changeCallBarringPassword(aOptions);
return wrapDomRequestAsPromise(request)
.then(null, () => { throw request.error });
}
/** /**
* Set data connection enabling state and wait for "datachange" event. * Set data connection enabling state and wait for "datachange" event.
* *
@ -959,6 +1007,94 @@ function setEmulatorOperatorNamesAndWait(aOperator, aLongName, aShortName,
return Promise.all(promises); return Promise.all(promises);
} }
/**
* Set GSM signal strength.
*
* Fulfill params: (none)
* Reject params: (none)
*
* @param aRssi
*
* @return A deferred promise.
*/
function setEmulatorGsmSignalStrength(aRssi) {
let cmd = "gsm signal " + aRssi;
return runEmulatorCmdSafe(cmd);
}
/**
* Set emulator GSM signal strength and wait for voice and/or data state change.
*
* Fulfill params: (none)
*
* @param aRssi
* @param aWaitVoice [optional]
* A boolean value. Default true.
* @param aWaitData [optional]
* A boolean value. Default false.
*
* @return A deferred promise.
*/
function setEmulatorGsmSignalStrengthAndWait(aRssi,
aWaitVoice = true,
aWaitData = false) {
let promises = [];
if (aWaitVoice) {
promises.push(waitForManagerEvent("voicechange"));
}
if (aWaitData) {
promises.push(waitForManagerEvent("datachange"));
}
promises.push(setEmulatorGsmSignalStrength(aRssi));
return Promise.all(promises);
}
/**
* Set LTE signal strength.
*
* Fulfill params: (none)
* Reject params: (none)
*
* @param aRxlev
* @param aRsrp
* @param aRssnr
*
* @return A deferred promise.
*/
function setEmulatorLteSignalStrength(aRxlev, aRsrp, aRssnr) {
let cmd = "gsm lte_signal " + aRxlev + " " + aRsrp + " " + aRssnr;
return runEmulatorCmdSafe(cmd);
}
/**
* Set emulator LTE signal strength and wait for voice and/or data state change.
*
* Fulfill params: (none)
*
* @param aRxlev
* @param aRsrp
* @param aRssnr
* @param aWaitVoice [optional]
* A boolean value. Default true.
* @param aWaitData [optional]
* A boolean value. Default false.
*
* @return A deferred promise.
*/
function setEmulatorLteSignalStrengthAndWait(aRxlev, aRsrp, aRssnr,
aWaitVoice = true,
aWaitData = false) {
let promises = [];
if (aWaitVoice) {
promises.push(waitForManagerEvent("voicechange"));
}
if (aWaitData) {
promises.push(waitForManagerEvent("datachange"));
}
promises.push(setEmulatorLteSignalStrength(aRxlev, aRsrp, aRssnr));
return Promise.all(promises);
}
let _networkManager; let _networkManager;
/** /**

Просмотреть файл

@ -1,60 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
SpecialPowers.addPermission("mobileconnection", true, document);
// In single sim scenario, there is only one mobileConnection, we can always use
// the first instance.
let mobileConnection = window.navigator.mozMobileConnections[0];
ok(mobileConnection instanceof MozMobileConnection,
"mobileConnection is instanceof " + mobileConnection.constructor);
let _pendingEmulatorCmdCount = 0;
/* Remove permission and execute finish() */
let cleanUp = function() {
waitFor(function() {
SpecialPowers.removePermission("mobileconnection", document);
finish();
}, function() {
return _pendingEmulatorCmdCount === 0;
});
};
/* Helper for tasks */
let taskHelper = {
tasks: [],
push: function(task) {
this.tasks.push(task);
},
runNext: function() {
let task = this.tasks.shift();
if (!task) {
cleanUp();
return;
}
if (typeof task === "function") {
task();
}
},
};
/* Helper for emulator console command */
let emulatorHelper = {
sendCommand: function(cmd, callback) {
_pendingEmulatorCmdCount++;
runEmulatorCmd(cmd, function(results) {
_pendingEmulatorCmdCount--;
let result = results[results.length - 1];
is(result, "OK", "'"+ cmd +"' returns '" + result + "'");
if (callback && typeof callback === "function") {
callback(results);
}
});
},
};

Просмотреть файл

@ -2,62 +2,49 @@
http://creativecommons.org/publicdomain/zero/1.0/ */ http://creativecommons.org/publicdomain/zero/1.0/ */
MARIONETTE_TIMEOUT = 60000; MARIONETTE_TIMEOUT = 60000;
MARIONETTE_HEAD_JS = "head.js";
SpecialPowers.addPermission("mobileconnection", true, document); const TEST_DATA = [
// [<pin>, <new pin>, <expected error>]
// Permission changes can't change existing Navigator.prototype // Test passing an invalid pin or newPin.
// objects, so grab our objects from a new Navigator [null, "0000", "InvalidPassword"],
let ifr = document.createElement("iframe"); ["0000", null, "InvalidPassword"],
let connection; [null, null, "InvalidPassword"],
ifr.onload = function() {
connection = ifr.contentWindow.navigator.mozMobileConnections[0];
ok(connection instanceof ifr.contentWindow.MozMobileConnection, // Test passing mismatched newPin.
"connection is instanceof " + connection.constructor); ["000", "0000", "InvalidPassword"],
["00000", "1111", "InvalidPassword"],
["abcd", "efgh", "InvalidPassword"],
setTimeout(testChangeCallBarringPasswordWithFailure, 0); // TODO: Bug 906603 - B2G RIL: Support Change Call Barring Password on Emulator.
}; // Currently emulator doesn't support REQUEST_CHANGE_BARRING_PASSWORD, so we
document.body.appendChild(ifr); // expect to get a 'RequestNotSupported' error here.
["1234", "1234", "RequestNotSupported"]
];
function testChangeCallBarringPasswordWithFailure() { function testChangeCallBarringPassword(aPin, aNewPin, aExpectedError) {
// Incorrect parameters, expect onerror callback. log("Test changing call barring password to " + aPin + "/" + aNewPin);
let options = [
{pin: null, newPin: '0000'},
{pin: '0000', newPin: null},
{pin: null, newPin: null},
{pin: '000', newPin: '0000'},
{pin: '00000', newPin: '1111'},
{pin: 'abcd', newPin: 'efgh'},
];
function do_test() { let options = {
for (let i = 0; i < options.length; i++) { pin: aPin,
let request = connection.changeCallBarringPassword(options[i]); newPin: aNewPin
};
return changeCallBarringPassword(options)
.then(function resolve() {
ok(!aExpectedError, "changeCallBarringPassword success");
}, function reject(aError) {
is(aError.name, aExpectedError, "failed to changeCallBarringPassword");
});
}
request.onsuccess = function() { // Start tests
ok(false, 'Unexpected result.'); startTestCommon(function() {
setTimeout(cleanUp , 0); let promise = Promise.resolve();
}; for (let i = 0; i < TEST_DATA.length; i++) {
let data = TEST_DATA[i];
request.onerror = function() { promise =
ok(request.error.name === 'InvalidPassword', 'InvalidPassword'); promise.then(() => testChangeCallBarringPassword(data[0], data[1], data[2]));
if (i >= options.length) {
setTimeout(testChangeCallBarringPasswordWithSuccess, 0);
}
};
}
} }
return promise;
do_test(); });
}
function testChangeCallBarringPasswordWithSuccess() {
// TODO: Bug 906603 - B2G RIL: Support Change Call Barring Password on
// Emulator.
setTimeout(cleanUp , 0);
}
function cleanUp() {
SpecialPowers.removePermission("mobileconnection", document);
finish();
}

Просмотреть файл

@ -2,38 +2,73 @@
http://creativecommons.org/publicdomain/zero/1.0/ */ http://creativecommons.org/publicdomain/zero/1.0/ */
MARIONETTE_TIMEOUT = 60000; MARIONETTE_TIMEOUT = 60000;
MARIONETTE_HEAD_JS = "head.js";
SpecialPowers.addPermission("mobileconnection", true, document); const TEST_DATA = [
// Test passing invalid program.
{
options: {
program: 5, /* Invalid program */
serviceClass: 0
},
expectedError: "InvalidParameter"
}, {
options: {
program: null,
serviceClass: 0
},
expectedError: "InvalidParameter"
}, {
options: {
/* Undefined program */
serviceClass: 0
},
expectedError: "InvalidParameter"
},
// Test passing invalid serviceClass.
{
options: {
program: MozMobileConnection.CALL_BARRING_PROGRAM_ALL_OUTGOING,
serviceClass: null
},
expectedError: "InvalidParameter"
}, {
options: {
program: MozMobileConnection.CALL_BARRING_PROGRAM_ALL_OUTGOING,
/* Undefined serviceClass */
},
expectedError: "InvalidParameter"
},
// TODO: Bug 1027546 - [B2G][Emulator] Support call barring
// Currently emulator doesn't support call barring, so we expect to get a
// 'RequestNotSupported' error here.
{
options: {
program: MozMobileConnection.CALL_BARRING_PROGRAM_ALL_OUTGOING,
serviceClass: 0
},
expectedError: "RequestNotSupported"
}
];
// Permission changes can't change existing Navigator.prototype function testGetCallBarringOption(aOptions, aExpectedError) {
// objects, so grab our objects from a new Navigator log("Test getting call barring to " + JSON.stringify(aOptions));
let ifr = document.createElement("iframe");
let connection;
ifr.onload = function() {
connection = ifr.contentWindow.navigator.mozMobileConnections[0];
ok(connection instanceof ifr.contentWindow.MozMobileConnection, return getCallBarringOption(aOptions)
"connection is instanceof " + connection.constructor); .then(function resolve(aResult) {
ok(false, "should not success");
testGetCallBarringOption(); }, function reject(aError) {
}; is(aError.name, aExpectedError, "failed to getCallBarringOption");
document.body.appendChild(ifr); });
function testGetCallBarringOption() {
let option = {'program': 0, 'password': '', 'serviceClass': 0};
let request = connection.getCallBarringOption(option);
request.onsuccess = function() {
ok(request.result);
ok('enabled' in request.result, 'should have "enabled" field');
cleanUp();
};
request.onerror = function() {
// Call barring is not supported by current emulator.
cleanUp();
};
} }
function cleanUp() { // Start tests
SpecialPowers.removePermission("mobileconnection", document); startTestCommon(function() {
finish(); let promise = Promise.resolve();
} for (let i = 0; i < TEST_DATA.length; i++) {
let data = TEST_DATA[i];
promise = promise.then(() => testGetCallBarringOption(data.options,
data.expectedError));
}
return promise;
});

Просмотреть файл

@ -2,73 +2,121 @@
http://creativecommons.org/publicdomain/zero/1.0/ */ http://creativecommons.org/publicdomain/zero/1.0/ */
MARIONETTE_TIMEOUT = 60000; MARIONETTE_TIMEOUT = 60000;
MARIONETTE_HEAD_JS = "head.js";
SpecialPowers.addPermission("mobileconnection", true, document); const TEST_DATA = [
// Test passing invalid program.
// Permission changes can't change existing Navigator.prototype {
// objects, so grab our objects from a new Navigator options: {
let ifr = document.createElement("iframe"); "program": 5, /* Invalid program */
let connection; "enabled": true,
ifr.onload = function() { "password": "0000",
connection = ifr.contentWindow.navigator.mozMobileConnections[0]; "serviceClass": 0
},
ok(connection instanceof ifr.contentWindow.MozMobileConnection, expectedError: "InvalidParameter"
"connection is instanceof " + connection.constructor); }, {
options: {
nextTest(); "program": null,
}; "enabled": true,
document.body.appendChild(ifr); "password": "0000",
"serviceClass": 0
let caseId = 0; },
let options = [ expectedError: "InvalidParameter"
buildOption(5, true, '0000', 0), // invalid program. }, {
options: {
// test null. /* Undefined program */
buildOption(null, true, '0000', 0), "enabled": true,
buildOption(0, null, '0000', 0), "password": "0000",
buildOption(0, true, null, 0), "serviceClass": 0
buildOption(0, true, '0000', null), },
expectedError: "InvalidParameter"
// test undefined. },
{'enabled': true, 'password': '0000', 'serviceClass': 0}, // Test passing invalid enabled.
{'program': 0, 'password': '0000', 'serviceClass': 0}, {
{'program': 0, 'enabled': true, 'serviceClass': 0}, options: {
{'program': 0, 'enabled': true, 'password': '0000'}, "program": MozMobileConnection.CALL_BARRING_PROGRAM_ALL_OUTGOING,
"enabled": null,
"password": "0000",
"serviceClass": 0
},
expectedError: "InvalidParameter"
}, {
options: {
"program": MozMobileConnection.CALL_BARRING_PROGRAM_ALL_OUTGOING,
/* Undefined enabled */
"password": "0000",
"serviceClass": 0
},
expectedError: "InvalidParameter"
},
// Test passing invalid password.
{
options: {
"program": MozMobileConnection.CALL_BARRING_PROGRAM_ALL_OUTGOING,
"enabled": true,
"password": null,
"serviceClass": 0
},
expectedError: "InvalidParameter"
}, {
options: {
"program": MozMobileConnection.CALL_BARRING_PROGRAM_ALL_OUTGOING,
"enabled": true,
/* Undefined password */
"serviceClass": 0
},
expectedError: "InvalidParameter"
},
// Test passing invalid serviceClass.
{
options: {
"program": MozMobileConnection.CALL_BARRING_PROGRAM_ALL_OUTGOING,
"enabled": true,
"password": "0000",
"serviceClass": null
},
expectedError: "InvalidParameter"
}, {
options: {
"program": MozMobileConnection.CALL_BARRING_PROGRAM_ALL_OUTGOING,
"enabled": true,
"password": "0000",
/* Undefined serviceClass */
},
expectedError: "InvalidParameter"
},
// TODO: Bug 1027546 - [B2G][Emulator] Support call barring
// Currently emulator doesn't support call barring, so we expect to get a
// 'RequestNotSupported' error here.
{
options: {
"program": MozMobileConnection.CALL_BARRING_PROGRAM_ALL_OUTGOING,
"enabled": true,
"password": "0000",
"serviceClass": 0
},
expectedError: "RequestNotSupported"
}
]; ];
function buildOption(program, enabled, password, serviceClass) { function testSetCallBarringOption(aOptions, aExpectedError) {
return { log("Test setting call barring to " + JSON.stringify(aOptions));
'program': program,
'enabled': enabled, return setCallBarringOption(aOptions)
'password': password, .then(function resolve() {
'serviceClass': serviceClass ok(false, "changeCallBarringPassword success");
}; }, function reject(aError) {
is(aError.name, aExpectedError, "failed to changeCallBarringPassword");
});
} }
function testSetCallBarringOptionError(option) { // Start tests
let request = connection.setCallBarringOption(option); startTestCommon(function() {
request.onsuccess = function() { let promise = Promise.resolve();
ok(false, for (let i = 0; i < TEST_DATA.length; i++) {
'should not fire onsuccess for invaild call barring option: ' let data = TEST_DATA[i];
+ JSON.stringify(option)); promise = promise.then(() => testSetCallBarringOption(data.options,
}; data.expectedError));
request.onerror = function(event) {
is(event.target.error.name, 'InvalidParameter', JSON.stringify(option));
nextTest();
};
}
function nextTest() {
if (caseId >= options.length) {
cleanUp();
} else {
let option = options[caseId++];
log('test for ' + JSON.stringify(option));
testSetCallBarringOptionError(option);
} }
} return promise;
});
function cleanUp() {
SpecialPowers.removePermission("mobileconnection", document);
finish();
}

Просмотреть файл

@ -1,84 +1,33 @@
/* Any copyright is dedicated to the Public Domain. /* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */ http://creativecommons.org/publicdomain/zero/1.0/ */
MARIONETTE_TIMEOUT = 30000; MARIONETTE_TIMEOUT = 60000;
MARIONETTE_HEAD_JS = "head.js";
SpecialPowers.addPermission("mobileconnection", true, document); // The emulator's hard coded iccid value.
const ICCID = "89014103211118510720";
// Permission changes can't change existing Navigator.prototype function setRadioEnabledAndWaitIccChange(aEnabled) {
// objects, so grab our objects from a new Navigator let promises = [];
let ifr = document.createElement("iframe"); promises.push(waitForManagerEvent("iccchange"));
let connection; promises.push(setRadioEnabled(aEnabled));
ifr.onload = function() {
connection = ifr.contentWindow.navigator.mozMobileConnections[0];
ok(connection instanceof ifr.contentWindow.MozMobileConnection,
"connection is instanceof " + connection.constructor);
// The emulator's hard coded iccid value. return Promise.all(promises);
// See it here {B2G_HOME}/external/qemu/telephony/sim_card.c.
is(connection.iccId, 89014103211118510720);
runNextTest();
};
document.body.appendChild(ifr);
function waitForIccChange(callback) {
connection.addEventListener("iccchange", function handler() {
connection.removeEventListener("iccchange", handler);
callback();
});
} }
function setRadioEnabled(enabled) { // Start tests
let request = connection.setRadioEnabled(enabled); startTestCommon(function() {
log("Test initial iccId");
is(mobileConnection.iccId, ICCID);
request.onsuccess = function onsuccess() { return setRadioEnabledAndWaitIccChange(false)
log('setRadioEnabled: ' + enabled); .then(() => {
}; is(mobileConnection.iccId, null);
})
request.onerror = function onerror() { // Restore radio state.
ok(false, "setRadioEnabled should be ok"); .then(() => setRadioEnabledAndWaitIccChange(true))
}; .then(() => {
} is(mobileConnection.iccId, ICCID);
});
function testIccChangeOnRadioPowerOff() { });
// Turn off radio
setRadioEnabled(false);
waitForIccChange(function() {
is(connection.iccId, null);
runNextTest();
});
}
function testIccChangeOnRadioPowerOn() {
// Turn on radio
setRadioEnabled(true);
waitForIccChange(function() {
// The emulator's hard coded iccid value.
is(connection.iccId, 89014103211118510720);
runNextTest();
});
}
let tests = [
testIccChangeOnRadioPowerOff,
testIccChangeOnRadioPowerOn
];
function runNextTest() {
let test = tests.shift();
if (!test) {
cleanUp();
return;
}
test();
}
function cleanUp() {
SpecialPowers.removePermission("mobileconnection", document);
finish();
}

Просмотреть файл

@ -1,47 +1,13 @@
/* Any copyright is dedicated to the Public Domain. /* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */ http://creativecommons.org/publicdomain/zero/1.0/ */
MARIONETTE_TIMEOUT = 30000; MARIONETTE_TIMEOUT = 60000;
MARIONETTE_HEAD_JS = "head.js";
SpecialPowers.addPermission("mobilenetwork", true, document); // Start tests
startTestCommon(function() {
let connection = navigator.mozMobileConnections[0];
ok(connection instanceof MozMobileConnection,
"connection is instanceof " + connection.constructor);
function testLastKnownNetwork() {
log("testLastKnownNetwork: " + connection.lastKnownNetwork);
// The emulator's hard coded operatoer's mcc and mnc codes. // The emulator's hard coded operatoer's mcc and mnc codes.
is(connection.lastKnownNetwork, "310-260"); is(mobileConnection.lastKnownNetwork, "310-260");
runNextTest();
}
function testLastKnownHomeNetwork() {
log("testLastKnownHomeNetwork: " + connection.lastKnownHomeNetwork);
// The emulator's hard coded icc's mcc and mnc codes. // The emulator's hard coded icc's mcc and mnc codes.
is(connection.lastKnownHomeNetwork, "310-260"); is(mobileConnection.lastKnownHomeNetwork, "310-260");
runNextTest(); });
}
let tests = [
testLastKnownNetwork,
testLastKnownHomeNetwork
];
function runNextTest() {
let test = tests.shift();
if (!test) {
cleanUp();
return;
}
test();
}
function cleanUp() {
SpecialPowers.removePermission("mobilenetwork", document);
finish();
}
runNextTest();

Просмотреть файл

@ -2,167 +2,40 @@
* http://creativecommons.org/publicdomain/zero/1.0/ */ * http://creativecommons.org/publicdomain/zero/1.0/ */
MARIONETTE_TIMEOUT = 60000; MARIONETTE_TIMEOUT = 60000;
MARIONETTE_HEAD_JS = "head.js";
const DATA_KEY = "ril.data.enabled"; // Start tests
const APN_KEY = "ril.data.apnSettings"; startTestCommon(function() {
let Promise = SpecialPowers.Cu.import("resource://gre/modules/Promise.jsm").Promise; let origApnSettings;
return getDataApnSettings()
.then(value => {
origApnSettings = value;
})
SpecialPowers.setBoolPref("dom.mozSettings.enabled", true); // Test disabling/enabling radio power.
SpecialPowers.addPermission("mobileconnection", true, document); .then(() => setRadioEnabledAndWait(false))
SpecialPowers.addPermission("settings-read", true, document); .then(() => setRadioEnabledAndWait(true))
SpecialPowers.addPermission("settings-write", true, document);
let settings = window.navigator.mozSettings; // Test disabling radio when data is connected.
let connection = window.navigator.mozMobileConnections[0]; .then(() => {
ok(connection instanceof MozMobileConnection, let apnSettings = [[
"connection is instanceof " + connection.constructor);
function setSetting(key, value) {
let deferred = Promise.defer();
let setLock = settings.createLock();
let obj = {};
obj[key] = value;
let setReq = setLock.set(obj);
setReq.addEventListener("success", function onSetSuccess() {
ok(true, "set '" + key + "' to " + obj[key]);
deferred.resolve();
});
setReq.addEventListener("error", function onSetError() {
ok(false, "cannot set '" + key + "'");
deferred.reject();
});
return deferred.promise;
}
function setEmulatorAPN() {
let apn =
[
[
{"carrier":"T-Mobile US", {"carrier":"T-Mobile US",
"apn":"epc.tmobile.com", "apn":"epc.tmobile.com",
"mmsc":"http://mms.msg.eng.t-mobile.com/mms/wapenc", "mmsc":"http://mms.msg.eng.t-mobile.com/mms/wapenc",
"types":["default","supl","mms"]} "types":["default","supl","mms"]}]];
] return setDataApnSettings(apnSettings);
]; })
return setSetting(APN_KEY, apn); .then(() => setDataEnabledAndWait(true))
} .then(() => setRadioEnabledAndWait(false))
function enableData() {
log("Turn data on.");
let deferred = Promise.defer();
connection.addEventListener("datachange", function ondatachange() {
if (connection.data.connected === true) {
connection.removeEventListener("datachange", ondatachange);
log("mobileConnection.data.connected is now '"
+ connection.data.connected + "'.");
deferred.resolve();
}
});
setEmulatorAPN()
.then(() => setSetting(DATA_KEY, true));
return deferred.promise;
}
function receivedPending(received, pending, nextAction) {
let index = pending.indexOf(received);
if (index != -1) {
pending.splice(index, 1);
}
if (pending.length === 0) {
nextAction();
}
}
function waitRadioState(state) {
let deferred = Promise.defer();
waitFor(function() {
deferred.resolve();
}, function() {
return connection.radioState == state;
});
return deferred.promise;
}
function setRadioEnabled(enabled, transientState, finalState) {
log("setRadioEnabled to " + enabled);
let deferred = Promise.defer();
let done = function() {
deferred.resolve();
};
let pending = ["onradiostatechange", "onsuccess"];
let receivedTransient = false;
connection.onradiostatechange = function() {
let state = connection.radioState;
log("Received 'radiostatechange' event, radioState: " + state);
if (state == transientState) {
receivedTransient = true;
} else if (state == finalState) {
ok(receivedTransient);
receivedPending("onradiostatechange", pending, done);
}
};
let req = connection.setRadioEnabled(enabled);
req.onsuccess = function() {
log("setRadioEnabled success");
receivedPending("onsuccess", pending, done);
};
req.onerror = function() {
ok(false, "setRadioEnabled should not fail");
deferred.reject();
};
return deferred.promise;
}
function testSwitchRadio() {
log("= testSwitchRadio =");
return waitRadioState("enabled")
.then(setRadioEnabled.bind(null, false, "disabling", "disabled"))
.then(setRadioEnabled.bind(null, true, "enabling", "enabled"));
}
function testDisableRadioWhenDataConnected() {
log("= testDisableRadioWhenDataConnected =");
return waitRadioState("enabled")
.then(enableData)
.then(setRadioEnabled.bind(null, false, "disabling", "disabled"))
.then(() => { .then(() => {
// Data should be disconnected. // Data should be disconnected.
is(connection.data.connected, false); is(mobileConnection.data.connected, false);
}) })
.then(setRadioEnabled.bind(null, true, "enabling", "enabled"))
// Disable data
.then(setSetting.bind(null, DATA_KEY, false));
}
function cleanUp() { // Restore test environment.
SpecialPowers.removePermission("mobileconnection", document); .then(() => setDataApnSettings(origApnSettings))
SpecialPowers.removePermission("settings-write", document); .then(() => setDataEnabled(false))
SpecialPowers.removePermission("settings-read", document); .then(() => setRadioEnabledAndWait(true));
SpecialPowers.clearUserPref("dom.mozSettings.enabled");
finish();
}
testSwitchRadio() }, ["settings-read", "settings-write"]);
.then(testDisableRadioWhenDataConnected)
.then(null, () => {
ok(false, "promise reject somewhere");
})
.then(cleanUp);

Просмотреть файл

@ -1,132 +1,98 @@
/* Any copyright is dedicated to the Public Domain. /* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */ http://creativecommons.org/publicdomain/zero/1.0/ */
MARIONETTE_TIMEOUT = 30000; MARIONETTE_TIMEOUT = 60000;
MARIONETTE_HEAD_JS = "mobile_header.js"; MARIONETTE_HEAD_JS = "head.js";
/* Emulator command for GSM/UMTS signal strength */ // Emulator uses rssi = 7 as default value.
function setEmulatorGsmSignalStrength(rssi) { const DEFAULT_RSSI = 7;
emulatorHelper.sendCommand("gsm signal " + rssi);
}
/* Emulator command for LTE signal strength */ const TEST_DATA = [
function setEmulatorLteSignalStrength(rxlev, rsrp, rssnr) { // All invalid case.
let lteSignal = rxlev + " " + rsrp + " " + rssnr; {
emulatorHelper.sendCommand("gsm lte_signal " + lteSignal); input: {
} rxlev: 99,
rsrp: 65535,
function waitForVoiceChangeEvent(callback) { rssnr: 65535
mobileConnection.addEventListener("voicechange", function onvoicechange() { },
mobileConnection.removeEventListener("voicechange", onvoicechange); expect: {
signalStrength: null,
if (callback && typeof callback === "function") { relSignalStrength: null
callback();
} }
}); },
} // Valid rxlev with max value.
{
input: {
rxlev: 63,
rsrp: 65535,
rssnr: 65535
},
expect: {
signalStrength: -48,
relSignalStrength: 100
}
},
// Valid rxlev.
{
input: {
rxlev: 12,
rsrp: 65535,
rssnr: 65535
},
expect: {
signalStrength: -99,
relSignalStrength: 100
}
},
// Valid rxlev with min value.
{
input: {
rxlev: 0,
rsrp: 65535,
rssnr: 65535
},
expect: {
signalStrength: -111,
relSignalStrength: 0
}
}
];
/* Test Initial Signal Strength Info */ function testInitialSignalStrengthInfo() {
taskHelper.push(function testInitialSignalStrengthInfo() {
log("Test initial signal strength info"); log("Test initial signal strength info");
let voice = mobileConnection.voice; let voice = mobileConnection.voice;
// Android emulator initializes the signal strength to -99 dBm // Android emulator initializes the signal strength to -99 dBm
is(voice.signalStrength, -99, "check voice.signalStrength"); is(voice.signalStrength, -99, "check voice.signalStrength");
is(voice.relSignalStrength, 44, "check voice.relSignalStrength"); is(voice.relSignalStrength, 44, "check voice.relSignalStrength");
}
taskHelper.runNext(); function testLteSignalStrength(aInput, aExpect) {
}); log("Test setting LTE signal strength to " + JSON.stringify(aInput));
/* Test Unsolicited Signal Strength Events for LTE */ return setEmulatorLteSignalStrengthAndWait(aInput.rxlev, aInput.rsrp, aInput.rssnr)
taskHelper.push(function testLteSignalStrength() { .then(() => {
// Set emulator's LTE signal strength and wait for 'onvoicechange' event.
function doTestLteSignalStrength(input, expect, callback) {
log("Test LTE signal info with data : " + JSON.stringify(input));
waitForVoiceChangeEvent(function() {
let voice = mobileConnection.voice; let voice = mobileConnection.voice;
is(voice.signalStrength, expect.signalStrength, is(voice.signalStrength, aExpect.signalStrength,
"check voice.signalStrength"); "check voice.signalStrength");
is(voice.relSignalStrength, expect.relSignalStrength, is(voice.relSignalStrength, aExpect.relSignalStrength,
"check voice.relSignalStrength"); "check voice.relSignalStrength");
if (callback && typeof callback === "function") {
callback();
}
}); });
}
setEmulatorLteSignalStrength(input.rxlev, input.rsrp, input.rssnr); // Start tests
startTestCommon(function() {
// Test initial status
testInitialSignalStrengthInfo();
// Test Unsolicited Signal Strength Events for LTE
let promise = Promise.resolve();
for (let i = 0; i < TEST_DATA.length; i++) {
let data = TEST_DATA[i];
promise = promise.then(() => testLteSignalStrength(data.input,
data.expect));
} }
let testData = [ // Reset Signal Strength Info to default
// All invalid case. return promise.then(() => setEmulatorGsmSignalStrengthAndWait(DEFAULT_RSSI));
{input: {
rxlev: 99,
rsrp: 65535,
rssnr: 65535},
expect: {
signalStrength: null,
relSignalStrength: null}
},
// Valid rxlev with max value.
{input: {
rxlev: 63,
rsrp: 65535,
rssnr: 65535},
expect: {
signalStrength: -48,
relSignalStrength: 100}
},
// Valid rxlev.
{input: {
rxlev: 12,
rsrp: 65535,
rssnr: 65535},
expect: {
signalStrength: -99,
relSignalStrength: 100}
},
// Valid rxlev with min value.
{input: {
rxlev: 0,
rsrp: 65535,
rssnr: 65535},
expect: {
signalStrength: -111,
relSignalStrength: 0}
}
];
// Run all test data.
(function do_call() {
let next = testData.shift();
if (!next) {
taskHelper.runNext();
return;
}
doTestLteSignalStrength(next.input, next.expect, do_call);
})();
}); });
/* Reset Signal Strength Info to default, and finsih the test */
taskHelper.push(function testResetSignalStrengthInfo() {
// Reset emulator's signal strength and wait for 'onvoicechange' event.
function doResetSignalStrength(rssi) {
waitForVoiceChangeEvent(function() {
let voice = mobileConnection.voice;
is(voice.signalStrength, -99, "check voice.signalStrength");
is(voice.relSignalStrength, 44, "check voice.relSignalStrength");
taskHelper.runNext();
});
setEmulatorGsmSignalStrength(rssi);
}
// Emulator uses rssi = 7 as default value, and we need to reset it after
// finishing test in case other test cases need those values for testing.
doResetSignalStrength(7);
});
// Start test
taskHelper.runNext();

Просмотреть файл

@ -19,6 +19,24 @@ struct MutexData {
namespace mozilla { namespace mozilla {
static void
InitMutex(pthread_mutex_t* mMutex)
{
pthread_mutexattr_t mutexAttributes;
pthread_mutexattr_init(&mutexAttributes);
// Make the mutex reentrant so it behaves the same as a win32 mutex
if (pthread_mutexattr_settype(&mutexAttributes, PTHREAD_MUTEX_RECURSIVE)) {
MOZ_CRASH();
}
if (pthread_mutexattr_setpshared(&mutexAttributes, PTHREAD_PROCESS_SHARED)) {
MOZ_CRASH();
}
if (pthread_mutex_init(mMutex, &mutexAttributes)) {
MOZ_CRASH();
}
}
CrossProcessMutex::CrossProcessMutex(const char*) CrossProcessMutex::CrossProcessMutex(const char*)
: mSharedBuffer(nullptr) : mSharedBuffer(nullptr)
, mMutex(nullptr) , mMutex(nullptr)
@ -43,20 +61,7 @@ CrossProcessMutex::CrossProcessMutex(const char*)
mCount = &(data->mCount); mCount = &(data->mCount);
*mCount = 1; *mCount = 1;
InitMutex(mMutex);
pthread_mutexattr_t mutexAttributes;
pthread_mutexattr_init(&mutexAttributes);
// Make the mutex reentrant so it behaves the same as a win32 mutex
if (pthread_mutexattr_settype(&mutexAttributes, PTHREAD_MUTEX_RECURSIVE)) {
MOZ_CRASH();
}
if (pthread_mutexattr_setpshared(&mutexAttributes, PTHREAD_PROCESS_SHARED)) {
MOZ_CRASH();
}
if (pthread_mutex_init(mMutex, &mutexAttributes)) {
MOZ_CRASH();
}
MOZ_COUNT_CTOR(CrossProcessMutex); MOZ_COUNT_CTOR(CrossProcessMutex);
} }
@ -84,7 +89,13 @@ CrossProcessMutex::CrossProcessMutex(CrossProcessMutexHandle aHandle)
mMutex = &(data->mMutex); mMutex = &(data->mMutex);
mCount = &(data->mCount); mCount = &(data->mCount);
(*mCount)++; int32_t count = (*mCount)++;
if (count == 0) {
// The other side has already let go of their CrossProcessMutex, so now
// mMutex is garbage. We need to re-initialize it.
InitMutex(mMutex);
}
MOZ_COUNT_CTOR(CrossProcessMutex); MOZ_COUNT_CTOR(CrossProcessMutex);
} }

Просмотреть файл

@ -34,3 +34,6 @@ MOCHITEST_MANIFESTS += [
'reftests/fonts/mochitest.ini', 'reftests/fonts/mochitest.ini',
'reftests/fonts/mplus/mochitest.ini', 'reftests/fonts/mplus/mochitest.ini',
] ]
REFTEST_MANIFESTS += ['reftests/reftest.list']
CRASHTEST_MANIFESTS += ['../testing/crashtest/crashtests.list']

Просмотреть файл

@ -3,77 +3,27 @@
# License, v. 2.0. If a copy of the MPL was not distributed with this # 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/. # file, You can obtain one at http://mozilla.org/MPL/2.0/.
import sys, os.path, re import os
import sys
commentRE = re.compile(r"\s+#") from reftest import ReftestManifest
conditionsRE = re.compile(r"^(fails|needs-focus|random|skip|asserts|slow|require-or|silentfail|pref|test-pref|ref-pref|fuzzy)")
httpRE = re.compile(r"HTTP\((\.\.(\/\.\.)*)\)")
protocolRE = re.compile(r"^\w+:")
def parseManifest(manifest, dirs):
"""Parse the reftest manifest |manifest|, adding all directories containing
tests (and the dirs containing the manifests themselves) to the set |dirs|."""
manifestdir = os.path.dirname(os.path.abspath(manifest))
dirs.add(manifestdir)
f = file(manifest)
urlprefix = ''
for line in f:
if line[0] == '#':
continue # entire line was a comment
m = commentRE.search(line)
if m:
line = line[:m.start()]
line = line.strip()
if not line:
continue
items = line.split()
while conditionsRE.match(items[0]):
del items[0]
if items[0] == "HTTP":
del items[0]
m = httpRE.match(items[0])
if m:
# need to package the dir referenced here
d = os.path.normpath(os.path.join(manifestdir, m.group(1)))
dirs.add(d)
del items[0]
if items[0] == "url-prefix":
urlprefix = items[1]
continue
elif items[0] == "default-preferences":
continue
elif items[0] == "include":
parseManifest(os.path.join(manifestdir, items[1]), dirs)
continue
elif items[0] == "load" or items[0] == "script":
testURLs = [items[1]]
elif items[0] == "==" or items[0] == "!=":
testURLs = items[1:3]
for u in testURLs:
m = protocolRE.match(u)
if m:
# can't very well package about: or data: URIs
continue
d = os.path.dirname(os.path.normpath(os.path.join(manifestdir, urlprefix + u)))
dirs.add(d)
f.close()
def printTestDirs(topsrcdir, topmanifests): def printTestDirs(topsrcdir, topmanifests):
"""Parse |topmanifests| and print a list of directories containing the tests """Parse |topmanifests| and print a list of directories containing the tests
within (and the manifests including those tests), relative to |topsrcdir|.""" within (and the manifests including those tests), relative to |topsrcdir|.
topsrcdir = os.path.abspath(topsrcdir) """
dirs = set() topsrcdir = os.path.abspath(topsrcdir)
for manifest in topmanifests: dirs = set()
parseManifest(manifest, dirs) for path in topmanifests:
for dir in sorted(dirs): m = ReftestManifest()
d = dir[len(topsrcdir):].replace('\\','/') m.load(path)
if d[0] == '/': dirs |= m.dirs
d = d[1:]
print d for d in sorted(dirs):
d = d[len(topsrcdir):].replace('\\', '/').lstrip('/')
print(d)
if __name__ == '__main__': if __name__ == '__main__':
if len(sys.argv) < 3: if len(sys.argv) < 3:
print >>sys.stderr, "Usage: %s topsrcdir reftest.list [reftest.list]*" % sys.argv[0] print >>sys.stderr, "Usage: %s topsrcdir reftest.list [reftest.list]*" % sys.argv[0]
sys.exit(1) sys.exit(1)
printTestDirs(sys.argv[1], sys.argv[2:]) printTestDirs(sys.argv[1], sys.argv[2:])

Просмотреть файл

@ -0,0 +1,125 @@
# 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/.
from __future__ import unicode_literals
import os
import re
RE_COMMENT = re.compile(r'\s+#')
RE_HTTP = re.compile(r'HTTP\((\.\.(\/\.\.)*)\)')
RE_PROTOCOL = re.compile(r'^\w+:')
FAILURE_TYPES = (
'fails',
'fails-if',
'needs-focus',
'random',
'random-if',
'silentfail',
'silentfail-if',
'skip',
'skip-if',
'slow',
'slow-if',
'fuzzy',
'fuzzy-if',
'require-or',
'asserts',
'asserts-if',
)
PREF_ITEMS = (
'pref',
'test-pref',
'ref-pref',
)
class ReftestManifest(object):
"""Represents a parsed reftest manifest.
We currently only capture file information because that is the only thing
tools require.
"""
def __init__(self):
self.path = None
self.dirs = set()
self.files = set()
self.manifests = set()
def load(self, path):
"""Parse a reftest manifest file."""
normalized = os.path.normpath(os.path.abspath(path))
self.manifests.add(normalized)
if not self.path:
self.path = normalized
mdir = os.path.dirname(normalized)
self.dirs.add(mdir)
with open(path, 'r') as fh:
urlprefix = ''
for line in fh:
line = line.decode('utf-8')
# Entire line is a comment.
if line.startswith('#'):
continue
# Comments can begin mid line. Strip them.
m = RE_COMMENT.search(line)
if m:
line = line[:m.start()]
line = line.strip()
if not line:
continue
items = line.split()
tests = []
for i in range(len(items)):
item = items[i]
if item.startswith(FAILURE_TYPES):
continue
if item.startswith(PREF_ITEMS):
continue
if item == 'HTTP':
continue
m = RE_HTTP.match(item)
if m:
# Need to package the referenced directory.
self.dirs.add(os.path.normpath(os.path.join(
mdir, m.group(1))))
continue
if item == 'url-prefix':
urlprefix = items[i+1]
break
if item == 'default-preferences':
break
if item == 'include':
self.load(os.path.join(mdir, items[i+1]))
break
if item == 'load' or item == 'script':
tests.append(items[i+1])
break
if item == '==' or item == '!=':
tests.extend(items[i+1:i+3])
break
for f in tests:
# We can't package about: or data: URIs.
# Discarding data isn't correct for a parser. But retaining
# all data isn't currently a requirement.
if RE_PROTOCOL.match(f):
continue
test = os.path.normpath(os.path.join(mdir, urlprefix + f))
self.files.add(test)
self.dirs.add(os.path.dirname(test))

Просмотреть файл

@ -121,7 +121,7 @@ pref("browser.sessionhistory.max_entries", 50);
pref("browser.sessionstore.resume_session_once", false); pref("browser.sessionstore.resume_session_once", false);
pref("browser.sessionstore.resume_from_crash", true); pref("browser.sessionstore.resume_from_crash", true);
pref("browser.sessionstore.interval", 10000); // milliseconds pref("browser.sessionstore.interval", 10000); // milliseconds
pref("browser.sessionstore.max_tabs_undo", 1); pref("browser.sessionstore.max_tabs_undo", 5);
pref("browser.sessionstore.max_resumed_crashes", 1); pref("browser.sessionstore.max_resumed_crashes", 1);
pref("browser.sessionstore.recent_crashes", 0); pref("browser.sessionstore.recent_crashes", 0);

Просмотреть файл

@ -1130,6 +1130,10 @@ abstract public class BrowserApp extends GeckoApp
// Do exactly the same thing as if you tapped 'Sync' in Settings. // Do exactly the same thing as if you tapped 'Sync' in Settings.
final Intent intent = new Intent(getContext(), FxAccountGetStartedActivity.class); final Intent intent = new Intent(getContext(), FxAccountGetStartedActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
final NativeJSObject extras = message.optObject("extras", null);
if (extras != null) {
intent.putExtra("extras", extras.toString());
}
getContext().startActivity(intent); getContext().startActivity(intent);
} else if ("CharEncoding:Data".equals(event)) { } else if ("CharEncoding:Data".equals(event)) {

Просмотреть файл

@ -15,6 +15,7 @@ public class FxAccountConstants {
public static final String DEFAULT_AUTH_SERVER_ENDPOINT = "https://api.accounts.firefox.com/v1"; public static final String DEFAULT_AUTH_SERVER_ENDPOINT = "https://api.accounts.firefox.com/v1";
public static final String DEFAULT_TOKEN_SERVER_ENDPOINT = "https://token.services.mozilla.com/1.0/sync/1.5"; public static final String DEFAULT_TOKEN_SERVER_ENDPOINT = "https://token.services.mozilla.com/1.0/sync/1.5";
public static final String STAGE_AUTH_SERVER_ENDPOINT = "https://api-accounts.stage.mozaws.net/v1";
public static final String STAGE_TOKEN_SERVER_ENDPOINT = "https://token.stage.mozaws.net/1.0/sync/1.5"; public static final String STAGE_TOKEN_SERVER_ENDPOINT = "https://token.stage.mozaws.net/1.0/sync/1.5";
// For extra debugging. Not final so it can be changed from Fennec, or from // For extra debugging. Not final so it can be changed from Fennec, or from

Просмотреть файл

@ -23,6 +23,7 @@ import org.mozilla.gecko.fxa.authenticator.AndroidFxAccount;
import org.mozilla.gecko.fxa.login.Engaged; import org.mozilla.gecko.fxa.login.Engaged;
import org.mozilla.gecko.fxa.login.State; import org.mozilla.gecko.fxa.login.State;
import org.mozilla.gecko.fxa.tasks.FxAccountSetupTask.ProgressDisplay; import org.mozilla.gecko.fxa.tasks.FxAccountSetupTask.ProgressDisplay;
import org.mozilla.gecko.sync.ExtendedJSONObject;
import org.mozilla.gecko.sync.SyncConfiguration; import org.mozilla.gecko.sync.SyncConfiguration;
import org.mozilla.gecko.sync.setup.Constants; import org.mozilla.gecko.sync.setup.Constants;
import org.mozilla.gecko.sync.setup.activities.ActivityUtils; import org.mozilla.gecko.sync.setup.activities.ActivityUtils;
@ -32,6 +33,7 @@ import android.accounts.AccountManager;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.os.AsyncTask; import android.os.AsyncTask;
import android.os.Bundle;
import android.text.Editable; import android.text.Editable;
import android.text.TextWatcher; import android.text.TextWatcher;
import android.text.method.PasswordTransformationMethod; import android.text.method.PasswordTransformationMethod;
@ -50,6 +52,16 @@ import android.widget.TextView;
import android.widget.TextView.OnEditorActionListener; import android.widget.TextView.OnEditorActionListener;
abstract public class FxAccountAbstractSetupActivity extends FxAccountAbstractActivity implements ProgressDisplay { abstract public class FxAccountAbstractSetupActivity extends FxAccountAbstractActivity implements ProgressDisplay {
public static final String EXTRA_EMAIL = "email";
public static final String EXTRA_PASSWORD = "password";
public static final String EXTRA_PASSWORD_SHOWN = "password_shown";
public static final String EXTRA_YEAR = "year";
public static final String EXTRA_EXTRAS = "extras";
public static final String JSON_KEY_AUTH = "auth";
public static final String JSON_KEY_SERVICES = "services";
public static final String JSON_KEY_SYNC = "sync";
public FxAccountAbstractSetupActivity() { public FxAccountAbstractSetupActivity() {
super(CANNOT_RESUME_WHEN_ACCOUNTS_EXIST | CANNOT_RESUME_WHEN_LOCKED_OUT); super(CANNOT_RESUME_WHEN_ACCOUNTS_EXIST | CANNOT_RESUME_WHEN_LOCKED_OUT);
} }
@ -60,6 +72,10 @@ abstract public class FxAccountAbstractSetupActivity extends FxAccountAbstractAc
private static final String LOG_TAG = FxAccountAbstractSetupActivity.class.getSimpleName(); private static final String LOG_TAG = FxAccountAbstractSetupActivity.class.getSimpleName();
// By default, any custom server configuration is only shown when the account
// is configured to use a custom server.
private static boolean ALWAYS_SHOW_CUSTOM_SERVER_LAYOUT = false;
protected int minimumPasswordLength = 8; protected int minimumPasswordLength = 8;
protected AutoCompleteTextView emailEdit; protected AutoCompleteTextView emailEdit;
@ -69,33 +85,47 @@ abstract public class FxAccountAbstractSetupActivity extends FxAccountAbstractAc
protected Button button; protected Button button;
protected ProgressBar progressBar; protected ProgressBar progressBar;
private String authServerEndpoint;
private String syncServerEndpoint;
protected String getAuthServerEndpoint() {
return authServerEndpoint;
}
protected String getTokenServerEndpoint() {
return syncServerEndpoint;
}
protected void createShowPasswordButton() { protected void createShowPasswordButton() {
showPasswordButton.setOnClickListener(new OnClickListener() { showPasswordButton.setOnClickListener(new OnClickListener() {
@SuppressWarnings("deprecation")
@Override @Override
public void onClick(View v) { public void onClick(View v) {
boolean isShown = passwordEdit.getTransformationMethod() instanceof SingleLineTransformationMethod; boolean isShown = passwordEdit.getTransformationMethod() instanceof SingleLineTransformationMethod;
setPasswordButtonShown(!isShown);
// Changing input type loses position in edit text; let's try to maintain it.
int start = passwordEdit.getSelectionStart();
int stop = passwordEdit.getSelectionEnd();
if (isShown) {
passwordEdit.setTransformationMethod(PasswordTransformationMethod.getInstance());
showPasswordButton.setText(R.string.fxaccount_password_show);
showPasswordButton.setBackgroundDrawable(getResources().getDrawable(R.drawable.fxaccount_password_button_show_background));
showPasswordButton.setTextColor(getResources().getColor(R.color.fxaccount_password_show_textcolor));
} else {
passwordEdit.setTransformationMethod(SingleLineTransformationMethod.getInstance());
showPasswordButton.setText(R.string.fxaccount_password_hide);
showPasswordButton.setBackgroundDrawable(getResources().getDrawable(R.drawable.fxaccount_password_button_hide_background));
showPasswordButton.setTextColor(getResources().getColor(R.color.fxaccount_password_hide_textcolor));
}
passwordEdit.setSelection(start, stop);
} }
}); });
} }
@SuppressWarnings("deprecation")
protected void setPasswordButtonShown(boolean shouldShow) {
// Changing input type loses position in edit text; let's try to maintain it.
int start = passwordEdit.getSelectionStart();
int stop = passwordEdit.getSelectionEnd();
if (!shouldShow) {
passwordEdit.setTransformationMethod(PasswordTransformationMethod.getInstance());
showPasswordButton.setText(R.string.fxaccount_password_show);
showPasswordButton.setBackgroundDrawable(getResources().getDrawable(R.drawable.fxaccount_password_button_show_background));
showPasswordButton.setTextColor(getResources().getColor(R.color.fxaccount_password_show_textcolor));
} else {
passwordEdit.setTransformationMethod(SingleLineTransformationMethod.getInstance());
showPasswordButton.setText(R.string.fxaccount_password_hide);
showPasswordButton.setBackgroundDrawable(getResources().getDrawable(R.drawable.fxaccount_password_button_hide_background));
showPasswordButton.setTextColor(getResources().getColor(R.color.fxaccount_password_hide_textcolor));
}
passwordEdit.setSelection(start, stop);
}
protected void linkifyPolicy() { protected void linkifyPolicy() {
TextView policyView = (TextView) ensureFindViewById(null, R.id.policy, "policy links"); TextView policyView = (TextView) ensureFindViewById(null, R.id.policy, "policy links");
final String linkTerms = getString(R.string.fxaccount_link_tos); final String linkTerms = getString(R.string.fxaccount_link_tos);
@ -262,7 +292,7 @@ abstract public class FxAccountAbstractSetupActivity extends FxAccountAbstractAc
AndroidFxAccount fxAccount; AndroidFxAccount fxAccount;
try { try {
final String profile = Constants.DEFAULT_PROFILE; final String profile = Constants.DEFAULT_PROFILE;
final String tokenServerURI = FxAccountConstants.DEFAULT_TOKEN_SERVER_ENDPOINT; final String tokenServerURI = getTokenServerEndpoint();
// It is crucial that we use the email address provided by the server // It is crucial that we use the email address provided by the server
// (rather than whatever the user entered), because the user's keys are // (rather than whatever the user entered), because the user's keys are
// wrapped and salted with the initial email they provided to // wrapped and salted with the initial email they provided to
@ -356,6 +386,31 @@ abstract public class FxAccountAbstractSetupActivity extends FxAccountAbstractAc
emailEdit.setAdapter(adapter); emailEdit.setAdapter(adapter);
} }
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
protected void updateFromIntentExtras() {
// Only set email/password in onCreate; we don't want to overwrite edited values onResume.
if (getIntent() != null && getIntent().getExtras() != null) {
Bundle bundle = getIntent().getExtras();
emailEdit.setText(bundle.getString(EXTRA_EMAIL));
passwordEdit.setText(bundle.getString(EXTRA_PASSWORD));
setPasswordButtonShown(bundle.getBoolean(EXTRA_PASSWORD_SHOWN, false));
}
// This sets defaults as well as extracting from extras, so it's not conditional.
updateServersFromIntentExtras(getIntent());
if (FxAccountConstants.LOG_PERSONAL_INFORMATION) {
FxAccountConstants.pii(LOG_TAG, "Using auth server: " + authServerEndpoint);
FxAccountConstants.pii(LOG_TAG, "Using sync server: " + syncServerEndpoint);
}
updateCustomServerView();
}
@Override @Override
public void onResume() { public void onResume() {
super.onResume(); super.onResume();
@ -370,4 +425,110 @@ abstract public class FxAccountAbstractSetupActivity extends FxAccountAbstractAc
}; };
task.execute(); task.execute();
} }
protected Bundle makeExtrasBundle(String email, String password) {
final Bundle bundle = new Bundle();
// Pass through any extras that we were started with.
if (getIntent() != null && getIntent().getExtras() != null) {
bundle.putAll(getIntent().getExtras());
}
// Overwrite with current settings.
if (email == null) {
email = emailEdit.getText().toString();
}
if (password == null) {
password = passwordEdit.getText().toString();
}
bundle.putString(EXTRA_EMAIL, email);
bundle.putString(EXTRA_PASSWORD, password);
boolean isPasswordShown = passwordEdit.getTransformationMethod() instanceof SingleLineTransformationMethod;
bundle.putBoolean(EXTRA_PASSWORD_SHOWN, isPasswordShown);
return bundle;
}
protected void startActivityInstead(Class<?> cls, int requestCode, Bundle extras) {
Intent intent = new Intent(this, cls);
if (extras != null) {
intent.putExtras(extras);
}
// Per http://stackoverflow.com/a/8992365, this triggers a known bug with
// the soft keyboard not being shown for the started activity. Why, Android, why?
intent.setFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION);
startActivityForResult(intent, requestCode);
}
protected void updateServersFromIntentExtras(Intent intent) {
// Start with defaults.
this.authServerEndpoint = FxAccountConstants.DEFAULT_AUTH_SERVER_ENDPOINT;
this.syncServerEndpoint = FxAccountConstants.DEFAULT_TOKEN_SERVER_ENDPOINT;
if (intent == null) {
Logger.warn(LOG_TAG, "Intent is null; ignoring and using default servers.");
return;
}
final String extrasString = intent.getStringExtra(EXTRA_EXTRAS);
if (extrasString == null) {
return;
}
final ExtendedJSONObject extras;
final ExtendedJSONObject services;
try {
extras = new ExtendedJSONObject(extrasString);
services = extras.getObject(JSON_KEY_SERVICES);
} catch (Exception e) {
Logger.warn(LOG_TAG, "Got exception parsing extras; ignoring and using default servers.");
return;
}
String authServer = extras.getString(JSON_KEY_AUTH);
String syncServer = services == null ? null : services.getString(JSON_KEY_SYNC);
if (authServer != null) {
this.authServerEndpoint = authServer;
}
if (syncServer != null) {
this.syncServerEndpoint = syncServer;
}
if (FxAccountConstants.DEFAULT_TOKEN_SERVER_ENDPOINT.equals(syncServerEndpoint) &&
!FxAccountConstants.DEFAULT_AUTH_SERVER_ENDPOINT.equals(authServerEndpoint)) {
// We really don't want to hard-code assumptions about server
// configurations into client code in such a way that if and when the
// situation is relaxed, the client code stops valid usage. Instead, we
// warn. This configuration should present itself as an auth exception at
// Sync time.
Logger.warn(LOG_TAG, "Mozilla's Sync token servers only works with Mozilla's auth servers. Sync will likely be mis-configured.");
}
}
protected void updateCustomServerView() {
final boolean shouldShow =
ALWAYS_SHOW_CUSTOM_SERVER_LAYOUT ||
!FxAccountConstants.DEFAULT_AUTH_SERVER_ENDPOINT.equals(authServerEndpoint) ||
!FxAccountConstants.DEFAULT_TOKEN_SERVER_ENDPOINT.equals(syncServerEndpoint);
if (!shouldShow) {
setCustomServerViewVisibility(View.GONE);
return;
}
final TextView authServerView = (TextView) ensureFindViewById(null, R.id.account_server_summary, "account server");
final TextView syncServerView = (TextView) ensureFindViewById(null, R.id.sync_server_summary, "Sync server");
authServerView.setText(authServerEndpoint);
syncServerView.setText(syncServerEndpoint);
setCustomServerViewVisibility(View.VISIBLE);
}
protected void setCustomServerViewVisibility(int visibility) {
ensureFindViewById(null, R.id.account_server_layout, "account server layout").setVisibility(visibility);
ensureFindViewById(null, R.id.sync_server_layout, "sync server layout").setVisibility(visibility);
}
} }

Просмотреть файл

@ -89,32 +89,29 @@ public class FxAccountCreateAccountActivity extends FxAccountAbstractSetupActivi
signInInsteadLink.setOnClickListener(new OnClickListener() { signInInsteadLink.setOnClickListener(new OnClickListener() {
@Override @Override
public void onClick(View v) { public void onClick(View v) {
final String email = emailEdit.getText().toString(); final Bundle extras = makeExtrasBundle(null, null);
final String password = passwordEdit.getText().toString(); startActivityInstead(FxAccountSignInActivity.class, CHILD_REQUEST_CODE, extras);
doSigninInstead(email, password);
} }
}); });
// Only set email/password in onCreate; we don't want to overwrite edited values onResume. updateFromIntentExtras();
if (getIntent() != null && getIntent().getExtras() != null) {
Bundle bundle = getIntent().getExtras();
emailEdit.setText(bundle.getString("email"));
passwordEdit.setText(bundle.getString("password"));
}
} }
protected void doSigninInstead(final String email, final String password) { @Override
Intent intent = new Intent(this, FxAccountSignInActivity.class); protected Bundle makeExtrasBundle(String email, String password) {
if (email != null) { final Bundle extras = super.makeExtrasBundle(email, password);
intent.putExtra("email", email); final String year = yearEdit.getText().toString();
extras.putString(EXTRA_YEAR, year);
return extras;
}
@Override
protected void updateFromIntentExtras() {
super.updateFromIntentExtras();
if (getIntent() != null) {
yearEdit.setText(getIntent().getStringExtra(EXTRA_YEAR));
} }
if (password != null) {
intent.putExtra("password", password);
}
// Per http://stackoverflow.com/a/8992365, this triggers a known bug with
// the soft keyboard not being shown for the started activity. Why, Android, why?
intent.setFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION);
startActivityForResult(intent, CHILD_REQUEST_CODE);
} }
@Override @Override
@ -142,7 +139,9 @@ public class FxAccountCreateAccountActivity extends FxAccountAbstractSetupActivi
email = emailEdit.getText().toString(); email = emailEdit.getText().toString();
} }
final String password = passwordEdit.getText().toString(); final String password = passwordEdit.getText().toString();
doSigninInstead(email, password);
final Bundle extras = makeExtrasBundle(email, password);
startActivityInstead(FxAccountSignInActivity.class, CHILD_REQUEST_CODE, extras);
} }
}, clickableStart, clickableEnd, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); }, clickableStart, clickableEnd, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
remoteErrorTextView.setMovementMethod(LinkMovementMethod.getInstance()); remoteErrorTextView.setMovementMethod(LinkMovementMethod.getInstance());
@ -205,7 +204,7 @@ public class FxAccountCreateAccountActivity extends FxAccountAbstractSetupActivi
} }
public void createAccount(String email, String password, Map<String, Boolean> engines) { public void createAccount(String email, String password, Map<String, Boolean> engines) {
String serverURI = FxAccountConstants.DEFAULT_AUTH_SERVER_ENDPOINT; String serverURI = getAuthServerEndpoint();
PasswordStretcher passwordStretcher = makePasswordStretcher(password); PasswordStretcher passwordStretcher = makePasswordStretcher(password);
// This delegate creates a new Android account on success, opens the // This delegate creates a new Android account on success, opens the
// appropriate "success!" activity, and finishes this activity. // appropriate "success!" activity, and finishes this activity.

Просмотреть файл

@ -50,15 +50,26 @@ public class FxAccountGetStartedActivity extends AccountAuthenticatorActivity {
button.setOnClickListener(new OnClickListener() { button.setOnClickListener(new OnClickListener() {
@Override @Override
public void onClick(View v) { public void onClick(View v) {
Intent intent = new Intent(FxAccountGetStartedActivity.this, FxAccountCreateAccountActivity.class); Bundle extras = null; // startFlow accepts null.
// Per http://stackoverflow.com/a/8992365, this triggers a known bug with if (getIntent() != null) {
// the soft keyboard not being shown for the started activity. Why, Android, why? extras = getIntent().getExtras();
intent.setFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION); }
startActivityForResult(intent, CHILD_REQUEST_CODE); startFlow(extras);
} }
}); });
} }
protected void startFlow(Bundle extras) {
final Intent intent = new Intent(this, FxAccountCreateAccountActivity.class);
// Per http://stackoverflow.com/a/8992365, this triggers a known bug with
// the soft keyboard not being shown for the started activity. Why, Android, why?
intent.setFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION);
if (extras != null) {
intent.putExtras(extras);
}
startActivityForResult(intent, CHILD_REQUEST_CODE);
}
@Override @Override
public void onResume() { public void onResume() {
super.onResume(); super.onResume();
@ -79,6 +90,20 @@ public class FxAccountGetStartedActivity extends AccountAuthenticatorActivity {
this.startActivity(intent); this.startActivity(intent);
this.finish(); this.finish();
} }
// If we've been launched with extras (namely custom server URLs), continue
// past go and collect 200 dollars. If we ever get back here (for example,
// if the user hits the back button), forget that we had extras entirely, so
// that we don't enter a loop.
Bundle extras = null;
if (getIntent() != null) {
extras = getIntent().getExtras();
}
if (extras != null && extras.containsKey(FxAccountAbstractSetupActivity.EXTRA_EXTRAS)) {
getIntent().replaceExtras(Bundle.EMPTY);
startFlow((Bundle) extras.clone());
return;
}
} }
/** /**

Просмотреть файл

@ -15,7 +15,6 @@ import org.mozilla.gecko.background.fxa.FxAccountClient20;
import org.mozilla.gecko.background.fxa.FxAccountClient20.LoginResponse; import org.mozilla.gecko.background.fxa.FxAccountClient20.LoginResponse;
import org.mozilla.gecko.background.fxa.FxAccountClientException.FxAccountClientRemoteException; import org.mozilla.gecko.background.fxa.FxAccountClientException.FxAccountClientRemoteException;
import org.mozilla.gecko.background.fxa.PasswordStretcher; import org.mozilla.gecko.background.fxa.PasswordStretcher;
import org.mozilla.gecko.fxa.FxAccountConstants;
import org.mozilla.gecko.fxa.tasks.FxAccountSignInTask; import org.mozilla.gecko.fxa.tasks.FxAccountSignInTask;
import org.mozilla.gecko.sync.setup.activities.ActivityUtils; import org.mozilla.gecko.sync.setup.activities.ActivityUtils;
@ -65,22 +64,12 @@ public class FxAccountSignInActivity extends FxAccountAbstractSetupActivity {
createAccountInsteadLink.setOnClickListener(new OnClickListener() { createAccountInsteadLink.setOnClickListener(new OnClickListener() {
@Override @Override
public void onClick(View v) { public void onClick(View v) {
Intent intent = new Intent(FxAccountSignInActivity.this, FxAccountCreateAccountActivity.class); final Bundle extras = makeExtrasBundle(null, null);
intent.putExtra("email", emailEdit.getText().toString()); startActivityInstead(FxAccountCreateAccountActivity.class, CHILD_REQUEST_CODE, extras);
intent.putExtra("password", passwordEdit.getText().toString());
// Per http://stackoverflow.com/a/8992365, this triggers a known bug with
// the soft keyboard not being shown for the started activity. Why, Android, why?
intent.setFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION);
startActivityForResult(intent, CHILD_REQUEST_CODE);
} }
}); });
// Only set email/password in onCreate; we don't want to overwrite edited values onResume. updateFromIntentExtras();
if (getIntent() != null && getIntent().getExtras() != null) {
Bundle bundle = getIntent().getExtras();
emailEdit.setText(bundle.getString("email"));
passwordEdit.setText(bundle.getString("password"));
}
TextView view = (TextView) findViewById(R.id.forgot_password_link); TextView view = (TextView) findViewById(R.id.forgot_password_link);
ActivityUtils.linkTextView(view, R.string.fxaccount_sign_in_forgot_password, R.string.fxaccount_link_forgot_password); ActivityUtils.linkTextView(view, R.string.fxaccount_sign_in_forgot_password, R.string.fxaccount_link_forgot_password);
@ -102,7 +91,7 @@ public class FxAccountSignInActivity extends FxAccountAbstractSetupActivity {
} }
public void signIn(String email, String password) { public void signIn(String email, String password) {
String serverURI = FxAccountConstants.DEFAULT_AUTH_SERVER_ENDPOINT; String serverURI = getAuthServerEndpoint();
PasswordStretcher passwordStretcher = makePasswordStretcher(password); PasswordStretcher passwordStretcher = makePasswordStretcher(password);
// This delegate creates a new Android account on success, opens the // This delegate creates a new Android account on success, opens the
// appropriate "success!" activity, and finishes this activity. // appropriate "success!" activity, and finishes this activity.

Просмотреть файл

@ -18,6 +18,7 @@ import org.mozilla.gecko.fxa.login.Married;
import org.mozilla.gecko.fxa.login.State; import org.mozilla.gecko.fxa.login.State;
import org.mozilla.gecko.fxa.sync.FxAccountSyncStatusHelper; import org.mozilla.gecko.fxa.sync.FxAccountSyncStatusHelper;
import org.mozilla.gecko.fxa.tasks.FxAccountCodeResender; import org.mozilla.gecko.fxa.tasks.FxAccountCodeResender;
import org.mozilla.gecko.sync.ExtendedJSONObject;
import org.mozilla.gecko.sync.SharedPreferencesClientsDataDelegate; import org.mozilla.gecko.sync.SharedPreferencesClientsDataDelegate;
import org.mozilla.gecko.sync.SyncConfiguration; import org.mozilla.gecko.sync.SyncConfiguration;
@ -56,7 +57,17 @@ public class FxAccountStatusFragment
// collection. // collection.
private static final long DELAY_IN_MILLISECONDS_BEFORE_REQUESTING_SYNC = 5 * 1000; private static final long DELAY_IN_MILLISECONDS_BEFORE_REQUESTING_SYNC = 5 * 1000;
// By default, the auth/account server preference is only shown when the
// account is configured to use a custom server. In debug mode, this is set.
private static boolean ALWAYS_SHOW_AUTH_SERVER = false;
// By default, the Sync server preference is only shown when the account is
// configured to use a custom Sync server. In debug mode, this is set.
private static boolean ALWAYS_SHOW_SYNC_SERVER = false;
protected PreferenceCategory accountCategory;
protected Preference emailPreference; protected Preference emailPreference;
protected Preference authServerPreference;
protected Preference needsPasswordPreference; protected Preference needsPasswordPreference;
protected Preference needsUpgradePreference; protected Preference needsUpgradePreference;
@ -72,6 +83,7 @@ public class FxAccountStatusFragment
protected CheckBoxPreference passwordsPreference; protected CheckBoxPreference passwordsPreference;
protected EditTextPreference deviceNamePreference; protected EditTextPreference deviceNamePreference;
protected Preference syncServerPreference;
protected volatile AndroidFxAccount fxAccount; protected volatile AndroidFxAccount fxAccount;
// The contract is: when fxAccount is non-null, then clientsDataDelegate is // The contract is: when fxAccount is non-null, then clientsDataDelegate is
@ -105,7 +117,9 @@ public class FxAccountStatusFragment
protected void addPreferences() { protected void addPreferences() {
addPreferencesFromResource(R.xml.fxaccount_status_prefscreen); addPreferencesFromResource(R.xml.fxaccount_status_prefscreen);
accountCategory = (PreferenceCategory) ensureFindPreference("signed_in_as_category");
emailPreference = ensureFindPreference("email"); emailPreference = ensureFindPreference("email");
authServerPreference = ensureFindPreference("auth_server");
needsPasswordPreference = ensureFindPreference("needs_credentials"); needsPasswordPreference = ensureFindPreference("needs_credentials");
needsUpgradePreference = ensureFindPreference("needs_upgrade"); needsUpgradePreference = ensureFindPreference("needs_upgrade");
@ -124,6 +138,8 @@ public class FxAccountStatusFragment
removeDebugButtons(); removeDebugButtons();
} else { } else {
connectDebugButtons(); connectDebugButtons();
ALWAYS_SHOW_AUTH_SERVER = true;
ALWAYS_SHOW_SYNC_SERVER = true;
} }
needsPasswordPreference.setOnPreferenceClickListener(this); needsPasswordPreference.setOnPreferenceClickListener(this);
@ -137,6 +153,8 @@ public class FxAccountStatusFragment
deviceNamePreference = (EditTextPreference) ensureFindPreference("device_name"); deviceNamePreference = (EditTextPreference) ensureFindPreference("device_name");
deviceNamePreference.setOnPreferenceChangeListener(this); deviceNamePreference.setOnPreferenceChangeListener(this);
syncServerPreference = ensureFindPreference("sync_server");
} }
/** /**
@ -152,6 +170,10 @@ public class FxAccountStatusFragment
public boolean onPreferenceClick(Preference preference) { public boolean onPreferenceClick(Preference preference) {
if (preference == needsPasswordPreference) { if (preference == needsPasswordPreference) {
Intent intent = new Intent(getActivity(), FxAccountUpdateCredentialsActivity.class); Intent intent = new Intent(getActivity(), FxAccountUpdateCredentialsActivity.class);
final Bundle extras = getExtrasForAccount();
if (extras != null) {
intent.putExtras(extras);
}
// Per http://stackoverflow.com/a/8992365, this triggers a known bug with // Per http://stackoverflow.com/a/8992365, this triggers a known bug with
// the soft keyboard not being shown for the started activity. Why, Android, why? // the soft keyboard not being shown for the started activity. Why, Android, why?
intent.setFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION); intent.setFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION);
@ -190,6 +212,17 @@ public class FxAccountStatusFragment
return false; return false;
} }
protected Bundle getExtrasForAccount() {
final Bundle extras = new Bundle();
final ExtendedJSONObject o = new ExtendedJSONObject();
o.put(FxAccountAbstractSetupActivity.JSON_KEY_AUTH, fxAccount.getAccountServerURI());
final ExtendedJSONObject services = new ExtendedJSONObject();
services.put(FxAccountAbstractSetupActivity.JSON_KEY_SYNC, fxAccount.getTokenServerURI());
o.put(FxAccountAbstractSetupActivity.JSON_KEY_SERVICES, services);
extras.putString(FxAccountAbstractSetupActivity.EXTRA_EXTRAS, o.toJSONString());
return extras;
}
protected void setCheckboxesEnabled(boolean enabled) { protected void setCheckboxesEnabled(boolean enabled) {
bookmarksPreference.setEnabled(enabled); bookmarksPreference.setEnabled(enabled);
historyPreference.setEnabled(enabled); historyPreference.setEnabled(enabled);
@ -367,6 +400,8 @@ public class FxAccountStatusFragment
} }
emailPreference.setTitle(fxAccount.getEmail()); emailPreference.setTitle(fxAccount.getEmail());
updateAuthServerPreference();
updateSyncServerPreference();
try { try {
// There are error states determined by Android, not the login state // There are error states determined by Android, not the login state
@ -417,6 +452,38 @@ public class FxAccountStatusFragment
deviceNamePreference.setText(clientName); deviceNamePreference.setText(clientName);
} }
protected void updateAuthServerPreference() {
final String authServer = fxAccount.getAccountServerURI();
final boolean shouldBeShown = ALWAYS_SHOW_AUTH_SERVER || !FxAccountConstants.DEFAULT_AUTH_SERVER_ENDPOINT.equals(authServer);
final boolean currentlyShown = null != findPreference(authServerPreference.getKey());
if (currentlyShown != shouldBeShown) {
if (shouldBeShown) {
accountCategory.addPreference(authServerPreference);
} else {
accountCategory.removePreference(authServerPreference);
}
}
// Always set the summary, because on first run, the preference is visible,
// and the above block will be skipped if there is a custom value.
authServerPreference.setSummary(authServer);
}
protected void updateSyncServerPreference() {
final String syncServer = fxAccount.getTokenServerURI();
final boolean shouldBeShown = ALWAYS_SHOW_SYNC_SERVER || !FxAccountConstants.DEFAULT_TOKEN_SERVER_ENDPOINT.equals(syncServer);
final boolean currentlyShown = null != findPreference(syncServerPreference.getKey());
if (currentlyShown != shouldBeShown) {
if (shouldBeShown) {
syncCategory.addPreference(syncServerPreference);
} else {
syncCategory.removePreference(syncServerPreference);
}
}
// Always set the summary, because on first run, the preference is visible,
// and the above block will be skipped if there is a custom value.
syncServerPreference.setSummary(syncServer);
}
/** /**
* Query shared prefs for the current engine state, and update the UI * Query shared prefs for the current engine state, and update the UI
* accordingly. * accordingly.

Просмотреть файл

@ -77,6 +77,8 @@ public class FxAccountUpdateCredentialsActivity extends FxAccountAbstractSetupAc
TextView view = (TextView) findViewById(R.id.forgot_password_link); TextView view = (TextView) findViewById(R.id.forgot_password_link);
ActivityUtils.linkTextView(view, R.string.fxaccount_sign_in_forgot_password, R.string.fxaccount_link_forgot_password); ActivityUtils.linkTextView(view, R.string.fxaccount_sign_in_forgot_password, R.string.fxaccount_link_forgot_password);
updateFromIntentExtras();
} }
@Override @Override

Просмотреть файл

@ -56,18 +56,18 @@ public class AccountPickler {
public static final long PICKLE_VERSION = 2; public static final long PICKLE_VERSION = 2;
private static final String KEY_PICKLE_VERSION = "pickle_version"; public static final String KEY_PICKLE_VERSION = "pickle_version";
private static final String KEY_PICKLE_TIMESTAMP = "pickle_timestamp"; public static final String KEY_PICKLE_TIMESTAMP = "pickle_timestamp";
private static final String KEY_ACCOUNT_VERSION = "account_version"; public static final String KEY_ACCOUNT_VERSION = "account_version";
private static final String KEY_ACCOUNT_TYPE = "account_type"; public static final String KEY_ACCOUNT_TYPE = "account_type";
private static final String KEY_EMAIL = "email"; public static final String KEY_EMAIL = "email";
private static final String KEY_PROFILE = "profile"; public static final String KEY_PROFILE = "profile";
private static final String KEY_IDP_SERVER_URI = "idpServerURI"; public static final String KEY_IDP_SERVER_URI = "idpServerURI";
private static final String KEY_TOKEN_SERVER_URI = "tokenServerURI"; public static final String KEY_TOKEN_SERVER_URI = "tokenServerURI";
private static final String KEY_IS_SYNCING_ENABLED = "isSyncingEnabled"; public static final String KEY_IS_SYNCING_ENABLED = "isSyncingEnabled";
private static final String KEY_BUNDLE = "bundle"; public static final String KEY_BUNDLE = "bundle";
/** /**
* Remove Firefox account persisted to disk. * Remove Firefox account persisted to disk.
@ -80,16 +80,10 @@ public class AccountPickler {
return context.deleteFile(filename); return context.deleteFile(filename);
} }
/** public static ExtendedJSONObject toJSON(final AndroidFxAccount account, final long now) {
* Persist Firefox account to disk as a JSON object.
*
* @param AndroidFxAccount the account to persist to disk
* @param filename name of file to persist to; must not contain path separators.
*/
public static void pickle(final AndroidFxAccount account, final String filename) {
final ExtendedJSONObject o = new ExtendedJSONObject(); final ExtendedJSONObject o = new ExtendedJSONObject();
o.put(KEY_PICKLE_VERSION, Long.valueOf(PICKLE_VERSION)); o.put(KEY_PICKLE_VERSION, Long.valueOf(PICKLE_VERSION));
o.put(KEY_PICKLE_TIMESTAMP, Long.valueOf(System.currentTimeMillis())); o.put(KEY_PICKLE_TIMESTAMP, Long.valueOf(now));
o.put(KEY_ACCOUNT_VERSION, AndroidFxAccount.CURRENT_ACCOUNT_VERSION); o.put(KEY_ACCOUNT_VERSION, AndroidFxAccount.CURRENT_ACCOUNT_VERSION);
o.put(KEY_ACCOUNT_TYPE, FxAccountConstants.ACCOUNT_TYPE); o.put(KEY_ACCOUNT_TYPE, FxAccountConstants.ACCOUNT_TYPE);
@ -104,10 +98,21 @@ public class AccountPickler {
final ExtendedJSONObject bundle = account.unbundle(); final ExtendedJSONObject bundle = account.unbundle();
if (bundle == null) { if (bundle == null) {
Logger.warn(LOG_TAG, "Unable to obtain account bundle; aborting."); Logger.warn(LOG_TAG, "Unable to obtain account bundle; aborting.");
return; return null;
} }
o.put(KEY_BUNDLE, bundle); o.put(KEY_BUNDLE, bundle);
return o;
}
/**
* Persist Firefox account to disk as a JSON object.
*
* @param AndroidFxAccount the account to persist to disk
* @param filename name of file to persist to; must not contain path separators.
*/
public static void pickle(final AndroidFxAccount account, final String filename) {
final ExtendedJSONObject o = toJSON(account, System.currentTimeMillis());
writeToDisk(account.context, filename, o); writeToDisk(account.context, filename, o);
} }

Просмотреть файл

@ -323,6 +323,9 @@ public class AndroidFxAccount {
if (email == null) { if (email == null) {
throw new IllegalArgumentException("email must not be null"); throw new IllegalArgumentException("email must not be null");
} }
if (profile == null) {
throw new IllegalArgumentException("profile must not be null");
}
if (idpServerURI == null) { if (idpServerURI == null) {
throw new IllegalArgumentException("idpServerURI must not be null"); throw new IllegalArgumentException("idpServerURI must not be null");
} }
@ -368,6 +371,15 @@ public class AndroidFxAccount {
return null; return null;
} }
// Try to work around an intermittent issue described at
// http://stackoverflow.com/a/11698139. What happens is that tests that
// delete and re-create the same account frequently will find the account
// missing all or some of the userdata bundle, possibly due to an Android
// AccountManager caching bug.
for (String key : userdata.keySet()) {
accountManager.setUserData(account, key, userdata.getString(key));
}
AndroidFxAccount fxAccount = new AndroidFxAccount(context, account); AndroidFxAccount fxAccount = new AndroidFxAccount(context, account);
if (!fromPickle) { if (!fromPickle) {

Просмотреть файл

@ -5,23 +5,81 @@
package org.mozilla.gecko.home; package org.mozilla.gecko.home;
import org.mozilla.gecko.R; import java.util.Date;
import org.mozilla.gecko.widget.IconTabWidget; import java.util.EnumSet;
import android.content.res.Configuration; import org.mozilla.gecko.R;
import org.mozilla.gecko.Telemetry;
import org.mozilla.gecko.TelemetryContract;
import org.mozilla.gecko.db.BrowserContract.Combined;
import org.mozilla.gecko.db.BrowserDB;
import org.mozilla.gecko.db.BrowserDB.URLColumns;
import org.mozilla.gecko.home.HomePager.OnUrlOpenListener;
import org.mozilla.gecko.util.ThreadUtils;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.ContentResolver;
import android.content.Context;
import android.content.DialogInterface;
import android.database.Cursor;
import android.os.Bundle; import android.os.Bundle;
import android.support.v4.app.Fragment; import android.support.v4.app.LoaderManager.LoaderCallbacks;
import android.support.v4.content.Loader;
import android.util.SparseArray;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.view.ViewStub;
import android.widget.AdapterView;
import android.widget.ImageView;
import android.widget.TextView;
public class HistoryPanel extends HomeFragment /**
implements IconTabWidget.OnTabChangedListener { * Fragment that displays recent history in a ListView.
*/
public class HistoryPanel extends HomeFragment {
// Logging tag name // Logging tag name
private static final String LOGTAG = "GeckoHistoryPanel"; private static final String LOGTAG = "GeckoHistoryPanel";
private IconTabWidget mTabWidget;
private int mSelectedTab; // Cursor loader ID for history query
private boolean initializeRecentPanel; private static final int LOADER_ID_HISTORY = 0;
// Adapter for the list of recent history entries.
private HistoryAdapter mAdapter;
// The view shown by the fragment.
private HomeListView mList;
// The button view for clearing browsing history.
private View mClearHistoryButton;
// Reference to the View to display when there are no results.
private View mEmptyView;
// Callbacks used for the search and favicon cursor loaders
private CursorLoaderCallbacks mCursorLoaderCallbacks;
// On URL open listener
private OnUrlOpenListener mUrlOpenListener;
@Override
public void onAttach(Activity activity) {
super.onAttach(activity);
try {
mUrlOpenListener = (OnUrlOpenListener) activity;
} catch (ClassCastException e) {
throw new ClassCastException(activity.toString()
+ " must implement HomePager.OnUrlOpenListener");
}
}
@Override
public void onDetach() {
super.onDetach();
mUrlOpenListener = null;
}
@Override @Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
@ -30,75 +88,335 @@ public class HistoryPanel extends HomeFragment
@Override @Override
public void onViewCreated(View view, Bundle savedInstanceState) { public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState); mList = (HomeListView) view.findViewById(R.id.list);
mList.setTag(HomePager.LIST_TAG_HISTORY);
mTabWidget = (IconTabWidget) view.findViewById(R.id.tab_icon_widget); mList.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
position -= mAdapter.getMostRecentSectionsCountBefore(position);
final Cursor c = mAdapter.getCursor(position);
final String url = c.getString(c.getColumnIndexOrThrow(URLColumns.URL));
mTabWidget.addTab(R.drawable.icon_most_recent, R.string.home_most_recent_title); Telemetry.sendUIEvent(TelemetryContract.Event.LOAD_URL);
mTabWidget.addTab(R.drawable.icon_last_tabs, R.string.home_last_tabs_title);
mTabWidget.setTabSelectionListener(this); // This item is a TwoLinePageRow, so we allow switch-to-tab.
mTabWidget.setCurrentTab(mSelectedTab); mUrlOpenListener.onUrlOpen(url, EnumSet.of(OnUrlOpenListener.Flags.ALLOW_SWITCH_TO_TAB));
}
});
mList.setContextMenuInfoFactory(new HomeContextMenuInfo.Factory() {
@Override
public HomeContextMenuInfo makeInfoForCursor(View view, int position, long id, Cursor cursor) {
final HomeContextMenuInfo info = new HomeContextMenuInfo(view, position, id);
info.url = cursor.getString(cursor.getColumnIndexOrThrow(Combined.URL));
info.title = cursor.getString(cursor.getColumnIndexOrThrow(Combined.TITLE));
info.display = cursor.getInt(cursor.getColumnIndexOrThrow(Combined.DISPLAY));
info.historyId = cursor.getInt(cursor.getColumnIndexOrThrow(Combined.HISTORY_ID));
final int bookmarkIdCol = cursor.getColumnIndexOrThrow(Combined.BOOKMARK_ID);
if (cursor.isNull(bookmarkIdCol)) {
// If this is a combined cursor, we may get a history item without a
// bookmark, in which case the bookmarks ID column value will be null.
info.bookmarkId = -1;
} else {
info.bookmarkId = cursor.getInt(bookmarkIdCol);
}
return info;
}
});
registerForContextMenu(mList);
mClearHistoryButton = view.findViewById(R.id.clear_history_button);
mClearHistoryButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
final Context context = getActivity();
final AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(context);
dialogBuilder.setMessage(R.string.home_clear_history_confirm);
dialogBuilder.setNegativeButton(R.string.button_cancel, new AlertDialog.OnClickListener() {
@Override
public void onClick(final DialogInterface dialog, final int which) {
dialog.dismiss();
}
});
dialogBuilder.setPositiveButton(R.string.button_ok, new AlertDialog.OnClickListener() {
@Override
public void onClick(final DialogInterface dialog, final int which) {
dialog.dismiss();
ThreadUtils.postToBackgroundThread(new Runnable() {
@Override
public void run() {
final ContentResolver cr = context.getContentResolver();
BrowserDB.clearHistory(cr);
}
});
Telemetry.sendUIEvent(TelemetryContract.Event.SANITIZE, TelemetryContract.Method.BUTTON, "history");
}
});
dialogBuilder.show();
}
});
}
@Override
public void onDestroyView() {
super.onDestroyView();
mList = null;
mEmptyView = null;
mClearHistoryButton = null;
}
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
// Intialize adapter
mAdapter = new HistoryAdapter(getActivity());
mList.setAdapter(mAdapter);
// Create callbacks before the initial loader is started
mCursorLoaderCallbacks = new CursorLoaderCallbacks();
loadIfVisible(); loadIfVisible();
} }
@Override @Override
public void load() { protected void load() {
// Show most recent panel as the initial panel. getLoaderManager().initLoader(LOADER_ID_HISTORY, null, mCursorLoaderCallbacks);
// Since we detach/attach on config change, this prevents from replacing current fragment. }
if (!initializeRecentPanel) {
showMostRecentPanel(); private static class HistoryCursorLoader extends SimpleCursorLoader {
initializeRecentPanel = true; // Max number of history results
private static final int HISTORY_LIMIT = 100;
public HistoryCursorLoader(Context context) {
super(context);
}
@Override
public Cursor loadCursor() {
final ContentResolver cr = getContext().getContentResolver();
return BrowserDB.getRecentHistory(cr, HISTORY_LIMIT);
} }
} }
@Override private void updateUiFromCursor(Cursor c) {
public void onTabChanged(int index) { if (c != null && c.getCount() > 0) {
if (index == mSelectedTab) { mClearHistoryButton.setVisibility(View.VISIBLE);
return; return;
} }
if (index == 0) { // Cursor is empty, so hide the "Clear browsing history" button,
showMostRecentPanel(); // and set the empty view if it hasn't been set already.
} else if (index == 1) { mClearHistoryButton.setVisibility(View.GONE);
showLastTabsPanel();
}
mTabWidget.setCurrentTab(index); if (mEmptyView == null) {
mSelectedTab = index; // Set empty panel view. We delay this so that the empty view won't flash.
} final ViewStub emptyViewStub = (ViewStub) getView().findViewById(R.id.home_empty_view_stub);
mEmptyView = emptyViewStub.inflate();
@Override final ImageView emptyIcon = (ImageView) mEmptyView.findViewById(R.id.home_empty_image);
public void onConfigurationChanged(Configuration newConfig) { emptyIcon.setImageResource(R.drawable.icon_most_recent_empty);
super.onConfigurationChanged(newConfig);
// Rotation should detach and re-attach to use a different layout. final TextView emptyText = (TextView) mEmptyView.findViewById(R.id.home_empty_text);
if (isVisible()) { emptyText.setText(R.string.home_most_recent_empty);
getFragmentManager().beginTransaction()
.detach(this) mList.setEmptyView(mEmptyView);
.attach(this)
.commitAllowingStateLoss();
} }
} }
private void showSubPanel(Fragment subPanel) { private static class HistoryAdapter extends MultiTypeCursorAdapter {
final Bundle args = new Bundle(); private static final int ROW_HEADER = 0;
args.putBoolean(HomePager.CAN_LOAD_ARG, getCanLoadHint()); private static final int ROW_STANDARD = 1;
subPanel.setArguments(args);
getChildFragmentManager().beginTransaction() private static final int[] VIEW_TYPES = new int[] { ROW_STANDARD, ROW_HEADER };
.addToBackStack(null).replace(R.id.history_panel_container, subPanel) private static final int[] LAYOUT_TYPES = new int[] { R.layout.home_item_row, R.layout.home_header_row };
.commitAllowingStateLoss();
// For the time sections in history
private static final long MS_PER_DAY = 86400000;
private static final long MS_PER_WEEK = MS_PER_DAY * 7;
// The time ranges for each section
private static enum MostRecentSection {
TODAY,
YESTERDAY,
WEEK,
OLDER
};
private final Context mContext;
// Maps headers in the list with their respective sections
private final SparseArray<MostRecentSection> mMostRecentSections;
public HistoryAdapter(Context context) {
super(context, null, VIEW_TYPES, LAYOUT_TYPES);
mContext = context;
// Initialize map of history sections
mMostRecentSections = new SparseArray<MostRecentSection>();
}
@Override
public Object getItem(int position) {
final int type = getItemViewType(position);
// Header items are not in the cursor
if (type == ROW_HEADER) {
return null;
}
return super.getItem(position - getMostRecentSectionsCountBefore(position));
}
@Override
public int getItemViewType(int position) {
if (mMostRecentSections.get(position) != null) {
return ROW_HEADER;
}
return ROW_STANDARD;
}
@Override
public boolean isEnabled(int position) {
return (getItemViewType(position) == ROW_STANDARD);
}
@Override
public int getCount() {
// Add the history section headers to the number of reported results.
return super.getCount() + mMostRecentSections.size();
}
@Override
public Cursor swapCursor(Cursor cursor) {
loadMostRecentSections(cursor);
Cursor oldCursor = super.swapCursor(cursor);
return oldCursor;
}
@Override
public void bindView(View view, Context context, int position) {
final int type = getItemViewType(position);
if (type == ROW_HEADER) {
final MostRecentSection section = mMostRecentSections.get(position);
final TextView row = (TextView) view;
row.setText(getMostRecentSectionTitle(section));
} else {
// Account for the most recent section headers
position -= getMostRecentSectionsCountBefore(position);
final Cursor c = getCursor(position);
final TwoLinePageRow row = (TwoLinePageRow) view;
row.updateFromCursor(c);
}
}
private String getMostRecentSectionTitle(MostRecentSection section) {
switch (section) {
case TODAY:
return mContext.getString(R.string.history_today_section);
case YESTERDAY:
return mContext.getString(R.string.history_yesterday_section);
case WEEK:
return mContext.getString(R.string.history_week_section);
case OLDER:
return mContext.getString(R.string.history_older_section);
}
throw new IllegalStateException("Unrecognized history section");
}
private int getMostRecentSectionsCountBefore(int position) {
// Account for the number headers before the given position
int sectionsBefore = 0;
final int historySectionsCount = mMostRecentSections.size();
for (int i = 0; i < historySectionsCount; i++) {
final int sectionPosition = mMostRecentSections.keyAt(i);
if (sectionPosition > position) {
break;
}
sectionsBefore++;
}
return sectionsBefore;
}
private static MostRecentSection getMostRecentSectionForTime(long from, long time) {
long delta = from - time;
if (delta < 0) {
return MostRecentSection.TODAY;
}
if (delta < MS_PER_DAY) {
return MostRecentSection.YESTERDAY;
}
if (delta < MS_PER_WEEK) {
return MostRecentSection.WEEK;
}
return MostRecentSection.OLDER;
}
private void loadMostRecentSections(Cursor c) {
// Clear any history sections that may have been loaded before.
mMostRecentSections.clear();
if (c == null || !c.moveToFirst()) {
return;
}
final Date now = new Date();
now.setHours(0);
now.setMinutes(0);
now.setSeconds(0);
final long today = now.getTime();
MostRecentSection section = null;
do {
final int position = c.getPosition();
final long time = c.getLong(c.getColumnIndexOrThrow(URLColumns.DATE_LAST_VISITED));
final MostRecentSection itemSection = HistoryAdapter.getMostRecentSectionForTime(today, time);
if (section != itemSection) {
section = itemSection;
mMostRecentSections.append(position + mMostRecentSections.size(), section);
}
// Reached the last section, no need to continue
if (section == MostRecentSection.OLDER) {
break;
}
} while (c.moveToNext());
}
} }
private void showMostRecentPanel() { private class CursorLoaderCallbacks implements LoaderCallbacks<Cursor> {
final MostRecentPanel mostRecentPanel = MostRecentPanel.newInstance(); @Override
showSubPanel(mostRecentPanel); public Loader<Cursor> onCreateLoader(int id, Bundle args) {
} return new HistoryCursorLoader(getActivity());
}
private void showLastTabsPanel() { @Override
final LastTabsPanel lastTabsPanel = LastTabsPanel.newInstance(); public void onLoadFinished(Loader<Cursor> loader, Cursor c) {
showSubPanel(lastTabsPanel); mAdapter.swapCursor(c);
updateUiFromCursor(c);
}
@Override
public void onLoaderReset(Loader<Cursor> loader) {
mAdapter.swapCursor(null);
}
} }
} }

Просмотреть файл

@ -40,6 +40,7 @@ public final class HomeConfig {
BOOKMARKS("bookmarks", BookmarksPanel.class), BOOKMARKS("bookmarks", BookmarksPanel.class),
HISTORY("history", HistoryPanel.class), HISTORY("history", HistoryPanel.class),
READING_LIST("reading_list", ReadingListPanel.class), READING_LIST("reading_list", ReadingListPanel.class),
RECENT_TABS("recent_tabs", RecentTabsPanel.class),
DYNAMIC("dynamic", DynamicPanel.class); DYNAMIC("dynamic", DynamicPanel.class);
private final String mId; private final String mId;
@ -1495,6 +1496,7 @@ public final class HomeConfig {
private static final String BOOKMARKS_PANEL_ID = "7f6d419a-cd6c-4e34-b26f-f68b1b551907"; private static final String BOOKMARKS_PANEL_ID = "7f6d419a-cd6c-4e34-b26f-f68b1b551907";
private static final String READING_LIST_PANEL_ID = "20f4549a-64ad-4c32-93e4-1dcef792733b"; private static final String READING_LIST_PANEL_ID = "20f4549a-64ad-4c32-93e4-1dcef792733b";
private static final String HISTORY_PANEL_ID = "f134bf20-11f7-4867-ab8b-e8e705d7fbe8"; private static final String HISTORY_PANEL_ID = "f134bf20-11f7-4867-ab8b-e8e705d7fbe8";
private static final String RECENT_TABS_PANEL_ID = "5c2601a5-eedc-4477-b297-ce4cef52adf8";
private final HomeConfigBackend mBackend; private final HomeConfigBackend mBackend;
@ -1550,6 +1552,11 @@ public final class HomeConfig {
id = READING_LIST_PANEL_ID; id = READING_LIST_PANEL_ID;
break; break;
case RECENT_TABS:
titleId = R.string.recent_tabs_title;
id = RECENT_TABS_PANEL_ID;
break;
case DYNAMIC: case DYNAMIC:
throw new IllegalArgumentException("createBuiltinPanelConfig() is only for built-in panels"); throw new IllegalArgumentException("createBuiltinPanelConfig() is only for built-in panels");
} }

Просмотреть файл

@ -36,7 +36,19 @@ import android.util.Log;
class HomeConfigPrefsBackend implements HomeConfigBackend { class HomeConfigPrefsBackend implements HomeConfigBackend {
private static final String LOGTAG = "GeckoHomeConfigBackend"; private static final String LOGTAG = "GeckoHomeConfigBackend";
private static final String PREFS_CONFIG_KEY = "home_panels"; // Increment this to trigger a migration.
private static final int VERSION = 1;
// This key was originally used to store only an array of panel configs.
private static final String PREFS_CONFIG_KEY_OLD = "home_panels";
// This key is now used to store a version number with the array of panel configs.
private static final String PREFS_CONFIG_KEY = "home_panels_with_version";
// Keys used with JSON object stored in prefs.
private static final String JSON_KEY_PANELS = "panels";
private static final String JSON_KEY_VERSION = "version";
private static final String PREFS_LOCALE_KEY = "home_locale"; private static final String PREFS_LOCALE_KEY = "home_locale";
private static final String RELOAD_BROADCAST = "HomeConfigPrefsBackend:Reload"; private static final String RELOAD_BROADCAST = "HomeConfigPrefsBackend:Reload";
@ -45,6 +57,8 @@ class HomeConfigPrefsBackend implements HomeConfigBackend {
private ReloadBroadcastReceiver mReloadBroadcastReceiver; private ReloadBroadcastReceiver mReloadBroadcastReceiver;
private OnReloadListener mReloadListener; private OnReloadListener mReloadListener;
private static boolean sMigrationDone = false;
public HomeConfigPrefsBackend(Context context) { public HomeConfigPrefsBackend(Context context) {
mContext = context; mContext = context;
} }
@ -68,22 +82,109 @@ class HomeConfigPrefsBackend implements HomeConfigBackend {
} }
final PanelConfig historyEntry = createBuiltinPanelConfig(mContext, PanelType.HISTORY); final PanelConfig historyEntry = createBuiltinPanelConfig(mContext, PanelType.HISTORY);
final PanelConfig recentTabsEntry = createBuiltinPanelConfig(mContext, PanelType.RECENT_TABS);
// On tablets, the history panel is the last. // On tablets, the history panel is the last.
// On phones, the history panel is the first one. // On phones, the history panel is the first one.
if (HardwareUtils.isTablet()) { if (HardwareUtils.isTablet()) {
panelConfigs.add(historyEntry); panelConfigs.add(historyEntry);
panelConfigs.add(recentTabsEntry);
} else { } else {
panelConfigs.add(0, historyEntry); panelConfigs.add(0, historyEntry);
panelConfigs.add(0, recentTabsEntry);
} }
return new State(panelConfigs, true); return new State(panelConfigs, true);
} }
/**
* Migrates JSON config data storage.
*
* @param context Context used to get shared preferences and create built-in panel.
* @param jsonString String currently stored in preferences.
*
* @return JSONArray array representing new set of panel configs.
*/
private static synchronized JSONArray maybePerformMigration(Context context, String jsonString) throws JSONException {
// If the migration is already done, we're at the current version.
if (sMigrationDone) {
final JSONObject json = new JSONObject(jsonString);
return json.getJSONArray(JSON_KEY_PANELS);
}
// Make sure we only do this version check once.
sMigrationDone = true;
final JSONArray originalJsonPanels;
final int version;
final SharedPreferences prefs = GeckoSharedPrefs.forProfile(context);
if (prefs.contains(PREFS_CONFIG_KEY_OLD)) {
// Our original implementation did not contain versioning, so this is implicitly version 0.
originalJsonPanels = new JSONArray(jsonString);
version = 0;
} else {
final JSONObject json = new JSONObject(jsonString);
originalJsonPanels = json.getJSONArray(JSON_KEY_PANELS);
version = json.getInt(JSON_KEY_VERSION);
}
if (version == VERSION) {
return originalJsonPanels;
}
Log.d(LOGTAG, "Performing migration");
final JSONArray newJsonPanels = new JSONArray();
final SharedPreferences.Editor prefsEditor = prefs.edit();
for (int v = version + 1; v <= VERSION; v++) {
Log.d(LOGTAG, "Migrating to version = " + v);
switch (v) {
case 1:
// Add "Recent Tabs" panel
final PanelConfig recentTabsConfig = createBuiltinPanelConfig(context, PanelType.RECENT_TABS);
final JSONObject jsonRecentTabsConfig = recentTabsConfig.toJSON();
// Add the new panel to the front of the array on phones.
if (!HardwareUtils.isTablet()) {
newJsonPanels.put(jsonRecentTabsConfig);
}
// Copy the original panel configs.
final int count = originalJsonPanels.length();
for (int i = 0; i < count; i++) {
final JSONObject jsonPanelConfig = originalJsonPanels.getJSONObject(i);
newJsonPanels.put(jsonPanelConfig);
}
// Add the new panel to the end of the array on tablets.
if (HardwareUtils.isTablet()) {
newJsonPanels.put(jsonRecentTabsConfig);
}
// Remove the old pref key.
prefsEditor.remove(PREFS_CONFIG_KEY_OLD);
break;
}
}
// Save the new panel config and the new version number.
final JSONObject newJson = new JSONObject();
newJson.put(JSON_KEY_PANELS, newJsonPanels);
newJson.put(JSON_KEY_VERSION, VERSION);
prefsEditor.putString(PREFS_CONFIG_KEY, newJson.toString());
prefsEditor.commit();
return newJsonPanels;
}
private State loadConfigFromString(String jsonString) { private State loadConfigFromString(String jsonString) {
final JSONArray jsonPanelConfigs; final JSONArray jsonPanelConfigs;
try { try {
jsonPanelConfigs = new JSONArray(jsonString); jsonPanelConfigs = maybePerformMigration(mContext, jsonString);
} catch (JSONException e) { } catch (JSONException e) {
Log.e(LOGTAG, "Error loading the list of home panels from JSON prefs", e); Log.e(LOGTAG, "Error loading the list of home panels from JSON prefs", e);
@ -110,7 +211,9 @@ class HomeConfigPrefsBackend implements HomeConfigBackend {
@Override @Override
public State load() { public State load() {
final SharedPreferences prefs = getSharedPreferences(); final SharedPreferences prefs = getSharedPreferences();
final String jsonString = prefs.getString(PREFS_CONFIG_KEY, null);
final String key = (prefs.contains(PREFS_CONFIG_KEY_OLD) ? PREFS_CONFIG_KEY_OLD : PREFS_CONFIG_KEY);
final String jsonString = prefs.getString(key, null);
final State configState; final State configState;
if (TextUtils.isEmpty(jsonString)) { if (TextUtils.isEmpty(jsonString)) {
@ -142,7 +245,15 @@ class HomeConfigPrefsBackend implements HomeConfigBackend {
} }
} }
editor.putString(PREFS_CONFIG_KEY, jsonPanelConfigs.toString()); try {
final JSONObject json = new JSONObject();
json.put(JSON_KEY_PANELS, jsonPanelConfigs);
json.put(JSON_KEY_VERSION, VERSION);
editor.putString(PREFS_CONFIG_KEY, json.toString());
} catch (JSONException e) {
Log.e(LOGTAG, "Exception saving PanelConfig state", e);
}
} }
editor.putString(PREFS_LOCALE_KEY, Locale.getDefault().toString()); editor.putString(PREFS_LOCALE_KEY, Locale.getDefault().toString());

Просмотреть файл

@ -68,8 +68,7 @@ public class HomePager extends ViewPager {
static final String LIST_TAG_BOOKMARKS = "bookmarks"; static final String LIST_TAG_BOOKMARKS = "bookmarks";
static final String LIST_TAG_READING_LIST = "reading_list"; static final String LIST_TAG_READING_LIST = "reading_list";
static final String LIST_TAG_TOP_SITES = "top_sites"; static final String LIST_TAG_TOP_SITES = "top_sites";
static final String LIST_TAG_MOST_RECENT = "most_recent"; static final String LIST_TAG_RECENT_TABS = "recent_tabs";
static final String LIST_TAG_LAST_TABS = "last_tabs";
static final String LIST_TAG_BROWSER_SEARCH = "browser_search"; static final String LIST_TAG_BROWSER_SEARCH = "browser_search";
public interface OnUrlOpenListener { public interface OnUrlOpenListener {

Просмотреть файл

@ -1,430 +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.home;
import java.util.Date;
import java.util.EnumSet;
import org.mozilla.gecko.R;
import org.mozilla.gecko.Telemetry;
import org.mozilla.gecko.TelemetryContract;
import org.mozilla.gecko.db.BrowserContract.Combined;
import org.mozilla.gecko.db.BrowserDB;
import org.mozilla.gecko.db.BrowserDB.URLColumns;
import org.mozilla.gecko.home.HomePager.OnUrlOpenListener;
import org.mozilla.gecko.util.ThreadUtils;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.ContentResolver;
import android.content.Context;
import android.content.DialogInterface;
import android.database.Cursor;
import android.os.Bundle;
import android.support.v4.app.LoaderManager.LoaderCallbacks;
import android.support.v4.content.Loader;
import android.util.SparseArray;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewStub;
import android.widget.AdapterView;
import android.widget.ImageView;
import android.widget.TextView;
/**
* Fragment that displays recent history in a ListView.
*/
public class MostRecentPanel extends HomeFragment {
// Logging tag name
private static final String LOGTAG = "GeckoMostRecentPanel";
// Cursor loader ID for history query
private static final int LOADER_ID_HISTORY = 0;
// Adapter for the list of search results
private MostRecentAdapter mAdapter;
// The view shown by the fragment.
private HomeListView mList;
// The button view for clearing browsing history.
private View mClearHistoryButton;
// Reference to the View to display when there are no results.
private View mEmptyView;
// Callbacks used for the search and favicon cursor loaders
private CursorLoaderCallbacks mCursorLoaderCallbacks;
// On URL open listener
private OnUrlOpenListener mUrlOpenListener;
public static MostRecentPanel newInstance() {
return new MostRecentPanel();
}
public MostRecentPanel() {
mUrlOpenListener = null;
}
@Override
public void onAttach(Activity activity) {
super.onAttach(activity);
try {
mUrlOpenListener = (OnUrlOpenListener) activity;
} catch (ClassCastException e) {
throw new ClassCastException(activity.toString()
+ " must implement HomePager.OnUrlOpenListener");
}
}
@Override
public void onDetach() {
super.onDetach();
mUrlOpenListener = null;
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
return inflater.inflate(R.layout.home_most_recent_panel, container, false);
}
@Override
public void onViewCreated(View view, Bundle savedInstanceState) {
mList = (HomeListView) view.findViewById(R.id.list);
mList.setTag(HomePager.LIST_TAG_MOST_RECENT);
mList.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
position -= mAdapter.getMostRecentSectionsCountBefore(position);
final Cursor c = mAdapter.getCursor(position);
final String url = c.getString(c.getColumnIndexOrThrow(URLColumns.URL));
Telemetry.sendUIEvent(TelemetryContract.Event.LOAD_URL);
// This item is a TwoLinePageRow, so we allow switch-to-tab.
mUrlOpenListener.onUrlOpen(url, EnumSet.of(OnUrlOpenListener.Flags.ALLOW_SWITCH_TO_TAB));
}
});
mList.setContextMenuInfoFactory(new HomeContextMenuInfo.Factory() {
@Override
public HomeContextMenuInfo makeInfoForCursor(View view, int position, long id, Cursor cursor) {
final HomeContextMenuInfo info = new HomeContextMenuInfo(view, position, id);
info.url = cursor.getString(cursor.getColumnIndexOrThrow(Combined.URL));
info.title = cursor.getString(cursor.getColumnIndexOrThrow(Combined.TITLE));
info.display = cursor.getInt(cursor.getColumnIndexOrThrow(Combined.DISPLAY));
info.historyId = cursor.getInt(cursor.getColumnIndexOrThrow(Combined.HISTORY_ID));
final int bookmarkIdCol = cursor.getColumnIndexOrThrow(Combined.BOOKMARK_ID);
if (cursor.isNull(bookmarkIdCol)) {
// If this is a combined cursor, we may get a history item without a
// bookmark, in which case the bookmarks ID column value will be null.
info.bookmarkId = -1;
} else {
info.bookmarkId = cursor.getInt(bookmarkIdCol);
}
return info;
}
});
registerForContextMenu(mList);
mClearHistoryButton = view.findViewById(R.id.clear_history_button);
mClearHistoryButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
final Context context = getActivity();
final AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(context);
dialogBuilder.setMessage(R.string.home_clear_history_confirm);
dialogBuilder.setNegativeButton(R.string.button_cancel, new AlertDialog.OnClickListener() {
@Override
public void onClick(final DialogInterface dialog, final int which) {
dialog.dismiss();
}
});
dialogBuilder.setPositiveButton(R.string.button_ok, new AlertDialog.OnClickListener() {
@Override
public void onClick(final DialogInterface dialog, final int which) {
dialog.dismiss();
ThreadUtils.postToBackgroundThread(new Runnable() {
@Override
public void run() {
final ContentResolver cr = context.getContentResolver();
BrowserDB.clearHistory(cr);
}
});
Telemetry.sendUIEvent(TelemetryContract.Event.SANITIZE, TelemetryContract.Method.BUTTON, "history");
}
});
dialogBuilder.show();
}
});
}
@Override
public void onDestroyView() {
super.onDestroyView();
mList = null;
mEmptyView = null;
mClearHistoryButton = null;
}
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
// Intialize adapter
mAdapter = new MostRecentAdapter(getActivity());
mList.setAdapter(mAdapter);
// Create callbacks before the initial loader is started
mCursorLoaderCallbacks = new CursorLoaderCallbacks();
loadIfVisible();
}
@Override
protected void load() {
getLoaderManager().initLoader(LOADER_ID_HISTORY, null, mCursorLoaderCallbacks);
}
private static class MostRecentCursorLoader extends SimpleCursorLoader {
// Max number of history results
private static final int HISTORY_LIMIT = 100;
public MostRecentCursorLoader(Context context) {
super(context);
}
@Override
public Cursor loadCursor() {
final ContentResolver cr = getContext().getContentResolver();
return BrowserDB.getRecentHistory(cr, HISTORY_LIMIT);
}
}
private void updateUiFromCursor(Cursor c) {
if (c != null && c.getCount() > 0) {
mClearHistoryButton.setVisibility(View.VISIBLE);
return;
}
// Cursor is empty, so hide the "Clear browsing history" button,
// and set the empty view if it hasn't been set already.
mClearHistoryButton.setVisibility(View.GONE);
if (mEmptyView == null) {
// Set empty panel view. We delay this so that the empty view won't flash.
final ViewStub emptyViewStub = (ViewStub) getView().findViewById(R.id.home_empty_view_stub);
mEmptyView = emptyViewStub.inflate();
final ImageView emptyIcon = (ImageView) mEmptyView.findViewById(R.id.home_empty_image);
emptyIcon.setImageResource(R.drawable.icon_most_recent_empty);
final TextView emptyText = (TextView) mEmptyView.findViewById(R.id.home_empty_text);
emptyText.setText(R.string.home_most_recent_empty);
mList.setEmptyView(mEmptyView);
}
}
private static class MostRecentAdapter extends MultiTypeCursorAdapter {
private static final int ROW_HEADER = 0;
private static final int ROW_STANDARD = 1;
private static final int[] VIEW_TYPES = new int[] { ROW_STANDARD, ROW_HEADER };
private static final int[] LAYOUT_TYPES = new int[] { R.layout.home_item_row, R.layout.home_header_row };
// For the time sections in history
private static final long MS_PER_DAY = 86400000;
private static final long MS_PER_WEEK = MS_PER_DAY * 7;
// The time ranges for each section
private static enum MostRecentSection {
TODAY,
YESTERDAY,
WEEK,
OLDER
};
private final Context mContext;
// Maps headers in the list with their respective sections
private final SparseArray<MostRecentSection> mMostRecentSections;
public MostRecentAdapter(Context context) {
super(context, null, VIEW_TYPES, LAYOUT_TYPES);
mContext = context;
// Initialize map of history sections
mMostRecentSections = new SparseArray<MostRecentSection>();
}
@Override
public Object getItem(int position) {
final int type = getItemViewType(position);
// Header items are not in the cursor
if (type == ROW_HEADER) {
return null;
}
return super.getItem(position - getMostRecentSectionsCountBefore(position));
}
@Override
public int getItemViewType(int position) {
if (mMostRecentSections.get(position) != null) {
return ROW_HEADER;
}
return ROW_STANDARD;
}
@Override
public boolean isEnabled(int position) {
return (getItemViewType(position) == ROW_STANDARD);
}
@Override
public int getCount() {
// Add the history section headers to the number of reported results.
return super.getCount() + mMostRecentSections.size();
}
@Override
public Cursor swapCursor(Cursor cursor) {
loadMostRecentSections(cursor);
Cursor oldCursor = super.swapCursor(cursor);
return oldCursor;
}
@Override
public void bindView(View view, Context context, int position) {
final int type = getItemViewType(position);
if (type == ROW_HEADER) {
final MostRecentSection section = mMostRecentSections.get(position);
final TextView row = (TextView) view;
row.setText(getMostRecentSectionTitle(section));
} else {
// Account for the most recent section headers
position -= getMostRecentSectionsCountBefore(position);
final Cursor c = getCursor(position);
final TwoLinePageRow row = (TwoLinePageRow) view;
row.updateFromCursor(c);
}
}
private String getMostRecentSectionTitle(MostRecentSection section) {
switch (section) {
case TODAY:
return mContext.getString(R.string.history_today_section);
case YESTERDAY:
return mContext.getString(R.string.history_yesterday_section);
case WEEK:
return mContext.getString(R.string.history_week_section);
case OLDER:
return mContext.getString(R.string.history_older_section);
}
throw new IllegalStateException("Unrecognized history section");
}
private int getMostRecentSectionsCountBefore(int position) {
// Account for the number headers before the given position
int sectionsBefore = 0;
final int historySectionsCount = mMostRecentSections.size();
for (int i = 0; i < historySectionsCount; i++) {
final int sectionPosition = mMostRecentSections.keyAt(i);
if (sectionPosition > position) {
break;
}
sectionsBefore++;
}
return sectionsBefore;
}
private static MostRecentSection getMostRecentSectionForTime(long from, long time) {
long delta = from - time;
if (delta < 0) {
return MostRecentSection.TODAY;
}
if (delta < MS_PER_DAY) {
return MostRecentSection.YESTERDAY;
}
if (delta < MS_PER_WEEK) {
return MostRecentSection.WEEK;
}
return MostRecentSection.OLDER;
}
private void loadMostRecentSections(Cursor c) {
// Clear any history sections that may have been loaded before.
mMostRecentSections.clear();
if (c == null || !c.moveToFirst()) {
return;
}
final Date now = new Date();
now.setHours(0);
now.setMinutes(0);
now.setSeconds(0);
final long today = now.getTime();
MostRecentSection section = null;
do {
final int position = c.getPosition();
final long time = c.getLong(c.getColumnIndexOrThrow(URLColumns.DATE_LAST_VISITED));
final MostRecentSection itemSection = MostRecentAdapter.getMostRecentSectionForTime(today, time);
if (section != itemSection) {
section = itemSection;
mMostRecentSections.append(position + mMostRecentSections.size(), section);
}
// Reached the last section, no need to continue
if (section == MostRecentSection.OLDER) {
break;
}
} while (c.moveToNext());
}
}
private class CursorLoaderCallbacks implements LoaderCallbacks<Cursor> {
@Override
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
return new MostRecentCursorLoader(getActivity());
}
@Override
public void onLoadFinished(Loader<Cursor> loader, Cursor c) {
mAdapter.swapCursor(c);
updateUiFromCursor(c);
}
@Override
public void onLoaderReset(Loader<Cursor> loader) {
mAdapter.swapCursor(null);
}
}
}

Просмотреть файл

@ -6,16 +6,25 @@
package org.mozilla.gecko.home; package org.mozilla.gecko.home;
import org.mozilla.gecko.AboutPages; import org.mozilla.gecko.AboutPages;
import org.mozilla.gecko.EventDispatcher;
import org.mozilla.gecko.GeckoAppShell;
import org.mozilla.gecko.GeckoEvent;
import org.mozilla.gecko.GeckoProfile; import org.mozilla.gecko.GeckoProfile;
import org.mozilla.gecko.R; import org.mozilla.gecko.R;
import org.mozilla.gecko.SessionParser; import org.mozilla.gecko.SessionParser;
import org.mozilla.gecko.Telemetry; import org.mozilla.gecko.Telemetry;
import org.mozilla.gecko.TelemetryContract; import org.mozilla.gecko.TelemetryContract;
import org.mozilla.gecko.db.BrowserContract.Combined; import org.mozilla.gecko.db.BrowserContract.CommonColumns;
import org.mozilla.gecko.db.BrowserContract.URLColumns;
import org.mozilla.gecko.home.HomePager.OnNewTabsListener; import org.mozilla.gecko.home.HomePager.OnNewTabsListener;
import org.mozilla.gecko.util.EventCallback;
import org.mozilla.gecko.util.NativeEventListener;
import org.mozilla.gecko.util.NativeJSObject;
import org.mozilla.gecko.util.ThreadUtils;
import android.app.Activity; import android.app.Activity;
import android.content.Context; import android.content.Context;
import android.content.res.Configuration;
import android.database.Cursor; import android.database.Cursor;
import android.database.MatrixCursor; import android.database.MatrixCursor;
import android.database.MatrixCursor.RowBuilder; import android.database.MatrixCursor.RowBuilder;
@ -23,6 +32,7 @@ import android.os.Bundle;
import android.support.v4.app.LoaderManager.LoaderCallbacks; import android.support.v4.app.LoaderManager.LoaderCallbacks;
import android.support.v4.content.Loader; import android.support.v4.content.Loader;
import android.support.v4.widget.CursorAdapter; import android.support.v4.widget.CursorAdapter;
import android.util.Log;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
@ -34,25 +44,20 @@ import android.widget.TextView;
/** /**
* Fragment that displays tabs from last session in a ListView. * Fragment that displays tabs from last session in a ListView.
*/ */
public class LastTabsPanel extends HomeFragment { public class RecentTabsPanel extends HomeFragment
implements NativeEventListener {
// Logging tag name // Logging tag name
private static final String LOGTAG = "GeckoLastTabsPanel"; private static final String LOGTAG = "GeckoRecentTabsPanel";
// Cursor loader ID for the session parser // Cursor loader ID for the loader that loads recent tabs
private static final int LOADER_ID_LAST_TABS = 0; private static final int LOADER_ID_RECENT_TABS = 0;
// Adapter for the list of search results // Adapter for the list of recent tabs.
private LastTabsAdapter mAdapter; private RecentTabsAdapter mAdapter;
// The view shown by the fragment. // The view shown by the fragment.
private HomeListView mList; private HomeListView mList;
// The title for this HomeFragment panel.
private TextView mTitle;
// The button view for restoring tabs from last session.
private View mRestoreButton;
// Reference to the View to display when there are no results. // Reference to the View to display when there are no results.
private View mEmptyView; private View mEmptyView;
@ -62,12 +67,25 @@ public class LastTabsPanel extends HomeFragment {
// On new tabs listener // On new tabs listener
private OnNewTabsListener mNewTabsListener; private OnNewTabsListener mNewTabsListener;
public static LastTabsPanel newInstance() { // Recently closed tabs from gecko
return new LastTabsPanel(); private ClosedTab[] mClosedTabs;
private static final class ClosedTab {
public final String url;
public final String title;
public ClosedTab(String url, String title) {
this.url = url;
this.title = title;
}
} }
public LastTabsPanel() { public static final class RecentTabs implements URLColumns, CommonColumns {
mNewTabsListener = null; public static final String TYPE = "type";
public static final int TYPE_HEADER = 0;
public static final int TYPE_LAST_TIME = 1;
public static final int TYPE_CLOSED = 2;
} }
@Override @Override
@ -91,18 +109,13 @@ public class LastTabsPanel extends HomeFragment {
@Override @Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
return inflater.inflate(R.layout.home_last_tabs_panel, container, false); return inflater.inflate(R.layout.home_recent_tabs_panel, container, false);
} }
@Override @Override
public void onViewCreated(View view, Bundle savedInstanceState) { public void onViewCreated(View view, Bundle savedInstanceState) {
mTitle = (TextView) view.findViewById(R.id.title);
if (mTitle != null) {
mTitle.setText(R.string.home_last_tabs_title);
}
mList = (HomeListView) view.findViewById(R.id.list); mList = (HomeListView) view.findViewById(R.id.list);
mList.setTag(HomePager.LIST_TAG_LAST_TABS); mList.setTag(HomePager.LIST_TAG_RECENT_TABS);
mList.setOnItemClickListener(new AdapterView.OnItemClickListener() { mList.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override @Override
@ -114,7 +127,7 @@ public class LastTabsPanel extends HomeFragment {
Telemetry.sendUIEvent(TelemetryContract.Event.LOAD_URL); Telemetry.sendUIEvent(TelemetryContract.Event.LOAD_URL);
final String url = c.getString(c.getColumnIndexOrThrow(Combined.URL)); final String url = c.getString(c.getColumnIndexOrThrow(RecentTabs.URL));
mNewTabsListener.onNewTabs(new String[] { url }); mNewTabsListener.onNewTabs(new String[] { url });
} }
}); });
@ -123,30 +136,39 @@ public class LastTabsPanel extends HomeFragment {
@Override @Override
public HomeContextMenuInfo makeInfoForCursor(View view, int position, long id, Cursor cursor) { public HomeContextMenuInfo makeInfoForCursor(View view, int position, long id, Cursor cursor) {
final HomeContextMenuInfo info = new HomeContextMenuInfo(view, position, id); final HomeContextMenuInfo info = new HomeContextMenuInfo(view, position, id);
info.url = cursor.getString(cursor.getColumnIndexOrThrow(Combined.URL)); info.url = cursor.getString(cursor.getColumnIndexOrThrow(RecentTabs.URL));
info.title = cursor.getString(cursor.getColumnIndexOrThrow(Combined.TITLE)); info.title = cursor.getString(cursor.getColumnIndexOrThrow(RecentTabs.TITLE));
return info; return info;
} }
}); });
registerForContextMenu(mList); registerForContextMenu(mList);
mRestoreButton = view.findViewById(R.id.open_all_tabs_button); EventDispatcher.getInstance().registerGeckoThreadListener(this, "ClosedTabs:Data");
mRestoreButton.setOnClickListener(new View.OnClickListener() { GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("ClosedTabs:StartNotifications", null));
@Override
public void onClick(View v) {
openAllTabs();
}
});
} }
@Override @Override
public void onDestroyView() { public void onDestroyView() {
super.onDestroyView(); super.onDestroyView();
mList = null; mList = null;
mTitle = null;
mEmptyView = null; mEmptyView = null;
mRestoreButton = null;
EventDispatcher.getInstance().unregisterGeckoThreadListener(this, "ClosedTabs:Data");
GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("ClosedTabs:StopNotifications", null));
}
@Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
// Detach and reattach the fragment as the layout changes.
if (isVisible()) {
getFragmentManager().beginTransaction()
.detach(this)
.attach(this)
.commitAllowingStateLoss();
}
} }
@Override @Override
@ -154,7 +176,7 @@ public class LastTabsPanel extends HomeFragment {
super.onActivityCreated(savedInstanceState); super.onActivityCreated(savedInstanceState);
// Intialize adapter // Intialize adapter
mAdapter = new LastTabsAdapter(getActivity()); mAdapter = new RecentTabsAdapter(getActivity());
mList.setAdapter(mAdapter); mList.setAdapter(mAdapter);
// Create callbacks before the initial loader is started // Create callbacks before the initial loader is started
@ -164,20 +186,9 @@ public class LastTabsPanel extends HomeFragment {
private void updateUiFromCursor(Cursor c) { private void updateUiFromCursor(Cursor c) {
if (c != null && c.getCount() > 0) { if (c != null && c.getCount() > 0) {
if (mTitle != null) {
mTitle.setVisibility(View.VISIBLE);
}
mRestoreButton.setVisibility(View.VISIBLE);
return; return;
} }
// Cursor is empty, so hide the title and set the
// empty view if it hasn't been set already.
if (mTitle != null) {
mTitle.setVisibility(View.GONE);
}
mRestoreButton.setVisibility(View.GONE);
if (mEmptyView == null) { if (mEmptyView == null) {
// Set empty panel view. We delay this so that the empty view won't flash. // Set empty panel view. We delay this so that the empty view won't flash.
final ViewStub emptyViewStub = (ViewStub) getView().findViewById(R.id.home_empty_view_stub); final ViewStub emptyViewStub = (ViewStub) getView().findViewById(R.id.home_empty_view_stub);
@ -195,7 +206,32 @@ public class LastTabsPanel extends HomeFragment {
@Override @Override
protected void load() { protected void load() {
getLoaderManager().initLoader(LOADER_ID_LAST_TABS, null, mCursorLoaderCallbacks); getLoaderManager().initLoader(LOADER_ID_RECENT_TABS, null, mCursorLoaderCallbacks);
}
@Override
public void handleMessage(String event, NativeJSObject message, EventCallback callback) {
final NativeJSObject[] tabs = message.getObjectArray("tabs");
final int length = tabs.length;
final ClosedTab[] closedTabs = new ClosedTab[length];
for (int i = 0; i < length; i++) {
final NativeJSObject tab = tabs[i];
closedTabs[i] = new ClosedTab(tab.getString("url"), tab.getString("title"));
}
// Only modify mClosedTabs on the UI thread
ThreadUtils.postToUiThread(new Runnable() {
@Override
public void run() {
mClosedTabs = closedTabs;
// Reload the cursor to show recently closed tabs.
if (mClosedTabs.length > 0 && canLoad()) {
getLoaderManager().restartLoader(LOADER_ID_RECENT_TABS, null, mCursorLoaderCallbacks);
}
}
});
} }
private void openAllTabs() { private void openAllTabs() {
@ -207,7 +243,7 @@ public class LastTabsPanel extends HomeFragment {
final String[] urls = new String[c.getCount()]; final String[] urls = new String[c.getCount()];
do { do {
urls[c.getPosition()] = c.getString(c.getColumnIndexOrThrow(Combined.URL)); urls[c.getPosition()] = c.getString(c.getColumnIndexOrThrow(RecentTabs.URL));
} while (c.moveToNext()); } while (c.moveToNext());
Telemetry.sendUIEvent(TelemetryContract.Event.LOAD_URL, TelemetryContract.Method.BUTTON); Telemetry.sendUIEvent(TelemetryContract.Event.LOAD_URL, TelemetryContract.Method.BUTTON);
@ -215,24 +251,52 @@ public class LastTabsPanel extends HomeFragment {
mNewTabsListener.onNewTabs(urls); mNewTabsListener.onNewTabs(urls);
} }
private static class LastTabsCursorLoader extends SimpleCursorLoader { private static class RecentTabsCursorLoader extends SimpleCursorLoader {
public LastTabsCursorLoader(Context context) { private final ClosedTab[] closedTabs;
public RecentTabsCursorLoader(Context context, ClosedTab[] closedTabs) {
super(context); super(context);
this.closedTabs = closedTabs;
}
private void addRow(MatrixCursor c, String url, String title, int type) {
final RowBuilder row = c.newRow();
row.add(-1);
row.add(url);
row.add(title);
row.add(type);
} }
@Override @Override
public Cursor loadCursor() { public Cursor loadCursor() {
final Context context = getContext(); final Context context = getContext();
final MatrixCursor c = new MatrixCursor(new String[] { RecentTabs._ID,
RecentTabs.URL,
RecentTabs.TITLE,
RecentTabs.TYPE });
if (closedTabs != null && closedTabs.length > 0) {
addRow(c, null, context.getString(R.string.home_closed_tabs_title), RecentTabs.TYPE_HEADER);
final int length = closedTabs.length;
for (int i = 0; i < length; i++) {
final String url = closedTabs[i].url;
// Don't show recent tabs for about:home
if (!AboutPages.isAboutHome(url)) {
addRow(c, url, closedTabs[i].title, RecentTabs.TYPE_CLOSED);
}
}
}
final String jsonString = GeckoProfile.get(context).readSessionFile(true); final String jsonString = GeckoProfile.get(context).readSessionFile(true);
if (jsonString == null) { if (jsonString == null) {
// No previous session data // No previous session data
return null; return c;
} }
final MatrixCursor c = new MatrixCursor(new String[] { Combined._ID, final int count = c.getCount();
Combined.URL,
Combined.TITLE });
new SessionParser() { new SessionParser() {
@Override @Override
@ -244,12 +308,12 @@ public class LastTabsPanel extends HomeFragment {
return; return;
} }
final RowBuilder row = c.newRow(); // If this is the first tab we're reading, add a header.
row.add(-1); if (c.getCount() == count) {
row.add(url); addRow(c, null, context.getString(R.string.home_last_tabs_title), RecentTabs.TYPE_HEADER);
}
final String title = tab.getTitle(); addRow(c, url, tab.getTitle(), RecentTabs.TYPE_LAST_TIME);
row.add(title);
} }
}.parse(jsonString); }.parse(jsonString);
@ -257,26 +321,52 @@ public class LastTabsPanel extends HomeFragment {
} }
} }
private static class LastTabsAdapter extends CursorAdapter { private static class RecentTabsAdapter extends MultiTypeCursorAdapter {
public LastTabsAdapter(Context context) { private static final int ROW_HEADER = 0;
super(context, null, 0); private static final int ROW_STANDARD = 1;
private static final int[] VIEW_TYPES = new int[] { ROW_STANDARD, ROW_HEADER };
private static final int[] LAYOUT_TYPES = new int[] { R.layout.home_item_row, R.layout.home_header_row };
public RecentTabsAdapter(Context context) {
super(context, null, VIEW_TYPES, LAYOUT_TYPES);
}
public int getItemViewType(int position) {
final Cursor c = getCursor(position);
final int type = c.getInt(c.getColumnIndexOrThrow(RecentTabs.TYPE));
if (type == RecentTabs.TYPE_HEADER) {
return ROW_HEADER;
}
return ROW_STANDARD;
}
public boolean isEnabled(int position) {
return (getItemViewType(position) != ROW_HEADER);
} }
@Override @Override
public void bindView(View view, Context context, Cursor cursor) { public void bindView(View view, Context context, int position) {
((TwoLinePageRow) view).updateFromCursor(cursor); final int itemType = getItemViewType(position);
} final Cursor c = getCursor(position);
@Override if (itemType == ROW_HEADER) {
public View newView(Context context, Cursor cursor, ViewGroup parent) { final String title = c.getString(c.getColumnIndexOrThrow(RecentTabs.TITLE));
return LayoutInflater.from(context).inflate(R.layout.home_item_row, parent, false); final TextView textView = (TextView) view;
} textView.setText(title);
} else if (itemType == ROW_STANDARD) {
final TwoLinePageRow pageRow = (TwoLinePageRow) view;
pageRow.updateFromCursor(c);
}
}
} }
private class CursorLoaderCallbacks implements LoaderCallbacks<Cursor> { private class CursorLoaderCallbacks implements LoaderCallbacks<Cursor> {
@Override @Override
public Loader<Cursor> onCreateLoader(int id, Bundle args) { public Loader<Cursor> onCreateLoader(int id, Bundle args) {
return new LastTabsCursorLoader(getActivity()); return new RecentTabsCursorLoader(getActivity(), mClosedTabs);
} }
@Override @Override

Просмотреть файл

@ -11,6 +11,7 @@
<!ENTITY bookmarks_title "Bookmarks"> <!ENTITY bookmarks_title "Bookmarks">
<!ENTITY history_title "History"> <!ENTITY history_title "History">
<!ENTITY reading_list_title "Reading List"> <!ENTITY reading_list_title "Reading List">
<!ENTITY recent_tabs_title "Recent Tabs">
<!ENTITY switch_to_tab "Switch to tab"> <!ENTITY switch_to_tab "Switch to tab">
@ -349,10 +350,10 @@ size. -->
<!ENTITY home_clear_history_button "Clear browsing history"> <!ENTITY home_clear_history_button "Clear browsing history">
<!ENTITY home_clear_history_confirm "Are you sure you want to clear your history?"> <!ENTITY home_clear_history_confirm "Are you sure you want to clear your history?">
<!ENTITY home_bookmarks_empty "Bookmarks you save show up here."> <!ENTITY home_bookmarks_empty "Bookmarks you save show up here.">
<!ENTITY home_closed_tabs_title "Recently closed tabs">
<!ENTITY home_last_tabs_title "Tabs from last time"> <!ENTITY home_last_tabs_title "Tabs from last time">
<!ENTITY home_last_tabs_open "Open all tabs from last time"> <!ENTITY home_last_tabs_open "Open all tabs from last time">
<!ENTITY home_last_tabs_empty "Your recent tabs show up here."> <!ENTITY home_last_tabs_empty "Your recent tabs show up here.">
<!ENTITY home_most_recent_title "Most recent">
<!ENTITY home_most_recent_empty "Websites you visited most recently show up here."> <!ENTITY home_most_recent_empty "Websites you visited most recently show up here.">
<!ENTITY home_reading_list_empty "Articles you save for later show up here."> <!ENTITY home_reading_list_empty "Articles you save for later show up here.">
<!-- Localization note (home_reading_list_hint): The "TIP" string is synonymous to "hint", "clue", etc. This string is displayed <!-- Localization note (home_reading_list_hint): The "TIP" string is synonymous to "hint", "clue", etc. This string is displayed

Просмотреть файл

@ -109,6 +109,9 @@
<!ENTITY fxaccount_full_label 'Firefox Accounts'> <!ENTITY fxaccount_full_label 'Firefox Accounts'>
<!ENTITY fxaccount_custom_server_account_title 'Using account on server'>
<!ENTITY fxaccount_custom_server_sync_title 'Storing Sync data on server'>
<!-- Localization note: these are shown in all screens that query the <!-- Localization note: these are shown in all screens that query the
user for an email address and password. Hide and show are button user for an email address and password. Hide and show are button
labels. --> labels. -->
@ -175,7 +178,9 @@
<!ENTITY fxaccount_status_header2 'Firefox Account'> <!ENTITY fxaccount_status_header2 'Firefox Account'>
<!ENTITY fxaccount_status_signed_in_as 'Signed in as'> <!ENTITY fxaccount_status_signed_in_as 'Signed in as'>
<!ENTITY fxaccount_status_auth_server 'Account server'>
<!ENTITY fxaccount_status_device_name 'Device name'> <!ENTITY fxaccount_status_device_name 'Device name'>
<!ENTITY fxaccount_status_sync_server 'Sync server'>
<!ENTITY fxaccount_status_sync '&syncBrand.shortName.label;'> <!ENTITY fxaccount_status_sync '&syncBrand.shortName.label;'>
<!ENTITY fxaccount_status_sync_enabled '&syncBrand.shortName.label;: enabled'> <!ENTITY fxaccount_status_sync_enabled '&syncBrand.shortName.label;: enabled'>
<!ENTITY fxaccount_status_needs_verification2 'Your account needs to be verified. Tap to resend verification email.'> <!ENTITY fxaccount_status_needs_verification2 'Your account needs to be verified. Tap to resend verification email.'>

Просмотреть файл

@ -272,8 +272,6 @@ gbjar.sources += [
'home/HomePagerTabStrip.java', 'home/HomePagerTabStrip.java',
'home/HomePanelPicker.java', 'home/HomePanelPicker.java',
'home/HomePanelsManager.java', 'home/HomePanelsManager.java',
'home/LastTabsPanel.java',
'home/MostRecentPanel.java',
'home/MultiTypeCursorAdapter.java', 'home/MultiTypeCursorAdapter.java',
'home/PanelAuthCache.java', 'home/PanelAuthCache.java',
'home/PanelAuthLayout.java', 'home/PanelAuthLayout.java',
@ -289,6 +287,7 @@ gbjar.sources += [
'home/PinSiteDialog.java', 'home/PinSiteDialog.java',
'home/ReadingListPanel.java', 'home/ReadingListPanel.java',
'home/ReadingListRow.java', 'home/ReadingListRow.java',
'home/RecentTabsPanel.java',
'home/SearchEngine.java', 'home/SearchEngine.java',
'home/SearchEngineRow.java', 'home/SearchEngineRow.java',
'home/SearchLoader.java', 'home/SearchLoader.java',

Просмотреть файл

@ -4,15 +4,21 @@
package org.mozilla.gecko.preferences; package org.mozilla.gecko.preferences;
import java.nio.ByteBuffer;
import java.text.Collator; import java.text.Collator;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import java.util.HashSet;
import java.util.Locale; import java.util.Locale;
import java.util.Set;
import org.mozilla.gecko.BrowserLocaleManager; import org.mozilla.gecko.BrowserLocaleManager;
import org.mozilla.gecko.R; import org.mozilla.gecko.R;
import android.content.Context; import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.preference.ListPreference; import android.preference.ListPreference;
import android.text.TextUtils; import android.text.TextUtils;
import android.util.AttributeSet; import android.util.AttributeSet;
@ -21,7 +27,51 @@ import android.util.Log;
public class LocaleListPreference extends ListPreference { public class LocaleListPreference extends ListPreference {
private static final String LOG_TAG = "GeckoLocaleList"; private static final String LOG_TAG = "GeckoLocaleList";
/**
* With thanks to <http://stackoverflow.com/a/22679283/22003> for the
* initial solution.
*
* This class encapsulates an approach to checking whether a script
* is usable on a device. We attempt to draw a character from the
* script (e.g., ). If the fonts on the device don't have the correct
* glyph, Android typically renders whitespace (rather than .notdef).
*
* Pass in part of the name of the locale in its local representation,
* and a whitespace character; this class performs the graphical comparison.
*
* See Bug 1023451 Comment 24 for extensive explanation.
*/
private static class CharacterValidator {
private static final int BITMAP_WIDTH = 32;
private static final int BITMAP_HEIGHT = 48;
private final Paint paint = new Paint();
private final byte[] missingCharacter;
public CharacterValidator(String missing) {
this.missingCharacter = getPixels(drawBitmap(missing));
}
private Bitmap drawBitmap(String text){
Bitmap b = Bitmap.createBitmap(BITMAP_WIDTH, BITMAP_HEIGHT, Bitmap.Config.ALPHA_8);
Canvas c = new Canvas(b);
c.drawText(text, 0, BITMAP_HEIGHT / 2, this.paint);
return b;
}
private static byte[] getPixels(Bitmap b) {
ByteBuffer buffer = ByteBuffer.allocate(b.getByteCount());
b.copyPixelsToBuffer(buffer);
return buffer.array();
}
public boolean characterIsMissingInFont(String ch) {
byte[] rendered = getPixels(drawBitmap(ch));
return Arrays.equals(rendered, missingCharacter);
}
}
private volatile Locale entriesLocale; private volatile Locale entriesLocale;
private final CharacterValidator characterValidator;
public LocaleListPreference(Context context) { public LocaleListPreference(Context context) {
this(context, null); this(context, null);
@ -29,6 +79,10 @@ public class LocaleListPreference extends ListPreference {
public LocaleListPreference(Context context, AttributeSet attributes) { public LocaleListPreference(Context context, AttributeSet attributes) {
super(context, attributes); super(context, attributes);
// Thus far, missing glyphs are replaced by whitespace, not a box
// or other Unicode codepoint.
this.characterValidator = new CharacterValidator(" ");
buildList(); buildList();
} }
@ -86,9 +140,54 @@ public class LocaleListPreference extends ListPreference {
// We sort by name, so we use Collator. // We sort by name, so we use Collator.
return COLLATOR.compare(this.nativeName, another.nativeName); return COLLATOR.compare(this.nativeName, another.nativeName);
} }
/**
* See Bug 1023451 Comment 10 for the research that led to
* this method.
*
* @return true if this locale can be used for displaying UI
* on this device without known issues.
*/
public boolean isUsable(CharacterValidator validator) {
// Oh, for Java 7 switch statements.
if (this.tag.equals("bn-IN")) {
// Bengali sometimes has an English label if the Bengali script
// is missing. This prevents us from simply checking character
// rendering for bn-IN; we'll get a false positive for "B", not "".
//
// This doesn't seem to affect other Bengali-script locales
// (below), which always have a label in native script.
if (!this.nativeName.startsWith("বাংলা")) {
// We're on an Android version that doesn't even have
// characters to say . Definite failure.
return false;
}
}
// These locales use a script that is often unavailable
// on common Android devices. Make sure we can show them.
// See documentation for CharacterValidator.
// Note that bn-IN is checked here even if it passed above.
if (this.tag.equals("or") ||
this.tag.equals("pa-IN") ||
this.tag.equals("gu-IN") ||
this.tag.equals("bn-IN")) {
if (validator.characterIsMissingInFont(this.nativeName.substring(0, 1))) {
return false;
}
}
return true;
}
} }
private LocaleDescriptor[] getShippingLocales() { /**
* Not every locale we ship can be used on every device, due to
* font or rendering constraints.
*
* This method filters down the list before generating the descriptor array.
*/
private LocaleDescriptor[] getUsableLocales() {
Collection<String> shippingLocales = BrowserLocaleManager.getPackagedLocaleTags(getContext()); Collection<String> shippingLocales = BrowserLocaleManager.getPackagedLocaleTags(getContext());
// Future: single-locale builds should be specified, too. // Future: single-locale builds should be specified, too.
@ -97,15 +196,22 @@ public class LocaleListPreference extends ListPreference {
return new LocaleDescriptor[] { new LocaleDescriptor(fallbackTag) }; return new LocaleDescriptor[] { new LocaleDescriptor(fallbackTag) };
} }
final int count = shippingLocales.size(); final int initialCount = shippingLocales.size();
final LocaleDescriptor[] descriptors = new LocaleDescriptor[count]; final Set<LocaleDescriptor> locales = new HashSet<LocaleDescriptor>(initialCount);
int i = 0;
for (String tag : shippingLocales) { for (String tag : shippingLocales) {
descriptors[i++] = new LocaleDescriptor(tag); final LocaleDescriptor descriptor = new LocaleDescriptor(tag);
if (!descriptor.isUsable(this.characterValidator)) {
Log.w(LOG_TAG, "Skipping locale " + tag + " on this device.");
continue;
}
locales.add(descriptor);
} }
Arrays.sort(descriptors, 0, count); final int usableCount = locales.size();
final LocaleDescriptor[] descriptors = locales.toArray(new LocaleDescriptor[usableCount]);
Arrays.sort(descriptors, 0, usableCount);
return descriptors; return descriptors;
} }
@ -154,7 +260,7 @@ public class LocaleListPreference extends ListPreference {
return; return;
} }
final LocaleDescriptor[] descriptors = getShippingLocales(); final LocaleDescriptor[] descriptors = getUsableLocales();
final int count = descriptors.length; final int count = descriptors.length;
this.entriesLocale = currentLocale; this.entriesLocale = currentLocale;

Просмотреть файл

@ -1,48 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- 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/. -->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_focused="false"
android:state_selected="false"
android:state_pressed="false"
android:drawable="@android:color/transparent"/>
<item android:state_focused="false"
android:state_selected="true"
android:state_pressed="false"
android:drawable="@color/background_light"/>
<item android:state_focused="true"
android:state_selected="false"
android:state_pressed="false"
android:drawable="@color/background_normal"/>
<item android:state_focused="true"
android:state_selected="true"
android:state_pressed="false"
android:drawable="@color/background_light"/>
<item android:state_focused="false"
android:state_selected="false"
android:state_pressed="true"
android:drawable="@color/background_normal"/>
<item android:state_focused="false"
android:state_selected="true"
android:state_pressed="true"
android:drawable="@color/background_normal"/>
<item android:state_focused="true"
android:state_selected="false"
android:state_pressed="true"
android:drawable="@color/background_normal"/>
<item android:state_focused="true"
android:state_selected="true"
android:state_pressed="true"
android:drawable="@color/background_normal"/>
</selector>

Просмотреть файл

@ -1,48 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- 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/. -->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_focused="false"
android:state_selected="false"
android:state_pressed="false"
android:drawable="@android:color/transparent"/>
<item android:state_focused="false"
android:state_selected="true"
android:state_pressed="false"
android:drawable="@color/background_light"/>
<item android:state_focused="true"
android:state_selected="false"
android:state_pressed="false"
android:drawable="@color/background_normal"/>
<item android:state_focused="true"
android:state_selected="true"
android:state_pressed="false"
android:drawable="@color/background_light"/>
<item android:state_focused="false"
android:state_selected="false"
android:state_pressed="true"
android:drawable="@color/background_normal"/>
<item android:state_focused="false"
android:state_selected="true"
android:state_pressed="true"
android:drawable="@color/background_normal"/>
<item android:state_focused="true"
android:state_selected="false"
android:state_pressed="true"
android:drawable="@color/background_normal"/>
<item android:state_focused="true"
android:state_selected="true"
android:state_pressed="true"
android:drawable="@color/background_normal"/>
</selector>

Просмотреть файл

@ -1,48 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- 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/. -->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_focused="false"
android:state_selected="false"
android:state_pressed="false"
android:drawable="@android:color/transparent"/>
<item android:state_focused="false"
android:state_selected="true"
android:state_pressed="false"
android:drawable="@drawable/history_tabs_indicator_selected"/>
<item android:state_focused="true"
android:state_selected="false"
android:state_pressed="false"
android:drawable="@color/background_normal"/>
<item android:state_focused="true"
android:state_selected="true"
android:state_pressed="false"
android:drawable="@drawable/history_tabs_indicator_selected"/>
<item android:state_focused="false"
android:state_selected="false"
android:state_pressed="true"
android:drawable="@color/background_normal"/>
<item android:state_focused="false"
android:state_selected="true"
android:state_pressed="true"
android:drawable="@color/background_normal"/>
<item android:state_focused="true"
android:state_selected="false"
android:state_pressed="true"
android:drawable="@color/background_normal"/>
<item android:state_focused="true"
android:state_selected="true"
android:state_pressed="true"
android:drawable="@color/background_normal"/>
</selector>

Просмотреть файл

@ -1,24 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- 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/. -->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:gecko="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<org.mozilla.gecko.widget.IconTabWidget android:id="@+id/tab_icon_widget"
style="@style/Widget.Home.HistoryTabWidget"
android:layout_width="@dimen/history_tab_widget_width"
android:layout_height="@dimen/history_tab_widget_height"
android:orientation="vertical"
android:layout="@layout/home_history_tabs_indicator"
gecko:display="text"/>
<FrameLayout android:id="@+id/history_panel_container"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1" />
</LinearLayout>

Просмотреть файл

@ -1,10 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- 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/. -->
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
style="@style/Widget.Home.HistoryTabIndicator"
android:layout_width="match_parent"
android:layout_height="@dimen/history_tab_indicator_height"
android:background="@drawable/home_history_tabs_indicator"/>

Просмотреть файл

@ -1,24 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- 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/. -->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:gecko="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<org.mozilla.gecko.widget.IconTabWidget android:id="@+id/tab_icon_widget"
style="@style/Widget.Home.HistoryTabWidget"
android:layout_width="@dimen/history_tab_widget_width"
android:layout_height="@dimen/history_tab_widget_height"
android:orientation="vertical"
android:layout="@layout/home_history_tabs_indicator"
gecko:display="text"/>
<FrameLayout android:id="@+id/history_panel_container"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1" />
</LinearLayout>

Просмотреть файл

@ -1,10 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- 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/. -->
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
style="@style/Widget.Home.HistoryTabIndicator"
android:layout_width="match_parent"
android:layout_height="@dimen/history_tab_indicator_height"
android:background="@drawable/home_history_tabs_indicator"/>

Просмотреть файл

@ -5,8 +5,8 @@
- file, You can obtain one at http://mozilla.org/MPL/2.0/. - file, You can obtain one at http://mozilla.org/MPL/2.0/.
--> -->
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android" <ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent" android:layout_width="fill_parent"
android:layout_height="match_parent" android:layout_height="fill_parent"
android:fillViewport="true" > android:fillViewport="true" >
<LinearLayout style="@style/FxAccountMiddle" > <LinearLayout style="@style/FxAccountMiddle" >

Просмотреть файл

@ -5,8 +5,8 @@
- file, You can obtain one at http://mozilla.org/MPL/2.0/. - file, You can obtain one at http://mozilla.org/MPL/2.0/.
--> -->
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android" <ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent" android:layout_width="fill_parent"
android:layout_height="match_parent" android:layout_height="fill_parent"
android:fillViewport="true" > android:fillViewport="true" >
<LinearLayout style="@style/FxAccountMiddle" > <LinearLayout style="@style/FxAccountMiddle" >

Просмотреть файл

@ -6,8 +6,8 @@
--> -->
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android" <ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent" android:layout_width="fill_parent"
android:layout_height="match_parent" android:layout_height="fill_parent"
android:fillViewport="true" > android:fillViewport="true" >
<LinearLayout <LinearLayout
@ -20,6 +20,8 @@
style="@style/FxAccountHeaderItem" style="@style/FxAccountHeaderItem"
android:text="@string/fxaccount_create_account_header" /> android:text="@string/fxaccount_create_account_header" />
<include layout="@layout/fxaccount_custom_server_view" />
<include layout="@layout/fxaccount_email_password_view" /> <include layout="@layout/fxaccount_email_password_view" />
<TextView <TextView
@ -89,4 +91,4 @@
android:contentDescription="@string/fxaccount_empty_contentDescription" /> android:contentDescription="@string/fxaccount_empty_contentDescription" />
</LinearLayout> </LinearLayout>
</ScrollView> </ScrollView>

Просмотреть файл

@ -5,8 +5,8 @@
- file, You can obtain one at http://mozilla.org/MPL/2.0/. - file, You can obtain one at http://mozilla.org/MPL/2.0/.
--> -->
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android" <ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent" android:layout_width="fill_parent"
android:layout_height="match_parent" android:layout_height="fill_parent"
android:fillViewport="true" > android:fillViewport="true" >
<LinearLayout <LinearLayout

Просмотреть файл

@ -0,0 +1,116 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
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/.
-->
<merge xmlns:android="http://schemas.android.com/apk/res/android" >
<LinearLayout
android:id="@+id/account_server_layout"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="10dp"
android:background="@color/fxaccount_error_preference_backgroundcolor"
android:gravity="center_vertical"
android:minHeight="?android:attr/listPreferredItemHeight" >
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:contentDescription="@string/fxaccount_empty_contentDescription"
android:gravity="center"
android:minWidth="48dip"
android:padding="10dip"
android:src="@drawable/fxaccount_sync_error" />
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:orientation="vertical"
android:paddingBottom="6dip"
android:paddingRight="10dip"
android:paddingTop="6dip" >
<TextView
android:id="@+android:id/account_server_title"
style="@style/FxAccountTextItem"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:layout_marginBottom="0dp"
android:gravity="center_vertical"
android:text="@string/fxaccount_custom_server_account_title" >
</TextView>
<TextView
android:id="@+android:id/account_server_summary"
style="@style/FxAccountTextItem"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="0dp"
android:ellipsize="middle"
android:focusable="true"
android:focusableInTouchMode="true"
android:singleLine="true"
android:textAppearance="?android:attr/textAppearanceSmall"
android:textSize="12sp" />
</LinearLayout>
</LinearLayout>
<LinearLayout
android:id="@+id/sync_server_layout"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="10dp"
android:background="@color/fxaccount_error_preference_backgroundcolor"
android:gravity="center_vertical"
android:minHeight="?android:attr/listPreferredItemHeight" >
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:contentDescription="@string/fxaccount_empty_contentDescription"
android:gravity="center"
android:minWidth="48dip"
android:padding="10dip"
android:src="@drawable/fxaccount_sync_error" />
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:orientation="vertical"
android:paddingBottom="6dip"
android:paddingRight="10dip"
android:paddingTop="6dip" >
<TextView
android:id="@+android:id/sync_server_title"
style="@style/FxAccountTextItem"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:layout_marginBottom="0dp"
android:gravity="center_vertical"
android:text="@string/fxaccount_custom_server_sync_title" >
</TextView>
<TextView
android:id="@+android:id/sync_server_summary"
style="@style/FxAccountTextItem"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="0dp"
android:ellipsize="marquee"
android:focusable="true"
android:focusableInTouchMode="true"
android:singleLine="true"
android:textAppearance="?android:attr/textAppearanceSmall"
android:textSize="12sp" />
</LinearLayout>
</LinearLayout>
</merge>

Просмотреть файл

@ -8,7 +8,7 @@
<merge xmlns:android="http://schemas.android.com/apk/res/android" > <merge xmlns:android="http://schemas.android.com/apk/res/android" >
<LinearLayout <LinearLayout
android:layout_width="match_parent" android:layout_width="fill_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="vertical" > android:orientation="vertical" >
@ -25,14 +25,14 @@
</AutoCompleteTextView> </AutoCompleteTextView>
<LinearLayout <LinearLayout
android:layout_width="match_parent" android:layout_width="fill_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="horizontal" > android:orientation="horizontal" >
<EditText <EditText
android:id="@+id/password" android:id="@+id/password"
style="@style/FxAccountEditItem" style="@style/FxAccountEditItem"
android:layout_width="match_parent" android:layout_width="fill_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_weight="1" android:layout_weight="1"
android:background="@drawable/fxaccount_password_background" android:background="@drawable/fxaccount_password_background"
@ -51,22 +51,22 @@
happy. Be thankful there are not three buttons! --> happy. Be thankful there are not three buttons! -->
<FrameLayout <FrameLayout
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="match_parent" android:layout_height="fill_parent"
android:layout_weight="0" android:layout_weight="0"
android:orientation="horizontal" > android:orientation="horizontal" >
<Button <Button
android:id="@+id/show_password" android:id="@+id/show_password"
style="@style/FxAccountShowHidePasswordButton" style="@style/FxAccountShowHidePasswordButton"
android:layout_width="match_parent" android:layout_width="fill_parent"
android:layout_height="match_parent" android:layout_height="fill_parent"
android:text="@string/fxaccount_password_show" > android:text="@string/fxaccount_password_show" >
</Button> </Button>
<Button <Button
style="@style/FxAccountShowHidePasswordButton" style="@style/FxAccountShowHidePasswordButton"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="match_parent" android:layout_height="fill_parent"
android:text="@string/fxaccount_password_show" android:text="@string/fxaccount_password_show"
android:visibility="invisible" > android:visibility="invisible" >
</Button> </Button>
@ -74,7 +74,7 @@
<Button <Button
style="@style/FxAccountShowHidePasswordButton" style="@style/FxAccountShowHidePasswordButton"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="match_parent" android:layout_height="fill_parent"
android:text="@string/fxaccount_password_hide" android:text="@string/fxaccount_password_hide"
android:visibility="invisible" > android:visibility="invisible" >
</Button> </Button>

Просмотреть файл

@ -5,8 +5,8 @@
- file, You can obtain one at http://mozilla.org/MPL/2.0/. - file, You can obtain one at http://mozilla.org/MPL/2.0/.
--> -->
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android" <ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent" android:layout_width="fill_parent"
android:layout_height="match_parent" android:layout_height="fill_parent"
android:fillViewport="true" > android:fillViewport="true" >
<LinearLayout <LinearLayout

Просмотреть файл

@ -19,12 +19,12 @@
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:orientation="vertical"
android:layout_height="match_parent" android:layout_height="fill_parent"
android:layout_width="match_parent" android:layout_width="fill_parent"
android:background="@android:color/transparent"> android:background="@android:color/transparent">
<ListView android:id="@android:id/list" <ListView android:id="@android:id/list"
android:layout_width="match_parent" android:layout_width="fill_parent"
android:layout_height="0px" android:layout_height="0px"
android:layout_weight="1" android:layout_weight="1"
android:paddingTop="0dip" android:paddingTop="0dip"

Просмотреть файл

@ -6,8 +6,8 @@
--> -->
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android" <ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent" android:layout_width="fill_parent"
android:layout_height="match_parent" android:layout_height="fill_parent"
android:fillViewport="true" > android:fillViewport="true" >
<LinearLayout <LinearLayout
@ -20,6 +20,8 @@
style="@style/FxAccountHeaderItem" style="@style/FxAccountHeaderItem"
android:text="@string/fxaccount_sign_in_sub_header" /> android:text="@string/fxaccount_sign_in_sub_header" />
<include layout="@layout/fxaccount_custom_server_view" />
<include layout="@layout/fxaccount_email_password_view" /> <include layout="@layout/fxaccount_email_password_view" />
<TextView <TextView
@ -39,7 +41,7 @@
</RelativeLayout> </RelativeLayout>
<LinearLayout <LinearLayout
android:layout_width="match_parent" android:layout_width="fill_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="10dp" android:layout_marginTop="10dp"
android:orientation="horizontal" > android:orientation="horizontal" >

Просмотреть файл

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent" android:layout_width="fill_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:background="@color/fxaccount_error_preference_backgroundcolor" android:background="@color/fxaccount_error_preference_backgroundcolor"
android:gravity="center_vertical" android:gravity="center_vertical"
@ -35,7 +35,7 @@
<TextView <TextView
android:id="@+android:id/title" android:id="@+android:id/title"
style="@style/FxAccountTextItem" style="@style/FxAccountTextItem"
android:layout_width="match_parent" android:layout_width="fill_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_gravity="center_horizontal" android:layout_gravity="center_horizontal"
android:gravity="center_vertical" > android:gravity="center_vertical" >

Просмотреть файл

@ -6,8 +6,8 @@
--> -->
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android" <ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent" android:layout_width="fill_parent"
android:layout_height="match_parent" android:layout_height="fill_parent"
android:fillViewport="true" > android:fillViewport="true" >
<LinearLayout <LinearLayout
@ -20,6 +20,8 @@
style="@style/FxAccountHeaderItem" style="@style/FxAccountHeaderItem"
android:text="@string/fxaccount_update_credentials_header" /> android:text="@string/fxaccount_update_credentials_header" />
<include layout="@layout/fxaccount_custom_server_view" />
<include layout="@layout/fxaccount_email_password_view" /> <include layout="@layout/fxaccount_email_password_view" />
<TextView <TextView
@ -41,6 +43,7 @@
<TextView <TextView
android:id="@+id/forgot_password_link" android:id="@+id/forgot_password_link"
style="@style/FxAccountLinkifiedItem" style="@style/FxAccountLinkifiedItem"
android:layout_marginTop="10dp"
android:text="@string/fxaccount_sign_in_forgot_password" /> android:text="@string/fxaccount_sign_in_forgot_password" />
<LinearLayout style="@style/FxAccountSpacer" /> <LinearLayout style="@style/FxAccountSpacer" />

Просмотреть файл

@ -8,17 +8,18 @@
android:layout_height="match_parent" android:layout_height="match_parent"
android:orientation="vertical"> android:orientation="vertical">
<FrameLayout android:id="@+id/history_panel_container" <include layout="@layout/home_history_list"/>
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1" />
<org.mozilla.gecko.widget.IconTabWidget android:id="@+id/tab_icon_widget" <LinearLayout android:layout_width="match_parent"
android:layout_width="match_parent" android:layout_height="wrap_content"
android:layout_height="@dimen/browser_toolbar_height" android:background="@color/home_button_bar_bg">
android:tabStripEnabled="false"
android:showDividers="none" <Button android:id="@+id/clear_history_button"
android:background="@color/background_light" style="@style/Widget.Home.ActionButton"
android:layout="@layout/home_history_tabs_indicator"/> android:text="@string/home_clear_history_button"
android:gravity="center"
android:visibility="gone"/>
</LinearLayout>
</LinearLayout> </LinearLayout>

Просмотреть файл

@ -1,10 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- 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/. -->
<ImageButton xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:background="@drawable/home_history_tabs_indicator"/>

Просмотреть файл

@ -1,25 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- 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/. -->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<include layout="@layout/home_history_list"/>
<LinearLayout android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/home_button_bar_bg">
<Button android:id="@+id/clear_history_button"
style="@style/Widget.Home.ActionButton"
android:text="@string/home_clear_history_button"
android:gravity="center"
android:visibility="gone"/>
</LinearLayout>
</LinearLayout>

Просмотреть файл

@ -10,16 +10,4 @@
<include layout="@layout/home_history_list"/> <include layout="@layout/home_history_list"/>
<LinearLayout android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/home_button_bar_bg">
<Button android:id="@+id/open_all_tabs_button"
style="@style/Widget.Home.ActionButton"
android:text="@string/home_last_tabs_open"
android:gravity="center"
android:visibility="gone"/>
</LinearLayout>
</LinearLayout> </LinearLayout>

Просмотреть файл

@ -5,7 +5,7 @@
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:gravity="center_vertical" android:gravity="center_vertical"
android:layout_width="match_parent" android:layout_width="fill_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:minHeight="?android:attr/listPreferredItemHeight" android:minHeight="?android:attr/listPreferredItemHeight"
android:orientation="horizontal" android:orientation="horizontal"

Просмотреть файл

@ -11,20 +11,20 @@
android:text="@string/sync_title_send_tab" /> android:text="@string/sync_title_send_tab" />
<LinearLayout <LinearLayout
android:layout_width="match_parent" android:layout_width="fill_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="vertical" android:orientation="vertical"
android:padding="@dimen/SyncSpace" > android:padding="@dimen/SyncSpace" >
<TextView <TextView
android:id="@+id/title" android:id="@+id/title"
android:layout_width="match_parent" android:layout_width="fill_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceMedium" android:textAppearance="?android:attr/textAppearanceMedium"
android:text="@string/sync_title_send_tab" /> android:text="@string/sync_title_send_tab" />
<TextView <TextView
android:id="@+id/uri" android:id="@+id/uri"
android:layout_width="match_parent" android:layout_width="fill_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceSmall" android:textAppearance="?android:attr/textAppearanceSmall"
android:text="@string/sync_title_send_tab" /> android:text="@string/sync_title_send_tab" />

Просмотреть файл

@ -19,7 +19,7 @@
<ProgressBar <ProgressBar
android:id="@+id/waiting_content1" android:id="@+id/waiting_content1"
style="@style/Widget.ProgressBar.Horizontal" style="@style/Widget.ProgressBar.Horizontal"
android:layout_width="match_parent" android:layout_width="fill_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:indeterminateOnly="true" android:indeterminateOnly="true"
android:padding="@dimen/SyncSpace" /> android:padding="@dimen/SyncSpace" />

Просмотреть файл

@ -6,6 +6,6 @@
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
style="@style/SyncLayout" > style="@style/SyncLayout" >
<WebView android:id="@+id/web_engine" <WebView android:id="@+id/web_engine"
android:layout_width="match_parent" android:layout_width="fill_parent"
android:layout_height="match_parent" /> android:layout_height="fill_parent" />
</LinearLayout> </LinearLayout>

Просмотреть файл

@ -1,10 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- 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/. -->
<resources>
<dimen name="history_tab_widget_width">360dp</dimen>
</resources>

Просмотреть файл

@ -7,7 +7,7 @@
<!-- Top title bar: a text view with the Sync icon to the left. --> <!-- Top title bar: a text view with the Sync icon to the left. -->
<style name="SyncTop" parent="@android:style/Widget.Holo.ActionBar"> <style name="SyncTop" parent="@android:style/Widget.Holo.ActionBar">
<item name="android:textAppearance">@android:style/TextAppearance.Holo.Widget.ActionBar.Title</item> <item name="android:textAppearance">@android:style/TextAppearance.Holo.Widget.ActionBar.Title</item>
<item name="android:layout_width">match_parent</item> <item name="android:layout_width">fill_parent</item>
<item name="android:layout_height">wrap_content</item> <item name="android:layout_height">wrap_content</item>
<item name="android:gravity">center_vertical|left</item> <item name="android:gravity">center_vertical|left</item>
<item name="android:drawableLeft">@drawable/icon</item> <item name="android:drawableLeft">@drawable/icon</item>
@ -17,12 +17,12 @@
<!-- Bottom bar: a horizontal linear layout with buttons in it. --> <!-- Bottom bar: a horizontal linear layout with buttons in it. -->
<style name="SyncBottom" parent="@android:style/Holo.Light.ButtonBar"> <style name="SyncBottom" parent="@android:style/Holo.Light.ButtonBar">
<item name="android:layout_width">match_parent</item> <item name="android:layout_width">fill_parent</item>
<item name="android:layout_height">wrap_content</item> <item name="android:layout_height">wrap_content</item>
</style> </style>
<style name="SyncButton" parent="@android:style/Widget.Holo.Light.Button.Small"> <style name="SyncButton" parent="@android:style/Widget.Holo.Light.Button.Small">
<item name="android:layout_width">match_parent</item> <item name="android:layout_width">fill_parent</item>
<item name="android:layout_height">wrap_content</item> <item name="android:layout_height">wrap_content</item>
<item name="android:layout_weight">1</item> <item name="android:layout_weight">1</item>
<item name="android:background">?android:attr/selectableItemBackground</item> <item name="android:background">?android:attr/selectableItemBackground</item>

Просмотреть файл

@ -1,10 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- 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/. -->
<resources>
<dimen name="history_tab_widget_width">480dp</dimen>
</resources>

Просмотреть файл

@ -11,7 +11,6 @@
<dimen name="tabs_counter_size">26sp</dimen> <dimen name="tabs_counter_size">26sp</dimen>
<dimen name="tabs_panel_indicator_width">60dp</dimen> <dimen name="tabs_panel_indicator_width">60dp</dimen>
<dimen name="tabs_panel_list_padding">8dip</dimen> <dimen name="tabs_panel_list_padding">8dip</dimen>
<dimen name="history_tab_widget_width">270dp</dimen>
<dimen name="panel_grid_view_column_width">250dp</dimen> <dimen name="panel_grid_view_column_width">250dp</dimen>
</resources> </resources>

Просмотреть файл

@ -82,12 +82,6 @@
<dimen name="url_bar_offset_left">32dp</dimen> <dimen name="url_bar_offset_left">32dp</dimen>
<dimen name="history_tab_indicator_height">50dp</dimen> <dimen name="history_tab_indicator_height">50dp</dimen>
<!-- We need to maintain height for the tab widget on History Page
since android does not add footer/header divider height to its
calculation for wrap_content in LinearLayout.
50dp * 2 Views + 30dp padding + 4dp dividers-->
<dimen name="history_tab_widget_height">134dp</dimen>
<!-- PageActionButtons dimensions --> <!-- PageActionButtons dimensions -->
<dimen name="page_action_button_width">32dp</dimen> <dimen name="page_action_button_width">32dp</dimen>

Просмотреть файл

@ -16,7 +16,7 @@
<style name="FxAccountMiddle"> <style name="FxAccountMiddle">
<item name="android:background">@android:color/white</item> <item name="android:background">@android:color/white</item>
<item name="android:orientation">vertical</item> <item name="android:orientation">vertical</item>
<item name="android:layout_width">match_parent</item> <item name="android:layout_width">fill_parent</item>
<item name="android:layout_height">wrap_content</item> <item name="android:layout_height">wrap_content</item>
<item name="android:layout_weight">1</item> <item name="android:layout_weight">1</item>
<item name="android:paddingTop">30dp</item> <item name="android:paddingTop">30dp</item>
@ -27,7 +27,7 @@
<style name="FxAccountSpacer"> <style name="FxAccountSpacer">
<item name="android:orientation">vertical</item> <item name="android:orientation">vertical</item>
<item name="android:layout_width">match_parent</item> <item name="android:layout_width">fill_parent</item>
<item name="android:layout_height">0dp</item> <item name="android:layout_height">0dp</item>
<item name="android:layout_weight">1</item> <item name="android:layout_weight">1</item>
</style> </style>
@ -39,7 +39,7 @@
<style name="FxAccountTextItem" parent="@android:style/TextAppearance.Medium"> <style name="FxAccountTextItem" parent="@android:style/TextAppearance.Medium">
<item name="android:textColor">@color/fxaccount_textColor</item> <item name="android:textColor">@color/fxaccount_textColor</item>
<item name="android:layout_width">match_parent</item> <item name="android:layout_width">fill_parent</item>
<item name="android:layout_height">wrap_content</item> <item name="android:layout_height">wrap_content</item>
<item name="android:gravity">center_horizontal</item> <item name="android:gravity">center_horizontal</item>
<item name="android:textSize">16sp</item> <item name="android:textSize">16sp</item>
@ -57,7 +57,7 @@
<item name="android:textColor">@drawable/fxaccount_button_color</item> <item name="android:textColor">@drawable/fxaccount_button_color</item>
<item name="android:textSize">24sp</item> <item name="android:textSize">24sp</item>
<item name="android:padding">20dp</item> <item name="android:padding">20dp</item>
<item name="android:layout_width">match_parent</item> <item name="android:layout_width">fill_parent</item>
<item name="android:layout_height">wrap_content</item> <item name="android:layout_height">wrap_content</item>
<item name="android:layout_marginBottom">10dp</item> <item name="android:layout_marginBottom">10dp</item>
</style> </style>
@ -81,7 +81,7 @@
<item name="android:focusable">true</item> <item name="android:focusable">true</item>
<item name="android:textColor">@color/fxaccount_linkified_textColor</item> <item name="android:textColor">@color/fxaccount_linkified_textColor</item>
<item name="android:textColorLink">@color/fxaccount_linkified_textColorLink</item> <item name="android:textColorLink">@color/fxaccount_linkified_textColorLink</item>
<item name="android:layout_width">match_parent</item> <item name="android:layout_width">fill_parent</item>
<item name="android:layout_height">wrap_content</item> <item name="android:layout_height">wrap_content</item>
<item name="android:gravity">center</item> <item name="android:gravity">center</item>
</style> </style>
@ -95,7 +95,7 @@
</style> </style>
<style name="FxAccountErrorItem"> <style name="FxAccountErrorItem">
<item name="android:layout_width">match_parent</item> <item name="android:layout_width">fill_parent</item>
<item name="android:layout_marginBottom">10dp</item> <item name="android:layout_marginBottom">10dp</item>
<item name="android:layout_marginTop">10dp</item> <item name="android:layout_marginTop">10dp</item>
<item name="android:layout_height">wrap_content</item> <item name="android:layout_height">wrap_content</item>
@ -118,13 +118,13 @@
<style name="FxAccountButtonLayout"> <style name="FxAccountButtonLayout">
<item name="android:orientation">vertical</item> <item name="android:orientation">vertical</item>
<item name="android:layout_width">match_parent</item> <item name="android:layout_width">fill_parent</item>
<item name="android:layout_height">wrap_content</item> <item name="android:layout_height">wrap_content</item>
<item name="android:background">@drawable/fxaccount_button_background</item> <item name="android:background">@drawable/fxaccount_button_background</item>
</style> </style>
<style name="FxAccountCheckBox"> <style name="FxAccountCheckBox">
<item name="android:layout_width">match_parent</item> <item name="android:layout_width">fill_parent</item>
<item name="android:layout_height">wrap_content</item> <item name="android:layout_height">wrap_content</item>
<item name="android:layout_marginBottom">10dp</item> <item name="android:layout_marginBottom">10dp</item>
<item name="android:textColor">@drawable/fxaccount_checkbox_textcolor</item> <item name="android:textColor">@drawable/fxaccount_checkbox_textcolor</item>

Просмотреть файл

@ -5,8 +5,8 @@
<resources> <resources>
<style name="SyncLayout"> <style name="SyncLayout">
<item name="android:layout_width">match_parent</item> <item name="android:layout_width">fill_parent</item>
<item name="android:layout_height">match_parent</item> <item name="android:layout_height">fill_parent</item>
</style> </style>
<style name="SyncLayout.Vertical" parent="@style/SyncLayout"> <style name="SyncLayout.Vertical" parent="@style/SyncLayout">
<item name="android:orientation">vertical</item> <item name="android:orientation">vertical</item>
@ -17,14 +17,14 @@
<!-- TextView Styles --> <!-- TextView Styles -->
<style name="SyncTextFrame" parent="@style/TextAppearance"> <style name="SyncTextFrame" parent="@style/TextAppearance">
<item name="android:layout_width">match_parent</item> <item name="android:layout_width">fill_parent</item>
<item name="android:layout_height">match_parent</item> <item name="android:layout_height">fill_parent</item>
<item name="android:layout_gravity">center</item> <item name="android:layout_gravity">center</item>
<item name="android:padding">@dimen/SyncSpace</item> <item name="android:padding">@dimen/SyncSpace</item>
<item name="android:orientation">vertical</item> <item name="android:orientation">vertical</item>
</style> </style>
<style name="SyncTextItem" parent="@style/TextAppearance.Medium"> <style name="SyncTextItem" parent="@style/TextAppearance.Medium">
<item name="android:layout_width">match_parent</item> <item name="android:layout_width">fill_parent</item>
<item name="android:layout_height">wrap_content</item> <item name="android:layout_height">wrap_content</item>
<item name="android:textSize">15dp</item> <item name="android:textSize">15dp</item>
</style> </style>
@ -44,7 +44,7 @@
</style> </style>
<!-- EditView Styles --> <!-- EditView Styles -->
<style name="SyncEditItem" parent="@style/Widget.EditText"> <style name="SyncEditItem" parent="@style/Widget.EditText">
<item name="android:layout_width">match_parent</item> <item name="android:layout_width">fill_parent</item>
<item name="android:layout_height">wrap_content</item> <item name="android:layout_height">wrap_content</item>
<item name="android:singleLine">true</item> <item name="android:singleLine">true</item>
<item name="android:inputType">text|textNoSuggestions</item> <item name="android:inputType">text|textNoSuggestions</item>
@ -67,7 +67,7 @@
<!-- Top title bar: a text view with the Sync icon to the left. --> <!-- Top title bar: a text view with the Sync icon to the left. -->
<style name="SyncTop"> <style name="SyncTop">
<item name="android:textAppearance">@style/TextAppearance.Large</item> <item name="android:textAppearance">@style/TextAppearance.Large</item>
<item name="android:layout_width">match_parent</item> <item name="android:layout_width">fill_parent</item>
<item name="android:layout_height">wrap_content</item> <item name="android:layout_height">wrap_content</item>
<item name="android:gravity">center_vertical|left</item> <item name="android:gravity">center_vertical|left</item>
<item name="android:drawableLeft">@drawable/icon</item> <item name="android:drawableLeft">@drawable/icon</item>
@ -80,7 +80,7 @@
<!-- Middle scroller: a scroll view with content in it. --> <!-- Middle scroller: a scroll view with content in it. -->
<style name="SyncMiddle"> <style name="SyncMiddle">
<item name="android:layout_width">match_parent</item> <item name="android:layout_width">fill_parent</item>
<item name="android:layout_height">0dp</item> <item name="android:layout_height">0dp</item>
<item name="android:layout_weight">1</item> <item name="android:layout_weight">1</item>
<item name="android:padding">@dimen/SyncSpace</item> <item name="android:padding">@dimen/SyncSpace</item>
@ -88,7 +88,7 @@
<!-- Bottom bar: a horizontal linear layout with buttons in it. --> <!-- Bottom bar: a horizontal linear layout with buttons in it. -->
<style name="SyncBottom"> <style name="SyncBottom">
<item name="android:layout_width">match_parent</item> <item name="android:layout_width">fill_parent</item>
<item name="android:layout_height">wrap_content</item> <item name="android:layout_height">wrap_content</item>
<item name="android:layout_gravity">center</item> <item name="android:layout_gravity">center</item>
<item name="android:gravity">center</item> <item name="android:gravity">center</item>
@ -104,7 +104,7 @@
</style> </style>
<style name="SyncButton" parent="@style/Widget.Button"> <style name="SyncButton" parent="@style/Widget.Button">
<item name="android:layout_width">match_parent</item> <item name="android:layout_width">fill_parent</item>
<item name="android:layout_height">wrap_content</item> <item name="android:layout_height">wrap_content</item>
<item name="android:layout_weight">1</item> <item name="android:layout_weight">1</item>
</style> </style>

Просмотреть файл

@ -10,6 +10,11 @@
android:key="email" android:key="email"
android:persistent="false" android:persistent="false"
android:title="@string/fxaccount_email_hint" /> android:title="@string/fxaccount_email_hint" />
<Preference
android:editable="false"
android:key="auth_server"
android:persistent="false"
android:title="@string/fxaccount_status_auth_server" />
</PreferenceCategory> </PreferenceCategory>
<PreferenceCategory <PreferenceCategory
android:key="sync_category" android:key="sync_category"
@ -72,6 +77,13 @@
android:key="device_name" android:key="device_name"
android:persistent="false" android:persistent="false"
android:title="@string/fxaccount_status_device_name" /> android:title="@string/fxaccount_status_device_name" />
<Preference
android:editable="false"
android:key="sync_server"
android:persistent="false"
android:title="@string/fxaccount_status_sync_server" />
</PreferenceCategory> </PreferenceCategory>
<PreferenceCategory <PreferenceCategory
android:key="legal_category" android:key="legal_category"

Просмотреть файл

@ -36,6 +36,7 @@
<string name="bookmarks_title">&bookmarks_title;</string> <string name="bookmarks_title">&bookmarks_title;</string>
<string name="history_title">&history_title;</string> <string name="history_title">&history_title;</string>
<string name="reading_list_title">&reading_list_title;</string> <string name="reading_list_title">&reading_list_title;</string>
<string name="recent_tabs_title">&recent_tabs_title;</string>
<string name="switch_to_tab">&switch_to_tab;</string> <string name="switch_to_tab">&switch_to_tab;</string>
@ -313,10 +314,10 @@
<string name="home_clear_history_button">&home_clear_history_button;</string> <string name="home_clear_history_button">&home_clear_history_button;</string>
<string name="home_clear_history_confirm">&home_clear_history_confirm;</string> <string name="home_clear_history_confirm">&home_clear_history_confirm;</string>
<string name="home_bookmarks_empty">&home_bookmarks_empty;</string> <string name="home_bookmarks_empty">&home_bookmarks_empty;</string>
<string name="home_closed_tabs_title">&home_closed_tabs_title;</string>
<string name="home_last_tabs_title">&home_last_tabs_title;</string> <string name="home_last_tabs_title">&home_last_tabs_title;</string>
<string name="home_last_tabs_open">&home_last_tabs_open;</string> <string name="home_last_tabs_open">&home_last_tabs_open;</string>
<string name="home_last_tabs_empty">&home_last_tabs_empty;</string> <string name="home_last_tabs_empty">&home_last_tabs_empty;</string>
<string name="home_most_recent_title">&home_most_recent_title;</string>
<string name="home_most_recent_empty">&home_most_recent_empty;</string> <string name="home_most_recent_empty">&home_most_recent_empty;</string>
<string name="home_reading_list_empty">&home_reading_list_empty;</string> <string name="home_reading_list_empty">&home_reading_list_empty;</string>
<string name="home_reading_list_hint">&home_reading_list_hint2;</string> <string name="home_reading_list_hint">&home_reading_list_hint2;</string>

Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше