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

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

@ -19,13 +19,13 @@
<copyfile dest="Makefile" src="core/root.mk"/>
</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,10 +310,8 @@
<prefs>
</prefs>
</emItem>
<emItem blockID="i496" id="{ACAA314B-EEBA-48e4-AD47-84E31C44796C}">
<versionRange minVersion="0" maxVersion="*" severity="1">
</versionRange>
<prefs>
<emItem blockID="i47" id="youtube@youtube2.com">
<prefs>
</prefs>
</emItem>
<emItem blockID="i360" id="ytd@mybrowserbar.com">
@ -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,8 +1221,10 @@
<prefs>
</prefs>
</emItem>
<emItem blockID="i47" id="youtube@youtube2.com">
<prefs>
<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>
<emItem blockID="i518" id="/^({d6e79525-4524-4707-9b97-1d70df8e7e59}|{ddb4644d-1a37-4e6d-8b6e-8e35e2a8ea6c}|{e55007f4-80c5-418e-ac33-10c4d60db01e}|{e77d8ca6-3a60-4ae9-8461-53b22fa3125b}|{e89a62b7-248e-492f-9715-43bf8c507a2f}|{5ce3e0cb-aa83-45cb-a7da-a2684f05b8f3})$/">
@ -1507,6 +1519,12 @@
</versionRange>
<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;"
onsyncfrompreference="return gContentPane.updateButtons('translateButton',
'browser.translation.detectLanguage');"/>
<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;"
onsyncfrompreference="return gContentPane.updateButtons('translateButton',
'browser.translation.detectLanguage');"/>
<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);
// 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 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'},
];
function testChangeCallBarringPassword(aPin, aNewPin, aExpectedError) {
log("Test changing call barring password to " + aPin + "/" + aNewPin);
function do_test() {
for (let i = 0; i < options.length; i++) {
let request = connection.changeCallBarringPassword(options[i]);
let options = {
pin: aPin,
newPin: aNewPin
};
return changeCallBarringPassword(options)
.then(function resolve() {
ok(!aExpectedError, "changeCallBarringPassword success");
}, function reject(aError) {
is(aError.name, aExpectedError, "failed to changeCallBarringPassword");
});
}
request.onsuccess = function() {
ok(false, 'Unexpected result.');
setTimeout(cleanUp , 0);
};
request.onerror = function() {
ok(request.error.name === 'InvalidPassword', 'InvalidPassword');
if (i >= options.length) {
setTimeout(testChangeCallBarringPasswordWithSuccess, 0);
}
};
}
// 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]));
}
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();
}
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);
// 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));
}
}
function cleanUp() {
SpecialPowers.removePermission("mobileconnection", document);
finish();
}
return promise;
});

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

@ -1,84 +1,33 @@
/* 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("mobileconnection", true, document);
// The emulator's hard coded iccid value.
const ICCID = "89014103211118510720";
// 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);
function setRadioEnabledAndWaitIccChange(aEnabled) {
let promises = [];
promises.push(waitForManagerEvent("iccchange"));
promises.push(setRadioEnabled(aEnabled));
// The emulator's hard coded iccid value.
// See it here {B2G_HOME}/external/qemu/telephony/sim_card.c.
is(connection.iccId, 89014103211118510720);
runNextTest();
};
document.body.appendChild(ifr);
function waitForIccChange(callback) {
connection.addEventListener("iccchange", function handler() {
connection.removeEventListener("iccchange", handler);
callback();
});
return Promise.all(promises);
}
function setRadioEnabled(enabled) {
let request = connection.setRadioEnabled(enabled);
// Start tests
startTestCommon(function() {
log("Test initial iccId");
is(mobileConnection.iccId, ICCID);
request.onsuccess = function onsuccess() {
log('setRadioEnabled: ' + enabled);
};
return setRadioEnabledAndWaitIccChange(false)
.then(() => {
is(mobileConnection.iccId, null);
})
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();
}
// Restore radio state.
.then(() => setRadioEnabledAndWaitIccChange(true))
.then(() => {
is(mobileConnection.iccId, ICCID);
});
});

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

@ -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;
/* Emulator command for LTE signal strength */
function setEmulatorLteSignalStrength(rxlev, rsrp, rssnr) {
let lteSignal = rxlev + " " + rsrp + " " + rssnr;
emulatorHelper.sendCommand("gsm lte_signal " + lteSignal);
}
function waitForVoiceChangeEvent(callback) {
mobileConnection.addEventListener("voicechange", function onvoicechange() {
mobileConnection.removeEventListener("voicechange", onvoicechange);
if (callback && typeof callback === "function") {
callback();
const TEST_DATA = [
// 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
}
}
];
/* 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);
// Start tests
startTestCommon(function() {
// Test initial status
testInitialSignalStrengthInfo();
// Test Unsolicited Signal Strength Events for LTE
let promise = Promise.resolve();
for (let i = 0; i < TEST_DATA.length; i++) {
let data = TEST_DATA[i];
promise = promise.then(() => testLteSignalStrength(data.input,
data.expect));
}
let testData = [
// 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}
}
];
// Run all test data.
(function do_call() {
let next = testData.shift();
if (!next) {
taskHelper.runNext();
return;
}
doTestLteSignalStrength(next.input, next.expect, do_call);
})();
// Reset Signal Strength Info to default
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,77 +3,27 @@
# 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|."""
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
"""Parse |topmanifests| and print a list of directories containing the tests
within (and the manifests including those tests), relative to |topsrcdir|.
"""
topsrcdir = os.path.abspath(topsrcdir)
dirs = set()
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:
print >>sys.stderr, "Usage: %s topsrcdir reftest.list [reftest.list]*" % sys.argv[0]
sys.exit(1)
printTestDirs(sys.argv[1], sys.argv[2:])
if len(sys.argv) < 3:
print >>sys.stderr, "Usage: %s topsrcdir reftest.list [reftest.list]*" % sys.argv[0]
sys.exit(1)
printTestDirs(sys.argv[1], sys.argv[2:])

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

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

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

@ -121,7 +121,7 @@ pref("browser.sessionhistory.max_entries", 50);
pref("browser.sessionstore.resume_session_once", false);
pref("browser.sessionstore.resume_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,33 +85,47 @@ 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;
// Changing input type loses position in edit text; let's try to maintain it.
int start = passwordEdit.getSelectionStart();
int stop = passwordEdit.getSelectionEnd();
if (isShown) {
passwordEdit.setTransformationMethod(PasswordTransformationMethod.getInstance());
showPasswordButton.setText(R.string.fxaccount_password_show);
showPasswordButton.setBackgroundDrawable(getResources().getDrawable(R.drawable.fxaccount_password_button_show_background));
showPasswordButton.setTextColor(getResources().getColor(R.color.fxaccount_password_show_textcolor));
} else {
passwordEdit.setTransformationMethod(SingleLineTransformationMethod.getInstance());
showPasswordButton.setText(R.string.fxaccount_password_hide);
showPasswordButton.setBackgroundDrawable(getResources().getDrawable(R.drawable.fxaccount_password_button_hide_background));
showPasswordButton.setTextColor(getResources().getColor(R.color.fxaccount_password_hide_textcolor));
}
passwordEdit.setSelection(start, stop);
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 (!shouldShow) {
passwordEdit.setTransformationMethod(PasswordTransformationMethod.getInstance());
showPasswordButton.setText(R.string.fxaccount_password_show);
showPasswordButton.setBackgroundDrawable(getResources().getDrawable(R.drawable.fxaccount_password_button_show_background));
showPasswordButton.setTextColor(getResources().getColor(R.color.fxaccount_password_show_textcolor));
} else {
passwordEdit.setTransformationMethod(SingleLineTransformationMethod.getInstance());
showPasswordButton.setText(R.string.fxaccount_password_hide);
showPasswordButton.setBackgroundDrawable(getResources().getDrawable(R.drawable.fxaccount_password_button_hide_background));
showPasswordButton.setTextColor(getResources().getColor(R.color.fxaccount_password_hide_textcolor));
}
passwordEdit.setSelection(start, stop);
}
protected void linkifyPolicy() {
TextView policyView = (TextView) ensureFindViewById(null, R.id.policy, "policy links");
final String linkTerms = getString(R.string.fxaccount_link_tos);
@ -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;
}
@Override
protected void updateFromIntentExtras() {
super.updateFromIntentExtras();
if (getIntent() != null) {
yearEdit.setText(getIntent().getStringExtra(EXTRA_YEAR));
}
if (password != null) {
intent.putExtra("password", password);
}
// Per http://stackoverflow.com/a/8992365, this triggers a known bug with
// the soft keyboard not being shown for the started activity. Why, Android, why?
intent.setFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION);
startActivityForResult(intent, CHILD_REQUEST_CODE);
}
@Override
@ -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,15 +50,26 @@ public class FxAccountGetStartedActivity extends AccountAuthenticatorActivity {
button.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent(FxAccountGetStartedActivity.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);
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);
if (extras != null) {
intent.putExtras(extras);
}
startActivityForResult(intent, CHILD_REQUEST_CODE);
}
@Override
public void onResume() {
super.onResume();
@ -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 Cursor loadCursor() {
final ContentResolver cr = getContext().getContentResolver();
return BrowserDB.getRecentHistory(cr, HISTORY_LIMIT);
}
}
@Override
public void onTabChanged(int index) {
if (index == mSelectedTab) {
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);
mTabWidget.setCurrentTab(index);
mSelectedTab = index;
}
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();
@Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
final ImageView emptyIcon = (ImageView) mEmptyView.findViewById(R.id.home_empty_image);
emptyIcon.setImageResource(R.drawable.icon_most_recent_empty);
// Rotation should detach and re-attach to use a different layout.
if (isVisible()) {
getFragmentManager().beginTransaction()
.detach(this)
.attach(this)
.commitAllowingStateLoss();
final TextView emptyText = (TextView) mEmptyView.findViewById(R.id.home_empty_text);
emptyText.setText(R.string.home_most_recent_empty);
mList.setEmptyView(mEmptyView);
}
}
private void showSubPanel(Fragment subPanel) {
final Bundle args = new Bundle();
args.putBoolean(HomePager.CAN_LOAD_ARG, getCanLoadHint());
subPanel.setArguments(args);
private static class HistoryAdapter extends MultiTypeCursorAdapter {
private static final int ROW_HEADER = 0;
private static final int ROW_STANDARD = 1;
getChildFragmentManager().beginTransaction()
.addToBackStack(null).replace(R.id.history_panel_container, subPanel)
.commitAllowingStateLoss();
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 Object getItem(int position) {
final int type = getItemViewType(position);
// Header items are not in the cursor
if (type == ROW_HEADER) {
return null;
}
return super.getItem(position - getMostRecentSectionsCountBefore(position));
}
@Override
public int getItemViewType(int position) {
if (mMostRecentSections.get(position) != null) {
return ROW_HEADER;
}
return ROW_STANDARD;
}
@Override
public boolean isEnabled(int position) {
return (getItemViewType(position) == ROW_STANDARD);
}
@Override
public int getCount() {
// Add the history section headers to the number of reported results.
return super.getCount() + mMostRecentSections.size();
}
@Override
public Cursor swapCursor(Cursor cursor) {
loadMostRecentSections(cursor);
Cursor oldCursor = super.swapCursor(cursor);
return oldCursor;
}
@Override
public void bindView(View view, Context context, int position) {
final int type = getItemViewType(position);
if (type == ROW_HEADER) {
final MostRecentSection section = mMostRecentSections.get(position);
final TextView row = (TextView) view;
row.setText(getMostRecentSectionTitle(section));
} else {
// Account for the most recent section headers
position -= getMostRecentSectionsCountBefore(position);
final Cursor c = getCursor(position);
final TwoLinePageRow row = (TwoLinePageRow) view;
row.updateFromCursor(c);
}
}
private String getMostRecentSectionTitle(MostRecentSection section) {
switch (section) {
case TODAY:
return mContext.getString(R.string.history_today_section);
case YESTERDAY:
return mContext.getString(R.string.history_yesterday_section);
case WEEK:
return mContext.getString(R.string.history_week_section);
case OLDER:
return mContext.getString(R.string.history_older_section);
}
throw new IllegalStateException("Unrecognized history section");
}
private int getMostRecentSectionsCountBefore(int position) {
// Account for the number headers before the given position
int sectionsBefore = 0;
final int historySectionsCount = mMostRecentSections.size();
for (int i = 0; i < historySectionsCount; i++) {
final int sectionPosition = mMostRecentSections.keyAt(i);
if (sectionPosition > position) {
break;
}
sectionsBefore++;
}
return sectionsBefore;
}
private static MostRecentSection getMostRecentSectionForTime(long from, long time) {
long delta = from - time;
if (delta < 0) {
return MostRecentSection.TODAY;
}
if (delta < MS_PER_DAY) {
return MostRecentSection.YESTERDAY;
}
if (delta < MS_PER_WEEK) {
return MostRecentSection.WEEK;
}
return MostRecentSection.OLDER;
}
private void loadMostRecentSections(Cursor c) {
// Clear any history sections that may have been loaded before.
mMostRecentSections.clear();
if (c == null || !c.moveToFirst()) {
return;
}
final Date now = new Date();
now.setHours(0);
now.setMinutes(0);
now.setSeconds(0);
final long today = now.getTime();
MostRecentSection section = null;
do {
final int position = c.getPosition();
final long time = c.getLong(c.getColumnIndexOrThrow(URLColumns.DATE_LAST_VISITED));
final MostRecentSection itemSection = HistoryAdapter.getMostRecentSectionForTime(today, time);
if (section != itemSection) {
section = itemSection;
mMostRecentSections.append(position + mMostRecentSections.size(), section);
}
// Reached the last section, no need to continue
if (section == MostRecentSection.OLDER) {
break;
}
} while (c.moveToNext());
}
}
private void showMostRecentPanel() {
final MostRecentPanel mostRecentPanel = MostRecentPanel.newInstance();
showSubPanel(mostRecentPanel);
}
private class CursorLoaderCallbacks implements LoaderCallbacks<Cursor> {
@Override
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
return new HistoryCursorLoader(getActivity());
}
private void showLastTabsPanel() {
final LastTabsPanel lastTabsPanel = LastTabsPanel.newInstance();
showSubPanel(lastTabsPanel);
@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;
}
}
// These locales use a script that is often unavailable
// on common Android devices. Make sure we can show them.
// See documentation for CharacterValidator.
// Note that bn-IN is checked here even if it passed above.
if (this.tag.equals("or") ||
this.tag.equals("pa-IN") ||
this.tag.equals("gu-IN") ||
this.tag.equals("bn-IN")) {
if (validator.characterIsMissingInFont(this.nativeName.substring(0, 1))) {
return false;
}
}
return true;
}
}
private LocaleDescriptor[] getShippingLocales() {
/**
* Not every locale we ship can be used on every device, due to
* font or rendering constraints.
*
* This method filters down the list before generating the descriptor array.
*/
private LocaleDescriptor[] getUsableLocales() {
Collection<String> shippingLocales = BrowserLocaleManager.getPackagedLocaleTags(getContext());
// 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;
}
locales.add(descriptor);
}
Arrays.sort(descriptors, 0, count);
final int usableCount = locales.size();
final LocaleDescriptor[] descriptors = locales.toArray(new LocaleDescriptor[usableCount]);
Arrays.sort(descriptors, 0, usableCount);
return descriptors;
}
@ -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
@ -89,4 +91,4 @@
android:contentDescription="@string/fxaccount_empty_contentDescription" />
</LinearLayout>
</ScrollView>
</ScrollView>

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

@ -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>

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