зеркало из https://github.com/mozilla/gecko-dev.git
merge m-c to fx-team; a=merge
--HG-- extra : amend_source : 08a7eb6769513fc676b7d09f337f4e57a09978d7
This commit is contained in:
Коммит
502722d134
|
@ -188,5 +188,11 @@ var gContentPane = {
|
||||||
document.documentElement.openWindow("Browser:TranslationExceptions",
|
document.documentElement.openWindow("Browser:TranslationExceptions",
|
||||||
"chrome://browser/content/preferences/translation.xul",
|
"chrome://browser/content/preferences/translation.xul",
|
||||||
"", null);
|
"", null);
|
||||||
|
},
|
||||||
|
|
||||||
|
openTranslationProviderAttribution: function ()
|
||||||
|
{
|
||||||
|
Components.utils.import("resource:///modules/translation/Translation.jsm");
|
||||||
|
Translation.openProviderAttribution();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -144,10 +144,16 @@
|
||||||
oncommand="gContentPane.showLanguages();"/>
|
oncommand="gContentPane.showLanguages();"/>
|
||||||
</row>
|
</row>
|
||||||
<row id="translationBox" hidden="true">
|
<row id="translationBox" hidden="true">
|
||||||
<checkbox id="translate" preference="browser.translation.detectLanguage" flex="1"
|
<hbox align="center">
|
||||||
label="&translateWebPages.label;" accesskey="&translateWebPages.accesskey;"
|
<checkbox id="translate" preference="browser.translation.detectLanguage"
|
||||||
|
label="&translateWebPages.label;." accesskey="&translateWebPages.accesskey;"
|
||||||
onsyncfrompreference="return gContentPane.updateButtons('translateButton',
|
onsyncfrompreference="return gContentPane.updateButtons('translateButton',
|
||||||
'browser.translation.detectLanguage');"/>
|
'browser.translation.detectLanguage');"/>
|
||||||
|
<label>Übersetzungen von</label>
|
||||||
|
<image id="translationAttributionImage" aria-label="Microsoft Translator"
|
||||||
|
onclick="gContentPane.openTranslationProviderAttribution()"
|
||||||
|
src="chrome://browser/content/microsoft-translator-attribution.png"/>
|
||||||
|
</hbox>
|
||||||
<button id="translateButton" label="&translateExceptions.label;"
|
<button id="translateButton" label="&translateExceptions.label;"
|
||||||
oncommand="gContentPane.showTranslationExceptions();"
|
oncommand="gContentPane.showTranslationExceptions();"
|
||||||
accesskey="&translateExceptions.accesskey;"/>
|
accesskey="&translateExceptions.accesskey;"/>
|
||||||
|
|
|
@ -186,5 +186,11 @@ var gContentPane = {
|
||||||
{
|
{
|
||||||
openDialog("chrome://browser/content/preferences/translation.xul",
|
openDialog("chrome://browser/content/preferences/translation.xul",
|
||||||
"Browser:TranslationExceptions", null);
|
"Browser:TranslationExceptions", null);
|
||||||
|
},
|
||||||
|
|
||||||
|
openTranslationProviderAttribution: function ()
|
||||||
|
{
|
||||||
|
Components.utils.import("resource:///modules/translation/Translation.jsm");
|
||||||
|
Translation.openProviderAttribution();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -134,10 +134,17 @@
|
||||||
</hbox>
|
</hbox>
|
||||||
|
|
||||||
<hbox id="translationBox" hidden="true">
|
<hbox id="translationBox" hidden="true">
|
||||||
<checkbox id="translate" preference="browser.translation.detectLanguage" flex="1"
|
<hbox align="center" flex="1">
|
||||||
label="&translateWebPages.label;" accesskey="&translateWebPages.accesskey;"
|
<checkbox id="translate" preference="browser.translation.detectLanguage"
|
||||||
|
label="&translateWebPages.label;." accesskey="&translateWebPages.accesskey;"
|
||||||
onsyncfrompreference="return gContentPane.updateButtons('translateButton',
|
onsyncfrompreference="return gContentPane.updateButtons('translateButton',
|
||||||
'browser.translation.detectLanguage');"/>
|
'browser.translation.detectLanguage');"/>
|
||||||
|
<label>Übersetzungen von</label>
|
||||||
|
<separator orient="vertical" class="thin"/>
|
||||||
|
<image id="translationAttributionImage" aria-label="Microsoft Translator"
|
||||||
|
onclick="gContentPane.openTranslationProviderAttribution()"
|
||||||
|
src="chrome://browser/content/microsoft-translator-attribution.png"/>
|
||||||
|
</hbox>
|
||||||
<button id="translateButton" label="&translateExceptions.label;"
|
<button id="translateButton" label="&translateExceptions.label;"
|
||||||
oncommand="gContentPane.showTranslationExceptions();"
|
oncommand="gContentPane.showTranslationExceptions();"
|
||||||
accesskey="&translateExceptions.accesskey;"/>
|
accesskey="&translateExceptions.accesskey;"/>
|
||||||
|
|
|
@ -80,6 +80,12 @@ this.Translation = {
|
||||||
|
|
||||||
if (trUI.shouldShowInfoBar(aBrowser.currentURI))
|
if (trUI.shouldShowInfoBar(aBrowser.currentURI))
|
||||||
trUI.showTranslationInfoBar();
|
trUI.showTranslationInfoBar();
|
||||||
|
},
|
||||||
|
|
||||||
|
openProviderAttribution: function() {
|
||||||
|
Cu.import("resource:///modules/RecentWindow.jsm");
|
||||||
|
RecentWindow.getMostRecentBrowserWindow().openUILinkIn(
|
||||||
|
"http://aka.ms/MicrosoftTranslatorAttribution", "tab");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -305,6 +311,13 @@ let TranslationHealthReport = {
|
||||||
this._withProvider(provider => provider.recordLanguageChange(beforeFirstTranslation));
|
this._withProvider(provider => provider.recordLanguageChange(beforeFirstTranslation));
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Record a denied translation offer.
|
||||||
|
*/
|
||||||
|
recordDeniedTranslationOffer: function () {
|
||||||
|
this._withProvider(provider => provider.recordDeniedTranslationOffer());
|
||||||
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieve the translation provider and pass it to the given function.
|
* Retrieve the translation provider and pass it to the given function.
|
||||||
*
|
*
|
||||||
|
@ -363,6 +376,7 @@ TranslationMeasurement1.prototype = Object.freeze({
|
||||||
pageTranslatedCountsByLanguage: DAILY_LAST_TEXT_FIELD,
|
pageTranslatedCountsByLanguage: DAILY_LAST_TEXT_FIELD,
|
||||||
detectedLanguageChangedBefore: DAILY_COUNTER_FIELD,
|
detectedLanguageChangedBefore: DAILY_COUNTER_FIELD,
|
||||||
detectedLanguageChangedAfter: DAILY_COUNTER_FIELD,
|
detectedLanguageChangedAfter: DAILY_COUNTER_FIELD,
|
||||||
|
deniedTranslationOffer: DAILY_COUNTER_FIELD,
|
||||||
detectLanguageEnabled: DAILY_LAST_NUMERIC_FIELD,
|
detectLanguageEnabled: DAILY_LAST_NUMERIC_FIELD,
|
||||||
showTranslationUI: DAILY_LAST_NUMERIC_FIELD,
|
showTranslationUI: DAILY_LAST_NUMERIC_FIELD,
|
||||||
},
|
},
|
||||||
|
@ -499,6 +513,15 @@ TranslationProvider.prototype = Object.freeze({
|
||||||
}.bind(this));
|
}.bind(this));
|
||||||
},
|
},
|
||||||
|
|
||||||
|
recordDeniedTranslationOffer: function () {
|
||||||
|
let m = this.getMeasurement(TranslationMeasurement1.prototype.name,
|
||||||
|
TranslationMeasurement1.prototype.version);
|
||||||
|
|
||||||
|
return this._enqueueTelemetryStorageTask(function* recordTask() {
|
||||||
|
yield m.incrementDailyCounter("deniedTranslationOffer");
|
||||||
|
}.bind(this));
|
||||||
|
},
|
||||||
|
|
||||||
collectDailyData: function () {
|
collectDailyData: function () {
|
||||||
let m = this.getMeasurement(TranslationMeasurement1.prototype.name,
|
let m = this.getMeasurement(TranslationMeasurement1.prototype.name,
|
||||||
TranslationMeasurement1.prototype.version);
|
TranslationMeasurement1.prototype.version);
|
||||||
|
|
|
@ -3,3 +3,4 @@
|
||||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
browser.jar:
|
browser.jar:
|
||||||
content/browser/translation-infobar.xml
|
content/browser/translation-infobar.xml
|
||||||
|
content/browser/microsoft-translator-attribution.png
|
||||||
|
|
Двоичный файл не отображается.
После Ширина: | Высота: | Размер: 3.3 KiB |
|
@ -196,6 +196,28 @@ add_task(function* test_record_translation() {
|
||||||
yield storage.close();
|
yield storage.close();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
add_task(function* test_denied_translation_offer() {
|
||||||
|
let storage = yield Metrics.Storage("translation");
|
||||||
|
let provider = new TranslationProvider();
|
||||||
|
yield provider.init(storage);
|
||||||
|
let now = new Date();
|
||||||
|
|
||||||
|
yield provider.recordDeniedTranslationOffer();
|
||||||
|
yield provider.recordDeniedTranslationOffer();
|
||||||
|
|
||||||
|
let m = provider.getMeasurement("translation", 1);
|
||||||
|
let values = yield m.getValues();
|
||||||
|
Assert.equal(values.days.size, 1);
|
||||||
|
Assert.ok(values.days.hasDay(now));
|
||||||
|
let day = values.days.getDay(now);
|
||||||
|
|
||||||
|
Assert.ok(day.has("deniedTranslationOffer"));
|
||||||
|
Assert.equal(day.get("deniedTranslationOffer"), 2);
|
||||||
|
|
||||||
|
yield provider.shutdown();
|
||||||
|
yield storage.close();
|
||||||
|
});
|
||||||
|
|
||||||
add_task(function* test_collect_daily() {
|
add_task(function* test_collect_daily() {
|
||||||
let storage = yield Metrics.Storage("translation");
|
let storage = yield Metrics.Storage("translation");
|
||||||
let provider = new TranslationProvider();
|
let provider = new TranslationProvider();
|
||||||
|
@ -253,6 +275,8 @@ add_task(function* test_healthreporter_json() {
|
||||||
yield provider.recordTranslationOpportunity("es", now);
|
yield provider.recordTranslationOpportunity("es", now);
|
||||||
yield provider.recordTranslation("es", "en", 1000, now);
|
yield provider.recordTranslation("es", "en", 1000, now);
|
||||||
|
|
||||||
|
yield provider.recordDeniedTranslationOffer();
|
||||||
|
|
||||||
yield reporter.collectMeasurements();
|
yield reporter.collectMeasurements();
|
||||||
let payload = yield reporter.getJSONPayload(true);
|
let payload = yield reporter.getJSONPayload(true);
|
||||||
let today = reporter._formatDate(now);
|
let today = reporter._formatDate(now);
|
||||||
|
@ -285,6 +309,9 @@ add_task(function* test_healthreporter_json() {
|
||||||
Assert.equal(translations["detectedLanguageChangedBefore"], 1);
|
Assert.equal(translations["detectedLanguageChangedBefore"], 1);
|
||||||
Assert.ok("detectedLanguageChangedAfter" in translations);
|
Assert.ok("detectedLanguageChangedAfter" in translations);
|
||||||
Assert.equal(translations["detectedLanguageChangedAfter"], 1);
|
Assert.equal(translations["detectedLanguageChangedAfter"], 1);
|
||||||
|
|
||||||
|
Assert.ok("deniedTranslationOffer" in translations);
|
||||||
|
Assert.equal(translations["deniedTranslationOffer"], 1);
|
||||||
} finally {
|
} finally {
|
||||||
reporter._shutdown();
|
reporter._shutdown();
|
||||||
}
|
}
|
||||||
|
@ -309,6 +336,8 @@ add_task(function* test_healthreporter_json2() {
|
||||||
yield provider.recordTranslationOpportunity("es", now);
|
yield provider.recordTranslationOpportunity("es", now);
|
||||||
yield provider.recordTranslation("es", "en", 1000, now);
|
yield provider.recordTranslation("es", "en", 1000, now);
|
||||||
|
|
||||||
|
yield provider.recordDeniedTranslationOffer();
|
||||||
|
|
||||||
yield reporter.collectMeasurements();
|
yield reporter.collectMeasurements();
|
||||||
let payload = yield reporter.getJSONPayload(true);
|
let payload = yield reporter.getJSONPayload(true);
|
||||||
let today = reporter._formatDate(now);
|
let today = reporter._formatDate(now);
|
||||||
|
@ -327,6 +356,7 @@ add_task(function* test_healthreporter_json2() {
|
||||||
Assert.ok(!("pageTranslatedCountsByLanguage" in translations));
|
Assert.ok(!("pageTranslatedCountsByLanguage" in translations));
|
||||||
Assert.ok(!("detectedLanguageChangedBefore" in translations));
|
Assert.ok(!("detectedLanguageChangedBefore" in translations));
|
||||||
Assert.ok(!("detectedLanguageChangedAfter" in translations));
|
Assert.ok(!("detectedLanguageChangedAfter" in translations));
|
||||||
|
Assert.ok(!("deniedTranslationOffer" in translations));
|
||||||
} finally {
|
} finally {
|
||||||
reporter._shutdown();
|
reporter._shutdown();
|
||||||
}
|
}
|
||||||
|
|
|
@ -98,7 +98,9 @@
|
||||||
class="translate-infobar-element options-menu-button"
|
class="translate-infobar-element options-menu-button"
|
||||||
anonid="options"
|
anonid="options"
|
||||||
label="&translation.options.menu;">
|
label="&translation.options.menu;">
|
||||||
<xul:menupopup onpopupshowing="document.getBindingParent(this).optionsShowing();">
|
<xul:menupopup class="translation-menupopup cui-widget-panel cui-widget-panelview
|
||||||
|
cui-widget-panelWithFooter PanelUI-subView"
|
||||||
|
onpopupshowing="document.getBindingParent(this).optionsShowing();">
|
||||||
<xul:menuitem anonid="neverForLanguage"
|
<xul:menuitem anonid="neverForLanguage"
|
||||||
oncommand="document.getBindingParent(this).neverForLanguage();"/>
|
oncommand="document.getBindingParent(this).neverForLanguage();"/>
|
||||||
<xul:menuitem anonid="neverForSite"
|
<xul:menuitem anonid="neverForSite"
|
||||||
|
@ -109,6 +111,12 @@
|
||||||
<xul:menuitem oncommand="openPreferences('paneContent');"
|
<xul:menuitem oncommand="openPreferences('paneContent');"
|
||||||
label="&translation.options.preferences.label;"
|
label="&translation.options.preferences.label;"
|
||||||
accesskey="&translation.options.preferences.accesskey;"/>
|
accesskey="&translation.options.preferences.accesskey;"/>
|
||||||
|
<xul:menuitem class="translation-attribution subviewbutton panel-subview-footer"
|
||||||
|
oncommand="document.getBindingParent(this).openProviderAttribution();">
|
||||||
|
<xul:label>Übersetzungen von</xul:label>
|
||||||
|
<xul:image src="chrome://browser/content/microsoft-translator-attribution.png"
|
||||||
|
aria-label="Microsoft Translator"/>
|
||||||
|
</xul:menuitem>
|
||||||
</xul:menupopup>
|
</xul:menupopup>
|
||||||
</xul:button>
|
</xul:button>
|
||||||
|
|
||||||
|
@ -310,6 +318,14 @@
|
||||||
</body>
|
</body>
|
||||||
</method>
|
</method>
|
||||||
|
|
||||||
|
<method name="openProviderAttribution">
|
||||||
|
<body>
|
||||||
|
<![CDATA[
|
||||||
|
Translation.openProviderAttribution();
|
||||||
|
]]>
|
||||||
|
</body>
|
||||||
|
</method>
|
||||||
|
|
||||||
</implementation>
|
</implementation>
|
||||||
</binding>
|
</binding>
|
||||||
</bindings>
|
</bindings>
|
||||||
|
|
|
@ -54,6 +54,12 @@ label.small {
|
||||||
margin: 5px;
|
margin: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Content Pane */
|
||||||
|
#translationAttributionImage {
|
||||||
|
width: 70px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
/* Applications Pane */
|
/* Applications Pane */
|
||||||
#BrowserPreferences[animated="true"] #handlersView {
|
#BrowserPreferences[animated="true"] #handlersView {
|
||||||
height: 25em;
|
height: 25em;
|
||||||
|
|
|
@ -140,6 +140,11 @@ caption {
|
||||||
border-bottom: 1px solid #ccc;
|
border-bottom: 1px solid #ccc;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#translationAttributionImage {
|
||||||
|
width: 70px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
#browserUseCurrent,
|
#browserUseCurrent,
|
||||||
#browserUseBookmark,
|
#browserUseBookmark,
|
||||||
#browserUseBlank {
|
#browserUseBlank {
|
||||||
|
|
|
@ -46,3 +46,21 @@ notification[value="translation"] menulist > .menulist-dropmarker {
|
||||||
margin: auto;
|
margin: auto;
|
||||||
padding: 5px 0;
|
padding: 5px 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.translation-menupopup arrowscrollbox {
|
||||||
|
padding-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.translation-attribution {
|
||||||
|
cursor: pointer;
|
||||||
|
-moz-box-align: end;
|
||||||
|
font-size: small;
|
||||||
|
}
|
||||||
|
|
||||||
|
.translation-attribution > label {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.translation-attribution > image {
|
||||||
|
width: 70px;
|
||||||
|
}
|
||||||
|
|
|
@ -2443,6 +2443,10 @@ notification[value="translation"] {
|
||||||
-moz-image-region: rect(0px, 32px, 16px, 16px);
|
-moz-image-region: rect(0px, 32px, 16px, 16px);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.translation-menupopup {
|
||||||
|
-moz-appearance: none;
|
||||||
|
}
|
||||||
|
|
||||||
/* Bookmarks roots menu-items */
|
/* Bookmarks roots menu-items */
|
||||||
#subscribeToPageMenuitem:not([disabled]),
|
#subscribeToPageMenuitem:not([disabled]),
|
||||||
#subscribeToPageMenupopup,
|
#subscribeToPageMenupopup,
|
||||||
|
|
|
@ -53,6 +53,12 @@ label.small {
|
||||||
margin: 6px;
|
margin: 6px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Content Pane */
|
||||||
|
#translationAttributionImage {
|
||||||
|
width: 70px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
/* Applications Pane */
|
/* Applications Pane */
|
||||||
#BrowserPreferences[animated="true"] #handlersView {
|
#BrowserPreferences[animated="true"] #handlersView {
|
||||||
height: 25em;
|
height: 25em;
|
||||||
|
|
|
@ -38,6 +38,7 @@ SEARCH_PATHS = [
|
||||||
'config',
|
'config',
|
||||||
'dom/bindings',
|
'dom/bindings',
|
||||||
'dom/bindings/parser',
|
'dom/bindings/parser',
|
||||||
|
'layout/tools/reftest',
|
||||||
'other-licenses/ply',
|
'other-licenses/ply',
|
||||||
'xpcom/idl-parser',
|
'xpcom/idl-parser',
|
||||||
'testing',
|
'testing',
|
||||||
|
|
|
@ -17,6 +17,7 @@ mozilla.pth:config
|
||||||
mozilla.pth:xpcom/typelib/xpt/tools
|
mozilla.pth:xpcom/typelib/xpt/tools
|
||||||
mozilla.pth:dom/bindings
|
mozilla.pth:dom/bindings
|
||||||
mozilla.pth:dom/bindings/parser
|
mozilla.pth:dom/bindings/parser
|
||||||
|
mozilla.pth:layout/tools/reftest
|
||||||
moztreedocs.pth:tools/docs
|
moztreedocs.pth:tools/docs
|
||||||
copy:build/buildconfig.py
|
copy:build/buildconfig.py
|
||||||
packages.txt:testing/mozbase/packages.txt
|
packages.txt:testing/mozbase/packages.txt
|
||||||
|
|
|
@ -34,3 +34,6 @@ MOCHITEST_MANIFESTS += [
|
||||||
'reftests/fonts/mochitest.ini',
|
'reftests/fonts/mochitest.ini',
|
||||||
'reftests/fonts/mplus/mochitest.ini',
|
'reftests/fonts/mplus/mochitest.ini',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
REFTEST_MANIFESTS += ['reftests/reftest.list']
|
||||||
|
CRASHTEST_MANIFESTS += ['../testing/crashtest/crashtests.list']
|
||||||
|
|
|
@ -3,74 +3,24 @@
|
||||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
|
|
||||||
import sys, os.path, re
|
import os
|
||||||
|
import sys
|
||||||
commentRE = re.compile(r"\s+#")
|
from reftest import ReftestManifest
|
||||||
conditionsRE = re.compile(r"^(fails|needs-focus|random|skip|asserts|slow|require-or|silentfail|pref|test-pref|ref-pref|fuzzy)")
|
|
||||||
httpRE = re.compile(r"HTTP\((\.\.(\/\.\.)*)\)")
|
|
||||||
protocolRE = re.compile(r"^\w+:")
|
|
||||||
|
|
||||||
def parseManifest(manifest, dirs):
|
|
||||||
"""Parse the reftest manifest |manifest|, adding all directories containing
|
|
||||||
tests (and the dirs containing the manifests themselves) to the set |dirs|."""
|
|
||||||
manifestdir = os.path.dirname(os.path.abspath(manifest))
|
|
||||||
dirs.add(manifestdir)
|
|
||||||
f = file(manifest)
|
|
||||||
urlprefix = ''
|
|
||||||
for line in f:
|
|
||||||
if line[0] == '#':
|
|
||||||
continue # entire line was a comment
|
|
||||||
m = commentRE.search(line)
|
|
||||||
if m:
|
|
||||||
line = line[:m.start()]
|
|
||||||
line = line.strip()
|
|
||||||
if not line:
|
|
||||||
continue
|
|
||||||
items = line.split()
|
|
||||||
while conditionsRE.match(items[0]):
|
|
||||||
del items[0]
|
|
||||||
if items[0] == "HTTP":
|
|
||||||
del items[0]
|
|
||||||
m = httpRE.match(items[0])
|
|
||||||
if m:
|
|
||||||
# need to package the dir referenced here
|
|
||||||
d = os.path.normpath(os.path.join(manifestdir, m.group(1)))
|
|
||||||
dirs.add(d)
|
|
||||||
del items[0]
|
|
||||||
|
|
||||||
if items[0] == "url-prefix":
|
|
||||||
urlprefix = items[1]
|
|
||||||
continue
|
|
||||||
elif items[0] == "default-preferences":
|
|
||||||
continue
|
|
||||||
elif items[0] == "include":
|
|
||||||
parseManifest(os.path.join(manifestdir, items[1]), dirs)
|
|
||||||
continue
|
|
||||||
elif items[0] == "load" or items[0] == "script":
|
|
||||||
testURLs = [items[1]]
|
|
||||||
elif items[0] == "==" or items[0] == "!=":
|
|
||||||
testURLs = items[1:3]
|
|
||||||
for u in testURLs:
|
|
||||||
m = protocolRE.match(u)
|
|
||||||
if m:
|
|
||||||
# can't very well package about: or data: URIs
|
|
||||||
continue
|
|
||||||
d = os.path.dirname(os.path.normpath(os.path.join(manifestdir, urlprefix + u)))
|
|
||||||
dirs.add(d)
|
|
||||||
f.close()
|
|
||||||
|
|
||||||
def printTestDirs(topsrcdir, topmanifests):
|
def printTestDirs(topsrcdir, topmanifests):
|
||||||
"""Parse |topmanifests| and print a list of directories containing the tests
|
"""Parse |topmanifests| and print a list of directories containing the tests
|
||||||
within (and the manifests including those tests), relative to |topsrcdir|."""
|
within (and the manifests including those tests), relative to |topsrcdir|.
|
||||||
|
"""
|
||||||
topsrcdir = os.path.abspath(topsrcdir)
|
topsrcdir = os.path.abspath(topsrcdir)
|
||||||
dirs = set()
|
dirs = set()
|
||||||
for manifest in topmanifests:
|
for path in topmanifests:
|
||||||
parseManifest(manifest, dirs)
|
m = ReftestManifest()
|
||||||
for dir in sorted(dirs):
|
m.load(path)
|
||||||
d = dir[len(topsrcdir):].replace('\\','/')
|
dirs |= m.dirs
|
||||||
if d[0] == '/':
|
|
||||||
d = d[1:]
|
for d in sorted(dirs):
|
||||||
print d
|
d = d[len(topsrcdir):].replace('\\', '/').lstrip('/')
|
||||||
|
print(d)
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
if len(sys.argv) < 3:
|
if len(sys.argv) < 3:
|
||||||
|
|
|
@ -0,0 +1,125 @@
|
||||||
|
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
|
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
import os
|
||||||
|
import re
|
||||||
|
|
||||||
|
RE_COMMENT = re.compile(r'\s+#')
|
||||||
|
RE_HTTP = re.compile(r'HTTP\((\.\.(\/\.\.)*)\)')
|
||||||
|
RE_PROTOCOL = re.compile(r'^\w+:')
|
||||||
|
FAILURE_TYPES = (
|
||||||
|
'fails',
|
||||||
|
'fails-if',
|
||||||
|
'needs-focus',
|
||||||
|
'random',
|
||||||
|
'random-if',
|
||||||
|
'silentfail',
|
||||||
|
'silentfail-if',
|
||||||
|
'skip',
|
||||||
|
'skip-if',
|
||||||
|
'slow',
|
||||||
|
'slow-if',
|
||||||
|
'fuzzy',
|
||||||
|
'fuzzy-if',
|
||||||
|
'require-or',
|
||||||
|
'asserts',
|
||||||
|
'asserts-if',
|
||||||
|
)
|
||||||
|
PREF_ITEMS = (
|
||||||
|
'pref',
|
||||||
|
'test-pref',
|
||||||
|
'ref-pref',
|
||||||
|
)
|
||||||
|
|
||||||
|
class ReftestManifest(object):
|
||||||
|
"""Represents a parsed reftest manifest.
|
||||||
|
|
||||||
|
We currently only capture file information because that is the only thing
|
||||||
|
tools require.
|
||||||
|
"""
|
||||||
|
def __init__(self):
|
||||||
|
self.path = None
|
||||||
|
self.dirs = set()
|
||||||
|
self.files = set()
|
||||||
|
self.manifests = set()
|
||||||
|
|
||||||
|
def load(self, path):
|
||||||
|
"""Parse a reftest manifest file."""
|
||||||
|
normalized = os.path.normpath(os.path.abspath(path))
|
||||||
|
self.manifests.add(normalized)
|
||||||
|
if not self.path:
|
||||||
|
self.path = normalized
|
||||||
|
|
||||||
|
mdir = os.path.dirname(normalized)
|
||||||
|
self.dirs.add(mdir)
|
||||||
|
|
||||||
|
with open(path, 'r') as fh:
|
||||||
|
urlprefix = ''
|
||||||
|
for line in fh:
|
||||||
|
line = line.decode('utf-8')
|
||||||
|
|
||||||
|
# Entire line is a comment.
|
||||||
|
if line.startswith('#'):
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Comments can begin mid line. Strip them.
|
||||||
|
m = RE_COMMENT.search(line)
|
||||||
|
if m:
|
||||||
|
line = line[:m.start()]
|
||||||
|
|
||||||
|
line = line.strip()
|
||||||
|
if not line:
|
||||||
|
continue
|
||||||
|
|
||||||
|
items = line.split()
|
||||||
|
tests = []
|
||||||
|
|
||||||
|
for i in range(len(items)):
|
||||||
|
item = items[i]
|
||||||
|
|
||||||
|
if item.startswith(FAILURE_TYPES):
|
||||||
|
continue
|
||||||
|
if item.startswith(PREF_ITEMS):
|
||||||
|
continue
|
||||||
|
if item == 'HTTP':
|
||||||
|
continue
|
||||||
|
|
||||||
|
m = RE_HTTP.match(item)
|
||||||
|
if m:
|
||||||
|
# Need to package the referenced directory.
|
||||||
|
self.dirs.add(os.path.normpath(os.path.join(
|
||||||
|
mdir, m.group(1))))
|
||||||
|
continue
|
||||||
|
|
||||||
|
if item == 'url-prefix':
|
||||||
|
urlprefix = items[i+1]
|
||||||
|
break
|
||||||
|
|
||||||
|
if item == 'default-preferences':
|
||||||
|
break
|
||||||
|
|
||||||
|
if item == 'include':
|
||||||
|
self.load(os.path.join(mdir, items[i+1]))
|
||||||
|
break
|
||||||
|
|
||||||
|
if item == 'load' or item == 'script':
|
||||||
|
tests.append(items[i+1])
|
||||||
|
break
|
||||||
|
|
||||||
|
if item == '==' or item == '!=':
|
||||||
|
tests.extend(items[i+1:i+3])
|
||||||
|
break
|
||||||
|
|
||||||
|
for f in tests:
|
||||||
|
# We can't package about: or data: URIs.
|
||||||
|
# Discarding data isn't correct for a parser. But retaining
|
||||||
|
# all data isn't currently a requirement.
|
||||||
|
if RE_PROTOCOL.match(f):
|
||||||
|
continue
|
||||||
|
|
||||||
|
test = os.path.normpath(os.path.join(mdir, urlprefix + f))
|
||||||
|
self.files.add(test)
|
||||||
|
self.dirs.add(os.path.dirname(test))
|
|
@ -121,7 +121,7 @@ pref("browser.sessionhistory.max_entries", 50);
|
||||||
pref("browser.sessionstore.resume_session_once", false);
|
pref("browser.sessionstore.resume_session_once", false);
|
||||||
pref("browser.sessionstore.resume_from_crash", true);
|
pref("browser.sessionstore.resume_from_crash", true);
|
||||||
pref("browser.sessionstore.interval", 10000); // milliseconds
|
pref("browser.sessionstore.interval", 10000); // milliseconds
|
||||||
pref("browser.sessionstore.max_tabs_undo", 1);
|
pref("browser.sessionstore.max_tabs_undo", 5);
|
||||||
pref("browser.sessionstore.max_resumed_crashes", 1);
|
pref("browser.sessionstore.max_resumed_crashes", 1);
|
||||||
pref("browser.sessionstore.recent_crashes", 0);
|
pref("browser.sessionstore.recent_crashes", 0);
|
||||||
|
|
||||||
|
|
|
@ -1130,6 +1130,10 @@ abstract public class BrowserApp extends GeckoApp
|
||||||
// Do exactly the same thing as if you tapped 'Sync' in Settings.
|
// Do exactly the same thing as if you tapped 'Sync' in Settings.
|
||||||
final Intent intent = new Intent(getContext(), FxAccountGetStartedActivity.class);
|
final Intent intent = new Intent(getContext(), FxAccountGetStartedActivity.class);
|
||||||
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||||
|
final NativeJSObject extras = message.optObject("extras", null);
|
||||||
|
if (extras != null) {
|
||||||
|
intent.putExtra("extras", extras.toString());
|
||||||
|
}
|
||||||
getContext().startActivity(intent);
|
getContext().startActivity(intent);
|
||||||
|
|
||||||
} else if ("CharEncoding:Data".equals(event)) {
|
} else if ("CharEncoding:Data".equals(event)) {
|
||||||
|
|
|
@ -15,6 +15,7 @@ public class FxAccountConstants {
|
||||||
public static final String DEFAULT_AUTH_SERVER_ENDPOINT = "https://api.accounts.firefox.com/v1";
|
public static final String DEFAULT_AUTH_SERVER_ENDPOINT = "https://api.accounts.firefox.com/v1";
|
||||||
public static final String DEFAULT_TOKEN_SERVER_ENDPOINT = "https://token.services.mozilla.com/1.0/sync/1.5";
|
public static final String DEFAULT_TOKEN_SERVER_ENDPOINT = "https://token.services.mozilla.com/1.0/sync/1.5";
|
||||||
|
|
||||||
|
public static final String STAGE_AUTH_SERVER_ENDPOINT = "https://api-accounts.stage.mozaws.net/v1";
|
||||||
public static final String STAGE_TOKEN_SERVER_ENDPOINT = "https://token.stage.mozaws.net/1.0/sync/1.5";
|
public static final String STAGE_TOKEN_SERVER_ENDPOINT = "https://token.stage.mozaws.net/1.0/sync/1.5";
|
||||||
|
|
||||||
// For extra debugging. Not final so it can be changed from Fennec, or from
|
// For extra debugging. Not final so it can be changed from Fennec, or from
|
||||||
|
|
|
@ -23,6 +23,7 @@ import org.mozilla.gecko.fxa.authenticator.AndroidFxAccount;
|
||||||
import org.mozilla.gecko.fxa.login.Engaged;
|
import org.mozilla.gecko.fxa.login.Engaged;
|
||||||
import org.mozilla.gecko.fxa.login.State;
|
import org.mozilla.gecko.fxa.login.State;
|
||||||
import org.mozilla.gecko.fxa.tasks.FxAccountSetupTask.ProgressDisplay;
|
import org.mozilla.gecko.fxa.tasks.FxAccountSetupTask.ProgressDisplay;
|
||||||
|
import org.mozilla.gecko.sync.ExtendedJSONObject;
|
||||||
import org.mozilla.gecko.sync.SyncConfiguration;
|
import org.mozilla.gecko.sync.SyncConfiguration;
|
||||||
import org.mozilla.gecko.sync.setup.Constants;
|
import org.mozilla.gecko.sync.setup.Constants;
|
||||||
import org.mozilla.gecko.sync.setup.activities.ActivityUtils;
|
import org.mozilla.gecko.sync.setup.activities.ActivityUtils;
|
||||||
|
@ -32,6 +33,7 @@ import android.accounts.AccountManager;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.os.AsyncTask;
|
import android.os.AsyncTask;
|
||||||
|
import android.os.Bundle;
|
||||||
import android.text.Editable;
|
import android.text.Editable;
|
||||||
import android.text.TextWatcher;
|
import android.text.TextWatcher;
|
||||||
import android.text.method.PasswordTransformationMethod;
|
import android.text.method.PasswordTransformationMethod;
|
||||||
|
@ -50,6 +52,16 @@ import android.widget.TextView;
|
||||||
import android.widget.TextView.OnEditorActionListener;
|
import android.widget.TextView.OnEditorActionListener;
|
||||||
|
|
||||||
abstract public class FxAccountAbstractSetupActivity extends FxAccountAbstractActivity implements ProgressDisplay {
|
abstract public class FxAccountAbstractSetupActivity extends FxAccountAbstractActivity implements ProgressDisplay {
|
||||||
|
public static final String EXTRA_EMAIL = "email";
|
||||||
|
public static final String EXTRA_PASSWORD = "password";
|
||||||
|
public static final String EXTRA_PASSWORD_SHOWN = "password_shown";
|
||||||
|
public static final String EXTRA_YEAR = "year";
|
||||||
|
public static final String EXTRA_EXTRAS = "extras";
|
||||||
|
|
||||||
|
public static final String JSON_KEY_AUTH = "auth";
|
||||||
|
public static final String JSON_KEY_SERVICES = "services";
|
||||||
|
public static final String JSON_KEY_SYNC = "sync";
|
||||||
|
|
||||||
public FxAccountAbstractSetupActivity() {
|
public FxAccountAbstractSetupActivity() {
|
||||||
super(CANNOT_RESUME_WHEN_ACCOUNTS_EXIST | CANNOT_RESUME_WHEN_LOCKED_OUT);
|
super(CANNOT_RESUME_WHEN_ACCOUNTS_EXIST | CANNOT_RESUME_WHEN_LOCKED_OUT);
|
||||||
}
|
}
|
||||||
|
@ -60,6 +72,10 @@ abstract public class FxAccountAbstractSetupActivity extends FxAccountAbstractAc
|
||||||
|
|
||||||
private static final String LOG_TAG = FxAccountAbstractSetupActivity.class.getSimpleName();
|
private static final String LOG_TAG = FxAccountAbstractSetupActivity.class.getSimpleName();
|
||||||
|
|
||||||
|
// By default, any custom server configuration is only shown when the account
|
||||||
|
// is configured to use a custom server.
|
||||||
|
private static boolean ALWAYS_SHOW_CUSTOM_SERVER_LAYOUT = false;
|
||||||
|
|
||||||
protected int minimumPasswordLength = 8;
|
protected int minimumPasswordLength = 8;
|
||||||
|
|
||||||
protected AutoCompleteTextView emailEdit;
|
protected AutoCompleteTextView emailEdit;
|
||||||
|
@ -69,18 +85,34 @@ abstract public class FxAccountAbstractSetupActivity extends FxAccountAbstractAc
|
||||||
protected Button button;
|
protected Button button;
|
||||||
protected ProgressBar progressBar;
|
protected ProgressBar progressBar;
|
||||||
|
|
||||||
|
private String authServerEndpoint;
|
||||||
|
private String syncServerEndpoint;
|
||||||
|
|
||||||
|
protected String getAuthServerEndpoint() {
|
||||||
|
return authServerEndpoint;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected String getTokenServerEndpoint() {
|
||||||
|
return syncServerEndpoint;
|
||||||
|
}
|
||||||
|
|
||||||
protected void createShowPasswordButton() {
|
protected void createShowPasswordButton() {
|
||||||
showPasswordButton.setOnClickListener(new OnClickListener() {
|
showPasswordButton.setOnClickListener(new OnClickListener() {
|
||||||
@SuppressWarnings("deprecation")
|
|
||||||
@Override
|
@Override
|
||||||
public void onClick(View v) {
|
public void onClick(View v) {
|
||||||
boolean isShown = passwordEdit.getTransformationMethod() instanceof SingleLineTransformationMethod;
|
boolean isShown = passwordEdit.getTransformationMethod() instanceof SingleLineTransformationMethod;
|
||||||
|
setPasswordButtonShown(!isShown);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("deprecation")
|
||||||
|
protected void setPasswordButtonShown(boolean shouldShow) {
|
||||||
// Changing input type loses position in edit text; let's try to maintain it.
|
// Changing input type loses position in edit text; let's try to maintain it.
|
||||||
int start = passwordEdit.getSelectionStart();
|
int start = passwordEdit.getSelectionStart();
|
||||||
int stop = passwordEdit.getSelectionEnd();
|
int stop = passwordEdit.getSelectionEnd();
|
||||||
|
|
||||||
if (isShown) {
|
if (!shouldShow) {
|
||||||
passwordEdit.setTransformationMethod(PasswordTransformationMethod.getInstance());
|
passwordEdit.setTransformationMethod(PasswordTransformationMethod.getInstance());
|
||||||
showPasswordButton.setText(R.string.fxaccount_password_show);
|
showPasswordButton.setText(R.string.fxaccount_password_show);
|
||||||
showPasswordButton.setBackgroundDrawable(getResources().getDrawable(R.drawable.fxaccount_password_button_show_background));
|
showPasswordButton.setBackgroundDrawable(getResources().getDrawable(R.drawable.fxaccount_password_button_show_background));
|
||||||
|
@ -93,8 +125,6 @@ abstract public class FxAccountAbstractSetupActivity extends FxAccountAbstractAc
|
||||||
}
|
}
|
||||||
passwordEdit.setSelection(start, stop);
|
passwordEdit.setSelection(start, stop);
|
||||||
}
|
}
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void linkifyPolicy() {
|
protected void linkifyPolicy() {
|
||||||
TextView policyView = (TextView) ensureFindViewById(null, R.id.policy, "policy links");
|
TextView policyView = (TextView) ensureFindViewById(null, R.id.policy, "policy links");
|
||||||
|
@ -262,7 +292,7 @@ abstract public class FxAccountAbstractSetupActivity extends FxAccountAbstractAc
|
||||||
AndroidFxAccount fxAccount;
|
AndroidFxAccount fxAccount;
|
||||||
try {
|
try {
|
||||||
final String profile = Constants.DEFAULT_PROFILE;
|
final String profile = Constants.DEFAULT_PROFILE;
|
||||||
final String tokenServerURI = FxAccountConstants.DEFAULT_TOKEN_SERVER_ENDPOINT;
|
final String tokenServerURI = getTokenServerEndpoint();
|
||||||
// It is crucial that we use the email address provided by the server
|
// It is crucial that we use the email address provided by the server
|
||||||
// (rather than whatever the user entered), because the user's keys are
|
// (rather than whatever the user entered), because the user's keys are
|
||||||
// wrapped and salted with the initial email they provided to
|
// wrapped and salted with the initial email they provided to
|
||||||
|
@ -356,6 +386,31 @@ abstract public class FxAccountAbstractSetupActivity extends FxAccountAbstractAc
|
||||||
emailEdit.setAdapter(adapter);
|
emailEdit.setAdapter(adapter);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void updateFromIntentExtras() {
|
||||||
|
// Only set email/password in onCreate; we don't want to overwrite edited values onResume.
|
||||||
|
if (getIntent() != null && getIntent().getExtras() != null) {
|
||||||
|
Bundle bundle = getIntent().getExtras();
|
||||||
|
emailEdit.setText(bundle.getString(EXTRA_EMAIL));
|
||||||
|
passwordEdit.setText(bundle.getString(EXTRA_PASSWORD));
|
||||||
|
setPasswordButtonShown(bundle.getBoolean(EXTRA_PASSWORD_SHOWN, false));
|
||||||
|
}
|
||||||
|
|
||||||
|
// This sets defaults as well as extracting from extras, so it's not conditional.
|
||||||
|
updateServersFromIntentExtras(getIntent());
|
||||||
|
|
||||||
|
if (FxAccountConstants.LOG_PERSONAL_INFORMATION) {
|
||||||
|
FxAccountConstants.pii(LOG_TAG, "Using auth server: " + authServerEndpoint);
|
||||||
|
FxAccountConstants.pii(LOG_TAG, "Using sync server: " + syncServerEndpoint);
|
||||||
|
}
|
||||||
|
|
||||||
|
updateCustomServerView();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onResume() {
|
public void onResume() {
|
||||||
super.onResume();
|
super.onResume();
|
||||||
|
@ -370,4 +425,110 @@ abstract public class FxAccountAbstractSetupActivity extends FxAccountAbstractAc
|
||||||
};
|
};
|
||||||
task.execute();
|
task.execute();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected Bundle makeExtrasBundle(String email, String password) {
|
||||||
|
final Bundle bundle = new Bundle();
|
||||||
|
|
||||||
|
// Pass through any extras that we were started with.
|
||||||
|
if (getIntent() != null && getIntent().getExtras() != null) {
|
||||||
|
bundle.putAll(getIntent().getExtras());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Overwrite with current settings.
|
||||||
|
if (email == null) {
|
||||||
|
email = emailEdit.getText().toString();
|
||||||
|
}
|
||||||
|
if (password == null) {
|
||||||
|
password = passwordEdit.getText().toString();
|
||||||
|
}
|
||||||
|
bundle.putString(EXTRA_EMAIL, email);
|
||||||
|
bundle.putString(EXTRA_PASSWORD, password);
|
||||||
|
|
||||||
|
boolean isPasswordShown = passwordEdit.getTransformationMethod() instanceof SingleLineTransformationMethod;
|
||||||
|
bundle.putBoolean(EXTRA_PASSWORD_SHOWN, isPasswordShown);
|
||||||
|
|
||||||
|
return bundle;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void startActivityInstead(Class<?> cls, int requestCode, Bundle extras) {
|
||||||
|
Intent intent = new Intent(this, cls);
|
||||||
|
if (extras != null) {
|
||||||
|
intent.putExtras(extras);
|
||||||
|
}
|
||||||
|
// Per http://stackoverflow.com/a/8992365, this triggers a known bug with
|
||||||
|
// the soft keyboard not being shown for the started activity. Why, Android, why?
|
||||||
|
intent.setFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION);
|
||||||
|
startActivityForResult(intent, requestCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void updateServersFromIntentExtras(Intent intent) {
|
||||||
|
// Start with defaults.
|
||||||
|
this.authServerEndpoint = FxAccountConstants.DEFAULT_AUTH_SERVER_ENDPOINT;
|
||||||
|
this.syncServerEndpoint = FxAccountConstants.DEFAULT_TOKEN_SERVER_ENDPOINT;
|
||||||
|
|
||||||
|
if (intent == null) {
|
||||||
|
Logger.warn(LOG_TAG, "Intent is null; ignoring and using default servers.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final String extrasString = intent.getStringExtra(EXTRA_EXTRAS);
|
||||||
|
|
||||||
|
if (extrasString == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final ExtendedJSONObject extras;
|
||||||
|
final ExtendedJSONObject services;
|
||||||
|
try {
|
||||||
|
extras = new ExtendedJSONObject(extrasString);
|
||||||
|
services = extras.getObject(JSON_KEY_SERVICES);
|
||||||
|
} catch (Exception e) {
|
||||||
|
Logger.warn(LOG_TAG, "Got exception parsing extras; ignoring and using default servers.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
String authServer = extras.getString(JSON_KEY_AUTH);
|
||||||
|
String syncServer = services == null ? null : services.getString(JSON_KEY_SYNC);
|
||||||
|
|
||||||
|
if (authServer != null) {
|
||||||
|
this.authServerEndpoint = authServer;
|
||||||
|
}
|
||||||
|
if (syncServer != null) {
|
||||||
|
this.syncServerEndpoint = syncServer;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (FxAccountConstants.DEFAULT_TOKEN_SERVER_ENDPOINT.equals(syncServerEndpoint) &&
|
||||||
|
!FxAccountConstants.DEFAULT_AUTH_SERVER_ENDPOINT.equals(authServerEndpoint)) {
|
||||||
|
// We really don't want to hard-code assumptions about server
|
||||||
|
// configurations into client code in such a way that if and when the
|
||||||
|
// situation is relaxed, the client code stops valid usage. Instead, we
|
||||||
|
// warn. This configuration should present itself as an auth exception at
|
||||||
|
// Sync time.
|
||||||
|
Logger.warn(LOG_TAG, "Mozilla's Sync token servers only works with Mozilla's auth servers. Sync will likely be mis-configured.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void updateCustomServerView() {
|
||||||
|
final boolean shouldShow =
|
||||||
|
ALWAYS_SHOW_CUSTOM_SERVER_LAYOUT ||
|
||||||
|
!FxAccountConstants.DEFAULT_AUTH_SERVER_ENDPOINT.equals(authServerEndpoint) ||
|
||||||
|
!FxAccountConstants.DEFAULT_TOKEN_SERVER_ENDPOINT.equals(syncServerEndpoint);
|
||||||
|
|
||||||
|
if (!shouldShow) {
|
||||||
|
setCustomServerViewVisibility(View.GONE);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final TextView authServerView = (TextView) ensureFindViewById(null, R.id.account_server_summary, "account server");
|
||||||
|
final TextView syncServerView = (TextView) ensureFindViewById(null, R.id.sync_server_summary, "Sync server");
|
||||||
|
authServerView.setText(authServerEndpoint);
|
||||||
|
syncServerView.setText(syncServerEndpoint);
|
||||||
|
|
||||||
|
setCustomServerViewVisibility(View.VISIBLE);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void setCustomServerViewVisibility(int visibility) {
|
||||||
|
ensureFindViewById(null, R.id.account_server_layout, "account server layout").setVisibility(visibility);
|
||||||
|
ensureFindViewById(null, R.id.sync_server_layout, "sync server layout").setVisibility(visibility);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -89,32 +89,29 @@ public class FxAccountCreateAccountActivity extends FxAccountAbstractSetupActivi
|
||||||
signInInsteadLink.setOnClickListener(new OnClickListener() {
|
signInInsteadLink.setOnClickListener(new OnClickListener() {
|
||||||
@Override
|
@Override
|
||||||
public void onClick(View v) {
|
public void onClick(View v) {
|
||||||
final String email = emailEdit.getText().toString();
|
final Bundle extras = makeExtrasBundle(null, null);
|
||||||
final String password = passwordEdit.getText().toString();
|
startActivityInstead(FxAccountSignInActivity.class, CHILD_REQUEST_CODE, extras);
|
||||||
doSigninInstead(email, password);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Only set email/password in onCreate; we don't want to overwrite edited values onResume.
|
updateFromIntentExtras();
|
||||||
if (getIntent() != null && getIntent().getExtras() != null) {
|
|
||||||
Bundle bundle = getIntent().getExtras();
|
|
||||||
emailEdit.setText(bundle.getString("email"));
|
|
||||||
passwordEdit.setText(bundle.getString("password"));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void doSigninInstead(final String email, final String password) {
|
@Override
|
||||||
Intent intent = new Intent(this, FxAccountSignInActivity.class);
|
protected Bundle makeExtrasBundle(String email, String password) {
|
||||||
if (email != null) {
|
final Bundle extras = super.makeExtrasBundle(email, password);
|
||||||
intent.putExtra("email", email);
|
final String year = yearEdit.getText().toString();
|
||||||
|
extras.putString(EXTRA_YEAR, year);
|
||||||
|
return extras;
|
||||||
}
|
}
|
||||||
if (password != null) {
|
|
||||||
intent.putExtra("password", password);
|
@Override
|
||||||
|
protected void updateFromIntentExtras() {
|
||||||
|
super.updateFromIntentExtras();
|
||||||
|
|
||||||
|
if (getIntent() != null) {
|
||||||
|
yearEdit.setText(getIntent().getStringExtra(EXTRA_YEAR));
|
||||||
}
|
}
|
||||||
// Per http://stackoverflow.com/a/8992365, this triggers a known bug with
|
|
||||||
// the soft keyboard not being shown for the started activity. Why, Android, why?
|
|
||||||
intent.setFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION);
|
|
||||||
startActivityForResult(intent, CHILD_REQUEST_CODE);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -142,7 +139,9 @@ public class FxAccountCreateAccountActivity extends FxAccountAbstractSetupActivi
|
||||||
email = emailEdit.getText().toString();
|
email = emailEdit.getText().toString();
|
||||||
}
|
}
|
||||||
final String password = passwordEdit.getText().toString();
|
final String password = passwordEdit.getText().toString();
|
||||||
doSigninInstead(email, password);
|
|
||||||
|
final Bundle extras = makeExtrasBundle(email, password);
|
||||||
|
startActivityInstead(FxAccountSignInActivity.class, CHILD_REQUEST_CODE, extras);
|
||||||
}
|
}
|
||||||
}, clickableStart, clickableEnd, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
}, clickableStart, clickableEnd, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||||
remoteErrorTextView.setMovementMethod(LinkMovementMethod.getInstance());
|
remoteErrorTextView.setMovementMethod(LinkMovementMethod.getInstance());
|
||||||
|
@ -205,7 +204,7 @@ public class FxAccountCreateAccountActivity extends FxAccountAbstractSetupActivi
|
||||||
}
|
}
|
||||||
|
|
||||||
public void createAccount(String email, String password, Map<String, Boolean> engines) {
|
public void createAccount(String email, String password, Map<String, Boolean> engines) {
|
||||||
String serverURI = FxAccountConstants.DEFAULT_AUTH_SERVER_ENDPOINT;
|
String serverURI = getAuthServerEndpoint();
|
||||||
PasswordStretcher passwordStretcher = makePasswordStretcher(password);
|
PasswordStretcher passwordStretcher = makePasswordStretcher(password);
|
||||||
// This delegate creates a new Android account on success, opens the
|
// This delegate creates a new Android account on success, opens the
|
||||||
// appropriate "success!" activity, and finishes this activity.
|
// appropriate "success!" activity, and finishes this activity.
|
||||||
|
|
|
@ -50,13 +50,24 @@ public class FxAccountGetStartedActivity extends AccountAuthenticatorActivity {
|
||||||
button.setOnClickListener(new OnClickListener() {
|
button.setOnClickListener(new OnClickListener() {
|
||||||
@Override
|
@Override
|
||||||
public void onClick(View v) {
|
public void onClick(View v) {
|
||||||
Intent intent = new Intent(FxAccountGetStartedActivity.this, FxAccountCreateAccountActivity.class);
|
Bundle extras = null; // startFlow accepts null.
|
||||||
|
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
|
// Per http://stackoverflow.com/a/8992365, this triggers a known bug with
|
||||||
// the soft keyboard not being shown for the started activity. Why, Android, why?
|
// the soft keyboard not being shown for the started activity. Why, Android, why?
|
||||||
intent.setFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION);
|
intent.setFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION);
|
||||||
startActivityForResult(intent, CHILD_REQUEST_CODE);
|
if (extras != null) {
|
||||||
|
intent.putExtras(extras);
|
||||||
}
|
}
|
||||||
});
|
startActivityForResult(intent, CHILD_REQUEST_CODE);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -79,6 +90,20 @@ public class FxAccountGetStartedActivity extends AccountAuthenticatorActivity {
|
||||||
this.startActivity(intent);
|
this.startActivity(intent);
|
||||||
this.finish();
|
this.finish();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If we've been launched with extras (namely custom server URLs), continue
|
||||||
|
// past go and collect 200 dollars. If we ever get back here (for example,
|
||||||
|
// if the user hits the back button), forget that we had extras entirely, so
|
||||||
|
// that we don't enter a loop.
|
||||||
|
Bundle extras = null;
|
||||||
|
if (getIntent() != null) {
|
||||||
|
extras = getIntent().getExtras();
|
||||||
|
}
|
||||||
|
if (extras != null && extras.containsKey(FxAccountAbstractSetupActivity.EXTRA_EXTRAS)) {
|
||||||
|
getIntent().replaceExtras(Bundle.EMPTY);
|
||||||
|
startFlow((Bundle) extras.clone());
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -15,7 +15,6 @@ import org.mozilla.gecko.background.fxa.FxAccountClient20;
|
||||||
import org.mozilla.gecko.background.fxa.FxAccountClient20.LoginResponse;
|
import org.mozilla.gecko.background.fxa.FxAccountClient20.LoginResponse;
|
||||||
import org.mozilla.gecko.background.fxa.FxAccountClientException.FxAccountClientRemoteException;
|
import org.mozilla.gecko.background.fxa.FxAccountClientException.FxAccountClientRemoteException;
|
||||||
import org.mozilla.gecko.background.fxa.PasswordStretcher;
|
import org.mozilla.gecko.background.fxa.PasswordStretcher;
|
||||||
import org.mozilla.gecko.fxa.FxAccountConstants;
|
|
||||||
import org.mozilla.gecko.fxa.tasks.FxAccountSignInTask;
|
import org.mozilla.gecko.fxa.tasks.FxAccountSignInTask;
|
||||||
import org.mozilla.gecko.sync.setup.activities.ActivityUtils;
|
import org.mozilla.gecko.sync.setup.activities.ActivityUtils;
|
||||||
|
|
||||||
|
@ -65,22 +64,12 @@ public class FxAccountSignInActivity extends FxAccountAbstractSetupActivity {
|
||||||
createAccountInsteadLink.setOnClickListener(new OnClickListener() {
|
createAccountInsteadLink.setOnClickListener(new OnClickListener() {
|
||||||
@Override
|
@Override
|
||||||
public void onClick(View v) {
|
public void onClick(View v) {
|
||||||
Intent intent = new Intent(FxAccountSignInActivity.this, FxAccountCreateAccountActivity.class);
|
final Bundle extras = makeExtrasBundle(null, null);
|
||||||
intent.putExtra("email", emailEdit.getText().toString());
|
startActivityInstead(FxAccountCreateAccountActivity.class, CHILD_REQUEST_CODE, extras);
|
||||||
intent.putExtra("password", passwordEdit.getText().toString());
|
|
||||||
// Per http://stackoverflow.com/a/8992365, this triggers a known bug with
|
|
||||||
// the soft keyboard not being shown for the started activity. Why, Android, why?
|
|
||||||
intent.setFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION);
|
|
||||||
startActivityForResult(intent, CHILD_REQUEST_CODE);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Only set email/password in onCreate; we don't want to overwrite edited values onResume.
|
updateFromIntentExtras();
|
||||||
if (getIntent() != null && getIntent().getExtras() != null) {
|
|
||||||
Bundle bundle = getIntent().getExtras();
|
|
||||||
emailEdit.setText(bundle.getString("email"));
|
|
||||||
passwordEdit.setText(bundle.getString("password"));
|
|
||||||
}
|
|
||||||
|
|
||||||
TextView view = (TextView) findViewById(R.id.forgot_password_link);
|
TextView view = (TextView) findViewById(R.id.forgot_password_link);
|
||||||
ActivityUtils.linkTextView(view, R.string.fxaccount_sign_in_forgot_password, R.string.fxaccount_link_forgot_password);
|
ActivityUtils.linkTextView(view, R.string.fxaccount_sign_in_forgot_password, R.string.fxaccount_link_forgot_password);
|
||||||
|
@ -102,7 +91,7 @@ public class FxAccountSignInActivity extends FxAccountAbstractSetupActivity {
|
||||||
}
|
}
|
||||||
|
|
||||||
public void signIn(String email, String password) {
|
public void signIn(String email, String password) {
|
||||||
String serverURI = FxAccountConstants.DEFAULT_AUTH_SERVER_ENDPOINT;
|
String serverURI = getAuthServerEndpoint();
|
||||||
PasswordStretcher passwordStretcher = makePasswordStretcher(password);
|
PasswordStretcher passwordStretcher = makePasswordStretcher(password);
|
||||||
// This delegate creates a new Android account on success, opens the
|
// This delegate creates a new Android account on success, opens the
|
||||||
// appropriate "success!" activity, and finishes this activity.
|
// appropriate "success!" activity, and finishes this activity.
|
||||||
|
|
|
@ -18,6 +18,7 @@ import org.mozilla.gecko.fxa.login.Married;
|
||||||
import org.mozilla.gecko.fxa.login.State;
|
import org.mozilla.gecko.fxa.login.State;
|
||||||
import org.mozilla.gecko.fxa.sync.FxAccountSyncStatusHelper;
|
import org.mozilla.gecko.fxa.sync.FxAccountSyncStatusHelper;
|
||||||
import org.mozilla.gecko.fxa.tasks.FxAccountCodeResender;
|
import org.mozilla.gecko.fxa.tasks.FxAccountCodeResender;
|
||||||
|
import org.mozilla.gecko.sync.ExtendedJSONObject;
|
||||||
import org.mozilla.gecko.sync.SharedPreferencesClientsDataDelegate;
|
import org.mozilla.gecko.sync.SharedPreferencesClientsDataDelegate;
|
||||||
import org.mozilla.gecko.sync.SyncConfiguration;
|
import org.mozilla.gecko.sync.SyncConfiguration;
|
||||||
|
|
||||||
|
@ -56,7 +57,17 @@ public class FxAccountStatusFragment
|
||||||
// collection.
|
// collection.
|
||||||
private static final long DELAY_IN_MILLISECONDS_BEFORE_REQUESTING_SYNC = 5 * 1000;
|
private static final long DELAY_IN_MILLISECONDS_BEFORE_REQUESTING_SYNC = 5 * 1000;
|
||||||
|
|
||||||
|
// By default, the auth/account server preference is only shown when the
|
||||||
|
// account is configured to use a custom server. In debug mode, this is set.
|
||||||
|
private static boolean ALWAYS_SHOW_AUTH_SERVER = false;
|
||||||
|
|
||||||
|
// By default, the Sync server preference is only shown when the account is
|
||||||
|
// configured to use a custom Sync server. In debug mode, this is set.
|
||||||
|
private static boolean ALWAYS_SHOW_SYNC_SERVER = false;
|
||||||
|
|
||||||
|
protected PreferenceCategory accountCategory;
|
||||||
protected Preference emailPreference;
|
protected Preference emailPreference;
|
||||||
|
protected Preference authServerPreference;
|
||||||
|
|
||||||
protected Preference needsPasswordPreference;
|
protected Preference needsPasswordPreference;
|
||||||
protected Preference needsUpgradePreference;
|
protected Preference needsUpgradePreference;
|
||||||
|
@ -72,6 +83,7 @@ public class FxAccountStatusFragment
|
||||||
protected CheckBoxPreference passwordsPreference;
|
protected CheckBoxPreference passwordsPreference;
|
||||||
|
|
||||||
protected EditTextPreference deviceNamePreference;
|
protected EditTextPreference deviceNamePreference;
|
||||||
|
protected Preference syncServerPreference;
|
||||||
|
|
||||||
protected volatile AndroidFxAccount fxAccount;
|
protected volatile AndroidFxAccount fxAccount;
|
||||||
// The contract is: when fxAccount is non-null, then clientsDataDelegate is
|
// The contract is: when fxAccount is non-null, then clientsDataDelegate is
|
||||||
|
@ -105,7 +117,9 @@ public class FxAccountStatusFragment
|
||||||
protected void addPreferences() {
|
protected void addPreferences() {
|
||||||
addPreferencesFromResource(R.xml.fxaccount_status_prefscreen);
|
addPreferencesFromResource(R.xml.fxaccount_status_prefscreen);
|
||||||
|
|
||||||
|
accountCategory = (PreferenceCategory) ensureFindPreference("signed_in_as_category");
|
||||||
emailPreference = ensureFindPreference("email");
|
emailPreference = ensureFindPreference("email");
|
||||||
|
authServerPreference = ensureFindPreference("auth_server");
|
||||||
|
|
||||||
needsPasswordPreference = ensureFindPreference("needs_credentials");
|
needsPasswordPreference = ensureFindPreference("needs_credentials");
|
||||||
needsUpgradePreference = ensureFindPreference("needs_upgrade");
|
needsUpgradePreference = ensureFindPreference("needs_upgrade");
|
||||||
|
@ -124,6 +138,8 @@ public class FxAccountStatusFragment
|
||||||
removeDebugButtons();
|
removeDebugButtons();
|
||||||
} else {
|
} else {
|
||||||
connectDebugButtons();
|
connectDebugButtons();
|
||||||
|
ALWAYS_SHOW_AUTH_SERVER = true;
|
||||||
|
ALWAYS_SHOW_SYNC_SERVER = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
needsPasswordPreference.setOnPreferenceClickListener(this);
|
needsPasswordPreference.setOnPreferenceClickListener(this);
|
||||||
|
@ -137,6 +153,8 @@ public class FxAccountStatusFragment
|
||||||
|
|
||||||
deviceNamePreference = (EditTextPreference) ensureFindPreference("device_name");
|
deviceNamePreference = (EditTextPreference) ensureFindPreference("device_name");
|
||||||
deviceNamePreference.setOnPreferenceChangeListener(this);
|
deviceNamePreference.setOnPreferenceChangeListener(this);
|
||||||
|
|
||||||
|
syncServerPreference = ensureFindPreference("sync_server");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -152,6 +170,10 @@ public class FxAccountStatusFragment
|
||||||
public boolean onPreferenceClick(Preference preference) {
|
public boolean onPreferenceClick(Preference preference) {
|
||||||
if (preference == needsPasswordPreference) {
|
if (preference == needsPasswordPreference) {
|
||||||
Intent intent = new Intent(getActivity(), FxAccountUpdateCredentialsActivity.class);
|
Intent intent = new Intent(getActivity(), FxAccountUpdateCredentialsActivity.class);
|
||||||
|
final Bundle extras = getExtrasForAccount();
|
||||||
|
if (extras != null) {
|
||||||
|
intent.putExtras(extras);
|
||||||
|
}
|
||||||
// Per http://stackoverflow.com/a/8992365, this triggers a known bug with
|
// Per http://stackoverflow.com/a/8992365, this triggers a known bug with
|
||||||
// the soft keyboard not being shown for the started activity. Why, Android, why?
|
// the soft keyboard not being shown for the started activity. Why, Android, why?
|
||||||
intent.setFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION);
|
intent.setFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION);
|
||||||
|
@ -190,6 +212,17 @@ public class FxAccountStatusFragment
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected Bundle getExtrasForAccount() {
|
||||||
|
final Bundle extras = new Bundle();
|
||||||
|
final ExtendedJSONObject o = new ExtendedJSONObject();
|
||||||
|
o.put(FxAccountAbstractSetupActivity.JSON_KEY_AUTH, fxAccount.getAccountServerURI());
|
||||||
|
final ExtendedJSONObject services = new ExtendedJSONObject();
|
||||||
|
services.put(FxAccountAbstractSetupActivity.JSON_KEY_SYNC, fxAccount.getTokenServerURI());
|
||||||
|
o.put(FxAccountAbstractSetupActivity.JSON_KEY_SERVICES, services);
|
||||||
|
extras.putString(FxAccountAbstractSetupActivity.EXTRA_EXTRAS, o.toJSONString());
|
||||||
|
return extras;
|
||||||
|
}
|
||||||
|
|
||||||
protected void setCheckboxesEnabled(boolean enabled) {
|
protected void setCheckboxesEnabled(boolean enabled) {
|
||||||
bookmarksPreference.setEnabled(enabled);
|
bookmarksPreference.setEnabled(enabled);
|
||||||
historyPreference.setEnabled(enabled);
|
historyPreference.setEnabled(enabled);
|
||||||
|
@ -367,6 +400,8 @@ public class FxAccountStatusFragment
|
||||||
}
|
}
|
||||||
|
|
||||||
emailPreference.setTitle(fxAccount.getEmail());
|
emailPreference.setTitle(fxAccount.getEmail());
|
||||||
|
updateAuthServerPreference();
|
||||||
|
updateSyncServerPreference();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// There are error states determined by Android, not the login state
|
// There are error states determined by Android, not the login state
|
||||||
|
@ -417,6 +452,38 @@ public class FxAccountStatusFragment
|
||||||
deviceNamePreference.setText(clientName);
|
deviceNamePreference.setText(clientName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected void updateAuthServerPreference() {
|
||||||
|
final String authServer = fxAccount.getAccountServerURI();
|
||||||
|
final boolean shouldBeShown = ALWAYS_SHOW_AUTH_SERVER || !FxAccountConstants.DEFAULT_AUTH_SERVER_ENDPOINT.equals(authServer);
|
||||||
|
final boolean currentlyShown = null != findPreference(authServerPreference.getKey());
|
||||||
|
if (currentlyShown != shouldBeShown) {
|
||||||
|
if (shouldBeShown) {
|
||||||
|
accountCategory.addPreference(authServerPreference);
|
||||||
|
} else {
|
||||||
|
accountCategory.removePreference(authServerPreference);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Always set the summary, because on first run, the preference is visible,
|
||||||
|
// and the above block will be skipped if there is a custom value.
|
||||||
|
authServerPreference.setSummary(authServer);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void updateSyncServerPreference() {
|
||||||
|
final String syncServer = fxAccount.getTokenServerURI();
|
||||||
|
final boolean shouldBeShown = ALWAYS_SHOW_SYNC_SERVER || !FxAccountConstants.DEFAULT_TOKEN_SERVER_ENDPOINT.equals(syncServer);
|
||||||
|
final boolean currentlyShown = null != findPreference(syncServerPreference.getKey());
|
||||||
|
if (currentlyShown != shouldBeShown) {
|
||||||
|
if (shouldBeShown) {
|
||||||
|
syncCategory.addPreference(syncServerPreference);
|
||||||
|
} else {
|
||||||
|
syncCategory.removePreference(syncServerPreference);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Always set the summary, because on first run, the preference is visible,
|
||||||
|
// and the above block will be skipped if there is a custom value.
|
||||||
|
syncServerPreference.setSummary(syncServer);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Query shared prefs for the current engine state, and update the UI
|
* Query shared prefs for the current engine state, and update the UI
|
||||||
* accordingly.
|
* accordingly.
|
||||||
|
|
|
@ -77,6 +77,8 @@ public class FxAccountUpdateCredentialsActivity extends FxAccountAbstractSetupAc
|
||||||
|
|
||||||
TextView view = (TextView) findViewById(R.id.forgot_password_link);
|
TextView view = (TextView) findViewById(R.id.forgot_password_link);
|
||||||
ActivityUtils.linkTextView(view, R.string.fxaccount_sign_in_forgot_password, R.string.fxaccount_link_forgot_password);
|
ActivityUtils.linkTextView(view, R.string.fxaccount_sign_in_forgot_password, R.string.fxaccount_link_forgot_password);
|
||||||
|
|
||||||
|
updateFromIntentExtras();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -56,18 +56,18 @@ public class AccountPickler {
|
||||||
|
|
||||||
public static final long PICKLE_VERSION = 2;
|
public static final long PICKLE_VERSION = 2;
|
||||||
|
|
||||||
private static final String KEY_PICKLE_VERSION = "pickle_version";
|
public static final String KEY_PICKLE_VERSION = "pickle_version";
|
||||||
private static final String KEY_PICKLE_TIMESTAMP = "pickle_timestamp";
|
public static final String KEY_PICKLE_TIMESTAMP = "pickle_timestamp";
|
||||||
|
|
||||||
private static final String KEY_ACCOUNT_VERSION = "account_version";
|
public static final String KEY_ACCOUNT_VERSION = "account_version";
|
||||||
private static final String KEY_ACCOUNT_TYPE = "account_type";
|
public static final String KEY_ACCOUNT_TYPE = "account_type";
|
||||||
private static final String KEY_EMAIL = "email";
|
public static final String KEY_EMAIL = "email";
|
||||||
private static final String KEY_PROFILE = "profile";
|
public static final String KEY_PROFILE = "profile";
|
||||||
private static final String KEY_IDP_SERVER_URI = "idpServerURI";
|
public static final String KEY_IDP_SERVER_URI = "idpServerURI";
|
||||||
private static final String KEY_TOKEN_SERVER_URI = "tokenServerURI";
|
public static final String KEY_TOKEN_SERVER_URI = "tokenServerURI";
|
||||||
private static final String KEY_IS_SYNCING_ENABLED = "isSyncingEnabled";
|
public static final String KEY_IS_SYNCING_ENABLED = "isSyncingEnabled";
|
||||||
|
|
||||||
private static final String KEY_BUNDLE = "bundle";
|
public static final String KEY_BUNDLE = "bundle";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Remove Firefox account persisted to disk.
|
* Remove Firefox account persisted to disk.
|
||||||
|
@ -80,16 +80,10 @@ public class AccountPickler {
|
||||||
return context.deleteFile(filename);
|
return context.deleteFile(filename);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public static ExtendedJSONObject toJSON(final AndroidFxAccount account, final long now) {
|
||||||
* Persist Firefox account to disk as a JSON object.
|
|
||||||
*
|
|
||||||
* @param AndroidFxAccount the account to persist to disk
|
|
||||||
* @param filename name of file to persist to; must not contain path separators.
|
|
||||||
*/
|
|
||||||
public static void pickle(final AndroidFxAccount account, final String filename) {
|
|
||||||
final ExtendedJSONObject o = new ExtendedJSONObject();
|
final ExtendedJSONObject o = new ExtendedJSONObject();
|
||||||
o.put(KEY_PICKLE_VERSION, Long.valueOf(PICKLE_VERSION));
|
o.put(KEY_PICKLE_VERSION, Long.valueOf(PICKLE_VERSION));
|
||||||
o.put(KEY_PICKLE_TIMESTAMP, Long.valueOf(System.currentTimeMillis()));
|
o.put(KEY_PICKLE_TIMESTAMP, Long.valueOf(now));
|
||||||
|
|
||||||
o.put(KEY_ACCOUNT_VERSION, AndroidFxAccount.CURRENT_ACCOUNT_VERSION);
|
o.put(KEY_ACCOUNT_VERSION, AndroidFxAccount.CURRENT_ACCOUNT_VERSION);
|
||||||
o.put(KEY_ACCOUNT_TYPE, FxAccountConstants.ACCOUNT_TYPE);
|
o.put(KEY_ACCOUNT_TYPE, FxAccountConstants.ACCOUNT_TYPE);
|
||||||
|
@ -104,10 +98,21 @@ public class AccountPickler {
|
||||||
final ExtendedJSONObject bundle = account.unbundle();
|
final ExtendedJSONObject bundle = account.unbundle();
|
||||||
if (bundle == null) {
|
if (bundle == null) {
|
||||||
Logger.warn(LOG_TAG, "Unable to obtain account bundle; aborting.");
|
Logger.warn(LOG_TAG, "Unable to obtain account bundle; aborting.");
|
||||||
return;
|
return null;
|
||||||
}
|
}
|
||||||
o.put(KEY_BUNDLE, bundle);
|
o.put(KEY_BUNDLE, bundle);
|
||||||
|
|
||||||
|
return o;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Persist Firefox account to disk as a JSON object.
|
||||||
|
*
|
||||||
|
* @param AndroidFxAccount the account to persist to disk
|
||||||
|
* @param filename name of file to persist to; must not contain path separators.
|
||||||
|
*/
|
||||||
|
public static void pickle(final AndroidFxAccount account, final String filename) {
|
||||||
|
final ExtendedJSONObject o = toJSON(account, System.currentTimeMillis());
|
||||||
writeToDisk(account.context, filename, o);
|
writeToDisk(account.context, filename, o);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -323,6 +323,9 @@ public class AndroidFxAccount {
|
||||||
if (email == null) {
|
if (email == null) {
|
||||||
throw new IllegalArgumentException("email must not be null");
|
throw new IllegalArgumentException("email must not be null");
|
||||||
}
|
}
|
||||||
|
if (profile == null) {
|
||||||
|
throw new IllegalArgumentException("profile must not be null");
|
||||||
|
}
|
||||||
if (idpServerURI == null) {
|
if (idpServerURI == null) {
|
||||||
throw new IllegalArgumentException("idpServerURI must not be null");
|
throw new IllegalArgumentException("idpServerURI must not be null");
|
||||||
}
|
}
|
||||||
|
@ -368,6 +371,15 @@ public class AndroidFxAccount {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Try to work around an intermittent issue described at
|
||||||
|
// http://stackoverflow.com/a/11698139. What happens is that tests that
|
||||||
|
// delete and re-create the same account frequently will find the account
|
||||||
|
// missing all or some of the userdata bundle, possibly due to an Android
|
||||||
|
// AccountManager caching bug.
|
||||||
|
for (String key : userdata.keySet()) {
|
||||||
|
accountManager.setUserData(account, key, userdata.getString(key));
|
||||||
|
}
|
||||||
|
|
||||||
AndroidFxAccount fxAccount = new AndroidFxAccount(context, account);
|
AndroidFxAccount fxAccount = new AndroidFxAccount(context, account);
|
||||||
|
|
||||||
if (!fromPickle) {
|
if (!fromPickle) {
|
||||||
|
|
|
@ -5,23 +5,81 @@
|
||||||
|
|
||||||
package org.mozilla.gecko.home;
|
package org.mozilla.gecko.home;
|
||||||
|
|
||||||
import org.mozilla.gecko.R;
|
import java.util.Date;
|
||||||
import org.mozilla.gecko.widget.IconTabWidget;
|
import java.util.EnumSet;
|
||||||
|
|
||||||
import android.content.res.Configuration;
|
import org.mozilla.gecko.R;
|
||||||
|
import org.mozilla.gecko.Telemetry;
|
||||||
|
import org.mozilla.gecko.TelemetryContract;
|
||||||
|
import org.mozilla.gecko.db.BrowserContract.Combined;
|
||||||
|
import org.mozilla.gecko.db.BrowserDB;
|
||||||
|
import org.mozilla.gecko.db.BrowserDB.URLColumns;
|
||||||
|
import org.mozilla.gecko.home.HomePager.OnUrlOpenListener;
|
||||||
|
import org.mozilla.gecko.util.ThreadUtils;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.app.AlertDialog;
|
||||||
|
import android.content.ContentResolver;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.DialogInterface;
|
||||||
|
import android.database.Cursor;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.support.v4.app.Fragment;
|
import android.support.v4.app.LoaderManager.LoaderCallbacks;
|
||||||
|
import android.support.v4.content.Loader;
|
||||||
|
import android.util.SparseArray;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
|
import android.view.ViewStub;
|
||||||
|
import android.widget.AdapterView;
|
||||||
|
import android.widget.ImageView;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
public class HistoryPanel extends HomeFragment
|
/**
|
||||||
implements IconTabWidget.OnTabChangedListener {
|
* Fragment that displays recent history in a ListView.
|
||||||
|
*/
|
||||||
|
public class HistoryPanel extends HomeFragment {
|
||||||
// Logging tag name
|
// Logging tag name
|
||||||
private static final String LOGTAG = "GeckoHistoryPanel";
|
private static final String LOGTAG = "GeckoHistoryPanel";
|
||||||
private IconTabWidget mTabWidget;
|
|
||||||
private int mSelectedTab;
|
// Cursor loader ID for history query
|
||||||
private boolean initializeRecentPanel;
|
private static final int LOADER_ID_HISTORY = 0;
|
||||||
|
|
||||||
|
// Adapter for the list of recent history entries.
|
||||||
|
private HistoryAdapter mAdapter;
|
||||||
|
|
||||||
|
// The view shown by the fragment.
|
||||||
|
private HomeListView mList;
|
||||||
|
|
||||||
|
// The button view for clearing browsing history.
|
||||||
|
private View mClearHistoryButton;
|
||||||
|
|
||||||
|
// Reference to the View to display when there are no results.
|
||||||
|
private View mEmptyView;
|
||||||
|
|
||||||
|
// Callbacks used for the search and favicon cursor loaders
|
||||||
|
private CursorLoaderCallbacks mCursorLoaderCallbacks;
|
||||||
|
|
||||||
|
// On URL open listener
|
||||||
|
private OnUrlOpenListener mUrlOpenListener;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onAttach(Activity activity) {
|
||||||
|
super.onAttach(activity);
|
||||||
|
|
||||||
|
try {
|
||||||
|
mUrlOpenListener = (OnUrlOpenListener) activity;
|
||||||
|
} catch (ClassCastException e) {
|
||||||
|
throw new ClassCastException(activity.toString()
|
||||||
|
+ " must implement HomePager.OnUrlOpenListener");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDetach() {
|
||||||
|
super.onDetach();
|
||||||
|
mUrlOpenListener = null;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||||
|
@ -30,75 +88,335 @@ public class HistoryPanel extends HomeFragment
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onViewCreated(View view, Bundle savedInstanceState) {
|
public void onViewCreated(View view, Bundle savedInstanceState) {
|
||||||
super.onViewCreated(view, savedInstanceState);
|
mList = (HomeListView) view.findViewById(R.id.list);
|
||||||
|
mList.setTag(HomePager.LIST_TAG_HISTORY);
|
||||||
|
|
||||||
mTabWidget = (IconTabWidget) view.findViewById(R.id.tab_icon_widget);
|
mList.setOnItemClickListener(new AdapterView.OnItemClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
|
||||||
|
position -= mAdapter.getMostRecentSectionsCountBefore(position);
|
||||||
|
final Cursor c = mAdapter.getCursor(position);
|
||||||
|
final String url = c.getString(c.getColumnIndexOrThrow(URLColumns.URL));
|
||||||
|
|
||||||
mTabWidget.addTab(R.drawable.icon_most_recent, R.string.home_most_recent_title);
|
Telemetry.sendUIEvent(TelemetryContract.Event.LOAD_URL);
|
||||||
mTabWidget.addTab(R.drawable.icon_last_tabs, R.string.home_last_tabs_title);
|
|
||||||
|
|
||||||
mTabWidget.setTabSelectionListener(this);
|
// This item is a TwoLinePageRow, so we allow switch-to-tab.
|
||||||
mTabWidget.setCurrentTab(mSelectedTab);
|
mUrlOpenListener.onUrlOpen(url, EnumSet.of(OnUrlOpenListener.Flags.ALLOW_SWITCH_TO_TAB));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
mList.setContextMenuInfoFactory(new HomeContextMenuInfo.Factory() {
|
||||||
|
@Override
|
||||||
|
public HomeContextMenuInfo makeInfoForCursor(View view, int position, long id, Cursor cursor) {
|
||||||
|
final HomeContextMenuInfo info = new HomeContextMenuInfo(view, position, id);
|
||||||
|
info.url = cursor.getString(cursor.getColumnIndexOrThrow(Combined.URL));
|
||||||
|
info.title = cursor.getString(cursor.getColumnIndexOrThrow(Combined.TITLE));
|
||||||
|
info.display = cursor.getInt(cursor.getColumnIndexOrThrow(Combined.DISPLAY));
|
||||||
|
info.historyId = cursor.getInt(cursor.getColumnIndexOrThrow(Combined.HISTORY_ID));
|
||||||
|
final int bookmarkIdCol = cursor.getColumnIndexOrThrow(Combined.BOOKMARK_ID);
|
||||||
|
if (cursor.isNull(bookmarkIdCol)) {
|
||||||
|
// If this is a combined cursor, we may get a history item without a
|
||||||
|
// bookmark, in which case the bookmarks ID column value will be null.
|
||||||
|
info.bookmarkId = -1;
|
||||||
|
} else {
|
||||||
|
info.bookmarkId = cursor.getInt(bookmarkIdCol);
|
||||||
|
}
|
||||||
|
return info;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
registerForContextMenu(mList);
|
||||||
|
|
||||||
|
mClearHistoryButton = view.findViewById(R.id.clear_history_button);
|
||||||
|
mClearHistoryButton.setOnClickListener(new View.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(View v) {
|
||||||
|
final Context context = getActivity();
|
||||||
|
|
||||||
|
final AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(context);
|
||||||
|
dialogBuilder.setMessage(R.string.home_clear_history_confirm);
|
||||||
|
|
||||||
|
dialogBuilder.setNegativeButton(R.string.button_cancel, new AlertDialog.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(final DialogInterface dialog, final int which) {
|
||||||
|
dialog.dismiss();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
dialogBuilder.setPositiveButton(R.string.button_ok, new AlertDialog.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(final DialogInterface dialog, final int which) {
|
||||||
|
dialog.dismiss();
|
||||||
|
ThreadUtils.postToBackgroundThread(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
final ContentResolver cr = context.getContentResolver();
|
||||||
|
BrowserDB.clearHistory(cr);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Telemetry.sendUIEvent(TelemetryContract.Event.SANITIZE, TelemetryContract.Method.BUTTON, "history");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
dialogBuilder.show();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDestroyView() {
|
||||||
|
super.onDestroyView();
|
||||||
|
mList = null;
|
||||||
|
mEmptyView = null;
|
||||||
|
mClearHistoryButton = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onActivityCreated(Bundle savedInstanceState) {
|
||||||
|
super.onActivityCreated(savedInstanceState);
|
||||||
|
|
||||||
|
// Intialize adapter
|
||||||
|
mAdapter = new HistoryAdapter(getActivity());
|
||||||
|
mList.setAdapter(mAdapter);
|
||||||
|
|
||||||
|
// Create callbacks before the initial loader is started
|
||||||
|
mCursorLoaderCallbacks = new CursorLoaderCallbacks();
|
||||||
loadIfVisible();
|
loadIfVisible();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void load() {
|
protected void load() {
|
||||||
// Show most recent panel as the initial panel.
|
getLoaderManager().initLoader(LOADER_ID_HISTORY, null, mCursorLoaderCallbacks);
|
||||||
// Since we detach/attach on config change, this prevents from replacing current fragment.
|
|
||||||
if (!initializeRecentPanel) {
|
|
||||||
showMostRecentPanel();
|
|
||||||
initializeRecentPanel = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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
|
@Override
|
||||||
public void onTabChanged(int index) {
|
public Cursor loadCursor() {
|
||||||
if (index == mSelectedTab) {
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (index == 0) {
|
// Cursor is empty, so hide the "Clear browsing history" button,
|
||||||
showMostRecentPanel();
|
// and set the empty view if it hasn't been set already.
|
||||||
} else if (index == 1) {
|
mClearHistoryButton.setVisibility(View.GONE);
|
||||||
showLastTabsPanel();
|
|
||||||
|
if (mEmptyView == null) {
|
||||||
|
// Set empty panel view. We delay this so that the empty view won't flash.
|
||||||
|
final ViewStub emptyViewStub = (ViewStub) getView().findViewById(R.id.home_empty_view_stub);
|
||||||
|
mEmptyView = emptyViewStub.inflate();
|
||||||
|
|
||||||
|
final ImageView emptyIcon = (ImageView) mEmptyView.findViewById(R.id.home_empty_image);
|
||||||
|
emptyIcon.setImageResource(R.drawable.icon_most_recent_empty);
|
||||||
|
|
||||||
|
final TextView emptyText = (TextView) mEmptyView.findViewById(R.id.home_empty_text);
|
||||||
|
emptyText.setText(R.string.home_most_recent_empty);
|
||||||
|
|
||||||
|
mList.setEmptyView(mEmptyView);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
mTabWidget.setCurrentTab(index);
|
private static class HistoryAdapter extends MultiTypeCursorAdapter {
|
||||||
mSelectedTab = index;
|
private static final int ROW_HEADER = 0;
|
||||||
|
private static final int ROW_STANDARD = 1;
|
||||||
|
|
||||||
|
private static final int[] VIEW_TYPES = new int[] { ROW_STANDARD, ROW_HEADER };
|
||||||
|
private static final int[] LAYOUT_TYPES = new int[] { R.layout.home_item_row, R.layout.home_header_row };
|
||||||
|
|
||||||
|
// For the time sections in history
|
||||||
|
private static final long MS_PER_DAY = 86400000;
|
||||||
|
private static final long MS_PER_WEEK = MS_PER_DAY * 7;
|
||||||
|
|
||||||
|
// The time ranges for each section
|
||||||
|
private static enum MostRecentSection {
|
||||||
|
TODAY,
|
||||||
|
YESTERDAY,
|
||||||
|
WEEK,
|
||||||
|
OLDER
|
||||||
|
};
|
||||||
|
|
||||||
|
private final Context mContext;
|
||||||
|
|
||||||
|
// Maps headers in the list with their respective sections
|
||||||
|
private final SparseArray<MostRecentSection> mMostRecentSections;
|
||||||
|
|
||||||
|
public HistoryAdapter(Context context) {
|
||||||
|
super(context, null, VIEW_TYPES, LAYOUT_TYPES);
|
||||||
|
|
||||||
|
mContext = context;
|
||||||
|
|
||||||
|
// Initialize map of history sections
|
||||||
|
mMostRecentSections = new SparseArray<MostRecentSection>();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onConfigurationChanged(Configuration newConfig) {
|
public Object getItem(int position) {
|
||||||
super.onConfigurationChanged(newConfig);
|
final int type = getItemViewType(position);
|
||||||
|
|
||||||
// Rotation should detach and re-attach to use a different layout.
|
// Header items are not in the cursor
|
||||||
if (isVisible()) {
|
if (type == ROW_HEADER) {
|
||||||
getFragmentManager().beginTransaction()
|
return null;
|
||||||
.detach(this)
|
}
|
||||||
.attach(this)
|
|
||||||
.commitAllowingStateLoss();
|
return super.getItem(position - getMostRecentSectionsCountBefore(position));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getItemViewType(int position) {
|
||||||
|
if (mMostRecentSections.get(position) != null) {
|
||||||
|
return ROW_HEADER;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ROW_STANDARD;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isEnabled(int position) {
|
||||||
|
return (getItemViewType(position) == ROW_STANDARD);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getCount() {
|
||||||
|
// Add the history section headers to the number of reported results.
|
||||||
|
return super.getCount() + mMostRecentSections.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Cursor swapCursor(Cursor cursor) {
|
||||||
|
loadMostRecentSections(cursor);
|
||||||
|
Cursor oldCursor = super.swapCursor(cursor);
|
||||||
|
return oldCursor;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void bindView(View view, Context context, int position) {
|
||||||
|
final int type = getItemViewType(position);
|
||||||
|
|
||||||
|
if (type == ROW_HEADER) {
|
||||||
|
final MostRecentSection section = mMostRecentSections.get(position);
|
||||||
|
final TextView row = (TextView) view;
|
||||||
|
row.setText(getMostRecentSectionTitle(section));
|
||||||
|
} else {
|
||||||
|
// Account for the most recent section headers
|
||||||
|
position -= getMostRecentSectionsCountBefore(position);
|
||||||
|
final Cursor c = getCursor(position);
|
||||||
|
final TwoLinePageRow row = (TwoLinePageRow) view;
|
||||||
|
row.updateFromCursor(c);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void showSubPanel(Fragment subPanel) {
|
private String getMostRecentSectionTitle(MostRecentSection section) {
|
||||||
final Bundle args = new Bundle();
|
switch (section) {
|
||||||
args.putBoolean(HomePager.CAN_LOAD_ARG, getCanLoadHint());
|
case TODAY:
|
||||||
subPanel.setArguments(args);
|
return mContext.getString(R.string.history_today_section);
|
||||||
|
case YESTERDAY:
|
||||||
getChildFragmentManager().beginTransaction()
|
return mContext.getString(R.string.history_yesterday_section);
|
||||||
.addToBackStack(null).replace(R.id.history_panel_container, subPanel)
|
case WEEK:
|
||||||
.commitAllowingStateLoss();
|
return mContext.getString(R.string.history_week_section);
|
||||||
|
case OLDER:
|
||||||
|
return mContext.getString(R.string.history_older_section);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void showMostRecentPanel() {
|
throw new IllegalStateException("Unrecognized history section");
|
||||||
final MostRecentPanel mostRecentPanel = MostRecentPanel.newInstance();
|
|
||||||
showSubPanel(mostRecentPanel);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void showLastTabsPanel() {
|
private int getMostRecentSectionsCountBefore(int position) {
|
||||||
final LastTabsPanel lastTabsPanel = LastTabsPanel.newInstance();
|
// Account for the number headers before the given position
|
||||||
showSubPanel(lastTabsPanel);
|
int sectionsBefore = 0;
|
||||||
|
|
||||||
|
final int historySectionsCount = mMostRecentSections.size();
|
||||||
|
for (int i = 0; i < historySectionsCount; i++) {
|
||||||
|
final int sectionPosition = mMostRecentSections.keyAt(i);
|
||||||
|
if (sectionPosition > position) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
sectionsBefore++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return sectionsBefore;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static MostRecentSection getMostRecentSectionForTime(long from, long time) {
|
||||||
|
long delta = from - time;
|
||||||
|
|
||||||
|
if (delta < 0) {
|
||||||
|
return MostRecentSection.TODAY;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (delta < MS_PER_DAY) {
|
||||||
|
return MostRecentSection.YESTERDAY;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (delta < MS_PER_WEEK) {
|
||||||
|
return MostRecentSection.WEEK;
|
||||||
|
}
|
||||||
|
|
||||||
|
return MostRecentSection.OLDER;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void loadMostRecentSections(Cursor c) {
|
||||||
|
// Clear any history sections that may have been loaded before.
|
||||||
|
mMostRecentSections.clear();
|
||||||
|
|
||||||
|
if (c == null || !c.moveToFirst()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final Date now = new Date();
|
||||||
|
now.setHours(0);
|
||||||
|
now.setMinutes(0);
|
||||||
|
now.setSeconds(0);
|
||||||
|
|
||||||
|
final long today = now.getTime();
|
||||||
|
MostRecentSection section = null;
|
||||||
|
|
||||||
|
do {
|
||||||
|
final int position = c.getPosition();
|
||||||
|
final long time = c.getLong(c.getColumnIndexOrThrow(URLColumns.DATE_LAST_VISITED));
|
||||||
|
final MostRecentSection itemSection = HistoryAdapter.getMostRecentSectionForTime(today, time);
|
||||||
|
|
||||||
|
if (section != itemSection) {
|
||||||
|
section = itemSection;
|
||||||
|
mMostRecentSections.append(position + mMostRecentSections.size(), section);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reached the last section, no need to continue
|
||||||
|
if (section == MostRecentSection.OLDER) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} while (c.moveToNext());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class CursorLoaderCallbacks implements LoaderCallbacks<Cursor> {
|
||||||
|
@Override
|
||||||
|
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
|
||||||
|
return new HistoryCursorLoader(getActivity());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onLoadFinished(Loader<Cursor> loader, Cursor c) {
|
||||||
|
mAdapter.swapCursor(c);
|
||||||
|
updateUiFromCursor(c);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onLoaderReset(Loader<Cursor> loader) {
|
||||||
|
mAdapter.swapCursor(null);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -40,6 +40,7 @@ public final class HomeConfig {
|
||||||
BOOKMARKS("bookmarks", BookmarksPanel.class),
|
BOOKMARKS("bookmarks", BookmarksPanel.class),
|
||||||
HISTORY("history", HistoryPanel.class),
|
HISTORY("history", HistoryPanel.class),
|
||||||
READING_LIST("reading_list", ReadingListPanel.class),
|
READING_LIST("reading_list", ReadingListPanel.class),
|
||||||
|
RECENT_TABS("recent_tabs", RecentTabsPanel.class),
|
||||||
DYNAMIC("dynamic", DynamicPanel.class);
|
DYNAMIC("dynamic", DynamicPanel.class);
|
||||||
|
|
||||||
private final String mId;
|
private final String mId;
|
||||||
|
@ -1495,6 +1496,7 @@ public final class HomeConfig {
|
||||||
private static final String BOOKMARKS_PANEL_ID = "7f6d419a-cd6c-4e34-b26f-f68b1b551907";
|
private static final String BOOKMARKS_PANEL_ID = "7f6d419a-cd6c-4e34-b26f-f68b1b551907";
|
||||||
private static final String READING_LIST_PANEL_ID = "20f4549a-64ad-4c32-93e4-1dcef792733b";
|
private static final String READING_LIST_PANEL_ID = "20f4549a-64ad-4c32-93e4-1dcef792733b";
|
||||||
private static final String HISTORY_PANEL_ID = "f134bf20-11f7-4867-ab8b-e8e705d7fbe8";
|
private static final String HISTORY_PANEL_ID = "f134bf20-11f7-4867-ab8b-e8e705d7fbe8";
|
||||||
|
private static final String RECENT_TABS_PANEL_ID = "5c2601a5-eedc-4477-b297-ce4cef52adf8";
|
||||||
|
|
||||||
private final HomeConfigBackend mBackend;
|
private final HomeConfigBackend mBackend;
|
||||||
|
|
||||||
|
@ -1550,6 +1552,11 @@ public final class HomeConfig {
|
||||||
id = READING_LIST_PANEL_ID;
|
id = READING_LIST_PANEL_ID;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case RECENT_TABS:
|
||||||
|
titleId = R.string.recent_tabs_title;
|
||||||
|
id = RECENT_TABS_PANEL_ID;
|
||||||
|
break;
|
||||||
|
|
||||||
case DYNAMIC:
|
case DYNAMIC:
|
||||||
throw new IllegalArgumentException("createBuiltinPanelConfig() is only for built-in panels");
|
throw new IllegalArgumentException("createBuiltinPanelConfig() is only for built-in panels");
|
||||||
}
|
}
|
||||||
|
|
|
@ -36,7 +36,19 @@ import android.util.Log;
|
||||||
class HomeConfigPrefsBackend implements HomeConfigBackend {
|
class HomeConfigPrefsBackend implements HomeConfigBackend {
|
||||||
private static final String LOGTAG = "GeckoHomeConfigBackend";
|
private static final String LOGTAG = "GeckoHomeConfigBackend";
|
||||||
|
|
||||||
private static final String PREFS_CONFIG_KEY = "home_panels";
|
// Increment this to trigger a migration.
|
||||||
|
private static final int VERSION = 1;
|
||||||
|
|
||||||
|
// This key was originally used to store only an array of panel configs.
|
||||||
|
private static final String PREFS_CONFIG_KEY_OLD = "home_panels";
|
||||||
|
|
||||||
|
// This key is now used to store a version number with the array of panel configs.
|
||||||
|
private static final String PREFS_CONFIG_KEY = "home_panels_with_version";
|
||||||
|
|
||||||
|
// Keys used with JSON object stored in prefs.
|
||||||
|
private static final String JSON_KEY_PANELS = "panels";
|
||||||
|
private static final String JSON_KEY_VERSION = "version";
|
||||||
|
|
||||||
private static final String PREFS_LOCALE_KEY = "home_locale";
|
private static final String PREFS_LOCALE_KEY = "home_locale";
|
||||||
|
|
||||||
private static final String RELOAD_BROADCAST = "HomeConfigPrefsBackend:Reload";
|
private static final String RELOAD_BROADCAST = "HomeConfigPrefsBackend:Reload";
|
||||||
|
@ -45,6 +57,8 @@ class HomeConfigPrefsBackend implements HomeConfigBackend {
|
||||||
private ReloadBroadcastReceiver mReloadBroadcastReceiver;
|
private ReloadBroadcastReceiver mReloadBroadcastReceiver;
|
||||||
private OnReloadListener mReloadListener;
|
private OnReloadListener mReloadListener;
|
||||||
|
|
||||||
|
private static boolean sMigrationDone = false;
|
||||||
|
|
||||||
public HomeConfigPrefsBackend(Context context) {
|
public HomeConfigPrefsBackend(Context context) {
|
||||||
mContext = context;
|
mContext = context;
|
||||||
}
|
}
|
||||||
|
@ -68,22 +82,109 @@ class HomeConfigPrefsBackend implements HomeConfigBackend {
|
||||||
}
|
}
|
||||||
|
|
||||||
final PanelConfig historyEntry = createBuiltinPanelConfig(mContext, PanelType.HISTORY);
|
final PanelConfig historyEntry = createBuiltinPanelConfig(mContext, PanelType.HISTORY);
|
||||||
|
final PanelConfig recentTabsEntry = createBuiltinPanelConfig(mContext, PanelType.RECENT_TABS);
|
||||||
|
|
||||||
// On tablets, the history panel is the last.
|
// On tablets, the history panel is the last.
|
||||||
// On phones, the history panel is the first one.
|
// On phones, the history panel is the first one.
|
||||||
if (HardwareUtils.isTablet()) {
|
if (HardwareUtils.isTablet()) {
|
||||||
panelConfigs.add(historyEntry);
|
panelConfigs.add(historyEntry);
|
||||||
|
panelConfigs.add(recentTabsEntry);
|
||||||
} else {
|
} else {
|
||||||
panelConfigs.add(0, historyEntry);
|
panelConfigs.add(0, historyEntry);
|
||||||
|
panelConfigs.add(0, recentTabsEntry);
|
||||||
}
|
}
|
||||||
|
|
||||||
return new State(panelConfigs, true);
|
return new State(panelConfigs, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Migrates JSON config data storage.
|
||||||
|
*
|
||||||
|
* @param context Context used to get shared preferences and create built-in panel.
|
||||||
|
* @param jsonString String currently stored in preferences.
|
||||||
|
*
|
||||||
|
* @return JSONArray array representing new set of panel configs.
|
||||||
|
*/
|
||||||
|
private static synchronized JSONArray maybePerformMigration(Context context, String jsonString) throws JSONException {
|
||||||
|
// If the migration is already done, we're at the current version.
|
||||||
|
if (sMigrationDone) {
|
||||||
|
final JSONObject json = new JSONObject(jsonString);
|
||||||
|
return json.getJSONArray(JSON_KEY_PANELS);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make sure we only do this version check once.
|
||||||
|
sMigrationDone = true;
|
||||||
|
|
||||||
|
final JSONArray originalJsonPanels;
|
||||||
|
final int version;
|
||||||
|
|
||||||
|
final SharedPreferences prefs = GeckoSharedPrefs.forProfile(context);
|
||||||
|
if (prefs.contains(PREFS_CONFIG_KEY_OLD)) {
|
||||||
|
// Our original implementation did not contain versioning, so this is implicitly version 0.
|
||||||
|
originalJsonPanels = new JSONArray(jsonString);
|
||||||
|
version = 0;
|
||||||
|
} else {
|
||||||
|
final JSONObject json = new JSONObject(jsonString);
|
||||||
|
originalJsonPanels = json.getJSONArray(JSON_KEY_PANELS);
|
||||||
|
version = json.getInt(JSON_KEY_VERSION);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (version == VERSION) {
|
||||||
|
return originalJsonPanels;
|
||||||
|
}
|
||||||
|
|
||||||
|
Log.d(LOGTAG, "Performing migration");
|
||||||
|
|
||||||
|
final JSONArray newJsonPanels = new JSONArray();
|
||||||
|
final SharedPreferences.Editor prefsEditor = prefs.edit();
|
||||||
|
|
||||||
|
for (int v = version + 1; v <= VERSION; v++) {
|
||||||
|
Log.d(LOGTAG, "Migrating to version = " + v);
|
||||||
|
|
||||||
|
switch (v) {
|
||||||
|
case 1:
|
||||||
|
// Add "Recent Tabs" panel
|
||||||
|
final PanelConfig recentTabsConfig = createBuiltinPanelConfig(context, PanelType.RECENT_TABS);
|
||||||
|
final JSONObject jsonRecentTabsConfig = recentTabsConfig.toJSON();
|
||||||
|
|
||||||
|
// Add the new panel to the front of the array on phones.
|
||||||
|
if (!HardwareUtils.isTablet()) {
|
||||||
|
newJsonPanels.put(jsonRecentTabsConfig);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy the original panel configs.
|
||||||
|
final int count = originalJsonPanels.length();
|
||||||
|
for (int i = 0; i < count; i++) {
|
||||||
|
final JSONObject jsonPanelConfig = originalJsonPanels.getJSONObject(i);
|
||||||
|
newJsonPanels.put(jsonPanelConfig);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add the new panel to the end of the array on tablets.
|
||||||
|
if (HardwareUtils.isTablet()) {
|
||||||
|
newJsonPanels.put(jsonRecentTabsConfig);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove the old pref key.
|
||||||
|
prefsEditor.remove(PREFS_CONFIG_KEY_OLD);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save the new panel config and the new version number.
|
||||||
|
final JSONObject newJson = new JSONObject();
|
||||||
|
newJson.put(JSON_KEY_PANELS, newJsonPanels);
|
||||||
|
newJson.put(JSON_KEY_VERSION, VERSION);
|
||||||
|
|
||||||
|
prefsEditor.putString(PREFS_CONFIG_KEY, newJson.toString());
|
||||||
|
prefsEditor.commit();
|
||||||
|
|
||||||
|
return newJsonPanels;
|
||||||
|
}
|
||||||
|
|
||||||
private State loadConfigFromString(String jsonString) {
|
private State loadConfigFromString(String jsonString) {
|
||||||
final JSONArray jsonPanelConfigs;
|
final JSONArray jsonPanelConfigs;
|
||||||
try {
|
try {
|
||||||
jsonPanelConfigs = new JSONArray(jsonString);
|
jsonPanelConfigs = maybePerformMigration(mContext, jsonString);
|
||||||
} catch (JSONException e) {
|
} catch (JSONException e) {
|
||||||
Log.e(LOGTAG, "Error loading the list of home panels from JSON prefs", e);
|
Log.e(LOGTAG, "Error loading the list of home panels from JSON prefs", e);
|
||||||
|
|
||||||
|
@ -110,7 +211,9 @@ class HomeConfigPrefsBackend implements HomeConfigBackend {
|
||||||
@Override
|
@Override
|
||||||
public State load() {
|
public State load() {
|
||||||
final SharedPreferences prefs = getSharedPreferences();
|
final SharedPreferences prefs = getSharedPreferences();
|
||||||
final String jsonString = prefs.getString(PREFS_CONFIG_KEY, null);
|
|
||||||
|
final String key = (prefs.contains(PREFS_CONFIG_KEY_OLD) ? PREFS_CONFIG_KEY_OLD : PREFS_CONFIG_KEY);
|
||||||
|
final String jsonString = prefs.getString(key, null);
|
||||||
|
|
||||||
final State configState;
|
final State configState;
|
||||||
if (TextUtils.isEmpty(jsonString)) {
|
if (TextUtils.isEmpty(jsonString)) {
|
||||||
|
@ -142,7 +245,15 @@ class HomeConfigPrefsBackend implements HomeConfigBackend {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
editor.putString(PREFS_CONFIG_KEY, jsonPanelConfigs.toString());
|
try {
|
||||||
|
final JSONObject json = new JSONObject();
|
||||||
|
json.put(JSON_KEY_PANELS, jsonPanelConfigs);
|
||||||
|
json.put(JSON_KEY_VERSION, VERSION);
|
||||||
|
|
||||||
|
editor.putString(PREFS_CONFIG_KEY, json.toString());
|
||||||
|
} catch (JSONException e) {
|
||||||
|
Log.e(LOGTAG, "Exception saving PanelConfig state", e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
editor.putString(PREFS_LOCALE_KEY, Locale.getDefault().toString());
|
editor.putString(PREFS_LOCALE_KEY, Locale.getDefault().toString());
|
||||||
|
|
|
@ -68,8 +68,7 @@ public class HomePager extends ViewPager {
|
||||||
static final String LIST_TAG_BOOKMARKS = "bookmarks";
|
static final String LIST_TAG_BOOKMARKS = "bookmarks";
|
||||||
static final String LIST_TAG_READING_LIST = "reading_list";
|
static final String LIST_TAG_READING_LIST = "reading_list";
|
||||||
static final String LIST_TAG_TOP_SITES = "top_sites";
|
static final String LIST_TAG_TOP_SITES = "top_sites";
|
||||||
static final String LIST_TAG_MOST_RECENT = "most_recent";
|
static final String LIST_TAG_RECENT_TABS = "recent_tabs";
|
||||||
static final String LIST_TAG_LAST_TABS = "last_tabs";
|
|
||||||
static final String LIST_TAG_BROWSER_SEARCH = "browser_search";
|
static final String LIST_TAG_BROWSER_SEARCH = "browser_search";
|
||||||
|
|
||||||
public interface OnUrlOpenListener {
|
public interface OnUrlOpenListener {
|
||||||
|
|
|
@ -1,430 +0,0 @@
|
||||||
/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
|
|
||||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
||||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
||||||
|
|
||||||
package org.mozilla.gecko.home;
|
|
||||||
|
|
||||||
import java.util.Date;
|
|
||||||
import java.util.EnumSet;
|
|
||||||
|
|
||||||
import org.mozilla.gecko.R;
|
|
||||||
import org.mozilla.gecko.Telemetry;
|
|
||||||
import org.mozilla.gecko.TelemetryContract;
|
|
||||||
import org.mozilla.gecko.db.BrowserContract.Combined;
|
|
||||||
import org.mozilla.gecko.db.BrowserDB;
|
|
||||||
import org.mozilla.gecko.db.BrowserDB.URLColumns;
|
|
||||||
import org.mozilla.gecko.home.HomePager.OnUrlOpenListener;
|
|
||||||
import org.mozilla.gecko.util.ThreadUtils;
|
|
||||||
|
|
||||||
import android.app.Activity;
|
|
||||||
import android.app.AlertDialog;
|
|
||||||
import android.content.ContentResolver;
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.DialogInterface;
|
|
||||||
import android.database.Cursor;
|
|
||||||
import android.os.Bundle;
|
|
||||||
import android.support.v4.app.LoaderManager.LoaderCallbacks;
|
|
||||||
import android.support.v4.content.Loader;
|
|
||||||
import android.util.SparseArray;
|
|
||||||
import android.view.LayoutInflater;
|
|
||||||
import android.view.View;
|
|
||||||
import android.view.ViewGroup;
|
|
||||||
import android.view.ViewStub;
|
|
||||||
import android.widget.AdapterView;
|
|
||||||
import android.widget.ImageView;
|
|
||||||
import android.widget.TextView;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Fragment that displays recent history in a ListView.
|
|
||||||
*/
|
|
||||||
public class MostRecentPanel extends HomeFragment {
|
|
||||||
// Logging tag name
|
|
||||||
private static final String LOGTAG = "GeckoMostRecentPanel";
|
|
||||||
|
|
||||||
// Cursor loader ID for history query
|
|
||||||
private static final int LOADER_ID_HISTORY = 0;
|
|
||||||
|
|
||||||
// Adapter for the list of search results
|
|
||||||
private MostRecentAdapter mAdapter;
|
|
||||||
|
|
||||||
// The view shown by the fragment.
|
|
||||||
private HomeListView mList;
|
|
||||||
|
|
||||||
// The button view for clearing browsing history.
|
|
||||||
private View mClearHistoryButton;
|
|
||||||
|
|
||||||
// Reference to the View to display when there are no results.
|
|
||||||
private View mEmptyView;
|
|
||||||
|
|
||||||
// Callbacks used for the search and favicon cursor loaders
|
|
||||||
private CursorLoaderCallbacks mCursorLoaderCallbacks;
|
|
||||||
|
|
||||||
// On URL open listener
|
|
||||||
private OnUrlOpenListener mUrlOpenListener;
|
|
||||||
|
|
||||||
public static MostRecentPanel newInstance() {
|
|
||||||
return new MostRecentPanel();
|
|
||||||
}
|
|
||||||
|
|
||||||
public MostRecentPanel() {
|
|
||||||
mUrlOpenListener = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onAttach(Activity activity) {
|
|
||||||
super.onAttach(activity);
|
|
||||||
|
|
||||||
try {
|
|
||||||
mUrlOpenListener = (OnUrlOpenListener) activity;
|
|
||||||
} catch (ClassCastException e) {
|
|
||||||
throw new ClassCastException(activity.toString()
|
|
||||||
+ " must implement HomePager.OnUrlOpenListener");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onDetach() {
|
|
||||||
super.onDetach();
|
|
||||||
mUrlOpenListener = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
|
||||||
return inflater.inflate(R.layout.home_most_recent_panel, container, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onViewCreated(View view, Bundle savedInstanceState) {
|
|
||||||
mList = (HomeListView) view.findViewById(R.id.list);
|
|
||||||
mList.setTag(HomePager.LIST_TAG_MOST_RECENT);
|
|
||||||
|
|
||||||
mList.setOnItemClickListener(new AdapterView.OnItemClickListener() {
|
|
||||||
@Override
|
|
||||||
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
|
|
||||||
position -= mAdapter.getMostRecentSectionsCountBefore(position);
|
|
||||||
final Cursor c = mAdapter.getCursor(position);
|
|
||||||
final String url = c.getString(c.getColumnIndexOrThrow(URLColumns.URL));
|
|
||||||
|
|
||||||
Telemetry.sendUIEvent(TelemetryContract.Event.LOAD_URL);
|
|
||||||
|
|
||||||
// This item is a TwoLinePageRow, so we allow switch-to-tab.
|
|
||||||
mUrlOpenListener.onUrlOpen(url, EnumSet.of(OnUrlOpenListener.Flags.ALLOW_SWITCH_TO_TAB));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
mList.setContextMenuInfoFactory(new HomeContextMenuInfo.Factory() {
|
|
||||||
@Override
|
|
||||||
public HomeContextMenuInfo makeInfoForCursor(View view, int position, long id, Cursor cursor) {
|
|
||||||
final HomeContextMenuInfo info = new HomeContextMenuInfo(view, position, id);
|
|
||||||
info.url = cursor.getString(cursor.getColumnIndexOrThrow(Combined.URL));
|
|
||||||
info.title = cursor.getString(cursor.getColumnIndexOrThrow(Combined.TITLE));
|
|
||||||
info.display = cursor.getInt(cursor.getColumnIndexOrThrow(Combined.DISPLAY));
|
|
||||||
info.historyId = cursor.getInt(cursor.getColumnIndexOrThrow(Combined.HISTORY_ID));
|
|
||||||
final int bookmarkIdCol = cursor.getColumnIndexOrThrow(Combined.BOOKMARK_ID);
|
|
||||||
if (cursor.isNull(bookmarkIdCol)) {
|
|
||||||
// If this is a combined cursor, we may get a history item without a
|
|
||||||
// bookmark, in which case the bookmarks ID column value will be null.
|
|
||||||
info.bookmarkId = -1;
|
|
||||||
} else {
|
|
||||||
info.bookmarkId = cursor.getInt(bookmarkIdCol);
|
|
||||||
}
|
|
||||||
return info;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
registerForContextMenu(mList);
|
|
||||||
|
|
||||||
mClearHistoryButton = view.findViewById(R.id.clear_history_button);
|
|
||||||
mClearHistoryButton.setOnClickListener(new View.OnClickListener() {
|
|
||||||
@Override
|
|
||||||
public void onClick(View v) {
|
|
||||||
final Context context = getActivity();
|
|
||||||
|
|
||||||
final AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(context);
|
|
||||||
dialogBuilder.setMessage(R.string.home_clear_history_confirm);
|
|
||||||
|
|
||||||
dialogBuilder.setNegativeButton(R.string.button_cancel, new AlertDialog.OnClickListener() {
|
|
||||||
@Override
|
|
||||||
public void onClick(final DialogInterface dialog, final int which) {
|
|
||||||
dialog.dismiss();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
dialogBuilder.setPositiveButton(R.string.button_ok, new AlertDialog.OnClickListener() {
|
|
||||||
@Override
|
|
||||||
public void onClick(final DialogInterface dialog, final int which) {
|
|
||||||
dialog.dismiss();
|
|
||||||
ThreadUtils.postToBackgroundThread(new Runnable() {
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
final ContentResolver cr = context.getContentResolver();
|
|
||||||
BrowserDB.clearHistory(cr);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
Telemetry.sendUIEvent(TelemetryContract.Event.SANITIZE, TelemetryContract.Method.BUTTON, "history");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
dialogBuilder.show();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onDestroyView() {
|
|
||||||
super.onDestroyView();
|
|
||||||
mList = null;
|
|
||||||
mEmptyView = null;
|
|
||||||
mClearHistoryButton = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onActivityCreated(Bundle savedInstanceState) {
|
|
||||||
super.onActivityCreated(savedInstanceState);
|
|
||||||
|
|
||||||
// Intialize adapter
|
|
||||||
mAdapter = new MostRecentAdapter(getActivity());
|
|
||||||
mList.setAdapter(mAdapter);
|
|
||||||
|
|
||||||
// Create callbacks before the initial loader is started
|
|
||||||
mCursorLoaderCallbacks = new CursorLoaderCallbacks();
|
|
||||||
loadIfVisible();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void load() {
|
|
||||||
getLoaderManager().initLoader(LOADER_ID_HISTORY, null, mCursorLoaderCallbacks);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class MostRecentCursorLoader extends SimpleCursorLoader {
|
|
||||||
// Max number of history results
|
|
||||||
private static final int HISTORY_LIMIT = 100;
|
|
||||||
|
|
||||||
public MostRecentCursorLoader(Context context) {
|
|
||||||
super(context);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Cursor loadCursor() {
|
|
||||||
final ContentResolver cr = getContext().getContentResolver();
|
|
||||||
return BrowserDB.getRecentHistory(cr, HISTORY_LIMIT);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void updateUiFromCursor(Cursor c) {
|
|
||||||
if (c != null && c.getCount() > 0) {
|
|
||||||
mClearHistoryButton.setVisibility(View.VISIBLE);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Cursor is empty, so hide the "Clear browsing history" button,
|
|
||||||
// and set the empty view if it hasn't been set already.
|
|
||||||
mClearHistoryButton.setVisibility(View.GONE);
|
|
||||||
|
|
||||||
if (mEmptyView == null) {
|
|
||||||
// Set empty panel view. We delay this so that the empty view won't flash.
|
|
||||||
final ViewStub emptyViewStub = (ViewStub) getView().findViewById(R.id.home_empty_view_stub);
|
|
||||||
mEmptyView = emptyViewStub.inflate();
|
|
||||||
|
|
||||||
final ImageView emptyIcon = (ImageView) mEmptyView.findViewById(R.id.home_empty_image);
|
|
||||||
emptyIcon.setImageResource(R.drawable.icon_most_recent_empty);
|
|
||||||
|
|
||||||
final TextView emptyText = (TextView) mEmptyView.findViewById(R.id.home_empty_text);
|
|
||||||
emptyText.setText(R.string.home_most_recent_empty);
|
|
||||||
|
|
||||||
mList.setEmptyView(mEmptyView);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class MostRecentAdapter extends MultiTypeCursorAdapter {
|
|
||||||
private static final int ROW_HEADER = 0;
|
|
||||||
private static final int ROW_STANDARD = 1;
|
|
||||||
|
|
||||||
private static final int[] VIEW_TYPES = new int[] { ROW_STANDARD, ROW_HEADER };
|
|
||||||
private static final int[] LAYOUT_TYPES = new int[] { R.layout.home_item_row, R.layout.home_header_row };
|
|
||||||
|
|
||||||
// For the time sections in history
|
|
||||||
private static final long MS_PER_DAY = 86400000;
|
|
||||||
private static final long MS_PER_WEEK = MS_PER_DAY * 7;
|
|
||||||
|
|
||||||
// The time ranges for each section
|
|
||||||
private static enum MostRecentSection {
|
|
||||||
TODAY,
|
|
||||||
YESTERDAY,
|
|
||||||
WEEK,
|
|
||||||
OLDER
|
|
||||||
};
|
|
||||||
|
|
||||||
private final Context mContext;
|
|
||||||
|
|
||||||
// Maps headers in the list with their respective sections
|
|
||||||
private final SparseArray<MostRecentSection> mMostRecentSections;
|
|
||||||
|
|
||||||
public MostRecentAdapter(Context context) {
|
|
||||||
super(context, null, VIEW_TYPES, LAYOUT_TYPES);
|
|
||||||
|
|
||||||
mContext = context;
|
|
||||||
|
|
||||||
// Initialize map of history sections
|
|
||||||
mMostRecentSections = new SparseArray<MostRecentSection>();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Object getItem(int position) {
|
|
||||||
final int type = getItemViewType(position);
|
|
||||||
|
|
||||||
// Header items are not in the cursor
|
|
||||||
if (type == ROW_HEADER) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return super.getItem(position - getMostRecentSectionsCountBefore(position));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getItemViewType(int position) {
|
|
||||||
if (mMostRecentSections.get(position) != null) {
|
|
||||||
return ROW_HEADER;
|
|
||||||
}
|
|
||||||
|
|
||||||
return ROW_STANDARD;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isEnabled(int position) {
|
|
||||||
return (getItemViewType(position) == ROW_STANDARD);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getCount() {
|
|
||||||
// Add the history section headers to the number of reported results.
|
|
||||||
return super.getCount() + mMostRecentSections.size();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Cursor swapCursor(Cursor cursor) {
|
|
||||||
loadMostRecentSections(cursor);
|
|
||||||
Cursor oldCursor = super.swapCursor(cursor);
|
|
||||||
return oldCursor;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void bindView(View view, Context context, int position) {
|
|
||||||
final int type = getItemViewType(position);
|
|
||||||
|
|
||||||
if (type == ROW_HEADER) {
|
|
||||||
final MostRecentSection section = mMostRecentSections.get(position);
|
|
||||||
final TextView row = (TextView) view;
|
|
||||||
row.setText(getMostRecentSectionTitle(section));
|
|
||||||
} else {
|
|
||||||
// Account for the most recent section headers
|
|
||||||
position -= getMostRecentSectionsCountBefore(position);
|
|
||||||
final Cursor c = getCursor(position);
|
|
||||||
final TwoLinePageRow row = (TwoLinePageRow) view;
|
|
||||||
row.updateFromCursor(c);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private String getMostRecentSectionTitle(MostRecentSection section) {
|
|
||||||
switch (section) {
|
|
||||||
case TODAY:
|
|
||||||
return mContext.getString(R.string.history_today_section);
|
|
||||||
case YESTERDAY:
|
|
||||||
return mContext.getString(R.string.history_yesterday_section);
|
|
||||||
case WEEK:
|
|
||||||
return mContext.getString(R.string.history_week_section);
|
|
||||||
case OLDER:
|
|
||||||
return mContext.getString(R.string.history_older_section);
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new IllegalStateException("Unrecognized history section");
|
|
||||||
}
|
|
||||||
|
|
||||||
private int getMostRecentSectionsCountBefore(int position) {
|
|
||||||
// Account for the number headers before the given position
|
|
||||||
int sectionsBefore = 0;
|
|
||||||
|
|
||||||
final int historySectionsCount = mMostRecentSections.size();
|
|
||||||
for (int i = 0; i < historySectionsCount; i++) {
|
|
||||||
final int sectionPosition = mMostRecentSections.keyAt(i);
|
|
||||||
if (sectionPosition > position) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
sectionsBefore++;
|
|
||||||
}
|
|
||||||
|
|
||||||
return sectionsBefore;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static MostRecentSection getMostRecentSectionForTime(long from, long time) {
|
|
||||||
long delta = from - time;
|
|
||||||
|
|
||||||
if (delta < 0) {
|
|
||||||
return MostRecentSection.TODAY;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (delta < MS_PER_DAY) {
|
|
||||||
return MostRecentSection.YESTERDAY;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (delta < MS_PER_WEEK) {
|
|
||||||
return MostRecentSection.WEEK;
|
|
||||||
}
|
|
||||||
|
|
||||||
return MostRecentSection.OLDER;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void loadMostRecentSections(Cursor c) {
|
|
||||||
// Clear any history sections that may have been loaded before.
|
|
||||||
mMostRecentSections.clear();
|
|
||||||
|
|
||||||
if (c == null || !c.moveToFirst()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
final Date now = new Date();
|
|
||||||
now.setHours(0);
|
|
||||||
now.setMinutes(0);
|
|
||||||
now.setSeconds(0);
|
|
||||||
|
|
||||||
final long today = now.getTime();
|
|
||||||
MostRecentSection section = null;
|
|
||||||
|
|
||||||
do {
|
|
||||||
final int position = c.getPosition();
|
|
||||||
final long time = c.getLong(c.getColumnIndexOrThrow(URLColumns.DATE_LAST_VISITED));
|
|
||||||
final MostRecentSection itemSection = MostRecentAdapter.getMostRecentSectionForTime(today, time);
|
|
||||||
|
|
||||||
if (section != itemSection) {
|
|
||||||
section = itemSection;
|
|
||||||
mMostRecentSections.append(position + mMostRecentSections.size(), section);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reached the last section, no need to continue
|
|
||||||
if (section == MostRecentSection.OLDER) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
} while (c.moveToNext());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private class CursorLoaderCallbacks implements LoaderCallbacks<Cursor> {
|
|
||||||
@Override
|
|
||||||
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
|
|
||||||
return new MostRecentCursorLoader(getActivity());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onLoadFinished(Loader<Cursor> loader, Cursor c) {
|
|
||||||
mAdapter.swapCursor(c);
|
|
||||||
updateUiFromCursor(c);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onLoaderReset(Loader<Cursor> loader) {
|
|
||||||
mAdapter.swapCursor(null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -6,16 +6,25 @@
|
||||||
package org.mozilla.gecko.home;
|
package org.mozilla.gecko.home;
|
||||||
|
|
||||||
import org.mozilla.gecko.AboutPages;
|
import org.mozilla.gecko.AboutPages;
|
||||||
|
import org.mozilla.gecko.EventDispatcher;
|
||||||
|
import org.mozilla.gecko.GeckoAppShell;
|
||||||
|
import org.mozilla.gecko.GeckoEvent;
|
||||||
import org.mozilla.gecko.GeckoProfile;
|
import org.mozilla.gecko.GeckoProfile;
|
||||||
import org.mozilla.gecko.R;
|
import org.mozilla.gecko.R;
|
||||||
import org.mozilla.gecko.SessionParser;
|
import org.mozilla.gecko.SessionParser;
|
||||||
import org.mozilla.gecko.Telemetry;
|
import org.mozilla.gecko.Telemetry;
|
||||||
import org.mozilla.gecko.TelemetryContract;
|
import org.mozilla.gecko.TelemetryContract;
|
||||||
import org.mozilla.gecko.db.BrowserContract.Combined;
|
import org.mozilla.gecko.db.BrowserContract.CommonColumns;
|
||||||
|
import org.mozilla.gecko.db.BrowserContract.URLColumns;
|
||||||
import org.mozilla.gecko.home.HomePager.OnNewTabsListener;
|
import org.mozilla.gecko.home.HomePager.OnNewTabsListener;
|
||||||
|
import org.mozilla.gecko.util.EventCallback;
|
||||||
|
import org.mozilla.gecko.util.NativeEventListener;
|
||||||
|
import org.mozilla.gecko.util.NativeJSObject;
|
||||||
|
import org.mozilla.gecko.util.ThreadUtils;
|
||||||
|
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
import android.content.res.Configuration;
|
||||||
import android.database.Cursor;
|
import android.database.Cursor;
|
||||||
import android.database.MatrixCursor;
|
import android.database.MatrixCursor;
|
||||||
import android.database.MatrixCursor.RowBuilder;
|
import android.database.MatrixCursor.RowBuilder;
|
||||||
|
@ -23,6 +32,7 @@ import android.os.Bundle;
|
||||||
import android.support.v4.app.LoaderManager.LoaderCallbacks;
|
import android.support.v4.app.LoaderManager.LoaderCallbacks;
|
||||||
import android.support.v4.content.Loader;
|
import android.support.v4.content.Loader;
|
||||||
import android.support.v4.widget.CursorAdapter;
|
import android.support.v4.widget.CursorAdapter;
|
||||||
|
import android.util.Log;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
|
@ -34,25 +44,20 @@ import android.widget.TextView;
|
||||||
/**
|
/**
|
||||||
* Fragment that displays tabs from last session in a ListView.
|
* Fragment that displays tabs from last session in a ListView.
|
||||||
*/
|
*/
|
||||||
public class LastTabsPanel extends HomeFragment {
|
public class RecentTabsPanel extends HomeFragment
|
||||||
|
implements NativeEventListener {
|
||||||
// Logging tag name
|
// Logging tag name
|
||||||
private static final String LOGTAG = "GeckoLastTabsPanel";
|
private static final String LOGTAG = "GeckoRecentTabsPanel";
|
||||||
|
|
||||||
// Cursor loader ID for the session parser
|
// Cursor loader ID for the loader that loads recent tabs
|
||||||
private static final int LOADER_ID_LAST_TABS = 0;
|
private static final int LOADER_ID_RECENT_TABS = 0;
|
||||||
|
|
||||||
// Adapter for the list of search results
|
// Adapter for the list of recent tabs.
|
||||||
private LastTabsAdapter mAdapter;
|
private RecentTabsAdapter mAdapter;
|
||||||
|
|
||||||
// The view shown by the fragment.
|
// The view shown by the fragment.
|
||||||
private HomeListView mList;
|
private HomeListView mList;
|
||||||
|
|
||||||
// The title for this HomeFragment panel.
|
|
||||||
private TextView mTitle;
|
|
||||||
|
|
||||||
// The button view for restoring tabs from last session.
|
|
||||||
private View mRestoreButton;
|
|
||||||
|
|
||||||
// Reference to the View to display when there are no results.
|
// Reference to the View to display when there are no results.
|
||||||
private View mEmptyView;
|
private View mEmptyView;
|
||||||
|
|
||||||
|
@ -62,12 +67,25 @@ public class LastTabsPanel extends HomeFragment {
|
||||||
// On new tabs listener
|
// On new tabs listener
|
||||||
private OnNewTabsListener mNewTabsListener;
|
private OnNewTabsListener mNewTabsListener;
|
||||||
|
|
||||||
public static LastTabsPanel newInstance() {
|
// Recently closed tabs from gecko
|
||||||
return new LastTabsPanel();
|
private ClosedTab[] mClosedTabs;
|
||||||
|
|
||||||
|
private static final class ClosedTab {
|
||||||
|
public final String url;
|
||||||
|
public final String title;
|
||||||
|
|
||||||
|
public ClosedTab(String url, String title) {
|
||||||
|
this.url = url;
|
||||||
|
this.title = title;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public LastTabsPanel() {
|
public static final class RecentTabs implements URLColumns, CommonColumns {
|
||||||
mNewTabsListener = null;
|
public static final String TYPE = "type";
|
||||||
|
|
||||||
|
public static final int TYPE_HEADER = 0;
|
||||||
|
public static final int TYPE_LAST_TIME = 1;
|
||||||
|
public static final int TYPE_CLOSED = 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -91,18 +109,13 @@ public class LastTabsPanel extends HomeFragment {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||||
return inflater.inflate(R.layout.home_last_tabs_panel, container, false);
|
return inflater.inflate(R.layout.home_recent_tabs_panel, container, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onViewCreated(View view, Bundle savedInstanceState) {
|
public void onViewCreated(View view, Bundle savedInstanceState) {
|
||||||
mTitle = (TextView) view.findViewById(R.id.title);
|
|
||||||
if (mTitle != null) {
|
|
||||||
mTitle.setText(R.string.home_last_tabs_title);
|
|
||||||
}
|
|
||||||
|
|
||||||
mList = (HomeListView) view.findViewById(R.id.list);
|
mList = (HomeListView) view.findViewById(R.id.list);
|
||||||
mList.setTag(HomePager.LIST_TAG_LAST_TABS);
|
mList.setTag(HomePager.LIST_TAG_RECENT_TABS);
|
||||||
|
|
||||||
mList.setOnItemClickListener(new AdapterView.OnItemClickListener() {
|
mList.setOnItemClickListener(new AdapterView.OnItemClickListener() {
|
||||||
@Override
|
@Override
|
||||||
|
@ -114,7 +127,7 @@ public class LastTabsPanel extends HomeFragment {
|
||||||
|
|
||||||
Telemetry.sendUIEvent(TelemetryContract.Event.LOAD_URL);
|
Telemetry.sendUIEvent(TelemetryContract.Event.LOAD_URL);
|
||||||
|
|
||||||
final String url = c.getString(c.getColumnIndexOrThrow(Combined.URL));
|
final String url = c.getString(c.getColumnIndexOrThrow(RecentTabs.URL));
|
||||||
mNewTabsListener.onNewTabs(new String[] { url });
|
mNewTabsListener.onNewTabs(new String[] { url });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -123,30 +136,39 @@ public class LastTabsPanel extends HomeFragment {
|
||||||
@Override
|
@Override
|
||||||
public HomeContextMenuInfo makeInfoForCursor(View view, int position, long id, Cursor cursor) {
|
public HomeContextMenuInfo makeInfoForCursor(View view, int position, long id, Cursor cursor) {
|
||||||
final HomeContextMenuInfo info = new HomeContextMenuInfo(view, position, id);
|
final HomeContextMenuInfo info = new HomeContextMenuInfo(view, position, id);
|
||||||
info.url = cursor.getString(cursor.getColumnIndexOrThrow(Combined.URL));
|
info.url = cursor.getString(cursor.getColumnIndexOrThrow(RecentTabs.URL));
|
||||||
info.title = cursor.getString(cursor.getColumnIndexOrThrow(Combined.TITLE));
|
info.title = cursor.getString(cursor.getColumnIndexOrThrow(RecentTabs.TITLE));
|
||||||
return info;
|
return info;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
registerForContextMenu(mList);
|
registerForContextMenu(mList);
|
||||||
|
|
||||||
mRestoreButton = view.findViewById(R.id.open_all_tabs_button);
|
EventDispatcher.getInstance().registerGeckoThreadListener(this, "ClosedTabs:Data");
|
||||||
mRestoreButton.setOnClickListener(new View.OnClickListener() {
|
GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("ClosedTabs:StartNotifications", null));
|
||||||
@Override
|
|
||||||
public void onClick(View v) {
|
|
||||||
openAllTabs();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onDestroyView() {
|
public void onDestroyView() {
|
||||||
super.onDestroyView();
|
super.onDestroyView();
|
||||||
mList = null;
|
mList = null;
|
||||||
mTitle = null;
|
|
||||||
mEmptyView = null;
|
mEmptyView = null;
|
||||||
mRestoreButton = null;
|
|
||||||
|
EventDispatcher.getInstance().unregisterGeckoThreadListener(this, "ClosedTabs:Data");
|
||||||
|
GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("ClosedTabs:StopNotifications", null));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onConfigurationChanged(Configuration newConfig) {
|
||||||
|
super.onConfigurationChanged(newConfig);
|
||||||
|
|
||||||
|
// Detach and reattach the fragment as the layout changes.
|
||||||
|
if (isVisible()) {
|
||||||
|
getFragmentManager().beginTransaction()
|
||||||
|
.detach(this)
|
||||||
|
.attach(this)
|
||||||
|
.commitAllowingStateLoss();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -154,7 +176,7 @@ public class LastTabsPanel extends HomeFragment {
|
||||||
super.onActivityCreated(savedInstanceState);
|
super.onActivityCreated(savedInstanceState);
|
||||||
|
|
||||||
// Intialize adapter
|
// Intialize adapter
|
||||||
mAdapter = new LastTabsAdapter(getActivity());
|
mAdapter = new RecentTabsAdapter(getActivity());
|
||||||
mList.setAdapter(mAdapter);
|
mList.setAdapter(mAdapter);
|
||||||
|
|
||||||
// Create callbacks before the initial loader is started
|
// Create callbacks before the initial loader is started
|
||||||
|
@ -164,20 +186,9 @@ public class LastTabsPanel extends HomeFragment {
|
||||||
|
|
||||||
private void updateUiFromCursor(Cursor c) {
|
private void updateUiFromCursor(Cursor c) {
|
||||||
if (c != null && c.getCount() > 0) {
|
if (c != null && c.getCount() > 0) {
|
||||||
if (mTitle != null) {
|
|
||||||
mTitle.setVisibility(View.VISIBLE);
|
|
||||||
}
|
|
||||||
mRestoreButton.setVisibility(View.VISIBLE);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cursor is empty, so hide the title and set the
|
|
||||||
// empty view if it hasn't been set already.
|
|
||||||
if (mTitle != null) {
|
|
||||||
mTitle.setVisibility(View.GONE);
|
|
||||||
}
|
|
||||||
mRestoreButton.setVisibility(View.GONE);
|
|
||||||
|
|
||||||
if (mEmptyView == null) {
|
if (mEmptyView == null) {
|
||||||
// Set empty panel view. We delay this so that the empty view won't flash.
|
// Set empty panel view. We delay this so that the empty view won't flash.
|
||||||
final ViewStub emptyViewStub = (ViewStub) getView().findViewById(R.id.home_empty_view_stub);
|
final ViewStub emptyViewStub = (ViewStub) getView().findViewById(R.id.home_empty_view_stub);
|
||||||
|
@ -195,7 +206,32 @@ public class LastTabsPanel extends HomeFragment {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void load() {
|
protected void load() {
|
||||||
getLoaderManager().initLoader(LOADER_ID_LAST_TABS, null, mCursorLoaderCallbacks);
|
getLoaderManager().initLoader(LOADER_ID_RECENT_TABS, null, mCursorLoaderCallbacks);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void handleMessage(String event, NativeJSObject message, EventCallback callback) {
|
||||||
|
final NativeJSObject[] tabs = message.getObjectArray("tabs");
|
||||||
|
final int length = tabs.length;
|
||||||
|
|
||||||
|
final ClosedTab[] closedTabs = new ClosedTab[length];
|
||||||
|
for (int i = 0; i < length; i++) {
|
||||||
|
final NativeJSObject tab = tabs[i];
|
||||||
|
closedTabs[i] = new ClosedTab(tab.getString("url"), tab.getString("title"));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only modify mClosedTabs on the UI thread
|
||||||
|
ThreadUtils.postToUiThread(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
mClosedTabs = closedTabs;
|
||||||
|
|
||||||
|
// Reload the cursor to show recently closed tabs.
|
||||||
|
if (mClosedTabs.length > 0 && canLoad()) {
|
||||||
|
getLoaderManager().restartLoader(LOADER_ID_RECENT_TABS, null, mCursorLoaderCallbacks);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private void openAllTabs() {
|
private void openAllTabs() {
|
||||||
|
@ -207,7 +243,7 @@ public class LastTabsPanel extends HomeFragment {
|
||||||
final String[] urls = new String[c.getCount()];
|
final String[] urls = new String[c.getCount()];
|
||||||
|
|
||||||
do {
|
do {
|
||||||
urls[c.getPosition()] = c.getString(c.getColumnIndexOrThrow(Combined.URL));
|
urls[c.getPosition()] = c.getString(c.getColumnIndexOrThrow(RecentTabs.URL));
|
||||||
} while (c.moveToNext());
|
} while (c.moveToNext());
|
||||||
|
|
||||||
Telemetry.sendUIEvent(TelemetryContract.Event.LOAD_URL, TelemetryContract.Method.BUTTON);
|
Telemetry.sendUIEvent(TelemetryContract.Event.LOAD_URL, TelemetryContract.Method.BUTTON);
|
||||||
|
@ -215,24 +251,52 @@ public class LastTabsPanel extends HomeFragment {
|
||||||
mNewTabsListener.onNewTabs(urls);
|
mNewTabsListener.onNewTabs(urls);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class LastTabsCursorLoader extends SimpleCursorLoader {
|
private static class RecentTabsCursorLoader extends SimpleCursorLoader {
|
||||||
public LastTabsCursorLoader(Context context) {
|
private final ClosedTab[] closedTabs;
|
||||||
|
|
||||||
|
public RecentTabsCursorLoader(Context context, ClosedTab[] closedTabs) {
|
||||||
super(context);
|
super(context);
|
||||||
|
this.closedTabs = closedTabs;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addRow(MatrixCursor c, String url, String title, int type) {
|
||||||
|
final RowBuilder row = c.newRow();
|
||||||
|
row.add(-1);
|
||||||
|
row.add(url);
|
||||||
|
row.add(title);
|
||||||
|
row.add(type);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Cursor loadCursor() {
|
public Cursor loadCursor() {
|
||||||
final Context context = getContext();
|
final Context context = getContext();
|
||||||
|
|
||||||
|
final MatrixCursor c = new MatrixCursor(new String[] { RecentTabs._ID,
|
||||||
|
RecentTabs.URL,
|
||||||
|
RecentTabs.TITLE,
|
||||||
|
RecentTabs.TYPE });
|
||||||
|
|
||||||
|
if (closedTabs != null && closedTabs.length > 0) {
|
||||||
|
addRow(c, null, context.getString(R.string.home_closed_tabs_title), RecentTabs.TYPE_HEADER);
|
||||||
|
|
||||||
|
final int length = closedTabs.length;
|
||||||
|
for (int i = 0; i < length; i++) {
|
||||||
|
final String url = closedTabs[i].url;
|
||||||
|
|
||||||
|
// Don't show recent tabs for about:home
|
||||||
|
if (!AboutPages.isAboutHome(url)) {
|
||||||
|
addRow(c, url, closedTabs[i].title, RecentTabs.TYPE_CLOSED);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
final String jsonString = GeckoProfile.get(context).readSessionFile(true);
|
final String jsonString = GeckoProfile.get(context).readSessionFile(true);
|
||||||
if (jsonString == null) {
|
if (jsonString == null) {
|
||||||
// No previous session data
|
// No previous session data
|
||||||
return null;
|
return c;
|
||||||
}
|
}
|
||||||
|
|
||||||
final MatrixCursor c = new MatrixCursor(new String[] { Combined._ID,
|
final int count = c.getCount();
|
||||||
Combined.URL,
|
|
||||||
Combined.TITLE });
|
|
||||||
|
|
||||||
new SessionParser() {
|
new SessionParser() {
|
||||||
@Override
|
@Override
|
||||||
|
@ -244,12 +308,12 @@ public class LastTabsPanel extends HomeFragment {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
final RowBuilder row = c.newRow();
|
// If this is the first tab we're reading, add a header.
|
||||||
row.add(-1);
|
if (c.getCount() == count) {
|
||||||
row.add(url);
|
addRow(c, null, context.getString(R.string.home_last_tabs_title), RecentTabs.TYPE_HEADER);
|
||||||
|
}
|
||||||
|
|
||||||
final String title = tab.getTitle();
|
addRow(c, url, tab.getTitle(), RecentTabs.TYPE_LAST_TIME);
|
||||||
row.add(title);
|
|
||||||
}
|
}
|
||||||
}.parse(jsonString);
|
}.parse(jsonString);
|
||||||
|
|
||||||
|
@ -257,26 +321,52 @@ public class LastTabsPanel extends HomeFragment {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class LastTabsAdapter extends CursorAdapter {
|
private static class RecentTabsAdapter extends MultiTypeCursorAdapter {
|
||||||
public LastTabsAdapter(Context context) {
|
private static final int ROW_HEADER = 0;
|
||||||
super(context, null, 0);
|
private static final int ROW_STANDARD = 1;
|
||||||
|
|
||||||
|
private static final int[] VIEW_TYPES = new int[] { ROW_STANDARD, ROW_HEADER };
|
||||||
|
private static final int[] LAYOUT_TYPES = new int[] { R.layout.home_item_row, R.layout.home_header_row };
|
||||||
|
|
||||||
|
public RecentTabsAdapter(Context context) {
|
||||||
|
super(context, null, VIEW_TYPES, LAYOUT_TYPES);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getItemViewType(int position) {
|
||||||
|
final Cursor c = getCursor(position);
|
||||||
|
final int type = c.getInt(c.getColumnIndexOrThrow(RecentTabs.TYPE));
|
||||||
|
|
||||||
|
if (type == RecentTabs.TYPE_HEADER) {
|
||||||
|
return ROW_HEADER;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ROW_STANDARD;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isEnabled(int position) {
|
||||||
|
return (getItemViewType(position) != ROW_HEADER);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void bindView(View view, Context context, Cursor cursor) {
|
public void bindView(View view, Context context, int position) {
|
||||||
((TwoLinePageRow) view).updateFromCursor(cursor);
|
final int itemType = getItemViewType(position);
|
||||||
}
|
final Cursor c = getCursor(position);
|
||||||
|
|
||||||
@Override
|
if (itemType == ROW_HEADER) {
|
||||||
public View newView(Context context, Cursor cursor, ViewGroup parent) {
|
final String title = c.getString(c.getColumnIndexOrThrow(RecentTabs.TITLE));
|
||||||
return LayoutInflater.from(context).inflate(R.layout.home_item_row, parent, false);
|
final TextView textView = (TextView) view;
|
||||||
|
textView.setText(title);
|
||||||
|
} else if (itemType == ROW_STANDARD) {
|
||||||
|
final TwoLinePageRow pageRow = (TwoLinePageRow) view;
|
||||||
|
pageRow.updateFromCursor(c);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class CursorLoaderCallbacks implements LoaderCallbacks<Cursor> {
|
private class CursorLoaderCallbacks implements LoaderCallbacks<Cursor> {
|
||||||
@Override
|
@Override
|
||||||
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
|
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
|
||||||
return new LastTabsCursorLoader(getActivity());
|
return new RecentTabsCursorLoader(getActivity(), mClosedTabs);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
|
@ -11,6 +11,7 @@
|
||||||
<!ENTITY bookmarks_title "Bookmarks">
|
<!ENTITY bookmarks_title "Bookmarks">
|
||||||
<!ENTITY history_title "History">
|
<!ENTITY history_title "History">
|
||||||
<!ENTITY reading_list_title "Reading List">
|
<!ENTITY reading_list_title "Reading List">
|
||||||
|
<!ENTITY recent_tabs_title "Recent Tabs">
|
||||||
|
|
||||||
<!ENTITY switch_to_tab "Switch to tab">
|
<!ENTITY switch_to_tab "Switch to tab">
|
||||||
|
|
||||||
|
@ -349,10 +350,10 @@ size. -->
|
||||||
<!ENTITY home_clear_history_button "Clear browsing history">
|
<!ENTITY home_clear_history_button "Clear browsing history">
|
||||||
<!ENTITY home_clear_history_confirm "Are you sure you want to clear your history?">
|
<!ENTITY home_clear_history_confirm "Are you sure you want to clear your history?">
|
||||||
<!ENTITY home_bookmarks_empty "Bookmarks you save show up here.">
|
<!ENTITY home_bookmarks_empty "Bookmarks you save show up here.">
|
||||||
|
<!ENTITY home_closed_tabs_title "Recently closed tabs">
|
||||||
<!ENTITY home_last_tabs_title "Tabs from last time">
|
<!ENTITY home_last_tabs_title "Tabs from last time">
|
||||||
<!ENTITY home_last_tabs_open "Open all tabs from last time">
|
<!ENTITY home_last_tabs_open "Open all tabs from last time">
|
||||||
<!ENTITY home_last_tabs_empty "Your recent tabs show up here.">
|
<!ENTITY home_last_tabs_empty "Your recent tabs show up here.">
|
||||||
<!ENTITY home_most_recent_title "Most recent">
|
|
||||||
<!ENTITY home_most_recent_empty "Websites you visited most recently show up here.">
|
<!ENTITY home_most_recent_empty "Websites you visited most recently show up here.">
|
||||||
<!ENTITY home_reading_list_empty "Articles you save for later show up here.">
|
<!ENTITY home_reading_list_empty "Articles you save for later show up here.">
|
||||||
<!-- Localization note (home_reading_list_hint): The "TIP" string is synonymous to "hint", "clue", etc. This string is displayed
|
<!-- Localization note (home_reading_list_hint): The "TIP" string is synonymous to "hint", "clue", etc. This string is displayed
|
||||||
|
|
|
@ -109,6 +109,9 @@
|
||||||
|
|
||||||
<!ENTITY fxaccount_full_label 'Firefox Accounts'>
|
<!ENTITY fxaccount_full_label 'Firefox Accounts'>
|
||||||
|
|
||||||
|
<!ENTITY fxaccount_custom_server_account_title 'Using account on server'>
|
||||||
|
<!ENTITY fxaccount_custom_server_sync_title 'Storing Sync data on server'>
|
||||||
|
|
||||||
<!-- Localization note: these are shown in all screens that query the
|
<!-- Localization note: these are shown in all screens that query the
|
||||||
user for an email address and password. Hide and show are button
|
user for an email address and password. Hide and show are button
|
||||||
labels. -->
|
labels. -->
|
||||||
|
@ -175,7 +178,9 @@
|
||||||
|
|
||||||
<!ENTITY fxaccount_status_header2 'Firefox Account'>
|
<!ENTITY fxaccount_status_header2 'Firefox Account'>
|
||||||
<!ENTITY fxaccount_status_signed_in_as 'Signed in as'>
|
<!ENTITY fxaccount_status_signed_in_as 'Signed in as'>
|
||||||
|
<!ENTITY fxaccount_status_auth_server 'Account server'>
|
||||||
<!ENTITY fxaccount_status_device_name 'Device name'>
|
<!ENTITY fxaccount_status_device_name 'Device name'>
|
||||||
|
<!ENTITY fxaccount_status_sync_server 'Sync server'>
|
||||||
<!ENTITY fxaccount_status_sync '&syncBrand.shortName.label;'>
|
<!ENTITY fxaccount_status_sync '&syncBrand.shortName.label;'>
|
||||||
<!ENTITY fxaccount_status_sync_enabled '&syncBrand.shortName.label;: enabled'>
|
<!ENTITY fxaccount_status_sync_enabled '&syncBrand.shortName.label;: enabled'>
|
||||||
<!ENTITY fxaccount_status_needs_verification2 'Your account needs to be verified. Tap to resend verification email.'>
|
<!ENTITY fxaccount_status_needs_verification2 'Your account needs to be verified. Tap to resend verification email.'>
|
||||||
|
|
|
@ -272,8 +272,6 @@ gbjar.sources += [
|
||||||
'home/HomePagerTabStrip.java',
|
'home/HomePagerTabStrip.java',
|
||||||
'home/HomePanelPicker.java',
|
'home/HomePanelPicker.java',
|
||||||
'home/HomePanelsManager.java',
|
'home/HomePanelsManager.java',
|
||||||
'home/LastTabsPanel.java',
|
|
||||||
'home/MostRecentPanel.java',
|
|
||||||
'home/MultiTypeCursorAdapter.java',
|
'home/MultiTypeCursorAdapter.java',
|
||||||
'home/PanelAuthCache.java',
|
'home/PanelAuthCache.java',
|
||||||
'home/PanelAuthLayout.java',
|
'home/PanelAuthLayout.java',
|
||||||
|
@ -289,6 +287,7 @@ gbjar.sources += [
|
||||||
'home/PinSiteDialog.java',
|
'home/PinSiteDialog.java',
|
||||||
'home/ReadingListPanel.java',
|
'home/ReadingListPanel.java',
|
||||||
'home/ReadingListRow.java',
|
'home/ReadingListRow.java',
|
||||||
|
'home/RecentTabsPanel.java',
|
||||||
'home/SearchEngine.java',
|
'home/SearchEngine.java',
|
||||||
'home/SearchEngineRow.java',
|
'home/SearchEngineRow.java',
|
||||||
'home/SearchLoader.java',
|
'home/SearchLoader.java',
|
||||||
|
|
|
@ -4,15 +4,21 @@
|
||||||
|
|
||||||
package org.mozilla.gecko.preferences;
|
package org.mozilla.gecko.preferences;
|
||||||
|
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
import java.text.Collator;
|
import java.text.Collator;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
|
import java.util.HashSet;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
import org.mozilla.gecko.BrowserLocaleManager;
|
import org.mozilla.gecko.BrowserLocaleManager;
|
||||||
import org.mozilla.gecko.R;
|
import org.mozilla.gecko.R;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
import android.graphics.Bitmap;
|
||||||
|
import android.graphics.Canvas;
|
||||||
|
import android.graphics.Paint;
|
||||||
import android.preference.ListPreference;
|
import android.preference.ListPreference;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
import android.util.AttributeSet;
|
import android.util.AttributeSet;
|
||||||
|
@ -21,7 +27,51 @@ import android.util.Log;
|
||||||
public class LocaleListPreference extends ListPreference {
|
public class LocaleListPreference extends ListPreference {
|
||||||
private static final String LOG_TAG = "GeckoLocaleList";
|
private static final String LOG_TAG = "GeckoLocaleList";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* With thanks to <http://stackoverflow.com/a/22679283/22003> for the
|
||||||
|
* initial solution.
|
||||||
|
*
|
||||||
|
* This class encapsulates an approach to checking whether a script
|
||||||
|
* is usable on a device. We attempt to draw a character from the
|
||||||
|
* script (e.g., ব). If the fonts on the device don't have the correct
|
||||||
|
* glyph, Android typically renders whitespace (rather than .notdef).
|
||||||
|
*
|
||||||
|
* Pass in part of the name of the locale in its local representation,
|
||||||
|
* and a whitespace character; this class performs the graphical comparison.
|
||||||
|
*
|
||||||
|
* See Bug 1023451 Comment 24 for extensive explanation.
|
||||||
|
*/
|
||||||
|
private static class CharacterValidator {
|
||||||
|
private static final int BITMAP_WIDTH = 32;
|
||||||
|
private static final int BITMAP_HEIGHT = 48;
|
||||||
|
|
||||||
|
private final Paint paint = new Paint();
|
||||||
|
private final byte[] missingCharacter;
|
||||||
|
|
||||||
|
public CharacterValidator(String missing) {
|
||||||
|
this.missingCharacter = getPixels(drawBitmap(missing));
|
||||||
|
}
|
||||||
|
|
||||||
|
private Bitmap drawBitmap(String text){
|
||||||
|
Bitmap b = Bitmap.createBitmap(BITMAP_WIDTH, BITMAP_HEIGHT, Bitmap.Config.ALPHA_8);
|
||||||
|
Canvas c = new Canvas(b);
|
||||||
|
c.drawText(text, 0, BITMAP_HEIGHT / 2, this.paint);
|
||||||
|
return b;
|
||||||
|
}
|
||||||
|
private static byte[] getPixels(Bitmap b) {
|
||||||
|
ByteBuffer buffer = ByteBuffer.allocate(b.getByteCount());
|
||||||
|
b.copyPixelsToBuffer(buffer);
|
||||||
|
return buffer.array();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean characterIsMissingInFont(String ch) {
|
||||||
|
byte[] rendered = getPixels(drawBitmap(ch));
|
||||||
|
return Arrays.equals(rendered, missingCharacter);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private volatile Locale entriesLocale;
|
private volatile Locale entriesLocale;
|
||||||
|
private final CharacterValidator characterValidator;
|
||||||
|
|
||||||
public LocaleListPreference(Context context) {
|
public LocaleListPreference(Context context) {
|
||||||
this(context, null);
|
this(context, null);
|
||||||
|
@ -29,6 +79,10 @@ public class LocaleListPreference extends ListPreference {
|
||||||
|
|
||||||
public LocaleListPreference(Context context, AttributeSet attributes) {
|
public LocaleListPreference(Context context, AttributeSet attributes) {
|
||||||
super(context, attributes);
|
super(context, attributes);
|
||||||
|
|
||||||
|
// Thus far, missing glyphs are replaced by whitespace, not a box
|
||||||
|
// or other Unicode codepoint.
|
||||||
|
this.characterValidator = new CharacterValidator(" ");
|
||||||
buildList();
|
buildList();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -86,9 +140,54 @@ public class LocaleListPreference extends ListPreference {
|
||||||
// We sort by name, so we use Collator.
|
// We sort by name, so we use Collator.
|
||||||
return COLLATOR.compare(this.nativeName, another.nativeName);
|
return COLLATOR.compare(this.nativeName, another.nativeName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* See Bug 1023451 Comment 10 for the research that led to
|
||||||
|
* this method.
|
||||||
|
*
|
||||||
|
* @return true if this locale can be used for displaying UI
|
||||||
|
* on this device without known issues.
|
||||||
|
*/
|
||||||
|
public boolean isUsable(CharacterValidator validator) {
|
||||||
|
// Oh, for Java 7 switch statements.
|
||||||
|
if (this.tag.equals("bn-IN")) {
|
||||||
|
// Bengali sometimes has an English label if the Bengali script
|
||||||
|
// is missing. This prevents us from simply checking character
|
||||||
|
// rendering for bn-IN; we'll get a false positive for "B", not "ব".
|
||||||
|
//
|
||||||
|
// This doesn't seem to affect other Bengali-script locales
|
||||||
|
// (below), which always have a label in native script.
|
||||||
|
if (!this.nativeName.startsWith("বাংলা")) {
|
||||||
|
// We're on an Android version that doesn't even have
|
||||||
|
// characters to say বাংলা. Definite failure.
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private LocaleDescriptor[] getShippingLocales() {
|
// These locales use a script that is often unavailable
|
||||||
|
// on common Android devices. Make sure we can show them.
|
||||||
|
// See documentation for CharacterValidator.
|
||||||
|
// Note that bn-IN is checked here even if it passed above.
|
||||||
|
if (this.tag.equals("or") ||
|
||||||
|
this.tag.equals("pa-IN") ||
|
||||||
|
this.tag.equals("gu-IN") ||
|
||||||
|
this.tag.equals("bn-IN")) {
|
||||||
|
if (validator.characterIsMissingInFont(this.nativeName.substring(0, 1))) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Not every locale we ship can be used on every device, due to
|
||||||
|
* font or rendering constraints.
|
||||||
|
*
|
||||||
|
* This method filters down the list before generating the descriptor array.
|
||||||
|
*/
|
||||||
|
private LocaleDescriptor[] getUsableLocales() {
|
||||||
Collection<String> shippingLocales = BrowserLocaleManager.getPackagedLocaleTags(getContext());
|
Collection<String> shippingLocales = BrowserLocaleManager.getPackagedLocaleTags(getContext());
|
||||||
|
|
||||||
// Future: single-locale builds should be specified, too.
|
// Future: single-locale builds should be specified, too.
|
||||||
|
@ -97,15 +196,22 @@ public class LocaleListPreference extends ListPreference {
|
||||||
return new LocaleDescriptor[] { new LocaleDescriptor(fallbackTag) };
|
return new LocaleDescriptor[] { new LocaleDescriptor(fallbackTag) };
|
||||||
}
|
}
|
||||||
|
|
||||||
final int count = shippingLocales.size();
|
final int initialCount = shippingLocales.size();
|
||||||
final LocaleDescriptor[] descriptors = new LocaleDescriptor[count];
|
final Set<LocaleDescriptor> locales = new HashSet<LocaleDescriptor>(initialCount);
|
||||||
|
|
||||||
int i = 0;
|
|
||||||
for (String tag : shippingLocales) {
|
for (String tag : shippingLocales) {
|
||||||
descriptors[i++] = new LocaleDescriptor(tag);
|
final LocaleDescriptor descriptor = new LocaleDescriptor(tag);
|
||||||
|
|
||||||
|
if (!descriptor.isUsable(this.characterValidator)) {
|
||||||
|
Log.w(LOG_TAG, "Skipping locale " + tag + " on this device.");
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
Arrays.sort(descriptors, 0, count);
|
locales.add(descriptor);
|
||||||
|
}
|
||||||
|
|
||||||
|
final int usableCount = locales.size();
|
||||||
|
final LocaleDescriptor[] descriptors = locales.toArray(new LocaleDescriptor[usableCount]);
|
||||||
|
Arrays.sort(descriptors, 0, usableCount);
|
||||||
return descriptors;
|
return descriptors;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -154,7 +260,7 @@ public class LocaleListPreference extends ListPreference {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
final LocaleDescriptor[] descriptors = getShippingLocales();
|
final LocaleDescriptor[] descriptors = getUsableLocales();
|
||||||
final int count = descriptors.length;
|
final int count = descriptors.length;
|
||||||
|
|
||||||
this.entriesLocale = currentLocale;
|
this.entriesLocale = currentLocale;
|
||||||
|
|
|
@ -1,48 +0,0 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<!-- This Source Code Form is subject to the terms of the Mozilla Public
|
|
||||||
- License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
||||||
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
|
|
||||||
|
|
||||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
|
||||||
|
|
||||||
<item android:state_focused="false"
|
|
||||||
android:state_selected="false"
|
|
||||||
android:state_pressed="false"
|
|
||||||
android:drawable="@android:color/transparent"/>
|
|
||||||
|
|
||||||
<item android:state_focused="false"
|
|
||||||
android:state_selected="true"
|
|
||||||
android:state_pressed="false"
|
|
||||||
android:drawable="@color/background_light"/>
|
|
||||||
|
|
||||||
<item android:state_focused="true"
|
|
||||||
android:state_selected="false"
|
|
||||||
android:state_pressed="false"
|
|
||||||
android:drawable="@color/background_normal"/>
|
|
||||||
|
|
||||||
<item android:state_focused="true"
|
|
||||||
android:state_selected="true"
|
|
||||||
android:state_pressed="false"
|
|
||||||
android:drawable="@color/background_light"/>
|
|
||||||
|
|
||||||
<item android:state_focused="false"
|
|
||||||
android:state_selected="false"
|
|
||||||
android:state_pressed="true"
|
|
||||||
android:drawable="@color/background_normal"/>
|
|
||||||
|
|
||||||
<item android:state_focused="false"
|
|
||||||
android:state_selected="true"
|
|
||||||
android:state_pressed="true"
|
|
||||||
android:drawable="@color/background_normal"/>
|
|
||||||
|
|
||||||
<item android:state_focused="true"
|
|
||||||
android:state_selected="false"
|
|
||||||
android:state_pressed="true"
|
|
||||||
android:drawable="@color/background_normal"/>
|
|
||||||
|
|
||||||
<item android:state_focused="true"
|
|
||||||
android:state_selected="true"
|
|
||||||
android:state_pressed="true"
|
|
||||||
android:drawable="@color/background_normal"/>
|
|
||||||
|
|
||||||
</selector>
|
|
|
@ -1,48 +0,0 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<!-- This Source Code Form is subject to the terms of the Mozilla Public
|
|
||||||
- License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
||||||
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
|
|
||||||
|
|
||||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
|
||||||
|
|
||||||
<item android:state_focused="false"
|
|
||||||
android:state_selected="false"
|
|
||||||
android:state_pressed="false"
|
|
||||||
android:drawable="@android:color/transparent"/>
|
|
||||||
|
|
||||||
<item android:state_focused="false"
|
|
||||||
android:state_selected="true"
|
|
||||||
android:state_pressed="false"
|
|
||||||
android:drawable="@color/background_light"/>
|
|
||||||
|
|
||||||
<item android:state_focused="true"
|
|
||||||
android:state_selected="false"
|
|
||||||
android:state_pressed="false"
|
|
||||||
android:drawable="@color/background_normal"/>
|
|
||||||
|
|
||||||
<item android:state_focused="true"
|
|
||||||
android:state_selected="true"
|
|
||||||
android:state_pressed="false"
|
|
||||||
android:drawable="@color/background_light"/>
|
|
||||||
|
|
||||||
<item android:state_focused="false"
|
|
||||||
android:state_selected="false"
|
|
||||||
android:state_pressed="true"
|
|
||||||
android:drawable="@color/background_normal"/>
|
|
||||||
|
|
||||||
<item android:state_focused="false"
|
|
||||||
android:state_selected="true"
|
|
||||||
android:state_pressed="true"
|
|
||||||
android:drawable="@color/background_normal"/>
|
|
||||||
|
|
||||||
<item android:state_focused="true"
|
|
||||||
android:state_selected="false"
|
|
||||||
android:state_pressed="true"
|
|
||||||
android:drawable="@color/background_normal"/>
|
|
||||||
|
|
||||||
<item android:state_focused="true"
|
|
||||||
android:state_selected="true"
|
|
||||||
android:state_pressed="true"
|
|
||||||
android:drawable="@color/background_normal"/>
|
|
||||||
|
|
||||||
</selector>
|
|
|
@ -1,48 +0,0 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<!-- This Source Code Form is subject to the terms of the Mozilla Public
|
|
||||||
- License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
||||||
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
|
|
||||||
|
|
||||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
|
||||||
|
|
||||||
<item android:state_focused="false"
|
|
||||||
android:state_selected="false"
|
|
||||||
android:state_pressed="false"
|
|
||||||
android:drawable="@android:color/transparent"/>
|
|
||||||
|
|
||||||
<item android:state_focused="false"
|
|
||||||
android:state_selected="true"
|
|
||||||
android:state_pressed="false"
|
|
||||||
android:drawable="@drawable/history_tabs_indicator_selected"/>
|
|
||||||
|
|
||||||
<item android:state_focused="true"
|
|
||||||
android:state_selected="false"
|
|
||||||
android:state_pressed="false"
|
|
||||||
android:drawable="@color/background_normal"/>
|
|
||||||
|
|
||||||
<item android:state_focused="true"
|
|
||||||
android:state_selected="true"
|
|
||||||
android:state_pressed="false"
|
|
||||||
android:drawable="@drawable/history_tabs_indicator_selected"/>
|
|
||||||
|
|
||||||
<item android:state_focused="false"
|
|
||||||
android:state_selected="false"
|
|
||||||
android:state_pressed="true"
|
|
||||||
android:drawable="@color/background_normal"/>
|
|
||||||
|
|
||||||
<item android:state_focused="false"
|
|
||||||
android:state_selected="true"
|
|
||||||
android:state_pressed="true"
|
|
||||||
android:drawable="@color/background_normal"/>
|
|
||||||
|
|
||||||
<item android:state_focused="true"
|
|
||||||
android:state_selected="false"
|
|
||||||
android:state_pressed="true"
|
|
||||||
android:drawable="@color/background_normal"/>
|
|
||||||
|
|
||||||
<item android:state_focused="true"
|
|
||||||
android:state_selected="true"
|
|
||||||
android:state_pressed="true"
|
|
||||||
android:drawable="@color/background_normal"/>
|
|
||||||
|
|
||||||
</selector>
|
|
|
@ -1,24 +0,0 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<!-- This Source Code Form is subject to the terms of the Mozilla Public
|
|
||||||
- License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
||||||
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
|
|
||||||
|
|
||||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
xmlns:gecko="http://schemas.android.com/apk/res-auto"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent">
|
|
||||||
|
|
||||||
<org.mozilla.gecko.widget.IconTabWidget android:id="@+id/tab_icon_widget"
|
|
||||||
style="@style/Widget.Home.HistoryTabWidget"
|
|
||||||
android:layout_width="@dimen/history_tab_widget_width"
|
|
||||||
android:layout_height="@dimen/history_tab_widget_height"
|
|
||||||
android:orientation="vertical"
|
|
||||||
android:layout="@layout/home_history_tabs_indicator"
|
|
||||||
gecko:display="text"/>
|
|
||||||
|
|
||||||
<FrameLayout android:id="@+id/history_panel_container"
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:layout_weight="1" />
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
|
@ -1,10 +0,0 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<!-- This Source Code Form is subject to the terms of the Mozilla Public
|
|
||||||
- License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
||||||
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
|
|
||||||
|
|
||||||
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
style="@style/Widget.Home.HistoryTabIndicator"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="@dimen/history_tab_indicator_height"
|
|
||||||
android:background="@drawable/home_history_tabs_indicator"/>
|
|
|
@ -1,24 +0,0 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<!-- This Source Code Form is subject to the terms of the Mozilla Public
|
|
||||||
- License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
||||||
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
|
|
||||||
|
|
||||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
xmlns:gecko="http://schemas.android.com/apk/res-auto"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent">
|
|
||||||
|
|
||||||
<org.mozilla.gecko.widget.IconTabWidget android:id="@+id/tab_icon_widget"
|
|
||||||
style="@style/Widget.Home.HistoryTabWidget"
|
|
||||||
android:layout_width="@dimen/history_tab_widget_width"
|
|
||||||
android:layout_height="@dimen/history_tab_widget_height"
|
|
||||||
android:orientation="vertical"
|
|
||||||
android:layout="@layout/home_history_tabs_indicator"
|
|
||||||
gecko:display="text"/>
|
|
||||||
|
|
||||||
<FrameLayout android:id="@+id/history_panel_container"
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:layout_weight="1" />
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
|
@ -1,10 +0,0 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<!-- This Source Code Form is subject to the terms of the Mozilla Public
|
|
||||||
- License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
||||||
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
|
|
||||||
|
|
||||||
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
style="@style/Widget.Home.HistoryTabIndicator"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="@dimen/history_tab_indicator_height"
|
|
||||||
android:background="@drawable/home_history_tabs_indicator"/>
|
|
|
@ -5,8 +5,8 @@
|
||||||
- file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
- file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
-->
|
-->
|
||||||
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="fill_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="fill_parent"
|
||||||
android:fillViewport="true" >
|
android:fillViewport="true" >
|
||||||
|
|
||||||
<LinearLayout style="@style/FxAccountMiddle" >
|
<LinearLayout style="@style/FxAccountMiddle" >
|
||||||
|
|
|
@ -5,8 +5,8 @@
|
||||||
- file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
- file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
-->
|
-->
|
||||||
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="fill_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="fill_parent"
|
||||||
android:fillViewport="true" >
|
android:fillViewport="true" >
|
||||||
|
|
||||||
<LinearLayout style="@style/FxAccountMiddle" >
|
<LinearLayout style="@style/FxAccountMiddle" >
|
||||||
|
|
|
@ -6,8 +6,8 @@
|
||||||
-->
|
-->
|
||||||
|
|
||||||
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="fill_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="fill_parent"
|
||||||
android:fillViewport="true" >
|
android:fillViewport="true" >
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
|
@ -20,6 +20,8 @@
|
||||||
style="@style/FxAccountHeaderItem"
|
style="@style/FxAccountHeaderItem"
|
||||||
android:text="@string/fxaccount_create_account_header" />
|
android:text="@string/fxaccount_create_account_header" />
|
||||||
|
|
||||||
|
<include layout="@layout/fxaccount_custom_server_view" />
|
||||||
|
|
||||||
<include layout="@layout/fxaccount_email_password_view" />
|
<include layout="@layout/fxaccount_email_password_view" />
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
|
|
|
@ -5,8 +5,8 @@
|
||||||
- file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
- file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
-->
|
-->
|
||||||
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="fill_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="fill_parent"
|
||||||
android:fillViewport="true" >
|
android:fillViewport="true" >
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
|
|
|
@ -0,0 +1,116 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!--
|
||||||
|
This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
- License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
- file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<merge xmlns:android="http://schemas.android.com/apk/res/android" >
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/account_server_layout"
|
||||||
|
android:layout_width="fill_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginBottom="10dp"
|
||||||
|
android:background="@color/fxaccount_error_preference_backgroundcolor"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:minHeight="?android:attr/listPreferredItemHeight" >
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:contentDescription="@string/fxaccount_empty_contentDescription"
|
||||||
|
android:gravity="center"
|
||||||
|
android:minWidth="48dip"
|
||||||
|
android:padding="10dip"
|
||||||
|
android:src="@drawable/fxaccount_sync_error" />
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:paddingBottom="6dip"
|
||||||
|
android:paddingRight="10dip"
|
||||||
|
android:paddingTop="6dip" >
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+android:id/account_server_title"
|
||||||
|
style="@style/FxAccountTextItem"
|
||||||
|
android:layout_width="fill_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="center_horizontal"
|
||||||
|
android:layout_marginBottom="0dp"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:text="@string/fxaccount_custom_server_account_title" >
|
||||||
|
</TextView>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+android:id/account_server_summary"
|
||||||
|
style="@style/FxAccountTextItem"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginBottom="0dp"
|
||||||
|
android:ellipsize="middle"
|
||||||
|
android:focusable="true"
|
||||||
|
android:focusableInTouchMode="true"
|
||||||
|
android:singleLine="true"
|
||||||
|
android:textAppearance="?android:attr/textAppearanceSmall"
|
||||||
|
android:textSize="12sp" />
|
||||||
|
</LinearLayout>
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/sync_server_layout"
|
||||||
|
android:layout_width="fill_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginBottom="10dp"
|
||||||
|
android:background="@color/fxaccount_error_preference_backgroundcolor"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:minHeight="?android:attr/listPreferredItemHeight" >
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:contentDescription="@string/fxaccount_empty_contentDescription"
|
||||||
|
android:gravity="center"
|
||||||
|
android:minWidth="48dip"
|
||||||
|
android:padding="10dip"
|
||||||
|
android:src="@drawable/fxaccount_sync_error" />
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:paddingBottom="6dip"
|
||||||
|
android:paddingRight="10dip"
|
||||||
|
android:paddingTop="6dip" >
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+android:id/sync_server_title"
|
||||||
|
style="@style/FxAccountTextItem"
|
||||||
|
android:layout_width="fill_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="center_horizontal"
|
||||||
|
android:layout_marginBottom="0dp"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:text="@string/fxaccount_custom_server_sync_title" >
|
||||||
|
</TextView>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+android:id/sync_server_summary"
|
||||||
|
style="@style/FxAccountTextItem"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginBottom="0dp"
|
||||||
|
android:ellipsize="marquee"
|
||||||
|
android:focusable="true"
|
||||||
|
android:focusableInTouchMode="true"
|
||||||
|
android:singleLine="true"
|
||||||
|
android:textAppearance="?android:attr/textAppearanceSmall"
|
||||||
|
android:textSize="12sp" />
|
||||||
|
</LinearLayout>
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</merge>
|
|
@ -8,7 +8,7 @@
|
||||||
<merge xmlns:android="http://schemas.android.com/apk/res/android" >
|
<merge xmlns:android="http://schemas.android.com/apk/res/android" >
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="fill_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:orientation="vertical" >
|
android:orientation="vertical" >
|
||||||
|
|
||||||
|
@ -25,14 +25,14 @@
|
||||||
</AutoCompleteTextView>
|
</AutoCompleteTextView>
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="fill_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:orientation="horizontal" >
|
android:orientation="horizontal" >
|
||||||
|
|
||||||
<EditText
|
<EditText
|
||||||
android:id="@+id/password"
|
android:id="@+id/password"
|
||||||
style="@style/FxAccountEditItem"
|
style="@style/FxAccountEditItem"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="fill_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_weight="1"
|
android:layout_weight="1"
|
||||||
android:background="@drawable/fxaccount_password_background"
|
android:background="@drawable/fxaccount_password_background"
|
||||||
|
@ -51,22 +51,22 @@
|
||||||
happy. Be thankful there are not three buttons! -->
|
happy. Be thankful there are not three buttons! -->
|
||||||
<FrameLayout
|
<FrameLayout
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="fill_parent"
|
||||||
android:layout_weight="0"
|
android:layout_weight="0"
|
||||||
android:orientation="horizontal" >
|
android:orientation="horizontal" >
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
android:id="@+id/show_password"
|
android:id="@+id/show_password"
|
||||||
style="@style/FxAccountShowHidePasswordButton"
|
style="@style/FxAccountShowHidePasswordButton"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="fill_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="fill_parent"
|
||||||
android:text="@string/fxaccount_password_show" >
|
android:text="@string/fxaccount_password_show" >
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
style="@style/FxAccountShowHidePasswordButton"
|
style="@style/FxAccountShowHidePasswordButton"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="fill_parent"
|
||||||
android:text="@string/fxaccount_password_show"
|
android:text="@string/fxaccount_password_show"
|
||||||
android:visibility="invisible" >
|
android:visibility="invisible" >
|
||||||
</Button>
|
</Button>
|
||||||
|
@ -74,7 +74,7 @@
|
||||||
<Button
|
<Button
|
||||||
style="@style/FxAccountShowHidePasswordButton"
|
style="@style/FxAccountShowHidePasswordButton"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="fill_parent"
|
||||||
android:text="@string/fxaccount_password_hide"
|
android:text="@string/fxaccount_password_hide"
|
||||||
android:visibility="invisible" >
|
android:visibility="invisible" >
|
||||||
</Button>
|
</Button>
|
||||||
|
|
|
@ -5,8 +5,8 @@
|
||||||
- file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
- file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
-->
|
-->
|
||||||
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="fill_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="fill_parent"
|
||||||
android:fillViewport="true" >
|
android:fillViewport="true" >
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
|
|
|
@ -19,12 +19,12 @@
|
||||||
|
|
||||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:orientation="vertical"
|
android:orientation="vertical"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="fill_parent"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="fill_parent"
|
||||||
android:background="@android:color/transparent">
|
android:background="@android:color/transparent">
|
||||||
|
|
||||||
<ListView android:id="@android:id/list"
|
<ListView android:id="@android:id/list"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="fill_parent"
|
||||||
android:layout_height="0px"
|
android:layout_height="0px"
|
||||||
android:layout_weight="1"
|
android:layout_weight="1"
|
||||||
android:paddingTop="0dip"
|
android:paddingTop="0dip"
|
||||||
|
|
|
@ -6,8 +6,8 @@
|
||||||
-->
|
-->
|
||||||
|
|
||||||
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="fill_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="fill_parent"
|
||||||
android:fillViewport="true" >
|
android:fillViewport="true" >
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
|
@ -20,6 +20,8 @@
|
||||||
style="@style/FxAccountHeaderItem"
|
style="@style/FxAccountHeaderItem"
|
||||||
android:text="@string/fxaccount_sign_in_sub_header" />
|
android:text="@string/fxaccount_sign_in_sub_header" />
|
||||||
|
|
||||||
|
<include layout="@layout/fxaccount_custom_server_view" />
|
||||||
|
|
||||||
<include layout="@layout/fxaccount_email_password_view" />
|
<include layout="@layout/fxaccount_email_password_view" />
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
|
@ -39,7 +41,7 @@
|
||||||
</RelativeLayout>
|
</RelativeLayout>
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="fill_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginTop="10dp"
|
android:layout_marginTop="10dp"
|
||||||
android:orientation="horizontal" >
|
android:orientation="horizontal" >
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="fill_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:background="@color/fxaccount_error_preference_backgroundcolor"
|
android:background="@color/fxaccount_error_preference_backgroundcolor"
|
||||||
android:gravity="center_vertical"
|
android:gravity="center_vertical"
|
||||||
|
@ -35,7 +35,7 @@
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+android:id/title"
|
android:id="@+android:id/title"
|
||||||
style="@style/FxAccountTextItem"
|
style="@style/FxAccountTextItem"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="fill_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_gravity="center_horizontal"
|
android:layout_gravity="center_horizontal"
|
||||||
android:gravity="center_vertical" >
|
android:gravity="center_vertical" >
|
||||||
|
|
|
@ -6,8 +6,8 @@
|
||||||
-->
|
-->
|
||||||
|
|
||||||
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="fill_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="fill_parent"
|
||||||
android:fillViewport="true" >
|
android:fillViewport="true" >
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
|
@ -20,6 +20,8 @@
|
||||||
style="@style/FxAccountHeaderItem"
|
style="@style/FxAccountHeaderItem"
|
||||||
android:text="@string/fxaccount_update_credentials_header" />
|
android:text="@string/fxaccount_update_credentials_header" />
|
||||||
|
|
||||||
|
<include layout="@layout/fxaccount_custom_server_view" />
|
||||||
|
|
||||||
<include layout="@layout/fxaccount_email_password_view" />
|
<include layout="@layout/fxaccount_email_password_view" />
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
|
@ -41,6 +43,7 @@
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/forgot_password_link"
|
android:id="@+id/forgot_password_link"
|
||||||
style="@style/FxAccountLinkifiedItem"
|
style="@style/FxAccountLinkifiedItem"
|
||||||
|
android:layout_marginTop="10dp"
|
||||||
android:text="@string/fxaccount_sign_in_forgot_password" />
|
android:text="@string/fxaccount_sign_in_forgot_password" />
|
||||||
|
|
||||||
<LinearLayout style="@style/FxAccountSpacer" />
|
<LinearLayout style="@style/FxAccountSpacer" />
|
||||||
|
|
|
@ -8,17 +8,18 @@
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:orientation="vertical">
|
android:orientation="vertical">
|
||||||
|
|
||||||
<FrameLayout android:id="@+id/history_panel_container"
|
<include layout="@layout/home_history_list"/>
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="0dp"
|
|
||||||
android:layout_weight="1" />
|
|
||||||
|
|
||||||
<org.mozilla.gecko.widget.IconTabWidget android:id="@+id/tab_icon_widget"
|
<LinearLayout android:layout_width="match_parent"
|
||||||
android:layout_width="match_parent"
|
android:layout_height="wrap_content"
|
||||||
android:layout_height="@dimen/browser_toolbar_height"
|
android:background="@color/home_button_bar_bg">
|
||||||
android:tabStripEnabled="false"
|
|
||||||
android:showDividers="none"
|
<Button android:id="@+id/clear_history_button"
|
||||||
android:background="@color/background_light"
|
style="@style/Widget.Home.ActionButton"
|
||||||
android:layout="@layout/home_history_tabs_indicator"/>
|
android:text="@string/home_clear_history_button"
|
||||||
|
android:gravity="center"
|
||||||
|
android:visibility="gone"/>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
|
@ -1,10 +0,0 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<!-- This Source Code Form is subject to the terms of the Mozilla Public
|
|
||||||
- License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
||||||
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
|
|
||||||
|
|
||||||
<ImageButton xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:layout_weight="1"
|
|
||||||
android:background="@drawable/home_history_tabs_indicator"/>
|
|
|
@ -1,25 +0,0 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<!-- This Source Code Form is subject to the terms of the Mozilla Public
|
|
||||||
- License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
||||||
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
|
|
||||||
|
|
||||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:orientation="vertical">
|
|
||||||
|
|
||||||
<include layout="@layout/home_history_list"/>
|
|
||||||
|
|
||||||
<LinearLayout android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:background="@color/home_button_bar_bg">
|
|
||||||
|
|
||||||
<Button android:id="@+id/clear_history_button"
|
|
||||||
style="@style/Widget.Home.ActionButton"
|
|
||||||
android:text="@string/home_clear_history_button"
|
|
||||||
android:gravity="center"
|
|
||||||
android:visibility="gone"/>
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
|
@ -10,16 +10,4 @@
|
||||||
|
|
||||||
<include layout="@layout/home_history_list"/>
|
<include layout="@layout/home_history_list"/>
|
||||||
|
|
||||||
<LinearLayout android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:background="@color/home_button_bar_bg">
|
|
||||||
|
|
||||||
<Button android:id="@+id/open_all_tabs_button"
|
|
||||||
style="@style/Widget.Home.ActionButton"
|
|
||||||
android:text="@string/home_last_tabs_open"
|
|
||||||
android:gravity="center"
|
|
||||||
android:visibility="gone"/>
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
|
@ -5,7 +5,7 @@
|
||||||
|
|
||||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:gravity="center_vertical"
|
android:gravity="center_vertical"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="fill_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:minHeight="?android:attr/listPreferredItemHeight"
|
android:minHeight="?android:attr/listPreferredItemHeight"
|
||||||
android:orientation="horizontal"
|
android:orientation="horizontal"
|
||||||
|
|
|
@ -11,20 +11,20 @@
|
||||||
android:text="@string/sync_title_send_tab" />
|
android:text="@string/sync_title_send_tab" />
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="fill_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:orientation="vertical"
|
android:orientation="vertical"
|
||||||
android:padding="@dimen/SyncSpace" >
|
android:padding="@dimen/SyncSpace" >
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/title"
|
android:id="@+id/title"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="fill_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:textAppearance="?android:attr/textAppearanceMedium"
|
android:textAppearance="?android:attr/textAppearanceMedium"
|
||||||
android:text="@string/sync_title_send_tab" />
|
android:text="@string/sync_title_send_tab" />
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/uri"
|
android:id="@+id/uri"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="fill_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:textAppearance="?android:attr/textAppearanceSmall"
|
android:textAppearance="?android:attr/textAppearanceSmall"
|
||||||
android:text="@string/sync_title_send_tab" />
|
android:text="@string/sync_title_send_tab" />
|
||||||
|
|
|
@ -19,7 +19,7 @@
|
||||||
<ProgressBar
|
<ProgressBar
|
||||||
android:id="@+id/waiting_content1"
|
android:id="@+id/waiting_content1"
|
||||||
style="@style/Widget.ProgressBar.Horizontal"
|
style="@style/Widget.ProgressBar.Horizontal"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="fill_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:indeterminateOnly="true"
|
android:indeterminateOnly="true"
|
||||||
android:padding="@dimen/SyncSpace" />
|
android:padding="@dimen/SyncSpace" />
|
||||||
|
|
|
@ -6,6 +6,6 @@
|
||||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
style="@style/SyncLayout" >
|
style="@style/SyncLayout" >
|
||||||
<WebView android:id="@+id/web_engine"
|
<WebView android:id="@+id/web_engine"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="fill_parent"
|
||||||
android:layout_height="match_parent" />
|
android:layout_height="fill_parent" />
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
|
@ -1,10 +0,0 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<!-- This Source Code Form is subject to the terms of the Mozilla Public
|
|
||||||
- License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
||||||
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
|
|
||||||
|
|
||||||
<resources>
|
|
||||||
|
|
||||||
<dimen name="history_tab_widget_width">360dp</dimen>
|
|
||||||
|
|
||||||
</resources>
|
|
|
@ -7,7 +7,7 @@
|
||||||
<!-- Top title bar: a text view with the Sync icon to the left. -->
|
<!-- Top title bar: a text view with the Sync icon to the left. -->
|
||||||
<style name="SyncTop" parent="@android:style/Widget.Holo.ActionBar">
|
<style name="SyncTop" parent="@android:style/Widget.Holo.ActionBar">
|
||||||
<item name="android:textAppearance">@android:style/TextAppearance.Holo.Widget.ActionBar.Title</item>
|
<item name="android:textAppearance">@android:style/TextAppearance.Holo.Widget.ActionBar.Title</item>
|
||||||
<item name="android:layout_width">match_parent</item>
|
<item name="android:layout_width">fill_parent</item>
|
||||||
<item name="android:layout_height">wrap_content</item>
|
<item name="android:layout_height">wrap_content</item>
|
||||||
<item name="android:gravity">center_vertical|left</item>
|
<item name="android:gravity">center_vertical|left</item>
|
||||||
<item name="android:drawableLeft">@drawable/icon</item>
|
<item name="android:drawableLeft">@drawable/icon</item>
|
||||||
|
@ -17,12 +17,12 @@
|
||||||
|
|
||||||
<!-- Bottom bar: a horizontal linear layout with buttons in it. -->
|
<!-- Bottom bar: a horizontal linear layout with buttons in it. -->
|
||||||
<style name="SyncBottom" parent="@android:style/Holo.Light.ButtonBar">
|
<style name="SyncBottom" parent="@android:style/Holo.Light.ButtonBar">
|
||||||
<item name="android:layout_width">match_parent</item>
|
<item name="android:layout_width">fill_parent</item>
|
||||||
<item name="android:layout_height">wrap_content</item>
|
<item name="android:layout_height">wrap_content</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<style name="SyncButton" parent="@android:style/Widget.Holo.Light.Button.Small">
|
<style name="SyncButton" parent="@android:style/Widget.Holo.Light.Button.Small">
|
||||||
<item name="android:layout_width">match_parent</item>
|
<item name="android:layout_width">fill_parent</item>
|
||||||
<item name="android:layout_height">wrap_content</item>
|
<item name="android:layout_height">wrap_content</item>
|
||||||
<item name="android:layout_weight">1</item>
|
<item name="android:layout_weight">1</item>
|
||||||
<item name="android:background">?android:attr/selectableItemBackground</item>
|
<item name="android:background">?android:attr/selectableItemBackground</item>
|
||||||
|
|
|
@ -1,10 +0,0 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<!-- This Source Code Form is subject to the terms of the Mozilla Public
|
|
||||||
- License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
||||||
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
|
|
||||||
|
|
||||||
<resources>
|
|
||||||
|
|
||||||
<dimen name="history_tab_widget_width">480dp</dimen>
|
|
||||||
|
|
||||||
</resources>
|
|
|
@ -11,7 +11,6 @@
|
||||||
<dimen name="tabs_counter_size">26sp</dimen>
|
<dimen name="tabs_counter_size">26sp</dimen>
|
||||||
<dimen name="tabs_panel_indicator_width">60dp</dimen>
|
<dimen name="tabs_panel_indicator_width">60dp</dimen>
|
||||||
<dimen name="tabs_panel_list_padding">8dip</dimen>
|
<dimen name="tabs_panel_list_padding">8dip</dimen>
|
||||||
<dimen name="history_tab_widget_width">270dp</dimen>
|
|
||||||
<dimen name="panel_grid_view_column_width">250dp</dimen>
|
<dimen name="panel_grid_view_column_width">250dp</dimen>
|
||||||
|
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
@ -82,12 +82,6 @@
|
||||||
<dimen name="url_bar_offset_left">32dp</dimen>
|
<dimen name="url_bar_offset_left">32dp</dimen>
|
||||||
<dimen name="history_tab_indicator_height">50dp</dimen>
|
<dimen name="history_tab_indicator_height">50dp</dimen>
|
||||||
|
|
||||||
<!-- We need to maintain height for the tab widget on History Page
|
|
||||||
since android does not add footer/header divider height to its
|
|
||||||
calculation for wrap_content in LinearLayout.
|
|
||||||
50dp * 2 Views + 30dp padding + 4dp dividers-->
|
|
||||||
<dimen name="history_tab_widget_height">134dp</dimen>
|
|
||||||
|
|
||||||
<!-- PageActionButtons dimensions -->
|
<!-- PageActionButtons dimensions -->
|
||||||
<dimen name="page_action_button_width">32dp</dimen>
|
<dimen name="page_action_button_width">32dp</dimen>
|
||||||
|
|
||||||
|
|
|
@ -16,7 +16,7 @@
|
||||||
<style name="FxAccountMiddle">
|
<style name="FxAccountMiddle">
|
||||||
<item name="android:background">@android:color/white</item>
|
<item name="android:background">@android:color/white</item>
|
||||||
<item name="android:orientation">vertical</item>
|
<item name="android:orientation">vertical</item>
|
||||||
<item name="android:layout_width">match_parent</item>
|
<item name="android:layout_width">fill_parent</item>
|
||||||
<item name="android:layout_height">wrap_content</item>
|
<item name="android:layout_height">wrap_content</item>
|
||||||
<item name="android:layout_weight">1</item>
|
<item name="android:layout_weight">1</item>
|
||||||
<item name="android:paddingTop">30dp</item>
|
<item name="android:paddingTop">30dp</item>
|
||||||
|
@ -27,7 +27,7 @@
|
||||||
|
|
||||||
<style name="FxAccountSpacer">
|
<style name="FxAccountSpacer">
|
||||||
<item name="android:orientation">vertical</item>
|
<item name="android:orientation">vertical</item>
|
||||||
<item name="android:layout_width">match_parent</item>
|
<item name="android:layout_width">fill_parent</item>
|
||||||
<item name="android:layout_height">0dp</item>
|
<item name="android:layout_height">0dp</item>
|
||||||
<item name="android:layout_weight">1</item>
|
<item name="android:layout_weight">1</item>
|
||||||
</style>
|
</style>
|
||||||
|
@ -39,7 +39,7 @@
|
||||||
|
|
||||||
<style name="FxAccountTextItem" parent="@android:style/TextAppearance.Medium">
|
<style name="FxAccountTextItem" parent="@android:style/TextAppearance.Medium">
|
||||||
<item name="android:textColor">@color/fxaccount_textColor</item>
|
<item name="android:textColor">@color/fxaccount_textColor</item>
|
||||||
<item name="android:layout_width">match_parent</item>
|
<item name="android:layout_width">fill_parent</item>
|
||||||
<item name="android:layout_height">wrap_content</item>
|
<item name="android:layout_height">wrap_content</item>
|
||||||
<item name="android:gravity">center_horizontal</item>
|
<item name="android:gravity">center_horizontal</item>
|
||||||
<item name="android:textSize">16sp</item>
|
<item name="android:textSize">16sp</item>
|
||||||
|
@ -57,7 +57,7 @@
|
||||||
<item name="android:textColor">@drawable/fxaccount_button_color</item>
|
<item name="android:textColor">@drawable/fxaccount_button_color</item>
|
||||||
<item name="android:textSize">24sp</item>
|
<item name="android:textSize">24sp</item>
|
||||||
<item name="android:padding">20dp</item>
|
<item name="android:padding">20dp</item>
|
||||||
<item name="android:layout_width">match_parent</item>
|
<item name="android:layout_width">fill_parent</item>
|
||||||
<item name="android:layout_height">wrap_content</item>
|
<item name="android:layout_height">wrap_content</item>
|
||||||
<item name="android:layout_marginBottom">10dp</item>
|
<item name="android:layout_marginBottom">10dp</item>
|
||||||
</style>
|
</style>
|
||||||
|
@ -81,7 +81,7 @@
|
||||||
<item name="android:focusable">true</item>
|
<item name="android:focusable">true</item>
|
||||||
<item name="android:textColor">@color/fxaccount_linkified_textColor</item>
|
<item name="android:textColor">@color/fxaccount_linkified_textColor</item>
|
||||||
<item name="android:textColorLink">@color/fxaccount_linkified_textColorLink</item>
|
<item name="android:textColorLink">@color/fxaccount_linkified_textColorLink</item>
|
||||||
<item name="android:layout_width">match_parent</item>
|
<item name="android:layout_width">fill_parent</item>
|
||||||
<item name="android:layout_height">wrap_content</item>
|
<item name="android:layout_height">wrap_content</item>
|
||||||
<item name="android:gravity">center</item>
|
<item name="android:gravity">center</item>
|
||||||
</style>
|
</style>
|
||||||
|
@ -95,7 +95,7 @@
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<style name="FxAccountErrorItem">
|
<style name="FxAccountErrorItem">
|
||||||
<item name="android:layout_width">match_parent</item>
|
<item name="android:layout_width">fill_parent</item>
|
||||||
<item name="android:layout_marginBottom">10dp</item>
|
<item name="android:layout_marginBottom">10dp</item>
|
||||||
<item name="android:layout_marginTop">10dp</item>
|
<item name="android:layout_marginTop">10dp</item>
|
||||||
<item name="android:layout_height">wrap_content</item>
|
<item name="android:layout_height">wrap_content</item>
|
||||||
|
@ -118,13 +118,13 @@
|
||||||
|
|
||||||
<style name="FxAccountButtonLayout">
|
<style name="FxAccountButtonLayout">
|
||||||
<item name="android:orientation">vertical</item>
|
<item name="android:orientation">vertical</item>
|
||||||
<item name="android:layout_width">match_parent</item>
|
<item name="android:layout_width">fill_parent</item>
|
||||||
<item name="android:layout_height">wrap_content</item>
|
<item name="android:layout_height">wrap_content</item>
|
||||||
<item name="android:background">@drawable/fxaccount_button_background</item>
|
<item name="android:background">@drawable/fxaccount_button_background</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<style name="FxAccountCheckBox">
|
<style name="FxAccountCheckBox">
|
||||||
<item name="android:layout_width">match_parent</item>
|
<item name="android:layout_width">fill_parent</item>
|
||||||
<item name="android:layout_height">wrap_content</item>
|
<item name="android:layout_height">wrap_content</item>
|
||||||
<item name="android:layout_marginBottom">10dp</item>
|
<item name="android:layout_marginBottom">10dp</item>
|
||||||
<item name="android:textColor">@drawable/fxaccount_checkbox_textcolor</item>
|
<item name="android:textColor">@drawable/fxaccount_checkbox_textcolor</item>
|
||||||
|
|
|
@ -5,8 +5,8 @@
|
||||||
|
|
||||||
<resources>
|
<resources>
|
||||||
<style name="SyncLayout">
|
<style name="SyncLayout">
|
||||||
<item name="android:layout_width">match_parent</item>
|
<item name="android:layout_width">fill_parent</item>
|
||||||
<item name="android:layout_height">match_parent</item>
|
<item name="android:layout_height">fill_parent</item>
|
||||||
</style>
|
</style>
|
||||||
<style name="SyncLayout.Vertical" parent="@style/SyncLayout">
|
<style name="SyncLayout.Vertical" parent="@style/SyncLayout">
|
||||||
<item name="android:orientation">vertical</item>
|
<item name="android:orientation">vertical</item>
|
||||||
|
@ -17,14 +17,14 @@
|
||||||
|
|
||||||
<!-- TextView Styles -->
|
<!-- TextView Styles -->
|
||||||
<style name="SyncTextFrame" parent="@style/TextAppearance">
|
<style name="SyncTextFrame" parent="@style/TextAppearance">
|
||||||
<item name="android:layout_width">match_parent</item>
|
<item name="android:layout_width">fill_parent</item>
|
||||||
<item name="android:layout_height">match_parent</item>
|
<item name="android:layout_height">fill_parent</item>
|
||||||
<item name="android:layout_gravity">center</item>
|
<item name="android:layout_gravity">center</item>
|
||||||
<item name="android:padding">@dimen/SyncSpace</item>
|
<item name="android:padding">@dimen/SyncSpace</item>
|
||||||
<item name="android:orientation">vertical</item>
|
<item name="android:orientation">vertical</item>
|
||||||
</style>
|
</style>
|
||||||
<style name="SyncTextItem" parent="@style/TextAppearance.Medium">
|
<style name="SyncTextItem" parent="@style/TextAppearance.Medium">
|
||||||
<item name="android:layout_width">match_parent</item>
|
<item name="android:layout_width">fill_parent</item>
|
||||||
<item name="android:layout_height">wrap_content</item>
|
<item name="android:layout_height">wrap_content</item>
|
||||||
<item name="android:textSize">15dp</item>
|
<item name="android:textSize">15dp</item>
|
||||||
</style>
|
</style>
|
||||||
|
@ -44,7 +44,7 @@
|
||||||
</style>
|
</style>
|
||||||
<!-- EditView Styles -->
|
<!-- EditView Styles -->
|
||||||
<style name="SyncEditItem" parent="@style/Widget.EditText">
|
<style name="SyncEditItem" parent="@style/Widget.EditText">
|
||||||
<item name="android:layout_width">match_parent</item>
|
<item name="android:layout_width">fill_parent</item>
|
||||||
<item name="android:layout_height">wrap_content</item>
|
<item name="android:layout_height">wrap_content</item>
|
||||||
<item name="android:singleLine">true</item>
|
<item name="android:singleLine">true</item>
|
||||||
<item name="android:inputType">text|textNoSuggestions</item>
|
<item name="android:inputType">text|textNoSuggestions</item>
|
||||||
|
@ -67,7 +67,7 @@
|
||||||
<!-- Top title bar: a text view with the Sync icon to the left. -->
|
<!-- Top title bar: a text view with the Sync icon to the left. -->
|
||||||
<style name="SyncTop">
|
<style name="SyncTop">
|
||||||
<item name="android:textAppearance">@style/TextAppearance.Large</item>
|
<item name="android:textAppearance">@style/TextAppearance.Large</item>
|
||||||
<item name="android:layout_width">match_parent</item>
|
<item name="android:layout_width">fill_parent</item>
|
||||||
<item name="android:layout_height">wrap_content</item>
|
<item name="android:layout_height">wrap_content</item>
|
||||||
<item name="android:gravity">center_vertical|left</item>
|
<item name="android:gravity">center_vertical|left</item>
|
||||||
<item name="android:drawableLeft">@drawable/icon</item>
|
<item name="android:drawableLeft">@drawable/icon</item>
|
||||||
|
@ -80,7 +80,7 @@
|
||||||
|
|
||||||
<!-- Middle scroller: a scroll view with content in it. -->
|
<!-- Middle scroller: a scroll view with content in it. -->
|
||||||
<style name="SyncMiddle">
|
<style name="SyncMiddle">
|
||||||
<item name="android:layout_width">match_parent</item>
|
<item name="android:layout_width">fill_parent</item>
|
||||||
<item name="android:layout_height">0dp</item>
|
<item name="android:layout_height">0dp</item>
|
||||||
<item name="android:layout_weight">1</item>
|
<item name="android:layout_weight">1</item>
|
||||||
<item name="android:padding">@dimen/SyncSpace</item>
|
<item name="android:padding">@dimen/SyncSpace</item>
|
||||||
|
@ -88,7 +88,7 @@
|
||||||
|
|
||||||
<!-- Bottom bar: a horizontal linear layout with buttons in it. -->
|
<!-- Bottom bar: a horizontal linear layout with buttons in it. -->
|
||||||
<style name="SyncBottom">
|
<style name="SyncBottom">
|
||||||
<item name="android:layout_width">match_parent</item>
|
<item name="android:layout_width">fill_parent</item>
|
||||||
<item name="android:layout_height">wrap_content</item>
|
<item name="android:layout_height">wrap_content</item>
|
||||||
<item name="android:layout_gravity">center</item>
|
<item name="android:layout_gravity">center</item>
|
||||||
<item name="android:gravity">center</item>
|
<item name="android:gravity">center</item>
|
||||||
|
@ -104,7 +104,7 @@
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<style name="SyncButton" parent="@style/Widget.Button">
|
<style name="SyncButton" parent="@style/Widget.Button">
|
||||||
<item name="android:layout_width">match_parent</item>
|
<item name="android:layout_width">fill_parent</item>
|
||||||
<item name="android:layout_height">wrap_content</item>
|
<item name="android:layout_height">wrap_content</item>
|
||||||
<item name="android:layout_weight">1</item>
|
<item name="android:layout_weight">1</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -10,6 +10,11 @@
|
||||||
android:key="email"
|
android:key="email"
|
||||||
android:persistent="false"
|
android:persistent="false"
|
||||||
android:title="@string/fxaccount_email_hint" />
|
android:title="@string/fxaccount_email_hint" />
|
||||||
|
<Preference
|
||||||
|
android:editable="false"
|
||||||
|
android:key="auth_server"
|
||||||
|
android:persistent="false"
|
||||||
|
android:title="@string/fxaccount_status_auth_server" />
|
||||||
</PreferenceCategory>
|
</PreferenceCategory>
|
||||||
<PreferenceCategory
|
<PreferenceCategory
|
||||||
android:key="sync_category"
|
android:key="sync_category"
|
||||||
|
@ -72,6 +77,13 @@
|
||||||
android:key="device_name"
|
android:key="device_name"
|
||||||
android:persistent="false"
|
android:persistent="false"
|
||||||
android:title="@string/fxaccount_status_device_name" />
|
android:title="@string/fxaccount_status_device_name" />
|
||||||
|
|
||||||
|
<Preference
|
||||||
|
android:editable="false"
|
||||||
|
android:key="sync_server"
|
||||||
|
android:persistent="false"
|
||||||
|
android:title="@string/fxaccount_status_sync_server" />
|
||||||
|
|
||||||
</PreferenceCategory>
|
</PreferenceCategory>
|
||||||
<PreferenceCategory
|
<PreferenceCategory
|
||||||
android:key="legal_category"
|
android:key="legal_category"
|
||||||
|
|
|
@ -36,6 +36,7 @@
|
||||||
<string name="bookmarks_title">&bookmarks_title;</string>
|
<string name="bookmarks_title">&bookmarks_title;</string>
|
||||||
<string name="history_title">&history_title;</string>
|
<string name="history_title">&history_title;</string>
|
||||||
<string name="reading_list_title">&reading_list_title;</string>
|
<string name="reading_list_title">&reading_list_title;</string>
|
||||||
|
<string name="recent_tabs_title">&recent_tabs_title;</string>
|
||||||
|
|
||||||
<string name="switch_to_tab">&switch_to_tab;</string>
|
<string name="switch_to_tab">&switch_to_tab;</string>
|
||||||
|
|
||||||
|
@ -313,10 +314,10 @@
|
||||||
<string name="home_clear_history_button">&home_clear_history_button;</string>
|
<string name="home_clear_history_button">&home_clear_history_button;</string>
|
||||||
<string name="home_clear_history_confirm">&home_clear_history_confirm;</string>
|
<string name="home_clear_history_confirm">&home_clear_history_confirm;</string>
|
||||||
<string name="home_bookmarks_empty">&home_bookmarks_empty;</string>
|
<string name="home_bookmarks_empty">&home_bookmarks_empty;</string>
|
||||||
|
<string name="home_closed_tabs_title">&home_closed_tabs_title;</string>
|
||||||
<string name="home_last_tabs_title">&home_last_tabs_title;</string>
|
<string name="home_last_tabs_title">&home_last_tabs_title;</string>
|
||||||
<string name="home_last_tabs_open">&home_last_tabs_open;</string>
|
<string name="home_last_tabs_open">&home_last_tabs_open;</string>
|
||||||
<string name="home_last_tabs_empty">&home_last_tabs_empty;</string>
|
<string name="home_last_tabs_empty">&home_last_tabs_empty;</string>
|
||||||
<string name="home_most_recent_title">&home_most_recent_title;</string>
|
|
||||||
<string name="home_most_recent_empty">&home_most_recent_empty;</string>
|
<string name="home_most_recent_empty">&home_most_recent_empty;</string>
|
||||||
<string name="home_reading_list_empty">&home_reading_list_empty;</string>
|
<string name="home_reading_list_empty">&home_reading_list_empty;</string>
|
||||||
<string name="home_reading_list_hint">&home_reading_list_hint2;</string>
|
<string name="home_reading_list_hint">&home_reading_list_hint2;</string>
|
||||||
|
|
|
@ -472,7 +472,7 @@ public class SyncConfiguration {
|
||||||
} else {
|
} else {
|
||||||
edit.putString(PREF_ENABLED_ENGINE_NAMES, setToJSONObjectString(enabledEngineNames));
|
edit.putString(PREF_ENABLED_ENGINE_NAMES, setToJSONObjectString(enabledEngineNames));
|
||||||
}
|
}
|
||||||
if (declinedEngineNames.isEmpty()) {
|
if (declinedEngineNames == null || declinedEngineNames.isEmpty()) {
|
||||||
edit.remove(PREF_DECLINED_ENGINE_NAMES);
|
edit.remove(PREF_DECLINED_ENGINE_NAMES);
|
||||||
} else {
|
} else {
|
||||||
edit.putString(PREF_DECLINED_ENGINE_NAMES, setToJSONObjectString(declinedEngineNames));
|
edit.putString(PREF_DECLINED_ENGINE_NAMES, setToJSONObjectString(declinedEngineNames));
|
||||||
|
|
|
@ -26,7 +26,14 @@ import com.jayway.android.robotium.solo.Condition;
|
||||||
* To use any of these methods in your test make sure it extends AboutHomeTest instead of BaseTest
|
* To use any of these methods in your test make sure it extends AboutHomeTest instead of BaseTest
|
||||||
*/
|
*/
|
||||||
abstract class AboutHomeTest extends PixelTest {
|
abstract class AboutHomeTest extends PixelTest {
|
||||||
protected enum AboutHomeTabs {HISTORY, MOST_RECENT, TABS_FROM_LAST_TIME, TOP_SITES, BOOKMARKS, READING_LIST};
|
protected enum AboutHomeTabs {
|
||||||
|
RECENT_TABS,
|
||||||
|
HISTORY,
|
||||||
|
TOP_SITES,
|
||||||
|
BOOKMARKS,
|
||||||
|
READING_LIST
|
||||||
|
};
|
||||||
|
|
||||||
private ArrayList<String> aboutHomeTabs = new ArrayList<String>() {{
|
private ArrayList<String> aboutHomeTabs = new ArrayList<String>() {{
|
||||||
add("TOP_SITES");
|
add("TOP_SITES");
|
||||||
add("BOOKMARKS");
|
add("BOOKMARKS");
|
||||||
|
@ -42,8 +49,10 @@ abstract class AboutHomeTest extends PixelTest {
|
||||||
// Update it for tablets vs. phones.
|
// Update it for tablets vs. phones.
|
||||||
if (mDevice.type.equals("phone")) {
|
if (mDevice.type.equals("phone")) {
|
||||||
aboutHomeTabs.add(0, AboutHomeTabs.HISTORY.toString());
|
aboutHomeTabs.add(0, AboutHomeTabs.HISTORY.toString());
|
||||||
|
aboutHomeTabs.add(0, AboutHomeTabs.RECENT_TABS.toString());
|
||||||
} else {
|
} else {
|
||||||
aboutHomeTabs.add(AboutHomeTabs.HISTORY.toString());
|
aboutHomeTabs.add(AboutHomeTabs.HISTORY.toString());
|
||||||
|
aboutHomeTabs.add(AboutHomeTabs.RECENT_TABS.toString());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -223,7 +232,7 @@ abstract class AboutHomeTest extends PixelTest {
|
||||||
/**
|
/**
|
||||||
* This method can be used to open the different tabs of about:home.
|
* This method can be used to open the different tabs of about:home.
|
||||||
*
|
*
|
||||||
* @param AboutHomeTabs enum item {MOST_RECENT, TABS_FROM_LAST_TIME, TOP_SITES, BOOKMARKS, READING_LIST}
|
* @param AboutHomeTabs enum item
|
||||||
*/
|
*/
|
||||||
protected void openAboutHomeTab(AboutHomeTabs tab) {
|
protected void openAboutHomeTab(AboutHomeTabs tab) {
|
||||||
focusUrlBar();
|
focusUrlBar();
|
||||||
|
@ -233,71 +242,13 @@ abstract class AboutHomeTest extends PixelTest {
|
||||||
|
|
||||||
// Handle tablets by just clicking the visible tab title.
|
// Handle tablets by just clicking the visible tab title.
|
||||||
if (mDevice.type.equals("tablet")) {
|
if (mDevice.type.equals("tablet")) {
|
||||||
if (AboutHomeTabs.MOST_RECENT == tab || AboutHomeTabs.TABS_FROM_LAST_TIME == tab) {
|
|
||||||
tabOffset = aboutHomeTabs.indexOf(AboutHomeTabs.HISTORY.toString()) - currentTabIndex;
|
|
||||||
swipeAboutHome(tabOffset);
|
|
||||||
waitForAboutHomeTab(aboutHomeTabs.indexOf(StringHelper.HISTORY_LABEL));
|
|
||||||
TabWidget tabwidget = (TabWidget)mSolo.getView(TabWidget.class, 0);
|
|
||||||
|
|
||||||
switch (tab) {
|
|
||||||
case MOST_RECENT: {
|
|
||||||
mSolo.clickOnView(tabwidget.getChildAt(0));
|
|
||||||
// We can determine if we are on the MOST_RECENT tab only if pages were first visited during the test
|
|
||||||
mAsserter.ok(waitForText(StringHelper.TODAY_LABEL), "Checking that we are in the most recent tab of about:home", "We are in the most recent tab");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case TABS_FROM_LAST_TIME: {
|
|
||||||
mSolo.clickOnView(tabwidget.getChildAt(1));
|
|
||||||
mAsserter.ok(waitForText(StringHelper.TABS_FROM_LAST_TIME_LABEL), "Checking that we are in the Tabs from last time tab of about:home", "We are in the Tabs from last time tab");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
clickAboutHomeTab(tab);
|
clickAboutHomeTab(tab);
|
||||||
}
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle phones (non-tablets).
|
// Handle phones (non-tablets).
|
||||||
tabOffset = aboutHomeTabs.indexOf(tab.toString()) - currentTabIndex;
|
tabOffset = aboutHomeTabs.indexOf(tab.toString()) - currentTabIndex;
|
||||||
switch (tab) {
|
|
||||||
case TOP_SITES : {
|
|
||||||
swipeAboutHome(tabOffset);
|
swipeAboutHome(tabOffset);
|
||||||
waitForAboutHomeTab(aboutHomeTabs.indexOf(tab.toString()));
|
waitForAboutHomeTab(aboutHomeTabs.indexOf(tab.toString()));
|
||||||
break;
|
|
||||||
}
|
|
||||||
case BOOKMARKS : {
|
|
||||||
swipeAboutHome(tabOffset);
|
|
||||||
waitForAboutHomeTab(aboutHomeTabs.indexOf(tab.toString()));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case MOST_RECENT: {
|
|
||||||
// MOST_RECENT is contained in the HISTORY tab.
|
|
||||||
tabOffset = aboutHomeTabs.indexOf(AboutHomeTabs.HISTORY.toString()) - currentTabIndex;
|
|
||||||
swipeAboutHome(tabOffset);
|
|
||||||
waitForAboutHomeTab(aboutHomeTabs.indexOf(StringHelper.HISTORY_LABEL));
|
|
||||||
TabWidget tabwidget = (TabWidget)mSolo.getView(TabWidget.class, 0);
|
|
||||||
mSolo.clickOnView(tabwidget.getChildAt(0));
|
|
||||||
// We can determine if we are on the MOST_RECENT tab only if pages were first visited during the test
|
|
||||||
mAsserter.ok(waitForText(StringHelper.TODAY_LABEL), "Checking that we are in the most recent tab of about:home", "We are in the most recent tab");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case TABS_FROM_LAST_TIME: {
|
|
||||||
// TABS_FROM_LAST_TIME is contained in the HISTORY tab.
|
|
||||||
tabOffset = aboutHomeTabs.indexOf(AboutHomeTabs.HISTORY.toString()) - currentTabIndex;
|
|
||||||
swipeAboutHome(tabOffset);
|
|
||||||
waitForAboutHomeTab(aboutHomeTabs.indexOf(StringHelper.HISTORY_LABEL));
|
|
||||||
TabWidget tabwidget = (TabWidget)mSolo.getView(TabWidget.class, 0);
|
|
||||||
mSolo.clickOnView(tabwidget.getChildAt(1));
|
|
||||||
mAsserter.ok(waitForText(StringHelper.TABS_FROM_LAST_TIME_LABEL), "Checking that we are in the Tabs from last time tab of about:home", "We are in the Tabs from last time tab");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case READING_LIST: {
|
|
||||||
swipeAboutHome(tabOffset);
|
|
||||||
waitForAboutHomeTab(aboutHomeTabs.indexOf(tab.toString()));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,13 +30,15 @@ public class AboutHomeComponent extends BaseComponent {
|
||||||
HISTORY,
|
HISTORY,
|
||||||
TOP_SITES,
|
TOP_SITES,
|
||||||
BOOKMARKS,
|
BOOKMARKS,
|
||||||
READING_LIST
|
READING_LIST,
|
||||||
|
RECENT_TABS
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Having a specific ordering of panels is prone to fail and thus temporary.
|
// TODO: Having a specific ordering of panels is prone to fail and thus temporary.
|
||||||
// Hopefully the work in bug 940565 will alleviate the need for these enums.
|
// Hopefully the work in bug 940565 will alleviate the need for these enums.
|
||||||
// Explicit ordering of HomePager panels on a phone.
|
// Explicit ordering of HomePager panels on a phone.
|
||||||
private enum PhonePanel {
|
private enum PhonePanel {
|
||||||
|
RECENT_TABS,
|
||||||
HISTORY,
|
HISTORY,
|
||||||
TOP_SITES,
|
TOP_SITES,
|
||||||
BOOKMARKS,
|
BOOKMARKS,
|
||||||
|
@ -48,7 +50,8 @@ public class AboutHomeComponent extends BaseComponent {
|
||||||
TOP_SITES,
|
TOP_SITES,
|
||||||
BOOKMARKS,
|
BOOKMARKS,
|
||||||
READING_LIST,
|
READING_LIST,
|
||||||
HISTORY
|
HISTORY,
|
||||||
|
RECENT_TABS
|
||||||
}
|
}
|
||||||
|
|
||||||
// The percentage of the panel to swipe between 0 and 1. This value was set through
|
// The percentage of the panel to swipe between 0 and 1. This value was set through
|
||||||
|
|
|
@ -6,6 +6,8 @@ import org.mozilla.gecko.tests.helpers.GeckoHelper;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tests functionality related to navigating between the various about:home panels.
|
* Tests functionality related to navigating between the various about:home panels.
|
||||||
|
*
|
||||||
|
* TODO: Update this test to account for recent tabs panel (bug 1028727).
|
||||||
*/
|
*/
|
||||||
public class testAboutHomePageNavigation extends UITest {
|
public class testAboutHomePageNavigation extends UITest {
|
||||||
// TODO: Define this test dynamically by creating dynamic representations of the Page
|
// TODO: Define this test dynamically by creating dynamic representations of the Page
|
||||||
|
|
|
@ -21,9 +21,9 @@ public class testHistory extends AboutHomeTest {
|
||||||
inputAndLoadUrl(url3);
|
inputAndLoadUrl(url3);
|
||||||
verifyPageTitle("Browser Blank Page 03");
|
verifyPageTitle("Browser Blank Page 03");
|
||||||
|
|
||||||
openAboutHomeTab(AboutHomeTabs.MOST_RECENT);
|
openAboutHomeTab(AboutHomeTabs.HISTORY);
|
||||||
|
|
||||||
final ListView hList = findListViewWithTag("most_recent");
|
final ListView hList = findListViewWithTag("history");
|
||||||
mAsserter.is(waitForNonEmptyListToLoad(hList), true, "list is properly loaded");
|
mAsserter.is(waitForNonEmptyListToLoad(hList), true, "list is properly loaded");
|
||||||
|
|
||||||
// Click on the history item and wait for the page to load
|
// Click on the history item and wait for the page to load
|
||||||
|
|
|
@ -106,8 +106,8 @@ public class testReaderMode extends AboutHomeTest {
|
||||||
mAsserter.ok(mSolo.waitForText("Robocop Text Page"), "Verify if the page is added to your Reading List", "The page is present in your Reading List");
|
mAsserter.ok(mSolo.waitForText("Robocop Text Page"), "Verify if the page is added to your Reading List", "The page is present in your Reading List");
|
||||||
|
|
||||||
// Check if the page is added in History tab like a Reading List item
|
// Check if the page is added in History tab like a Reading List item
|
||||||
openAboutHomeTab(AboutHomeTabs.MOST_RECENT);
|
openAboutHomeTab(AboutHomeTabs.HISTORY);
|
||||||
list = findListViewWithTag("most_recent");
|
list = findListViewWithTag("history");
|
||||||
child = list.getChildAt(1);
|
child = list.getChildAt(1);
|
||||||
mAsserter.ok(child != null, "item can be retrieved", child != null ? child.toString() : "null!");
|
mAsserter.ok(child != null, "item can be retrieved", child != null ? child.toString() : "null!");
|
||||||
mSolo.clickLongOnView(child);
|
mSolo.clickLongOnView(child);
|
||||||
|
|
|
@ -119,10 +119,10 @@ public class testShareLink extends AboutHomeTest {
|
||||||
mSolo.clickLongOnView(mostVisitedItem);
|
mSolo.clickLongOnView(mostVisitedItem);
|
||||||
verifySharePopup(shareOptions,"top_sites");
|
verifySharePopup(shareOptions,"top_sites");
|
||||||
|
|
||||||
// Test the share popup in the Most Recent tab
|
// Test the share popup in the history tab
|
||||||
openAboutHomeTab(AboutHomeTabs.MOST_RECENT);
|
openAboutHomeTab(AboutHomeTabs.HISTORY);
|
||||||
|
|
||||||
ListView mostRecentList = findListViewWithTag("most_recent");
|
ListView mostRecentList = findListViewWithTag("history");
|
||||||
mAsserter.is(waitForNonEmptyListToLoad(mostRecentList), true, "list is properly loaded");
|
mAsserter.is(waitForNonEmptyListToLoad(mostRecentList), true, "list is properly loaded");
|
||||||
|
|
||||||
// Getting second child after header views because the first is the "Today" label
|
// Getting second child after header views because the first is the "Today" label
|
||||||
|
|
|
@ -43,13 +43,16 @@ SessionStore.prototype = {
|
||||||
_windows: {},
|
_windows: {},
|
||||||
_lastSaveTime: 0,
|
_lastSaveTime: 0,
|
||||||
_interval: 10000,
|
_interval: 10000,
|
||||||
_maxTabsUndo: 1,
|
_maxTabsUndo: 5,
|
||||||
_pendingWrite: 0,
|
_pendingWrite: 0,
|
||||||
|
|
||||||
// The index where the most recently closed tab was in the tabs array
|
// The index where the most recently closed tab was in the tabs array
|
||||||
// when it was closed.
|
// when it was closed.
|
||||||
_lastClosedTabIndex: -1,
|
_lastClosedTabIndex: -1,
|
||||||
|
|
||||||
|
// Whether or not to send notifications for changes to the closed tabs.
|
||||||
|
_notifyClosedTabs: false,
|
||||||
|
|
||||||
init: function ss_init() {
|
init: function ss_init() {
|
||||||
// Get file references
|
// Get file references
|
||||||
this._sessionFile = Services.dirsvc.get("ProfD", Ci.nsILocalFile);
|
this._sessionFile = Services.dirsvc.get("ProfD", Ci.nsILocalFile);
|
||||||
|
@ -79,6 +82,8 @@ SessionStore.prototype = {
|
||||||
observerService.addObserver(this, "browser:purge-session-history", true);
|
observerService.addObserver(this, "browser:purge-session-history", true);
|
||||||
observerService.addObserver(this, "Session:Restore", true);
|
observerService.addObserver(this, "Session:Restore", true);
|
||||||
observerService.addObserver(this, "application-background", true);
|
observerService.addObserver(this, "application-background", true);
|
||||||
|
observerService.addObserver(this, "ClosedTabs:StartNotifications", true);
|
||||||
|
observerService.addObserver(this, "ClosedTabs:StopNotifications", true);
|
||||||
break;
|
break;
|
||||||
case "final-ui-startup":
|
case "final-ui-startup":
|
||||||
observerService.removeObserver(this, "final-ui-startup");
|
observerService.removeObserver(this, "final-ui-startup");
|
||||||
|
@ -157,6 +162,13 @@ SessionStore.prototype = {
|
||||||
// pending save state to ensure that this data does not get lost.
|
// pending save state to ensure that this data does not get lost.
|
||||||
this.flushPendingState();
|
this.flushPendingState();
|
||||||
break;
|
break;
|
||||||
|
case "ClosedTabs:StartNotifications":
|
||||||
|
this._notifyClosedTabs = true;
|
||||||
|
this._sendClosedTabsToJava(Services.wm.getMostRecentWindow("navigator:browser"));
|
||||||
|
break;
|
||||||
|
case "ClosedTabs:StopNotifications":
|
||||||
|
this._notifyClosedTabs = false;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -291,6 +303,10 @@ SessionStore.prototype = {
|
||||||
this._windows[aWindow.__SSID].closedTabs.splice(this._maxTabsUndo, length - this._maxTabsUndo);
|
this._windows[aWindow.__SSID].closedTabs.splice(this._maxTabsUndo, length - this._maxTabsUndo);
|
||||||
|
|
||||||
this._lastClosedTabIndex = aTabIndex;
|
this._lastClosedTabIndex = aTabIndex;
|
||||||
|
|
||||||
|
if (this._notifyClosedTabs) {
|
||||||
|
this._sendClosedTabsToJava(aWindow);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -864,6 +880,10 @@ SessionStore.prototype = {
|
||||||
// Put back the extra data
|
// Put back the extra data
|
||||||
tab.browser.__SS_extdata = closedTab.extData;
|
tab.browser.__SS_extdata = closedTab.extData;
|
||||||
|
|
||||||
|
if (this._notifyClosedTabs) {
|
||||||
|
this._sendClosedTabsToJava(aWindow);
|
||||||
|
}
|
||||||
|
|
||||||
return tab.browser;
|
return tab.browser;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -885,6 +905,30 @@ SessionStore.prototype = {
|
||||||
if (aIndex == 0) {
|
if (aIndex == 0) {
|
||||||
this._lastClosedTabIndex = -1;
|
this._lastClosedTabIndex = -1;
|
||||||
}
|
}
|
||||||
|
if (this._notifyClosedTabs) {
|
||||||
|
this._sendClosedTabsToJava(aWindow);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
_sendClosedTabsToJava: function ss_sendClosedTabsToJava(aWindow) {
|
||||||
|
if (!aWindow.__SSID)
|
||||||
|
throw (Components.returnCode = Cr.NS_ERROR_INVALID_ARG);
|
||||||
|
|
||||||
|
let closedTabs = this._windows[aWindow.__SSID].closedTabs;
|
||||||
|
|
||||||
|
let tabs = closedTabs.map(function (tab) {
|
||||||
|
// Get the url and title for the last entry in the session history.
|
||||||
|
let lastEntry = tab.entries[tab.entries.length - 1];
|
||||||
|
return {
|
||||||
|
url: lastEntry.url,
|
||||||
|
title: lastEntry.title || ""
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
sendMessageToJava({
|
||||||
|
type: "ClosedTabs:Data",
|
||||||
|
tabs: tabs
|
||||||
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
getTabValue: function ss_getTabValue(aTab, aKey) {
|
getTabValue: function ss_getTabValue(aTab, aKey) {
|
||||||
|
|
|
@ -65,12 +65,16 @@ let Accounts = Object.freeze({
|
||||||
* Fire-and-forget: open the Firefox accounts activity, which
|
* Fire-and-forget: open the Firefox accounts activity, which
|
||||||
* will be the Getting Started screen if FxA isn't yet set up.
|
* will be the Getting Started screen if FxA isn't yet set up.
|
||||||
*
|
*
|
||||||
|
* Optional extras are passed, as a JSON string, to the Firefox
|
||||||
|
* Account Getting Started activity in the extras bundle of the
|
||||||
|
* activity launch intent, under the key "extras".
|
||||||
|
*
|
||||||
* There is no return value from this method.
|
* There is no return value from this method.
|
||||||
*/
|
*/
|
||||||
launchSetup: function () {
|
launchSetup: function (extras) {
|
||||||
sendMessageToJava({
|
sendMessageToJava({
|
||||||
type: "Accounts:Create",
|
type: "Accounts:Create",
|
||||||
|
extras: extras,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -117,6 +117,9 @@
|
||||||
contentDescription text, and it should not be translated. -->
|
contentDescription text, and it should not be translated. -->
|
||||||
<string name="fxaccount_empty_contentDescription"></string>
|
<string name="fxaccount_empty_contentDescription"></string>
|
||||||
|
|
||||||
|
<string name="fxaccount_custom_server_account_title">&fxaccount_custom_server_account_title;</string>
|
||||||
|
<string name="fxaccount_custom_server_sync_title">&fxaccount_custom_server_sync_title;</string>
|
||||||
|
|
||||||
<string name="fxaccount_email_hint">&fxaccount_email_hint;</string>
|
<string name="fxaccount_email_hint">&fxaccount_email_hint;</string>
|
||||||
<string name="fxaccount_password_hint">&fxaccount_password_hint;</string>
|
<string name="fxaccount_password_hint">&fxaccount_password_hint;</string>
|
||||||
<string name="fxaccount_password_hide">&fxaccount_password_hide;</string>
|
<string name="fxaccount_password_hide">&fxaccount_password_hide;</string>
|
||||||
|
@ -169,7 +172,9 @@
|
||||||
<string name="fxaccount_status_activity_label">&syncBrand.shortName.label;</string>
|
<string name="fxaccount_status_activity_label">&syncBrand.shortName.label;</string>
|
||||||
<string name="fxaccount_status_header">&fxaccount_status_header2;</string>
|
<string name="fxaccount_status_header">&fxaccount_status_header2;</string>
|
||||||
<string name="fxaccount_status_signed_in_as">&fxaccount_status_signed_in_as;</string>
|
<string name="fxaccount_status_signed_in_as">&fxaccount_status_signed_in_as;</string>
|
||||||
|
<string name="fxaccount_status_auth_server">&fxaccount_status_auth_server;</string>
|
||||||
<string name="fxaccount_status_device_name">&fxaccount_status_device_name;</string>
|
<string name="fxaccount_status_device_name">&fxaccount_status_device_name;</string>
|
||||||
|
<string name="fxaccount_status_sync_server">&fxaccount_status_sync_server;</string>
|
||||||
<string name="fxaccount_status_sync">&fxaccount_status_sync;</string>
|
<string name="fxaccount_status_sync">&fxaccount_status_sync;</string>
|
||||||
<string name="fxaccount_status_sync_enabled">&fxaccount_status_sync_enabled;</string>
|
<string name="fxaccount_status_sync_enabled">&fxaccount_status_sync_enabled;</string>
|
||||||
<string name="fxaccount_status_needs_verification">&fxaccount_status_needs_verification2;</string>
|
<string name="fxaccount_status_needs_verification">&fxaccount_status_needs_verification2;</string>
|
||||||
|
|
|
@ -47,6 +47,7 @@ BACKGROUND_TESTS_JAVA_FILES := \
|
||||||
src/helpers/DBProviderTestCase.java \
|
src/helpers/DBProviderTestCase.java \
|
||||||
src/helpers/FakeProfileTestCase.java \
|
src/helpers/FakeProfileTestCase.java \
|
||||||
src/nativecode/test/TestNativeCrypto.java \
|
src/nativecode/test/TestNativeCrypto.java \
|
||||||
|
src/sync/AndroidSyncTestCaseWithAccounts.java \
|
||||||
src/sync/helpers/BookmarkHelpers.java \
|
src/sync/helpers/BookmarkHelpers.java \
|
||||||
src/sync/helpers/DefaultBeginDelegate.java \
|
src/sync/helpers/DefaultBeginDelegate.java \
|
||||||
src/sync/helpers/DefaultCleanDelegate.java \
|
src/sync/helpers/DefaultCleanDelegate.java \
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="fill_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="fill_parent"
|
||||||
android:orientation="vertical" >
|
android:orientation="vertical" >
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:layout_width="match_parent"
|
android:layout_width="fill_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:text="@string/app_name" />
|
android:text="@string/app_name" />
|
||||||
|
|
||||||
|
|
|
@ -3,30 +3,42 @@
|
||||||
|
|
||||||
package org.mozilla.gecko.background.fxa.authenticator;
|
package org.mozilla.gecko.background.fxa.authenticator;
|
||||||
|
|
||||||
import org.mozilla.gecko.background.helpers.AndroidSyncTestCase;
|
import org.mozilla.gecko.background.sync.AndroidSyncTestCaseWithAccounts;
|
||||||
import org.mozilla.gecko.background.sync.TestSyncAccounts;
|
|
||||||
import org.mozilla.gecko.fxa.FxAccountConstants;
|
import org.mozilla.gecko.fxa.FxAccountConstants;
|
||||||
import org.mozilla.gecko.fxa.authenticator.AccountPickler;
|
import org.mozilla.gecko.fxa.authenticator.AccountPickler;
|
||||||
import org.mozilla.gecko.fxa.authenticator.AndroidFxAccount;
|
import org.mozilla.gecko.fxa.authenticator.AndroidFxAccount;
|
||||||
import org.mozilla.gecko.fxa.login.Separated;
|
import org.mozilla.gecko.fxa.login.Separated;
|
||||||
import org.mozilla.gecko.fxa.login.State;
|
import org.mozilla.gecko.fxa.login.State;
|
||||||
|
import org.mozilla.gecko.sync.ExtendedJSONObject;
|
||||||
import org.mozilla.gecko.sync.Utils;
|
import org.mozilla.gecko.sync.Utils;
|
||||||
|
|
||||||
import android.accounts.Account;
|
import android.accounts.Account;
|
||||||
import android.accounts.AccountManager;
|
import android.accounts.AccountManager;
|
||||||
import android.test.InstrumentationTestCase;
|
|
||||||
import android.test.RenamingDelegatingContext;
|
import android.test.RenamingDelegatingContext;
|
||||||
|
|
||||||
public class TestAccountPickler extends AndroidSyncTestCase {
|
public class TestAccountPickler extends AndroidSyncTestCaseWithAccounts {
|
||||||
|
private static final String TEST_TOKEN_SERVER_URI = "tokenServerURI";
|
||||||
|
private static final String TEST_AUTH_SERVER_URI = "serverURI";
|
||||||
|
private static final String TEST_PROFILE = "profile";
|
||||||
private final static String FILENAME_PREFIX = "TestAccountPickler-";
|
private final static String FILENAME_PREFIX = "TestAccountPickler-";
|
||||||
private final static String PICKLE_FILENAME = "pickle";
|
private final static String PICKLE_FILENAME = "pickle";
|
||||||
|
|
||||||
|
private final static String TEST_ACCOUNTTYPE = FxAccountConstants.ACCOUNT_TYPE;
|
||||||
|
|
||||||
|
// Test account names must start with TEST_USERNAME in order to be recognized
|
||||||
|
// as test accounts and deleted in tearDown.
|
||||||
|
public static final String TEST_USERNAME = "testFirefoxAccount@mozilla.com";
|
||||||
|
|
||||||
public Account account;
|
public Account account;
|
||||||
public RenamingDelegatingContext context;
|
public RenamingDelegatingContext context;
|
||||||
public AccountManager accountManager;
|
|
||||||
|
public TestAccountPickler() {
|
||||||
|
super(TEST_ACCOUNTTYPE, TEST_USERNAME);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setUp() {
|
public void setUp() {
|
||||||
|
super.setUp();
|
||||||
this.account = null;
|
this.account = null;
|
||||||
// Randomize the filename prefix in case we don't clean up correctly.
|
// Randomize the filename prefix in case we don't clean up correctly.
|
||||||
this.context = new RenamingDelegatingContext(getApplicationContext(), FILENAME_PREFIX +
|
this.context = new RenamingDelegatingContext(getApplicationContext(), FILENAME_PREFIX +
|
||||||
|
@ -36,54 +48,72 @@ public class TestAccountPickler extends AndroidSyncTestCase {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void tearDown() {
|
public void tearDown() {
|
||||||
if (this.account != null) {
|
super.tearDown();
|
||||||
deleteAccount(this, this.accountManager, this.account);
|
|
||||||
this.account = null;
|
|
||||||
}
|
|
||||||
this.context.deleteFile(PICKLE_FILENAME);
|
this.context.deleteFile(PICKLE_FILENAME);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void deleteAccount(final InstrumentationTestCase test,
|
public AndroidFxAccount addTestAccount() throws Exception {
|
||||||
final AccountManager accountManager, final Account account) {
|
final State state = new Separated(TEST_USERNAME, "uid", false); // State choice is arbitrary.
|
||||||
TestSyncAccounts.deleteAccount(test, accountManager, account);
|
final AndroidFxAccount account = AndroidFxAccount.addAndroidAccount(context, TEST_USERNAME,
|
||||||
}
|
TEST_PROFILE, TEST_AUTH_SERVER_URI, TEST_TOKEN_SERVER_URI, state);
|
||||||
|
|
||||||
private boolean accountsExist() {
|
|
||||||
// Note that we don't use FirefoxAccounts.firefoxAccountsExist because it unpickles.
|
|
||||||
return AccountManager.get(context).getAccountsByType(FxAccountConstants.ACCOUNT_TYPE).length > 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
public AndroidFxAccount addDummyAccount() throws Exception {
|
|
||||||
final String email = "iu@fakedomain.io";
|
|
||||||
final State state = new Separated(email, "uid", false); // State choice is arbitrary.
|
|
||||||
final AndroidFxAccount account = AndroidFxAccount.addAndroidAccount(context, email,
|
|
||||||
"profile", "serverURI", "tokenServerURI", state);
|
|
||||||
assertNotNull(account);
|
assertNotNull(account);
|
||||||
assertTrue(accountsExist()); // Sanity check.
|
assertNotNull(account.getProfile());
|
||||||
|
assertTrue(testAccountsExist()); // Sanity check.
|
||||||
this.account = account.getAndroidAccount(); // To remove in tearDown() if we throw.
|
this.account = account.getAndroidAccount(); // To remove in tearDown() if we throw.
|
||||||
return account;
|
return account;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void testPickle() throws Exception {
|
||||||
|
final AndroidFxAccount account = addTestAccount();
|
||||||
|
// Sync is enabled by default so we do a more thorough test by disabling it.
|
||||||
|
account.disableSyncing();
|
||||||
|
|
||||||
|
final long now = System.currentTimeMillis();
|
||||||
|
final ExtendedJSONObject o = AccountPickler.toJSON(account, now);
|
||||||
|
|
||||||
|
assertEquals(2, o.getLong(AccountPickler.KEY_PICKLE_VERSION).longValue());
|
||||||
|
assertTrue(o.getLong(AccountPickler.KEY_PICKLE_TIMESTAMP).longValue() < System.currentTimeMillis());
|
||||||
|
|
||||||
|
assertEquals(AndroidFxAccount.CURRENT_ACCOUNT_VERSION, o.getIntegerSafely(AccountPickler.KEY_ACCOUNT_VERSION).intValue());
|
||||||
|
assertEquals(FxAccountConstants.ACCOUNT_TYPE, o.getString(AccountPickler.KEY_ACCOUNT_TYPE));
|
||||||
|
|
||||||
|
assertEquals(TEST_USERNAME, o.getString(AccountPickler.KEY_EMAIL));
|
||||||
|
assertEquals(TEST_PROFILE, o.getString(AccountPickler.KEY_PROFILE));
|
||||||
|
assertEquals(TEST_AUTH_SERVER_URI, o.getString(AccountPickler.KEY_IDP_SERVER_URI));
|
||||||
|
assertEquals(TEST_TOKEN_SERVER_URI, o.getString(AccountPickler.KEY_TOKEN_SERVER_URI));
|
||||||
|
|
||||||
|
assertFalse(o.getBoolean(AccountPickler.KEY_IS_SYNCING_ENABLED));
|
||||||
|
assertNotNull(o.get(AccountPickler.KEY_BUNDLE));
|
||||||
|
}
|
||||||
|
|
||||||
public void testPickleAndUnpickle() throws Exception {
|
public void testPickleAndUnpickle() throws Exception {
|
||||||
final AndroidFxAccount inputAccount = addDummyAccount();
|
final AndroidFxAccount inputAccount = addTestAccount();
|
||||||
// Sync is enabled by default so we do a more thorough test by disabling it.
|
// Sync is enabled by default so we do a more thorough test by disabling it.
|
||||||
inputAccount.disableSyncing();
|
inputAccount.disableSyncing();
|
||||||
|
|
||||||
AccountPickler.pickle(inputAccount, PICKLE_FILENAME);
|
AccountPickler.pickle(inputAccount, PICKLE_FILENAME);
|
||||||
|
final ExtendedJSONObject inputJSON = AccountPickler.toJSON(inputAccount, 0);
|
||||||
|
final State inputState = inputAccount.getState();
|
||||||
|
assertNotNull(inputJSON);
|
||||||
|
assertNotNull(inputState);
|
||||||
|
|
||||||
// unpickle adds an account to the AccountManager so delete it first.
|
// unpickle adds an account to the AccountManager so delete it first.
|
||||||
deleteAccount(this, this.accountManager, inputAccount.getAndroidAccount());
|
deleteTestAccounts();
|
||||||
assertFalse(accountsExist());
|
assertFalse(testAccountsExist());
|
||||||
|
|
||||||
final AndroidFxAccount unpickledAccount =
|
final AndroidFxAccount unpickledAccount = AccountPickler.unpickle(context, PICKLE_FILENAME);
|
||||||
AccountPickler.unpickle(context, PICKLE_FILENAME);
|
|
||||||
assertNotNull(unpickledAccount);
|
assertNotNull(unpickledAccount);
|
||||||
this.account = unpickledAccount.getAndroidAccount(); // To remove in tearDown().
|
final ExtendedJSONObject unpickledJSON = AccountPickler.toJSON(unpickledAccount, 0);
|
||||||
assertAccountsEquals(inputAccount, unpickledAccount);
|
final State unpickledState = unpickledAccount.getState();
|
||||||
|
assertNotNull(unpickledJSON);
|
||||||
|
assertNotNull(unpickledState);
|
||||||
|
|
||||||
|
assertEquals(inputJSON, unpickledJSON);
|
||||||
|
assertStateEquals(inputState, unpickledState);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testDeletePickle() throws Exception {
|
public void testDeletePickle() throws Exception {
|
||||||
final AndroidFxAccount account = addDummyAccount();
|
final AndroidFxAccount account = addTestAccount();
|
||||||
AccountPickler.pickle(account, PICKLE_FILENAME);
|
AccountPickler.pickle(account, PICKLE_FILENAME);
|
||||||
|
|
||||||
final String s = Utils.readFile(context, PICKLE_FILENAME);
|
final String s = Utils.readFile(context, PICKLE_FILENAME);
|
||||||
|
@ -91,23 +121,7 @@ public class TestAccountPickler extends AndroidSyncTestCase {
|
||||||
assertTrue(s.length() > 0);
|
assertTrue(s.length() > 0);
|
||||||
|
|
||||||
AccountPickler.deletePickle(context, PICKLE_FILENAME);
|
AccountPickler.deletePickle(context, PICKLE_FILENAME);
|
||||||
org.mozilla.gecko.background.sync.TestAccountPickler.assertFileNotPresent(
|
assertFileNotPresent(context, PICKLE_FILENAME);
|
||||||
context, PICKLE_FILENAME);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void assertAccountsEquals(final AndroidFxAccount expected,
|
|
||||||
final AndroidFxAccount actual) throws Exception {
|
|
||||||
// TODO: Write and use AndroidFxAccount.equals
|
|
||||||
// TODO: protected.
|
|
||||||
//assertEquals(expected.getAccountVersion(), actual.getAccountVersion());
|
|
||||||
assertEquals(expected.getProfile(), actual.getProfile());
|
|
||||||
assertEquals(expected.getAccountServerURI(), actual.getAccountServerURI());
|
|
||||||
assertEquals(expected.getAudience(), actual.getAudience());
|
|
||||||
assertEquals(expected.getTokenServerURI(), actual.getTokenServerURI());
|
|
||||||
assertEquals(expected.getSyncPrefsPath(), actual.getSyncPrefsPath());
|
|
||||||
assertEquals(expected.isSyncing(), actual.isSyncing());
|
|
||||||
assertEquals(expected.getEmail(), actual.getEmail());
|
|
||||||
assertStateEquals(expected.getState(), actual.getState());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void assertStateEquals(final State expected, final State actual) throws Exception {
|
private void assertStateEquals(final State expected, final State actual) throws Exception {
|
||||||
|
|
|
@ -10,7 +10,6 @@ import org.mozilla.gecko.background.helpers.BackgroundServiceTestCase;
|
||||||
|
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
import android.test.mock.MockContext;
|
|
||||||
|
|
||||||
public class TestHealthReportPruneService
|
public class TestHealthReportPruneService
|
||||||
extends BackgroundServiceTestCase<TestHealthReportPruneService.MockHealthReportPruneService> {
|
extends BackgroundServiceTestCase<TestHealthReportPruneService.MockHealthReportPruneService> {
|
||||||
|
@ -37,7 +36,10 @@ public class TestHealthReportPruneService
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public PrunePolicy getPrunePolicy(final String profilePath) {
|
public PrunePolicy getPrunePolicy(final String profilePath) {
|
||||||
final PrunePolicyStorage storage = new PrunePolicyDatabaseStorage(new MockContext(), profilePath);
|
// We don't actually need any storage, so just make it null. Actually
|
||||||
|
// creating storage requires a valid context; here, we only have a
|
||||||
|
// MockContext.
|
||||||
|
final PrunePolicyStorage storage = null;
|
||||||
prunePolicy = new MockPrunePolicy(storage, getSharedPreferences());
|
prunePolicy = new MockPrunePolicy(storage, getSharedPreferences());
|
||||||
return prunePolicy;
|
return prunePolicy;
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,6 +21,12 @@ public class TestHealthReportUploadService
|
||||||
GlobalConstants.SHARED_PREFERENCES_MODE);
|
GlobalConstants.SHARED_PREFERENCES_MODE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean backgroundDataIsEnabled() {
|
||||||
|
// When testing, we always want to say we can upload.
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onHandleIntent(Intent intent) {
|
public void onHandleIntent(Intent intent) {
|
||||||
super.onHandleIntent(intent);
|
super.onHandleIntent(intent);
|
||||||
|
|
|
@ -23,7 +23,7 @@ public class AndroidSyncTestCase extends ActivityInstrumentationTestCase2<Activi
|
||||||
}
|
}
|
||||||
|
|
||||||
public Context getApplicationContext() {
|
public Context getApplicationContext() {
|
||||||
return this.getInstrumentation().getTargetContext().getApplicationContext();
|
return this.getInstrumentation().getTargetContext();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void performWait(Runnable runnable) {
|
public static void performWait(Runnable runnable) {
|
||||||
|
|
|
@ -0,0 +1,83 @@
|
||||||
|
/* Any copyright is dedicated to the Public Domain.
|
||||||
|
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||||
|
|
||||||
|
package org.mozilla.gecko.background.sync;
|
||||||
|
|
||||||
|
import java.io.FileInputStream;
|
||||||
|
import java.io.FileNotFoundException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.mozilla.gecko.background.helpers.AndroidSyncTestCase;
|
||||||
|
|
||||||
|
import android.accounts.Account;
|
||||||
|
import android.accounts.AccountManager;
|
||||||
|
import android.content.Context;
|
||||||
|
|
||||||
|
public class AndroidSyncTestCaseWithAccounts extends AndroidSyncTestCase {
|
||||||
|
public final String testAccountType;
|
||||||
|
public final String testAccountPrefix;
|
||||||
|
|
||||||
|
protected Context context;
|
||||||
|
protected AccountManager accountManager;
|
||||||
|
protected int numAccounts;
|
||||||
|
|
||||||
|
public AndroidSyncTestCaseWithAccounts(String accountType, String accountPrefix) {
|
||||||
|
super();
|
||||||
|
this.testAccountType = accountType;
|
||||||
|
this.testAccountPrefix = accountPrefix;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setUp() {
|
||||||
|
context = getApplicationContext();
|
||||||
|
accountManager = AccountManager.get(context);
|
||||||
|
deleteTestAccounts(); // Always start with no test accounts.
|
||||||
|
numAccounts = accountManager.getAccountsByType(testAccountType).length;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Account> getTestAccounts() {
|
||||||
|
final List<Account> testAccounts = new ArrayList<Account>();
|
||||||
|
|
||||||
|
final Account[] accounts = accountManager.getAccountsByType(testAccountType);
|
||||||
|
for (Account account : accounts) {
|
||||||
|
if (account.name.startsWith(testAccountPrefix)) {
|
||||||
|
testAccounts.add(account);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return testAccounts;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void deleteTestAccounts() {
|
||||||
|
for (Account account : getTestAccounts()) {
|
||||||
|
TestSyncAccounts.deleteAccount(this, accountManager, account);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean testAccountsExist() {
|
||||||
|
// Note that we don't use FirefoxAccounts.firefoxAccountsExist because it unpickles.
|
||||||
|
return !getTestAccounts().isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void tearDown() {
|
||||||
|
deleteTestAccounts();
|
||||||
|
assertEquals(numAccounts, accountManager.getAccountsByType(testAccountType).length);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void assertFileNotPresent(final Context context, final String filename) throws Exception {
|
||||||
|
// Verify file is not present.
|
||||||
|
FileInputStream fis = null;
|
||||||
|
try {
|
||||||
|
fis = context.openFileInput(filename);
|
||||||
|
fail("Should get FileNotFoundException.");
|
||||||
|
} catch (FileNotFoundException e) {
|
||||||
|
// Do nothing; file should not exist.
|
||||||
|
} finally {
|
||||||
|
if (fis != null) {
|
||||||
|
fis.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -3,15 +3,10 @@
|
||||||
|
|
||||||
package org.mozilla.gecko.background.sync;
|
package org.mozilla.gecko.background.sync;
|
||||||
|
|
||||||
import java.io.FileInputStream;
|
|
||||||
import java.io.FileNotFoundException;
|
|
||||||
import java.io.FileOutputStream;
|
import java.io.FileOutputStream;
|
||||||
import java.io.PrintStream;
|
import java.io.PrintStream;
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import org.mozilla.gecko.background.common.GlobalConstants;
|
import org.mozilla.gecko.background.common.GlobalConstants;
|
||||||
import org.mozilla.gecko.background.helpers.AndroidSyncTestCase;
|
|
||||||
import org.mozilla.gecko.db.BrowserContract;
|
import org.mozilla.gecko.db.BrowserContract;
|
||||||
import org.mozilla.gecko.sync.ExtendedJSONObject;
|
import org.mozilla.gecko.sync.ExtendedJSONObject;
|
||||||
import org.mozilla.gecko.sync.SyncConfiguration;
|
import org.mozilla.gecko.sync.SyncConfiguration;
|
||||||
|
@ -28,7 +23,7 @@ import android.content.ContentResolver;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
|
|
||||||
public class TestAccountPickler extends AndroidSyncTestCase {
|
public class TestAccountPickler extends AndroidSyncTestCaseWithAccounts {
|
||||||
public static final String TEST_FILENAME = "test.json";
|
public static final String TEST_FILENAME = "test.json";
|
||||||
public static final String TEST_ACCOUNTTYPE = SyncConstants.ACCOUNTTYPE_SYNC;
|
public static final String TEST_ACCOUNTTYPE = SyncConstants.ACCOUNTTYPE_SYNC;
|
||||||
|
|
||||||
|
@ -49,56 +44,17 @@ public class TestAccountPickler extends AndroidSyncTestCase {
|
||||||
public static final long TEST_VERSION = SyncConfiguration.CURRENT_PREFS_VERSION;
|
public static final long TEST_VERSION = SyncConfiguration.CURRENT_PREFS_VERSION;
|
||||||
|
|
||||||
protected SyncAccountParameters params;
|
protected SyncAccountParameters params;
|
||||||
protected Context context;
|
|
||||||
protected AccountManager accountManager;
|
|
||||||
protected int numAccounts;
|
|
||||||
|
|
||||||
|
public TestAccountPickler() {
|
||||||
|
super(TEST_ACCOUNTTYPE, TEST_USERNAME);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public void setUp() {
|
public void setUp() {
|
||||||
context = getApplicationContext();
|
super.setUp();
|
||||||
accountManager = AccountManager.get(context);
|
|
||||||
params = new SyncAccountParameters(context, accountManager,
|
params = new SyncAccountParameters(context, accountManager,
|
||||||
TEST_USERNAME, TEST_SYNCKEY, TEST_PASSWORD, TEST_SERVER_URL,
|
TEST_USERNAME, TEST_SYNCKEY, TEST_PASSWORD, TEST_SERVER_URL,
|
||||||
TEST_CLUSTER_URL, TEST_CLIENT_NAME, TEST_CLIENT_GUID);
|
TEST_CLUSTER_URL, TEST_CLIENT_NAME, TEST_CLIENT_GUID);
|
||||||
numAccounts = accountManager.getAccountsByType(TEST_ACCOUNTTYPE).length;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static List<Account> getTestAccounts(final AccountManager accountManager) {
|
|
||||||
final List<Account> testAccounts = new ArrayList<Account>();
|
|
||||||
|
|
||||||
final Account[] accounts = accountManager.getAccountsByType(TEST_ACCOUNTTYPE);
|
|
||||||
for (Account account : accounts) {
|
|
||||||
if (account.name.startsWith(TEST_USERNAME)) {
|
|
||||||
testAccounts.add(account);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return testAccounts;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void deleteTestAccounts() {
|
|
||||||
for (Account account : getTestAccounts(accountManager)) {
|
|
||||||
TestSyncAccounts.deleteAccount(this, accountManager, account);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void tearDown() {
|
|
||||||
deleteTestAccounts();
|
|
||||||
assertEquals(numAccounts, accountManager.getAccountsByType(TEST_ACCOUNTTYPE).length);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void assertFileNotPresent(final Context context, final String filename) throws Exception {
|
|
||||||
// Verify file is not present.
|
|
||||||
FileInputStream fis = null;
|
|
||||||
try {
|
|
||||||
fis = context.openFileInput(TEST_FILENAME);
|
|
||||||
fail("Should get FileNotFoundException.");
|
|
||||||
} catch (FileNotFoundException e) {
|
|
||||||
// Do nothing; file should not exist.
|
|
||||||
} finally {
|
|
||||||
if (fis != null) {
|
|
||||||
fis.close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testPersist() throws Exception {
|
public void testPersist() throws Exception {
|
||||||
|
@ -137,7 +93,7 @@ public class TestAccountPickler extends AndroidSyncTestCase {
|
||||||
|
|
||||||
public Account deleteAccountsAndUnpickle(final Context context, final AccountManager accountManager, final String filename) {
|
public Account deleteAccountsAndUnpickle(final Context context, final AccountManager accountManager, final String filename) {
|
||||||
deleteTestAccounts();
|
deleteTestAccounts();
|
||||||
assertEquals(0, getTestAccounts(accountManager).size());
|
assertEquals(0, getTestAccounts().size());
|
||||||
|
|
||||||
return AccountPickler.unpickle(context, filename);
|
return AccountPickler.unpickle(context, filename);
|
||||||
}
|
}
|
||||||
|
@ -150,7 +106,7 @@ public class TestAccountPickler extends AndroidSyncTestCase {
|
||||||
assertNotNull(account);
|
assertNotNull(account);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
assertEquals(1, getTestAccounts(accountManager).size());
|
assertEquals(1, getTestAccounts().size());
|
||||||
assertTrue(ContentResolver.getSyncAutomatically(account, BrowserContract.AUTHORITY));
|
assertTrue(ContentResolver.getSyncAutomatically(account, BrowserContract.AUTHORITY));
|
||||||
assertEquals(account.name, TEST_USERNAME);
|
assertEquals(account.name, TEST_USERNAME);
|
||||||
|
|
||||||
|
@ -186,7 +142,7 @@ public class TestAccountPickler extends AndroidSyncTestCase {
|
||||||
assertNotNull(account);
|
assertNotNull(account);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
assertEquals(1, getTestAccounts(accountManager).size());
|
assertEquals(1, getTestAccounts().size());
|
||||||
assertFalse(ContentResolver.getSyncAutomatically(account, BrowserContract.AUTHORITY));
|
assertFalse(ContentResolver.getSyncAutomatically(account, BrowserContract.AUTHORITY));
|
||||||
} finally {
|
} finally {
|
||||||
TestSyncAccounts.deleteAccount(this, accountManager, account);
|
TestSyncAccounts.deleteAccount(this, accountManager, account);
|
||||||
|
@ -241,7 +197,7 @@ public class TestAccountPickler extends AndroidSyncTestCase {
|
||||||
// nothing was unpickled by verifying that the username has not changed.
|
// nothing was unpickled by verifying that the username has not changed.
|
||||||
assertTrue(SyncAccounts.syncAccountsExist(context));
|
assertTrue(SyncAccounts.syncAccountsExist(context));
|
||||||
|
|
||||||
for (Account a : getTestAccounts(accountManager)) {
|
for (Account a : getTestAccounts()) {
|
||||||
assertEquals(TEST_USERNAME, a.name);
|
assertEquals(TEST_USERNAME, a.name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -44,7 +44,8 @@ public class TestSyncConfiguration extends AndroidSyncTestCase {
|
||||||
config.persistToPrefs();
|
config.persistToPrefs();
|
||||||
assertFalse(prefs.contains(SyncConfiguration.PREF_DECLINED_ENGINE_NAMES));
|
assertFalse(prefs.contains(SyncConfiguration.PREF_DECLINED_ENGINE_NAMES));
|
||||||
config = newSyncConfiguration();
|
config = newSyncConfiguration();
|
||||||
assertNull(config.declinedEngineNames);
|
assertNotNull(config.declinedEngineNames);
|
||||||
|
assertTrue(config.declinedEngineNames.isEmpty());
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testEnabledEngineNames() {
|
public void testEnabledEngineNames() {
|
||||||
|
|
|
@ -13,6 +13,7 @@ import types
|
||||||
from collections import namedtuple
|
from collections import namedtuple
|
||||||
|
|
||||||
import mozwebidlcodegen
|
import mozwebidlcodegen
|
||||||
|
from reftest import ReftestManifest
|
||||||
|
|
||||||
import mozbuild.makeutil as mozmakeutil
|
import mozbuild.makeutil as mozmakeutil
|
||||||
from mozpack.copier import FilePurger
|
from mozpack.copier import FilePurger
|
||||||
|
@ -1075,6 +1076,11 @@ class RecursiveMakeBackend(CommonBackend):
|
||||||
(obj.install_prefix, set()))
|
(obj.install_prefix, set()))
|
||||||
m[1].add(obj.manifest_obj_relpath)
|
m[1].add(obj.manifest_obj_relpath)
|
||||||
|
|
||||||
|
if isinstance(obj.manifest, ReftestManifest):
|
||||||
|
# Mark included files as part of the build backend so changes
|
||||||
|
# result in re-config.
|
||||||
|
self.backend_input_files |= obj.manifest.manifests
|
||||||
|
|
||||||
def _process_local_include(self, local_include, backend_file):
|
def _process_local_include(self, local_include, backend_file):
|
||||||
if local_include.startswith('/'):
|
if local_include.startswith('/'):
|
||||||
path = '$(topsrcdir)'
|
path = '$(topsrcdir)'
|
||||||
|
|
|
@ -15,7 +15,7 @@ from mach.mixin.logging import LoggingMixin
|
||||||
|
|
||||||
import mozpack.path as mozpath
|
import mozpack.path as mozpath
|
||||||
import manifestparser
|
import manifestparser
|
||||||
|
import reftest
|
||||||
import mozinfo
|
import mozinfo
|
||||||
|
|
||||||
from .data import (
|
from .data import (
|
||||||
|
@ -419,6 +419,11 @@ class TreeMetadataEmitter(LoggingMixin):
|
||||||
for obj in self._process_test_manifest(sandbox, info, path):
|
for obj in self._process_test_manifest(sandbox, info, path):
|
||||||
yield obj
|
yield obj
|
||||||
|
|
||||||
|
for flavor in ('crashtest', 'reftest'):
|
||||||
|
for path in sandbox.get('%s_MANIFESTS' % flavor.upper(), []):
|
||||||
|
for obj in self._process_reftest_manifest(sandbox, flavor, path):
|
||||||
|
yield obj
|
||||||
|
|
||||||
jar_manifests = sandbox.get('JAR_MANIFESTS', [])
|
jar_manifests = sandbox.get('JAR_MANIFESTS', [])
|
||||||
if len(jar_manifests) > 1:
|
if len(jar_manifests) > 1:
|
||||||
raise SandboxValidationError('While JAR_MANIFESTS is a list, '
|
raise SandboxValidationError('While JAR_MANIFESTS is a list, '
|
||||||
|
@ -601,6 +606,38 @@ class TreeMetadataEmitter(LoggingMixin):
|
||||||
'manifest file %s: %s' % (path,
|
'manifest file %s: %s' % (path,
|
||||||
'\n'.join(traceback.format_exception(*sys.exc_info()))))
|
'\n'.join(traceback.format_exception(*sys.exc_info()))))
|
||||||
|
|
||||||
|
def _process_reftest_manifest(self, sandbox, flavor, manifest_path):
|
||||||
|
manifest_path = mozpath.normpath(manifest_path)
|
||||||
|
manifest_full_path = mozpath.normpath(mozpath.join(
|
||||||
|
sandbox['SRCDIR'], manifest_path))
|
||||||
|
manifest_reldir = mozpath.dirname(mozpath.relpath(manifest_full_path,
|
||||||
|
sandbox['TOPSRCDIR']))
|
||||||
|
|
||||||
|
manifest = reftest.ReftestManifest()
|
||||||
|
manifest.load(manifest_full_path)
|
||||||
|
|
||||||
|
# reftest manifests don't come from manifest parser. But they are
|
||||||
|
# similar enough that we can use the same emitted objects. Note
|
||||||
|
# that we don't perform any installs for reftests.
|
||||||
|
obj = TestManifest(sandbox, manifest_full_path, manifest,
|
||||||
|
flavor=flavor, install_prefix='%s/' % flavor,
|
||||||
|
relpath=mozpath.join(manifest_reldir,
|
||||||
|
mozpath.basename(manifest_path)))
|
||||||
|
|
||||||
|
for test in sorted(manifest.files):
|
||||||
|
obj.tests.append({
|
||||||
|
'path': test,
|
||||||
|
'here': mozpath.dirname(test),
|
||||||
|
'manifest': manifest_full_path,
|
||||||
|
'name': mozpath.basename(test),
|
||||||
|
'head': '',
|
||||||
|
'tail': '',
|
||||||
|
'support-files': '',
|
||||||
|
'subsuite': '',
|
||||||
|
})
|
||||||
|
|
||||||
|
yield obj
|
||||||
|
|
||||||
def _emit_directory_traversal_from_sandbox(self, sandbox):
|
def _emit_directory_traversal_from_sandbox(self, sandbox):
|
||||||
o = DirectoryTraversal(sandbox)
|
o = DirectoryTraversal(sandbox)
|
||||||
o.dirs = sandbox.get('DIRS', [])
|
o.dirs = sandbox.get('DIRS', [])
|
||||||
|
|
|
@ -599,6 +599,12 @@ VARIABLES = {
|
||||||
"""List of manifest files defining browser chrome tests.
|
"""List of manifest files defining browser chrome tests.
|
||||||
""", None),
|
""", None),
|
||||||
|
|
||||||
|
'CRASHTEST_MANIFESTS': (StrictOrderingOnAppendList, list,
|
||||||
|
"""List of manifest files defining crashtests.
|
||||||
|
|
||||||
|
These are commonly named crashtests.list.
|
||||||
|
""", None),
|
||||||
|
|
||||||
'METRO_CHROME_MANIFESTS': (StrictOrderingOnAppendList, list,
|
'METRO_CHROME_MANIFESTS': (StrictOrderingOnAppendList, list,
|
||||||
"""List of manifest files defining metro browser chrome tests.
|
"""List of manifest files defining metro browser chrome tests.
|
||||||
""", None),
|
""", None),
|
||||||
|
@ -615,6 +621,12 @@ VARIABLES = {
|
||||||
"""List of manifest files defining webapprt mochitest chrome tests.
|
"""List of manifest files defining webapprt mochitest chrome tests.
|
||||||
""", None),
|
""", None),
|
||||||
|
|
||||||
|
'REFTEST_MANIFESTS': (StrictOrderingOnAppendList, list,
|
||||||
|
"""List of manifest files defining reftests.
|
||||||
|
|
||||||
|
These are commonly named reftest.list.
|
||||||
|
""", None),
|
||||||
|
|
||||||
'WEBRTC_SIGNALLING_TEST_MANIFESTS': (StrictOrderingOnAppendList, list,
|
'WEBRTC_SIGNALLING_TEST_MANIFESTS': (StrictOrderingOnAppendList, list,
|
||||||
"""List of manifest files defining WebRTC signalling tests.
|
"""List of manifest files defining WebRTC signalling tests.
|
||||||
""", None),
|
""", None),
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
== crashtest1.html crashtest1-ref.html
|
|
@ -7,3 +7,5 @@ METRO_CHROME_MANIFESTS += ['metro.ini']
|
||||||
MOCHITEST_MANIFESTS += ['mochitest.ini']
|
MOCHITEST_MANIFESTS += ['mochitest.ini']
|
||||||
MOCHITEST_CHROME_MANIFESTS += ['chrome.ini']
|
MOCHITEST_CHROME_MANIFESTS += ['chrome.ini']
|
||||||
XPCSHELL_TESTS_MANIFESTS += ['xpcshell.ini']
|
XPCSHELL_TESTS_MANIFESTS += ['xpcshell.ini']
|
||||||
|
REFTEST_MANIFESTS += ['reftest.list']
|
||||||
|
CRASHTEST_MANIFESTS += ['crashtest.list']
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
== reftest1.html reftest1-ref.html
|
Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше
Загрузка…
Ссылка в новой задаче