зеркало из https://github.com/mozilla/gecko-dev.git
Merge m-c to inbound.
This commit is contained in:
Коммит
bee8ae28a0
|
@ -19,13 +19,13 @@
|
|||
<copyfile dest="Makefile" src="core/root.mk"/>
|
||||
</project>
|
||||
<project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
|
||||
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="bd5065ced020014df5fd45259fba1ac32d65673b"/>
|
||||
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="44b04243e31cd16f3baf54fcd4b5fecf9deb8b94"/>
|
||||
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="41cc1de26e4edbe12add0009cdc0bd292f2e94fe"/>
|
||||
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="bbb7659d8ea2afb396f99b3dc971ab3c42da3778"/>
|
||||
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
|
||||
<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="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 -->
|
||||
<project name="platform/abi/cpp" path="abi/cpp" revision="dd924f92906085b831bf1cbbc7484d3c043d613c"/>
|
||||
<project name="platform/bionic" path="bionic" revision="c72b8f6359de7ed17c11ddc9dfdde3f615d188a9"/>
|
||||
|
|
|
@ -17,10 +17,10 @@
|
|||
</project>
|
||||
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
|
||||
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
|
||||
<project name="gaia" path="gaia" remote="mozillaorg" revision="bd5065ced020014df5fd45259fba1ac32d65673b"/>
|
||||
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="44b04243e31cd16f3baf54fcd4b5fecf9deb8b94"/>
|
||||
<project name="gaia" path="gaia" remote="mozillaorg" revision="41cc1de26e4edbe12add0009cdc0bd292f2e94fe"/>
|
||||
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="bbb7659d8ea2afb396f99b3dc971ab3c42da3778"/>
|
||||
<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="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/>
|
||||
<!-- 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_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_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_hardware_ril" path="hardware/ril" remote="b2g" revision="832f4acaf481a19031e479a40b03d9ce5370ddee"/>
|
||||
<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">
|
||||
<copyfile dest="Makefile" src="core/root.mk"/>
|
||||
</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="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="moztt" path="external/moztt" remote="b2g" revision="c510babaf88dfa2cfe2c202afb2649ee124569af"/>
|
||||
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
|
||||
<project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>
|
||||
<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 -->
|
||||
<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"/>
|
||||
|
|
|
@ -19,13 +19,13 @@
|
|||
<copyfile dest="Makefile" src="core/root.mk"/>
|
||||
</project>
|
||||
<project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
|
||||
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="bd5065ced020014df5fd45259fba1ac32d65673b"/>
|
||||
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="44b04243e31cd16f3baf54fcd4b5fecf9deb8b94"/>
|
||||
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="41cc1de26e4edbe12add0009cdc0bd292f2e94fe"/>
|
||||
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="bbb7659d8ea2afb396f99b3dc971ab3c42da3778"/>
|
||||
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
|
||||
<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="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 -->
|
||||
<project name="platform/abi/cpp" path="abi/cpp" revision="dd924f92906085b831bf1cbbc7484d3c043d613c"/>
|
||||
<project name="platform/bionic" path="bionic" revision="c72b8f6359de7ed17c11ddc9dfdde3f615d188a9"/>
|
||||
|
|
|
@ -17,10 +17,10 @@
|
|||
</project>
|
||||
<project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>
|
||||
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
|
||||
<project name="gaia" path="gaia" remote="mozillaorg" revision="bd5065ced020014df5fd45259fba1ac32d65673b"/>
|
||||
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="44b04243e31cd16f3baf54fcd4b5fecf9deb8b94"/>
|
||||
<project name="gaia" path="gaia" remote="mozillaorg" revision="41cc1de26e4edbe12add0009cdc0bd292f2e94fe"/>
|
||||
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="bbb7659d8ea2afb396f99b3dc971ab3c42da3778"/>
|
||||
<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="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/>
|
||||
<!-- Stock Android things -->
|
||||
|
|
|
@ -4,6 +4,6 @@
|
|||
"remote": "",
|
||||
"branch": ""
|
||||
},
|
||||
"revision": "387f4c0123a7e82eae4c83f88f73e81f907c00e2",
|
||||
"revision": "aba00cfd579caaf205e05c269f0a8100f242f39c",
|
||||
"repo_path": "/integration/gaia-central"
|
||||
}
|
||||
|
|
|
@ -17,12 +17,12 @@
|
|||
<copyfile dest="Makefile" src="core/root.mk"/>
|
||||
</project>
|
||||
<project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
|
||||
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="bd5065ced020014df5fd45259fba1ac32d65673b"/>
|
||||
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="44b04243e31cd16f3baf54fcd4b5fecf9deb8b94"/>
|
||||
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="41cc1de26e4edbe12add0009cdc0bd292f2e94fe"/>
|
||||
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="bbb7659d8ea2afb396f99b3dc971ab3c42da3778"/>
|
||||
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
|
||||
<project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>
|
||||
<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 -->
|
||||
<project name="platform/abi/cpp" path="abi/cpp" revision="6426040f1be4a844082c9769171ce7f5341a5528"/>
|
||||
<project name="platform/bionic" path="bionic" revision="d2eb6c7b6e1bc7643c17df2d9d9bcb1704d0b9ab"/>
|
||||
|
|
|
@ -15,8 +15,8 @@
|
|||
<copyfile dest="Makefile" src="core/root.mk"/>
|
||||
</project>
|
||||
<project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
|
||||
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="bd5065ced020014df5fd45259fba1ac32d65673b"/>
|
||||
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="44b04243e31cd16f3baf54fcd4b5fecf9deb8b94"/>
|
||||
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="41cc1de26e4edbe12add0009cdc0bd292f2e94fe"/>
|
||||
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="bbb7659d8ea2afb396f99b3dc971ab3c42da3778"/>
|
||||
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
|
||||
<project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>
|
||||
<project name="moztt" path="external/moztt" remote="b2g" revision="c510babaf88dfa2cfe2c202afb2649ee124569af"/>
|
||||
|
|
|
@ -17,10 +17,10 @@
|
|||
</project>
|
||||
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
|
||||
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
|
||||
<project name="gaia" path="gaia" remote="mozillaorg" revision="bd5065ced020014df5fd45259fba1ac32d65673b"/>
|
||||
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="44b04243e31cd16f3baf54fcd4b5fecf9deb8b94"/>
|
||||
<project name="gaia" path="gaia" remote="mozillaorg" revision="41cc1de26e4edbe12add0009cdc0bd292f2e94fe"/>
|
||||
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="bbb7659d8ea2afb396f99b3dc971ab3c42da3778"/>
|
||||
<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="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/>
|
||||
<!-- Stock Android things -->
|
||||
|
|
|
@ -17,12 +17,12 @@
|
|||
<copyfile dest="Makefile" src="core/root.mk"/>
|
||||
</project>
|
||||
<project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
|
||||
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="bd5065ced020014df5fd45259fba1ac32d65673b"/>
|
||||
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="44b04243e31cd16f3baf54fcd4b5fecf9deb8b94"/>
|
||||
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="41cc1de26e4edbe12add0009cdc0bd292f2e94fe"/>
|
||||
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="bbb7659d8ea2afb396f99b3dc971ab3c42da3778"/>
|
||||
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
|
||||
<project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>
|
||||
<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"/>
|
||||
<!-- Stock Android things -->
|
||||
<project name="platform/abi/cpp" path="abi/cpp" revision="6426040f1be4a844082c9769171ce7f5341a5528"/>
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<?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>
|
||||
<emItem blockID="i454" id="sqlmoz@facebook.com">
|
||||
<versionRange minVersion="0" maxVersion="*" severity="3">
|
||||
|
@ -88,6 +88,12 @@
|
|||
</versionRange>
|
||||
<prefs>
|
||||
</prefs>
|
||||
</emItem>
|
||||
<emItem blockID="i626" id="{20AD702C-661E-4534-8CE9-BA4EC9AD6ECC}">
|
||||
<versionRange minVersion="0" maxVersion="*" severity="3">
|
||||
</versionRange>
|
||||
<prefs>
|
||||
</prefs>
|
||||
</emItem>
|
||||
<emItem blockID="i20" id="{AB2CE124-6272-4b12-94A9-7303C7397BD1}">
|
||||
<versionRange minVersion="0.1" maxVersion="5.2.0.7164" severity="1">
|
||||
|
@ -304,9 +310,7 @@
|
|||
<prefs>
|
||||
</prefs>
|
||||
</emItem>
|
||||
<emItem blockID="i496" id="{ACAA314B-EEBA-48e4-AD47-84E31C44796C}">
|
||||
<versionRange minVersion="0" maxVersion="*" severity="1">
|
||||
</versionRange>
|
||||
<emItem blockID="i47" id="youtube@youtube2.com">
|
||||
<prefs>
|
||||
</prefs>
|
||||
</emItem>
|
||||
|
@ -365,8 +369,8 @@
|
|||
<prefs>
|
||||
</prefs>
|
||||
</emItem>
|
||||
<emItem blockID="i584" id="{52b0f3db-f988-4788-b9dc-861d016f4487}">
|
||||
<versionRange minVersion="0" maxVersion="0.1.9999999" severity="1">
|
||||
<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="*" severity="1">
|
||||
</versionRange>
|
||||
<prefs>
|
||||
</prefs>
|
||||
|
@ -697,7 +701,7 @@
|
|||
</prefs>
|
||||
</emItem>
|
||||
<emItem blockID="i620" id="{21EAF666-26B3-4A3C-ABD0-CA2F5A326744}">
|
||||
<versionRange minVersion="0" maxVersion="*" severity="1">
|
||||
<versionRange minVersion="0" maxVersion="*" severity="3">
|
||||
</versionRange>
|
||||
<prefs>
|
||||
</prefs>
|
||||
|
@ -713,6 +717,12 @@
|
|||
</versionRange>
|
||||
<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 blockID="i370" id="happylyrics@hpyproductions.net">
|
||||
<versionRange minVersion="0" maxVersion="*" severity="1">
|
||||
|
@ -1211,7 +1221,9 @@
|
|||
<prefs>
|
||||
</prefs>
|
||||
</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})$/">
|
||||
<versionRange minVersion="0" maxVersion="*" severity="1">
|
||||
</versionRange>
|
||||
<prefs>
|
||||
</prefs>
|
||||
</emItem>
|
||||
|
@ -1507,6 +1519,12 @@
|
|||
</versionRange>
|
||||
<prefs>
|
||||
</prefs>
|
||||
</emItem>
|
||||
<emItem blockID="i496" id="{ACAA314B-EEBA-48e4-AD47-84E31C44796C}">
|
||||
<versionRange minVersion="0" maxVersion="*" severity="1">
|
||||
</versionRange>
|
||||
<prefs>
|
||||
</prefs>
|
||||
</emItem>
|
||||
<emItem blockID="i67" id="youtube2@youtube2.com">
|
||||
<versionRange minVersion="0" maxVersion="*">
|
||||
|
|
|
@ -188,5 +188,11 @@ var gContentPane = {
|
|||
document.documentElement.openWindow("Browser:TranslationExceptions",
|
||||
"chrome://browser/content/preferences/translation.xul",
|
||||
"", null);
|
||||
},
|
||||
|
||||
openTranslationProviderAttribution: function ()
|
||||
{
|
||||
Components.utils.import("resource:///modules/translation/Translation.jsm");
|
||||
Translation.openProviderAttribution();
|
||||
}
|
||||
};
|
||||
|
|
|
@ -144,10 +144,16 @@
|
|||
oncommand="gContentPane.showLanguages();"/>
|
||||
</row>
|
||||
<row id="translationBox" hidden="true">
|
||||
<checkbox id="translate" preference="browser.translation.detectLanguage" flex="1"
|
||||
label="&translateWebPages.label;" accesskey="&translateWebPages.accesskey;"
|
||||
<hbox align="center">
|
||||
<checkbox id="translate" preference="browser.translation.detectLanguage"
|
||||
label="&translateWebPages.label;." accesskey="&translateWebPages.accesskey;"
|
||||
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;"
|
||||
oncommand="gContentPane.showTranslationExceptions();"
|
||||
accesskey="&translateExceptions.accesskey;"/>
|
||||
|
|
|
@ -186,5 +186,11 @@ var gContentPane = {
|
|||
{
|
||||
openDialog("chrome://browser/content/preferences/translation.xul",
|
||||
"Browser:TranslationExceptions", null);
|
||||
},
|
||||
|
||||
openTranslationProviderAttribution: function ()
|
||||
{
|
||||
Components.utils.import("resource:///modules/translation/Translation.jsm");
|
||||
Translation.openProviderAttribution();
|
||||
}
|
||||
};
|
||||
|
|
|
@ -134,10 +134,17 @@
|
|||
</hbox>
|
||||
|
||||
<hbox id="translationBox" hidden="true">
|
||||
<checkbox id="translate" preference="browser.translation.detectLanguage" flex="1"
|
||||
label="&translateWebPages.label;" accesskey="&translateWebPages.accesskey;"
|
||||
<hbox align="center" flex="1">
|
||||
<checkbox id="translate" preference="browser.translation.detectLanguage"
|
||||
label="&translateWebPages.label;." accesskey="&translateWebPages.accesskey;"
|
||||
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;"
|
||||
oncommand="gContentPane.showTranslationExceptions();"
|
||||
accesskey="&translateExceptions.accesskey;"/>
|
||||
|
|
|
@ -80,6 +80,12 @@ this.Translation = {
|
|||
|
||||
if (trUI.shouldShowInfoBar(aBrowser.currentURI))
|
||||
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));
|
||||
},
|
||||
|
||||
/**
|
||||
* Record a denied translation offer.
|
||||
*/
|
||||
recordDeniedTranslationOffer: function () {
|
||||
this._withProvider(provider => provider.recordDeniedTranslationOffer());
|
||||
},
|
||||
|
||||
/**
|
||||
* Retrieve the translation provider and pass it to the given function.
|
||||
*
|
||||
|
@ -363,6 +376,7 @@ TranslationMeasurement1.prototype = Object.freeze({
|
|||
pageTranslatedCountsByLanguage: DAILY_LAST_TEXT_FIELD,
|
||||
detectedLanguageChangedBefore: DAILY_COUNTER_FIELD,
|
||||
detectedLanguageChangedAfter: DAILY_COUNTER_FIELD,
|
||||
deniedTranslationOffer: DAILY_COUNTER_FIELD,
|
||||
detectLanguageEnabled: DAILY_LAST_NUMERIC_FIELD,
|
||||
showTranslationUI: DAILY_LAST_NUMERIC_FIELD,
|
||||
},
|
||||
|
@ -499,6 +513,15 @@ TranslationProvider.prototype = Object.freeze({
|
|||
}.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 () {
|
||||
let m = this.getMeasurement(TranslationMeasurement1.prototype.name,
|
||||
TranslationMeasurement1.prototype.version);
|
||||
|
|
|
@ -3,3 +3,4 @@
|
|||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
browser.jar:
|
||||
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();
|
||||
});
|
||||
|
||||
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() {
|
||||
let storage = yield Metrics.Storage("translation");
|
||||
let provider = new TranslationProvider();
|
||||
|
@ -253,6 +275,8 @@ add_task(function* test_healthreporter_json() {
|
|||
yield provider.recordTranslationOpportunity("es", now);
|
||||
yield provider.recordTranslation("es", "en", 1000, now);
|
||||
|
||||
yield provider.recordDeniedTranslationOffer();
|
||||
|
||||
yield reporter.collectMeasurements();
|
||||
let payload = yield reporter.getJSONPayload(true);
|
||||
let today = reporter._formatDate(now);
|
||||
|
@ -285,6 +309,9 @@ add_task(function* test_healthreporter_json() {
|
|||
Assert.equal(translations["detectedLanguageChangedBefore"], 1);
|
||||
Assert.ok("detectedLanguageChangedAfter" in translations);
|
||||
Assert.equal(translations["detectedLanguageChangedAfter"], 1);
|
||||
|
||||
Assert.ok("deniedTranslationOffer" in translations);
|
||||
Assert.equal(translations["deniedTranslationOffer"], 1);
|
||||
} finally {
|
||||
reporter._shutdown();
|
||||
}
|
||||
|
@ -309,6 +336,8 @@ add_task(function* test_healthreporter_json2() {
|
|||
yield provider.recordTranslationOpportunity("es", now);
|
||||
yield provider.recordTranslation("es", "en", 1000, now);
|
||||
|
||||
yield provider.recordDeniedTranslationOffer();
|
||||
|
||||
yield reporter.collectMeasurements();
|
||||
let payload = yield reporter.getJSONPayload(true);
|
||||
let today = reporter._formatDate(now);
|
||||
|
@ -327,6 +356,7 @@ add_task(function* test_healthreporter_json2() {
|
|||
Assert.ok(!("pageTranslatedCountsByLanguage" in translations));
|
||||
Assert.ok(!("detectedLanguageChangedBefore" in translations));
|
||||
Assert.ok(!("detectedLanguageChangedAfter" in translations));
|
||||
Assert.ok(!("deniedTranslationOffer" in translations));
|
||||
} finally {
|
||||
reporter._shutdown();
|
||||
}
|
||||
|
|
|
@ -98,7 +98,9 @@
|
|||
class="translate-infobar-element options-menu-button"
|
||||
anonid="options"
|
||||
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"
|
||||
oncommand="document.getBindingParent(this).neverForLanguage();"/>
|
||||
<xul:menuitem anonid="neverForSite"
|
||||
|
@ -109,6 +111,12 @@
|
|||
<xul:menuitem oncommand="openPreferences('paneContent');"
|
||||
label="&translation.options.preferences.label;"
|
||||
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:button>
|
||||
|
||||
|
@ -310,6 +318,14 @@
|
|||
</body>
|
||||
</method>
|
||||
|
||||
<method name="openProviderAttribution">
|
||||
<body>
|
||||
<![CDATA[
|
||||
Translation.openProviderAttribution();
|
||||
]]>
|
||||
</body>
|
||||
</method>
|
||||
|
||||
</implementation>
|
||||
</binding>
|
||||
</bindings>
|
||||
|
|
|
@ -54,6 +54,12 @@ label.small {
|
|||
margin: 5px;
|
||||
}
|
||||
|
||||
/* Content Pane */
|
||||
#translationAttributionImage {
|
||||
width: 70px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/* Applications Pane */
|
||||
#BrowserPreferences[animated="true"] #handlersView {
|
||||
height: 25em;
|
||||
|
|
|
@ -140,6 +140,11 @@ caption {
|
|||
border-bottom: 1px solid #ccc;
|
||||
}
|
||||
|
||||
#translationAttributionImage {
|
||||
width: 70px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
#browserUseCurrent,
|
||||
#browserUseBookmark,
|
||||
#browserUseBlank {
|
||||
|
|
|
@ -46,3 +46,21 @@ notification[value="translation"] menulist > .menulist-dropmarker {
|
|||
margin: auto;
|
||||
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);
|
||||
}
|
||||
|
||||
.translation-menupopup {
|
||||
-moz-appearance: none;
|
||||
}
|
||||
|
||||
/* Bookmarks roots menu-items */
|
||||
#subscribeToPageMenuitem:not([disabled]),
|
||||
#subscribeToPageMenupopup,
|
||||
|
|
|
@ -53,6 +53,12 @@ label.small {
|
|||
margin: 6px;
|
||||
}
|
||||
|
||||
/* Content Pane */
|
||||
#translationAttributionImage {
|
||||
width: 70px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/* Applications Pane */
|
||||
#BrowserPreferences[animated="true"] #handlersView {
|
||||
height: 25em;
|
||||
|
|
|
@ -38,6 +38,7 @@ SEARCH_PATHS = [
|
|||
'config',
|
||||
'dom/bindings',
|
||||
'dom/bindings/parser',
|
||||
'layout/tools/reftest',
|
||||
'other-licenses/ply',
|
||||
'xpcom/idl-parser',
|
||||
'testing',
|
||||
|
|
|
@ -17,6 +17,7 @@ mozilla.pth:config
|
|||
mozilla.pth:xpcom/typelib/xpt/tools
|
||||
mozilla.pth:dom/bindings
|
||||
mozilla.pth:dom/bindings/parser
|
||||
mozilla.pth:layout/tools/reftest
|
||||
moztreedocs.pth:tools/docs
|
||||
copy:build/buildconfig.py
|
||||
packages.txt:testing/mozbase/packages.txt
|
||||
|
|
|
@ -1316,6 +1316,12 @@ this.DOMApplicationRegistry = {
|
|||
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.broadcastMessage("Webapps:UpdateState", {
|
||||
app: {
|
||||
|
@ -1339,6 +1345,7 @@ this.DOMApplicationRegistry = {
|
|||
|
||||
let id = this._appIdForManifestURL(aManifestURL);
|
||||
let app = this.webapps[id];
|
||||
|
||||
if (!app) {
|
||||
debug("startDownload: No app found for " + aManifestURL);
|
||||
throw new Error("NO_SUCH_APP");
|
||||
|
@ -2770,6 +2777,14 @@ this.DOMApplicationRegistry = {
|
|||
// initialize the progress to 0 right now
|
||||
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 hash = yield this._computeFileHash(zipFile.path);
|
||||
|
||||
|
@ -4083,6 +4098,15 @@ AppcacheObserver.prototype = {
|
|||
let setError = function appObs_setError(aError) {
|
||||
debug("Offlinecache setError to " + aError);
|
||||
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", {
|
||||
app: app,
|
||||
error: aError,
|
||||
|
@ -4092,7 +4116,6 @@ AppcacheObserver.prototype = {
|
|||
eventType: "downloaderror",
|
||||
manifestURL: app.manifestURL
|
||||
});
|
||||
mustSave = true;
|
||||
}
|
||||
|
||||
switch (aState) {
|
||||
|
|
|
@ -699,7 +699,10 @@ BluetoothAdapter::EnableDisable(bool aEnable)
|
|||
}
|
||||
|
||||
nsTArray<nsString> types;
|
||||
types.AppendElement(NS_LITERAL_STRING("State"));
|
||||
BT_APPEND_ENUM_STRING(types,
|
||||
BluetoothAdapterAttribute,
|
||||
BluetoothAdapterAttribute::State);
|
||||
|
||||
DispatchAttributeEvent(types);
|
||||
|
||||
nsRefPtr<BluetoothReplyRunnable> result =
|
||||
|
@ -792,7 +795,7 @@ BluetoothAdapter::HandlePropertyChanged(const BluetoothValue& aValue)
|
|||
// BluetoothAdapterAttribute properties
|
||||
if (IsAdapterAttributeChanged(type, arr[i].value())) {
|
||||
SetPropertyByValue(arr[i]);
|
||||
types.AppendElement(arr[i].name());
|
||||
BT_APPEND_ENUM_STRING(types, BluetoothAdapterAttribute, type);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -83,6 +83,18 @@ extern bool gBluetoothDebugFlag;
|
|||
} \
|
||||
} 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 \
|
||||
namespace mozilla { namespace dom { namespace bluetooth {
|
||||
#define END_BLUETOOTH_NAMESPACE \
|
||||
|
|
|
@ -260,7 +260,9 @@ BluetoothManager::DispatchAttributeEvent()
|
|||
JSAutoCompartment ac(cx, scope);
|
||||
|
||||
nsTArray<nsString> types;
|
||||
types.AppendElement(NS_LITERAL_STRING("DefaultAdapter"));
|
||||
BT_APPEND_ENUM_STRING(types,
|
||||
BluetoothManagerAttribute,
|
||||
BluetoothManagerAttribute::DefaultAdapter);
|
||||
|
||||
if (!ToJSValue(cx, types, &value)) {
|
||||
JS_ClearPendingException(cx);
|
||||
|
|
|
@ -566,7 +566,7 @@ function sendMMI(aMmi) {
|
|||
* Query current voice privacy mode.
|
||||
*
|
||||
* Fulfill params:
|
||||
A boolean indicates the current voice privacy mode.
|
||||
* A boolean indicates the current voice privacy mode.
|
||||
* Reject params:
|
||||
* 'RadioNotAvailable', 'RequestNotSupported', or 'GenericFailure'.
|
||||
*
|
||||
|
@ -578,6 +578,54 @@ function sendMMI(aMmi) {
|
|||
.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.
|
||||
*
|
||||
|
@ -959,6 +1007,94 @@ function setEmulatorOperatorNamesAndWait(aOperator, aLongName, aShortName,
|
|||
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;
|
||||
|
||||
/**
|
||||
|
|
|
@ -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/ */
|
||||
|
||||
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
|
||||
// objects, so grab our objects from a new Navigator
|
||||
let ifr = document.createElement("iframe");
|
||||
let connection;
|
||||
ifr.onload = function() {
|
||||
connection = ifr.contentWindow.navigator.mozMobileConnections[0];
|
||||
// Test passing an invalid pin or newPin.
|
||||
[null, "0000", "InvalidPassword"],
|
||||
["0000", null, "InvalidPassword"],
|
||||
[null, null, "InvalidPassword"],
|
||||
|
||||
ok(connection instanceof ifr.contentWindow.MozMobileConnection,
|
||||
"connection is instanceof " + connection.constructor);
|
||||
// Test passing mismatched newPin.
|
||||
["000", "0000", "InvalidPassword"],
|
||||
["00000", "1111", "InvalidPassword"],
|
||||
["abcd", "efgh", "InvalidPassword"],
|
||||
|
||||
setTimeout(testChangeCallBarringPasswordWithFailure, 0);
|
||||
};
|
||||
document.body.appendChild(ifr);
|
||||
|
||||
function testChangeCallBarringPasswordWithFailure() {
|
||||
// Incorrect parameters, expect onerror callback.
|
||||
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'},
|
||||
// TODO: Bug 906603 - B2G RIL: Support Change Call Barring Password on Emulator.
|
||||
// Currently emulator doesn't support REQUEST_CHANGE_BARRING_PASSWORD, so we
|
||||
// expect to get a 'RequestNotSupported' error here.
|
||||
["1234", "1234", "RequestNotSupported"]
|
||||
];
|
||||
|
||||
function do_test() {
|
||||
for (let i = 0; i < options.length; i++) {
|
||||
let request = connection.changeCallBarringPassword(options[i]);
|
||||
function testChangeCallBarringPassword(aPin, aNewPin, aExpectedError) {
|
||||
log("Test changing call barring password to " + aPin + "/" + aNewPin);
|
||||
|
||||
request.onsuccess = function() {
|
||||
ok(false, 'Unexpected result.');
|
||||
setTimeout(cleanUp , 0);
|
||||
let options = {
|
||||
pin: aPin,
|
||||
newPin: aNewPin
|
||||
};
|
||||
|
||||
request.onerror = function() {
|
||||
ok(request.error.name === 'InvalidPassword', 'InvalidPassword');
|
||||
if (i >= options.length) {
|
||||
setTimeout(testChangeCallBarringPasswordWithSuccess, 0);
|
||||
}
|
||||
};
|
||||
}
|
||||
return changeCallBarringPassword(options)
|
||||
.then(function resolve() {
|
||||
ok(!aExpectedError, "changeCallBarringPassword success");
|
||||
}, function reject(aError) {
|
||||
is(aError.name, aExpectedError, "failed to changeCallBarringPassword");
|
||||
});
|
||||
}
|
||||
|
||||
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();
|
||||
// Start tests
|
||||
startTestCommon(function() {
|
||||
let promise = Promise.resolve();
|
||||
for (let i = 0; i < TEST_DATA.length; i++) {
|
||||
let data = TEST_DATA[i];
|
||||
promise =
|
||||
promise.then(() => testChangeCallBarringPassword(data[0], data[1], data[2]));
|
||||
}
|
||||
return promise;
|
||||
});
|
||||
|
|
|
@ -2,38 +2,73 @@
|
|||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
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
|
||||
// objects, so grab our objects from a new Navigator
|
||||
let ifr = document.createElement("iframe");
|
||||
let connection;
|
||||
ifr.onload = function() {
|
||||
connection = ifr.contentWindow.navigator.mozMobileConnections[0];
|
||||
function testGetCallBarringOption(aOptions, aExpectedError) {
|
||||
log("Test getting call barring to " + JSON.stringify(aOptions));
|
||||
|
||||
ok(connection instanceof ifr.contentWindow.MozMobileConnection,
|
||||
"connection is instanceof " + connection.constructor);
|
||||
|
||||
testGetCallBarringOption();
|
||||
};
|
||||
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();
|
||||
};
|
||||
return getCallBarringOption(aOptions)
|
||||
.then(function resolve(aResult) {
|
||||
ok(false, "should not success");
|
||||
}, function reject(aError) {
|
||||
is(aError.name, aExpectedError, "failed to getCallBarringOption");
|
||||
});
|
||||
}
|
||||
|
||||
function cleanUp() {
|
||||
SpecialPowers.removePermission("mobileconnection", document);
|
||||
finish();
|
||||
// Start tests
|
||||
startTestCommon(function() {
|
||||
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/ */
|
||||
|
||||
MARIONETTE_TIMEOUT = 60000;
|
||||
MARIONETTE_HEAD_JS = "head.js";
|
||||
|
||||
SpecialPowers.addPermission("mobileconnection", true, document);
|
||||
|
||||
// Permission changes can't change existing Navigator.prototype
|
||||
// objects, so grab our objects from a new Navigator
|
||||
let ifr = document.createElement("iframe");
|
||||
let connection;
|
||||
ifr.onload = function() {
|
||||
connection = ifr.contentWindow.navigator.mozMobileConnections[0];
|
||||
|
||||
ok(connection instanceof ifr.contentWindow.MozMobileConnection,
|
||||
"connection is instanceof " + connection.constructor);
|
||||
|
||||
nextTest();
|
||||
};
|
||||
document.body.appendChild(ifr);
|
||||
|
||||
let caseId = 0;
|
||||
let options = [
|
||||
buildOption(5, true, '0000', 0), // invalid program.
|
||||
|
||||
// test null.
|
||||
buildOption(null, true, '0000', 0),
|
||||
buildOption(0, null, '0000', 0),
|
||||
buildOption(0, true, null, 0),
|
||||
buildOption(0, true, '0000', null),
|
||||
|
||||
// test undefined.
|
||||
{'enabled': true, 'password': '0000', 'serviceClass': 0},
|
||||
{'program': 0, 'password': '0000', 'serviceClass': 0},
|
||||
{'program': 0, 'enabled': true, 'serviceClass': 0},
|
||||
{'program': 0, 'enabled': true, 'password': '0000'},
|
||||
const TEST_DATA = [
|
||||
// Test passing invalid program.
|
||||
{
|
||||
options: {
|
||||
"program": 5, /* Invalid program */
|
||||
"enabled": true,
|
||||
"password": "0000",
|
||||
"serviceClass": 0
|
||||
},
|
||||
expectedError: "InvalidParameter"
|
||||
}, {
|
||||
options: {
|
||||
"program": null,
|
||||
"enabled": true,
|
||||
"password": "0000",
|
||||
"serviceClass": 0
|
||||
},
|
||||
expectedError: "InvalidParameter"
|
||||
}, {
|
||||
options: {
|
||||
/* Undefined program */
|
||||
"enabled": true,
|
||||
"password": "0000",
|
||||
"serviceClass": 0
|
||||
},
|
||||
expectedError: "InvalidParameter"
|
||||
},
|
||||
// Test passing invalid enabled.
|
||||
{
|
||||
options: {
|
||||
"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) {
|
||||
return {
|
||||
'program': program,
|
||||
'enabled': enabled,
|
||||
'password': password,
|
||||
'serviceClass': serviceClass
|
||||
};
|
||||
function testSetCallBarringOption(aOptions, aExpectedError) {
|
||||
log("Test setting call barring to " + JSON.stringify(aOptions));
|
||||
|
||||
return setCallBarringOption(aOptions)
|
||||
.then(function resolve() {
|
||||
ok(false, "changeCallBarringPassword success");
|
||||
}, function reject(aError) {
|
||||
is(aError.name, aExpectedError, "failed to changeCallBarringPassword");
|
||||
});
|
||||
}
|
||||
|
||||
function testSetCallBarringOptionError(option) {
|
||||
let request = connection.setCallBarringOption(option);
|
||||
request.onsuccess = function() {
|
||||
ok(false,
|
||||
'should not fire onsuccess for invaild call barring option: '
|
||||
+ JSON.stringify(option));
|
||||
};
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
function cleanUp() {
|
||||
SpecialPowers.removePermission("mobileconnection", document);
|
||||
finish();
|
||||
// Start tests
|
||||
startTestCommon(function() {
|
||||
let promise = Promise.resolve();
|
||||
for (let i = 0; i < TEST_DATA.length; i++) {
|
||||
let data = TEST_DATA[i];
|
||||
promise = promise.then(() => testSetCallBarringOption(data.options,
|
||||
data.expectedError));
|
||||
}
|
||||
return promise;
|
||||
});
|
||||
|
|
|
@ -1,84 +1,33 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
MARIONETTE_TIMEOUT = 30000;
|
||||
|
||||
SpecialPowers.addPermission("mobileconnection", true, document);
|
||||
|
||||
// Permission changes can't change existing Navigator.prototype
|
||||
// objects, so grab our objects from a new Navigator
|
||||
let ifr = document.createElement("iframe");
|
||||
let connection;
|
||||
ifr.onload = function() {
|
||||
connection = ifr.contentWindow.navigator.mozMobileConnections[0];
|
||||
ok(connection instanceof ifr.contentWindow.MozMobileConnection,
|
||||
"connection is instanceof " + connection.constructor);
|
||||
MARIONETTE_TIMEOUT = 60000;
|
||||
MARIONETTE_HEAD_JS = "head.js";
|
||||
|
||||
// The emulator's hard coded iccid value.
|
||||
// See it here {B2G_HOME}/external/qemu/telephony/sim_card.c.
|
||||
is(connection.iccId, 89014103211118510720);
|
||||
const ICCID = "89014103211118510720";
|
||||
|
||||
runNextTest();
|
||||
};
|
||||
document.body.appendChild(ifr);
|
||||
function setRadioEnabledAndWaitIccChange(aEnabled) {
|
||||
let promises = [];
|
||||
promises.push(waitForManagerEvent("iccchange"));
|
||||
promises.push(setRadioEnabled(aEnabled));
|
||||
|
||||
function waitForIccChange(callback) {
|
||||
connection.addEventListener("iccchange", function handler() {
|
||||
connection.removeEventListener("iccchange", handler);
|
||||
callback();
|
||||
return Promise.all(promises);
|
||||
}
|
||||
|
||||
// Start tests
|
||||
startTestCommon(function() {
|
||||
log("Test initial iccId");
|
||||
is(mobileConnection.iccId, ICCID);
|
||||
|
||||
return setRadioEnabledAndWaitIccChange(false)
|
||||
.then(() => {
|
||||
is(mobileConnection.iccId, null);
|
||||
})
|
||||
|
||||
// Restore radio state.
|
||||
.then(() => setRadioEnabledAndWaitIccChange(true))
|
||||
.then(() => {
|
||||
is(mobileConnection.iccId, ICCID);
|
||||
});
|
||||
}
|
||||
|
||||
function setRadioEnabled(enabled) {
|
||||
let request = connection.setRadioEnabled(enabled);
|
||||
|
||||
request.onsuccess = function onsuccess() {
|
||||
log('setRadioEnabled: ' + enabled);
|
||||
};
|
||||
|
||||
request.onerror = function onerror() {
|
||||
ok(false, "setRadioEnabled should be ok");
|
||||
};
|
||||
}
|
||||
|
||||
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.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
MARIONETTE_TIMEOUT = 30000;
|
||||
MARIONETTE_TIMEOUT = 60000;
|
||||
MARIONETTE_HEAD_JS = "head.js";
|
||||
|
||||
SpecialPowers.addPermission("mobilenetwork", true, document);
|
||||
|
||||
let connection = navigator.mozMobileConnections[0];
|
||||
ok(connection instanceof MozMobileConnection,
|
||||
"connection is instanceof " + connection.constructor);
|
||||
|
||||
|
||||
function testLastKnownNetwork() {
|
||||
log("testLastKnownNetwork: " + connection.lastKnownNetwork);
|
||||
// Start tests
|
||||
startTestCommon(function() {
|
||||
// The emulator's hard coded operatoer's mcc and mnc codes.
|
||||
is(connection.lastKnownNetwork, "310-260");
|
||||
runNextTest();
|
||||
}
|
||||
|
||||
function testLastKnownHomeNetwork() {
|
||||
log("testLastKnownHomeNetwork: " + connection.lastKnownHomeNetwork);
|
||||
is(mobileConnection.lastKnownNetwork, "310-260");
|
||||
// The emulator's hard coded icc's mcc and mnc codes.
|
||||
is(connection.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();
|
||||
is(mobileConnection.lastKnownHomeNetwork, "310-260");
|
||||
});
|
||||
|
|
|
@ -2,167 +2,40 @@
|
|||
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
MARIONETTE_TIMEOUT = 60000;
|
||||
MARIONETTE_HEAD_JS = "head.js";
|
||||
|
||||
const DATA_KEY = "ril.data.enabled";
|
||||
const APN_KEY = "ril.data.apnSettings";
|
||||
// Start tests
|
||||
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);
|
||||
SpecialPowers.addPermission("mobileconnection", true, document);
|
||||
SpecialPowers.addPermission("settings-read", true, document);
|
||||
SpecialPowers.addPermission("settings-write", true, document);
|
||||
// Test disabling/enabling radio power.
|
||||
.then(() => setRadioEnabledAndWait(false))
|
||||
.then(() => setRadioEnabledAndWait(true))
|
||||
|
||||
let settings = window.navigator.mozSettings;
|
||||
let connection = window.navigator.mozMobileConnections[0];
|
||||
ok(connection instanceof MozMobileConnection,
|
||||
"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 =
|
||||
[
|
||||
[
|
||||
// Test disabling radio when data is connected.
|
||||
.then(() => {
|
||||
let apnSettings = [[
|
||||
{"carrier":"T-Mobile US",
|
||||
"apn":"epc.tmobile.com",
|
||||
"mmsc":"http://mms.msg.eng.t-mobile.com/mms/wapenc",
|
||||
"types":["default","supl","mms"]}
|
||||
]
|
||||
];
|
||||
return setSetting(APN_KEY, apn);
|
||||
}
|
||||
|
||||
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"))
|
||||
"types":["default","supl","mms"]}]];
|
||||
return setDataApnSettings(apnSettings);
|
||||
})
|
||||
.then(() => setDataEnabledAndWait(true))
|
||||
.then(() => setRadioEnabledAndWait(false))
|
||||
.then(() => {
|
||||
// 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() {
|
||||
SpecialPowers.removePermission("mobileconnection", document);
|
||||
SpecialPowers.removePermission("settings-write", document);
|
||||
SpecialPowers.removePermission("settings-read", document);
|
||||
SpecialPowers.clearUserPref("dom.mozSettings.enabled");
|
||||
finish();
|
||||
}
|
||||
// Restore test environment.
|
||||
.then(() => setDataApnSettings(origApnSettings))
|
||||
.then(() => setDataEnabled(false))
|
||||
.then(() => setRadioEnabledAndWait(true));
|
||||
|
||||
testSwitchRadio()
|
||||
.then(testDisableRadioWhenDataConnected)
|
||||
.then(null, () => {
|
||||
ok(false, "promise reject somewhere");
|
||||
})
|
||||
.then(cleanUp);
|
||||
}, ["settings-read", "settings-write"]);
|
||||
|
|
|
@ -1,132 +1,98 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
MARIONETTE_TIMEOUT = 30000;
|
||||
MARIONETTE_HEAD_JS = "mobile_header.js";
|
||||
MARIONETTE_TIMEOUT = 60000;
|
||||
MARIONETTE_HEAD_JS = "head.js";
|
||||
|
||||
/* Emulator command for GSM/UMTS signal strength */
|
||||
function setEmulatorGsmSignalStrength(rssi) {
|
||||
emulatorHelper.sendCommand("gsm signal " + rssi);
|
||||
// Emulator uses rssi = 7 as default value.
|
||||
const DEFAULT_RSSI = 7;
|
||||
|
||||
const TEST_DATA = [
|
||||
// All invalid case.
|
||||
{
|
||||
input: {
|
||||
rxlev: 99,
|
||||
rsrp: 65535,
|
||||
rssnr: 65535
|
||||
},
|
||||
expect: {
|
||||
signalStrength: null,
|
||||
relSignalStrength: null
|
||||
}
|
||||
|
||||
/* Emulator command for LTE signal strength */
|
||||
function setEmulatorLteSignalStrength(rxlev, rsrp, rssnr) {
|
||||
let lteSignal = rxlev + " " + rsrp + " " + rssnr;
|
||||
emulatorHelper.sendCommand("gsm lte_signal " + lteSignal);
|
||||
},
|
||||
// Valid rxlev with max value.
|
||||
{
|
||||
input: {
|
||||
rxlev: 63,
|
||||
rsrp: 65535,
|
||||
rssnr: 65535
|
||||
},
|
||||
expect: {
|
||||
signalStrength: -48,
|
||||
relSignalStrength: 100
|
||||
}
|
||||
|
||||
function waitForVoiceChangeEvent(callback) {
|
||||
mobileConnection.addEventListener("voicechange", function onvoicechange() {
|
||||
mobileConnection.removeEventListener("voicechange", onvoicechange);
|
||||
|
||||
if (callback && typeof callback === "function") {
|
||||
callback();
|
||||
},
|
||||
// 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 */
|
||||
taskHelper.push(function testInitialSignalStrengthInfo() {
|
||||
function testInitialSignalStrengthInfo() {
|
||||
log("Test initial signal strength info");
|
||||
|
||||
let voice = mobileConnection.voice;
|
||||
// Android emulator initializes the signal strength to -99 dBm
|
||||
is(voice.signalStrength, -99, "check voice.signalStrength");
|
||||
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 */
|
||||
taskHelper.push(function testLteSignalStrength() {
|
||||
// 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() {
|
||||
return setEmulatorLteSignalStrengthAndWait(aInput.rxlev, aInput.rsrp, aInput.rssnr)
|
||||
.then(() => {
|
||||
let voice = mobileConnection.voice;
|
||||
is(voice.signalStrength, expect.signalStrength,
|
||||
is(voice.signalStrength, aExpect.signalStrength,
|
||||
"check voice.signalStrength");
|
||||
is(voice.relSignalStrength, expect.relSignalStrength,
|
||||
is(voice.relSignalStrength, aExpect.relSignalStrength,
|
||||
"check voice.relSignalStrength");
|
||||
|
||||
if (callback && typeof callback === "function") {
|
||||
callback();
|
||||
}
|
||||
});
|
||||
|
||||
setEmulatorLteSignalStrength(input.rxlev, input.rsrp, input.rssnr);
|
||||
}
|
||||
|
||||
let testData = [
|
||||
// All invalid case.
|
||||
{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}
|
||||
}
|
||||
];
|
||||
// Start tests
|
||||
startTestCommon(function() {
|
||||
// Test initial status
|
||||
testInitialSignalStrengthInfo();
|
||||
|
||||
// Run all test data.
|
||||
(function do_call() {
|
||||
let next = testData.shift();
|
||||
if (!next) {
|
||||
taskHelper.runNext();
|
||||
return;
|
||||
// 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));
|
||||
}
|
||||
doTestLteSignalStrength(next.input, next.expect, do_call);
|
||||
})();
|
||||
|
||||
// Reset Signal Strength Info to default
|
||||
return promise.then(() => setEmulatorGsmSignalStrengthAndWait(DEFAULT_RSSI));
|
||||
});
|
||||
|
||||
/* 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 {
|
||||
|
||||
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*)
|
||||
: mSharedBuffer(nullptr)
|
||||
, mMutex(nullptr)
|
||||
|
@ -43,20 +61,7 @@ CrossProcessMutex::CrossProcessMutex(const char*)
|
|||
mCount = &(data->mCount);
|
||||
|
||||
*mCount = 1;
|
||||
|
||||
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();
|
||||
}
|
||||
InitMutex(mMutex);
|
||||
|
||||
MOZ_COUNT_CTOR(CrossProcessMutex);
|
||||
}
|
||||
|
@ -84,7 +89,13 @@ CrossProcessMutex::CrossProcessMutex(CrossProcessMutexHandle aHandle)
|
|||
|
||||
mMutex = &(data->mMutex);
|
||||
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);
|
||||
}
|
||||
|
|
|
@ -34,3 +34,6 @@ MOCHITEST_MANIFESTS += [
|
|||
'reftests/fonts/mochitest.ini',
|
||||
'reftests/fonts/mplus/mochitest.ini',
|
||||
]
|
||||
|
||||
REFTEST_MANIFESTS += ['reftests/reftest.list']
|
||||
CRASHTEST_MANIFESTS += ['../testing/crashtest/crashtests.list']
|
||||
|
|
|
@ -3,74 +3,24 @@
|
|||
# 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/.
|
||||
|
||||
import sys, os.path, re
|
||||
|
||||
commentRE = re.compile(r"\s+#")
|
||||
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()
|
||||
import os
|
||||
import sys
|
||||
from reftest import ReftestManifest
|
||||
|
||||
def printTestDirs(topsrcdir, topmanifests):
|
||||
"""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()
|
||||
for manifest in topmanifests:
|
||||
parseManifest(manifest, dirs)
|
||||
for dir in sorted(dirs):
|
||||
d = dir[len(topsrcdir):].replace('\\','/')
|
||||
if d[0] == '/':
|
||||
d = d[1:]
|
||||
print d
|
||||
for path in topmanifests:
|
||||
m = ReftestManifest()
|
||||
m.load(path)
|
||||
dirs |= m.dirs
|
||||
|
||||
for d in sorted(dirs):
|
||||
d = d[len(topsrcdir):].replace('\\', '/').lstrip('/')
|
||||
print(d)
|
||||
|
||||
if __name__ == '__main__':
|
||||
if len(sys.argv) < 3:
|
||||
|
|
|
@ -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_from_crash", true);
|
||||
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.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.
|
||||
final Intent intent = new Intent(getContext(), FxAccountGetStartedActivity.class);
|
||||
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);
|
||||
|
||||
} 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_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";
|
||||
|
||||
// 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.State;
|
||||
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.setup.Constants;
|
||||
import org.mozilla.gecko.sync.setup.activities.ActivityUtils;
|
||||
|
@ -32,6 +33,7 @@ import android.accounts.AccountManager;
|
|||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Bundle;
|
||||
import android.text.Editable;
|
||||
import android.text.TextWatcher;
|
||||
import android.text.method.PasswordTransformationMethod;
|
||||
|
@ -50,6 +52,16 @@ import android.widget.TextView;
|
|||
import android.widget.TextView.OnEditorActionListener;
|
||||
|
||||
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() {
|
||||
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();
|
||||
|
||||
// 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 AutoCompleteTextView emailEdit;
|
||||
|
@ -69,18 +85,34 @@ abstract public class FxAccountAbstractSetupActivity extends FxAccountAbstractAc
|
|||
protected Button button;
|
||||
protected ProgressBar progressBar;
|
||||
|
||||
private String authServerEndpoint;
|
||||
private String syncServerEndpoint;
|
||||
|
||||
protected String getAuthServerEndpoint() {
|
||||
return authServerEndpoint;
|
||||
}
|
||||
|
||||
protected String getTokenServerEndpoint() {
|
||||
return syncServerEndpoint;
|
||||
}
|
||||
|
||||
protected void createShowPasswordButton() {
|
||||
showPasswordButton.setOnClickListener(new OnClickListener() {
|
||||
@SuppressWarnings("deprecation")
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
boolean isShown = passwordEdit.getTransformationMethod() instanceof SingleLineTransformationMethod;
|
||||
setPasswordButtonShown(!isShown);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@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 (isShown) {
|
||||
if (!shouldShow) {
|
||||
passwordEdit.setTransformationMethod(PasswordTransformationMethod.getInstance());
|
||||
showPasswordButton.setText(R.string.fxaccount_password_show);
|
||||
showPasswordButton.setBackgroundDrawable(getResources().getDrawable(R.drawable.fxaccount_password_button_show_background));
|
||||
|
@ -93,8 +125,6 @@ abstract public class FxAccountAbstractSetupActivity extends FxAccountAbstractAc
|
|||
}
|
||||
passwordEdit.setSelection(start, stop);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
protected void linkifyPolicy() {
|
||||
TextView policyView = (TextView) ensureFindViewById(null, R.id.policy, "policy links");
|
||||
|
@ -262,7 +292,7 @@ abstract public class FxAccountAbstractSetupActivity extends FxAccountAbstractAc
|
|||
AndroidFxAccount fxAccount;
|
||||
try {
|
||||
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
|
||||
// (rather than whatever the user entered), because the user's keys are
|
||||
// wrapped and salted with the initial email they provided to
|
||||
|
@ -356,6 +386,31 @@ abstract public class FxAccountAbstractSetupActivity extends FxAccountAbstractAc
|
|||
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
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
|
@ -370,4 +425,110 @@ abstract public class FxAccountAbstractSetupActivity extends FxAccountAbstractAc
|
|||
};
|
||||
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() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
final String email = emailEdit.getText().toString();
|
||||
final String password = passwordEdit.getText().toString();
|
||||
doSigninInstead(email, password);
|
||||
final Bundle extras = makeExtrasBundle(null, null);
|
||||
startActivityInstead(FxAccountSignInActivity.class, CHILD_REQUEST_CODE, extras);
|
||||
}
|
||||
});
|
||||
|
||||
// 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("email"));
|
||||
passwordEdit.setText(bundle.getString("password"));
|
||||
}
|
||||
updateFromIntentExtras();
|
||||
}
|
||||
|
||||
protected void doSigninInstead(final String email, final String password) {
|
||||
Intent intent = new Intent(this, FxAccountSignInActivity.class);
|
||||
if (email != null) {
|
||||
intent.putExtra("email", email);
|
||||
@Override
|
||||
protected Bundle makeExtrasBundle(String email, String password) {
|
||||
final Bundle extras = super.makeExtrasBundle(email, password);
|
||||
final String year = yearEdit.getText().toString();
|
||||
extras.putString(EXTRA_YEAR, year);
|
||||
return extras;
|
||||
}
|
||||
if (password != null) {
|
||||
intent.putExtra("password", password);
|
||||
|
||||
@Override
|
||||
protected void updateFromIntentExtras() {
|
||||
super.updateFromIntentExtras();
|
||||
|
||||
if (getIntent() != null) {
|
||||
yearEdit.setText(getIntent().getStringExtra(EXTRA_YEAR));
|
||||
}
|
||||
// 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
|
||||
|
@ -142,7 +139,9 @@ public class FxAccountCreateAccountActivity extends FxAccountAbstractSetupActivi
|
|||
email = emailEdit.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);
|
||||
remoteErrorTextView.setMovementMethod(LinkMovementMethod.getInstance());
|
||||
|
@ -205,7 +204,7 @@ public class FxAccountCreateAccountActivity extends FxAccountAbstractSetupActivi
|
|||
}
|
||||
|
||||
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);
|
||||
// This delegate creates a new Android account on success, opens the
|
||||
// appropriate "success!" activity, and finishes this activity.
|
||||
|
|
|
@ -50,13 +50,24 @@ public class FxAccountGetStartedActivity extends AccountAuthenticatorActivity {
|
|||
button.setOnClickListener(new OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
Intent intent = new Intent(FxAccountGetStartedActivity.this, FxAccountCreateAccountActivity.class);
|
||||
Bundle extras = null; // startFlow accepts null.
|
||||
if (getIntent() != null) {
|
||||
extras = getIntent().getExtras();
|
||||
}
|
||||
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);
|
||||
startActivityForResult(intent, CHILD_REQUEST_CODE);
|
||||
if (extras != null) {
|
||||
intent.putExtras(extras);
|
||||
}
|
||||
});
|
||||
startActivityForResult(intent, CHILD_REQUEST_CODE);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -79,6 +90,20 @@ public class FxAccountGetStartedActivity extends AccountAuthenticatorActivity {
|
|||
this.startActivity(intent);
|
||||
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.FxAccountClientException.FxAccountClientRemoteException;
|
||||
import org.mozilla.gecko.background.fxa.PasswordStretcher;
|
||||
import org.mozilla.gecko.fxa.FxAccountConstants;
|
||||
import org.mozilla.gecko.fxa.tasks.FxAccountSignInTask;
|
||||
import org.mozilla.gecko.sync.setup.activities.ActivityUtils;
|
||||
|
||||
|
@ -65,22 +64,12 @@ public class FxAccountSignInActivity extends FxAccountAbstractSetupActivity {
|
|||
createAccountInsteadLink.setOnClickListener(new OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
Intent intent = new Intent(FxAccountSignInActivity.this, FxAccountCreateAccountActivity.class);
|
||||
intent.putExtra("email", emailEdit.getText().toString());
|
||||
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);
|
||||
final Bundle extras = makeExtrasBundle(null, null);
|
||||
startActivityInstead(FxAccountCreateAccountActivity.class, CHILD_REQUEST_CODE, extras);
|
||||
}
|
||||
});
|
||||
|
||||
// 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("email"));
|
||||
passwordEdit.setText(bundle.getString("password"));
|
||||
}
|
||||
updateFromIntentExtras();
|
||||
|
||||
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);
|
||||
|
@ -102,7 +91,7 @@ public class FxAccountSignInActivity extends FxAccountAbstractSetupActivity {
|
|||
}
|
||||
|
||||
public void signIn(String email, String password) {
|
||||
String serverURI = FxAccountConstants.DEFAULT_AUTH_SERVER_ENDPOINT;
|
||||
String serverURI = getAuthServerEndpoint();
|
||||
PasswordStretcher passwordStretcher = makePasswordStretcher(password);
|
||||
// This delegate creates a new Android account on success, opens the
|
||||
// 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.sync.FxAccountSyncStatusHelper;
|
||||
import org.mozilla.gecko.fxa.tasks.FxAccountCodeResender;
|
||||
import org.mozilla.gecko.sync.ExtendedJSONObject;
|
||||
import org.mozilla.gecko.sync.SharedPreferencesClientsDataDelegate;
|
||||
import org.mozilla.gecko.sync.SyncConfiguration;
|
||||
|
||||
|
@ -56,7 +57,17 @@ public class FxAccountStatusFragment
|
|||
// collection.
|
||||
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 authServerPreference;
|
||||
|
||||
protected Preference needsPasswordPreference;
|
||||
protected Preference needsUpgradePreference;
|
||||
|
@ -72,6 +83,7 @@ public class FxAccountStatusFragment
|
|||
protected CheckBoxPreference passwordsPreference;
|
||||
|
||||
protected EditTextPreference deviceNamePreference;
|
||||
protected Preference syncServerPreference;
|
||||
|
||||
protected volatile AndroidFxAccount fxAccount;
|
||||
// The contract is: when fxAccount is non-null, then clientsDataDelegate is
|
||||
|
@ -105,7 +117,9 @@ public class FxAccountStatusFragment
|
|||
protected void addPreferences() {
|
||||
addPreferencesFromResource(R.xml.fxaccount_status_prefscreen);
|
||||
|
||||
accountCategory = (PreferenceCategory) ensureFindPreference("signed_in_as_category");
|
||||
emailPreference = ensureFindPreference("email");
|
||||
authServerPreference = ensureFindPreference("auth_server");
|
||||
|
||||
needsPasswordPreference = ensureFindPreference("needs_credentials");
|
||||
needsUpgradePreference = ensureFindPreference("needs_upgrade");
|
||||
|
@ -124,6 +138,8 @@ public class FxAccountStatusFragment
|
|||
removeDebugButtons();
|
||||
} else {
|
||||
connectDebugButtons();
|
||||
ALWAYS_SHOW_AUTH_SERVER = true;
|
||||
ALWAYS_SHOW_SYNC_SERVER = true;
|
||||
}
|
||||
|
||||
needsPasswordPreference.setOnPreferenceClickListener(this);
|
||||
|
@ -137,6 +153,8 @@ public class FxAccountStatusFragment
|
|||
|
||||
deviceNamePreference = (EditTextPreference) ensureFindPreference("device_name");
|
||||
deviceNamePreference.setOnPreferenceChangeListener(this);
|
||||
|
||||
syncServerPreference = ensureFindPreference("sync_server");
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -152,6 +170,10 @@ public class FxAccountStatusFragment
|
|||
public boolean onPreferenceClick(Preference preference) {
|
||||
if (preference == needsPasswordPreference) {
|
||||
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
|
||||
// the soft keyboard not being shown for the started activity. Why, Android, why?
|
||||
intent.setFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION);
|
||||
|
@ -190,6 +212,17 @@ public class FxAccountStatusFragment
|
|||
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) {
|
||||
bookmarksPreference.setEnabled(enabled);
|
||||
historyPreference.setEnabled(enabled);
|
||||
|
@ -367,6 +400,8 @@ public class FxAccountStatusFragment
|
|||
}
|
||||
|
||||
emailPreference.setTitle(fxAccount.getEmail());
|
||||
updateAuthServerPreference();
|
||||
updateSyncServerPreference();
|
||||
|
||||
try {
|
||||
// There are error states determined by Android, not the login state
|
||||
|
@ -417,6 +452,38 @@ public class FxAccountStatusFragment
|
|||
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
|
||||
* accordingly.
|
||||
|
|
|
@ -77,6 +77,8 @@ public class FxAccountUpdateCredentialsActivity extends FxAccountAbstractSetupAc
|
|||
|
||||
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);
|
||||
|
||||
updateFromIntentExtras();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -56,18 +56,18 @@ public class AccountPickler {
|
|||
|
||||
public static final long PICKLE_VERSION = 2;
|
||||
|
||||
private static final String KEY_PICKLE_VERSION = "pickle_version";
|
||||
private static final String KEY_PICKLE_TIMESTAMP = "pickle_timestamp";
|
||||
public static final String KEY_PICKLE_VERSION = "pickle_version";
|
||||
public static final String KEY_PICKLE_TIMESTAMP = "pickle_timestamp";
|
||||
|
||||
private static final String KEY_ACCOUNT_VERSION = "account_version";
|
||||
private static final String KEY_ACCOUNT_TYPE = "account_type";
|
||||
private static final String KEY_EMAIL = "email";
|
||||
private static final String KEY_PROFILE = "profile";
|
||||
private static final String KEY_IDP_SERVER_URI = "idpServerURI";
|
||||
private static final String KEY_TOKEN_SERVER_URI = "tokenServerURI";
|
||||
private static final String KEY_IS_SYNCING_ENABLED = "isSyncingEnabled";
|
||||
public static final String KEY_ACCOUNT_VERSION = "account_version";
|
||||
public static final String KEY_ACCOUNT_TYPE = "account_type";
|
||||
public static final String KEY_EMAIL = "email";
|
||||
public static final String KEY_PROFILE = "profile";
|
||||
public static final String KEY_IDP_SERVER_URI = "idpServerURI";
|
||||
public static final String KEY_TOKEN_SERVER_URI = "tokenServerURI";
|
||||
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.
|
||||
|
@ -80,16 +80,10 @@ public class AccountPickler {
|
|||
return context.deleteFile(filename);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
public static ExtendedJSONObject toJSON(final AndroidFxAccount account, final long now) {
|
||||
final ExtendedJSONObject o = new ExtendedJSONObject();
|
||||
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_TYPE, FxAccountConstants.ACCOUNT_TYPE);
|
||||
|
@ -104,10 +98,21 @@ public class AccountPickler {
|
|||
final ExtendedJSONObject bundle = account.unbundle();
|
||||
if (bundle == null) {
|
||||
Logger.warn(LOG_TAG, "Unable to obtain account bundle; aborting.");
|
||||
return;
|
||||
return null;
|
||||
}
|
||||
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);
|
||||
}
|
||||
|
||||
|
|
|
@ -323,6 +323,9 @@ public class AndroidFxAccount {
|
|||
if (email == null) {
|
||||
throw new IllegalArgumentException("email must not be null");
|
||||
}
|
||||
if (profile == null) {
|
||||
throw new IllegalArgumentException("profile must not be null");
|
||||
}
|
||||
if (idpServerURI == null) {
|
||||
throw new IllegalArgumentException("idpServerURI must not be null");
|
||||
}
|
||||
|
@ -368,6 +371,15 @@ public class AndroidFxAccount {
|
|||
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);
|
||||
|
||||
if (!fromPickle) {
|
||||
|
|
|
@ -5,23 +5,81 @@
|
|||
|
||||
package org.mozilla.gecko.home;
|
||||
|
||||
import org.mozilla.gecko.R;
|
||||
import org.mozilla.gecko.widget.IconTabWidget;
|
||||
import java.util.Date;
|
||||
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.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.View;
|
||||
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
|
||||
private static final String LOGTAG = "GeckoHistoryPanel";
|
||||
private IconTabWidget mTabWidget;
|
||||
private int mSelectedTab;
|
||||
private boolean initializeRecentPanel;
|
||||
|
||||
// Cursor loader ID for history query
|
||||
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
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||
|
@ -30,75 +88,335 @@ public class HistoryPanel extends HomeFragment
|
|||
|
||||
@Override
|
||||
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);
|
||||
mTabWidget.addTab(R.drawable.icon_last_tabs, R.string.home_last_tabs_title);
|
||||
Telemetry.sendUIEvent(TelemetryContract.Event.LOAD_URL);
|
||||
|
||||
mTabWidget.setTabSelectionListener(this);
|
||||
mTabWidget.setCurrentTab(mSelectedTab);
|
||||
// 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 HistoryAdapter(getActivity());
|
||||
mList.setAdapter(mAdapter);
|
||||
|
||||
// Create callbacks before the initial loader is started
|
||||
mCursorLoaderCallbacks = new CursorLoaderCallbacks();
|
||||
loadIfVisible();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void load() {
|
||||
// Show most recent panel as the initial panel.
|
||||
// Since we detach/attach on config change, this prevents from replacing current fragment.
|
||||
if (!initializeRecentPanel) {
|
||||
showMostRecentPanel();
|
||||
initializeRecentPanel = true;
|
||||
protected void load() {
|
||||
getLoaderManager().initLoader(LOADER_ID_HISTORY, null, mCursorLoaderCallbacks);
|
||||
}
|
||||
|
||||
private static class HistoryCursorLoader extends SimpleCursorLoader {
|
||||
// Max number of history results
|
||||
private static final int HISTORY_LIMIT = 100;
|
||||
|
||||
public HistoryCursorLoader(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTabChanged(int index) {
|
||||
if (index == mSelectedTab) {
|
||||
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;
|
||||
}
|
||||
|
||||
if (index == 0) {
|
||||
showMostRecentPanel();
|
||||
} else if (index == 1) {
|
||||
showLastTabsPanel();
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
|
||||
mTabWidget.setCurrentTab(index);
|
||||
mSelectedTab = index;
|
||||
private static class HistoryAdapter 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 HistoryAdapter(Context context) {
|
||||
super(context, null, VIEW_TYPES, LAYOUT_TYPES);
|
||||
|
||||
mContext = context;
|
||||
|
||||
// Initialize map of history sections
|
||||
mMostRecentSections = new SparseArray<MostRecentSection>();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onConfigurationChanged(Configuration newConfig) {
|
||||
super.onConfigurationChanged(newConfig);
|
||||
public Object getItem(int position) {
|
||||
final int type = getItemViewType(position);
|
||||
|
||||
// Rotation should detach and re-attach to use a different layout.
|
||||
if (isVisible()) {
|
||||
getFragmentManager().beginTransaction()
|
||||
.detach(this)
|
||||
.attach(this)
|
||||
.commitAllowingStateLoss();
|
||||
// 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 void showSubPanel(Fragment subPanel) {
|
||||
final Bundle args = new Bundle();
|
||||
args.putBoolean(HomePager.CAN_LOAD_ARG, getCanLoadHint());
|
||||
subPanel.setArguments(args);
|
||||
|
||||
getChildFragmentManager().beginTransaction()
|
||||
.addToBackStack(null).replace(R.id.history_panel_container, subPanel)
|
||||
.commitAllowingStateLoss();
|
||||
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);
|
||||
}
|
||||
|
||||
private void showMostRecentPanel() {
|
||||
final MostRecentPanel mostRecentPanel = MostRecentPanel.newInstance();
|
||||
showSubPanel(mostRecentPanel);
|
||||
throw new IllegalStateException("Unrecognized history section");
|
||||
}
|
||||
|
||||
private void showLastTabsPanel() {
|
||||
final LastTabsPanel lastTabsPanel = LastTabsPanel.newInstance();
|
||||
showSubPanel(lastTabsPanel);
|
||||
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 class CursorLoaderCallbacks implements LoaderCallbacks<Cursor> {
|
||||
@Override
|
||||
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
|
||||
return new HistoryCursorLoader(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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -40,6 +40,7 @@ public final class HomeConfig {
|
|||
BOOKMARKS("bookmarks", BookmarksPanel.class),
|
||||
HISTORY("history", HistoryPanel.class),
|
||||
READING_LIST("reading_list", ReadingListPanel.class),
|
||||
RECENT_TABS("recent_tabs", RecentTabsPanel.class),
|
||||
DYNAMIC("dynamic", DynamicPanel.class);
|
||||
|
||||
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 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 RECENT_TABS_PANEL_ID = "5c2601a5-eedc-4477-b297-ce4cef52adf8";
|
||||
|
||||
private final HomeConfigBackend mBackend;
|
||||
|
||||
|
@ -1550,6 +1552,11 @@ public final class HomeConfig {
|
|||
id = READING_LIST_PANEL_ID;
|
||||
break;
|
||||
|
||||
case RECENT_TABS:
|
||||
titleId = R.string.recent_tabs_title;
|
||||
id = RECENT_TABS_PANEL_ID;
|
||||
break;
|
||||
|
||||
case DYNAMIC:
|
||||
throw new IllegalArgumentException("createBuiltinPanelConfig() is only for built-in panels");
|
||||
}
|
||||
|
|
|
@ -36,7 +36,19 @@ import android.util.Log;
|
|||
class HomeConfigPrefsBackend implements HomeConfigBackend {
|
||||
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 RELOAD_BROADCAST = "HomeConfigPrefsBackend:Reload";
|
||||
|
@ -45,6 +57,8 @@ class HomeConfigPrefsBackend implements HomeConfigBackend {
|
|||
private ReloadBroadcastReceiver mReloadBroadcastReceiver;
|
||||
private OnReloadListener mReloadListener;
|
||||
|
||||
private static boolean sMigrationDone = false;
|
||||
|
||||
public HomeConfigPrefsBackend(Context context) {
|
||||
mContext = context;
|
||||
}
|
||||
|
@ -68,22 +82,109 @@ class HomeConfigPrefsBackend implements HomeConfigBackend {
|
|||
}
|
||||
|
||||
final PanelConfig historyEntry = createBuiltinPanelConfig(mContext, PanelType.HISTORY);
|
||||
final PanelConfig recentTabsEntry = createBuiltinPanelConfig(mContext, PanelType.RECENT_TABS);
|
||||
|
||||
// On tablets, the history panel is the last.
|
||||
// On phones, the history panel is the first one.
|
||||
if (HardwareUtils.isTablet()) {
|
||||
panelConfigs.add(historyEntry);
|
||||
panelConfigs.add(recentTabsEntry);
|
||||
} else {
|
||||
panelConfigs.add(0, historyEntry);
|
||||
panelConfigs.add(0, recentTabsEntry);
|
||||
}
|
||||
|
||||
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) {
|
||||
final JSONArray jsonPanelConfigs;
|
||||
try {
|
||||
jsonPanelConfigs = new JSONArray(jsonString);
|
||||
jsonPanelConfigs = maybePerformMigration(mContext, jsonString);
|
||||
} catch (JSONException e) {
|
||||
Log.e(LOGTAG, "Error loading the list of home panels from JSON prefs", e);
|
||||
|
||||
|
@ -110,7 +211,9 @@ class HomeConfigPrefsBackend implements HomeConfigBackend {
|
|||
@Override
|
||||
public State load() {
|
||||
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;
|
||||
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());
|
||||
|
|
|
@ -68,8 +68,7 @@ public class HomePager extends ViewPager {
|
|||
static final String LIST_TAG_BOOKMARKS = "bookmarks";
|
||||
static final String LIST_TAG_READING_LIST = "reading_list";
|
||||
static final String LIST_TAG_TOP_SITES = "top_sites";
|
||||
static final String LIST_TAG_MOST_RECENT = "most_recent";
|
||||
static final String LIST_TAG_LAST_TABS = "last_tabs";
|
||||
static final String LIST_TAG_RECENT_TABS = "recent_tabs";
|
||||
static final String LIST_TAG_BROWSER_SEARCH = "browser_search";
|
||||
|
||||
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;
|
||||
|
||||
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.R;
|
||||
import org.mozilla.gecko.SessionParser;
|
||||
import org.mozilla.gecko.Telemetry;
|
||||
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.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.content.Context;
|
||||
import android.content.res.Configuration;
|
||||
import android.database.Cursor;
|
||||
import android.database.MatrixCursor;
|
||||
import android.database.MatrixCursor.RowBuilder;
|
||||
|
@ -23,6 +32,7 @@ import android.os.Bundle;
|
|||
import android.support.v4.app.LoaderManager.LoaderCallbacks;
|
||||
import android.support.v4.content.Loader;
|
||||
import android.support.v4.widget.CursorAdapter;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
@ -34,25 +44,20 @@ import android.widget.TextView;
|
|||
/**
|
||||
* 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
|
||||
private static final String LOGTAG = "GeckoLastTabsPanel";
|
||||
private static final String LOGTAG = "GeckoRecentTabsPanel";
|
||||
|
||||
// Cursor loader ID for the session parser
|
||||
private static final int LOADER_ID_LAST_TABS = 0;
|
||||
// Cursor loader ID for the loader that loads recent tabs
|
||||
private static final int LOADER_ID_RECENT_TABS = 0;
|
||||
|
||||
// Adapter for the list of search results
|
||||
private LastTabsAdapter mAdapter;
|
||||
// Adapter for the list of recent tabs.
|
||||
private RecentTabsAdapter mAdapter;
|
||||
|
||||
// The view shown by the fragment.
|
||||
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.
|
||||
private View mEmptyView;
|
||||
|
||||
|
@ -62,12 +67,25 @@ public class LastTabsPanel extends HomeFragment {
|
|||
// On new tabs listener
|
||||
private OnNewTabsListener mNewTabsListener;
|
||||
|
||||
public static LastTabsPanel newInstance() {
|
||||
return new LastTabsPanel();
|
||||
// Recently closed tabs from gecko
|
||||
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() {
|
||||
mNewTabsListener = null;
|
||||
public static final class RecentTabs implements URLColumns, CommonColumns {
|
||||
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
|
||||
|
@ -91,18 +109,13 @@ public class LastTabsPanel extends HomeFragment {
|
|||
|
||||
@Override
|
||||
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
|
||||
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.setTag(HomePager.LIST_TAG_LAST_TABS);
|
||||
mList.setTag(HomePager.LIST_TAG_RECENT_TABS);
|
||||
|
||||
mList.setOnItemClickListener(new AdapterView.OnItemClickListener() {
|
||||
@Override
|
||||
|
@ -114,7 +127,7 @@ public class LastTabsPanel extends HomeFragment {
|
|||
|
||||
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 });
|
||||
}
|
||||
});
|
||||
|
@ -123,30 +136,39 @@ public class LastTabsPanel extends HomeFragment {
|
|||
@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.url = cursor.getString(cursor.getColumnIndexOrThrow(RecentTabs.URL));
|
||||
info.title = cursor.getString(cursor.getColumnIndexOrThrow(RecentTabs.TITLE));
|
||||
return info;
|
||||
}
|
||||
});
|
||||
|
||||
registerForContextMenu(mList);
|
||||
|
||||
mRestoreButton = view.findViewById(R.id.open_all_tabs_button);
|
||||
mRestoreButton.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
openAllTabs();
|
||||
}
|
||||
});
|
||||
EventDispatcher.getInstance().registerGeckoThreadListener(this, "ClosedTabs:Data");
|
||||
GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("ClosedTabs:StartNotifications", null));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroyView() {
|
||||
super.onDestroyView();
|
||||
mList = null;
|
||||
mTitle = 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
|
||||
|
@ -154,7 +176,7 @@ public class LastTabsPanel extends HomeFragment {
|
|||
super.onActivityCreated(savedInstanceState);
|
||||
|
||||
// Intialize adapter
|
||||
mAdapter = new LastTabsAdapter(getActivity());
|
||||
mAdapter = new RecentTabsAdapter(getActivity());
|
||||
mList.setAdapter(mAdapter);
|
||||
|
||||
// Create callbacks before the initial loader is started
|
||||
|
@ -164,20 +186,9 @@ public class LastTabsPanel extends HomeFragment {
|
|||
|
||||
private void updateUiFromCursor(Cursor c) {
|
||||
if (c != null && c.getCount() > 0) {
|
||||
if (mTitle != null) {
|
||||
mTitle.setVisibility(View.VISIBLE);
|
||||
}
|
||||
mRestoreButton.setVisibility(View.VISIBLE);
|
||||
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) {
|
||||
// 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);
|
||||
|
@ -195,7 +206,32 @@ public class LastTabsPanel extends HomeFragment {
|
|||
|
||||
@Override
|
||||
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() {
|
||||
|
@ -207,7 +243,7 @@ public class LastTabsPanel extends HomeFragment {
|
|||
final String[] urls = new String[c.getCount()];
|
||||
|
||||
do {
|
||||
urls[c.getPosition()] = c.getString(c.getColumnIndexOrThrow(Combined.URL));
|
||||
urls[c.getPosition()] = c.getString(c.getColumnIndexOrThrow(RecentTabs.URL));
|
||||
} while (c.moveToNext());
|
||||
|
||||
Telemetry.sendUIEvent(TelemetryContract.Event.LOAD_URL, TelemetryContract.Method.BUTTON);
|
||||
|
@ -215,24 +251,52 @@ public class LastTabsPanel extends HomeFragment {
|
|||
mNewTabsListener.onNewTabs(urls);
|
||||
}
|
||||
|
||||
private static class LastTabsCursorLoader extends SimpleCursorLoader {
|
||||
public LastTabsCursorLoader(Context context) {
|
||||
private static class RecentTabsCursorLoader extends SimpleCursorLoader {
|
||||
private final ClosedTab[] closedTabs;
|
||||
|
||||
public RecentTabsCursorLoader(Context context, ClosedTab[] closedTabs) {
|
||||
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
|
||||
public Cursor loadCursor() {
|
||||
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);
|
||||
if (jsonString == null) {
|
||||
// No previous session data
|
||||
return null;
|
||||
return c;
|
||||
}
|
||||
|
||||
final MatrixCursor c = new MatrixCursor(new String[] { Combined._ID,
|
||||
Combined.URL,
|
||||
Combined.TITLE });
|
||||
final int count = c.getCount();
|
||||
|
||||
new SessionParser() {
|
||||
@Override
|
||||
|
@ -244,12 +308,12 @@ public class LastTabsPanel extends HomeFragment {
|
|||
return;
|
||||
}
|
||||
|
||||
final RowBuilder row = c.newRow();
|
||||
row.add(-1);
|
||||
row.add(url);
|
||||
// If this is the first tab we're reading, add a header.
|
||||
if (c.getCount() == count) {
|
||||
addRow(c, null, context.getString(R.string.home_last_tabs_title), RecentTabs.TYPE_HEADER);
|
||||
}
|
||||
|
||||
final String title = tab.getTitle();
|
||||
row.add(title);
|
||||
addRow(c, url, tab.getTitle(), RecentTabs.TYPE_LAST_TIME);
|
||||
}
|
||||
}.parse(jsonString);
|
||||
|
||||
|
@ -257,26 +321,52 @@ public class LastTabsPanel extends HomeFragment {
|
|||
}
|
||||
}
|
||||
|
||||
private static class LastTabsAdapter extends CursorAdapter {
|
||||
public LastTabsAdapter(Context context) {
|
||||
super(context, null, 0);
|
||||
private static class RecentTabsAdapter 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 };
|
||||
|
||||
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
|
||||
public void bindView(View view, Context context, Cursor cursor) {
|
||||
((TwoLinePageRow) view).updateFromCursor(cursor);
|
||||
}
|
||||
public void bindView(View view, Context context, int position) {
|
||||
final int itemType = getItemViewType(position);
|
||||
final Cursor c = getCursor(position);
|
||||
|
||||
@Override
|
||||
public View newView(Context context, Cursor cursor, ViewGroup parent) {
|
||||
return LayoutInflater.from(context).inflate(R.layout.home_item_row, parent, false);
|
||||
if (itemType == ROW_HEADER) {
|
||||
final String title = c.getString(c.getColumnIndexOrThrow(RecentTabs.TITLE));
|
||||
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> {
|
||||
@Override
|
||||
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
|
||||
return new LastTabsCursorLoader(getActivity());
|
||||
return new RecentTabsCursorLoader(getActivity(), mClosedTabs);
|
||||
}
|
||||
|
||||
@Override
|
|
@ -11,6 +11,7 @@
|
|||
<!ENTITY bookmarks_title "Bookmarks">
|
||||
<!ENTITY history_title "History">
|
||||
<!ENTITY reading_list_title "Reading List">
|
||||
<!ENTITY recent_tabs_title "Recent Tabs">
|
||||
|
||||
<!ENTITY switch_to_tab "Switch to tab">
|
||||
|
||||
|
@ -349,10 +350,10 @@ size. -->
|
|||
<!ENTITY home_clear_history_button "Clear browsing 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_closed_tabs_title "Recently closed tabs">
|
||||
<!ENTITY home_last_tabs_title "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_most_recent_title "Most recent">
|
||||
<!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.">
|
||||
<!-- 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_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
|
||||
user for an email address and password. Hide and show are button
|
||||
labels. -->
|
||||
|
@ -175,7 +178,9 @@
|
|||
|
||||
<!ENTITY fxaccount_status_header2 'Firefox Account'>
|
||||
<!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_sync_server 'Sync server'>
|
||||
<!ENTITY fxaccount_status_sync '&syncBrand.shortName.label;'>
|
||||
<!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.'>
|
||||
|
|
|
@ -272,8 +272,6 @@ gbjar.sources += [
|
|||
'home/HomePagerTabStrip.java',
|
||||
'home/HomePanelPicker.java',
|
||||
'home/HomePanelsManager.java',
|
||||
'home/LastTabsPanel.java',
|
||||
'home/MostRecentPanel.java',
|
||||
'home/MultiTypeCursorAdapter.java',
|
||||
'home/PanelAuthCache.java',
|
||||
'home/PanelAuthLayout.java',
|
||||
|
@ -289,6 +287,7 @@ gbjar.sources += [
|
|||
'home/PinSiteDialog.java',
|
||||
'home/ReadingListPanel.java',
|
||||
'home/ReadingListRow.java',
|
||||
'home/RecentTabsPanel.java',
|
||||
'home/SearchEngine.java',
|
||||
'home/SearchEngineRow.java',
|
||||
'home/SearchLoader.java',
|
||||
|
|
|
@ -4,15 +4,21 @@
|
|||
|
||||
package org.mozilla.gecko.preferences;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.text.Collator;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.Locale;
|
||||
import java.util.Set;
|
||||
|
||||
import org.mozilla.gecko.BrowserLocaleManager;
|
||||
import org.mozilla.gecko.R;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Paint;
|
||||
import android.preference.ListPreference;
|
||||
import android.text.TextUtils;
|
||||
import android.util.AttributeSet;
|
||||
|
@ -21,7 +27,51 @@ import android.util.Log;
|
|||
public class LocaleListPreference extends ListPreference {
|
||||
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 final CharacterValidator characterValidator;
|
||||
|
||||
public LocaleListPreference(Context context) {
|
||||
this(context, null);
|
||||
|
@ -29,6 +79,10 @@ public class LocaleListPreference extends ListPreference {
|
|||
|
||||
public LocaleListPreference(Context context, AttributeSet attributes) {
|
||||
super(context, attributes);
|
||||
|
||||
// Thus far, missing glyphs are replaced by whitespace, not a box
|
||||
// or other Unicode codepoint.
|
||||
this.characterValidator = new CharacterValidator(" ");
|
||||
buildList();
|
||||
}
|
||||
|
||||
|
@ -86,9 +140,54 @@ public class LocaleListPreference extends ListPreference {
|
|||
// We sort by name, so we use Collator.
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
private LocaleDescriptor[] getShippingLocales() {
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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());
|
||||
|
||||
// Future: single-locale builds should be specified, too.
|
||||
|
@ -97,15 +196,22 @@ public class LocaleListPreference extends ListPreference {
|
|||
return new LocaleDescriptor[] { new LocaleDescriptor(fallbackTag) };
|
||||
}
|
||||
|
||||
final int count = shippingLocales.size();
|
||||
final LocaleDescriptor[] descriptors = new LocaleDescriptor[count];
|
||||
|
||||
int i = 0;
|
||||
final int initialCount = shippingLocales.size();
|
||||
final Set<LocaleDescriptor> locales = new HashSet<LocaleDescriptor>(initialCount);
|
||||
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;
|
||||
}
|
||||
|
||||
Arrays.sort(descriptors, 0, count);
|
||||
locales.add(descriptor);
|
||||
}
|
||||
|
||||
final int usableCount = locales.size();
|
||||
final LocaleDescriptor[] descriptors = locales.toArray(new LocaleDescriptor[usableCount]);
|
||||
Arrays.sort(descriptors, 0, usableCount);
|
||||
return descriptors;
|
||||
}
|
||||
|
||||
|
@ -154,7 +260,7 @@ public class LocaleListPreference extends ListPreference {
|
|||
return;
|
||||
}
|
||||
|
||||
final LocaleDescriptor[] descriptors = getShippingLocales();
|
||||
final LocaleDescriptor[] descriptors = getUsableLocales();
|
||||
final int count = descriptors.length;
|
||||
|
||||
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/.
|
||||
-->
|
||||
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="fill_parent"
|
||||
android:fillViewport="true" >
|
||||
|
||||
<LinearLayout style="@style/FxAccountMiddle" >
|
||||
|
|
|
@ -5,8 +5,8 @@
|
|||
- file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
-->
|
||||
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="fill_parent"
|
||||
android:fillViewport="true" >
|
||||
|
||||
<LinearLayout style="@style/FxAccountMiddle" >
|
||||
|
|
|
@ -6,8 +6,8 @@
|
|||
-->
|
||||
|
||||
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="fill_parent"
|
||||
android:fillViewport="true" >
|
||||
|
||||
<LinearLayout
|
||||
|
@ -20,6 +20,8 @@
|
|||
style="@style/FxAccountHeaderItem"
|
||||
android:text="@string/fxaccount_create_account_header" />
|
||||
|
||||
<include layout="@layout/fxaccount_custom_server_view" />
|
||||
|
||||
<include layout="@layout/fxaccount_email_password_view" />
|
||||
|
||||
<TextView
|
||||
|
|
|
@ -5,8 +5,8 @@
|
|||
- file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
-->
|
||||
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="fill_parent"
|
||||
android:fillViewport="true" >
|
||||
|
||||
<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" >
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical" >
|
||||
|
||||
|
@ -25,14 +25,14 @@
|
|||
</AutoCompleteTextView>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal" >
|
||||
|
||||
<EditText
|
||||
android:id="@+id/password"
|
||||
style="@style/FxAccountEditItem"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:background="@drawable/fxaccount_password_background"
|
||||
|
@ -51,22 +51,22 @@
|
|||
happy. Be thankful there are not three buttons! -->
|
||||
<FrameLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_height="fill_parent"
|
||||
android:layout_weight="0"
|
||||
android:orientation="horizontal" >
|
||||
|
||||
<Button
|
||||
android:id="@+id/show_password"
|
||||
style="@style/FxAccountShowHidePasswordButton"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="fill_parent"
|
||||
android:text="@string/fxaccount_password_show" >
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
style="@style/FxAccountShowHidePasswordButton"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_height="fill_parent"
|
||||
android:text="@string/fxaccount_password_show"
|
||||
android:visibility="invisible" >
|
||||
</Button>
|
||||
|
@ -74,7 +74,7 @@
|
|||
<Button
|
||||
style="@style/FxAccountShowHidePasswordButton"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_height="fill_parent"
|
||||
android:text="@string/fxaccount_password_hide"
|
||||
android:visibility="invisible" >
|
||||
</Button>
|
||||
|
|
|
@ -5,8 +5,8 @@
|
|||
- file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
-->
|
||||
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="fill_parent"
|
||||
android:fillViewport="true" >
|
||||
|
||||
<LinearLayout
|
||||
|
|
|
@ -19,12 +19,12 @@
|
|||
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:orientation="vertical"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="fill_parent"
|
||||
android:layout_width="fill_parent"
|
||||
android:background="@android:color/transparent">
|
||||
|
||||
<ListView android:id="@android:id/list"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="0px"
|
||||
android:layout_weight="1"
|
||||
android:paddingTop="0dip"
|
||||
|
|
|
@ -6,8 +6,8 @@
|
|||
-->
|
||||
|
||||
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="fill_parent"
|
||||
android:fillViewport="true" >
|
||||
|
||||
<LinearLayout
|
||||
|
@ -20,6 +20,8 @@
|
|||
style="@style/FxAccountHeaderItem"
|
||||
android:text="@string/fxaccount_sign_in_sub_header" />
|
||||
|
||||
<include layout="@layout/fxaccount_custom_server_view" />
|
||||
|
||||
<include layout="@layout/fxaccount_email_password_view" />
|
||||
|
||||
<TextView
|
||||
|
@ -39,7 +41,7 @@
|
|||
</RelativeLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="10dp"
|
||||
android:orientation="horizontal" >
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<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:background="@color/fxaccount_error_preference_backgroundcolor"
|
||||
android:gravity="center_vertical"
|
||||
|
@ -35,7 +35,7 @@
|
|||
<TextView
|
||||
android:id="@+android:id/title"
|
||||
style="@style/FxAccountTextItem"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:gravity="center_vertical" >
|
||||
|
|
|
@ -6,8 +6,8 @@
|
|||
-->
|
||||
|
||||
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="fill_parent"
|
||||
android:fillViewport="true" >
|
||||
|
||||
<LinearLayout
|
||||
|
@ -20,6 +20,8 @@
|
|||
style="@style/FxAccountHeaderItem"
|
||||
android:text="@string/fxaccount_update_credentials_header" />
|
||||
|
||||
<include layout="@layout/fxaccount_custom_server_view" />
|
||||
|
||||
<include layout="@layout/fxaccount_email_password_view" />
|
||||
|
||||
<TextView
|
||||
|
@ -41,6 +43,7 @@
|
|||
<TextView
|
||||
android:id="@+id/forgot_password_link"
|
||||
style="@style/FxAccountLinkifiedItem"
|
||||
android:layout_marginTop="10dp"
|
||||
android:text="@string/fxaccount_sign_in_forgot_password" />
|
||||
|
||||
<LinearLayout style="@style/FxAccountSpacer" />
|
||||
|
|
|
@ -8,17 +8,18 @@
|
|||
android:layout_height="match_parent"
|
||||
android:orientation="vertical">
|
||||
|
||||
<FrameLayout android:id="@+id/history_panel_container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1" />
|
||||
<include layout="@layout/home_history_list"/>
|
||||
|
||||
<org.mozilla.gecko.widget.IconTabWidget android:id="@+id/tab_icon_widget"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="@dimen/browser_toolbar_height"
|
||||
android:tabStripEnabled="false"
|
||||
android:showDividers="none"
|
||||
android:background="@color/background_light"
|
||||
android:layout="@layout/home_history_tabs_indicator"/>
|
||||
<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>
|
||||
|
|
|
@ -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"/>
|
||||
|
||||
<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>
|
|
@ -5,7 +5,7 @@
|
|||
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:gravity="center_vertical"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:minHeight="?android:attr/listPreferredItemHeight"
|
||||
android:orientation="horizontal"
|
||||
|
|
|
@ -11,20 +11,20 @@
|
|||
android:text="@string/sync_title_send_tab" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:padding="@dimen/SyncSpace" >
|
||||
|
||||
<TextView
|
||||
android:id="@+id/title"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:textAppearance="?android:attr/textAppearanceMedium"
|
||||
android:text="@string/sync_title_send_tab" />
|
||||
<TextView
|
||||
android:id="@+id/uri"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:textAppearance="?android:attr/textAppearanceSmall"
|
||||
android:text="@string/sync_title_send_tab" />
|
||||
|
|
|
@ -19,7 +19,7 @@
|
|||
<ProgressBar
|
||||
android:id="@+id/waiting_content1"
|
||||
style="@style/Widget.ProgressBar.Horizontal"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:indeterminateOnly="true"
|
||||
android:padding="@dimen/SyncSpace" />
|
||||
|
|
|
@ -6,6 +6,6 @@
|
|||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
style="@style/SyncLayout" >
|
||||
<WebView android:id="@+id/web_engine"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent" />
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="fill_parent" />
|
||||
</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. -->
|
||||
<style name="SyncTop" parent="@android:style/Widget.Holo.ActionBar">
|
||||
<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:gravity">center_vertical|left</item>
|
||||
<item name="android:drawableLeft">@drawable/icon</item>
|
||||
|
@ -17,12 +17,12 @@
|
|||
|
||||
<!-- Bottom bar: a horizontal linear layout with buttons in it. -->
|
||||
<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>
|
||||
</style>
|
||||
|
||||
<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_weight">1</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_panel_indicator_width">60dp</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>
|
||||
|
||||
</resources>
|
||||
|
|
|
@ -82,12 +82,6 @@
|
|||
<dimen name="url_bar_offset_left">32dp</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 -->
|
||||
<dimen name="page_action_button_width">32dp</dimen>
|
||||
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
<style name="FxAccountMiddle">
|
||||
<item name="android:background">@android:color/white</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_weight">1</item>
|
||||
<item name="android:paddingTop">30dp</item>
|
||||
|
@ -27,7 +27,7 @@
|
|||
|
||||
<style name="FxAccountSpacer">
|
||||
<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_weight">1</item>
|
||||
</style>
|
||||
|
@ -39,7 +39,7 @@
|
|||
|
||||
<style name="FxAccountTextItem" parent="@android:style/TextAppearance.Medium">
|
||||
<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:gravity">center_horizontal</item>
|
||||
<item name="android:textSize">16sp</item>
|
||||
|
@ -57,7 +57,7 @@
|
|||
<item name="android:textColor">@drawable/fxaccount_button_color</item>
|
||||
<item name="android:textSize">24sp</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_marginBottom">10dp</item>
|
||||
</style>
|
||||
|
@ -81,7 +81,7 @@
|
|||
<item name="android:focusable">true</item>
|
||||
<item name="android:textColor">@color/fxaccount_linkified_textColor</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:gravity">center</item>
|
||||
</style>
|
||||
|
@ -95,7 +95,7 @@
|
|||
</style>
|
||||
|
||||
<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_marginTop">10dp</item>
|
||||
<item name="android:layout_height">wrap_content</item>
|
||||
|
@ -118,13 +118,13 @@
|
|||
|
||||
<style name="FxAccountButtonLayout">
|
||||
<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:background">@drawable/fxaccount_button_background</item>
|
||||
</style>
|
||||
|
||||
<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_marginBottom">10dp</item>
|
||||
<item name="android:textColor">@drawable/fxaccount_checkbox_textcolor</item>
|
||||
|
|
|
@ -5,8 +5,8 @@
|
|||
|
||||
<resources>
|
||||
<style name="SyncLayout">
|
||||
<item name="android:layout_width">match_parent</item>
|
||||
<item name="android:layout_height">match_parent</item>
|
||||
<item name="android:layout_width">fill_parent</item>
|
||||
<item name="android:layout_height">fill_parent</item>
|
||||
</style>
|
||||
<style name="SyncLayout.Vertical" parent="@style/SyncLayout">
|
||||
<item name="android:orientation">vertical</item>
|
||||
|
@ -17,14 +17,14 @@
|
|||
|
||||
<!-- TextView Styles -->
|
||||
<style name="SyncTextFrame" parent="@style/TextAppearance">
|
||||
<item name="android:layout_width">match_parent</item>
|
||||
<item name="android:layout_height">match_parent</item>
|
||||
<item name="android:layout_width">fill_parent</item>
|
||||
<item name="android:layout_height">fill_parent</item>
|
||||
<item name="android:layout_gravity">center</item>
|
||||
<item name="android:padding">@dimen/SyncSpace</item>
|
||||
<item name="android:orientation">vertical</item>
|
||||
</style>
|
||||
<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:textSize">15dp</item>
|
||||
</style>
|
||||
|
@ -44,7 +44,7 @@
|
|||
</style>
|
||||
<!-- EditView Styles -->
|
||||
<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:singleLine">true</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. -->
|
||||
<style name="SyncTop">
|
||||
<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:gravity">center_vertical|left</item>
|
||||
<item name="android:drawableLeft">@drawable/icon</item>
|
||||
|
@ -80,7 +80,7 @@
|
|||
|
||||
<!-- Middle scroller: a scroll view with content in it. -->
|
||||
<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_weight">1</item>
|
||||
<item name="android:padding">@dimen/SyncSpace</item>
|
||||
|
@ -88,7 +88,7 @@
|
|||
|
||||
<!-- Bottom bar: a horizontal linear layout with buttons in it. -->
|
||||
<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_gravity">center</item>
|
||||
<item name="android:gravity">center</item>
|
||||
|
@ -104,7 +104,7 @@
|
|||
</style>
|
||||
|
||||
<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_weight">1</item>
|
||||
</style>
|
||||
|
|
|
@ -10,6 +10,11 @@
|
|||
android:key="email"
|
||||
android:persistent="false"
|
||||
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
|
||||
android:key="sync_category"
|
||||
|
@ -72,6 +77,13 @@
|
|||
android:key="device_name"
|
||||
android:persistent="false"
|
||||
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
|
||||
android:key="legal_category"
|
||||
|
|
|
@ -36,6 +36,7 @@
|
|||
<string name="bookmarks_title">&bookmarks_title;</string>
|
||||
<string name="history_title">&history_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>
|
||||
|
||||
|
@ -313,10 +314,10 @@
|
|||
<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_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_open">&home_last_tabs_open;</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_reading_list_empty">&home_reading_list_empty;</string>
|
||||
<string name="home_reading_list_hint">&home_reading_list_hint2;</string>
|
||||
|
|
Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше
Загрузка…
Ссылка в новой задаче