--HG--
extra : amend_source : 08a7eb6769513fc676b7d09f337f4e57a09978d7
This commit is contained in:
Tim Taubert 2014-06-23 14:34:34 +02:00
Родитель 2d11183756 00a37a577b
Коммит 502722d134
104 изменённых файлов: 1961 добавлений и 1283 удалений

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

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

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

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

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

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

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

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

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

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

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

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

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

После

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

@ -1,48 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_focused="false"
android:state_selected="false"
android:state_pressed="false"
android:drawable="@android:color/transparent"/>
<item android:state_focused="false"
android:state_selected="true"
android:state_pressed="false"
android:drawable="@color/background_light"/>
<item android:state_focused="true"
android:state_selected="false"
android:state_pressed="false"
android:drawable="@color/background_normal"/>
<item android:state_focused="true"
android:state_selected="true"
android:state_pressed="false"
android:drawable="@color/background_light"/>
<item android:state_focused="false"
android:state_selected="false"
android:state_pressed="true"
android:drawable="@color/background_normal"/>
<item android:state_focused="false"
android:state_selected="true"
android:state_pressed="true"
android:drawable="@color/background_normal"/>
<item android:state_focused="true"
android:state_selected="false"
android:state_pressed="true"
android:drawable="@color/background_normal"/>
<item android:state_focused="true"
android:state_selected="true"
android:state_pressed="true"
android:drawable="@color/background_normal"/>
</selector>

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

@ -1,48 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_focused="false"
android:state_selected="false"
android:state_pressed="false"
android:drawable="@android:color/transparent"/>
<item android:state_focused="false"
android:state_selected="true"
android:state_pressed="false"
android:drawable="@color/background_light"/>
<item android:state_focused="true"
android:state_selected="false"
android:state_pressed="false"
android:drawable="@color/background_normal"/>
<item android:state_focused="true"
android:state_selected="true"
android:state_pressed="false"
android:drawable="@color/background_light"/>
<item android:state_focused="false"
android:state_selected="false"
android:state_pressed="true"
android:drawable="@color/background_normal"/>
<item android:state_focused="false"
android:state_selected="true"
android:state_pressed="true"
android:drawable="@color/background_normal"/>
<item android:state_focused="true"
android:state_selected="false"
android:state_pressed="true"
android:drawable="@color/background_normal"/>
<item android:state_focused="true"
android:state_selected="true"
android:state_pressed="true"
android:drawable="@color/background_normal"/>
</selector>

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

@ -1,48 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_focused="false"
android:state_selected="false"
android:state_pressed="false"
android:drawable="@android:color/transparent"/>
<item android:state_focused="false"
android:state_selected="true"
android:state_pressed="false"
android:drawable="@drawable/history_tabs_indicator_selected"/>
<item android:state_focused="true"
android:state_selected="false"
android:state_pressed="false"
android:drawable="@color/background_normal"/>
<item android:state_focused="true"
android:state_selected="true"
android:state_pressed="false"
android:drawable="@drawable/history_tabs_indicator_selected"/>
<item android:state_focused="false"
android:state_selected="false"
android:state_pressed="true"
android:drawable="@color/background_normal"/>
<item android:state_focused="false"
android:state_selected="true"
android:state_pressed="true"
android:drawable="@color/background_normal"/>
<item android:state_focused="true"
android:state_selected="false"
android:state_pressed="true"
android:drawable="@color/background_normal"/>
<item android:state_focused="true"
android:state_selected="true"
android:state_pressed="true"
android:drawable="@color/background_normal"/>
</selector>

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

@ -1,24 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:gecko="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<org.mozilla.gecko.widget.IconTabWidget android:id="@+id/tab_icon_widget"
style="@style/Widget.Home.HistoryTabWidget"
android:layout_width="@dimen/history_tab_widget_width"
android:layout_height="@dimen/history_tab_widget_height"
android:orientation="vertical"
android:layout="@layout/home_history_tabs_indicator"
gecko:display="text"/>
<FrameLayout android:id="@+id/history_panel_container"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1" />
</LinearLayout>

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

@ -1,10 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
style="@style/Widget.Home.HistoryTabIndicator"
android:layout_width="match_parent"
android:layout_height="@dimen/history_tab_indicator_height"
android:background="@drawable/home_history_tabs_indicator"/>

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

@ -1,24 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:gecko="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<org.mozilla.gecko.widget.IconTabWidget android:id="@+id/tab_icon_widget"
style="@style/Widget.Home.HistoryTabWidget"
android:layout_width="@dimen/history_tab_widget_width"
android:layout_height="@dimen/history_tab_widget_height"
android:orientation="vertical"
android:layout="@layout/home_history_tabs_indicator"
gecko:display="text"/>
<FrameLayout android:id="@+id/history_panel_container"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1" />
</LinearLayout>

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

@ -1,10 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
style="@style/Widget.Home.HistoryTabIndicator"
android:layout_width="match_parent"
android:layout_height="@dimen/history_tab_indicator_height"
android:background="@drawable/home_history_tabs_indicator"/>

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

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

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

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

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

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

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

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

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

@ -0,0 +1,116 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at http://mozilla.org/MPL/2.0/.
-->
<merge xmlns:android="http://schemas.android.com/apk/res/android" >
<LinearLayout
android:id="@+id/account_server_layout"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="10dp"
android:background="@color/fxaccount_error_preference_backgroundcolor"
android:gravity="center_vertical"
android:minHeight="?android:attr/listPreferredItemHeight" >
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:contentDescription="@string/fxaccount_empty_contentDescription"
android:gravity="center"
android:minWidth="48dip"
android:padding="10dip"
android:src="@drawable/fxaccount_sync_error" />
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:orientation="vertical"
android:paddingBottom="6dip"
android:paddingRight="10dip"
android:paddingTop="6dip" >
<TextView
android:id="@+android:id/account_server_title"
style="@style/FxAccountTextItem"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:layout_marginBottom="0dp"
android:gravity="center_vertical"
android:text="@string/fxaccount_custom_server_account_title" >
</TextView>
<TextView
android:id="@+android:id/account_server_summary"
style="@style/FxAccountTextItem"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="0dp"
android:ellipsize="middle"
android:focusable="true"
android:focusableInTouchMode="true"
android:singleLine="true"
android:textAppearance="?android:attr/textAppearanceSmall"
android:textSize="12sp" />
</LinearLayout>
</LinearLayout>
<LinearLayout
android:id="@+id/sync_server_layout"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="10dp"
android:background="@color/fxaccount_error_preference_backgroundcolor"
android:gravity="center_vertical"
android:minHeight="?android:attr/listPreferredItemHeight" >
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:contentDescription="@string/fxaccount_empty_contentDescription"
android:gravity="center"
android:minWidth="48dip"
android:padding="10dip"
android:src="@drawable/fxaccount_sync_error" />
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:orientation="vertical"
android:paddingBottom="6dip"
android:paddingRight="10dip"
android:paddingTop="6dip" >
<TextView
android:id="@+android:id/sync_server_title"
style="@style/FxAccountTextItem"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:layout_marginBottom="0dp"
android:gravity="center_vertical"
android:text="@string/fxaccount_custom_server_sync_title" >
</TextView>
<TextView
android:id="@+android:id/sync_server_summary"
style="@style/FxAccountTextItem"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="0dp"
android:ellipsize="marquee"
android:focusable="true"
android:focusableInTouchMode="true"
android:singleLine="true"
android:textAppearance="?android:attr/textAppearanceSmall"
android:textSize="12sp" />
</LinearLayout>
</LinearLayout>
</merge>

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

@ -1,10 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<ImageButton xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:background="@drawable/home_history_tabs_indicator"/>

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

@ -1,25 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<include layout="@layout/home_history_list"/>
<LinearLayout android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/home_button_bar_bg">
<Button android:id="@+id/clear_history_button"
style="@style/Widget.Home.ActionButton"
android:text="@string/home_clear_history_button"
android:gravity="center"
android:visibility="gone"/>
</LinearLayout>
</LinearLayout>

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

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

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

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

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

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

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

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

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

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

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

@ -1,10 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<resources>
<dimen name="history_tab_widget_width">360dp</dimen>
</resources>

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

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

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

@ -1,10 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<resources>
<dimen name="history_tab_widget_width">480dp</dimen>
</resources>

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

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

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

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

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

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

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

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

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

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

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

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

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

@ -472,7 +472,7 @@ public class SyncConfiguration {
} else {
edit.putString(PREF_ENABLED_ENGINE_NAMES, setToJSONObjectString(enabledEngineNames));
}
if (declinedEngineNames.isEmpty()) {
if (declinedEngineNames == null || declinedEngineNames.isEmpty()) {
edit.remove(PREF_DECLINED_ENGINE_NAMES);
} else {
edit.putString(PREF_DECLINED_ENGINE_NAMES, setToJSONObjectString(declinedEngineNames));

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

@ -44,7 +44,7 @@ public class DecryptDataStage extends JPakeStage {
Logger.debug(LOG_TAG, "Decrypting data.");
String cleartext = null;
try {
cleartext = new String(decryptPayload(iPayload, jClient.myKeyBundle), "UTF-8");
cleartext = new String(decryptPayload(iPayload, jClient.myKeyBundle), "UTF-8");
} catch (UnsupportedEncodingException e) {
Logger.error(LOG_TAG, "Failed to decrypt data.", e);
jClient.abort(Constants.JPAKE_ERROR_INTERNAL);

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

@ -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
*/
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>() {{
add("TOP_SITES");
add("BOOKMARKS");
@ -42,8 +49,10 @@ abstract class AboutHomeTest extends PixelTest {
// Update it for tablets vs. phones.
if (mDevice.type.equals("phone")) {
aboutHomeTabs.add(0, AboutHomeTabs.HISTORY.toString());
aboutHomeTabs.add(0, AboutHomeTabs.RECENT_TABS.toString());
} else {
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.
*
* @param AboutHomeTabs enum item {MOST_RECENT, TABS_FROM_LAST_TIME, TOP_SITES, BOOKMARKS, READING_LIST}
* @param AboutHomeTabs enum item
*/
protected void openAboutHomeTab(AboutHomeTabs tab) {
focusUrlBar();
@ -233,71 +242,13 @@ abstract class AboutHomeTest extends PixelTest {
// Handle tablets by just clicking the visible tab title.
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;
}
// Handle phones (non-tablets).
tabOffset = aboutHomeTabs.indexOf(tab.toString()) - currentTabIndex;
switch (tab) {
case TOP_SITES : {
swipeAboutHome(tabOffset);
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;
}
}
swipeAboutHome(tabOffset);
waitForAboutHomeTab(aboutHomeTabs.indexOf(tab.toString()));
}
}

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

@ -30,13 +30,15 @@ public class AboutHomeComponent extends BaseComponent {
HISTORY,
TOP_SITES,
BOOKMARKS,
READING_LIST
READING_LIST,
RECENT_TABS
}
// 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.
// Explicit ordering of HomePager panels on a phone.
private enum PhonePanel {
RECENT_TABS,
HISTORY,
TOP_SITES,
BOOKMARKS,
@ -48,7 +50,8 @@ public class AboutHomeComponent extends BaseComponent {
TOP_SITES,
BOOKMARKS,
READING_LIST,
HISTORY
HISTORY,
RECENT_TABS
}
// 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.
*
* TODO: Update this test to account for recent tabs panel (bug 1028727).
*/
public class testAboutHomePageNavigation extends UITest {
// TODO: Define this test dynamically by creating dynamic representations of the Page

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

@ -21,9 +21,9 @@ public class testHistory extends AboutHomeTest {
inputAndLoadUrl(url3);
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");
// 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");
// Check if the page is added in History tab like a Reading List item
openAboutHomeTab(AboutHomeTabs.MOST_RECENT);
list = findListViewWithTag("most_recent");
openAboutHomeTab(AboutHomeTabs.HISTORY);
list = findListViewWithTag("history");
child = list.getChildAt(1);
mAsserter.ok(child != null, "item can be retrieved", child != null ? child.toString() : "null!");
mSolo.clickLongOnView(child);

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

@ -119,10 +119,10 @@ public class testShareLink extends AboutHomeTest {
mSolo.clickLongOnView(mostVisitedItem);
verifySharePopup(shareOptions,"top_sites");
// Test the share popup in the Most Recent tab
openAboutHomeTab(AboutHomeTabs.MOST_RECENT);
// Test the share popup in the history tab
openAboutHomeTab(AboutHomeTabs.HISTORY);
ListView mostRecentList = findListViewWithTag("most_recent");
ListView mostRecentList = findListViewWithTag("history");
mAsserter.is(waitForNonEmptyListToLoad(mostRecentList), true, "list is properly loaded");
// Getting second child after header views because the first is the "Today" label

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

@ -43,13 +43,16 @@ SessionStore.prototype = {
_windows: {},
_lastSaveTime: 0,
_interval: 10000,
_maxTabsUndo: 1,
_maxTabsUndo: 5,
_pendingWrite: 0,
// The index where the most recently closed tab was in the tabs array
// when it was closed.
_lastClosedTabIndex: -1,
// Whether or not to send notifications for changes to the closed tabs.
_notifyClosedTabs: false,
init: function ss_init() {
// Get file references
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, "Session:Restore", true);
observerService.addObserver(this, "application-background", true);
observerService.addObserver(this, "ClosedTabs:StartNotifications", true);
observerService.addObserver(this, "ClosedTabs:StopNotifications", true);
break;
case "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.
this.flushPendingState();
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._lastClosedTabIndex = aTabIndex;
if (this._notifyClosedTabs) {
this._sendClosedTabsToJava(aWindow);
}
}
},
@ -864,6 +880,10 @@ SessionStore.prototype = {
// Put back the extra data
tab.browser.__SS_extdata = closedTab.extData;
if (this._notifyClosedTabs) {
this._sendClosedTabsToJava(aWindow);
}
return tab.browser;
},
@ -885,6 +905,30 @@ SessionStore.prototype = {
if (aIndex == 0) {
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) {

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

@ -65,12 +65,16 @@ let Accounts = Object.freeze({
* Fire-and-forget: open the Firefox accounts activity, which
* 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.
*/
launchSetup: function () {
launchSetup: function (extras) {
sendMessageToJava({
type: "Accounts:Create",
extras: extras,
});
},
});

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

@ -117,6 +117,9 @@
contentDescription text, and it should not be translated. -->
<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_password_hint">&fxaccount_password_hint;</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_header">&fxaccount_status_header2;</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_sync_server">&fxaccount_status_sync_server;</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_needs_verification">&fxaccount_status_needs_verification2;</string>

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

@ -47,6 +47,7 @@ BACKGROUND_TESTS_JAVA_FILES := \
src/helpers/DBProviderTestCase.java \
src/helpers/FakeProfileTestCase.java \
src/nativecode/test/TestNativeCrypto.java \
src/sync/AndroidSyncTestCaseWithAccounts.java \
src/sync/helpers/BookmarkHelpers.java \
src/sync/helpers/DefaultBeginDelegate.java \
src/sync/helpers/DefaultCleanDelegate.java \

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

@ -1,11 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical" >
<TextView
android:layout_width="match_parent"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/app_name" />

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

@ -3,30 +3,42 @@
package org.mozilla.gecko.background.fxa.authenticator;
import org.mozilla.gecko.background.helpers.AndroidSyncTestCase;
import org.mozilla.gecko.background.sync.TestSyncAccounts;
import org.mozilla.gecko.background.sync.AndroidSyncTestCaseWithAccounts;
import org.mozilla.gecko.fxa.FxAccountConstants;
import org.mozilla.gecko.fxa.authenticator.AccountPickler;
import org.mozilla.gecko.fxa.authenticator.AndroidFxAccount;
import org.mozilla.gecko.fxa.login.Separated;
import org.mozilla.gecko.fxa.login.State;
import org.mozilla.gecko.sync.ExtendedJSONObject;
import org.mozilla.gecko.sync.Utils;
import android.accounts.Account;
import android.accounts.AccountManager;
import android.test.InstrumentationTestCase;
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 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 RenamingDelegatingContext context;
public AccountManager accountManager;
public TestAccountPickler() {
super(TEST_ACCOUNTTYPE, TEST_USERNAME);
}
@Override
public void setUp() {
super.setUp();
this.account = null;
// Randomize the filename prefix in case we don't clean up correctly.
this.context = new RenamingDelegatingContext(getApplicationContext(), FILENAME_PREFIX +
@ -36,54 +48,72 @@ public class TestAccountPickler extends AndroidSyncTestCase {
@Override
public void tearDown() {
if (this.account != null) {
deleteAccount(this, this.accountManager, this.account);
this.account = null;
}
super.tearDown();
this.context.deleteFile(PICKLE_FILENAME);
}
public static void deleteAccount(final InstrumentationTestCase test,
final AccountManager accountManager, final Account account) {
TestSyncAccounts.deleteAccount(test, accountManager, account);
}
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);
public AndroidFxAccount addTestAccount() throws Exception {
final State state = new Separated(TEST_USERNAME, "uid", false); // State choice is arbitrary.
final AndroidFxAccount account = AndroidFxAccount.addAndroidAccount(context, TEST_USERNAME,
TEST_PROFILE, TEST_AUTH_SERVER_URI, TEST_TOKEN_SERVER_URI, state);
assertNotNull(account);
assertTrue(accountsExist()); // Sanity check.
assertNotNull(account.getProfile());
assertTrue(testAccountsExist()); // Sanity check.
this.account = account.getAndroidAccount(); // To remove in tearDown() if we throw.
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 {
final AndroidFxAccount inputAccount = addDummyAccount();
final AndroidFxAccount inputAccount = addTestAccount();
// Sync is enabled by default so we do a more thorough test by disabling it.
inputAccount.disableSyncing();
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.
deleteAccount(this, this.accountManager, inputAccount.getAndroidAccount());
assertFalse(accountsExist());
deleteTestAccounts();
assertFalse(testAccountsExist());
final AndroidFxAccount unpickledAccount =
AccountPickler.unpickle(context, PICKLE_FILENAME);
final AndroidFxAccount unpickledAccount = AccountPickler.unpickle(context, PICKLE_FILENAME);
assertNotNull(unpickledAccount);
this.account = unpickledAccount.getAndroidAccount(); // To remove in tearDown().
assertAccountsEquals(inputAccount, unpickledAccount);
final ExtendedJSONObject unpickledJSON = AccountPickler.toJSON(unpickledAccount, 0);
final State unpickledState = unpickledAccount.getState();
assertNotNull(unpickledJSON);
assertNotNull(unpickledState);
assertEquals(inputJSON, unpickledJSON);
assertStateEquals(inputState, unpickledState);
}
public void testDeletePickle() throws Exception {
final AndroidFxAccount account = addDummyAccount();
final AndroidFxAccount account = addTestAccount();
AccountPickler.pickle(account, PICKLE_FILENAME);
final String s = Utils.readFile(context, PICKLE_FILENAME);
@ -91,23 +121,7 @@ public class TestAccountPickler extends AndroidSyncTestCase {
assertTrue(s.length() > 0);
AccountPickler.deletePickle(context, PICKLE_FILENAME);
org.mozilla.gecko.background.sync.TestAccountPickler.assertFileNotPresent(
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());
assertFileNotPresent(context, PICKLE_FILENAME);
}
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.SharedPreferences;
import android.test.mock.MockContext;
public class TestHealthReportPruneService
extends BackgroundServiceTestCase<TestHealthReportPruneService.MockHealthReportPruneService> {
@ -37,7 +36,10 @@ public class TestHealthReportPruneService
@Override
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());
return prunePolicy;
}

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

@ -21,6 +21,12 @@ public class TestHealthReportUploadService
GlobalConstants.SHARED_PREFERENCES_MODE);
}
@Override
public boolean backgroundDataIsEnabled() {
// When testing, we always want to say we can upload.
return true;
}
@Override
public void onHandleIntent(Intent intent) {
super.onHandleIntent(intent);

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

@ -23,7 +23,7 @@ public class AndroidSyncTestCase extends ActivityInstrumentationTestCase2<Activi
}
public Context getApplicationContext() {
return this.getInstrumentation().getTargetContext().getApplicationContext();
return this.getInstrumentation().getTargetContext();
}
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;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.PrintStream;
import java.util.ArrayList;
import java.util.List;
import org.mozilla.gecko.background.common.GlobalConstants;
import org.mozilla.gecko.background.helpers.AndroidSyncTestCase;
import org.mozilla.gecko.db.BrowserContract;
import org.mozilla.gecko.sync.ExtendedJSONObject;
import org.mozilla.gecko.sync.SyncConfiguration;
@ -28,7 +23,7 @@ import android.content.ContentResolver;
import android.content.Context;
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_ACCOUNTTYPE = SyncConstants.ACCOUNTTYPE_SYNC;
@ -49,56 +44,17 @@ public class TestAccountPickler extends AndroidSyncTestCase {
public static final long TEST_VERSION = SyncConfiguration.CURRENT_PREFS_VERSION;
protected SyncAccountParameters params;
protected Context context;
protected AccountManager accountManager;
protected int numAccounts;
public TestAccountPickler() {
super(TEST_ACCOUNTTYPE, TEST_USERNAME);
}
@Override
public void setUp() {
context = getApplicationContext();
accountManager = AccountManager.get(context);
super.setUp();
params = new SyncAccountParameters(context, accountManager,
TEST_USERNAME, TEST_SYNCKEY, TEST_PASSWORD, TEST_SERVER_URL,
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 {
@ -137,7 +93,7 @@ public class TestAccountPickler extends AndroidSyncTestCase {
public Account deleteAccountsAndUnpickle(final Context context, final AccountManager accountManager, final String filename) {
deleteTestAccounts();
assertEquals(0, getTestAccounts(accountManager).size());
assertEquals(0, getTestAccounts().size());
return AccountPickler.unpickle(context, filename);
}
@ -150,7 +106,7 @@ public class TestAccountPickler extends AndroidSyncTestCase {
assertNotNull(account);
try {
assertEquals(1, getTestAccounts(accountManager).size());
assertEquals(1, getTestAccounts().size());
assertTrue(ContentResolver.getSyncAutomatically(account, BrowserContract.AUTHORITY));
assertEquals(account.name, TEST_USERNAME);
@ -186,7 +142,7 @@ public class TestAccountPickler extends AndroidSyncTestCase {
assertNotNull(account);
try {
assertEquals(1, getTestAccounts(accountManager).size());
assertEquals(1, getTestAccounts().size());
assertFalse(ContentResolver.getSyncAutomatically(account, BrowserContract.AUTHORITY));
} finally {
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.
assertTrue(SyncAccounts.syncAccountsExist(context));
for (Account a : getTestAccounts(accountManager)) {
for (Account a : getTestAccounts()) {
assertEquals(TEST_USERNAME, a.name);
}
}

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

@ -44,7 +44,8 @@ public class TestSyncConfiguration extends AndroidSyncTestCase {
config.persistToPrefs();
assertFalse(prefs.contains(SyncConfiguration.PREF_DECLINED_ENGINE_NAMES));
config = newSyncConfiguration();
assertNull(config.declinedEngineNames);
assertNotNull(config.declinedEngineNames);
assertTrue(config.declinedEngineNames.isEmpty());
}
public void testEnabledEngineNames() {

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

@ -13,6 +13,7 @@ import types
from collections import namedtuple
import mozwebidlcodegen
from reftest import ReftestManifest
import mozbuild.makeutil as mozmakeutil
from mozpack.copier import FilePurger
@ -1075,6 +1076,11 @@ class RecursiveMakeBackend(CommonBackend):
(obj.install_prefix, set()))
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):
if local_include.startswith('/'):
path = '$(topsrcdir)'

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

@ -15,7 +15,7 @@ from mach.mixin.logging import LoggingMixin
import mozpack.path as mozpath
import manifestparser
import reftest
import mozinfo
from .data import (
@ -419,6 +419,11 @@ class TreeMetadataEmitter(LoggingMixin):
for obj in self._process_test_manifest(sandbox, info, path):
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', [])
if len(jar_manifests) > 1:
raise SandboxValidationError('While JAR_MANIFESTS is a list, '
@ -601,6 +606,38 @@ class TreeMetadataEmitter(LoggingMixin):
'manifest file %s: %s' % (path,
'\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):
o = DirectoryTraversal(sandbox)
o.dirs = sandbox.get('DIRS', [])

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

@ -599,6 +599,12 @@ VARIABLES = {
"""List of manifest files defining browser chrome tests.
""", None),
'CRASHTEST_MANIFESTS': (StrictOrderingOnAppendList, list,
"""List of manifest files defining crashtests.
These are commonly named crashtests.list.
""", None),
'METRO_CHROME_MANIFESTS': (StrictOrderingOnAppendList, list,
"""List of manifest files defining metro browser chrome tests.
""", None),
@ -615,6 +621,12 @@ VARIABLES = {
"""List of manifest files defining webapprt mochitest chrome tests.
""", None),
'REFTEST_MANIFESTS': (StrictOrderingOnAppendList, list,
"""List of manifest files defining reftests.
These are commonly named reftest.list.
""", None),
'WEBRTC_SIGNALLING_TEST_MANIFESTS': (StrictOrderingOnAppendList, list,
"""List of manifest files defining WebRTC signalling tests.
""", None),

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

@ -0,0 +1 @@
== crashtest1.html crashtest1-ref.html

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

@ -7,3 +7,5 @@ METRO_CHROME_MANIFESTS += ['metro.ini']
MOCHITEST_MANIFESTS += ['mochitest.ini']
MOCHITEST_CHROME_MANIFESTS += ['chrome.ini']
XPCSHELL_TESTS_MANIFESTS += ['xpcshell.ini']
REFTEST_MANIFESTS += ['reftest.list']
CRASHTEST_MANIFESTS += ['crashtest.list']

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