зеркало из https://github.com/mozilla/gecko-dev.git
Merge fx-team to mozilla-central
This commit is contained in:
Коммит
348406bdbc
|
@ -482,7 +482,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
// null parameter below specifies HTML response for search
|
// null parameter below specifies HTML response for search
|
||||||
var submission = this.currentEngine.getSubmission(aData);
|
var submission = this.currentEngine.getSubmission(aData, null, "searchbar");
|
||||||
BrowserSearch.recordSearchInHealthReport(this.currentEngine.name, "searchbar");
|
BrowserSearch.recordSearchInHealthReport(this.currentEngine.name, "searchbar");
|
||||||
openUILinkIn(submission.uri.spec, aWhere, null, submission.postData);
|
openUILinkIn(submission.uri.spec, aWhere, null, submission.postData);
|
||||||
]]></body>
|
]]></body>
|
||||||
|
@ -531,14 +531,14 @@
|
||||||
var engine = this.currentEngine;
|
var engine = this.currentEngine;
|
||||||
var connector =
|
var connector =
|
||||||
Services.io.QueryInterface(Components.interfaces.nsISpeculativeConnect);
|
Services.io.QueryInterface(Components.interfaces.nsISpeculativeConnect);
|
||||||
var searchURI = engine.getSubmission("dummy").uri;
|
var searchURI = engine.getSubmission("dummy", null, "searchbar").uri;
|
||||||
let callbacks = window.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
|
let callbacks = window.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
|
||||||
.getInterface(Components.interfaces.nsIWebNavigation)
|
.getInterface(Components.interfaces.nsIWebNavigation)
|
||||||
.QueryInterface(Components.interfaces.nsILoadContext);
|
.QueryInterface(Components.interfaces.nsILoadContext);
|
||||||
connector.speculativeConnect(searchURI, callbacks);
|
connector.speculativeConnect(searchURI, callbacks);
|
||||||
|
|
||||||
if (engine.supportsResponseType(SUGGEST_TYPE)) {
|
if (engine.supportsResponseType(SUGGEST_TYPE)) {
|
||||||
var suggestURI = engine.getSubmission("dummy", SUGGEST_TYPE).uri;
|
var suggestURI = engine.getSubmission("dummy", SUGGEST_TYPE, "searchbar").uri;
|
||||||
if (suggestURI.prePath != searchURI.prePath)
|
if (suggestURI.prePath != searchURI.prePath)
|
||||||
connector.speculativeConnect(suggestURI, callbacks);
|
connector.speculativeConnect(suggestURI, callbacks);
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
ifdef ENABLE_TESTS
|
ifdef ENABLE_TESTS
|
||||||
pp_mochitest_browser_files := \
|
pp_mochitest_browser_files := \
|
||||||
browser_google.js \
|
browser_google.js \
|
||||||
|
browser_google_behavior.js \
|
||||||
$(NULL)
|
$(NULL)
|
||||||
pp_mochitest_browser_files_PATH := $(DEPTH)/_tests/testing/mochitest/browser/$(relativesrcdir)
|
pp_mochitest_browser_files_PATH := $(DEPTH)/_tests/testing/mochitest/browser/$(relativesrcdir)
|
||||||
pp_mochitest_browser_files_FLAGS := -DMOZ_DISTRIBUTION_ID=$(MOZ_DISTRIBUTION_ID)
|
pp_mochitest_browser_files_FLAGS := -DMOZ_DISTRIBUTION_ID=$(MOZ_DISTRIBUTION_ID)
|
||||||
|
|
|
@ -80,6 +80,10 @@ function test() {
|
||||||
is(url, base + "&channel=rcs", "Check context menu search URL for 'foo'");
|
is(url, base + "&channel=rcs", "Check context menu search URL for 'foo'");
|
||||||
url = engine.getSubmission("foo", null, "keyword").uri.spec;
|
url = engine.getSubmission("foo", null, "keyword").uri.spec;
|
||||||
is(url, base + "&channel=fflb", "Check keyword search URL for 'foo'");
|
is(url, base + "&channel=fflb", "Check keyword search URL for 'foo'");
|
||||||
|
url = engine.getSubmission("foo", null, "searchbar").uri.spec;
|
||||||
|
is(url, base + "&channel=sb", "Check search bar search URL for 'foo'");
|
||||||
|
url = engine.getSubmission("foo", null, "homepage").uri.spec;
|
||||||
|
is(url, base + "&channel=np&source=hp", "Check homepage search URL for 'foo'");
|
||||||
|
|
||||||
// Check search suggestion URL.
|
// Check search suggestion URL.
|
||||||
url = engine.getSubmission("foo", "application/x-suggestions+json").uri.spec;
|
url = engine.getSubmission("foo", "application/x-suggestions+json").uri.spec;
|
||||||
|
@ -147,6 +151,11 @@ function test() {
|
||||||
"value": "fflb",
|
"value": "fflb",
|
||||||
"purpose": "keyword",
|
"purpose": "keyword",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "channel",
|
||||||
|
"value": "sb",
|
||||||
|
"purpose": "searchbar",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "channel",
|
"name": "channel",
|
||||||
"value": "np",
|
"value": "np",
|
||||||
|
|
|
@ -0,0 +1,187 @@
|
||||||
|
/* Any copyright is dedicated to the Public Domain.
|
||||||
|
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Test Google search plugin URLs
|
||||||
|
*/
|
||||||
|
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
const BROWSER_SEARCH_PREF = "browser.search.";
|
||||||
|
|
||||||
|
const MOZ_PARAM_LOCALE = /\{moz:locale\}/g;
|
||||||
|
const MOZ_PARAM_DIST_ID = /\{moz:distributionID\}/g;
|
||||||
|
const MOZ_PARAM_OFFICIAL = /\{moz:official\}/g;
|
||||||
|
|
||||||
|
// Custom search parameters
|
||||||
|
#ifdef MOZ_OFFICIAL_BRANDING
|
||||||
|
const MOZ_OFFICIAL = "official";
|
||||||
|
#else
|
||||||
|
const MOZ_OFFICIAL = "unofficial";
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if MOZ_UPDATE_CHANNEL == beta
|
||||||
|
const GOOGLE_CLIENT = "firefox-beta";
|
||||||
|
#elif MOZ_UPDATE_CHANNEL == aurora
|
||||||
|
const GOOGLE_CLIENT = "firefox-aurora";
|
||||||
|
#elif MOZ_UPDATE_CHANNEL == nightly
|
||||||
|
const GOOGLE_CLIENT = "firefox-nightly";
|
||||||
|
#else
|
||||||
|
const GOOGLE_CLIENT = "firefox-a";
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#expand const MOZ_DISTRIBUTION_ID = __MOZ_DISTRIBUTION_ID__;
|
||||||
|
|
||||||
|
function getLocale() {
|
||||||
|
const localePref = "general.useragent.locale";
|
||||||
|
return getLocalizedPref(localePref, Services.prefs.getCharPref(localePref));
|
||||||
|
}
|
||||||
|
|
||||||
|
function getLocalizedPref(aPrefName, aDefault) {
|
||||||
|
try {
|
||||||
|
return Services.prefs.getComplexValue(aPrefName, Ci.nsIPrefLocalizedString).data;
|
||||||
|
} catch (ex) {
|
||||||
|
return aDefault;
|
||||||
|
}
|
||||||
|
|
||||||
|
return aDefault;
|
||||||
|
}
|
||||||
|
|
||||||
|
function test() {
|
||||||
|
let engine = Services.search.getEngineByName("Google");
|
||||||
|
ok(engine, "Google is installed");
|
||||||
|
|
||||||
|
is(Services.search.defaultEngine, engine, "Check that Google is the default search engine");
|
||||||
|
|
||||||
|
let distributionID;
|
||||||
|
try {
|
||||||
|
distributionID = Services.prefs.getCharPref(BROWSER_SEARCH_PREF + "distributionID");
|
||||||
|
} catch (ex) {
|
||||||
|
distributionID = MOZ_DISTRIBUTION_ID;
|
||||||
|
}
|
||||||
|
|
||||||
|
let base = "https://www.google.com/search?q=foo&ie=utf-8&oe=utf-8&aq=t&rls={moz:distributionID}:{moz:locale}:{moz:official}&client=" + GOOGLE_CLIENT;
|
||||||
|
base = base.replace(MOZ_PARAM_LOCALE, getLocale());
|
||||||
|
base = base.replace(MOZ_PARAM_DIST_ID, distributionID);
|
||||||
|
base = base.replace(MOZ_PARAM_OFFICIAL, MOZ_OFFICIAL);
|
||||||
|
|
||||||
|
let url;
|
||||||
|
|
||||||
|
// Test search URLs (including purposes).
|
||||||
|
url = engine.getSubmission("foo").uri.spec;
|
||||||
|
is(url, base, "Check search URL for 'foo'");
|
||||||
|
|
||||||
|
waitForExplicitFinish();
|
||||||
|
|
||||||
|
var gCurrTest;
|
||||||
|
var gTests = [
|
||||||
|
{
|
||||||
|
name: "context menu search",
|
||||||
|
searchURL: base + "&channel=rcs",
|
||||||
|
run: function () {
|
||||||
|
// Simulate a contextmenu search
|
||||||
|
// FIXME: This is a bit "low-level"...
|
||||||
|
BrowserSearch.loadSearch("foo", false, "contextmenu");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "keyword search",
|
||||||
|
searchURL: base + "&channel=fflb",
|
||||||
|
run: function () {
|
||||||
|
gURLBar.value = "? foo";
|
||||||
|
gURLBar.focus();
|
||||||
|
EventUtils.synthesizeKey("VK_RETURN", {});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "search bar search",
|
||||||
|
searchURL: base + "&channel=sb",
|
||||||
|
run: function () {
|
||||||
|
let sb = BrowserSearch.searchBar;
|
||||||
|
sb.focus();
|
||||||
|
sb.value = "foo";
|
||||||
|
registerCleanupFunction(function () {
|
||||||
|
sb.value = "";
|
||||||
|
});
|
||||||
|
EventUtils.synthesizeKey("VK_RETURN", {});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "home page search",
|
||||||
|
searchURL: base + "&channel=np&source=hp",
|
||||||
|
run: function () {
|
||||||
|
// load about:home, but remove the listener first so it doesn't
|
||||||
|
// get in the way
|
||||||
|
gBrowser.removeProgressListener(listener);
|
||||||
|
gBrowser.loadURI("about:home");
|
||||||
|
info("Waiting for about:home load");
|
||||||
|
tab.linkedBrowser.addEventListener("load", function load(event) {
|
||||||
|
if (event.originalTarget != tab.linkedBrowser.contentDocument ||
|
||||||
|
event.target.location.href == "about:blank") {
|
||||||
|
info("skipping spurious load event");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
tab.linkedBrowser.removeEventListener("load", load, true);
|
||||||
|
|
||||||
|
// Observe page setup
|
||||||
|
let doc = gBrowser.contentDocument;
|
||||||
|
let mutationObserver = new MutationObserver(function (mutations) {
|
||||||
|
for (let mutation of mutations) {
|
||||||
|
if (mutation.attributeName == "searchEngineName") {
|
||||||
|
// Re-add the listener, and perform a search
|
||||||
|
gBrowser.addProgressListener(listener);
|
||||||
|
doc.getElementById("searchText").value = "foo";
|
||||||
|
doc.getElementById("searchSubmit").click();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
mutationObserver.observe(doc.documentElement, { attributes: true });
|
||||||
|
}, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
function nextTest() {
|
||||||
|
if (gTests.length) {
|
||||||
|
gCurrTest = gTests.shift();
|
||||||
|
info("Running : " + gCurrTest.name);
|
||||||
|
executeSoon(gCurrTest.run);
|
||||||
|
} else {
|
||||||
|
finish();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let tab = gBrowser.selectedTab = gBrowser.addTab();
|
||||||
|
|
||||||
|
let listener = {
|
||||||
|
onStateChange: function onStateChange(webProgress, req, flags, status) {
|
||||||
|
info("onStateChange");
|
||||||
|
// Only care about top-level document starts
|
||||||
|
let docStart = Ci.nsIWebProgressListener.STATE_IS_DOCUMENT |
|
||||||
|
Ci.nsIWebProgressListener.STATE_START;
|
||||||
|
if (!(flags & docStart) || !webProgress.isTopLevel)
|
||||||
|
return;
|
||||||
|
|
||||||
|
info("received document start");
|
||||||
|
|
||||||
|
ok(req instanceof Ci.nsIChannel, "req is a channel");
|
||||||
|
is(req.originalURI.spec, gCurrTest.searchURL, "search URL was loaded");
|
||||||
|
info("Actual URI: " + req.URI.spec);
|
||||||
|
|
||||||
|
req.cancel(Components.results.NS_ERROR_FAILURE);
|
||||||
|
|
||||||
|
executeSoon(nextTest);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
registerCleanupFunction(function () {
|
||||||
|
gBrowser.removeProgressListener(listener);
|
||||||
|
gBrowser.removeTab(tab);
|
||||||
|
});
|
||||||
|
|
||||||
|
tab.linkedBrowser.addEventListener("load", function load() {
|
||||||
|
tab.linkedBrowser.removeEventListener("load", load, true);
|
||||||
|
gBrowser.addProgressListener(listener);
|
||||||
|
nextTest();
|
||||||
|
}, true);
|
||||||
|
}
|
|
@ -25,6 +25,7 @@
|
||||||
#endif
|
#endif
|
||||||
<MozParam name="channel" condition="purpose" purpose="contextmenu" value="rcs"/>
|
<MozParam name="channel" condition="purpose" purpose="contextmenu" value="rcs"/>
|
||||||
<MozParam name="channel" condition="purpose" purpose="keyword" value="fflb"/>
|
<MozParam name="channel" condition="purpose" purpose="keyword" value="fflb"/>
|
||||||
|
<MozParam name="channel" condition="purpose" purpose="searchbar" value="sb"/>
|
||||||
<MozParam name="channel" condition="purpose" purpose="homepage" value="np"/>
|
<MozParam name="channel" condition="purpose" purpose="homepage" value="np"/>
|
||||||
<MozParam name="source" condition="purpose" purpose="homepage" value="hp"/>
|
<MozParam name="source" condition="purpose" purpose="homepage" value="hp"/>
|
||||||
</Url>
|
</Url>
|
||||||
|
|
|
@ -9,7 +9,7 @@
|
||||||
* link parameter/model object expected to have a .url property, and optionally .title
|
* link parameter/model object expected to have a .url property, and optionally .title
|
||||||
*/
|
*/
|
||||||
function Site(aLink) {
|
function Site(aLink) {
|
||||||
if(!aLink.url) {
|
if (!aLink.url) {
|
||||||
throw Cr.NS_ERROR_INVALID_ARG;
|
throw Cr.NS_ERROR_INVALID_ARG;
|
||||||
}
|
}
|
||||||
this._link = aLink;
|
this._link = aLink;
|
||||||
|
@ -64,7 +64,7 @@ Site.prototype = {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// is binding already applied?
|
// is binding already applied?
|
||||||
if (aNode.refresh) {
|
if ('refresh' in aNode) {
|
||||||
// just update it
|
// just update it
|
||||||
aNode.refresh();
|
aNode.refresh();
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -27,7 +27,7 @@
|
||||||
<field name="controller">null</field>
|
<field name="controller">null</field>
|
||||||
|
|
||||||
<!-- collection of child items excluding empty tiles -->
|
<!-- collection of child items excluding empty tiles -->
|
||||||
<property name="items" readonly="true" onget="return this.querySelectorAll('richgriditem');"/>
|
<property name="items" readonly="true" onget="return this.querySelectorAll('richgriditem[value]');"/>
|
||||||
<property name="itemCount" readonly="true" onget="return this.items.length;"/>
|
<property name="itemCount" readonly="true" onget="return this.items.length;"/>
|
||||||
|
|
||||||
<!-- nsIDOMXULMultiSelectControlElement (not fully implemented) -->
|
<!-- nsIDOMXULMultiSelectControlElement (not fully implemented) -->
|
||||||
|
@ -96,7 +96,7 @@
|
||||||
<parameter name="aEvent"/>
|
<parameter name="aEvent"/>
|
||||||
<body>
|
<body>
|
||||||
<![CDATA[
|
<![CDATA[
|
||||||
if(!this.isBound)
|
if (!this.isBound)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if ("single" == this.getAttribute("seltype")) {
|
if ("single" == this.getAttribute("seltype")) {
|
||||||
|
@ -116,7 +116,7 @@
|
||||||
<parameter name="aEvent"/>
|
<parameter name="aEvent"/>
|
||||||
<body>
|
<body>
|
||||||
<![CDATA[
|
<![CDATA[
|
||||||
if(!this.isBound || this.suppressOnSelect)
|
if (!this.isBound || this.suppressOnSelect)
|
||||||
return;
|
return;
|
||||||
// we'll republish this as a selectionchange event on the grid
|
// we'll republish this as a selectionchange event on the grid
|
||||||
aEvent.stopPropagation();
|
aEvent.stopPropagation();
|
||||||
|
@ -175,7 +175,7 @@
|
||||||
<property name="selectedItems">
|
<property name="selectedItems">
|
||||||
<getter>
|
<getter>
|
||||||
<![CDATA[
|
<![CDATA[
|
||||||
return this.querySelectorAll("richgriditem[selected]");
|
return this.querySelectorAll("richgriditem[value][selected]");
|
||||||
]]>
|
]]>
|
||||||
</getter>
|
</getter>
|
||||||
</property>
|
</property>
|
||||||
|
@ -204,28 +204,105 @@
|
||||||
<parameter name="aSkipArrange"/>
|
<parameter name="aSkipArrange"/>
|
||||||
<body>
|
<body>
|
||||||
<![CDATA[
|
<![CDATA[
|
||||||
let addition = this._createItemElement(aLabel, aValue);
|
let item = this.nextSlot();
|
||||||
this.appendChild(addition);
|
item.setAttribute("value", aValue);
|
||||||
|
item.setAttribute("label", aLabel);
|
||||||
|
|
||||||
if (!aSkipArrange)
|
if (!aSkipArrange)
|
||||||
this.arrangeItems();
|
this.arrangeItems();
|
||||||
return addition;
|
return item;
|
||||||
]]>
|
]]>
|
||||||
</body>
|
</body>
|
||||||
</method>
|
</method>
|
||||||
|
|
||||||
|
<method name="_slotValues">
|
||||||
|
<body><![CDATA[
|
||||||
|
return Array.map(this.children, (cnode) => cnode.getAttribute("value"));
|
||||||
|
]]></body>
|
||||||
|
</method>
|
||||||
|
|
||||||
|
<property name="minSlots" readonly="true"
|
||||||
|
onget="return this.getAttribute('minSlots') || 3;"/>
|
||||||
|
|
||||||
<method name="clearAll">
|
<method name="clearAll">
|
||||||
<parameter name="aSkipArrange"/>
|
<parameter name="aSkipArrange"/>
|
||||||
<body>
|
<body>
|
||||||
<![CDATA[
|
<![CDATA[
|
||||||
while (this.firstChild) {
|
const ELEMENT_NODE_TYPE = Components.interfaces.nsIDOMNode.ELEMENT_NODE;
|
||||||
this.removeChild(this.firstChild);
|
let slotCount = this.minSlots;
|
||||||
|
let childIndex = 0;
|
||||||
|
let child = this.firstChild;
|
||||||
|
while (child) {
|
||||||
|
// remove excess elements and non-element nodes
|
||||||
|
if (child.nodeType !== ELEMENT_NODE_TYPE || childIndex+1 > slotCount) {
|
||||||
|
let orphanNode = child;
|
||||||
|
child = orphanNode.nextSibling;
|
||||||
|
this.removeChild(orphanNode);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (child.hasAttribute("value")) {
|
||||||
|
this._releaseSlot(child);
|
||||||
|
}
|
||||||
|
child = child.nextSibling;
|
||||||
|
childIndex++;
|
||||||
}
|
}
|
||||||
|
// create our quota of item slots
|
||||||
|
for (let count = this.childElementCount; count < slotCount; count++) {
|
||||||
|
this.appendChild( this._createItemElement() );
|
||||||
|
}
|
||||||
|
|
||||||
if (!aSkipArrange)
|
if (!aSkipArrange)
|
||||||
this.arrangeItems();
|
this.arrangeItems();
|
||||||
]]>
|
]]>
|
||||||
</body>
|
</body>
|
||||||
</method>
|
</method>
|
||||||
|
|
||||||
|
<method name="_slotAt">
|
||||||
|
<parameter name="anIndex"/>
|
||||||
|
<body>
|
||||||
|
<![CDATA[
|
||||||
|
// backfill with new slots as necessary
|
||||||
|
let count = Math.max(1+anIndex, this.minSlots) - this.childElementCount;
|
||||||
|
for (; count > 0; count--) {
|
||||||
|
this.appendChild( this._createItemElement() );
|
||||||
|
}
|
||||||
|
return this.children[anIndex];
|
||||||
|
]]>
|
||||||
|
</body>
|
||||||
|
</method>
|
||||||
|
|
||||||
|
<method name="nextSlot">
|
||||||
|
<body>
|
||||||
|
<![CDATA[
|
||||||
|
if (!this.itemCount) {
|
||||||
|
return this._slotAt(0);
|
||||||
|
}
|
||||||
|
let lastItem = this.items[this.itemCount-1];
|
||||||
|
let nextIndex = 1 + Array.indexOf(this.children, lastItem);
|
||||||
|
return this._slotAt(nextIndex);
|
||||||
|
]]>
|
||||||
|
</body>
|
||||||
|
</method>
|
||||||
|
|
||||||
|
<method name="_releaseSlot">
|
||||||
|
<parameter name="anItem"/>
|
||||||
|
<body>
|
||||||
|
<![CDATA[
|
||||||
|
// Flush out data and state attributes so we can recycle this slot/element
|
||||||
|
let exclude = { value: 1, tiletype: 1 };
|
||||||
|
let attrNames = [attr.name for (attr of anItem.attributes)];
|
||||||
|
for (let attrName of attrNames) {
|
||||||
|
if (!(attrName in exclude))
|
||||||
|
anItem.removeAttribute(attrName);
|
||||||
|
}
|
||||||
|
// clear out inline styles
|
||||||
|
anItem.removeAttribute("style");
|
||||||
|
// finally clear the value, which should apply the richgrid-empty-item binding
|
||||||
|
anItem.removeAttribute("value");
|
||||||
|
]]>
|
||||||
|
</body>
|
||||||
|
</method>
|
||||||
|
|
||||||
<method name="insertItemAt">
|
<method name="insertItemAt">
|
||||||
<parameter name="anIndex"/>
|
<parameter name="anIndex"/>
|
||||||
<parameter name="aLabel"/>
|
<parameter name="aLabel"/>
|
||||||
|
@ -233,19 +310,30 @@
|
||||||
<parameter name="aSkipArrange"/>
|
<parameter name="aSkipArrange"/>
|
||||||
<body>
|
<body>
|
||||||
<![CDATA[
|
<![CDATA[
|
||||||
|
anIndex = Math.min(this.itemCount, anIndex);
|
||||||
|
let insertedItem;
|
||||||
let existing = this.getItemAtIndex(anIndex);
|
let existing = this.getItemAtIndex(anIndex);
|
||||||
let addition = this._createItemElement(aLabel, aValue);
|
|
||||||
if (existing) {
|
if (existing) {
|
||||||
this.insertBefore(addition, existing);
|
// use an empty slot if we have one, otherwise insert it
|
||||||
} else {
|
let childIndex = Array.indexOf(this.children, existing);
|
||||||
this.appendChild(addition);
|
if (childIndex > 0 && !this.children[childIndex-1].hasAttribute("value")) {
|
||||||
|
insertedItem = this.children[childIndex-1];
|
||||||
|
} else {
|
||||||
|
insertedItem = this.insertBefore(this._createItemElement(),existing);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
if (!insertedItem) {
|
||||||
|
insertedItem = this._slotAt(anIndex);
|
||||||
|
}
|
||||||
|
insertedItem.setAttribute("value", aValue);
|
||||||
|
insertedItem.setAttribute("label", aLabel);
|
||||||
if (!aSkipArrange)
|
if (!aSkipArrange)
|
||||||
this.arrangeItems();
|
this.arrangeItems();
|
||||||
return addition;
|
return insertedItem;
|
||||||
]]>
|
]]>
|
||||||
</body>
|
</body>
|
||||||
</method>
|
</method>
|
||||||
|
|
||||||
<method name="removeItemAt">
|
<method name="removeItemAt">
|
||||||
<parameter name="anIndex"/>
|
<parameter name="anIndex"/>
|
||||||
<parameter name="aSkipArrange"/>
|
<parameter name="aSkipArrange"/>
|
||||||
|
@ -266,7 +354,13 @@
|
||||||
<![CDATA[
|
<![CDATA[
|
||||||
if (!aItem || Array.indexOf(this.items, aItem) < 0)
|
if (!aItem || Array.indexOf(this.items, aItem) < 0)
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
let removal = this.removeChild(aItem);
|
let removal = this.removeChild(aItem);
|
||||||
|
// replace the slot if necessary
|
||||||
|
if (this.childElementCount < this.minSlots) {
|
||||||
|
this.nextSlot();
|
||||||
|
}
|
||||||
|
|
||||||
if (removal && !aSkipArrange)
|
if (removal && !aSkipArrange)
|
||||||
this.arrangeItems();
|
this.arrangeItems();
|
||||||
|
|
||||||
|
@ -422,6 +516,7 @@
|
||||||
<field name="_scheduledArrangeItemsTimerId">null</field>
|
<field name="_scheduledArrangeItemsTimerId">null</field>
|
||||||
<field name="_scheduledArrangeItemsTries">0</field>
|
<field name="_scheduledArrangeItemsTries">0</field>
|
||||||
<field name="_maxArrangeItemsRetries">5</field>
|
<field name="_maxArrangeItemsRetries">5</field>
|
||||||
|
|
||||||
<method name="_scheduleArrangeItems">
|
<method name="_scheduleArrangeItems">
|
||||||
<parameter name="aTime"/>
|
<parameter name="aTime"/>
|
||||||
<body>
|
<body>
|
||||||
|
@ -453,6 +548,7 @@
|
||||||
|
|
||||||
let itemDims = this._itemSize;
|
let itemDims = this._itemSize;
|
||||||
let containerDims = this._containerSize;
|
let containerDims = this._containerSize;
|
||||||
|
let slotsCount = this.childElementCount;
|
||||||
|
|
||||||
// reset the flags
|
// reset the flags
|
||||||
if (this._scheduledArrangeItemsTimerId) {
|
if (this._scheduledArrangeItemsTimerId) {
|
||||||
|
@ -468,25 +564,25 @@
|
||||||
|
|
||||||
if (this.hasAttribute("vertical")) {
|
if (this.hasAttribute("vertical")) {
|
||||||
this._columnCount = Math.floor(containerDims.width / itemDims.width) || 1;
|
this._columnCount = Math.floor(containerDims.width / itemDims.width) || 1;
|
||||||
this._rowCount = Math.floor(this.itemCount / this._columnCount);
|
this._rowCount = Math.floor(slotsCount / this._columnCount);
|
||||||
} else {
|
} else {
|
||||||
// We favor overflowing horizontally, not vertically (rows then colums)
|
// rows attribute is fixed number of rows
|
||||||
// rows attribute = max rows
|
let maxRows = Math.floor(containerDims.height / itemDims.height);
|
||||||
let maxRowCount = Math.min(this.getAttribute("rows") || Infinity, Math.floor(containerDims.height / itemDims.height));
|
this._rowCount = this.getAttribute("rows") ?
|
||||||
this._rowCount = Math.min(this.itemCount, maxRowCount);
|
// fit indicated rows when possible
|
||||||
|
Math.min(maxRows, this.getAttribute("rows")) :
|
||||||
|
// at least 1 row
|
||||||
|
Math.min(maxRows, slotsCount) || 1;
|
||||||
|
|
||||||
// columns attribute = min cols
|
// columns attribute is min number of cols
|
||||||
this._columnCount = this.itemCount ?
|
this._columnCount = Math.ceil(slotsCount / this._rowCount) || 1;
|
||||||
Math.max(
|
if (this.getAttribute("columns")) {
|
||||||
// at least 1 column when there are items
|
this._columnCount = Math.max(this._columnCount, this.getAttribute("columns"));
|
||||||
this.getAttribute("columns") || 1,
|
}
|
||||||
Math.ceil(this.itemCount / this._rowCount)
|
|
||||||
) : this.getAttribute("columns") || 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// width is typically auto, cap max columns by truncating items collection
|
// width is typically auto, cap max columns by truncating items collection
|
||||||
// or, setting max-width style property with overflow hidden
|
// or, setting max-width style property with overflow hidden
|
||||||
// '0' is an invalid value, just leave the property unset when 0 columns
|
|
||||||
if (this._columnCount) {
|
if (this._columnCount) {
|
||||||
gridStyle.MozColumnCount = this._columnCount;
|
gridStyle.MozColumnCount = this._columnCount;
|
||||||
}
|
}
|
||||||
|
@ -521,6 +617,12 @@
|
||||||
<field name="_xslideHandler"/>
|
<field name="_xslideHandler"/>
|
||||||
<constructor>
|
<constructor>
|
||||||
<![CDATA[
|
<![CDATA[
|
||||||
|
// create our quota of item slots
|
||||||
|
for (let count = this.childElementCount, slotCount = this.minSlots;
|
||||||
|
count < slotCount; count++) {
|
||||||
|
this.appendChild( this._createItemElement() );
|
||||||
|
}
|
||||||
|
|
||||||
if (this.controller && this.controller.gridBoundCallback != undefined)
|
if (this.controller && this.controller.gridBoundCallback != undefined)
|
||||||
this.controller.gridBoundCallback();
|
this.controller.gridBoundCallback();
|
||||||
|
|
||||||
|
@ -605,6 +707,7 @@
|
||||||
]]>
|
]]>
|
||||||
</body>
|
</body>
|
||||||
</method>
|
</method>
|
||||||
|
|
||||||
<method name="_isIndexInBounds">
|
<method name="_isIndexInBounds">
|
||||||
<parameter name="anIndex"/>
|
<parameter name="anIndex"/>
|
||||||
<body>
|
<body>
|
||||||
|
@ -626,7 +729,7 @@
|
||||||
if (aLabel) {
|
if (aLabel) {
|
||||||
item.setAttribute("label", aLabel);
|
item.setAttribute("label", aLabel);
|
||||||
}
|
}
|
||||||
if(this.hasAttribute("tiletype")) {
|
if (this.hasAttribute("tiletype")) {
|
||||||
item.setAttribute("tiletype", this.getAttribute("tiletype"));
|
item.setAttribute("tiletype", this.getAttribute("tiletype"));
|
||||||
}
|
}
|
||||||
return item;
|
return item;
|
||||||
|
@ -704,6 +807,7 @@
|
||||||
aItem.setAttribute("bending", true);
|
aItem.setAttribute("bending", true);
|
||||||
]]></body>
|
]]></body>
|
||||||
</method>
|
</method>
|
||||||
|
|
||||||
<method name="unbendItem">
|
<method name="unbendItem">
|
||||||
<parameter name="aItem"/>
|
<parameter name="aItem"/>
|
||||||
<body><![CDATA[
|
<body><![CDATA[
|
||||||
|
@ -749,7 +853,7 @@
|
||||||
let state = event.crossSlidingState;
|
let state = event.crossSlidingState;
|
||||||
let thresholds = this._xslideHandler.thresholds;
|
let thresholds = this._xslideHandler.thresholds;
|
||||||
let transformValue;
|
let transformValue;
|
||||||
switch(state) {
|
switch (state) {
|
||||||
case "cancelled":
|
case "cancelled":
|
||||||
this.unbendItem(event.target);
|
this.unbendItem(event.target);
|
||||||
event.target.removeAttribute('crosssliding');
|
event.target.removeAttribute('crosssliding');
|
||||||
|
@ -844,7 +948,7 @@
|
||||||
<body>
|
<body>
|
||||||
<![CDATA[
|
<![CDATA[
|
||||||
// Prevent an exception in case binding is not done yet.
|
// Prevent an exception in case binding is not done yet.
|
||||||
if(!this.isBound)
|
if (!this.isBound)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
// Seed the binding properties from bound-node attribute values
|
// Seed the binding properties from bound-node attribute values
|
||||||
|
@ -914,7 +1018,7 @@
|
||||||
|
|
||||||
<method name="refreshBackgroundImage">
|
<method name="refreshBackgroundImage">
|
||||||
<body><![CDATA[
|
<body><![CDATA[
|
||||||
if(!this.isBound)
|
if (!this.isBound)
|
||||||
return;
|
return;
|
||||||
if (this.backgroundImage) {
|
if (this.backgroundImage) {
|
||||||
this._top.style.removeProperty("background-image");
|
this._top.style.removeProperty("background-image");
|
||||||
|
@ -927,7 +1031,7 @@
|
||||||
<property name="contextActions">
|
<property name="contextActions">
|
||||||
<getter>
|
<getter>
|
||||||
<![CDATA[
|
<![CDATA[
|
||||||
if(!this._contextActions) {
|
if (!this._contextActions) {
|
||||||
this._contextActions = new Set();
|
this._contextActions = new Set();
|
||||||
let actionSet = this._contextActions;
|
let actionSet = this._contextActions;
|
||||||
let actions = this.getAttribute("data-contextactions");
|
let actions = this.getAttribute("data-contextactions");
|
||||||
|
@ -959,7 +1063,7 @@
|
||||||
// fires for right-click, long-click and (keyboard) contextmenu input
|
// fires for right-click, long-click and (keyboard) contextmenu input
|
||||||
// toggle the selected state of tiles in a grid
|
// toggle the selected state of tiles in a grid
|
||||||
let gridParent = this.control;
|
let gridParent = this.control;
|
||||||
if(!this.isBound || !gridParent)
|
if (!this.isBound || !gridParent)
|
||||||
return;
|
return;
|
||||||
gridParent.handleItemContextMenu(this, event);
|
gridParent.handleItemContextMenu(this, event);
|
||||||
]]>
|
]]>
|
||||||
|
@ -967,4 +1071,10 @@
|
||||||
</handlers>
|
</handlers>
|
||||||
</binding>
|
</binding>
|
||||||
|
|
||||||
|
<binding id="richgrid-empty-item">
|
||||||
|
<content>
|
||||||
|
<html:div anonid="anon-tile" class="tile-content"></html:div>
|
||||||
|
</content>
|
||||||
|
</binding>
|
||||||
|
|
||||||
</bindings>
|
</bindings>
|
||||||
|
|
|
@ -119,6 +119,9 @@ richgrid {
|
||||||
}
|
}
|
||||||
|
|
||||||
richgriditem {
|
richgriditem {
|
||||||
|
-moz-binding: url("chrome://browser/content/bindings/grid.xml#richgrid-empty-item");
|
||||||
|
}
|
||||||
|
richgriditem[value] {
|
||||||
-moz-binding: url("chrome://browser/content/bindings/grid.xml#richgrid-item");
|
-moz-binding: url("chrome://browser/content/bindings/grid.xml#richgrid-item");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -73,11 +73,11 @@ BookmarksView.prototype = Util.extend(Object.create(View.prototype), {
|
||||||
},
|
},
|
||||||
|
|
||||||
_getItemForBookmarkId: function bv__getItemForBookmark(aBookmarkId) {
|
_getItemForBookmarkId: function bv__getItemForBookmark(aBookmarkId) {
|
||||||
return this._set.querySelector("richgriditem[bookmarkId='" + aBookmarkId + "']");
|
return this._set.querySelector("richgriditem[anonid='" + aBookmarkId + "']");
|
||||||
},
|
},
|
||||||
|
|
||||||
_getBookmarkIdForItem: function bv__getBookmarkForItem(aItem) {
|
_getBookmarkIdForItem: function bv__getBookmarkForItem(aItem) {
|
||||||
return +aItem.getAttribute("bookmarkId");
|
return +aItem.getAttribute("anonid");
|
||||||
},
|
},
|
||||||
|
|
||||||
_updateItemWithAttrs: function dv__updateItemWithAttrs(anItem, aAttrs) {
|
_updateItemWithAttrs: function dv__updateItemWithAttrs(anItem, aAttrs) {
|
||||||
|
@ -142,6 +142,7 @@ BookmarksView.prototype = Util.extend(Object.create(View.prototype), {
|
||||||
this._set.removeItemAt(this._set.itemCount - 1, true);
|
this._set.removeItemAt(this._set.itemCount - 1, true);
|
||||||
}
|
}
|
||||||
this._set.arrangeItems();
|
this._set.arrangeItems();
|
||||||
|
this._set.removeAttribute("fade");
|
||||||
this._inBatch = false;
|
this._inBatch = false;
|
||||||
rootNode.containerOpen = false;
|
rootNode.containerOpen = false;
|
||||||
},
|
},
|
||||||
|
@ -154,7 +155,8 @@ BookmarksView.prototype = Util.extend(Object.create(View.prototype), {
|
||||||
},
|
},
|
||||||
|
|
||||||
clearBookmarks: function bv_clearBookmarks() {
|
clearBookmarks: function bv_clearBookmarks() {
|
||||||
this._set.clearAll();
|
if ('clearAll' in this._set)
|
||||||
|
this._set.clearAll();
|
||||||
},
|
},
|
||||||
|
|
||||||
addBookmark: function bv_addBookmark(aBookmarkId, aPos) {
|
addBookmark: function bv_addBookmark(aBookmarkId, aPos) {
|
||||||
|
@ -162,7 +164,7 @@ BookmarksView.prototype = Util.extend(Object.create(View.prototype), {
|
||||||
let uri = this._bookmarkService.getBookmarkURI(aBookmarkId);
|
let uri = this._bookmarkService.getBookmarkURI(aBookmarkId);
|
||||||
let title = this._bookmarkService.getItemTitle(aBookmarkId) || uri.spec;
|
let title = this._bookmarkService.getItemTitle(aBookmarkId) || uri.spec;
|
||||||
let item = this._set.insertItemAt(aPos || index, title, uri.spec, this._inBatch);
|
let item = this._set.insertItemAt(aPos || index, title, uri.spec, this._inBatch);
|
||||||
item.setAttribute("bookmarkId", aBookmarkId);
|
item.setAttribute("anonid", aBookmarkId);
|
||||||
this._setContextActions(item);
|
this._setContextActions(item);
|
||||||
this._updateFavicon(item, uri);
|
this._updateFavicon(item, uri);
|
||||||
},
|
},
|
||||||
|
@ -198,6 +200,7 @@ BookmarksView.prototype = Util.extend(Object.create(View.prototype), {
|
||||||
let uri = this._bookmarkService.getBookmarkURI(aBookmarkId);
|
let uri = this._bookmarkService.getBookmarkURI(aBookmarkId);
|
||||||
let title = this._bookmarkService.getItemTitle(aBookmarkId) || uri.spec;
|
let title = this._bookmarkService.getItemTitle(aBookmarkId) || uri.spec;
|
||||||
|
|
||||||
|
item.setAttribute("anonid", aBookmarkId);
|
||||||
item.setAttribute("value", uri.spec);
|
item.setAttribute("value", uri.spec);
|
||||||
item.setAttribute("label", title);
|
item.setAttribute("label", title);
|
||||||
|
|
||||||
|
|
|
@ -95,6 +95,7 @@ HistoryView.prototype = Util.extend(Object.create(View.prototype), {
|
||||||
|
|
||||||
rootNode.containerOpen = false;
|
rootNode.containerOpen = false;
|
||||||
this._set.arrangeItems();
|
this._set.arrangeItems();
|
||||||
|
this._set.removeAttribute("fade");
|
||||||
if (this._inBatch > 0)
|
if (this._inBatch > 0)
|
||||||
this._inBatch--;
|
this._inBatch--;
|
||||||
},
|
},
|
||||||
|
@ -130,6 +131,9 @@ HistoryView.prototype = Util.extend(Object.create(View.prototype), {
|
||||||
let tileGroup = this._set;
|
let tileGroup = this._set;
|
||||||
let selectedTiles = tileGroup.selectedItems;
|
let selectedTiles = tileGroup.selectedItems;
|
||||||
|
|
||||||
|
// just arrange the grid once at the end of any action handling
|
||||||
|
this._inBatch = true;
|
||||||
|
|
||||||
switch (aActionName){
|
switch (aActionName){
|
||||||
case "delete":
|
case "delete":
|
||||||
Array.forEach(selectedTiles, function(aNode) {
|
Array.forEach(selectedTiles, function(aNode) {
|
||||||
|
@ -182,9 +186,11 @@ HistoryView.prototype = Util.extend(Object.create(View.prototype), {
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
|
this._inBatch = false;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this._inBatch = false;
|
||||||
// Send refresh event so all view are in sync.
|
// Send refresh event so all view are in sync.
|
||||||
this._sendNeedsRefresh();
|
this._sendNeedsRefresh();
|
||||||
},
|
},
|
||||||
|
@ -254,7 +260,8 @@ HistoryView.prototype = Util.extend(Object.create(View.prototype), {
|
||||||
},
|
},
|
||||||
|
|
||||||
onClearHistory: function() {
|
onClearHistory: function() {
|
||||||
this._set.clearAll();
|
if ('clearAll' in this._set)
|
||||||
|
this._set.clearAll();
|
||||||
},
|
},
|
||||||
|
|
||||||
onPageChanged: function(aURI, aWhat, aValue) {
|
onPageChanged: function(aURI, aWhat, aValue) {
|
||||||
|
@ -264,7 +271,7 @@ HistoryView.prototype = Util.extend(Object.create(View.prototype), {
|
||||||
let currIcon = item.getAttribute("iconURI");
|
let currIcon = item.getAttribute("iconURI");
|
||||||
if (currIcon != aValue) {
|
if (currIcon != aValue) {
|
||||||
item.setAttribute("iconURI", aValue);
|
item.setAttribute("iconURI", aValue);
|
||||||
if("refresh" in item)
|
if ("refresh" in item)
|
||||||
item.refresh();
|
item.refresh();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -93,6 +93,7 @@ RemoteTabsView.prototype = Util.extend(Object.create(View.prototype), {
|
||||||
}
|
}
|
||||||
this.setUIAccessVisible(show);
|
this.setUIAccessVisible(show);
|
||||||
this._set.arrangeItems();
|
this._set.arrangeItems();
|
||||||
|
this._set.removeAttribute("fade");
|
||||||
},
|
},
|
||||||
|
|
||||||
destruct: function destruct() {
|
destruct: function destruct() {
|
||||||
|
|
|
@ -48,7 +48,17 @@
|
||||||
<html:div class="meta-section-title narrow-title" onclick="StartUI.onNarrowTitleClick('start-topsites')">
|
<html:div class="meta-section-title narrow-title" onclick="StartUI.onNarrowTitleClick('start-topsites')">
|
||||||
&narrowTopSitesHeader.label;
|
&narrowTopSitesHeader.label;
|
||||||
</html:div>
|
</html:div>
|
||||||
<richgrid id="start-topsites-grid" observes="bcast_windowState" set-name="topSites" rows="3" columns="3" tiletype="thumbnail" seltype="multiple" flex="1"/>
|
<richgrid id="start-topsites-grid" observes="bcast_windowState" set-name="topSites" rows="3" columns="3" tiletype="thumbnail" seltype="multiple" minSlots="8" fade="true" flex="1">
|
||||||
|
<richgriditem/>
|
||||||
|
<richgriditem/>
|
||||||
|
<richgriditem/>
|
||||||
|
<richgriditem/>
|
||||||
|
<richgriditem/>
|
||||||
|
<richgriditem/>
|
||||||
|
<richgriditem/>
|
||||||
|
<richgriditem/>
|
||||||
|
<richgriditem/>
|
||||||
|
</richgrid>
|
||||||
</vbox>
|
</vbox>
|
||||||
|
|
||||||
<vbox id="start-bookmarks" class="meta-section">
|
<vbox id="start-bookmarks" class="meta-section">
|
||||||
|
@ -56,7 +66,10 @@
|
||||||
<html:div class="meta-section-title narrow-title" onclick="StartUI.onNarrowTitleClick('start-bookmarks')">
|
<html:div class="meta-section-title narrow-title" onclick="StartUI.onNarrowTitleClick('start-bookmarks')">
|
||||||
&narrowBookmarksHeader.label;
|
&narrowBookmarksHeader.label;
|
||||||
</html:div>
|
</html:div>
|
||||||
<richgrid id="start-bookmarks-grid" observes="bcast_windowState" set-name="bookmarks" seltype="multiple" flex="1"/>
|
<richgrid id="start-bookmarks-grid" observes="bcast_windowState" set-name="bookmarks" seltype="multiple" fade="true" flex="1" minSlots="2">
|
||||||
|
<richgriditem/>
|
||||||
|
<richgriditem/>
|
||||||
|
</richgrid>
|
||||||
</vbox>
|
</vbox>
|
||||||
|
|
||||||
<vbox id="start-history" class="meta-section">
|
<vbox id="start-history" class="meta-section">
|
||||||
|
@ -64,7 +77,11 @@
|
||||||
<html:div class="meta-section-title narrow-title" onclick="StartUI.onNarrowTitleClick('start-history')">
|
<html:div class="meta-section-title narrow-title" onclick="StartUI.onNarrowTitleClick('start-history')">
|
||||||
&narrowRecentHistoryHeader.label;
|
&narrowRecentHistoryHeader.label;
|
||||||
</html:div>
|
</html:div>
|
||||||
<richgrid id="start-history-grid" observes="bcast_windowState" set-name="recentHistory" seltype="multiple" flex="1"/>
|
<richgrid id="start-history-grid" observes="bcast_windowState" set-name="recentHistory" seltype="multiple" fade="true" flex="1">
|
||||||
|
<richgriditem/>
|
||||||
|
<richgriditem/>
|
||||||
|
<richgriditem/>
|
||||||
|
</richgrid>
|
||||||
</vbox>
|
</vbox>
|
||||||
|
|
||||||
#ifdef MOZ_SERVICES_SYNC
|
#ifdef MOZ_SERVICES_SYNC
|
||||||
|
@ -73,7 +90,12 @@
|
||||||
<html:div id="snappedRemoteTabsLabel" class="meta-section-title narrow-title" onclick="StartUI.onNarrowTitleClick('start-remotetabs')">
|
<html:div id="snappedRemoteTabsLabel" class="meta-section-title narrow-title" onclick="StartUI.onNarrowTitleClick('start-remotetabs')">
|
||||||
&narrowRemoteTabsHeader.label;
|
&narrowRemoteTabsHeader.label;
|
||||||
</html:div>
|
</html:div>
|
||||||
<richgrid id="start-remotetabs-grid" observes="bcast_windowState" set-name="remoteTabs" seltype="multiple" flex="1"/>
|
<richgrid id="start-remotetabs-grid" observes="bcast_windowState" set-name="remoteTabs" seltype="multiple" fade="true" flex="1">
|
||||||
|
<richgriditem/>
|
||||||
|
<richgriditem/>
|
||||||
|
<richgriditem/>
|
||||||
|
</richgrid>
|
||||||
|
|
||||||
</vbox>
|
</vbox>
|
||||||
#endif
|
#endif
|
||||||
</hbox>
|
</hbox>
|
||||||
|
|
|
@ -158,6 +158,9 @@ TopSitesView.prototype = Util.extend(Object.create(View.prototype), {
|
||||||
},
|
},
|
||||||
|
|
||||||
updateTile: function(aTileNode, aSite, aArrangeGrid) {
|
updateTile: function(aTileNode, aSite, aArrangeGrid) {
|
||||||
|
if (!(aSite && aSite.url)) {
|
||||||
|
throw new Error("Invalid Site object passed to TopSitesView updateTile");
|
||||||
|
}
|
||||||
this._updateFavicon(aTileNode, Util.makeURI(aSite.url));
|
this._updateFavicon(aTileNode, Util.makeURI(aSite.url));
|
||||||
|
|
||||||
Task.spawn(function() {
|
Task.spawn(function() {
|
||||||
|
@ -192,14 +195,11 @@ TopSitesView.prototype = Util.extend(Object.create(View.prototype), {
|
||||||
tileset.clearAll(true);
|
tileset.clearAll(true);
|
||||||
|
|
||||||
for (let site of sites) {
|
for (let site of sites) {
|
||||||
// call to private _createItemElement is a temp measure
|
let slot = tileset.nextSlot();
|
||||||
// we'll eventually just request the next slot
|
this.updateTile(slot, site);
|
||||||
let item = tileset._createItemElement(site.title, site.url);
|
|
||||||
|
|
||||||
this.updateTile(item, site);
|
|
||||||
tileset.appendChild(item);
|
|
||||||
}
|
}
|
||||||
tileset.arrangeItems();
|
tileset.arrangeItems();
|
||||||
|
tileset.removeAttribute("fade");
|
||||||
this.isUpdating = false;
|
this.isUpdating = false;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -244,7 +244,7 @@ TopSitesView.prototype = Util.extend(Object.create(View.prototype), {
|
||||||
|
|
||||||
// nsIObservers
|
// nsIObservers
|
||||||
observe: function (aSubject, aTopic, aState) {
|
observe: function (aSubject, aTopic, aState) {
|
||||||
switch(aTopic) {
|
switch (aTopic) {
|
||||||
case "Metro:RefreshTopsiteThumbnail":
|
case "Metro:RefreshTopsiteThumbnail":
|
||||||
this.forceReloadOfThumbnail(aState);
|
this.forceReloadOfThumbnail(aState);
|
||||||
break;
|
break;
|
||||||
|
@ -269,7 +269,8 @@ TopSitesView.prototype = Util.extend(Object.create(View.prototype), {
|
||||||
},
|
},
|
||||||
|
|
||||||
onClearHistory: function() {
|
onClearHistory: function() {
|
||||||
this._set.clearAll();
|
if ('clearAll' in this._set)
|
||||||
|
this._set.clearAll();
|
||||||
},
|
},
|
||||||
|
|
||||||
onPageChanged: function(aURI, aWhat, aValue) {
|
onPageChanged: function(aURI, aWhat, aValue) {
|
||||||
|
|
|
@ -67,7 +67,7 @@ gTests.push({
|
||||||
|
|
||||||
ok(!item, "Item not in grid");
|
ok(!item, "Item not in grid");
|
||||||
ok(!gStartView._pinHelper.isPinned(uriFromIndex(2)), "Item unpinned");
|
ok(!gStartView._pinHelper.isPinned(uriFromIndex(2)), "Item unpinned");
|
||||||
ok(gStartView._set.itemCount === gStartView._limit, "Grid repopulated");
|
is(gStartView._set.itemCount, gStartView._limit, "Grid repopulated");
|
||||||
|
|
||||||
// --------- unpin multiple items
|
// --------- unpin multiple items
|
||||||
|
|
||||||
|
@ -124,7 +124,7 @@ gTests.push({
|
||||||
item = gStartView._set.getItemsByUrl(uriFromIndex(2))[0];
|
item = gStartView._set.getItemsByUrl(uriFromIndex(2))[0];
|
||||||
|
|
||||||
ok(!item, "Item not in grid");
|
ok(!item, "Item not in grid");
|
||||||
ok(HistoryTestHelper._nodes[uriFromIndex(2)], "Item not deleted yet");
|
ok(HistoryTestHelper._nodes[uriFromIndex(2)], "Item not actually deleted yet");
|
||||||
ok(!restoreButton.hidden, "Restore button is visible.");
|
ok(!restoreButton.hidden, "Restore button is visible.");
|
||||||
ok(gStartView._set.itemCount === gStartView._limit, "Grid repopulated");
|
ok(gStartView._set.itemCount === gStartView._limit, "Grid repopulated");
|
||||||
|
|
||||||
|
@ -150,9 +150,13 @@ gTests.push({
|
||||||
ok(!deleteButton.hidden, "Delete button is visible.");
|
ok(!deleteButton.hidden, "Delete button is visible.");
|
||||||
|
|
||||||
let promise = waitForCondition(() => !restoreButton.hidden);
|
let promise = waitForCondition(() => !restoreButton.hidden);
|
||||||
|
let populateGridSpy = spyOnMethod(gStartView, "populateGrid");
|
||||||
|
|
||||||
EventUtils.synthesizeMouse(deleteButton, 10, 10, {}, window);
|
EventUtils.synthesizeMouse(deleteButton, 10, 10, {}, window);
|
||||||
yield promise;
|
yield promise;
|
||||||
|
|
||||||
|
is(populateGridSpy.callCount, 1, "populateGrid was called in response to the deleting a tile");
|
||||||
|
|
||||||
item = gStartView._set.getItemsByUrl(uriFromIndex(2))[0];
|
item = gStartView._set.getItemsByUrl(uriFromIndex(2))[0];
|
||||||
|
|
||||||
ok(!item, "Item not in grid");
|
ok(!item, "Item not in grid");
|
||||||
|
@ -163,11 +167,14 @@ gTests.push({
|
||||||
Elements.contextappbar.dismiss();
|
Elements.contextappbar.dismiss();
|
||||||
yield promise;
|
yield promise;
|
||||||
|
|
||||||
|
is(populateGridSpy.callCount, 1, "populateGrid not called when a removed item is actually deleted");
|
||||||
|
populateGridSpy.restore();
|
||||||
|
|
||||||
item = gStartView._set.getItemsByUrl(uriFromIndex(2))[0];
|
item = gStartView._set.getItemsByUrl(uriFromIndex(2))[0];
|
||||||
|
|
||||||
ok(!item, "Item not in grid");
|
ok(!item, "Item not in grid");
|
||||||
ok(!HistoryTestHelper._nodes[uriFromIndex(2)], "Item RIP");
|
ok(!HistoryTestHelper._nodes[uriFromIndex(2)], "Item RIP");
|
||||||
ok(gStartView._set.itemCount === gStartView._limit, "Grid repopulated");
|
is(gStartView._set.itemCount, gStartView._limit, "Grid repopulated");
|
||||||
|
|
||||||
// --------- delete multiple items and restore
|
// --------- delete multiple items and restore
|
||||||
|
|
||||||
|
|
|
@ -17,6 +17,9 @@
|
||||||
<richgrid id="grid_layout" seltype="single" flex="1">
|
<richgrid id="grid_layout" seltype="single" flex="1">
|
||||||
</richgrid>
|
</richgrid>
|
||||||
</vbox>
|
</vbox>
|
||||||
|
<vbox>
|
||||||
|
<richgrid id="slots_grid" seltype="single" minSlots="6" flex="1"/>
|
||||||
|
</vbox>
|
||||||
<vbox style="height:600px">
|
<vbox style="height:600px">
|
||||||
<hbox>
|
<hbox>
|
||||||
<richgrid id="clearGrid" seltype="single" flex="1" rows="2">
|
<richgrid id="clearGrid" seltype="single" flex="1" rows="2">
|
||||||
|
@ -26,7 +29,7 @@
|
||||||
</richgrid>
|
</richgrid>
|
||||||
</hbox>
|
</hbox>
|
||||||
<hbox>
|
<hbox>
|
||||||
<richgrid id="emptyGrid" seltype="single" flex="1" rows="2">
|
<richgrid id="emptyGrid" seltype="single" flex="1" rows="2" minSlots="6">
|
||||||
</richgrid>
|
</richgrid>
|
||||||
</hbox>
|
</hbox>
|
||||||
<hbox>
|
<hbox>
|
||||||
|
|
|
@ -9,6 +9,14 @@ function test() {
|
||||||
}).then(runTests);
|
}).then(runTests);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function _checkIfBoundByRichGrid_Item(expected, node, idx) {
|
||||||
|
let binding = node.ownerDocument.defaultView.getComputedStyle(node).MozBinding;
|
||||||
|
let result = ('url("chrome://browser/content/bindings/grid.xml#richgrid-item")' == binding);
|
||||||
|
return (result == expected);
|
||||||
|
}
|
||||||
|
let isBoundByRichGrid_Item = _checkIfBoundByRichGrid_Item.bind(this, true);
|
||||||
|
let isNotBoundByRichGrid_Item = _checkIfBoundByRichGrid_Item.bind(this, false);
|
||||||
|
|
||||||
gTests.push({
|
gTests.push({
|
||||||
desc: "richgrid binding is applied",
|
desc: "richgrid binding is applied",
|
||||||
run: function() {
|
run: function() {
|
||||||
|
@ -17,9 +25,9 @@ gTests.push({
|
||||||
let grid = doc.querySelector("#grid1");
|
let grid = doc.querySelector("#grid1");
|
||||||
ok(grid, "#grid1 is found");
|
ok(grid, "#grid1 is found");
|
||||||
is(typeof grid.clearSelection, "function", "#grid1 has the binding applied");
|
is(typeof grid.clearSelection, "function", "#grid1 has the binding applied");
|
||||||
|
|
||||||
is(grid.items.length, 2, "#grid1 has a 2 items");
|
is(grid.items.length, 2, "#grid1 has a 2 items");
|
||||||
is(grid.items[0].control, grid, "#grid1 item's control points back at #grid1'");
|
is(grid.items[0].control, grid, "#grid1 item's control points back at #grid1'");
|
||||||
|
ok(Array.every(grid.items, isBoundByRichGrid_Item), "All items are bound by richgrid-item");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -125,19 +133,28 @@ gTests.push({
|
||||||
gTests.push({
|
gTests.push({
|
||||||
desc: "empty grid",
|
desc: "empty grid",
|
||||||
run: function() {
|
run: function() {
|
||||||
|
// XXX grids have minSlots and may not be ever truly empty
|
||||||
|
|
||||||
let grid = doc.getElementById("emptyGrid");
|
let grid = doc.getElementById("emptyGrid");
|
||||||
grid.arrangeItems();
|
grid.arrangeItems();
|
||||||
yield waitForCondition(() => !grid.isArranging);
|
yield waitForCondition(() => !grid.isArranging);
|
||||||
|
|
||||||
// grid has rows=2 but 0 items
|
// grid has 2 rows, 6 slots, 0 items
|
||||||
ok(grid.isBound, "binding was applied");
|
ok(grid.isBound, "binding was applied");
|
||||||
is(grid.itemCount, 0, "empty grid has 0 items");
|
is(grid.itemCount, 0, "empty grid has 0 items");
|
||||||
is(grid.rowCount, 0, "empty grid has 0 rows");
|
// minSlots attr. creates unpopulated slots
|
||||||
is(grid.columnCount, 0, "empty grid has 0 cols");
|
is(grid.rowCount, grid.getAttribute("rows"), "empty grid with rows-attribute has that number of rows");
|
||||||
|
is(grid.columnCount, 3, "empty grid has expected number of columns");
|
||||||
|
|
||||||
let columnsNode = grid._grid;
|
// remove rows attribute and allow space for the grid to find its own height
|
||||||
let cStyle = doc.defaultView.getComputedStyle(columnsNode);
|
// for its number of slots
|
||||||
is(cStyle.getPropertyValue("-moz-column-count"), "auto", "empty grid has -moz-column-count: auto");
|
grid.removeAttribute("rows");
|
||||||
|
grid.parentNode.style.height = 20+(grid.tileHeight*grid.minSlots)+"px";
|
||||||
|
|
||||||
|
grid.arrangeItems();
|
||||||
|
yield waitForCondition(() => !grid.isArranging);
|
||||||
|
is(grid.rowCount, grid.minSlots, "empty grid has this.minSlots rows");
|
||||||
|
is(grid.columnCount, 1, "empty grid has 1 column");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -211,16 +228,25 @@ gTests.push({
|
||||||
is(typeof grid.insertItemAt, "function", "insertItemAt is a function on the grid");
|
is(typeof grid.insertItemAt, "function", "insertItemAt is a function on the grid");
|
||||||
|
|
||||||
let arrangeStub = stubMethod(grid, "arrangeItems");
|
let arrangeStub = stubMethod(grid, "arrangeItems");
|
||||||
let insertedItem = grid.insertItemAt(1, "inserted item", "http://example.com/inserted");
|
let insertedAt0 = grid.insertItemAt(0, "inserted item 0", "http://example.com/inserted0");
|
||||||
|
let insertedAt00 = grid.insertItemAt(0, "inserted item 00", "http://example.com/inserted00");
|
||||||
|
|
||||||
ok(insertedItem, "insertItemAt gives back an item");
|
ok(insertedAt0 && insertedAt00, "insertItemAt gives back an item");
|
||||||
is(grid.items[1], insertedItem, "item is inserted at the correct index");
|
|
||||||
is(insertedItem.getAttribute("label"), "inserted item", "insertItemAt creates item with the correct label");
|
|
||||||
is(insertedItem.getAttribute("value"), "http://example.com/inserted", "insertItemAt creates item with the correct url value");
|
|
||||||
is(grid.items[2].getAttribute("id"), "grid3_item2", "following item ends up at the correct index");
|
|
||||||
is(grid.itemCount, 3, "itemCount is incremented when we insertItemAt");
|
|
||||||
|
|
||||||
is(arrangeStub.callCount, 1, "arrangeItems is called when we insertItemAt");
|
is(insertedAt0.getAttribute("label"), "inserted item 0", "insertItemAt creates item with the correct label");
|
||||||
|
is(insertedAt0.getAttribute("value"), "http://example.com/inserted0", "insertItemAt creates item with the correct url value");
|
||||||
|
|
||||||
|
is(grid.items[0], insertedAt00, "item is inserted at the correct index");
|
||||||
|
is(grid.children[0], insertedAt00, "first item occupies the first slot");
|
||||||
|
is(grid.items[1], insertedAt0, "item is inserted at the correct index");
|
||||||
|
is(grid.children[1], insertedAt0, "next item occupies the next slot");
|
||||||
|
|
||||||
|
is(grid.items[2].getAttribute("label"), "First item", "Old first item is now at index 2");
|
||||||
|
is(grid.items[3].getAttribute("label"), "2nd item", "Old 2nd item is now at index 3");
|
||||||
|
|
||||||
|
is(grid.itemCount, 4, "itemCount is incremented when we insertItemAt");
|
||||||
|
|
||||||
|
is(arrangeStub.callCount, 2, "arrangeItems is called when we insertItemAt");
|
||||||
arrangeStub.restore();
|
arrangeStub.restore();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -417,3 +443,172 @@ gTests.push({
|
||||||
doc.defaultView.removeEventListener("selectionchange", handler, false);
|
doc.defaultView.removeEventListener("selectionchange", handler, false);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
function gridSlotsSetup() {
|
||||||
|
let grid = this.grid = doc.createElement("richgrid");
|
||||||
|
grid.setAttribute("minSlots", 6);
|
||||||
|
doc.documentElement.appendChild(grid);
|
||||||
|
is(grid.ownerDocument, doc, "created grid in the expected document");
|
||||||
|
}
|
||||||
|
function gridSlotsTearDown() {
|
||||||
|
this.grid && this.grid.parentNode.removeChild(this.grid);
|
||||||
|
}
|
||||||
|
|
||||||
|
gTests.push({
|
||||||
|
desc: "richgrid slots init",
|
||||||
|
setUp: gridSlotsSetup,
|
||||||
|
run: function() {
|
||||||
|
let grid = this.grid;
|
||||||
|
// grid is initially populated with empty slots matching the minSlots attribute
|
||||||
|
is(grid.children.length, 6, "minSlots slots are created");
|
||||||
|
is(grid.itemCount, 0, "slots do not count towards itemCount");
|
||||||
|
ok(Array.every(grid.children, (node) => node.nodeName == 'richgriditem'), "slots have nodeName richgriditem");
|
||||||
|
ok(Array.every(grid.children, isNotBoundByRichGrid_Item), "slots aren't bound by the richgrid-item binding");
|
||||||
|
},
|
||||||
|
tearDown: gridSlotsTearDown
|
||||||
|
});
|
||||||
|
|
||||||
|
gTests.push({
|
||||||
|
desc: "richgrid using slots for items",
|
||||||
|
setUp: gridSlotsSetup, // creates grid with minSlots = num. slots = 6
|
||||||
|
run: function() {
|
||||||
|
let grid = this.grid;
|
||||||
|
let numSlots = grid.getAttribute("minSlots");
|
||||||
|
is(grid.children.length, numSlots);
|
||||||
|
// adding items occupies those slots
|
||||||
|
for (let idx of [0,1,2,3,4,5,6]) {
|
||||||
|
let slot = grid.children[idx];
|
||||||
|
let item = grid.appendItem("item "+idx, "about:mozilla");
|
||||||
|
if (idx < numSlots) {
|
||||||
|
is(grid.children.length, numSlots);
|
||||||
|
is(slot, item, "The same node is reused when an item is assigned to a slot");
|
||||||
|
} else {
|
||||||
|
is(typeof slot, 'undefined');
|
||||||
|
ok(item);
|
||||||
|
is(grid.children.length, grid.itemCount);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
tearDown: gridSlotsTearDown
|
||||||
|
});
|
||||||
|
|
||||||
|
gTests.push({
|
||||||
|
desc: "richgrid assign and release slots",
|
||||||
|
setUp: function(){
|
||||||
|
info("assign and release slots setUp");
|
||||||
|
this.grid = doc.getElementById("slots_grid");
|
||||||
|
this.grid.scrollIntoView();
|
||||||
|
let rect = this.grid.getBoundingClientRect();
|
||||||
|
info("slots grid at top: " + rect.top + ", window.pageYOffset: " + doc.defaultView.pageYOffset);
|
||||||
|
},
|
||||||
|
run: function() {
|
||||||
|
let grid = this.grid;
|
||||||
|
// start with 5 of 6 slots occupied
|
||||||
|
for (let idx of [0,1,2,3,4]) {
|
||||||
|
let item = grid.appendItem("item "+idx, "about:mozilla");
|
||||||
|
item.setAttribute("id", "test_item_"+idx);
|
||||||
|
}
|
||||||
|
is(grid.itemCount, 5);
|
||||||
|
is(grid.children.length, 6); // see setup, where we init with 6 slots
|
||||||
|
let firstItem = grid.items[0];
|
||||||
|
|
||||||
|
ok(firstItem.ownerDocument, "item has ownerDocument");
|
||||||
|
is(doc, firstItem.ownerDocument, "item's ownerDocument is the document we expect");
|
||||||
|
|
||||||
|
is(firstItem, grid.children[0], "Item and assigned slot are one and the same");
|
||||||
|
is(firstItem.control, grid, "Item is bound and its .control points back at the grid");
|
||||||
|
|
||||||
|
// before releasing, the grid should be nofified of clicks on that slot
|
||||||
|
let testWindow = grid.ownerDocument.defaultView;
|
||||||
|
|
||||||
|
let rect = firstItem.getBoundingClientRect();
|
||||||
|
{
|
||||||
|
let handleStub = stubMethod(grid, 'handleItemClick');
|
||||||
|
// send click to item and wait for next tick;
|
||||||
|
sendElementTap(testWindow, firstItem);
|
||||||
|
yield waitForMs(0);
|
||||||
|
|
||||||
|
is(handleStub.callCount, 1, "handleItemClick was called when we clicked an item");
|
||||||
|
handleStub.restore();
|
||||||
|
}
|
||||||
|
// _releaseSlot is semi-private, we don't expect consumers of the binding to call it
|
||||||
|
// but want to be sure it does what we expect
|
||||||
|
grid._releaseSlot(firstItem);
|
||||||
|
|
||||||
|
is(grid.itemCount, 4, "Releasing a slot gives us one less item");
|
||||||
|
is(firstItem, grid.children[0],"Released slot is still the same node we started with");
|
||||||
|
|
||||||
|
// after releasing, the grid should NOT be nofified of clicks
|
||||||
|
{
|
||||||
|
let handleStub = stubMethod(grid, 'handleItemClick');
|
||||||
|
// send click to item and wait for next tick;
|
||||||
|
sendElementTap(testWindow, firstItem);
|
||||||
|
yield waitForMs(0);
|
||||||
|
|
||||||
|
is(handleStub.callCount, 0, "handleItemClick was NOT called when we clicked a released slot");
|
||||||
|
handleStub.restore();
|
||||||
|
}
|
||||||
|
|
||||||
|
ok(!firstItem.mozMatchesSelector("richgriditem[value]"), "Released slot doesn't match binding selector");
|
||||||
|
ok(isNotBoundByRichGrid_Item(firstItem), "Released slot is no longer bound");
|
||||||
|
|
||||||
|
waitForCondition(() => isNotBoundByRichGrid_Item(firstItem));
|
||||||
|
ok(true, "Slot eventually gets unbound");
|
||||||
|
is(firstItem, grid.children[0], "Released slot is still at expected index in children collection");
|
||||||
|
|
||||||
|
let firstSlot = grid.children[0];
|
||||||
|
firstItem = grid.insertItemAt(0, "New item 0", "about:blank");
|
||||||
|
ok(firstItem == grid.items[0], "insertItemAt 0 creates item at expected index");
|
||||||
|
ok(firstItem == firstSlot, "insertItemAt occupies the released slot with the new item");
|
||||||
|
is(grid.itemCount, 5);
|
||||||
|
is(grid.children.length, 6);
|
||||||
|
is(firstItem.control, grid,"Item is bound and its .control points back at the grid");
|
||||||
|
|
||||||
|
let nextSlotIndex = grid.itemCount;
|
||||||
|
let lastItem = grid.insertItemAt(9, "New item 9", "about:blank");
|
||||||
|
// Check we don't create sparse collection of items
|
||||||
|
is(lastItem, grid.children[nextSlotIndex], "Item is appended at the next index when an out of bounds index is provided");
|
||||||
|
is(grid.children.length, 6);
|
||||||
|
is(grid.itemCount, 6);
|
||||||
|
|
||||||
|
grid.appendItem("one more", "about:blank");
|
||||||
|
is(grid.children.length, 7);
|
||||||
|
is(grid.itemCount, 7);
|
||||||
|
|
||||||
|
// clearAll results in slots being emptied
|
||||||
|
grid.clearAll();
|
||||||
|
is(grid.children.length, 6, "Extra slots are trimmed when we clearAll");
|
||||||
|
ok(!Array.some(grid.children, (node) => node.hasAttribute("value")), "All slots have no value attribute after clearAll")
|
||||||
|
},
|
||||||
|
tearDown: gridSlotsTearDown
|
||||||
|
});
|
||||||
|
|
||||||
|
gTests.push({
|
||||||
|
desc: "richgrid slot management",
|
||||||
|
setUp: gridSlotsSetup,
|
||||||
|
run: function() {
|
||||||
|
let grid = this.grid;
|
||||||
|
// populate grid with some items
|
||||||
|
let numSlots = grid.getAttribute("minSlots");
|
||||||
|
for (let idx of [0,1,2,3,4,5]) {
|
||||||
|
let item = grid.appendItem("item "+idx, "about:mozilla");
|
||||||
|
}
|
||||||
|
|
||||||
|
is(grid.itemCount, 6, "Grid setup with 6 items");
|
||||||
|
is(grid.children.length, 6, "Full grid has the expected number of slots");
|
||||||
|
|
||||||
|
// removing an item creates a replacement slot *on the end of the stack*
|
||||||
|
let item = grid.removeItemAt(0);
|
||||||
|
is(item.getAttribute("label"), "item 0", "removeItemAt gives back the populated node");
|
||||||
|
is(grid.children.length, 6);
|
||||||
|
is(grid.itemCount, 5);
|
||||||
|
is(grid.items[0].getAttribute("label"), "item 1", "removeItemAt removes the node so the nextSibling takes its place");
|
||||||
|
ok(grid.children[5] && !grid.children[5].hasAttribute("value"), "empty slot is added at the end of the existing children");
|
||||||
|
|
||||||
|
let item1 = grid.removeItem(grid.items[0]);
|
||||||
|
is(grid.children.length, 6);
|
||||||
|
is(grid.itemCount, 4);
|
||||||
|
is(grid.items[0].getAttribute("label"), "item 2", "removeItem removes the node so the nextSibling takes its place");
|
||||||
|
},
|
||||||
|
tearDown: gridSlotsTearDown
|
||||||
|
});
|
||||||
|
|
|
@ -275,6 +275,26 @@ richgriditem[bending] > .tile-content {
|
||||||
transform-origin: center center;
|
transform-origin: center center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Empty/unused tiles */
|
||||||
|
richgriditem:not([value]) {
|
||||||
|
visibility: hidden;
|
||||||
|
}
|
||||||
|
richgriditem[tiletype="thumbnail"]:not([value]) {
|
||||||
|
visibility: visible;
|
||||||
|
}
|
||||||
|
richgriditem:not([value]) > .tile-content {
|
||||||
|
padding: 10px 14px;
|
||||||
|
}
|
||||||
|
richgriditem[tiletype="thumbnail"]:not([value]) > .tile-content {
|
||||||
|
box-shadow: 0px 0px 0px 1px rgba(0, 0, 0, 0.05);
|
||||||
|
background-image: url("chrome://browser/skin/images/firefox-watermark.png");
|
||||||
|
background-origin: content-box;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-color: rgba(255,255,255, 0.2);
|
||||||
|
background-position: center center;
|
||||||
|
background-size: @grid_row_height@;
|
||||||
|
}
|
||||||
|
|
||||||
/* Snapped-view variation
|
/* Snapped-view variation
|
||||||
We use the compact, single-column grid treatment for <=320px */
|
We use the compact, single-column grid treatment for <=320px */
|
||||||
|
|
||||||
|
|
|
@ -169,7 +169,7 @@ let AboutHome = {
|
||||||
window.BrowserSearch.recordSearchInHealthReport(data.engineName, "abouthome");
|
window.BrowserSearch.recordSearchInHealthReport(data.engineName, "abouthome");
|
||||||
#endif
|
#endif
|
||||||
// Trigger a search through nsISearchEngine.getSubmission()
|
// Trigger a search through nsISearchEngine.getSubmission()
|
||||||
let submission = Services.search.currentEngine.getSubmission(data.searchTerms);
|
let submission = Services.search.currentEngine.getSubmission(data.searchTerms, null, "homepage");
|
||||||
window.loadURI(submission.uri.spec, null, submission.postData);
|
window.loadURI(submission.uri.spec, null, submission.postData);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,11 +9,7 @@ dir-tests := $(DEPTH)/$(mobile-tests)
|
||||||
ANDROID_APK_NAME := robocop-debug
|
ANDROID_APK_NAME := robocop-debug
|
||||||
|
|
||||||
ANDROID_EXTRA_JARS += \
|
ANDROID_EXTRA_JARS += \
|
||||||
$(srcdir)/robotium-solo-4.2.jar \
|
$(srcdir)/robotium-solo-4.3.jar \
|
||||||
$(NULL)
|
|
||||||
|
|
||||||
ANDROID_RESFILES = \
|
|
||||||
res/values/strings.xml \
|
|
||||||
$(NULL)
|
$(NULL)
|
||||||
|
|
||||||
ANDROID_ASSETS_DIR := $(TESTPATH)/assets
|
ANDROID_ASSETS_DIR := $(TESTPATH)/assets
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
Robocop is a Mozilla project which uses Robotium to test Firefox on Android devices.
|
Robocop is a Mozilla project which uses Robotium to test Firefox on Android devices.
|
||||||
|
|
||||||
Robotium is an open source tool licensed under the Apache 2.0 license and the original
|
Robotium is an open source tool licensed under the Apache 2.0 license and the original
|
||||||
source can be found here:
|
source can be found here:
|
||||||
http://code.google.com/p/robotium/
|
http://code.google.com/p/robotium/
|
||||||
|
|
||||||
We are including robotium-solo-4.2.jar as a binary and are not modifying it in any way
|
We are including robotium-solo-4.3.jar as a binary and are not modifying it in any way
|
||||||
from the original download found at:
|
from the original download found at:
|
||||||
http://code.google.com/p/robotium/
|
http://code.google.com/p/robotium/
|
||||||
|
|
||||||
Firefox for Android developers should read the documentation in
|
Firefox for Android developers should read the documentation in
|
||||||
|
|
|
@ -6,3 +6,6 @@
|
||||||
|
|
||||||
MODULE = 'robocop'
|
MODULE = 'robocop'
|
||||||
|
|
||||||
|
ANDROID_RESFILES = [
|
||||||
|
'res/values/strings.xml',
|
||||||
|
]
|
||||||
|
|
Двоичные данные
build/mobile/robocop/robotium-solo-4.2.jar
Двоичные данные
build/mobile/robocop/robotium-solo-4.2.jar
Двоичный файл не отображается.
Двоичный файл не отображается.
|
@ -20,16 +20,6 @@ JAVAFILES = \
|
||||||
WifiConfiguration.java \
|
WifiConfiguration.java \
|
||||||
$(NULL)
|
$(NULL)
|
||||||
|
|
||||||
ANDROID_RESFILES = \
|
|
||||||
res/drawable/icon.png \
|
|
||||||
res/drawable/ateamlogo.png \
|
|
||||||
res/drawable/ic_stat_first.png \
|
|
||||||
res/drawable/ic_stat_neterror.png \
|
|
||||||
res/drawable/ic_stat_warning.png \
|
|
||||||
res/layout/main.xml \
|
|
||||||
res/values/strings.xml \
|
|
||||||
$(NULL)
|
|
||||||
|
|
||||||
ANDROID_EXTRA_JARS = \
|
ANDROID_EXTRA_JARS = \
|
||||||
$(srcdir)/network-libs/commons-net-2.0.jar \
|
$(srcdir)/network-libs/commons-net-2.0.jar \
|
||||||
$(srcdir)/network-libs/jmdns.jar \
|
$(srcdir)/network-libs/jmdns.jar \
|
||||||
|
|
|
@ -11,14 +11,6 @@ JAVAFILES = \
|
||||||
FileCursor.java \
|
FileCursor.java \
|
||||||
$(NULL)
|
$(NULL)
|
||||||
|
|
||||||
ANDROID_RESFILES = \
|
|
||||||
res/drawable-hdpi/icon.png \
|
|
||||||
res/drawable-ldpi/icon.png \
|
|
||||||
res/drawable-mdpi/icon.png \
|
|
||||||
res/layout/main.xml \
|
|
||||||
res/values/strings.xml \
|
|
||||||
$(NULL)
|
|
||||||
|
|
||||||
include $(topsrcdir)/config/rules.mk
|
include $(topsrcdir)/config/rules.mk
|
||||||
|
|
||||||
tools:: $(ANDROID_APK_NAME).apk
|
tools:: $(ANDROID_APK_NAME).apk
|
||||||
|
|
|
@ -6,3 +6,10 @@
|
||||||
|
|
||||||
MODULE = 'FenCP'
|
MODULE = 'FenCP'
|
||||||
|
|
||||||
|
ANDROID_RESFILES = [
|
||||||
|
'res/drawable-hdpi/icon.png',
|
||||||
|
'res/drawable-ldpi/icon.png',
|
||||||
|
'res/drawable-mdpi/icon.png',
|
||||||
|
'res/layout/main.xml',
|
||||||
|
'res/values/strings.xml',
|
||||||
|
]
|
||||||
|
|
|
@ -11,14 +11,6 @@ JAVAFILES = \
|
||||||
FileCursor.java \
|
FileCursor.java \
|
||||||
$(NULL)
|
$(NULL)
|
||||||
|
|
||||||
ANDROID_RESFILES = \
|
|
||||||
res/drawable-hdpi/icon.png \
|
|
||||||
res/drawable-ldpi/icon.png \
|
|
||||||
res/drawable-mdpi/icon.png \
|
|
||||||
res/layout/main.xml \
|
|
||||||
res/values/strings.xml \
|
|
||||||
$(NULL)
|
|
||||||
|
|
||||||
include $(topsrcdir)/config/rules.mk
|
include $(topsrcdir)/config/rules.mk
|
||||||
|
|
||||||
tools:: $(ANDROID_APK_NAME).apk
|
tools:: $(ANDROID_APK_NAME).apk
|
||||||
|
|
|
@ -6,3 +6,10 @@
|
||||||
|
|
||||||
MODULE = 'FfxCP'
|
MODULE = 'FfxCP'
|
||||||
|
|
||||||
|
ANDROID_RESFILES = [
|
||||||
|
'res/drawable-hdpi/icon.png',
|
||||||
|
'res/drawable-ldpi/icon.png',
|
||||||
|
'res/drawable-mdpi/icon.png',
|
||||||
|
'res/layout/main.xml',
|
||||||
|
'res/values/strings.xml',
|
||||||
|
]
|
||||||
|
|
|
@ -6,3 +6,12 @@
|
||||||
|
|
||||||
MODULE = 'sutAgentAndroid'
|
MODULE = 'sutAgentAndroid'
|
||||||
|
|
||||||
|
ANDROID_RESFILES = [
|
||||||
|
'res/drawable/ateamlogo.png',
|
||||||
|
'res/drawable/ic_stat_first.png',
|
||||||
|
'res/drawable/ic_stat_neterror.png',
|
||||||
|
'res/drawable/ic_stat_warning.png',
|
||||||
|
'res/drawable/icon.png',
|
||||||
|
'res/layout/main.xml',
|
||||||
|
'res/values/strings.xml',
|
||||||
|
]
|
||||||
|
|
|
@ -12,17 +12,6 @@ JAVAFILES = \
|
||||||
WatcherService.java \
|
WatcherService.java \
|
||||||
$(NULL)
|
$(NULL)
|
||||||
|
|
||||||
ANDROID_RESFILES = \
|
|
||||||
res/drawable-hdpi/icon.png \
|
|
||||||
res/drawable-hdpi/ateamlogo.png \
|
|
||||||
res/drawable-ldpi/icon.png \
|
|
||||||
res/drawable-ldpi/ateamlogo.png \
|
|
||||||
res/drawable-mdpi/icon.png \
|
|
||||||
res/drawable-mdpi/ateamlogo.png \
|
|
||||||
res/layout/main.xml \
|
|
||||||
res/values/strings.xml \
|
|
||||||
$(NULL)
|
|
||||||
|
|
||||||
include $(topsrcdir)/config/rules.mk
|
include $(topsrcdir)/config/rules.mk
|
||||||
|
|
||||||
tools:: $(ANDROID_APK_NAME).apk
|
tools:: $(ANDROID_APK_NAME).apk
|
||||||
|
|
|
@ -6,3 +6,13 @@
|
||||||
|
|
||||||
MODULE = 'Watcher'
|
MODULE = 'Watcher'
|
||||||
|
|
||||||
|
ANDROID_RESFILES = [
|
||||||
|
'res/drawable-hdpi/ateamlogo.png',
|
||||||
|
'res/drawable-hdpi/icon.png',
|
||||||
|
'res/drawable-ldpi/ateamlogo.png',
|
||||||
|
'res/drawable-ldpi/icon.png',
|
||||||
|
'res/drawable-mdpi/ateamlogo.png',
|
||||||
|
'res/drawable-mdpi/icon.png',
|
||||||
|
'res/layout/main.xml',
|
||||||
|
'res/values/strings.xml',
|
||||||
|
]
|
||||||
|
|
|
@ -8,6 +8,7 @@
|
||||||
ifndef INCLUDED_JAVA_BUILD_MK #{
|
ifndef INCLUDED_JAVA_BUILD_MK #{
|
||||||
|
|
||||||
ifdef ANDROID_RESFILES #{
|
ifdef ANDROID_RESFILES #{
|
||||||
|
ifndef IGNORE_ANDROID_RESFILES #{
|
||||||
res-dep := .deps-copy-java-res
|
res-dep := .deps-copy-java-res
|
||||||
|
|
||||||
GENERATED_DIRS += res
|
GENERATED_DIRS += res
|
||||||
|
@ -25,6 +26,7 @@ res-dep-preqs := \
|
||||||
$(res-dep): $(res-dep-preqs)
|
$(res-dep): $(res-dep-preqs)
|
||||||
$(call copy_dir,$(srcdir)/res,$(CURDIR)/res)
|
$(call copy_dir,$(srcdir)/res,$(CURDIR)/res)
|
||||||
@$(TOUCH) $@
|
@$(TOUCH) $@
|
||||||
|
endif #} IGNORE_ANDROID_RESFILES
|
||||||
endif #} ANDROID_RESFILES
|
endif #} ANDROID_RESFILES
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -21,6 +21,8 @@ INCLUDED_RULES_MK = 1
|
||||||
# present. If they are, this is a violation of the separation of
|
# present. If they are, this is a violation of the separation of
|
||||||
# responsibility between Makefile.in and mozbuild files.
|
# responsibility between Makefile.in and mozbuild files.
|
||||||
_MOZBUILD_EXTERNAL_VARIABLES := \
|
_MOZBUILD_EXTERNAL_VARIABLES := \
|
||||||
|
ANDROID_GENERATED_RESFILES \
|
||||||
|
ANDROID_RESFILES \
|
||||||
CMMSRCS \
|
CMMSRCS \
|
||||||
CPP_UNIT_TESTS \
|
CPP_UNIT_TESTS \
|
||||||
DIRS \
|
DIRS \
|
||||||
|
|
|
@ -8,6 +8,7 @@
|
||||||
ifndef INCLUDED_JAVA_BUILD_MK #{
|
ifndef INCLUDED_JAVA_BUILD_MK #{
|
||||||
|
|
||||||
ifdef ANDROID_RESFILES #{
|
ifdef ANDROID_RESFILES #{
|
||||||
|
ifndef IGNORE_ANDROID_RESFILES #{
|
||||||
res-dep := .deps-copy-java-res
|
res-dep := .deps-copy-java-res
|
||||||
|
|
||||||
GENERATED_DIRS += res
|
GENERATED_DIRS += res
|
||||||
|
@ -25,6 +26,7 @@ res-dep-preqs := \
|
||||||
$(res-dep): $(res-dep-preqs)
|
$(res-dep): $(res-dep-preqs)
|
||||||
$(call copy_dir,$(srcdir)/res,$(CURDIR)/res)
|
$(call copy_dir,$(srcdir)/res,$(CURDIR)/res)
|
||||||
@$(TOUCH) $@
|
@$(TOUCH) $@
|
||||||
|
endif #} IGNORE_ANDROID_RESFILES
|
||||||
endif #} ANDROID_RESFILES
|
endif #} ANDROID_RESFILES
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -21,6 +21,8 @@ INCLUDED_RULES_MK = 1
|
||||||
# present. If they are, this is a violation of the separation of
|
# present. If they are, this is a violation of the separation of
|
||||||
# responsibility between Makefile.in and mozbuild files.
|
# responsibility between Makefile.in and mozbuild files.
|
||||||
_MOZBUILD_EXTERNAL_VARIABLES := \
|
_MOZBUILD_EXTERNAL_VARIABLES := \
|
||||||
|
ANDROID_GENERATED_RESFILES \
|
||||||
|
ANDROID_RESFILES \
|
||||||
CMMSRCS \
|
CMMSRCS \
|
||||||
CPP_UNIT_TESTS \
|
CPP_UNIT_TESTS \
|
||||||
DIRS \
|
DIRS \
|
||||||
|
|
|
@ -505,7 +505,7 @@ abstract public class BrowserApp extends GeckoApp
|
||||||
registerEventListener("Updater:Launch");
|
registerEventListener("Updater:Launch");
|
||||||
registerEventListener("Reader:GoToReadingList");
|
registerEventListener("Reader:GoToReadingList");
|
||||||
|
|
||||||
Distribution.init(this, getPackageResourcePath());
|
Distribution.init(this);
|
||||||
JavaAddonManager.getInstance().init(getApplicationContext());
|
JavaAddonManager.getInstance().init(getApplicationContext());
|
||||||
mSharedPreferencesHelper = new SharedPreferencesHelper(getApplicationContext());
|
mSharedPreferencesHelper = new SharedPreferencesHelper(getApplicationContext());
|
||||||
mOrderedBroadcastHelper = new OrderedBroadcastHelper(getApplicationContext());
|
mOrderedBroadcastHelper = new OrderedBroadcastHelper(getApplicationContext());
|
||||||
|
|
|
@ -1,11 +1,7 @@
|
||||||
/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
|
/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
|
||||||
* ***** BEGIN LICENSE BLOCK *****
|
|
||||||
*
|
|
||||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
* 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,
|
* 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/.
|
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
*
|
|
||||||
* ***** END LICENSE BLOCK ***** */
|
|
||||||
|
|
||||||
package org.mozilla.gecko;
|
package org.mozilla.gecko;
|
||||||
|
|
||||||
|
@ -13,113 +9,224 @@ import org.mozilla.gecko.util.ThreadUtils;
|
||||||
|
|
||||||
import org.json.JSONArray;
|
import org.json.JSONArray;
|
||||||
import org.json.JSONException;
|
import org.json.JSONException;
|
||||||
|
import org.json.JSONObject;
|
||||||
|
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
import java.io.BufferedReader;
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FileInputStream;
|
|
||||||
import java.io.FileOutputStream;
|
import java.io.FileOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.io.InputStreamReader;
|
|
||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.Enumeration;
|
import java.util.Enumeration;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Scanner;
|
||||||
import java.util.zip.ZipEntry;
|
import java.util.zip.ZipEntry;
|
||||||
import java.util.zip.ZipFile;
|
import java.util.zip.ZipFile;
|
||||||
|
|
||||||
public final class Distribution {
|
public final class Distribution {
|
||||||
private static final String LOGTAG = "GeckoDistribution";
|
private static final String LOGTAG = "GeckoDistribution";
|
||||||
|
|
||||||
|
private static final String DEFAULT_PREFS = GeckoApp.PREFS_NAME;
|
||||||
|
|
||||||
private static final int STATE_UNKNOWN = 0;
|
private static final int STATE_UNKNOWN = 0;
|
||||||
private static final int STATE_NONE = 1;
|
private static final int STATE_NONE = 1;
|
||||||
private static final int STATE_SET = 2;
|
private static final int STATE_SET = 2;
|
||||||
|
|
||||||
|
public static class DistributionDescriptor {
|
||||||
|
public final boolean valid;
|
||||||
|
public final String id;
|
||||||
|
public final String version; // Example uses a float, but that's a crazy idea.
|
||||||
|
|
||||||
|
// Default UI-visible description of the distribution.
|
||||||
|
public final String about;
|
||||||
|
|
||||||
|
// Each distribution file can include multiple localized versions of
|
||||||
|
// the 'about' string. These are represented as, e.g., "about.en-US"
|
||||||
|
// keys in the Global object.
|
||||||
|
// Here we map locale to description.
|
||||||
|
public final Map<String, String> localizedAbout;
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public DistributionDescriptor(JSONObject obj) {
|
||||||
|
this.id = obj.optString("id");
|
||||||
|
this.version = obj.optString("version");
|
||||||
|
this.about = obj.optString("about");
|
||||||
|
Map<String, String> loc = new HashMap<String, String>();
|
||||||
|
try {
|
||||||
|
Iterator<String> keys = obj.keys();
|
||||||
|
while (keys.hasNext()) {
|
||||||
|
String key = keys.next();
|
||||||
|
if (key.startsWith("about.")) {
|
||||||
|
String locale = key.substring(6);
|
||||||
|
if (!obj.isNull(locale)) {
|
||||||
|
loc.put(locale, obj.getString(key));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (JSONException ex) {
|
||||||
|
Log.w(LOGTAG, "Unable to completely process distribution JSON.", ex);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.localizedAbout = Collections.unmodifiableMap(loc);
|
||||||
|
this.valid = (null != this.id) &&
|
||||||
|
(null != this.version) &&
|
||||||
|
(null != this.about);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initializes distribution if it hasn't already been initalized.
|
* Initializes distribution if it hasn't already been initalized. Sends
|
||||||
|
* messages to Gecko as appropriate.
|
||||||
*
|
*
|
||||||
* @param packagePath specifies where to look for the distribution directory.
|
* @param packagePath where to look for the distribution directory.
|
||||||
*/
|
*/
|
||||||
public static void init(final Context context, final String packagePath) {
|
public static void init(final Context context, final String packagePath, final String prefsPath) {
|
||||||
// Read/write preferences and files on the background thread.
|
// Read/write preferences and files on the background thread.
|
||||||
ThreadUtils.postToBackgroundThread(new Runnable() {
|
ThreadUtils.postToBackgroundThread(new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
// Bail if we've already initialized the distribution.
|
Distribution dist = new Distribution(context, packagePath, prefsPath);
|
||||||
SharedPreferences settings = context.getSharedPreferences(GeckoApp.PREFS_NAME, Activity.MODE_PRIVATE);
|
boolean distributionSet = dist.doInit();
|
||||||
String keyName = context.getPackageName() + ".distribution_state";
|
|
||||||
int state = settings.getInt(keyName, STATE_UNKNOWN);
|
|
||||||
if (state == STATE_NONE) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Send a message to Gecko if we've set a distribution.
|
|
||||||
if (state == STATE_SET) {
|
|
||||||
GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("Distribution:Set", ""));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
boolean distributionSet = false;
|
|
||||||
try {
|
|
||||||
// First, try copying distribution files out of the APK.
|
|
||||||
distributionSet = copyFiles(context, packagePath);
|
|
||||||
} catch (IOException e) {
|
|
||||||
Log.e(LOGTAG, "Error copying distribution files", e);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!distributionSet) {
|
|
||||||
// If there aren't any distribution files in the APK, look in the /system directory.
|
|
||||||
File distDir = new File("/system/" + context.getPackageName() + "/distribution");
|
|
||||||
if (distDir.exists()) {
|
|
||||||
distributionSet = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (distributionSet) {
|
if (distributionSet) {
|
||||||
GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("Distribution:Set", ""));
|
GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("Distribution:Set", ""));
|
||||||
settings.edit().putInt(keyName, STATE_SET).commit();
|
|
||||||
} else {
|
|
||||||
settings.edit().putInt(keyName, STATE_NONE).commit();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Use <code>Context.getPackageResourcePath</code> to find an implicit
|
||||||
|
* package path.
|
||||||
|
*/
|
||||||
|
public static void init(final Context context) {
|
||||||
|
Distribution.init(context, context.getPackageResourcePath(), DEFAULT_PREFS);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns parsed contents of bookmarks.json.
|
||||||
|
* This method should only be called from a background thread.
|
||||||
|
*/
|
||||||
|
public static JSONArray getBookmarks(final Context context) {
|
||||||
|
Distribution dist = new Distribution(context);
|
||||||
|
return dist.getBookmarks();
|
||||||
|
}
|
||||||
|
|
||||||
|
private final Context context;
|
||||||
|
private final String packagePath;
|
||||||
|
private final String prefsBranch;
|
||||||
|
|
||||||
|
private int state = STATE_UNKNOWN;
|
||||||
|
private File distributionDir = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param packagePath where to look for the distribution directory.
|
||||||
|
*/
|
||||||
|
public Distribution(final Context context, final String packagePath, final String prefsBranch) {
|
||||||
|
this.context = context;
|
||||||
|
this.packagePath = packagePath;
|
||||||
|
this.prefsBranch = prefsBranch;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Distribution(final Context context) {
|
||||||
|
this(context, context.getPackageResourcePath(), DEFAULT_PREFS);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Don't call from the main thread.
|
||||||
|
*
|
||||||
|
* @return true if we've set a distribution.
|
||||||
|
*/
|
||||||
|
private boolean doInit() {
|
||||||
|
// Bail if we've already tried to initialize the distribution, and
|
||||||
|
// there wasn't one.
|
||||||
|
SharedPreferences settings = context.getSharedPreferences(prefsBranch, Activity.MODE_PRIVATE);
|
||||||
|
String keyName = context.getPackageName() + ".distribution_state";
|
||||||
|
this.state = settings.getInt(keyName, STATE_UNKNOWN);
|
||||||
|
if (this.state == STATE_NONE) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// We've done the work once; don't do it again.
|
||||||
|
if (this.state == STATE_SET) {
|
||||||
|
// Note that we don't compute the distribution directory.
|
||||||
|
// Call `ensureDistributionDir` if you need it.
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean distributionSet = false;
|
||||||
|
try {
|
||||||
|
// First, try copying distribution files out of the APK.
|
||||||
|
distributionSet = copyFiles();
|
||||||
|
if (distributionSet) {
|
||||||
|
// We always copy to the data dir, and we only copy files from
|
||||||
|
// a 'distribution' subdirectory. Track our dist dir now that
|
||||||
|
// we know it.
|
||||||
|
this.distributionDir = new File(getDataDir(), "distribution/");
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
Log.e(LOGTAG, "Error copying distribution files", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!distributionSet) {
|
||||||
|
// If there aren't any distribution files in the APK, look in the /system directory.
|
||||||
|
File distDir = getSystemDistributionDir();
|
||||||
|
if (distDir.exists()) {
|
||||||
|
distributionSet = true;
|
||||||
|
this.distributionDir = distDir;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.state = distributionSet ? STATE_SET : STATE_NONE;
|
||||||
|
settings.edit().putInt(keyName, this.state).commit();
|
||||||
|
return distributionSet;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Copies the /distribution folder out of the APK and into the app's data directory.
|
* Copies the /distribution folder out of the APK and into the app's data directory.
|
||||||
* Returns true if distribution files were found and copied.
|
* Returns true if distribution files were found and copied.
|
||||||
*/
|
*/
|
||||||
private static boolean copyFiles(Context context, String packagePath) throws IOException {
|
private boolean copyFiles() throws IOException {
|
||||||
File applicationPackage = new File(packagePath);
|
File applicationPackage = new File(packagePath);
|
||||||
ZipFile zip = new ZipFile(applicationPackage);
|
ZipFile zip = new ZipFile(applicationPackage);
|
||||||
|
|
||||||
boolean distributionSet = false;
|
boolean distributionSet = false;
|
||||||
Enumeration<? extends ZipEntry> zipEntries = zip.entries();
|
Enumeration<? extends ZipEntry> zipEntries = zip.entries();
|
||||||
|
|
||||||
|
byte[] buffer = new byte[1024];
|
||||||
while (zipEntries.hasMoreElements()) {
|
while (zipEntries.hasMoreElements()) {
|
||||||
ZipEntry fileEntry = zipEntries.nextElement();
|
ZipEntry fileEntry = zipEntries.nextElement();
|
||||||
String name = fileEntry.getName();
|
String name = fileEntry.getName();
|
||||||
|
|
||||||
if (!name.startsWith("distribution/"))
|
if (!name.startsWith("distribution/")) {
|
||||||
continue;
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
distributionSet = true;
|
distributionSet = true;
|
||||||
|
|
||||||
File dataDir = new File(context.getApplicationInfo().dataDir);
|
File outFile = new File(getDataDir(), name);
|
||||||
File outFile = new File(dataDir, name);
|
|
||||||
|
|
||||||
File dir = outFile.getParentFile();
|
File dir = outFile.getParentFile();
|
||||||
if (!dir.exists())
|
|
||||||
dir.mkdirs();
|
if (!dir.exists()) {
|
||||||
|
if (!dir.mkdirs()) {
|
||||||
|
Log.e(LOGTAG, "Unable to create directories: " + dir.getAbsolutePath());
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
InputStream fileStream = zip.getInputStream(fileEntry);
|
InputStream fileStream = zip.getInputStream(fileEntry);
|
||||||
OutputStream outStream = new FileOutputStream(outFile);
|
OutputStream outStream = new FileOutputStream(outFile);
|
||||||
|
|
||||||
int b;
|
int count;
|
||||||
while ((b = fileStream.read()) != -1)
|
while ((count = fileStream.read(buffer)) != -1) {
|
||||||
outStream.write(b);
|
outStream.write(buffer, 0, count);
|
||||||
|
}
|
||||||
|
|
||||||
fileStream.close();
|
fileStream.close();
|
||||||
outStream.close();
|
outStream.close();
|
||||||
|
@ -132,77 +239,125 @@ public final class Distribution {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns parsed contents of bookmarks.json.
|
* After calling this method, either <code>distributionDir</code>
|
||||||
* This method should only be called from a background thread.
|
* will be set, or there is no distribution in use.
|
||||||
|
*
|
||||||
|
* Only call after init.
|
||||||
*/
|
*/
|
||||||
public static JSONArray getBookmarks(Context context) {
|
private File ensureDistributionDir() {
|
||||||
SharedPreferences settings = context.getSharedPreferences(GeckoApp.PREFS_NAME, Activity.MODE_PRIVATE);
|
if (this.distributionDir != null) {
|
||||||
String keyName = context.getPackageName() + ".distribution_state";
|
return this.distributionDir;
|
||||||
int state = settings.getInt(keyName, STATE_UNKNOWN);
|
}
|
||||||
if (state == STATE_NONE) {
|
|
||||||
|
if (this.state != STATE_SET) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// After init, we know that either we've copied a distribution out of
|
||||||
|
// the APK, or it exists in /system/.
|
||||||
|
// Look in each location in turn.
|
||||||
|
// (This could be optimized by caching the path in shared prefs.)
|
||||||
|
File copied = new File(getDataDir(), "distribution/");
|
||||||
|
if (copied.exists()) {
|
||||||
|
return this.distributionDir = copied;
|
||||||
|
}
|
||||||
|
File system = getSystemDistributionDir();
|
||||||
|
if (system.exists()) {
|
||||||
|
return this.distributionDir = system;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper to grab a file in the distribution directory.
|
||||||
|
*
|
||||||
|
* Returns null if there is no distribution directory or the file
|
||||||
|
* doesn't exist. Ensures init first.
|
||||||
|
*/
|
||||||
|
private File getDistributionFile(String name) {
|
||||||
|
Log.i(LOGTAG, "Getting file from distribution.");
|
||||||
|
if (this.state == STATE_UNKNOWN) {
|
||||||
|
if (!this.doInit()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
File dist = ensureDistributionDir();
|
||||||
|
if (dist == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
File descFile = new File(dist, name);
|
||||||
|
if (!descFile.exists()) {
|
||||||
|
Log.e(LOGTAG, "Distribution directory exists, but no file named " + name);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return descFile;
|
||||||
|
}
|
||||||
|
|
||||||
|
public DistributionDescriptor getDescriptor() {
|
||||||
|
File descFile = getDistributionFile("preferences.json");
|
||||||
|
if (descFile == null) {
|
||||||
|
// Logging and existence checks are handled in getDistributionFile.
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
ZipFile zip = null;
|
|
||||||
InputStream inputStream = null;
|
|
||||||
try {
|
try {
|
||||||
if (state == STATE_UNKNOWN) {
|
JSONObject all = new JSONObject(getFileContents(descFile));
|
||||||
// If the distribution hasn't been set yet, first look for bookmarks.json in the APK.
|
|
||||||
File applicationPackage = new File(context.getPackageResourcePath());
|
|
||||||
zip = new ZipFile(applicationPackage);
|
|
||||||
ZipEntry zipEntry = zip.getEntry("distribution/bookmarks.json");
|
|
||||||
if (zipEntry != null) {
|
|
||||||
inputStream = zip.getInputStream(zipEntry);
|
|
||||||
} else {
|
|
||||||
// If there's no bookmarks.json in the APK, but there is a preferences.json,
|
|
||||||
// don't create any distribution bookmarks.
|
|
||||||
zipEntry = zip.getEntry("distribution/preferences.json");
|
|
||||||
if (zipEntry != null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
// Otherwise, look for bookmarks.json in the /system directory.
|
|
||||||
File systemFile = new File("/system/" + context.getPackageName() + "/distribution/bookmarks.json");
|
|
||||||
if (!systemFile.exists()) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
inputStream = new FileInputStream(systemFile);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Otherwise, first look for the distribution in the data directory.
|
|
||||||
File distDir = new File(context.getApplicationInfo().dataDir, "distribution");
|
|
||||||
if (!distDir.exists()) {
|
|
||||||
// If that doesn't exist, then we must be using a distribution from the system directory.
|
|
||||||
distDir = new File("/system/" + context.getPackageName() + "/distribution");
|
|
||||||
}
|
|
||||||
|
|
||||||
File file = new File(distDir, "bookmarks.json");
|
if (!all.has("Global")) {
|
||||||
inputStream = new FileInputStream(file);
|
Log.e(LOGTAG, "Distribution preferences.json has no Global entry!");
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Convert input stream to JSONArray
|
return new DistributionDescriptor(all.getJSONObject("Global"));
|
||||||
BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
|
|
||||||
StringBuilder stringBuilder = new StringBuilder();
|
} catch (IOException e) {
|
||||||
String s;
|
Log.e(LOGTAG, "Error getting distribution descriptor file.", e);
|
||||||
while ((s = reader.readLine()) != null) {
|
return null;
|
||||||
stringBuilder.append(s);
|
} catch (JSONException e) {
|
||||||
}
|
Log.e(LOGTAG, "Error parsing preferences.json", e);
|
||||||
return new JSONArray(stringBuilder.toString());
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public JSONArray getBookmarks() {
|
||||||
|
File bookmarks = getDistributionFile("bookmarks.json");
|
||||||
|
if (bookmarks == null) {
|
||||||
|
// Logging and existence checks are handled in getDistributionFile.
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
return new JSONArray(getFileContents(bookmarks));
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
Log.e(LOGTAG, "Error getting bookmarks", e);
|
Log.e(LOGTAG, "Error getting bookmarks", e);
|
||||||
} catch (JSONException e) {
|
} catch (JSONException e) {
|
||||||
Log.e(LOGTAG, "Error parsing bookmarks.json", e);
|
Log.e(LOGTAG, "Error parsing bookmarks.json", e);
|
||||||
} finally {
|
|
||||||
try {
|
|
||||||
if (zip != null) {
|
|
||||||
zip.close();
|
|
||||||
}
|
|
||||||
if (inputStream != null) {
|
|
||||||
inputStream.close();
|
|
||||||
}
|
|
||||||
} catch (IOException e) {
|
|
||||||
Log.e(LOGTAG, "Error closing streams", e);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Shortcut to slurp a file without messing around with streams.
|
||||||
|
private String getFileContents(File file) throws IOException {
|
||||||
|
Scanner scanner = null;
|
||||||
|
try {
|
||||||
|
scanner = new Scanner(file, "UTF-8");
|
||||||
|
return scanner.useDelimiter("\\A").next();
|
||||||
|
} finally {
|
||||||
|
if (scanner != null) {
|
||||||
|
scanner.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getDataDir() {
|
||||||
|
return context.getApplicationInfo().dataDir;
|
||||||
|
}
|
||||||
|
|
||||||
|
private File getSystemDistributionDir() {
|
||||||
|
return new File("/system/" + context.getPackageName() + "/distribution");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -117,6 +117,7 @@ import java.util.HashSet;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Locale;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.regex.Matcher;
|
import java.util.regex.Matcher;
|
||||||
|
@ -1291,7 +1292,16 @@ abstract public class GeckoApp
|
||||||
final String profilePath = getProfile().getDir().getAbsolutePath();
|
final String profilePath = getProfile().getDir().getAbsolutePath();
|
||||||
final EventDispatcher dispatcher = GeckoAppShell.getEventDispatcher();
|
final EventDispatcher dispatcher = GeckoAppShell.getEventDispatcher();
|
||||||
Log.i(LOGTAG, "Creating BrowserHealthRecorder.");
|
Log.i(LOGTAG, "Creating BrowserHealthRecorder.");
|
||||||
mHealthRecorder = new BrowserHealthRecorder(GeckoApp.this, profilePath, dispatcher,
|
final String osLocale = Locale.getDefault().toString();
|
||||||
|
Log.d(LOGTAG, "Locale is " + osLocale);
|
||||||
|
|
||||||
|
// Replace the duplicate `osLocale` argument when we support switchable
|
||||||
|
// application locales.
|
||||||
|
mHealthRecorder = new BrowserHealthRecorder(GeckoApp.this,
|
||||||
|
profilePath,
|
||||||
|
dispatcher,
|
||||||
|
osLocale,
|
||||||
|
osLocale, // Placeholder.
|
||||||
previousSession);
|
previousSession);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -1555,8 +1565,15 @@ abstract public class GeckoApp
|
||||||
GeckoPreferences.broadcastHealthReportUploadPref(context);
|
GeckoPreferences.broadcastHealthReportUploadPref(context);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
XXXX see bug 635342
|
XXXX see Bug 635342.
|
||||||
We want to disable this code if possible. It is about 145ms in runtime
|
We want to disable this code if possible. It is about 145ms in runtime.
|
||||||
|
|
||||||
|
If this code ever becomes live again, you'll need to chain the
|
||||||
|
new locale into BrowserHealthRecorder correctly. See
|
||||||
|
GeckoAppShell.setSelectedLocale.
|
||||||
|
We pass the OS locale into the BHR constructor: we need to grab
|
||||||
|
that *before* we modify the current locale!
|
||||||
|
|
||||||
SharedPreferences settings = getPreferences(Activity.MODE_PRIVATE);
|
SharedPreferences settings = getPreferences(Activity.MODE_PRIVATE);
|
||||||
String localeCode = settings.getString(getPackageName() + ".locale", "");
|
String localeCode = settings.getString(getPackageName() + ".locale", "");
|
||||||
if (localeCode != null && localeCode.length() > 0)
|
if (localeCode != null && localeCode.length() > 0)
|
||||||
|
|
|
@ -1525,6 +1525,12 @@ public class GeckoAppShell
|
||||||
Gecko resets the locale to en-US by calling this function with an empty string.
|
Gecko resets the locale to en-US by calling this function with an empty string.
|
||||||
This affects GeckoPreferences activity in multi-locale builds.
|
This affects GeckoPreferences activity in multi-locale builds.
|
||||||
|
|
||||||
|
N.B., if this code ever becomes live again, you need to hook it up to locale
|
||||||
|
recording in BrowserHealthRecorder: we track the current app and OS locales
|
||||||
|
as part of the recorded environment.
|
||||||
|
|
||||||
|
See similar note in GeckoApp.java for the startup path.
|
||||||
|
|
||||||
//We're not using this, not need to save it (see bug 635342)
|
//We're not using this, not need to save it (see bug 635342)
|
||||||
SharedPreferences settings =
|
SharedPreferences settings =
|
||||||
getContext().getPreferences(Activity.MODE_PRIVATE);
|
getContext().getPreferences(Activity.MODE_PRIVATE);
|
||||||
|
|
|
@ -313,8 +313,13 @@ public final class GeckoProfile {
|
||||||
}
|
}
|
||||||
|
|
||||||
public synchronized File getDir() {
|
public synchronized File getDir() {
|
||||||
|
forceCreate();
|
||||||
|
return mDir;
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized GeckoProfile forceCreate() {
|
||||||
if (mDir != null) {
|
if (mDir != null) {
|
||||||
return mDir;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
@ -330,7 +335,7 @@ public final class GeckoProfile {
|
||||||
} catch (IOException ioe) {
|
} catch (IOException ioe) {
|
||||||
Log.e(LOGTAG, "Error getting profile dir", ioe);
|
Log.e(LOGTAG, "Error getting profile dir", ioe);
|
||||||
}
|
}
|
||||||
return mDir;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public File getFile(String aFile) {
|
public File getFile(String aFile) {
|
||||||
|
|
|
@ -55,6 +55,7 @@ public class GeckoView extends LayerView
|
||||||
|
|
||||||
Clipboard.init(context);
|
Clipboard.init(context);
|
||||||
HardwareUtils.init(context);
|
HardwareUtils.init(context);
|
||||||
|
GeckoNetworkManager.getInstance().init(context);
|
||||||
|
|
||||||
GeckoLoader.loadMozGlue();
|
GeckoLoader.loadMozGlue();
|
||||||
BrowserDB.setEnableContentProviders(false);
|
BrowserDB.setEnableContentProviders(false);
|
||||||
|
@ -75,7 +76,7 @@ public class GeckoView extends LayerView
|
||||||
ThreadUtils.setUiThread(Thread.currentThread(), new Handler());
|
ThreadUtils.setUiThread(Thread.currentThread(), new Handler());
|
||||||
initializeView(GeckoAppShell.getEventDispatcher());
|
initializeView(GeckoAppShell.getEventDispatcher());
|
||||||
|
|
||||||
GeckoProfile profile = GeckoProfile.get(context);
|
GeckoProfile profile = GeckoProfile.get(context).forceCreate();
|
||||||
BrowserDB.initialize(profile.getName());
|
BrowserDB.initialize(profile.getName());
|
||||||
|
|
||||||
if (GeckoThread.checkAndSetLaunchState(GeckoThread.LaunchState.Launching, GeckoThread.LaunchState.Launched)) {
|
if (GeckoThread.checkAndSetLaunchState(GeckoThread.LaunchState.Launching, GeckoThread.LaunchState.Launched)) {
|
||||||
|
|
|
@ -416,789 +416,12 @@ ICON_PATH = $(topsrcdir)/$(MOZ_BRANDING_DIRECTORY)/content/icon48.png
|
||||||
ICON_PATH_HDPI = $(topsrcdir)/$(MOZ_BRANDING_DIRECTORY)/content/icon64.png
|
ICON_PATH_HDPI = $(topsrcdir)/$(MOZ_BRANDING_DIRECTORY)/content/icon64.png
|
||||||
endif
|
endif
|
||||||
|
|
||||||
RES_LAYOUT = \
|
|
||||||
$(SYNC_RES_LAYOUT) \
|
|
||||||
res/layout/arrow_popup.xml \
|
|
||||||
res/layout/autocomplete_list.xml \
|
|
||||||
res/layout/autocomplete_list_item.xml \
|
|
||||||
res/layout/bookmark_edit.xml \
|
|
||||||
res/layout/bookmark_folder_row.xml \
|
|
||||||
res/layout/bookmark_item_row.xml \
|
|
||||||
res/layout/browser_search.xml \
|
|
||||||
res/layout/browser_toolbar.xml \
|
|
||||||
res/layout/datetime_picker.xml \
|
|
||||||
res/layout/doorhanger.xml \
|
|
||||||
res/layout/doorhanger_button.xml \
|
|
||||||
res/layout/find_in_page_content.xml \
|
|
||||||
res/layout/font_size_preference.xml \
|
|
||||||
res/layout/gecko_app.xml \
|
|
||||||
res/layout/home_bookmarks_page.xml \
|
|
||||||
res/layout/home_empty_page.xml \
|
|
||||||
res/layout/home_empty_reading_page.xml \
|
|
||||||
res/layout/home_item_row.xml \
|
|
||||||
res/layout/home_header_row.xml \
|
|
||||||
res/layout/home_history_page.xml \
|
|
||||||
res/layout/home_history_tabs_indicator.xml \
|
|
||||||
res/layout/home_last_tabs_page.xml \
|
|
||||||
res/layout/home_history_list.xml \
|
|
||||||
res/layout/home_most_recent_page.xml \
|
|
||||||
res/layout/home_pager.xml \
|
|
||||||
res/layout/home_reading_list_page.xml \
|
|
||||||
res/layout/home_search_item_row.xml \
|
|
||||||
res/layout/home_banner.xml \
|
|
||||||
res/layout/home_suggestion_prompt.xml \
|
|
||||||
res/layout/home_top_sites_page.xml \
|
|
||||||
res/layout/icon_grid.xml \
|
|
||||||
res/layout/icon_grid_item.xml \
|
|
||||||
res/layout/web_app.xml \
|
|
||||||
res/layout/launch_app_list.xml \
|
|
||||||
res/layout/launch_app_listitem.xml \
|
|
||||||
res/layout/menu_action_bar.xml \
|
|
||||||
res/layout/menu_item_action_view.xml \
|
|
||||||
res/layout/menu_popup.xml \
|
|
||||||
res/layout/notification_icon_text.xml \
|
|
||||||
res/layout/notification_progress.xml \
|
|
||||||
res/layout/notification_progress_text.xml \
|
|
||||||
res/layout/pin_site_dialog.xml \
|
|
||||||
res/layout/preference_rightalign_icon.xml \
|
|
||||||
res/layout/preference_search_engine.xml \
|
|
||||||
res/layout/preference_search_tip.xml \
|
|
||||||
res/layout/site_setting_item.xml \
|
|
||||||
res/layout/site_setting_title.xml \
|
|
||||||
res/layout/shared_ui_components.xml \
|
|
||||||
res/layout/site_identity.xml \
|
|
||||||
res/layout/remote_tabs_child.xml \
|
|
||||||
res/layout/remote_tabs_group.xml \
|
|
||||||
res/layout/search_engine_row.xml \
|
|
||||||
res/layout/tab_menu_strip.xml \
|
|
||||||
res/layout/tabs_panel.xml \
|
|
||||||
res/layout/tabs_counter.xml \
|
|
||||||
res/layout/tabs_panel_header.xml \
|
|
||||||
res/layout/tabs_panel_indicator.xml \
|
|
||||||
res/layout/tabs_item_cell.xml \
|
|
||||||
res/layout/tabs_item_row.xml \
|
|
||||||
res/layout/text_selection_handles.xml \
|
|
||||||
res/layout/top_sites_grid_item_view.xml \
|
|
||||||
res/layout/two_line_page_row.xml \
|
|
||||||
res/layout/list_item_header.xml \
|
|
||||||
res/layout/select_dialog_list.xml \
|
|
||||||
res/layout/select_dialog_multichoice.xml \
|
|
||||||
res/layout/select_dialog_singlechoice.xml \
|
|
||||||
res/layout/simple_dropdown_item_1line.xml \
|
|
||||||
res/layout/suggestion_item.xml \
|
|
||||||
res/layout/validation_message.xml \
|
|
||||||
res/layout/videoplayer.xml \
|
|
||||||
$(NULL)
|
|
||||||
|
|
||||||
RES_LAYOUT_LARGE_V11 = \
|
|
||||||
res/layout-large-v11/browser_toolbar.xml \
|
|
||||||
res/layout-large-v11/home_pager.xml \
|
|
||||||
$(NULL)
|
|
||||||
|
|
||||||
RES_LAYOUT_LARGE_LAND_V11 = \
|
|
||||||
res/layout-large-land-v11/home_history_page.xml \
|
|
||||||
res/layout-large-land-v11/home_history_tabs_indicator.xml \
|
|
||||||
res/layout-large-land-v11/home_history_list.xml \
|
|
||||||
res/layout-large-land-v11/tabs_panel.xml \
|
|
||||||
res/layout-large-land-v11/tabs_panel_header.xml \
|
|
||||||
res/layout-large-land-v11/tabs_panel_footer.xml \
|
|
||||||
$(NULL)
|
|
||||||
|
|
||||||
RES_LAYOUT_XLARGE_V11 = \
|
|
||||||
res/layout-xlarge-v11/font_size_preference.xml \
|
|
||||||
res/layout-xlarge-v11/home_history_page.xml \
|
|
||||||
res/layout-xlarge-v11/home_history_tabs_indicator.xml \
|
|
||||||
res/layout-xlarge-v11/home_history_list.xml \
|
|
||||||
res/layout-xlarge-v11/remote_tabs_child.xml \
|
|
||||||
res/layout-xlarge-v11/remote_tabs_group.xml \
|
|
||||||
$(NULL)
|
|
||||||
|
|
||||||
RES_VALUES = \
|
|
||||||
$(SYNC_RES_VALUES) \
|
|
||||||
res/values/attrs.xml \
|
|
||||||
res/values/arrays.xml \
|
|
||||||
res/values/colors.xml \
|
|
||||||
res/values/dimens.xml \
|
|
||||||
res/values/integers.xml \
|
|
||||||
res/values/layout.xml \
|
|
||||||
res/values/styles.xml \
|
|
||||||
res/values/themes.xml \
|
|
||||||
$(NULL)
|
|
||||||
|
|
||||||
RES_VALUES_LAND = \
|
|
||||||
res/values-land/integers.xml \
|
|
||||||
res/values-land/layout.xml \
|
|
||||||
res/values-land/styles.xml \
|
|
||||||
$(NULL)
|
|
||||||
|
|
||||||
RES_VALUES_V11 = \
|
|
||||||
$(SYNC_RES_VALUES_V11) \
|
|
||||||
res/values-v11/colors.xml \
|
|
||||||
res/values-v11/dimens.xml \
|
|
||||||
res/values-v11/styles.xml \
|
|
||||||
res/values-v11/themes.xml \
|
|
||||||
$(NULL)
|
|
||||||
|
|
||||||
RES_VALUES_LARGE_V11 = \
|
|
||||||
$(SYNC_RES_VALUES_LARGE_V11) \
|
|
||||||
res/values-large-v11/dimens.xml \
|
|
||||||
res/values-large-v11/layout.xml \
|
|
||||||
res/values-large-v11/styles.xml \
|
|
||||||
res/values-large-v11/themes.xml \
|
|
||||||
$(NULL)
|
|
||||||
|
|
||||||
RES_VALUES_LARGE_LAND_V11 = \
|
|
||||||
res/values-large-land-v11/dimens.xml \
|
|
||||||
res/values-large-land-v11/styles.xml \
|
|
||||||
$(NULL)
|
|
||||||
|
|
||||||
RES_VALUES_XLARGE_V11 = \
|
|
||||||
res/values-xlarge-v11/dimens.xml \
|
|
||||||
res/values-xlarge-v11/integers.xml \
|
|
||||||
res/values-xlarge-v11/styles.xml \
|
|
||||||
$(NULL)
|
|
||||||
|
|
||||||
RES_VALUES_XLARGE_LAND_V11 = \
|
|
||||||
res/values-xlarge-land-v11/dimens.xml \
|
|
||||||
res/values-xlarge-land-v11/styles.xml \
|
|
||||||
$(NULL)
|
|
||||||
|
|
||||||
RES_VALUES_V14 = \
|
|
||||||
res/values-v14/styles.xml \
|
|
||||||
$(NULL)
|
|
||||||
|
|
||||||
RES_VALUES_V16 = \
|
|
||||||
res/values-v16/styles.xml \
|
|
||||||
$(NULL)
|
|
||||||
|
|
||||||
RES_XML = \
|
|
||||||
res/xml/preferences.xml \
|
|
||||||
res/xml/preferences_customize.xml \
|
|
||||||
res/xml/preferences_display.xml \
|
|
||||||
res/xml/preferences_search.xml \
|
|
||||||
res/xml/preferences_privacy.xml \
|
|
||||||
res/xml/preferences_vendor.xml \
|
|
||||||
res/xml/preferences_devtools.xml \
|
|
||||||
res/xml/searchable.xml \
|
|
||||||
$(SYNC_RES_XML) \
|
|
||||||
$(NULL)
|
|
||||||
|
|
||||||
RES_XML_V11 = \
|
|
||||||
res/xml-v11/preferences_customize.xml \
|
|
||||||
res/xml-v11/preference_headers.xml \
|
|
||||||
res/xml-v11/preferences_customize_tablet.xml \
|
|
||||||
res/xml-v11/preferences.xml \
|
|
||||||
$(NULL)
|
|
||||||
|
|
||||||
RES_ANIM = \
|
|
||||||
res/anim/popup_show.xml \
|
|
||||||
res/anim/popup_hide.xml \
|
|
||||||
res/anim/grow_fade_in.xml \
|
|
||||||
res/anim/grow_fade_in_center.xml \
|
|
||||||
res/anim/progress_spinner.xml \
|
|
||||||
res/anim/shrink_fade_out.xml \
|
|
||||||
$(NULL)
|
|
||||||
|
|
||||||
RES_DRAWABLE_MDPI = \
|
|
||||||
$(SYNC_RES_DRAWABLE_MDPI) \
|
|
||||||
res/drawable-mdpi/blank.png \
|
|
||||||
res/drawable-mdpi/favicon.png \
|
|
||||||
res/drawable-mdpi/folder.png \
|
|
||||||
res/drawable-mdpi/abouthome_thumbnail.png \
|
|
||||||
res/drawable-mdpi/alert_addon.png \
|
|
||||||
res/drawable-mdpi/alert_app.png \
|
|
||||||
res/drawable-mdpi/alert_download.png \
|
|
||||||
res/drawable-mdpi/alert_camera.png \
|
|
||||||
res/drawable-mdpi/alert_mic.png \
|
|
||||||
res/drawable-mdpi/alert_mic_camera.png \
|
|
||||||
res/drawable-mdpi/arrow_popup_bg.9.png \
|
|
||||||
res/drawable-mdpi/autocomplete_list_bg.9.png \
|
|
||||||
res/drawable-mdpi/bookmark_folder_closed.png \
|
|
||||||
res/drawable-mdpi/bookmark_folder_opened.png \
|
|
||||||
res/drawable-mdpi/desktop_notification.png \
|
|
||||||
res/drawable-mdpi/grid_icon_bg_activated.9.png \
|
|
||||||
res/drawable-mdpi/grid_icon_bg_focused.9.png \
|
|
||||||
res/drawable-mdpi/home_tab_menu_strip.9.png \
|
|
||||||
res/drawable-mdpi/ic_menu_addons_filler.png \
|
|
||||||
res/drawable-mdpi/ic_menu_bookmark_add.png \
|
|
||||||
res/drawable-mdpi/ic_menu_bookmark_remove.png \
|
|
||||||
res/drawable-mdpi/ic_menu_character_encoding.png \
|
|
||||||
res/drawable-mdpi/close.png \
|
|
||||||
res/drawable-mdpi/ic_menu_forward.png \
|
|
||||||
res/drawable-mdpi/ic_menu_guest.png \
|
|
||||||
res/drawable-mdpi/ic_menu_new_private_tab.png \
|
|
||||||
res/drawable-mdpi/ic_menu_new_tab.png \
|
|
||||||
res/drawable-mdpi/ic_menu_reload.png \
|
|
||||||
res/drawable-mdpi/ic_status_logo.png \
|
|
||||||
res/drawable-mdpi/ic_url_bar_go.png \
|
|
||||||
res/drawable-mdpi/ic_url_bar_reader.png \
|
|
||||||
res/drawable-mdpi/ic_url_bar_search.png \
|
|
||||||
res/drawable-mdpi/ic_url_bar_star.png \
|
|
||||||
res/drawable-mdpi/ic_url_bar_tab.png \
|
|
||||||
res/drawable-mdpi/icon_bookmarks_empty.png \
|
|
||||||
res/drawable-mdpi/icon_last_tabs.png \
|
|
||||||
res/drawable-mdpi/icon_last_tabs_empty.png \
|
|
||||||
res/drawable-mdpi/icon_most_recent.png \
|
|
||||||
res/drawable-mdpi/icon_most_recent_empty.png \
|
|
||||||
res/drawable-mdpi/icon_most_visited.png \
|
|
||||||
res/drawable-mdpi/icon_openinapp.png \
|
|
||||||
res/drawable-mdpi/icon_pageaction.png \
|
|
||||||
res/drawable-mdpi/icon_reading_list_empty.png \
|
|
||||||
res/drawable-mdpi/progress_spinner.png \
|
|
||||||
res/drawable-mdpi/play.png \
|
|
||||||
res/drawable-mdpi/pause.png \
|
|
||||||
res/drawable-mdpi/tab_indicator_divider.9.png \
|
|
||||||
res/drawable-mdpi/tab_indicator_selected.9.png \
|
|
||||||
res/drawable-mdpi/tab_indicator_selected_focused.9.png \
|
|
||||||
res/drawable-mdpi/spinner_default.9.png \
|
|
||||||
res/drawable-mdpi/spinner_focused.9.png \
|
|
||||||
res/drawable-mdpi/spinner_pressed.9.png \
|
|
||||||
res/drawable-mdpi/tab_new.png \
|
|
||||||
res/drawable-mdpi/tab_new_pb.png \
|
|
||||||
res/drawable-mdpi/tab_close.png \
|
|
||||||
res/drawable-mdpi/tab_thumbnail_default.png \
|
|
||||||
res/drawable-mdpi/tab_thumbnail_shadow.png \
|
|
||||||
res/drawable-mdpi/tabs_count.png \
|
|
||||||
res/drawable-mdpi/tabs_count_foreground.png \
|
|
||||||
res/drawable-mdpi/url_bar_entry_default.9.png \
|
|
||||||
res/drawable-mdpi/url_bar_entry_default_pb.9.png \
|
|
||||||
res/drawable-mdpi/url_bar_entry_pressed.9.png \
|
|
||||||
res/drawable-mdpi/url_bar_entry_pressed_pb.9.png \
|
|
||||||
res/drawable-mdpi/tip_addsearch.png \
|
|
||||||
res/drawable-mdpi/toast.9.png \
|
|
||||||
res/drawable-mdpi/toast_button_focused.9.png \
|
|
||||||
res/drawable-mdpi/toast_button_pressed.9.png \
|
|
||||||
res/drawable-mdpi/toast_divider.9.png \
|
|
||||||
res/drawable-mdpi/find_close.png \
|
|
||||||
res/drawable-mdpi/find_next.png \
|
|
||||||
res/drawable-mdpi/find_prev.png \
|
|
||||||
res/drawable-mdpi/larry.png \
|
|
||||||
res/drawable-mdpi/lock_identified.png \
|
|
||||||
res/drawable-mdpi/lock_verified.png \
|
|
||||||
res/drawable-mdpi/menu.png \
|
|
||||||
res/drawable-mdpi/menu_pb.png \
|
|
||||||
res/drawable-mdpi/menu_panel_bg.9.png \
|
|
||||||
res/drawable-mdpi/menu_popup_bg.9.png \
|
|
||||||
res/drawable-mdpi/menu_popup_arrow_bottom.png \
|
|
||||||
res/drawable-mdpi/menu_popup_arrow_top.png \
|
|
||||||
res/drawable-mdpi/menu_item_check.png \
|
|
||||||
res/drawable-mdpi/menu_item_more.png \
|
|
||||||
res/drawable-mdpi/menu_item_uncheck.png \
|
|
||||||
res/drawable-mdpi/pin.png \
|
|
||||||
res/drawable-mdpi/shield.png \
|
|
||||||
res/drawable-mdpi/shield_doorhanger.png \
|
|
||||||
res/drawable-mdpi/tabs_normal.png \
|
|
||||||
res/drawable-mdpi/tabs_private.png \
|
|
||||||
res/drawable-mdpi/tabs_synced.png \
|
|
||||||
res/drawable-mdpi/top_site_add.png \
|
|
||||||
res/drawable-mdpi/urlbar_stop.png \
|
|
||||||
res/drawable-mdpi/reader.png \
|
|
||||||
res/drawable-mdpi/reader_cropped.png \
|
|
||||||
res/drawable-mdpi/reader_active.png \
|
|
||||||
res/drawable-mdpi/reading_list.png \
|
|
||||||
res/drawable-mdpi/validation_arrow.png \
|
|
||||||
res/drawable-mdpi/validation_arrow_inverted.png \
|
|
||||||
res/drawable-mdpi/validation_bg.9.png \
|
|
||||||
res/drawable-mdpi/bookmarkdefaults_favicon_support.png \
|
|
||||||
res/drawable-mdpi/bookmarkdefaults_favicon_addons.png \
|
|
||||||
res/drawable-mdpi/handle_end.png \
|
|
||||||
res/drawable-mdpi/handle_middle.png \
|
|
||||||
res/drawable-mdpi/handle_start.png \
|
|
||||||
res/drawable-mdpi/scrollbar.png \
|
|
||||||
res/drawable-mdpi/shadow.png \
|
|
||||||
res/drawable-mdpi/start.png \
|
|
||||||
res/drawable-mdpi/marketplace.png \
|
|
||||||
res/drawable-mdpi/history_tabs_indicator_selected.9.png \
|
|
||||||
res/drawable-mdpi/warning.png \
|
|
||||||
res/drawable-mdpi/warning_doorhanger.png \
|
|
||||||
$(NULL)
|
|
||||||
|
|
||||||
RES_DRAWABLE_LDPI = \
|
|
||||||
$(SYNC_RES_DRAWABLE_LDPI) \
|
|
||||||
$(NULL)
|
|
||||||
|
|
||||||
RES_DRAWABLE_HDPI = \
|
|
||||||
$(SYNC_RES_DRAWABLE_HDPI) \
|
|
||||||
res/drawable-hdpi/blank.png \
|
|
||||||
res/drawable-hdpi/favicon.png \
|
|
||||||
res/drawable-hdpi/folder.png \
|
|
||||||
res/drawable-hdpi/home_bg.png \
|
|
||||||
res/drawable-hdpi/home_star.png \
|
|
||||||
res/drawable-hdpi/grid_icon_bg_activated.9.png \
|
|
||||||
res/drawable-hdpi/grid_icon_bg_focused.9.png \
|
|
||||||
res/drawable-hdpi/abouthome_thumbnail.png \
|
|
||||||
res/drawable-hdpi/alert_addon.png \
|
|
||||||
res/drawable-hdpi/alert_app.png \
|
|
||||||
res/drawable-hdpi/alert_download.png \
|
|
||||||
res/drawable-hdpi/bookmark_folder_closed.png \
|
|
||||||
res/drawable-hdpi/bookmark_folder_opened.png \
|
|
||||||
res/drawable-hdpi/alert_camera.png \
|
|
||||||
res/drawable-hdpi/alert_mic.png \
|
|
||||||
res/drawable-hdpi/alert_mic_camera.png \
|
|
||||||
res/drawable-hdpi/arrow_popup_bg.9.png \
|
|
||||||
res/drawable-hdpi/home_tab_menu_strip.9.png \
|
|
||||||
res/drawable-hdpi/ic_menu_addons_filler.png \
|
|
||||||
res/drawable-hdpi/ic_menu_bookmark_add.png \
|
|
||||||
res/drawable-hdpi/ic_menu_bookmark_remove.png \
|
|
||||||
res/drawable-hdpi/ic_menu_character_encoding.png \
|
|
||||||
res/drawable-hdpi/close.png \
|
|
||||||
res/drawable-hdpi/ic_menu_forward.png \
|
|
||||||
res/drawable-hdpi/ic_menu_guest.png \
|
|
||||||
res/drawable-hdpi/ic_menu_new_private_tab.png \
|
|
||||||
res/drawable-hdpi/ic_menu_new_tab.png \
|
|
||||||
res/drawable-hdpi/ic_menu_reload.png \
|
|
||||||
res/drawable-hdpi/ic_status_logo.png \
|
|
||||||
res/drawable-hdpi/ic_url_bar_go.png \
|
|
||||||
res/drawable-hdpi/ic_url_bar_reader.png \
|
|
||||||
res/drawable-hdpi/ic_url_bar_search.png \
|
|
||||||
res/drawable-hdpi/ic_url_bar_star.png \
|
|
||||||
res/drawable-hdpi/ic_url_bar_tab.png \
|
|
||||||
res/drawable-hdpi/icon_bookmarks_empty.png \
|
|
||||||
res/drawable-hdpi/icon_last_tabs.png \
|
|
||||||
res/drawable-hdpi/icon_last_tabs_empty.png \
|
|
||||||
res/drawable-hdpi/icon_most_recent.png \
|
|
||||||
res/drawable-hdpi/icon_most_recent_empty.png \
|
|
||||||
res/drawable-hdpi/icon_most_visited.png \
|
|
||||||
res/drawable-hdpi/icon_openinapp.png \
|
|
||||||
res/drawable-hdpi/icon_pageaction.png \
|
|
||||||
res/drawable-hdpi/icon_reading_list_empty.png \
|
|
||||||
res/drawable-hdpi/tab_indicator_divider.9.png \
|
|
||||||
res/drawable-hdpi/tab_indicator_selected.9.png \
|
|
||||||
res/drawable-hdpi/tab_indicator_selected_focused.9.png \
|
|
||||||
res/drawable-hdpi/spinner_default.9.png \
|
|
||||||
res/drawable-hdpi/spinner_focused.9.png \
|
|
||||||
res/drawable-hdpi/spinner_pressed.9.png \
|
|
||||||
res/drawable-hdpi/tab_new.png \
|
|
||||||
res/drawable-hdpi/tab_new_pb.png \
|
|
||||||
res/drawable-hdpi/tab_close.png \
|
|
||||||
res/drawable-hdpi/tab_thumbnail_default.png \
|
|
||||||
res/drawable-hdpi/tab_thumbnail_shadow.png \
|
|
||||||
res/drawable-hdpi/tabs_count.png \
|
|
||||||
res/drawable-hdpi/tabs_count_foreground.png \
|
|
||||||
res/drawable-hdpi/url_bar_entry_default.9.png \
|
|
||||||
res/drawable-hdpi/url_bar_entry_default_pb.9.png \
|
|
||||||
res/drawable-hdpi/url_bar_entry_pressed.9.png \
|
|
||||||
res/drawable-hdpi/url_bar_entry_pressed_pb.9.png \
|
|
||||||
res/drawable-hdpi/tip_addsearch.png \
|
|
||||||
res/drawable-hdpi/find_close.png \
|
|
||||||
res/drawable-hdpi/find_next.png \
|
|
||||||
res/drawable-hdpi/find_prev.png \
|
|
||||||
res/drawable-hdpi/larry.png \
|
|
||||||
res/drawable-hdpi/lock_identified.png \
|
|
||||||
res/drawable-hdpi/lock_verified.png \
|
|
||||||
res/drawable-hdpi/menu.png \
|
|
||||||
res/drawable-hdpi/menu_pb.png \
|
|
||||||
res/drawable-hdpi/menu_panel_bg.9.png \
|
|
||||||
res/drawable-hdpi/menu_popup_bg.9.png \
|
|
||||||
res/drawable-hdpi/menu_popup_arrow_bottom.png \
|
|
||||||
res/drawable-hdpi/menu_popup_arrow_top.png \
|
|
||||||
res/drawable-hdpi/menu_item_check.png \
|
|
||||||
res/drawable-hdpi/menu_item_more.png \
|
|
||||||
res/drawable-hdpi/menu_item_uncheck.png \
|
|
||||||
res/drawable-hdpi/pin.png \
|
|
||||||
res/drawable-hdpi/play.png \
|
|
||||||
res/drawable-hdpi/pause.png \
|
|
||||||
res/drawable-hdpi/shield.png \
|
|
||||||
res/drawable-hdpi/shield_doorhanger.png \
|
|
||||||
res/drawable-hdpi/tabs_normal.png \
|
|
||||||
res/drawable-hdpi/tabs_private.png \
|
|
||||||
res/drawable-hdpi/tabs_synced.png \
|
|
||||||
res/drawable-hdpi/top_site_add.png \
|
|
||||||
res/drawable-hdpi/urlbar_stop.png \
|
|
||||||
res/drawable-hdpi/reader.png \
|
|
||||||
res/drawable-hdpi/reader_cropped.png \
|
|
||||||
res/drawable-hdpi/reader_active.png \
|
|
||||||
res/drawable-hdpi/reading_list.png \
|
|
||||||
res/drawable-hdpi/validation_arrow.png \
|
|
||||||
res/drawable-hdpi/validation_arrow_inverted.png \
|
|
||||||
res/drawable-hdpi/validation_bg.9.png \
|
|
||||||
res/drawable-hdpi/handle_end.png \
|
|
||||||
res/drawable-hdpi/handle_middle.png \
|
|
||||||
res/drawable-hdpi/handle_start.png \
|
|
||||||
res/drawable-hdpi/history_tabs_indicator_selected.9.png \
|
|
||||||
res/drawable-hdpi/warning.png \
|
|
||||||
res/drawable-hdpi/warning_doorhanger.png \
|
|
||||||
$(NULL)
|
|
||||||
|
|
||||||
RES_DRAWABLE_XHDPI = \
|
|
||||||
res/drawable-xhdpi/blank.png \
|
|
||||||
res/drawable-xhdpi/favicon.png \
|
|
||||||
res/drawable-xhdpi/folder.png \
|
|
||||||
res/drawable-xhdpi/abouthome_thumbnail.png \
|
|
||||||
res/drawable-xhdpi/url_bar_entry_default.9.png \
|
|
||||||
res/drawable-xhdpi/url_bar_entry_default_pb.9.png \
|
|
||||||
res/drawable-xhdpi/url_bar_entry_pressed.9.png \
|
|
||||||
res/drawable-xhdpi/url_bar_entry_pressed_pb.9.png \
|
|
||||||
res/drawable-xhdpi/alert_addon.png \
|
|
||||||
res/drawable-xhdpi/alert_app.png \
|
|
||||||
res/drawable-xhdpi/alert_download.png \
|
|
||||||
res/drawable-xhdpi/bookmark_folder_closed.png \
|
|
||||||
res/drawable-xhdpi/bookmark_folder_opened.png \
|
|
||||||
res/drawable-xhdpi/alert_camera.png \
|
|
||||||
res/drawable-xhdpi/alert_mic.png \
|
|
||||||
res/drawable-xhdpi/alert_mic_camera.png \
|
|
||||||
res/drawable-xhdpi/arrow_popup_bg.9.png \
|
|
||||||
res/drawable-xhdpi/home_tab_menu_strip.9.png \
|
|
||||||
res/drawable-xhdpi/grid_icon_bg_activated.9.png \
|
|
||||||
res/drawable-xhdpi/grid_icon_bg_focused.9.png \
|
|
||||||
res/drawable-xhdpi/ic_menu_addons_filler.png \
|
|
||||||
res/drawable-xhdpi/ic_menu_bookmark_add.png \
|
|
||||||
res/drawable-xhdpi/ic_menu_bookmark_remove.png \
|
|
||||||
res/drawable-xhdpi/close.png \
|
|
||||||
res/drawable-xhdpi/ic_menu_character_encoding.png \
|
|
||||||
res/drawable-xhdpi/ic_menu_forward.png \
|
|
||||||
res/drawable-xhdpi/ic_menu_guest.png \
|
|
||||||
res/drawable-xhdpi/ic_menu_new_private_tab.png \
|
|
||||||
res/drawable-xhdpi/ic_menu_new_tab.png \
|
|
||||||
res/drawable-xhdpi/ic_menu_reload.png \
|
|
||||||
res/drawable-xhdpi/ic_status_logo.png \
|
|
||||||
res/drawable-xhdpi/ic_url_bar_go.png \
|
|
||||||
res/drawable-xhdpi/ic_url_bar_reader.png \
|
|
||||||
res/drawable-xhdpi/ic_url_bar_search.png \
|
|
||||||
res/drawable-xhdpi/ic_url_bar_star.png \
|
|
||||||
res/drawable-xhdpi/ic_url_bar_tab.png \
|
|
||||||
res/drawable-xhdpi/icon_bookmarks_empty.png \
|
|
||||||
res/drawable-xhdpi/icon_last_tabs.png \
|
|
||||||
res/drawable-xhdpi/icon_last_tabs_empty.png \
|
|
||||||
res/drawable-xhdpi/icon_most_recent.png \
|
|
||||||
res/drawable-xhdpi/icon_most_recent_empty.png \
|
|
||||||
res/drawable-xhdpi/icon_most_visited.png \
|
|
||||||
res/drawable-xhdpi/icon_openinapp.png \
|
|
||||||
res/drawable-xhdpi/icon_pageaction.png \
|
|
||||||
res/drawable-xhdpi/icon_reading_list_empty.png \
|
|
||||||
res/drawable-xhdpi/spinner_default.9.png \
|
|
||||||
res/drawable-xhdpi/spinner_focused.9.png \
|
|
||||||
res/drawable-xhdpi/spinner_pressed.9.png \
|
|
||||||
res/drawable-xhdpi/tab_new.png \
|
|
||||||
res/drawable-xhdpi/tab_new_pb.png \
|
|
||||||
res/drawable-xhdpi/tab_close.png \
|
|
||||||
res/drawable-xhdpi/tab_thumbnail_default.png \
|
|
||||||
res/drawable-xhdpi/tab_thumbnail_shadow.png \
|
|
||||||
res/drawable-xhdpi/tabs_count.png \
|
|
||||||
res/drawable-xhdpi/tabs_count_foreground.png \
|
|
||||||
res/drawable-xhdpi/tip_addsearch.png \
|
|
||||||
res/drawable-xhdpi/find_close.png \
|
|
||||||
res/drawable-xhdpi/find_next.png \
|
|
||||||
res/drawable-xhdpi/find_prev.png \
|
|
||||||
res/drawable-xhdpi/top_site_add.png \
|
|
||||||
res/drawable-xhdpi/urlbar_stop.png \
|
|
||||||
res/drawable-xhdpi/reader.png \
|
|
||||||
res/drawable-xhdpi/reader_cropped.png \
|
|
||||||
res/drawable-xhdpi/reader_active.png \
|
|
||||||
res/drawable-xhdpi/reading_list.png \
|
|
||||||
res/drawable-xhdpi/larry.png \
|
|
||||||
res/drawable-xhdpi/lock_identified.png \
|
|
||||||
res/drawable-xhdpi/lock_verified.png \
|
|
||||||
res/drawable-xhdpi/menu.png \
|
|
||||||
res/drawable-xhdpi/menu_pb.png \
|
|
||||||
res/drawable-xhdpi/menu_panel_bg.9.png \
|
|
||||||
res/drawable-xhdpi/menu_popup_bg.9.png \
|
|
||||||
res/drawable-xhdpi/menu_popup_arrow_bottom.png \
|
|
||||||
res/drawable-xhdpi/menu_popup_arrow_top.png \
|
|
||||||
res/drawable-xhdpi/menu_item_check.png \
|
|
||||||
res/drawable-xhdpi/menu_item_more.png \
|
|
||||||
res/drawable-xhdpi/menu_item_uncheck.png \
|
|
||||||
res/drawable-xhdpi/pin.png \
|
|
||||||
res/drawable-xhdpi/play.png \
|
|
||||||
res/drawable-xhdpi/pause.png \
|
|
||||||
res/drawable-xhdpi/shield.png \
|
|
||||||
res/drawable-xhdpi/shield_doorhanger.png \
|
|
||||||
res/drawable-xhdpi/tab_indicator_divider.9.png \
|
|
||||||
res/drawable-xhdpi/tab_indicator_selected.9.png \
|
|
||||||
res/drawable-xhdpi/tab_indicator_selected_focused.9.png \
|
|
||||||
res/drawable-xhdpi/tabs_normal.png \
|
|
||||||
res/drawable-xhdpi/tabs_private.png \
|
|
||||||
res/drawable-xhdpi/tabs_synced.png \
|
|
||||||
res/drawable-xhdpi/validation_arrow.png \
|
|
||||||
res/drawable-xhdpi/validation_arrow_inverted.png \
|
|
||||||
res/drawable-xhdpi/validation_bg.9.png \
|
|
||||||
res/drawable-xhdpi/handle_end.png \
|
|
||||||
res/drawable-xhdpi/handle_middle.png \
|
|
||||||
res/drawable-xhdpi/handle_start.png \
|
|
||||||
res/drawable-xhdpi/history_tabs_indicator_selected.9.png \
|
|
||||||
res/drawable-xhdpi/warning.png \
|
|
||||||
res/drawable-xhdpi/warning_doorhanger.png \
|
|
||||||
$(NULL)
|
|
||||||
|
|
||||||
RES_DRAWABLE_MDPI_V11 = \
|
|
||||||
res/drawable-mdpi-v11/alert_addon.png \
|
|
||||||
res/drawable-mdpi-v11/alert_app.png \
|
|
||||||
res/drawable-mdpi-v11/alert_download.png \
|
|
||||||
res/drawable-mdpi-v11/alert_camera.png \
|
|
||||||
res/drawable-mdpi-v11/alert_mic.png \
|
|
||||||
res/drawable-mdpi-v11/alert_mic_camera.png \
|
|
||||||
res/drawable-mdpi-v11/firefox_settings_alert.png \
|
|
||||||
res/drawable-mdpi-v11/ic_menu_addons.png \
|
|
||||||
res/drawable-mdpi-v11/ic_menu_apps.png \
|
|
||||||
res/drawable-mdpi-v11/ic_menu_back.png \
|
|
||||||
res/drawable-mdpi-v11/ic_menu_bookmark_add.png \
|
|
||||||
res/drawable-mdpi-v11/ic_menu_bookmark_remove.png \
|
|
||||||
res/drawable-mdpi-v11/ic_menu_desktop_mode_off.png \
|
|
||||||
res/drawable-mdpi-v11/ic_menu_desktop_mode_on.png \
|
|
||||||
res/drawable-mdpi-v11/ic_menu_downloads.png \
|
|
||||||
res/drawable-mdpi-v11/ic_menu_find_in_page.png \
|
|
||||||
res/drawable-mdpi-v11/ic_menu_forward.png \
|
|
||||||
res/drawable-mdpi-v11/ic_menu_new_private_tab.png \
|
|
||||||
res/drawable-mdpi-v11/ic_menu_new_tab.png \
|
|
||||||
res/drawable-mdpi-v11/ic_menu_reload.png \
|
|
||||||
res/drawable-mdpi-v11/ic_menu_save_as_pdf.png \
|
|
||||||
res/drawable-mdpi-v11/ic_menu_settings.png \
|
|
||||||
res/drawable-mdpi-v11/ic_menu_share.png \
|
|
||||||
res/drawable-mdpi-v11/ic_menu_tools.png \
|
|
||||||
res/drawable-mdpi-v11/ic_menu_quit.png \
|
|
||||||
res/drawable-mdpi-v11/ic_status_logo.png \
|
|
||||||
$(NULL)
|
|
||||||
|
|
||||||
RES_DRAWABLE_HDPI_V11 = \
|
|
||||||
res/drawable-hdpi-v11/alert_addon.png \
|
|
||||||
res/drawable-hdpi-v11/alert_app.png \
|
|
||||||
res/drawable-hdpi-v11/alert_download.png \
|
|
||||||
res/drawable-hdpi-v11/alert_camera.png \
|
|
||||||
res/drawable-hdpi-v11/alert_mic.png \
|
|
||||||
res/drawable-hdpi-v11/alert_mic_camera.png \
|
|
||||||
res/drawable-hdpi-v11/firefox_settings_alert.png \
|
|
||||||
res/drawable-hdpi-v11/ic_menu_addons.png \
|
|
||||||
res/drawable-hdpi-v11/ic_menu_apps.png \
|
|
||||||
res/drawable-hdpi-v11/ic_menu_back.png \
|
|
||||||
res/drawable-hdpi-v11/ic_menu_bookmark_add.png \
|
|
||||||
res/drawable-hdpi-v11/ic_menu_bookmark_remove.png \
|
|
||||||
res/drawable-hdpi-v11/ic_menu_desktop_mode_off.png \
|
|
||||||
res/drawable-hdpi-v11/ic_menu_desktop_mode_on.png \
|
|
||||||
res/drawable-hdpi-v11/ic_menu_downloads.png \
|
|
||||||
res/drawable-hdpi-v11/ic_menu_find_in_page.png \
|
|
||||||
res/drawable-hdpi-v11/ic_menu_forward.png \
|
|
||||||
res/drawable-hdpi-v11/ic_menu_new_private_tab.png \
|
|
||||||
res/drawable-hdpi-v11/ic_menu_new_tab.png \
|
|
||||||
res/drawable-hdpi-v11/ic_menu_reload.png \
|
|
||||||
res/drawable-hdpi-v11/ic_menu_save_as_pdf.png \
|
|
||||||
res/drawable-hdpi-v11/ic_menu_settings.png \
|
|
||||||
res/drawable-hdpi-v11/ic_menu_share.png \
|
|
||||||
res/drawable-hdpi-v11/ic_menu_tools.png \
|
|
||||||
res/drawable-hdpi-v11/ic_menu_quit.png \
|
|
||||||
res/drawable-hdpi-v11/ic_status_logo.png \
|
|
||||||
$(NULL)
|
|
||||||
|
|
||||||
RES_DRAWABLE_XHDPI_V11 = \
|
|
||||||
res/drawable-xhdpi-v11/alert_addon.png \
|
|
||||||
res/drawable-xhdpi-v11/alert_app.png \
|
|
||||||
res/drawable-xhdpi-v11/alert_download.png \
|
|
||||||
res/drawable-xhdpi-v11/alert_camera.png \
|
|
||||||
res/drawable-xhdpi-v11/alert_mic.png \
|
|
||||||
res/drawable-xhdpi-v11/alert_mic_camera.png \
|
|
||||||
res/drawable-xhdpi-v11/firefox_settings_alert.png \
|
|
||||||
res/drawable-xhdpi-v11/ic_menu_addons.png \
|
|
||||||
res/drawable-xhdpi-v11/ic_menu_apps.png \
|
|
||||||
res/drawable-xhdpi-v11/ic_menu_back.png \
|
|
||||||
res/drawable-xhdpi-v11/ic_menu_bookmark_add.png \
|
|
||||||
res/drawable-xhdpi-v11/ic_menu_bookmark_remove.png \
|
|
||||||
res/drawable-xhdpi-v11/ic_menu_desktop_mode_off.png \
|
|
||||||
res/drawable-xhdpi-v11/ic_menu_desktop_mode_on.png \
|
|
||||||
res/drawable-xhdpi-v11/ic_menu_downloads.png \
|
|
||||||
res/drawable-xhdpi-v11/ic_menu_find_in_page.png \
|
|
||||||
res/drawable-xhdpi-v11/ic_menu_forward.png \
|
|
||||||
res/drawable-xhdpi-v11/ic_menu_new_private_tab.png \
|
|
||||||
res/drawable-xhdpi-v11/ic_menu_new_tab.png \
|
|
||||||
res/drawable-xhdpi-v11/ic_menu_reload.png \
|
|
||||||
res/drawable-xhdpi-v11/ic_menu_save_as_pdf.png \
|
|
||||||
res/drawable-xhdpi-v11/ic_menu_settings.png \
|
|
||||||
res/drawable-xhdpi-v11/ic_menu_share.png \
|
|
||||||
res/drawable-xhdpi-v11/ic_menu_tools.png \
|
|
||||||
res/drawable-xhdpi-v11/ic_menu_quit.png \
|
|
||||||
res/drawable-xhdpi-v11/ic_status_logo.png \
|
|
||||||
$(NULL)
|
|
||||||
|
|
||||||
RES_DRAWABLE_LARGE_LAND_V11 = \
|
|
||||||
res/drawable-large-land-v11/home_history_tabs_indicator.xml \
|
|
||||||
$(NULL)
|
|
||||||
|
|
||||||
RES_DRAWABLE_LARGE_MDPI_V11 = \
|
|
||||||
res/drawable-large-mdpi-v11/arrow_popup_bg.9.png \
|
|
||||||
res/drawable-large-mdpi-v11/ic_menu_reload.png \
|
|
||||||
res/drawable-large-mdpi-v11/ic_menu_forward.png \
|
|
||||||
res/drawable-large-mdpi-v11/menu.png \
|
|
||||||
$(NULL)
|
|
||||||
|
|
||||||
RES_DRAWABLE_LARGE_HDPI_V11 = \
|
|
||||||
res/drawable-large-hdpi-v11/arrow_popup_bg.9.png \
|
|
||||||
res/drawable-large-hdpi-v11/ic_menu_reload.png \
|
|
||||||
res/drawable-large-hdpi-v11/ic_menu_forward.png \
|
|
||||||
res/drawable-large-hdpi-v11/menu.png \
|
|
||||||
$(NULL)
|
|
||||||
|
|
||||||
RES_DRAWABLE_LARGE_XHDPI_V11 = \
|
|
||||||
res/drawable-large-xhdpi-v11/arrow_popup_bg.9.png \
|
|
||||||
res/drawable-large-xhdpi-v11/ic_menu_reload.png \
|
|
||||||
res/drawable-large-xhdpi-v11/ic_menu_forward.png \
|
|
||||||
res/drawable-large-xhdpi-v11/menu.png \
|
|
||||||
$(NULL)
|
|
||||||
|
|
||||||
RES_DRAWABLE_XLARGE_V11 = \
|
|
||||||
res/drawable-xlarge-v11/home_history_tabs_indicator.xml \
|
|
||||||
$(NULL)
|
|
||||||
|
|
||||||
RES_DRAWABLE_XLARGE_MDPI_V11 = \
|
|
||||||
res/drawable-xlarge-mdpi-v11/ic_menu_bookmark_add.png \
|
|
||||||
res/drawable-xlarge-mdpi-v11/ic_menu_bookmark_remove.png \
|
|
||||||
$(NULL)
|
|
||||||
|
|
||||||
RES_DRAWABLE_XLARGE_HDPI_V11 = \
|
|
||||||
res/drawable-xlarge-hdpi-v11/ic_menu_bookmark_add.png \
|
|
||||||
res/drawable-xlarge-hdpi-v11/ic_menu_bookmark_remove.png \
|
|
||||||
$(NULL)
|
|
||||||
|
|
||||||
RES_DRAWABLE_XLARGE_XHDPI_V11 = \
|
|
||||||
res/drawable-xlarge-xhdpi-v11/ic_menu_bookmark_add.png \
|
|
||||||
res/drawable-xlarge-xhdpi-v11/ic_menu_bookmark_remove.png \
|
|
||||||
$(NULL)
|
|
||||||
|
|
||||||
RES_COLOR = \
|
|
||||||
res/color/primary_text.xml \
|
|
||||||
res/color/primary_text_inverse.xml \
|
|
||||||
res/color/secondary_text.xml \
|
|
||||||
res/color/secondary_text_inverse.xml \
|
|
||||||
res/color/select_item_multichoice.xml \
|
|
||||||
res/color/tertiary_text.xml \
|
|
||||||
res/color/tertiary_text_inverse.xml \
|
|
||||||
res/color/top_sites_grid_item_title.xml \
|
|
||||||
res/color/url_bar_title.xml \
|
|
||||||
res/color/url_bar_title_hint.xml \
|
|
||||||
$(NULL)
|
|
||||||
|
|
||||||
RES_MENU = \
|
|
||||||
res/menu/browser_app_menu.xml \
|
|
||||||
res/menu/gecko_app_menu.xml \
|
|
||||||
res/menu/home_contextmenu.xml \
|
|
||||||
res/menu/titlebar_contextmenu.xml \
|
|
||||||
res/menu/top_sites_contextmenu.xml \
|
|
||||||
res/menu-large-v11/browser_app_menu.xml \
|
|
||||||
res/menu-v11/browser_app_menu.xml \
|
|
||||||
res/menu-xlarge-v11/browser_app_menu.xml \
|
|
||||||
$(NULL)
|
|
||||||
|
|
||||||
JAVA_CLASSPATH = $(ANDROID_SDK)/android.jar
|
JAVA_CLASSPATH = $(ANDROID_SDK)/android.jar
|
||||||
|
|
||||||
ifdef MOZ_CRASHREPORTER
|
ifdef MOZ_CRASHREPORTER
|
||||||
FENNEC_JAVA_FILES += CrashReporter.java
|
FENNEC_JAVA_FILES += CrashReporter.java
|
||||||
RES_DRAWABLE_MDPI += res/drawable-mdpi/crash_reporter.png
|
|
||||||
RES_LAYOUT += res/layout/crash_reporter.xml
|
|
||||||
endif
|
endif
|
||||||
|
|
||||||
RES_DRAWABLE += \
|
|
||||||
$(SYNC_RES_DRAWABLE) \
|
|
||||||
res/drawable/action_bar_button.xml \
|
|
||||||
res/drawable/action_bar_button_inverse.xml \
|
|
||||||
res/drawable/top_sites_thumbnail_bg.xml \
|
|
||||||
res/drawable/url_bar_bg.xml \
|
|
||||||
res/drawable/url_bar_entry.xml \
|
|
||||||
res/drawable/url_bar_nav_button.xml \
|
|
||||||
res/drawable/icon_grid_item_bg.xml \
|
|
||||||
res/drawable/url_bar_right_edge.xml \
|
|
||||||
res/drawable/bookmark_folder.xml \
|
|
||||||
res/drawable/divider_horizontal.xml \
|
|
||||||
res/drawable/divider_vertical.xml \
|
|
||||||
res/drawable/favicon_bg.xml \
|
|
||||||
res/drawable/handle_end_level.xml \
|
|
||||||
res/drawable/handle_start_level.xml \
|
|
||||||
res/drawable/home_history_tabs_indicator.xml \
|
|
||||||
res/drawable/home_page_title_background.xml \
|
|
||||||
res/drawable/home_banner.xml \
|
|
||||||
res/drawable/ic_menu_back.xml \
|
|
||||||
res/drawable/ic_menu_desktop_mode_off.xml \
|
|
||||||
res/drawable/ic_menu_desktop_mode_on.xml \
|
|
||||||
res/drawable/ic_menu_quit.xml \
|
|
||||||
res/drawable/menu_item_state.xml \
|
|
||||||
res/drawable/menu_level.xml \
|
|
||||||
res/drawable/remote_tabs_child_divider.xml \
|
|
||||||
res/drawable/shaped_button.xml \
|
|
||||||
res/drawable/site_security_level.xml \
|
|
||||||
res/drawable/spinner.xml \
|
|
||||||
res/drawable/suggestion_selector.xml \
|
|
||||||
res/drawable/tab_new_level.xml \
|
|
||||||
res/drawable/tab_row.xml \
|
|
||||||
res/drawable/tab_thumbnail.xml \
|
|
||||||
res/drawable/tabs_panel_indicator.xml \
|
|
||||||
res/drawable/textbox_bg.xml \
|
|
||||||
res/drawable/toast_button.xml \
|
|
||||||
res/drawable/webapp_titlebar_bg.xml \
|
|
||||||
$(NULL)
|
|
||||||
|
|
||||||
RESOURCES = \
|
|
||||||
$(RES_ANIM) \
|
|
||||||
$(RES_COLOR) \
|
|
||||||
$(RES_DRAWABLE) \
|
|
||||||
$(RES_DRAWABLE_HDPI) \
|
|
||||||
$(RES_DRAWABLE_HDPI_V11) \
|
|
||||||
$(RES_DRAWABLE_LARGE_LAND_V11) \
|
|
||||||
$(RES_DRAWABLE_LARGE_HDPI_V11) \
|
|
||||||
$(RES_DRAWABLE_LARGE_MDPI_V11) \
|
|
||||||
$(RES_DRAWABLE_LARGE_XHDPI_V11) \
|
|
||||||
$(RES_DRAWABLE_LDPI) \
|
|
||||||
$(RES_DRAWABLE_MDPI) \
|
|
||||||
$(RES_DRAWABLE_MDPI_V11) \
|
|
||||||
$(RES_DRAWABLE_XHDPI) \
|
|
||||||
$(RES_DRAWABLE_XHDPI_V11) \
|
|
||||||
$(RES_DRAWABLE_XLARGE_V11) \
|
|
||||||
$(RES_DRAWABLE_XLARGE_HDPI_V11) \
|
|
||||||
$(RES_DRAWABLE_XLARGE_MDPI_V11) \
|
|
||||||
$(RES_DRAWABLE_XLARGE_XHDPI_V11) \
|
|
||||||
$(RES_LAYOUT) \
|
|
||||||
$(RES_LAYOUT_LARGE_LAND_V11) \
|
|
||||||
$(RES_LAYOUT_LARGE_V11) \
|
|
||||||
$(RES_LAYOUT_XLARGE_LAND_V11) \
|
|
||||||
$(RES_LAYOUT_XLARGE_V11) \
|
|
||||||
$(RES_MENU) \
|
|
||||||
$(RES_VALUES) \
|
|
||||||
$(RES_VALUES_LAND) \
|
|
||||||
$(RES_VALUES_LAND_V14) \
|
|
||||||
$(RES_VALUES_LARGE_LAND_V11) \
|
|
||||||
$(RES_VALUES_LARGE_V11) \
|
|
||||||
$(RES_VALUES_V11) \
|
|
||||||
$(RES_VALUES_V14) \
|
|
||||||
$(RES_VALUES_V16) \
|
|
||||||
$(RES_VALUES_XLARGE_LAND_V11) \
|
|
||||||
$(RES_VALUES_XLARGE_V11) \
|
|
||||||
$(RES_XML) \
|
|
||||||
$(RES_XML_V11) \
|
|
||||||
$(NULL)
|
|
||||||
|
|
||||||
RES_DIRS= \
|
|
||||||
res/layout \
|
|
||||||
res/layout-large-v11 \
|
|
||||||
res/layout-large-land-v11 \
|
|
||||||
res/layout-xlarge-v11 \
|
|
||||||
res/values \
|
|
||||||
res/values-v11 \
|
|
||||||
res/values-large-v11 \
|
|
||||||
res/values-xlarge-land-v11 \
|
|
||||||
res/values-xlarge-v11 \
|
|
||||||
res/values-v14 \
|
|
||||||
res/values-v16 \
|
|
||||||
res/xml \
|
|
||||||
res/xml-v11 \
|
|
||||||
res/anim \
|
|
||||||
res/drawable-ldpi \
|
|
||||||
res/drawable-mdpi \
|
|
||||||
res/drawable-hdpi \
|
|
||||||
res/drawable-xhdpi \
|
|
||||||
res/drawable \
|
|
||||||
res/drawable-mdpi-v11 \
|
|
||||||
res/drawable-hdpi-v11 \
|
|
||||||
res/drawable-xhdpi-v11 \
|
|
||||||
res/drawable-large-land-v11 \
|
|
||||||
res/drawable-large-mdpi-v11 \
|
|
||||||
res/drawable-large-hdpi-v11 \
|
|
||||||
res/drawable-large-xhdpi-v11 \
|
|
||||||
res/drawable-xlarge-v11 \
|
|
||||||
res/drawable-xlarge-mdpi-v11 \
|
|
||||||
res/drawable-xlarge-hdpi-v11 \
|
|
||||||
res/drawable-xlarge-xhdpi-v11 \
|
|
||||||
res/color \
|
|
||||||
res/menu \
|
|
||||||
res/menu-v11 \
|
|
||||||
res/menu-large-v11 \
|
|
||||||
res/menu-xlarge-v11 \
|
|
||||||
$(NULL)
|
|
||||||
|
|
||||||
ALL_JARS = \
|
ALL_JARS = \
|
||||||
jars/gecko-browser.jar \
|
jars/gecko-browser.jar \
|
||||||
jars/gecko-mozglue.jar \
|
jars/gecko-mozglue.jar \
|
||||||
|
@ -1280,6 +503,10 @@ endif
|
||||||
|
|
||||||
include $(topsrcdir)/config/makefiles/java-build.mk
|
include $(topsrcdir)/config/makefiles/java-build.mk
|
||||||
|
|
||||||
|
# We process ANDROID_RESFILES specially for now; the following flag
|
||||||
|
# disables the default processing.
|
||||||
|
IGNORE_ANDROID_RESFILES=1
|
||||||
|
|
||||||
include $(topsrcdir)/config/rules.mk
|
include $(topsrcdir)/config/rules.mk
|
||||||
|
|
||||||
# Override the Java settings with some specific android settings
|
# Override the Java settings with some specific android settings
|
||||||
|
@ -1360,12 +587,14 @@ res/drawable-xxhdpi/icon.png: $(ICON_PATH_XXHDPI)
|
||||||
$(NSINSTALL) -D res/drawable-xxhdpi
|
$(NSINSTALL) -D res/drawable-xxhdpi
|
||||||
cp $(ICON_PATH_XXHDPI) $@
|
cp $(ICON_PATH_XXHDPI) $@
|
||||||
|
|
||||||
$(call mkdir_deps,$(RES_DIRS)): $(subst res/,$(srcdir)/resources/,$(RESOURCES)) Makefile
|
ANDROID_RESDIRS := $(subst resources/,res/,$(sort $(dir $(ANDROID_RESFILES))))
|
||||||
|
|
||||||
|
$(call mkdir_deps,$(ANDROID_RESDIRS)): $(ANDROID_RESFILES) Makefile
|
||||||
$(RM) -r $(@D)
|
$(RM) -r $(@D)
|
||||||
$(NSINSTALL) -D $(@D)
|
$(NSINSTALL) -D $(@D)
|
||||||
$(TOUCH) $@
|
$(TOUCH) $@
|
||||||
|
|
||||||
$(RESOURCES): $(call mkdir_deps,$(RES_DIRS)) $(subst res/,$(srcdir)/resources/,$(RESOURCES))
|
$(subst resources/,res/,$(ANDROID_RESFILES)): $(call mkdir_deps,$(ANDROID_RESDIRS)) $(ANDROID_RESFILES)
|
||||||
@echo "creating $@"
|
@echo "creating $@"
|
||||||
$(NSINSTALL) $(subst res/,$(srcdir)/resources/,$@) $(dir $@)
|
$(NSINSTALL) $(subst res/,$(srcdir)/resources/,$@) $(dir $@)
|
||||||
|
|
||||||
|
@ -1376,14 +605,10 @@ res/values/strings.xml: $(call mkdir_deps,res/values)
|
||||||
# rebuild gecko.ap_ if any of them change.
|
# rebuild gecko.ap_ if any of them change.
|
||||||
MULTILOCALE_STRINGS_XML_FILES := $(wildcard res/values-*/strings.xml)
|
MULTILOCALE_STRINGS_XML_FILES := $(wildcard res/values-*/strings.xml)
|
||||||
all_resources = \
|
all_resources = \
|
||||||
res/drawable-mdpi/icon.png \
|
|
||||||
res/drawable-hdpi/icon.png \
|
|
||||||
res/drawable-xhdpi/icon.png \
|
|
||||||
res/drawable-xxhdpi/icon.png \
|
|
||||||
res/values/strings.xml \
|
|
||||||
$(MULTILOCALE_STRINGS_XML_FILES) \
|
$(MULTILOCALE_STRINGS_XML_FILES) \
|
||||||
AndroidManifest.xml \
|
AndroidManifest.xml \
|
||||||
$(RESOURCES) \
|
$(subst resources/,res/,$(ANDROID_RESFILES)) \
|
||||||
|
$(ANDROID_GENERATED_RESFILES) \
|
||||||
$(NULL)
|
$(NULL)
|
||||||
|
|
||||||
R.java: $(all_resources)
|
R.java: $(all_resources)
|
||||||
|
|
|
@ -40,6 +40,7 @@ SYNC_JAVA_FILES := \
|
||||||
background/db/Tab.java \
|
background/db/Tab.java \
|
||||||
background/healthreport/Environment.java \
|
background/healthreport/Environment.java \
|
||||||
background/healthreport/EnvironmentBuilder.java \
|
background/healthreport/EnvironmentBuilder.java \
|
||||||
|
background/healthreport/EnvironmentV1.java \
|
||||||
background/healthreport/HealthReportBroadcastReceiver.java \
|
background/healthreport/HealthReportBroadcastReceiver.java \
|
||||||
background/healthreport/HealthReportBroadcastService.java \
|
background/healthreport/HealthReportBroadcastService.java \
|
||||||
background/healthreport/HealthReportDatabases.java \
|
background/healthreport/HealthReportDatabases.java \
|
||||||
|
@ -301,53 +302,6 @@ SYNC_JAVA_FILES := \
|
||||||
sync/Utils.java \
|
sync/Utils.java \
|
||||||
$(NULL)
|
$(NULL)
|
||||||
|
|
||||||
SYNC_RES_DRAWABLE := \
|
|
||||||
res/drawable/pin_background.xml \
|
|
||||||
$(NULL)
|
|
||||||
|
|
||||||
SYNC_RES_DRAWABLE_LDPI := \
|
|
||||||
$(NULL)
|
|
||||||
|
|
||||||
SYNC_RES_DRAWABLE_MDPI := \
|
|
||||||
res/drawable-mdpi/desktop.png \
|
|
||||||
res/drawable-mdpi/mobile.png \
|
|
||||||
$(NULL)
|
|
||||||
|
|
||||||
SYNC_RES_DRAWABLE_HDPI := \
|
|
||||||
$(NULL)
|
|
||||||
|
|
||||||
SYNC_RES_LAYOUT := \
|
|
||||||
res/layout/sync_account.xml \
|
|
||||||
res/layout/sync_list_item.xml \
|
|
||||||
res/layout/sync_redirect_to_setup.xml \
|
|
||||||
res/layout/sync_send_tab.xml \
|
|
||||||
res/layout/sync_setup.xml \
|
|
||||||
res/layout/sync_setup_failure.xml \
|
|
||||||
res/layout/sync_setup_jpake_waiting.xml \
|
|
||||||
res/layout/sync_setup_nointernet.xml \
|
|
||||||
res/layout/sync_setup_pair.xml \
|
|
||||||
res/layout/sync_setup_success.xml \
|
|
||||||
res/layout/sync_setup_webview.xml \
|
|
||||||
$(NULL)
|
|
||||||
|
|
||||||
SYNC_RES_VALUES := \
|
|
||||||
res/values/sync_styles.xml \
|
|
||||||
$(NULL)
|
|
||||||
|
|
||||||
SYNC_RES_VALUES_V11 := \
|
|
||||||
res/values-v11/sync_styles.xml \
|
|
||||||
$(NULL)
|
|
||||||
|
|
||||||
SYNC_RES_VALUES_LARGE_V11 := \
|
|
||||||
res/values-large-v11/sync_styles.xml \
|
|
||||||
$(NULL)
|
|
||||||
|
|
||||||
SYNC_RES_XML := \
|
|
||||||
res/xml/sync_authenticator.xml \
|
|
||||||
res/xml/sync_syncadapter.xml \
|
|
||||||
res/xml/sync_options.xml \
|
|
||||||
$(NULL)
|
|
||||||
|
|
||||||
SYNC_THIRDPARTY_JAVA_FILES := \
|
SYNC_THIRDPARTY_JAVA_FILES := \
|
||||||
httpclientandroidlib/androidextra/HttpClientAndroidLog.java \
|
httpclientandroidlib/androidextra/HttpClientAndroidLog.java \
|
||||||
httpclientandroidlib/annotation/GuardedBy.java \
|
httpclientandroidlib/annotation/GuardedBy.java \
|
||||||
|
|
|
@ -0,0 +1,28 @@
|
||||||
|
# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
|
||||||
|
# vim: set filetype=python:
|
||||||
|
# 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/.
|
||||||
|
|
||||||
|
ANDROID_RESFILES += [
|
||||||
|
'resources/drawable-mdpi/desktop.png',
|
||||||
|
'resources/drawable-mdpi/mobile.png',
|
||||||
|
'resources/drawable/pin_background.xml',
|
||||||
|
'resources/layout/sync_account.xml',
|
||||||
|
'resources/layout/sync_list_item.xml',
|
||||||
|
'resources/layout/sync_redirect_to_setup.xml',
|
||||||
|
'resources/layout/sync_send_tab.xml',
|
||||||
|
'resources/layout/sync_setup.xml',
|
||||||
|
'resources/layout/sync_setup_failure.xml',
|
||||||
|
'resources/layout/sync_setup_jpake_waiting.xml',
|
||||||
|
'resources/layout/sync_setup_nointernet.xml',
|
||||||
|
'resources/layout/sync_setup_pair.xml',
|
||||||
|
'resources/layout/sync_setup_success.xml',
|
||||||
|
'resources/layout/sync_setup_webview.xml',
|
||||||
|
'resources/values-large-v11/sync_styles.xml',
|
||||||
|
'resources/values-v11/sync_styles.xml',
|
||||||
|
'resources/values/sync_styles.xml',
|
||||||
|
'resources/xml/sync_authenticator.xml',
|
||||||
|
'resources/xml/sync_options.xml',
|
||||||
|
'resources/xml/sync_syncadapter.xml',
|
||||||
|
]
|
|
@ -4,17 +4,6 @@
|
||||||
|
|
||||||
package org.mozilla.gecko.background.healthreport;
|
package org.mozilla.gecko.background.healthreport;
|
||||||
|
|
||||||
import java.io.UnsupportedEncodingException;
|
|
||||||
import java.security.MessageDigest;
|
|
||||||
import java.security.NoSuchAlgorithmException;
|
|
||||||
import java.util.Iterator;
|
|
||||||
import java.util.SortedSet;
|
|
||||||
|
|
||||||
import org.json.JSONException;
|
|
||||||
import org.json.JSONObject;
|
|
||||||
import org.mozilla.apache.commons.codec.binary.Base64;
|
|
||||||
import org.mozilla.gecko.background.common.log.Logger;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This captures all of the details that define an 'environment' for FHR's purposes.
|
* This captures all of the details that define an 'environment' for FHR's purposes.
|
||||||
* Whenever this format changes, it'll be changing with a build ID, so no migration
|
* Whenever this format changes, it'll be changing with a build ID, so no migration
|
||||||
|
@ -29,246 +18,32 @@ import org.mozilla.gecko.background.common.log.Logger;
|
||||||
* registered an <code>Environment</code>, don't do so again; start from scratch.
|
* registered an <code>Environment</code>, don't do so again; start from scratch.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
public abstract class Environment {
|
public abstract class Environment extends EnvironmentV1 {
|
||||||
private static final String LOG_TAG = "GeckoEnvironment";
|
// Version 2 adds osLocale, appLocale, acceptLangSet, and distribution.
|
||||||
|
public static final int CURRENT_VERSION = 2;
|
||||||
|
|
||||||
public static int VERSION = 1;
|
public String osLocale; // The Android OS "Locale" value.
|
||||||
|
public String appLocale;
|
||||||
protected final Class<? extends EnvironmentAppender> appenderClass;
|
public int acceptLangSet;
|
||||||
|
public String distribution; // ID + version. Typically empty.
|
||||||
protected volatile String hash = null;
|
|
||||||
protected volatile int id = -1;
|
|
||||||
|
|
||||||
// org.mozilla.profile.age.
|
|
||||||
public int profileCreation;
|
|
||||||
|
|
||||||
// org.mozilla.sysinfo.sysinfo.
|
|
||||||
public int cpuCount;
|
|
||||||
public int memoryMB;
|
|
||||||
public String architecture;
|
|
||||||
public String sysName;
|
|
||||||
public String sysVersion; // Kernel.
|
|
||||||
|
|
||||||
// geckoAppInfo. Not sure if we can/should provide this on Android.
|
|
||||||
public String vendor;
|
|
||||||
public String appName;
|
|
||||||
public String appID;
|
|
||||||
public String appVersion;
|
|
||||||
public String appBuildID;
|
|
||||||
public String platformVersion;
|
|
||||||
public String platformBuildID;
|
|
||||||
public String os;
|
|
||||||
public String xpcomabi;
|
|
||||||
public String updateChannel;
|
|
||||||
|
|
||||||
// appInfo.
|
|
||||||
public int isBlocklistEnabled;
|
|
||||||
public int isTelemetryEnabled;
|
|
||||||
// public int isDefaultBrowser; // This is meaningless on Android.
|
|
||||||
|
|
||||||
// org.mozilla.addons.active.
|
|
||||||
public JSONObject addons = null;
|
|
||||||
|
|
||||||
// org.mozilla.addons.counts.
|
|
||||||
public int extensionCount;
|
|
||||||
public int pluginCount;
|
|
||||||
public int themeCount;
|
|
||||||
|
|
||||||
public Environment() {
|
public Environment() {
|
||||||
this(Environment.HashAppender.class);
|
this(Environment.HashAppender.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Environment(Class<? extends EnvironmentAppender> appenderClass) {
|
public Environment(Class<? extends EnvironmentAppender> appenderClass) {
|
||||||
this.appenderClass = appenderClass;
|
super(appenderClass);
|
||||||
|
version = CURRENT_VERSION;
|
||||||
}
|
}
|
||||||
|
|
||||||
public JSONObject getNonIgnoredAddons() {
|
@Override
|
||||||
if (addons == null) {
|
protected void appendHash(EnvironmentAppender appender) {
|
||||||
return null;
|
super.appendHash(appender);
|
||||||
}
|
|
||||||
JSONObject out = new JSONObject();
|
// v2.
|
||||||
@SuppressWarnings("unchecked")
|
appender.append(osLocale);
|
||||||
Iterator<String> keys = addons.keys();
|
appender.append(appLocale);
|
||||||
while (keys.hasNext()) {
|
appender.append(acceptLangSet);
|
||||||
try {
|
appender.append(distribution);
|
||||||
final String key = keys.next();
|
|
||||||
final Object obj = addons.get(key);
|
|
||||||
if (obj != null && obj instanceof JSONObject && ((JSONObject) obj).optBoolean("ignore", false)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
out.put(key, obj);
|
|
||||||
} catch (JSONException ex) {
|
|
||||||
// Do nothing.
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return out;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* We break out this interface in order to allow for testing -- pass in your
|
|
||||||
* own appender that just records strings, for example.
|
|
||||||
*/
|
|
||||||
public static abstract class EnvironmentAppender {
|
|
||||||
public abstract void append(String s);
|
|
||||||
public abstract void append(int v);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class HashAppender extends EnvironmentAppender {
|
|
||||||
final MessageDigest hasher;
|
|
||||||
|
|
||||||
public HashAppender() throws NoSuchAlgorithmException {
|
|
||||||
// Note to the security minded reader: we deliberately use SHA-1 here, not
|
|
||||||
// a stronger hash. These identifiers don't strictly need a cryptographic
|
|
||||||
// hash function, because there is negligible value in attacking the hash.
|
|
||||||
// We use SHA-1 because it's *shorter* -- the exact same reason that Git
|
|
||||||
// chose SHA-1.
|
|
||||||
hasher = MessageDigest.getInstance("SHA-1");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void append(String s) {
|
|
||||||
try {
|
|
||||||
hasher.update(((s == null) ? "null" : s).getBytes("UTF-8"));
|
|
||||||
} catch (UnsupportedEncodingException e) {
|
|
||||||
// This can never occur. Thanks, Java.
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void append(int profileCreation) {
|
|
||||||
append(Integer.toString(profileCreation, 10));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
// We *could* use ASCII85… but the savings would be negated by the
|
|
||||||
// inclusion of JSON-unsafe characters like double-quote.
|
|
||||||
return new Base64(-1, null, false).encodeAsString(hasher.digest());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Compute the stable hash of the configured environment.
|
|
||||||
*
|
|
||||||
* @return the hash in base34, or null if there was a problem.
|
|
||||||
*/
|
|
||||||
public String getHash() {
|
|
||||||
// It's never unset, so we only care about partial reads. volatile is enough.
|
|
||||||
if (hash != null) {
|
|
||||||
return hash;
|
|
||||||
}
|
|
||||||
|
|
||||||
EnvironmentAppender appender;
|
|
||||||
try {
|
|
||||||
appender = appenderClass.newInstance();
|
|
||||||
} catch (InstantiationException ex) {
|
|
||||||
// Should never happen, but...
|
|
||||||
Logger.warn(LOG_TAG, "Could not compute hash.", ex);
|
|
||||||
return null;
|
|
||||||
} catch (IllegalAccessException ex) {
|
|
||||||
// Should never happen, but...
|
|
||||||
Logger.warn(LOG_TAG, "Could not compute hash.", ex);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
appender.append(profileCreation);
|
|
||||||
appender.append(cpuCount);
|
|
||||||
appender.append(memoryMB);
|
|
||||||
appender.append(architecture);
|
|
||||||
appender.append(sysName);
|
|
||||||
appender.append(sysVersion);
|
|
||||||
appender.append(vendor);
|
|
||||||
appender.append(appName);
|
|
||||||
appender.append(appID);
|
|
||||||
appender.append(appVersion);
|
|
||||||
appender.append(appBuildID);
|
|
||||||
appender.append(platformVersion);
|
|
||||||
appender.append(platformBuildID);
|
|
||||||
appender.append(os);
|
|
||||||
appender.append(xpcomabi);
|
|
||||||
appender.append(updateChannel);
|
|
||||||
appender.append(isBlocklistEnabled);
|
|
||||||
appender.append(isTelemetryEnabled);
|
|
||||||
appender.append(extensionCount);
|
|
||||||
appender.append(pluginCount);
|
|
||||||
appender.append(themeCount);
|
|
||||||
|
|
||||||
// We need sorted values.
|
|
||||||
if (addons != null) {
|
|
||||||
appendSortedAddons(getNonIgnoredAddons(), appender);
|
|
||||||
}
|
|
||||||
|
|
||||||
return hash = appender.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Take a collection of add-on descriptors, appending a consistent string
|
|
||||||
* to the provided builder.
|
|
||||||
*/
|
|
||||||
public static void appendSortedAddons(JSONObject addons,
|
|
||||||
final EnvironmentAppender builder) {
|
|
||||||
final SortedSet<String> keys = HealthReportUtils.sortedKeySet(addons);
|
|
||||||
|
|
||||||
// For each add-on, produce a consistent, sorted mapping of its descriptor.
|
|
||||||
for (String key : keys) {
|
|
||||||
try {
|
|
||||||
JSONObject addon = addons.getJSONObject(key);
|
|
||||||
|
|
||||||
// Now produce the output for this add-on.
|
|
||||||
builder.append(key);
|
|
||||||
builder.append("={");
|
|
||||||
|
|
||||||
for (String addonKey : HealthReportUtils.sortedKeySet(addon)) {
|
|
||||||
builder.append(addonKey);
|
|
||||||
builder.append("==");
|
|
||||||
try {
|
|
||||||
builder.append(addon.get(addonKey).toString());
|
|
||||||
} catch (JSONException e) {
|
|
||||||
builder.append("_e_");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
builder.append("}");
|
|
||||||
} catch (Exception e) {
|
|
||||||
// Muffle.
|
|
||||||
Logger.warn(LOG_TAG, "Invalid add-on for ID " + key);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setJSONForAddons(byte[] json) throws Exception {
|
|
||||||
setJSONForAddons(new String(json, "UTF-8"));
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setJSONForAddons(String json) throws Exception {
|
|
||||||
if (json == null || "null".equals(json)) {
|
|
||||||
addons = null;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
addons = new JSONObject(json);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setJSONForAddons(JSONObject json) {
|
|
||||||
addons = json;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Includes ignored add-ons.
|
|
||||||
*/
|
|
||||||
public String getNormalizedAddonsJSON() {
|
|
||||||
// We trust that our input will already be normalized. If that assumption
|
|
||||||
// is invalidated, then we'll be sorry.
|
|
||||||
return (addons == null) ? "null" : addons.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Ensure that the {@link Environment} has been registered with its
|
|
||||||
* storage layer, and can be used to annotate events.
|
|
||||||
*
|
|
||||||
* It's safe to call this method more than once, and each time you'll
|
|
||||||
* get the same ID.
|
|
||||||
*
|
|
||||||
* @return the integer ID to use in subsequent DB insertions.
|
|
||||||
*/
|
|
||||||
public abstract int register();
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -58,7 +58,13 @@ public class EnvironmentBuilder {
|
||||||
public static interface ProfileInformationProvider {
|
public static interface ProfileInformationProvider {
|
||||||
public boolean isBlocklistEnabled();
|
public boolean isBlocklistEnabled();
|
||||||
public boolean isTelemetryEnabled();
|
public boolean isTelemetryEnabled();
|
||||||
|
public boolean isAcceptLangUserSet();
|
||||||
public long getProfileCreationTime();
|
public long getProfileCreationTime();
|
||||||
|
|
||||||
|
public String getDistributionString();
|
||||||
|
public String getOSLocale();
|
||||||
|
public String getAppLocale();
|
||||||
|
|
||||||
public JSONObject getAddonsJSON();
|
public JSONObject getAddonsJSON();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -124,6 +130,12 @@ public class EnvironmentBuilder {
|
||||||
}
|
}
|
||||||
|
|
||||||
e.addons = addons;
|
e.addons = addons;
|
||||||
|
|
||||||
|
// v2 environment fields.
|
||||||
|
e.distribution = info.getDistributionString();
|
||||||
|
e.osLocale = info.getOSLocale();
|
||||||
|
e.appLocale = info.getAppLocale();
|
||||||
|
e.acceptLangSet = info.isAcceptLangUserSet() ? 1 : 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -0,0 +1,267 @@
|
||||||
|
/* 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.background.healthreport;
|
||||||
|
|
||||||
|
import java.io.UnsupportedEncodingException;
|
||||||
|
import java.security.MessageDigest;
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.SortedSet;
|
||||||
|
|
||||||
|
import org.json.JSONException;
|
||||||
|
import org.json.JSONObject;
|
||||||
|
import org.mozilla.apache.commons.codec.binary.Base64;
|
||||||
|
import org.mozilla.gecko.background.common.log.Logger;
|
||||||
|
|
||||||
|
public abstract class EnvironmentV1 {
|
||||||
|
private static final String LOG_TAG = "GeckoEnvironment";
|
||||||
|
private static final int VERSION = 1;
|
||||||
|
|
||||||
|
protected final Class<? extends EnvironmentAppender> appenderClass;
|
||||||
|
|
||||||
|
protected volatile String hash = null;
|
||||||
|
protected volatile int id = -1;
|
||||||
|
|
||||||
|
public int version = VERSION;
|
||||||
|
|
||||||
|
// org.mozilla.profile.age.
|
||||||
|
public int profileCreation;
|
||||||
|
|
||||||
|
// org.mozilla.sysinfo.sysinfo.
|
||||||
|
public int cpuCount;
|
||||||
|
public int memoryMB;
|
||||||
|
public String architecture;
|
||||||
|
public String sysName;
|
||||||
|
public String sysVersion; // Kernel.
|
||||||
|
|
||||||
|
// geckoAppInfo.
|
||||||
|
public String vendor;
|
||||||
|
public String appName;
|
||||||
|
public String appID;
|
||||||
|
public String appVersion;
|
||||||
|
public String appBuildID;
|
||||||
|
public String platformVersion;
|
||||||
|
public String platformBuildID;
|
||||||
|
public String os;
|
||||||
|
public String xpcomabi;
|
||||||
|
public String updateChannel;
|
||||||
|
|
||||||
|
// appinfo.
|
||||||
|
public int isBlocklistEnabled;
|
||||||
|
public int isTelemetryEnabled;
|
||||||
|
|
||||||
|
// org.mozilla.addons.active.
|
||||||
|
public JSONObject addons = null;
|
||||||
|
|
||||||
|
// org.mozilla.addons.counts.
|
||||||
|
public int extensionCount;
|
||||||
|
public int pluginCount;
|
||||||
|
public int themeCount;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* We break out this interface in order to allow for testing -- pass in your
|
||||||
|
* own appender that just records strings, for example.
|
||||||
|
*/
|
||||||
|
public static abstract class EnvironmentAppender {
|
||||||
|
public abstract void append(String s);
|
||||||
|
public abstract void append(int v);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class HashAppender extends EnvironmentAppender {
|
||||||
|
final MessageDigest hasher;
|
||||||
|
|
||||||
|
public HashAppender() throws NoSuchAlgorithmException {
|
||||||
|
// Note to the security-minded reader: we deliberately use SHA-1 here, not
|
||||||
|
// a stronger hash. These identifiers don't strictly need a cryptographic
|
||||||
|
// hash function, because there is negligible value in attacking the hash.
|
||||||
|
// We use SHA-1 because it's *shorter* -- the exact same reason that Git
|
||||||
|
// chose SHA-1.
|
||||||
|
hasher = MessageDigest.getInstance("SHA-1");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void append(String s) {
|
||||||
|
try {
|
||||||
|
hasher.update(((s == null) ? "null" : s).getBytes("UTF-8"));
|
||||||
|
} catch (UnsupportedEncodingException e) {
|
||||||
|
// This can never occur. Thanks, Java.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void append(int profileCreation) {
|
||||||
|
append(Integer.toString(profileCreation, 10));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
// We *could* use ASCII85… but the savings would be negated by the
|
||||||
|
// inclusion of JSON-unsafe characters like double-quote.
|
||||||
|
return new Base64(-1, null, false).encodeAsString(hasher.digest());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ensure that the {@link Environment} has been registered with its
|
||||||
|
* storage layer, and can be used to annotate events.
|
||||||
|
*
|
||||||
|
* It's safe to call this method more than once, and each time you'll
|
||||||
|
* get the same ID.
|
||||||
|
*
|
||||||
|
* @return the integer ID to use in subsequent DB insertions.
|
||||||
|
*/
|
||||||
|
public abstract int register();
|
||||||
|
|
||||||
|
protected EnvironmentAppender getAppender() {
|
||||||
|
EnvironmentAppender appender = null;
|
||||||
|
try {
|
||||||
|
appender = appenderClass.newInstance();
|
||||||
|
} catch (InstantiationException ex) {
|
||||||
|
// Should never happen, but...
|
||||||
|
Logger.warn(LOG_TAG, "Could not compute hash.", ex);
|
||||||
|
} catch (IllegalAccessException ex) {
|
||||||
|
// Should never happen, but...
|
||||||
|
Logger.warn(LOG_TAG, "Could not compute hash.", ex);
|
||||||
|
}
|
||||||
|
return appender;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void appendHash(EnvironmentAppender appender) {
|
||||||
|
appender.append(profileCreation);
|
||||||
|
appender.append(cpuCount);
|
||||||
|
appender.append(memoryMB);
|
||||||
|
appender.append(architecture);
|
||||||
|
appender.append(sysName);
|
||||||
|
appender.append(sysVersion);
|
||||||
|
appender.append(vendor);
|
||||||
|
appender.append(appName);
|
||||||
|
appender.append(appID);
|
||||||
|
appender.append(appVersion);
|
||||||
|
appender.append(appBuildID);
|
||||||
|
appender.append(platformVersion);
|
||||||
|
appender.append(platformBuildID);
|
||||||
|
appender.append(os);
|
||||||
|
appender.append(xpcomabi);
|
||||||
|
appender.append(updateChannel);
|
||||||
|
appender.append(isBlocklistEnabled);
|
||||||
|
appender.append(isTelemetryEnabled);
|
||||||
|
appender.append(extensionCount);
|
||||||
|
appender.append(pluginCount);
|
||||||
|
appender.append(themeCount);
|
||||||
|
|
||||||
|
// We need sorted values.
|
||||||
|
if (addons != null) {
|
||||||
|
appendSortedAddons(getNonIgnoredAddons(), appender);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compute the stable hash of the configured environment.
|
||||||
|
*
|
||||||
|
* @return the hash in base34, or null if there was a problem.
|
||||||
|
*/
|
||||||
|
public String getHash() {
|
||||||
|
// It's never unset, so we only care about partial reads. volatile is enough.
|
||||||
|
if (hash != null) {
|
||||||
|
return hash;
|
||||||
|
}
|
||||||
|
|
||||||
|
EnvironmentAppender appender = getAppender();
|
||||||
|
if (appender == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
appendHash(appender);
|
||||||
|
return hash = appender.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public EnvironmentV1(Class<? extends EnvironmentAppender> appenderClass) {
|
||||||
|
super();
|
||||||
|
this.appenderClass = appenderClass;
|
||||||
|
}
|
||||||
|
|
||||||
|
public JSONObject getNonIgnoredAddons() {
|
||||||
|
if (addons == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
JSONObject out = new JSONObject();
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
Iterator<String> keys = addons.keys();
|
||||||
|
while (keys.hasNext()) {
|
||||||
|
try {
|
||||||
|
final String key = keys.next();
|
||||||
|
final Object obj = addons.get(key);
|
||||||
|
if (obj != null &&
|
||||||
|
obj instanceof JSONObject &&
|
||||||
|
((JSONObject) obj).optBoolean("ignore", false)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
out.put(key, obj);
|
||||||
|
} catch (JSONException ex) {
|
||||||
|
// Do nothing.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Take a collection of add-on descriptors, appending a consistent string
|
||||||
|
* to the provided builder.
|
||||||
|
*/
|
||||||
|
public static void appendSortedAddons(JSONObject addons, final EnvironmentAppender builder) {
|
||||||
|
final SortedSet<String> keys = HealthReportUtils.sortedKeySet(addons);
|
||||||
|
|
||||||
|
// For each add-on, produce a consistent, sorted mapping of its descriptor.
|
||||||
|
for (String key : keys) {
|
||||||
|
try {
|
||||||
|
JSONObject addon = addons.getJSONObject(key);
|
||||||
|
|
||||||
|
// Now produce the output for this add-on.
|
||||||
|
builder.append(key);
|
||||||
|
builder.append("={");
|
||||||
|
|
||||||
|
for (String addonKey : HealthReportUtils.sortedKeySet(addon)) {
|
||||||
|
builder.append(addonKey);
|
||||||
|
builder.append("==");
|
||||||
|
try {
|
||||||
|
builder.append(addon.get(addonKey).toString());
|
||||||
|
} catch (JSONException e) {
|
||||||
|
builder.append("_e_");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
builder.append("}");
|
||||||
|
} catch (Exception e) {
|
||||||
|
// Muffle.
|
||||||
|
Logger.warn(LOG_TAG, "Invalid add-on for ID " + key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setJSONForAddons(byte[] json) throws Exception {
|
||||||
|
setJSONForAddons(new String(json, "UTF-8"));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setJSONForAddons(String json) throws Exception {
|
||||||
|
if (json == null || "null".equals(json)) {
|
||||||
|
addons = null;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
addons = new JSONObject(json);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setJSONForAddons(JSONObject json) {
|
||||||
|
addons = json;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Includes ignored add-ons.
|
||||||
|
*/
|
||||||
|
public String getNormalizedAddonsJSON() {
|
||||||
|
// We trust that our input will already be normalized. If that assumption
|
||||||
|
// is invalidated, then we'll be sorry.
|
||||||
|
return (addons == null) ? "null" : addons.toString();
|
||||||
|
}
|
||||||
|
}
|
|
@ -128,7 +128,7 @@ public class HealthReportDatabaseStorage implements HealthReportStorage {
|
||||||
};
|
};
|
||||||
|
|
||||||
private static final String[] COLUMNS_ENVIRONMENT_DETAILS = new String[] {
|
private static final String[] COLUMNS_ENVIRONMENT_DETAILS = new String[] {
|
||||||
"id", "hash",
|
"id", "version", "hash",
|
||||||
"profileCreation", "cpuCount", "memoryMB",
|
"profileCreation", "cpuCount", "memoryMB",
|
||||||
|
|
||||||
"isBlocklistEnabled", "isTelemetryEnabled", "extensionCount",
|
"isBlocklistEnabled", "isTelemetryEnabled", "extensionCount",
|
||||||
|
@ -138,6 +138,8 @@ public class HealthReportDatabaseStorage implements HealthReportStorage {
|
||||||
"appVersion", "appBuildID", "platformVersion", "platformBuildID", "os",
|
"appVersion", "appBuildID", "platformVersion", "platformBuildID", "os",
|
||||||
"xpcomabi", "updateChannel",
|
"xpcomabi", "updateChannel",
|
||||||
|
|
||||||
|
"distribution", "osLocale", "appLocale", "acceptLangSet",
|
||||||
|
|
||||||
// Joined to the add-ons table.
|
// Joined to the add-ons table.
|
||||||
"addonsBody"
|
"addonsBody"
|
||||||
};
|
};
|
||||||
|
@ -188,7 +190,7 @@ public class HealthReportDatabaseStorage implements HealthReportStorage {
|
||||||
protected final HealthReportSQLiteOpenHelper helper;
|
protected final HealthReportSQLiteOpenHelper helper;
|
||||||
|
|
||||||
public static class HealthReportSQLiteOpenHelper extends SQLiteOpenHelper {
|
public static class HealthReportSQLiteOpenHelper extends SQLiteOpenHelper {
|
||||||
public static final int CURRENT_VERSION = 5;
|
public static final int CURRENT_VERSION = 6;
|
||||||
public static final String LOG_TAG = "HealthReportSQL";
|
public static final String LOG_TAG = "HealthReportSQL";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -252,7 +254,10 @@ public class HealthReportDatabaseStorage implements HealthReportStorage {
|
||||||
" UNIQUE (body) " +
|
" UNIQUE (body) " +
|
||||||
")");
|
")");
|
||||||
|
|
||||||
|
// N.B., hash collisions can occur across versions. In that case, the system
|
||||||
|
// is likely to persist the original environment version.
|
||||||
db.execSQL("CREATE TABLE environments (id INTEGER PRIMARY KEY AUTOINCREMENT, " +
|
db.execSQL("CREATE TABLE environments (id INTEGER PRIMARY KEY AUTOINCREMENT, " +
|
||||||
|
" version INTEGER, " +
|
||||||
" hash TEXT, " +
|
" hash TEXT, " +
|
||||||
" profileCreation INTEGER, " +
|
" profileCreation INTEGER, " +
|
||||||
" cpuCount INTEGER, " +
|
" cpuCount INTEGER, " +
|
||||||
|
@ -275,6 +280,12 @@ public class HealthReportDatabaseStorage implements HealthReportStorage {
|
||||||
" os TEXT, " +
|
" os TEXT, " +
|
||||||
" xpcomabi TEXT, " +
|
" xpcomabi TEXT, " +
|
||||||
" updateChannel TEXT, " +
|
" updateChannel TEXT, " +
|
||||||
|
|
||||||
|
" distribution TEXT, " +
|
||||||
|
" osLocale TEXT, " +
|
||||||
|
" appLocale TEXT, " +
|
||||||
|
" acceptLangSet INTEGER, " +
|
||||||
|
|
||||||
" addonsID INTEGER, " +
|
" addonsID INTEGER, " +
|
||||||
" FOREIGN KEY (addonsID) REFERENCES addons(id) ON DELETE RESTRICT, " +
|
" FOREIGN KEY (addonsID) REFERENCES addons(id) ON DELETE RESTRICT, " +
|
||||||
" UNIQUE (hash) " +
|
" UNIQUE (hash) " +
|
||||||
|
@ -357,6 +368,7 @@ public class HealthReportDatabaseStorage implements HealthReportStorage {
|
||||||
private void createAddonsEnvironmentsView(SQLiteDatabase db) {
|
private void createAddonsEnvironmentsView(SQLiteDatabase db) {
|
||||||
db.execSQL("CREATE VIEW environments_with_addons AS " +
|
db.execSQL("CREATE VIEW environments_with_addons AS " +
|
||||||
"SELECT e.id AS id, " +
|
"SELECT e.id AS id, " +
|
||||||
|
" e.version AS version, " +
|
||||||
" e.hash AS hash, " +
|
" e.hash AS hash, " +
|
||||||
" e.profileCreation AS profileCreation, " +
|
" e.profileCreation AS profileCreation, " +
|
||||||
" e.cpuCount AS cpuCount, " +
|
" e.cpuCount AS cpuCount, " +
|
||||||
|
@ -379,6 +391,10 @@ public class HealthReportDatabaseStorage implements HealthReportStorage {
|
||||||
" e.os AS os, " +
|
" e.os AS os, " +
|
||||||
" e.xpcomabi AS xpcomabi, " +
|
" e.xpcomabi AS xpcomabi, " +
|
||||||
" e.updateChannel AS updateChannel, " +
|
" e.updateChannel AS updateChannel, " +
|
||||||
|
" e.distribution AS distribution, " +
|
||||||
|
" e.osLocale AS osLocale, " +
|
||||||
|
" e.appLocale AS appLocale, " +
|
||||||
|
" e.acceptLangSet AS acceptLangSet, " +
|
||||||
" addons.body AS addonsBody " +
|
" addons.body AS addonsBody " +
|
||||||
"FROM environments AS e, addons " +
|
"FROM environments AS e, addons " +
|
||||||
"WHERE e.addonsID = addons.id");
|
"WHERE e.addonsID = addons.id");
|
||||||
|
@ -417,6 +433,22 @@ public class HealthReportDatabaseStorage implements HealthReportStorage {
|
||||||
db.delete(EVENTS_TEXTUAL, "field NOT IN (SELECT id FROM fields)", null);
|
db.delete(EVENTS_TEXTUAL, "field NOT IN (SELECT id FROM fields)", null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void upgradeDatabaseFrom5to6(SQLiteDatabase db) {
|
||||||
|
db.execSQL("DROP VIEW environments_with_addons");
|
||||||
|
|
||||||
|
// Add version to environment (default to 1).
|
||||||
|
db.execSQL("ALTER TABLE environments ADD COLUMN version INTEGER DEFAULT 1");
|
||||||
|
|
||||||
|
// Add fields to environment (default to empty string).
|
||||||
|
db.execSQL("ALTER TABLE environments ADD COLUMN distribution TEXT DEFAULT ''");
|
||||||
|
db.execSQL("ALTER TABLE environments ADD COLUMN osLocale TEXT DEFAULT ''");
|
||||||
|
db.execSQL("ALTER TABLE environments ADD COLUMN appLocale TEXT DEFAULT ''");
|
||||||
|
db.execSQL("ALTER TABLE environments ADD COLUMN acceptLangSet INTEGER DEFAULT 0");
|
||||||
|
|
||||||
|
// Recreate view.
|
||||||
|
createAddonsEnvironmentsView(db);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
|
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
|
||||||
if (oldVersion >= newVersion) {
|
if (oldVersion >= newVersion) {
|
||||||
|
@ -432,6 +464,8 @@ public class HealthReportDatabaseStorage implements HealthReportStorage {
|
||||||
upgradeDatabaseFrom3To4(db);
|
upgradeDatabaseFrom3To4(db);
|
||||||
case 4:
|
case 4:
|
||||||
upgradeDatabaseFrom4to5(db);
|
upgradeDatabaseFrom4to5(db);
|
||||||
|
case 5:
|
||||||
|
upgradeDatabaseFrom5to6(db);
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
Logger.error(LOG_TAG, "Failure in onUpgrade.", e);
|
Logger.error(LOG_TAG, "Failure in onUpgrade.", e);
|
||||||
|
@ -536,6 +570,7 @@ public class HealthReportDatabaseStorage implements HealthReportStorage {
|
||||||
|
|
||||||
// Otherwise, add data and hash to the DB.
|
// Otherwise, add data and hash to the DB.
|
||||||
ContentValues v = new ContentValues();
|
ContentValues v = new ContentValues();
|
||||||
|
v.put("version", version);
|
||||||
v.put("hash", h);
|
v.put("hash", h);
|
||||||
v.put("profileCreation", profileCreation);
|
v.put("profileCreation", profileCreation);
|
||||||
v.put("cpuCount", cpuCount);
|
v.put("cpuCount", cpuCount);
|
||||||
|
@ -558,6 +593,10 @@ public class HealthReportDatabaseStorage implements HealthReportStorage {
|
||||||
v.put("os", os);
|
v.put("os", os);
|
||||||
v.put("xpcomabi", xpcomabi);
|
v.put("xpcomabi", xpcomabi);
|
||||||
v.put("updateChannel", updateChannel);
|
v.put("updateChannel", updateChannel);
|
||||||
|
v.put("distribution", distribution);
|
||||||
|
v.put("osLocale", osLocale);
|
||||||
|
v.put("appLocale", appLocale);
|
||||||
|
v.put("acceptLangSet", acceptLangSet);
|
||||||
|
|
||||||
final SQLiteDatabase db = storage.helper.getWritableDatabase();
|
final SQLiteDatabase db = storage.helper.getWritableDatabase();
|
||||||
|
|
||||||
|
@ -643,6 +682,7 @@ public class HealthReportDatabaseStorage implements HealthReportStorage {
|
||||||
}
|
}
|
||||||
|
|
||||||
public void init(ContentValues v) {
|
public void init(ContentValues v) {
|
||||||
|
version = v.containsKey("version") ? v.getAsInteger("version") : Environment.CURRENT_VERSION;
|
||||||
profileCreation = v.getAsInteger("profileCreation");
|
profileCreation = v.getAsInteger("profileCreation");
|
||||||
cpuCount = v.getAsInteger("cpuCount");
|
cpuCount = v.getAsInteger("cpuCount");
|
||||||
memoryMB = v.getAsInteger("memoryMB");
|
memoryMB = v.getAsInteger("memoryMB");
|
||||||
|
@ -667,6 +707,11 @@ public class HealthReportDatabaseStorage implements HealthReportStorage {
|
||||||
xpcomabi = v.getAsString("xpcomabi");
|
xpcomabi = v.getAsString("xpcomabi");
|
||||||
updateChannel = v.getAsString("updateChannel");
|
updateChannel = v.getAsString("updateChannel");
|
||||||
|
|
||||||
|
distribution = v.getAsString("distribution");
|
||||||
|
osLocale = v.getAsString("osLocale");
|
||||||
|
appLocale = v.getAsString("appLocale");
|
||||||
|
acceptLangSet = v.getAsInteger("acceptLangSet");
|
||||||
|
|
||||||
try {
|
try {
|
||||||
setJSONForAddons(v.getAsString("addonsBody"));
|
setJSONForAddons(v.getAsString("addonsBody"));
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
|
@ -686,6 +731,7 @@ public class HealthReportDatabaseStorage implements HealthReportStorage {
|
||||||
public boolean init(Cursor cursor) {
|
public boolean init(Cursor cursor) {
|
||||||
int i = 0;
|
int i = 0;
|
||||||
this.id = cursor.getInt(i++);
|
this.id = cursor.getInt(i++);
|
||||||
|
this.version = cursor.getInt(i++);
|
||||||
this.hash = cursor.getString(i++);
|
this.hash = cursor.getString(i++);
|
||||||
|
|
||||||
profileCreation = cursor.getInt(i++);
|
profileCreation = cursor.getInt(i++);
|
||||||
|
@ -712,6 +758,11 @@ public class HealthReportDatabaseStorage implements HealthReportStorage {
|
||||||
xpcomabi = cursor.getString(i++);
|
xpcomabi = cursor.getString(i++);
|
||||||
updateChannel = cursor.getString(i++);
|
updateChannel = cursor.getString(i++);
|
||||||
|
|
||||||
|
distribution = cursor.getString(i++);
|
||||||
|
osLocale = cursor.getString(i++);
|
||||||
|
appLocale = cursor.getString(i++);
|
||||||
|
acceptLangSet = cursor.getInt(i++);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
setJSONForAddons(cursor.getBlob(i++));
|
setJSONForAddons(cursor.getBlob(i++));
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
|
@ -1339,6 +1390,7 @@ public class HealthReportDatabaseStorage implements HealthReportStorage {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Called internally only to ensure the same db instance is used.
|
// Called internally only to ensure the same db instance is used.
|
||||||
|
@SuppressWarnings("static-method")
|
||||||
protected int deleteOrphanedEnv(final SQLiteDatabase db, final int curEnv) {
|
protected int deleteOrphanedEnv(final SQLiteDatabase db, final int curEnv) {
|
||||||
final String whereClause =
|
final String whereClause =
|
||||||
"id != ? AND " +
|
"id != ? AND " +
|
||||||
|
@ -1353,6 +1405,7 @@ public class HealthReportDatabaseStorage implements HealthReportStorage {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Called internally only to ensure the same db instance is used.
|
// Called internally only to ensure the same db instance is used.
|
||||||
|
@SuppressWarnings("static-method")
|
||||||
protected int deleteEventsBefore(final SQLiteDatabase db, final String dayString) {
|
protected int deleteEventsBefore(final SQLiteDatabase db, final String dayString) {
|
||||||
final String whereClause = "date < ?";
|
final String whereClause = "date < ?";
|
||||||
final String[] whereArgs = new String[] {dayString};
|
final String[] whereArgs = new String[] {dayString};
|
||||||
|
@ -1377,6 +1430,7 @@ public class HealthReportDatabaseStorage implements HealthReportStorage {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Called internally only to ensure the same db instance is used.
|
// Called internally only to ensure the same db instance is used.
|
||||||
|
@SuppressWarnings("static-method")
|
||||||
protected int deleteOrphanedAddons(final SQLiteDatabase db) {
|
protected int deleteOrphanedAddons(final SQLiteDatabase db) {
|
||||||
final String whereClause = "id NOT IN (SELECT addonsID FROM environments)";
|
final String whereClause = "id NOT IN (SELECT addonsID FROM environments)";
|
||||||
return db.delete("addons", whereClause, null);
|
return db.delete("addons", whereClause, null);
|
||||||
|
|
|
@ -388,24 +388,117 @@ public class HealthReportGenerator {
|
||||||
return gecko;
|
return gecko;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Null-safe string comparison.
|
||||||
|
private static boolean stringsDiffer(final String a, final String b) {
|
||||||
|
if (a == null) {
|
||||||
|
return b != null;
|
||||||
|
}
|
||||||
|
return !a.equals(b);
|
||||||
|
}
|
||||||
|
|
||||||
private static JSONObject getAppInfo(Environment e, Environment current) throws JSONException {
|
private static JSONObject getAppInfo(Environment e, Environment current) throws JSONException {
|
||||||
JSONObject appinfo = new JSONObject();
|
JSONObject appinfo = new JSONObject();
|
||||||
int changes = 0;
|
|
||||||
if (current == null || current.isBlocklistEnabled != e.isBlocklistEnabled) {
|
Logger.debug(LOG_TAG, "Generating appinfo for v" + e.version + " env " + e.hash);
|
||||||
appinfo.put("isBlocklistEnabled", e.isBlocklistEnabled);
|
|
||||||
changes++;
|
// Is the environment in question newer than the diff target, or is
|
||||||
|
// there no diff target?
|
||||||
|
final boolean outdated = current == null ||
|
||||||
|
e.version > current.version;
|
||||||
|
|
||||||
|
// Is the environment in question a different version (lower or higher),
|
||||||
|
// or is there no diff target?
|
||||||
|
final boolean differ = outdated || current.version > e.version;
|
||||||
|
|
||||||
|
// Always produce an output object if there's a version mismatch or this
|
||||||
|
// isn't a diff. Otherwise, track as we go if there's any difference.
|
||||||
|
boolean changed = differ;
|
||||||
|
|
||||||
|
switch (e.version) {
|
||||||
|
// There's a straightforward correspondence between environment versions
|
||||||
|
// and appinfo versions.
|
||||||
|
case 2:
|
||||||
|
appinfo.put("_v", 3);
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
appinfo.put("_v", 2);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
Logger.warn(LOG_TAG, "Unknown environment version: " + e.version);
|
||||||
|
return appinfo;
|
||||||
}
|
}
|
||||||
if (current == null || current.isTelemetryEnabled != e.isTelemetryEnabled) {
|
|
||||||
appinfo.put("isTelemetryEnabled", e.isTelemetryEnabled);
|
switch (e.version) {
|
||||||
changes++;
|
case 2:
|
||||||
|
if (populateAppInfoV2(appinfo, e, current, outdated)) {
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
// Fall through.
|
||||||
|
|
||||||
|
case 1:
|
||||||
|
// There is no older version than v1, so don't check outdated.
|
||||||
|
if (populateAppInfoV1(e, current, appinfo)) {
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (current != null && changes == 0) {
|
|
||||||
|
if (!changed) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
appinfo.put("_v", 2);
|
|
||||||
return appinfo;
|
return appinfo;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static boolean populateAppInfoV1(Environment e,
|
||||||
|
Environment current,
|
||||||
|
JSONObject appinfo)
|
||||||
|
throws JSONException {
|
||||||
|
boolean changes = false;
|
||||||
|
if (current == null || current.isBlocklistEnabled != e.isBlocklistEnabled) {
|
||||||
|
appinfo.put("isBlocklistEnabled", e.isBlocklistEnabled);
|
||||||
|
changes = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (current == null || current.isTelemetryEnabled != e.isTelemetryEnabled) {
|
||||||
|
appinfo.put("isTelemetryEnabled", e.isTelemetryEnabled);
|
||||||
|
changes = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return changes;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean populateAppInfoV2(JSONObject appinfo,
|
||||||
|
Environment e,
|
||||||
|
Environment current,
|
||||||
|
final boolean outdated)
|
||||||
|
throws JSONException {
|
||||||
|
boolean changes = false;
|
||||||
|
if (outdated ||
|
||||||
|
stringsDiffer(current.osLocale, e.osLocale)) {
|
||||||
|
appinfo.put("osLocale", e.osLocale);
|
||||||
|
changes = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (outdated ||
|
||||||
|
stringsDiffer(current.appLocale, e.appLocale)) {
|
||||||
|
appinfo.put("appLocale", e.appLocale);
|
||||||
|
changes = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (outdated ||
|
||||||
|
stringsDiffer(current.distribution, e.distribution)) {
|
||||||
|
appinfo.put("distribution", e.distribution);
|
||||||
|
changes = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (outdated ||
|
||||||
|
current.acceptLangSet != e.acceptLangSet) {
|
||||||
|
appinfo.put("acceptLangIsUserSet", e.acceptLangSet);
|
||||||
|
changes = true;
|
||||||
|
}
|
||||||
|
return changes;
|
||||||
|
}
|
||||||
|
|
||||||
private static JSONObject getAddonCounts(Environment e, Environment current) throws JSONException {
|
private static JSONObject getAddonCounts(Environment e, Environment current) throws JSONException {
|
||||||
JSONObject counts = new JSONObject();
|
JSONObject counts = new JSONObject();
|
||||||
int changes = 0;
|
int changes = 0;
|
||||||
|
|
|
@ -10,6 +10,7 @@ import java.io.FileOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.OutputStreamWriter;
|
import java.io.OutputStreamWriter;
|
||||||
import java.nio.charset.Charset;
|
import java.nio.charset.Charset;
|
||||||
|
import java.util.Locale;
|
||||||
import java.util.Scanner;
|
import java.util.Scanner;
|
||||||
|
|
||||||
import org.json.JSONException;
|
import org.json.JSONException;
|
||||||
|
@ -32,8 +33,9 @@ public class ProfileInformationCache implements ProfileInformationProvider {
|
||||||
* -: No version number; implicit v1.
|
* -: No version number; implicit v1.
|
||||||
* 1: Add versioning (Bug 878670).
|
* 1: Add versioning (Bug 878670).
|
||||||
* 2: Bump to regenerate add-on set after landing Bug 900694 (Bug 901622).
|
* 2: Bump to regenerate add-on set after landing Bug 900694 (Bug 901622).
|
||||||
|
* 3: Add distribution, osLocale, appLocale.
|
||||||
*/
|
*/
|
||||||
public static final int FORMAT_VERSION = 2;
|
public static final int FORMAT_VERSION = 3;
|
||||||
|
|
||||||
protected boolean initialized = false;
|
protected boolean initialized = false;
|
||||||
protected boolean needsWrite = false;
|
protected boolean needsWrite = false;
|
||||||
|
@ -42,7 +44,29 @@ public class ProfileInformationCache implements ProfileInformationProvider {
|
||||||
|
|
||||||
private volatile boolean blocklistEnabled = true;
|
private volatile boolean blocklistEnabled = true;
|
||||||
private volatile boolean telemetryEnabled = false;
|
private volatile boolean telemetryEnabled = false;
|
||||||
|
private volatile boolean isAcceptLangUserSet = false;
|
||||||
|
|
||||||
private volatile long profileCreationTime = 0;
|
private volatile long profileCreationTime = 0;
|
||||||
|
private volatile String distribution = "";
|
||||||
|
|
||||||
|
// There are really four kinds of locale in play:
|
||||||
|
//
|
||||||
|
// * The OS
|
||||||
|
// * The Android environment of the app (setDefault)
|
||||||
|
// * The Gecko locale
|
||||||
|
// * The requested content locale (Accept-Language).
|
||||||
|
//
|
||||||
|
// We track only the first two, assuming that the Gecko locale will typically
|
||||||
|
// be the same as the app locale.
|
||||||
|
//
|
||||||
|
// The app locale is fetched from the PIC because it can be modified at
|
||||||
|
// runtime -- it won't necessarily be what Locale.getDefaultLocale() returns
|
||||||
|
// in a fresh non-browser profile.
|
||||||
|
//
|
||||||
|
// We also track the OS locale here for the same reason -- we need to store
|
||||||
|
// the default (OS) value before the locale-switching code takes effect!
|
||||||
|
private volatile String osLocale = "";
|
||||||
|
private volatile String appLocale = "";
|
||||||
|
|
||||||
private volatile JSONObject addons = null;
|
private volatile JSONObject addons = null;
|
||||||
|
|
||||||
|
@ -62,7 +86,11 @@ public class ProfileInformationCache implements ProfileInformationProvider {
|
||||||
object.put("version", FORMAT_VERSION);
|
object.put("version", FORMAT_VERSION);
|
||||||
object.put("blocklist", blocklistEnabled);
|
object.put("blocklist", blocklistEnabled);
|
||||||
object.put("telemetry", telemetryEnabled);
|
object.put("telemetry", telemetryEnabled);
|
||||||
|
object.put("isAcceptLangUserSet", isAcceptLangUserSet);
|
||||||
object.put("profileCreated", profileCreationTime);
|
object.put("profileCreated", profileCreationTime);
|
||||||
|
object.put("osLocale", osLocale);
|
||||||
|
object.put("appLocale", appLocale);
|
||||||
|
object.put("distribution", distribution);
|
||||||
object.put("addons", addons);
|
object.put("addons", addons);
|
||||||
} catch (JSONException e) {
|
} catch (JSONException e) {
|
||||||
// There isn't much we can do about this.
|
// There isn't much we can do about this.
|
||||||
|
@ -86,8 +114,12 @@ public class ProfileInformationCache implements ProfileInformationProvider {
|
||||||
case FORMAT_VERSION:
|
case FORMAT_VERSION:
|
||||||
blocklistEnabled = object.getBoolean("blocklist");
|
blocklistEnabled = object.getBoolean("blocklist");
|
||||||
telemetryEnabled = object.getBoolean("telemetry");
|
telemetryEnabled = object.getBoolean("telemetry");
|
||||||
|
isAcceptLangUserSet = object.getBoolean("isAcceptLangUserSet");
|
||||||
profileCreationTime = object.getLong("profileCreated");
|
profileCreationTime = object.getLong("profileCreated");
|
||||||
addons = object.getJSONObject("addons");
|
addons = object.getJSONObject("addons");
|
||||||
|
distribution = object.getString("distribution");
|
||||||
|
osLocale = object.getString("osLocale");
|
||||||
|
appLocale = object.getString("appLocale");
|
||||||
return true;
|
return true;
|
||||||
default:
|
default:
|
||||||
Logger.warn(LOG_TAG, "Unable to restore from version " + version + " PIC file: expecting " + FORMAT_VERSION);
|
Logger.warn(LOG_TAG, "Unable to restore from version " + version + " PIC file: expecting " + FORMAT_VERSION);
|
||||||
|
@ -206,6 +238,18 @@ public class ProfileInformationCache implements ProfileInformationProvider {
|
||||||
needsWrite = true;
|
needsWrite = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isAcceptLangUserSet() {
|
||||||
|
ensureInitialized();
|
||||||
|
return isAcceptLangUserSet;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAcceptLangUserSet(boolean value) {
|
||||||
|
Logger.debug(LOG_TAG, "Setting accept-lang as user-set: " + value);
|
||||||
|
isAcceptLangUserSet = value;
|
||||||
|
needsWrite = true;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public long getProfileCreationTime() {
|
public long getProfileCreationTime() {
|
||||||
ensureInitialized();
|
ensureInitialized();
|
||||||
|
@ -218,17 +262,83 @@ public class ProfileInformationCache implements ProfileInformationProvider {
|
||||||
needsWrite = true;
|
needsWrite = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getDistributionString() {
|
||||||
|
ensureInitialized();
|
||||||
|
return distribution;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ensure that your arguments are non-null.
|
||||||
|
*/
|
||||||
|
public void setDistributionString(String distributionID, String distributionVersion) {
|
||||||
|
Logger.debug(LOG_TAG, "Setting distribution: " + distributionID + ", " + distributionVersion);
|
||||||
|
distribution = distributionID + ":" + distributionVersion;
|
||||||
|
needsWrite = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getAppLocale() {
|
||||||
|
ensureInitialized();
|
||||||
|
return appLocale;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAppLocale(String value) {
|
||||||
|
if (value.equalsIgnoreCase(appLocale)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Logger.debug(LOG_TAG, "Setting app locale: " + value);
|
||||||
|
appLocale = value.toLowerCase(Locale.US);
|
||||||
|
needsWrite = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getOSLocale() {
|
||||||
|
ensureInitialized();
|
||||||
|
return osLocale;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setOSLocale(String value) {
|
||||||
|
if (value.equalsIgnoreCase(osLocale)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Logger.debug(LOG_TAG, "Setting OS locale: " + value);
|
||||||
|
osLocale = value.toLowerCase(Locale.US);
|
||||||
|
needsWrite = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the PIC, if necessary, to match the current locale environment.
|
||||||
|
*
|
||||||
|
* @return true if the PIC needed to be updated.
|
||||||
|
*/
|
||||||
|
public boolean updateLocales(String osLocale, String appLocale) {
|
||||||
|
if (this.osLocale.equalsIgnoreCase(osLocale) &&
|
||||||
|
(appLocale == null || this.appLocale.equalsIgnoreCase(appLocale))) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
this.setOSLocale(osLocale);
|
||||||
|
if (appLocale != null) {
|
||||||
|
this.setAppLocale(appLocale);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public JSONObject getAddonsJSON() {
|
public JSONObject getAddonsJSON() {
|
||||||
|
ensureInitialized();
|
||||||
return addons;
|
return addons;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void updateJSONForAddon(String id, String json) throws Exception {
|
public void updateJSONForAddon(String id, String json) throws Exception {
|
||||||
addons.put(id, new JSONObject(json));
|
addons.put(id, new JSONObject(json));
|
||||||
|
needsWrite = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void removeAddon(String id) {
|
public void removeAddon(String id) {
|
||||||
addons.remove(id);
|
if (null != addons.remove(id)) {
|
||||||
|
needsWrite = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -240,6 +350,7 @@ public class ProfileInformationCache implements ProfileInformationProvider {
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
addons.put(id, json);
|
addons.put(id, json);
|
||||||
|
needsWrite = true;
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
// Why would this happen?
|
// Why would this happen?
|
||||||
Logger.warn(LOG_TAG, "Unexpected failure updating JSON for add-on.", e);
|
Logger.warn(LOG_TAG, "Unexpected failure updating JSON for add-on.", e);
|
||||||
|
@ -253,9 +364,11 @@ public class ProfileInformationCache implements ProfileInformationProvider {
|
||||||
*/
|
*/
|
||||||
public void setJSONForAddons(String json) throws Exception {
|
public void setJSONForAddons(String json) throws Exception {
|
||||||
addons = new JSONObject(json);
|
addons = new JSONObject(json);
|
||||||
|
needsWrite = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setJSONForAddons(JSONObject json) {
|
public void setJSONForAddons(JSONObject json) {
|
||||||
addons = json;
|
addons = json;
|
||||||
|
needsWrite = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,11 +13,11 @@ import android.content.SharedPreferences;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
import org.mozilla.gecko.AppConstants;
|
import org.mozilla.gecko.AppConstants;
|
||||||
|
import org.mozilla.gecko.Distribution;
|
||||||
|
import org.mozilla.gecko.Distribution.DistributionDescriptor;
|
||||||
import org.mozilla.gecko.GeckoApp;
|
import org.mozilla.gecko.GeckoApp;
|
||||||
import org.mozilla.gecko.GeckoAppShell;
|
import org.mozilla.gecko.GeckoAppShell;
|
||||||
import org.mozilla.gecko.GeckoEvent;
|
import org.mozilla.gecko.GeckoEvent;
|
||||||
import org.mozilla.gecko.PrefsHelper;
|
|
||||||
import org.mozilla.gecko.PrefsHelper.PrefHandler;
|
|
||||||
|
|
||||||
import org.mozilla.gecko.background.healthreport.EnvironmentBuilder;
|
import org.mozilla.gecko.background.healthreport.EnvironmentBuilder;
|
||||||
import org.mozilla.gecko.background.healthreport.HealthReportDatabaseStorage;
|
import org.mozilla.gecko.background.healthreport.HealthReportDatabaseStorage;
|
||||||
|
@ -38,6 +38,7 @@ import java.io.FileOutputStream;
|
||||||
import java.io.OutputStreamWriter;
|
import java.io.OutputStreamWriter;
|
||||||
import java.nio.charset.Charset;
|
import java.nio.charset.Charset;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Iterator;
|
||||||
import java.util.Scanner;
|
import java.util.Scanner;
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
|
||||||
|
@ -50,8 +51,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
* Keep an instance of this class around.
|
* Keep an instance of this class around.
|
||||||
*
|
*
|
||||||
* Tell it when an environment attribute has changed: call {@link
|
* Tell it when an environment attribute has changed: call {@link
|
||||||
* #onBlocklistPrefChanged(boolean)} or {@link
|
* #onAppLocaleChanged(String)} followed by {@link
|
||||||
* #onTelemetryPrefChanged(boolean)}, followed by {@link
|
|
||||||
* #onEnvironmentChanged()}.
|
* #onEnvironmentChanged()}.
|
||||||
*
|
*
|
||||||
* Use it to record events: {@link #recordSearch(String, String)}.
|
* Use it to record events: {@link #recordSearch(String, String)}.
|
||||||
|
@ -60,8 +60,9 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
*/
|
*/
|
||||||
public class BrowserHealthRecorder implements GeckoEventListener {
|
public class BrowserHealthRecorder implements GeckoEventListener {
|
||||||
private static final String LOG_TAG = "GeckoHealthRec";
|
private static final String LOG_TAG = "GeckoHealthRec";
|
||||||
|
private static final String PREF_ACCEPT_LANG = "intl.accept_languages";
|
||||||
private static final String PREF_BLOCKLIST_ENABLED = "extensions.blocklist.enabled";
|
private static final String PREF_BLOCKLIST_ENABLED = "extensions.blocklist.enabled";
|
||||||
private static final String EVENT_ADDONS_ALL = "Addons:All";
|
private static final String EVENT_SNAPSHOT = "HealthReport:Snapshot";
|
||||||
private static final String EVENT_ADDONS_CHANGE = "Addons:Change";
|
private static final String EVENT_ADDONS_CHANGE = "Addons:Change";
|
||||||
private static final String EVENT_ADDONS_UNINSTALLING = "Addons:Uninstalling";
|
private static final String EVENT_ADDONS_UNINSTALLING = "Addons:Uninstalling";
|
||||||
private static final String EVENT_PREF_CHANGE = "Pref:Change";
|
private static final String EVENT_PREF_CHANGE = "Pref:Change";
|
||||||
|
@ -242,8 +243,15 @@ public class BrowserHealthRecorder implements GeckoEventListener {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This constructor does IO. Run it on a background thread.
|
* This constructor does IO. Run it on a background thread.
|
||||||
|
*
|
||||||
|
* appLocale can be null, which indicates that it will be provided later.
|
||||||
*/
|
*/
|
||||||
public BrowserHealthRecorder(final Context context, final String profilePath, final EventDispatcher dispatcher, SessionInformation previousSession) {
|
public BrowserHealthRecorder(final Context context,
|
||||||
|
final String profilePath,
|
||||||
|
final EventDispatcher dispatcher,
|
||||||
|
final String osLocale,
|
||||||
|
final String appLocale,
|
||||||
|
SessionInformation previousSession) {
|
||||||
Log.d(LOG_TAG, "Initializing. Dispatcher is " + dispatcher);
|
Log.d(LOG_TAG, "Initializing. Dispatcher is " + dispatcher);
|
||||||
this.dispatcher = dispatcher;
|
this.dispatcher = dispatcher;
|
||||||
this.previousSession = previousSession;
|
this.previousSession = previousSession;
|
||||||
|
@ -263,9 +271,12 @@ public class BrowserHealthRecorder implements GeckoEventListener {
|
||||||
this.client = null;
|
this.client = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Note that the PIC is not necessarily fully initialized at this point:
|
||||||
|
// we haven't set the app locale. This must be done before an environment
|
||||||
|
// is recorded.
|
||||||
this.profileCache = new ProfileInformationCache(profilePath);
|
this.profileCache = new ProfileInformationCache(profilePath);
|
||||||
try {
|
try {
|
||||||
this.initialize(context, profilePath);
|
this.initialize(context, profilePath, osLocale, appLocale);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
Log.e(LOG_TAG, "Exception initializing.", e);
|
Log.e(LOG_TAG, "Exception initializing.", e);
|
||||||
}
|
}
|
||||||
|
@ -299,7 +310,7 @@ public class BrowserHealthRecorder implements GeckoEventListener {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void unregisterEventListeners() {
|
private void unregisterEventListeners() {
|
||||||
this.dispatcher.unregisterEventListener(EVENT_ADDONS_ALL, this);
|
this.dispatcher.unregisterEventListener(EVENT_SNAPSHOT, this);
|
||||||
this.dispatcher.unregisterEventListener(EVENT_ADDONS_CHANGE, this);
|
this.dispatcher.unregisterEventListener(EVENT_ADDONS_CHANGE, this);
|
||||||
this.dispatcher.unregisterEventListener(EVENT_ADDONS_UNINSTALLING, this);
|
this.dispatcher.unregisterEventListener(EVENT_ADDONS_UNINSTALLING, this);
|
||||||
this.dispatcher.unregisterEventListener(EVENT_PREF_CHANGE, this);
|
this.dispatcher.unregisterEventListener(EVENT_PREF_CHANGE, this);
|
||||||
|
@ -307,14 +318,9 @@ public class BrowserHealthRecorder implements GeckoEventListener {
|
||||||
this.dispatcher.unregisterEventListener(EVENT_SEARCH, this);
|
this.dispatcher.unregisterEventListener(EVENT_SEARCH, this);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void onBlocklistPrefChanged(boolean to) {
|
public void onAppLocaleChanged(String to) {
|
||||||
this.profileCache.beginInitialization();
|
this.profileCache.beginInitialization();
|
||||||
this.profileCache.setBlocklistEnabled(to);
|
this.profileCache.setAppLocale(to);
|
||||||
}
|
|
||||||
|
|
||||||
public void onTelemetryPrefChanged(boolean to) {
|
|
||||||
this.profileCache.beginInitialization();
|
|
||||||
this.profileCache.setTelemetryEnabled(to);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void onAddonChanged(String id, JSONObject json) {
|
public void onAddonChanged(String id, JSONObject json) {
|
||||||
|
@ -340,8 +346,7 @@ public class BrowserHealthRecorder implements GeckoEventListener {
|
||||||
* environment, such that a new environment should be computed and prepared
|
* environment, such that a new environment should be computed and prepared
|
||||||
* for use in future events.
|
* for use in future events.
|
||||||
*
|
*
|
||||||
* Invoke this method after calls that mutate the environment, such as
|
* Invoke this method after calls that mutate the environment.
|
||||||
* {@link #onBlocklistPrefChanged(boolean)}.
|
|
||||||
*
|
*
|
||||||
* If this change resulted in a transition between two environments, {@link
|
* If this change resulted in a transition between two environments, {@link
|
||||||
* #onEnvironmentTransition(int, int)} will be invoked on the background
|
* #onEnvironmentTransition(int, int)} will be invoked on the background
|
||||||
|
@ -491,14 +496,36 @@ public class BrowserHealthRecorder implements GeckoEventListener {
|
||||||
return time;
|
return time;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handlePrefValue(final String pref, final boolean value) {
|
private void onPrefMessage(final String pref, final JSONObject message) {
|
||||||
Log.d(LOG_TAG, "Incorporating environment: " + pref + " = " + value);
|
Log.d(LOG_TAG, "Incorporating environment: " + pref);
|
||||||
if (AppConstants.TELEMETRY_PREF_NAME.equals(pref)) {
|
if (PREF_ACCEPT_LANG.equals(pref)) {
|
||||||
profileCache.setTelemetryEnabled(value);
|
// We only record whether this is user-set.
|
||||||
|
try {
|
||||||
|
this.profileCache.beginInitialization();
|
||||||
|
this.profileCache.setAcceptLangUserSet(message.getBoolean("isUserSet"));
|
||||||
|
} catch (JSONException ex) {
|
||||||
|
Log.w(LOG_TAG, "Unexpected JSONException fetching isUserSet for " + pref);
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (PREF_BLOCKLIST_ENABLED.equals(pref)) {
|
|
||||||
profileCache.setBlocklistEnabled(value);
|
// (We only handle boolean prefs right now.)
|
||||||
|
try {
|
||||||
|
boolean value = message.getBoolean("value");
|
||||||
|
|
||||||
|
if (AppConstants.TELEMETRY_PREF_NAME.equals(pref)) {
|
||||||
|
this.profileCache.beginInitialization();
|
||||||
|
this.profileCache.setTelemetryEnabled(value);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (PREF_BLOCKLIST_ENABLED.equals(pref)) {
|
||||||
|
this.profileCache.beginInitialization();
|
||||||
|
this.profileCache.setBlocklistEnabled(value);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} catch (JSONException ex) {
|
||||||
|
Log.w(LOG_TAG, "Unexpected JSONException fetching boolean value for " + pref);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
Log.w(LOG_TAG, "Unexpected pref: " + pref);
|
Log.w(LOG_TAG, "Unexpected pref: " + pref);
|
||||||
|
@ -571,7 +598,9 @@ public class BrowserHealthRecorder implements GeckoEventListener {
|
||||||
* Add provider-specific initialization in this method.
|
* Add provider-specific initialization in this method.
|
||||||
*/
|
*/
|
||||||
private synchronized void initialize(final Context context,
|
private synchronized void initialize(final Context context,
|
||||||
final String profilePath)
|
final String profilePath,
|
||||||
|
final String osLocale,
|
||||||
|
final String appLocale)
|
||||||
throws java.io.IOException {
|
throws java.io.IOException {
|
||||||
|
|
||||||
Log.d(LOG_TAG, "Initializing profile cache.");
|
Log.d(LOG_TAG, "Initializing profile cache.");
|
||||||
|
@ -579,6 +608,9 @@ public class BrowserHealthRecorder implements GeckoEventListener {
|
||||||
|
|
||||||
// If we can restore state from last time, great.
|
// If we can restore state from last time, great.
|
||||||
if (this.profileCache.restoreUnlessInitialized()) {
|
if (this.profileCache.restoreUnlessInitialized()) {
|
||||||
|
this.profileCache.updateLocales(osLocale, appLocale);
|
||||||
|
this.profileCache.completeInitialization();
|
||||||
|
|
||||||
Log.d(LOG_TAG, "Successfully restored state. Initializing storage.");
|
Log.d(LOG_TAG, "Successfully restored state. Initializing storage.");
|
||||||
initializeStorage();
|
initializeStorage();
|
||||||
return;
|
return;
|
||||||
|
@ -587,31 +619,24 @@ public class BrowserHealthRecorder implements GeckoEventListener {
|
||||||
// Otherwise, let's initialize it from scratch.
|
// Otherwise, let's initialize it from scratch.
|
||||||
this.profileCache.beginInitialization();
|
this.profileCache.beginInitialization();
|
||||||
this.profileCache.setProfileCreationTime(getAndPersistProfileInitTime(context, profilePath));
|
this.profileCache.setProfileCreationTime(getAndPersistProfileInitTime(context, profilePath));
|
||||||
|
this.profileCache.setOSLocale(osLocale);
|
||||||
|
this.profileCache.setAppLocale(appLocale);
|
||||||
|
|
||||||
final BrowserHealthRecorder self = this;
|
// Because the distribution lookup can take some time, do it at the end of
|
||||||
|
// our background startup work, along with the Gecko snapshot fetch.
|
||||||
PrefHandler handler = new PrefsHelper.PrefHandlerBase() {
|
final GeckoEventListener self = this;
|
||||||
|
ThreadUtils.postToBackgroundThread(new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
public void prefValue(String pref, boolean value) {
|
public void run() {
|
||||||
handlePrefValue(pref, value);
|
final DistributionDescriptor desc = new Distribution(context).getDescriptor();
|
||||||
|
if (desc != null && desc.valid) {
|
||||||
|
profileCache.setDistributionString(desc.id, desc.version);
|
||||||
|
}
|
||||||
|
Log.d(LOG_TAG, "Requesting all add-ons and FHR prefs from Gecko.");
|
||||||
|
dispatcher.registerEventListener(EVENT_SNAPSHOT, self);
|
||||||
|
GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("HealthReport:RequestSnapshot", null));
|
||||||
}
|
}
|
||||||
|
});
|
||||||
@Override
|
|
||||||
public void finish() {
|
|
||||||
Log.d(LOG_TAG, "Requesting all add-ons from Gecko.");
|
|
||||||
dispatcher.registerEventListener(EVENT_ADDONS_ALL, self);
|
|
||||||
GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("Addons:FetchAll", null));
|
|
||||||
// Wait for the broadcast event which completes our initialization.
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Oh, singletons.
|
|
||||||
PrefsHelper.getPrefs(new String[] {
|
|
||||||
AppConstants.TELEMETRY_PREF_NAME,
|
|
||||||
PREF_BLOCKLIST_ENABLED
|
|
||||||
},
|
|
||||||
handler);
|
|
||||||
Log.d(LOG_TAG, "Requested prefs.");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -638,12 +663,22 @@ public class BrowserHealthRecorder implements GeckoEventListener {
|
||||||
@Override
|
@Override
|
||||||
public void handleMessage(String event, JSONObject message) {
|
public void handleMessage(String event, JSONObject message) {
|
||||||
try {
|
try {
|
||||||
if (EVENT_ADDONS_ALL.equals(event)) {
|
if (EVENT_SNAPSHOT.equals(event)) {
|
||||||
Log.d(LOG_TAG, "Got all add-ons.");
|
Log.d(LOG_TAG, "Got all add-ons and prefs.");
|
||||||
try {
|
try {
|
||||||
JSONObject addons = message.getJSONObject("json");
|
JSONObject json = message.getJSONObject("json");
|
||||||
|
JSONObject addons = json.getJSONObject("addons");
|
||||||
Log.i(LOG_TAG, "Persisting " + addons.length() + " add-ons.");
|
Log.i(LOG_TAG, "Persisting " + addons.length() + " add-ons.");
|
||||||
profileCache.setJSONForAddons(addons);
|
profileCache.setJSONForAddons(addons);
|
||||||
|
|
||||||
|
JSONObject prefs = json.getJSONObject("prefs");
|
||||||
|
Log.i(LOG_TAG, "Persisting prefs.");
|
||||||
|
Iterator<?> keys = prefs.keys();
|
||||||
|
while (keys.hasNext()) {
|
||||||
|
String pref = (String) keys.next();
|
||||||
|
this.onPrefMessage(pref, prefs.getJSONObject(pref));
|
||||||
|
}
|
||||||
|
|
||||||
profileCache.completeInitialization();
|
profileCache.completeInitialization();
|
||||||
} catch (java.io.IOException e) {
|
} catch (java.io.IOException e) {
|
||||||
Log.e(LOG_TAG, "Error completing profile cache initialization.", e);
|
Log.e(LOG_TAG, "Error completing profile cache initialization.", e);
|
||||||
|
@ -675,7 +710,7 @@ public class BrowserHealthRecorder implements GeckoEventListener {
|
||||||
if (EVENT_PREF_CHANGE.equals(event)) {
|
if (EVENT_PREF_CHANGE.equals(event)) {
|
||||||
final String pref = message.getString("pref");
|
final String pref = message.getString("pref");
|
||||||
Log.d(LOG_TAG, "Pref changed: " + pref);
|
Log.d(LOG_TAG, "Pref changed: " + pref);
|
||||||
handlePrefValue(pref, message.getBoolean("value"));
|
this.onPrefMessage(pref, message);
|
||||||
this.onEnvironmentChanged();
|
this.onEnvironmentChanged();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,3 +5,609 @@
|
||||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
|
|
||||||
DIRS += ['locales']
|
DIRS += ['locales']
|
||||||
|
|
||||||
|
include('android-services.mozbuild')
|
||||||
|
|
||||||
|
ANDROID_GENERATED_RESFILES += [
|
||||||
|
'res/drawable-hdpi/icon.png',
|
||||||
|
'res/drawable-mdpi/icon.png',
|
||||||
|
'res/drawable-xhdpi/icon.png',
|
||||||
|
'res/drawable-xxhdpi/icon.png',
|
||||||
|
'res/values/strings.xml',
|
||||||
|
]
|
||||||
|
|
||||||
|
ANDROID_RESFILES += [
|
||||||
|
'resources/anim/grow_fade_in.xml',
|
||||||
|
'resources/anim/grow_fade_in_center.xml',
|
||||||
|
'resources/anim/popup_hide.xml',
|
||||||
|
'resources/anim/popup_show.xml',
|
||||||
|
'resources/anim/progress_spinner.xml',
|
||||||
|
'resources/anim/shrink_fade_out.xml',
|
||||||
|
'resources/color/primary_text.xml',
|
||||||
|
'resources/color/primary_text_inverse.xml',
|
||||||
|
'resources/color/secondary_text.xml',
|
||||||
|
'resources/color/secondary_text_inverse.xml',
|
||||||
|
'resources/color/select_item_multichoice.xml',
|
||||||
|
'resources/color/tertiary_text.xml',
|
||||||
|
'resources/color/tertiary_text_inverse.xml',
|
||||||
|
'resources/color/top_sites_grid_item_title.xml',
|
||||||
|
'resources/color/url_bar_title.xml',
|
||||||
|
'resources/color/url_bar_title_hint.xml',
|
||||||
|
'resources/drawable-hdpi-v11/alert_addon.png',
|
||||||
|
'resources/drawable-hdpi-v11/alert_app.png',
|
||||||
|
'resources/drawable-hdpi-v11/alert_camera.png',
|
||||||
|
'resources/drawable-hdpi-v11/alert_download.png',
|
||||||
|
'resources/drawable-hdpi-v11/alert_mic.png',
|
||||||
|
'resources/drawable-hdpi-v11/alert_mic_camera.png',
|
||||||
|
'resources/drawable-hdpi-v11/firefox_settings_alert.png',
|
||||||
|
'resources/drawable-hdpi-v11/ic_menu_addons.png',
|
||||||
|
'resources/drawable-hdpi-v11/ic_menu_apps.png',
|
||||||
|
'resources/drawable-hdpi-v11/ic_menu_back.png',
|
||||||
|
'resources/drawable-hdpi-v11/ic_menu_bookmark_add.png',
|
||||||
|
'resources/drawable-hdpi-v11/ic_menu_bookmark_remove.png',
|
||||||
|
'resources/drawable-hdpi-v11/ic_menu_desktop_mode_off.png',
|
||||||
|
'resources/drawable-hdpi-v11/ic_menu_desktop_mode_on.png',
|
||||||
|
'resources/drawable-hdpi-v11/ic_menu_downloads.png',
|
||||||
|
'resources/drawable-hdpi-v11/ic_menu_find_in_page.png',
|
||||||
|
'resources/drawable-hdpi-v11/ic_menu_forward.png',
|
||||||
|
'resources/drawable-hdpi-v11/ic_menu_new_private_tab.png',
|
||||||
|
'resources/drawable-hdpi-v11/ic_menu_new_tab.png',
|
||||||
|
'resources/drawable-hdpi-v11/ic_menu_quit.png',
|
||||||
|
'resources/drawable-hdpi-v11/ic_menu_reload.png',
|
||||||
|
'resources/drawable-hdpi-v11/ic_menu_save_as_pdf.png',
|
||||||
|
'resources/drawable-hdpi-v11/ic_menu_settings.png',
|
||||||
|
'resources/drawable-hdpi-v11/ic_menu_share.png',
|
||||||
|
'resources/drawable-hdpi-v11/ic_menu_tools.png',
|
||||||
|
'resources/drawable-hdpi-v11/ic_status_logo.png',
|
||||||
|
'resources/drawable-hdpi/abouthome_thumbnail.png',
|
||||||
|
'resources/drawable-hdpi/alert_addon.png',
|
||||||
|
'resources/drawable-hdpi/alert_app.png',
|
||||||
|
'resources/drawable-hdpi/alert_camera.png',
|
||||||
|
'resources/drawable-hdpi/alert_download.png',
|
||||||
|
'resources/drawable-hdpi/alert_mic.png',
|
||||||
|
'resources/drawable-hdpi/alert_mic_camera.png',
|
||||||
|
'resources/drawable-hdpi/arrow_popup_bg.9.png',
|
||||||
|
'resources/drawable-hdpi/blank.png',
|
||||||
|
'resources/drawable-hdpi/bookmark_folder_closed.png',
|
||||||
|
'resources/drawable-hdpi/bookmark_folder_opened.png',
|
||||||
|
'resources/drawable-hdpi/close.png',
|
||||||
|
'resources/drawable-hdpi/favicon.png',
|
||||||
|
'resources/drawable-hdpi/find_close.png',
|
||||||
|
'resources/drawable-hdpi/find_next.png',
|
||||||
|
'resources/drawable-hdpi/find_prev.png',
|
||||||
|
'resources/drawable-hdpi/folder.png',
|
||||||
|
'resources/drawable-hdpi/grid_icon_bg_activated.9.png',
|
||||||
|
'resources/drawable-hdpi/grid_icon_bg_focused.9.png',
|
||||||
|
'resources/drawable-hdpi/handle_end.png',
|
||||||
|
'resources/drawable-hdpi/handle_middle.png',
|
||||||
|
'resources/drawable-hdpi/handle_start.png',
|
||||||
|
'resources/drawable-hdpi/history_tabs_indicator_selected.9.png',
|
||||||
|
'resources/drawable-hdpi/home_bg.png',
|
||||||
|
'resources/drawable-hdpi/home_star.png',
|
||||||
|
'resources/drawable-hdpi/home_tab_menu_strip.9.png',
|
||||||
|
'resources/drawable-hdpi/ic_menu_addons_filler.png',
|
||||||
|
'resources/drawable-hdpi/ic_menu_bookmark_add.png',
|
||||||
|
'resources/drawable-hdpi/ic_menu_bookmark_remove.png',
|
||||||
|
'resources/drawable-hdpi/ic_menu_character_encoding.png',
|
||||||
|
'resources/drawable-hdpi/ic_menu_forward.png',
|
||||||
|
'resources/drawable-hdpi/ic_menu_guest.png',
|
||||||
|
'resources/drawable-hdpi/ic_menu_new_private_tab.png',
|
||||||
|
'resources/drawable-hdpi/ic_menu_new_tab.png',
|
||||||
|
'resources/drawable-hdpi/ic_menu_reload.png',
|
||||||
|
'resources/drawable-hdpi/ic_status_logo.png',
|
||||||
|
'resources/drawable-hdpi/ic_url_bar_go.png',
|
||||||
|
'resources/drawable-hdpi/ic_url_bar_reader.png',
|
||||||
|
'resources/drawable-hdpi/ic_url_bar_search.png',
|
||||||
|
'resources/drawable-hdpi/ic_url_bar_star.png',
|
||||||
|
'resources/drawable-hdpi/ic_url_bar_tab.png',
|
||||||
|
'resources/drawable-hdpi/icon_bookmarks_empty.png',
|
||||||
|
'resources/drawable-hdpi/icon_last_tabs.png',
|
||||||
|
'resources/drawable-hdpi/icon_last_tabs_empty.png',
|
||||||
|
'resources/drawable-hdpi/icon_most_recent.png',
|
||||||
|
'resources/drawable-hdpi/icon_most_recent_empty.png',
|
||||||
|
'resources/drawable-hdpi/icon_most_visited.png',
|
||||||
|
'resources/drawable-hdpi/icon_openinapp.png',
|
||||||
|
'resources/drawable-hdpi/icon_pageaction.png',
|
||||||
|
'resources/drawable-hdpi/icon_reading_list_empty.png',
|
||||||
|
'resources/drawable-hdpi/larry.png',
|
||||||
|
'resources/drawable-hdpi/lock_identified.png',
|
||||||
|
'resources/drawable-hdpi/lock_verified.png',
|
||||||
|
'resources/drawable-hdpi/menu.png',
|
||||||
|
'resources/drawable-hdpi/menu_item_check.png',
|
||||||
|
'resources/drawable-hdpi/menu_item_more.png',
|
||||||
|
'resources/drawable-hdpi/menu_item_uncheck.png',
|
||||||
|
'resources/drawable-hdpi/menu_panel_bg.9.png',
|
||||||
|
'resources/drawable-hdpi/menu_pb.png',
|
||||||
|
'resources/drawable-hdpi/menu_popup_arrow_bottom.png',
|
||||||
|
'resources/drawable-hdpi/menu_popup_arrow_top.png',
|
||||||
|
'resources/drawable-hdpi/menu_popup_bg.9.png',
|
||||||
|
'resources/drawable-hdpi/pause.png',
|
||||||
|
'resources/drawable-hdpi/pin.png',
|
||||||
|
'resources/drawable-hdpi/play.png',
|
||||||
|
'resources/drawable-hdpi/reader.png',
|
||||||
|
'resources/drawable-hdpi/reader_active.png',
|
||||||
|
'resources/drawable-hdpi/reader_cropped.png',
|
||||||
|
'resources/drawable-hdpi/reading_list.png',
|
||||||
|
'resources/drawable-hdpi/shield.png',
|
||||||
|
'resources/drawable-hdpi/shield_doorhanger.png',
|
||||||
|
'resources/drawable-hdpi/spinner_default.9.png',
|
||||||
|
'resources/drawable-hdpi/spinner_focused.9.png',
|
||||||
|
'resources/drawable-hdpi/spinner_pressed.9.png',
|
||||||
|
'resources/drawable-hdpi/tab_close.png',
|
||||||
|
'resources/drawable-hdpi/tab_indicator_divider.9.png',
|
||||||
|
'resources/drawable-hdpi/tab_indicator_selected.9.png',
|
||||||
|
'resources/drawable-hdpi/tab_indicator_selected_focused.9.png',
|
||||||
|
'resources/drawable-hdpi/tab_new.png',
|
||||||
|
'resources/drawable-hdpi/tab_new_pb.png',
|
||||||
|
'resources/drawable-hdpi/tab_thumbnail_default.png',
|
||||||
|
'resources/drawable-hdpi/tab_thumbnail_shadow.png',
|
||||||
|
'resources/drawable-hdpi/tabs_count.png',
|
||||||
|
'resources/drawable-hdpi/tabs_count_foreground.png',
|
||||||
|
'resources/drawable-hdpi/tabs_normal.png',
|
||||||
|
'resources/drawable-hdpi/tabs_private.png',
|
||||||
|
'resources/drawable-hdpi/tabs_synced.png',
|
||||||
|
'resources/drawable-hdpi/tip_addsearch.png',
|
||||||
|
'resources/drawable-hdpi/top_site_add.png',
|
||||||
|
'resources/drawable-hdpi/url_bar_entry_default.9.png',
|
||||||
|
'resources/drawable-hdpi/url_bar_entry_default_pb.9.png',
|
||||||
|
'resources/drawable-hdpi/url_bar_entry_pressed.9.png',
|
||||||
|
'resources/drawable-hdpi/url_bar_entry_pressed_pb.9.png',
|
||||||
|
'resources/drawable-hdpi/urlbar_stop.png',
|
||||||
|
'resources/drawable-hdpi/validation_arrow.png',
|
||||||
|
'resources/drawable-hdpi/validation_arrow_inverted.png',
|
||||||
|
'resources/drawable-hdpi/validation_bg.9.png',
|
||||||
|
'resources/drawable-hdpi/warning.png',
|
||||||
|
'resources/drawable-hdpi/warning_doorhanger.png',
|
||||||
|
'resources/drawable-large-hdpi-v11/arrow_popup_bg.9.png',
|
||||||
|
'resources/drawable-large-hdpi-v11/ic_menu_forward.png',
|
||||||
|
'resources/drawable-large-hdpi-v11/ic_menu_reload.png',
|
||||||
|
'resources/drawable-large-hdpi-v11/menu.png',
|
||||||
|
'resources/drawable-large-land-v11/home_history_tabs_indicator.xml',
|
||||||
|
'resources/drawable-large-mdpi-v11/arrow_popup_bg.9.png',
|
||||||
|
'resources/drawable-large-mdpi-v11/ic_menu_forward.png',
|
||||||
|
'resources/drawable-large-mdpi-v11/ic_menu_reload.png',
|
||||||
|
'resources/drawable-large-mdpi-v11/menu.png',
|
||||||
|
'resources/drawable-large-xhdpi-v11/arrow_popup_bg.9.png',
|
||||||
|
'resources/drawable-large-xhdpi-v11/ic_menu_forward.png',
|
||||||
|
'resources/drawable-large-xhdpi-v11/ic_menu_reload.png',
|
||||||
|
'resources/drawable-large-xhdpi-v11/menu.png',
|
||||||
|
'resources/drawable-mdpi-v11/alert_addon.png',
|
||||||
|
'resources/drawable-mdpi-v11/alert_app.png',
|
||||||
|
'resources/drawable-mdpi-v11/alert_camera.png',
|
||||||
|
'resources/drawable-mdpi-v11/alert_download.png',
|
||||||
|
'resources/drawable-mdpi-v11/alert_mic.png',
|
||||||
|
'resources/drawable-mdpi-v11/alert_mic_camera.png',
|
||||||
|
'resources/drawable-mdpi-v11/firefox_settings_alert.png',
|
||||||
|
'resources/drawable-mdpi-v11/ic_menu_addons.png',
|
||||||
|
'resources/drawable-mdpi-v11/ic_menu_apps.png',
|
||||||
|
'resources/drawable-mdpi-v11/ic_menu_back.png',
|
||||||
|
'resources/drawable-mdpi-v11/ic_menu_bookmark_add.png',
|
||||||
|
'resources/drawable-mdpi-v11/ic_menu_bookmark_remove.png',
|
||||||
|
'resources/drawable-mdpi-v11/ic_menu_desktop_mode_off.png',
|
||||||
|
'resources/drawable-mdpi-v11/ic_menu_desktop_mode_on.png',
|
||||||
|
'resources/drawable-mdpi-v11/ic_menu_downloads.png',
|
||||||
|
'resources/drawable-mdpi-v11/ic_menu_find_in_page.png',
|
||||||
|
'resources/drawable-mdpi-v11/ic_menu_forward.png',
|
||||||
|
'resources/drawable-mdpi-v11/ic_menu_new_private_tab.png',
|
||||||
|
'resources/drawable-mdpi-v11/ic_menu_new_tab.png',
|
||||||
|
'resources/drawable-mdpi-v11/ic_menu_quit.png',
|
||||||
|
'resources/drawable-mdpi-v11/ic_menu_reload.png',
|
||||||
|
'resources/drawable-mdpi-v11/ic_menu_save_as_pdf.png',
|
||||||
|
'resources/drawable-mdpi-v11/ic_menu_settings.png',
|
||||||
|
'resources/drawable-mdpi-v11/ic_menu_share.png',
|
||||||
|
'resources/drawable-mdpi-v11/ic_menu_tools.png',
|
||||||
|
'resources/drawable-mdpi-v11/ic_status_logo.png',
|
||||||
|
'resources/drawable-mdpi/abouthome_thumbnail.png',
|
||||||
|
'resources/drawable-mdpi/alert_addon.png',
|
||||||
|
'resources/drawable-mdpi/alert_app.png',
|
||||||
|
'resources/drawable-mdpi/alert_camera.png',
|
||||||
|
'resources/drawable-mdpi/alert_download.png',
|
||||||
|
'resources/drawable-mdpi/alert_mic.png',
|
||||||
|
'resources/drawable-mdpi/alert_mic_camera.png',
|
||||||
|
'resources/drawable-mdpi/arrow_popup_bg.9.png',
|
||||||
|
'resources/drawable-mdpi/autocomplete_list_bg.9.png',
|
||||||
|
'resources/drawable-mdpi/blank.png',
|
||||||
|
'resources/drawable-mdpi/bookmark_folder_closed.png',
|
||||||
|
'resources/drawable-mdpi/bookmark_folder_opened.png',
|
||||||
|
'resources/drawable-mdpi/bookmarkdefaults_favicon_addons.png',
|
||||||
|
'resources/drawable-mdpi/bookmarkdefaults_favicon_support.png',
|
||||||
|
'resources/drawable-mdpi/close.png',
|
||||||
|
'resources/drawable-mdpi/desktop_notification.png',
|
||||||
|
'resources/drawable-mdpi/favicon.png',
|
||||||
|
'resources/drawable-mdpi/find_close.png',
|
||||||
|
'resources/drawable-mdpi/find_next.png',
|
||||||
|
'resources/drawable-mdpi/find_prev.png',
|
||||||
|
'resources/drawable-mdpi/folder.png',
|
||||||
|
'resources/drawable-mdpi/grid_icon_bg_activated.9.png',
|
||||||
|
'resources/drawable-mdpi/grid_icon_bg_focused.9.png',
|
||||||
|
'resources/drawable-mdpi/handle_end.png',
|
||||||
|
'resources/drawable-mdpi/handle_middle.png',
|
||||||
|
'resources/drawable-mdpi/handle_start.png',
|
||||||
|
'resources/drawable-mdpi/history_tabs_indicator_selected.9.png',
|
||||||
|
'resources/drawable-mdpi/home_tab_menu_strip.9.png',
|
||||||
|
'resources/drawable-mdpi/ic_menu_addons_filler.png',
|
||||||
|
'resources/drawable-mdpi/ic_menu_bookmark_add.png',
|
||||||
|
'resources/drawable-mdpi/ic_menu_bookmark_remove.png',
|
||||||
|
'resources/drawable-mdpi/ic_menu_character_encoding.png',
|
||||||
|
'resources/drawable-mdpi/ic_menu_forward.png',
|
||||||
|
'resources/drawable-mdpi/ic_menu_guest.png',
|
||||||
|
'resources/drawable-mdpi/ic_menu_new_private_tab.png',
|
||||||
|
'resources/drawable-mdpi/ic_menu_new_tab.png',
|
||||||
|
'resources/drawable-mdpi/ic_menu_reload.png',
|
||||||
|
'resources/drawable-mdpi/ic_status_logo.png',
|
||||||
|
'resources/drawable-mdpi/ic_url_bar_go.png',
|
||||||
|
'resources/drawable-mdpi/ic_url_bar_reader.png',
|
||||||
|
'resources/drawable-mdpi/ic_url_bar_search.png',
|
||||||
|
'resources/drawable-mdpi/ic_url_bar_star.png',
|
||||||
|
'resources/drawable-mdpi/ic_url_bar_tab.png',
|
||||||
|
'resources/drawable-mdpi/icon_bookmarks_empty.png',
|
||||||
|
'resources/drawable-mdpi/icon_last_tabs.png',
|
||||||
|
'resources/drawable-mdpi/icon_last_tabs_empty.png',
|
||||||
|
'resources/drawable-mdpi/icon_most_recent.png',
|
||||||
|
'resources/drawable-mdpi/icon_most_recent_empty.png',
|
||||||
|
'resources/drawable-mdpi/icon_most_visited.png',
|
||||||
|
'resources/drawable-mdpi/icon_openinapp.png',
|
||||||
|
'resources/drawable-mdpi/icon_pageaction.png',
|
||||||
|
'resources/drawable-mdpi/icon_reading_list_empty.png',
|
||||||
|
'resources/drawable-mdpi/larry.png',
|
||||||
|
'resources/drawable-mdpi/lock_identified.png',
|
||||||
|
'resources/drawable-mdpi/lock_verified.png',
|
||||||
|
'resources/drawable-mdpi/marketplace.png',
|
||||||
|
'resources/drawable-mdpi/menu.png',
|
||||||
|
'resources/drawable-mdpi/menu_item_check.png',
|
||||||
|
'resources/drawable-mdpi/menu_item_more.png',
|
||||||
|
'resources/drawable-mdpi/menu_item_uncheck.png',
|
||||||
|
'resources/drawable-mdpi/menu_panel_bg.9.png',
|
||||||
|
'resources/drawable-mdpi/menu_pb.png',
|
||||||
|
'resources/drawable-mdpi/menu_popup_arrow_bottom.png',
|
||||||
|
'resources/drawable-mdpi/menu_popup_arrow_top.png',
|
||||||
|
'resources/drawable-mdpi/menu_popup_bg.9.png',
|
||||||
|
'resources/drawable-mdpi/pause.png',
|
||||||
|
'resources/drawable-mdpi/pin.png',
|
||||||
|
'resources/drawable-mdpi/play.png',
|
||||||
|
'resources/drawable-mdpi/progress_spinner.png',
|
||||||
|
'resources/drawable-mdpi/reader.png',
|
||||||
|
'resources/drawable-mdpi/reader_active.png',
|
||||||
|
'resources/drawable-mdpi/reader_cropped.png',
|
||||||
|
'resources/drawable-mdpi/reading_list.png',
|
||||||
|
'resources/drawable-mdpi/scrollbar.png',
|
||||||
|
'resources/drawable-mdpi/shadow.png',
|
||||||
|
'resources/drawable-mdpi/shield.png',
|
||||||
|
'resources/drawable-mdpi/shield_doorhanger.png',
|
||||||
|
'resources/drawable-mdpi/spinner_default.9.png',
|
||||||
|
'resources/drawable-mdpi/spinner_focused.9.png',
|
||||||
|
'resources/drawable-mdpi/spinner_pressed.9.png',
|
||||||
|
'resources/drawable-mdpi/start.png',
|
||||||
|
'resources/drawable-mdpi/tab_close.png',
|
||||||
|
'resources/drawable-mdpi/tab_indicator_divider.9.png',
|
||||||
|
'resources/drawable-mdpi/tab_indicator_selected.9.png',
|
||||||
|
'resources/drawable-mdpi/tab_indicator_selected_focused.9.png',
|
||||||
|
'resources/drawable-mdpi/tab_new.png',
|
||||||
|
'resources/drawable-mdpi/tab_new_pb.png',
|
||||||
|
'resources/drawable-mdpi/tab_thumbnail_default.png',
|
||||||
|
'resources/drawable-mdpi/tab_thumbnail_shadow.png',
|
||||||
|
'resources/drawable-mdpi/tabs_count.png',
|
||||||
|
'resources/drawable-mdpi/tabs_count_foreground.png',
|
||||||
|
'resources/drawable-mdpi/tabs_normal.png',
|
||||||
|
'resources/drawable-mdpi/tabs_private.png',
|
||||||
|
'resources/drawable-mdpi/tabs_synced.png',
|
||||||
|
'resources/drawable-mdpi/tip_addsearch.png',
|
||||||
|
'resources/drawable-mdpi/toast.9.png',
|
||||||
|
'resources/drawable-mdpi/toast_button_focused.9.png',
|
||||||
|
'resources/drawable-mdpi/toast_button_pressed.9.png',
|
||||||
|
'resources/drawable-mdpi/toast_divider.9.png',
|
||||||
|
'resources/drawable-mdpi/top_site_add.png',
|
||||||
|
'resources/drawable-mdpi/url_bar_entry_default.9.png',
|
||||||
|
'resources/drawable-mdpi/url_bar_entry_default_pb.9.png',
|
||||||
|
'resources/drawable-mdpi/url_bar_entry_pressed.9.png',
|
||||||
|
'resources/drawable-mdpi/url_bar_entry_pressed_pb.9.png',
|
||||||
|
'resources/drawable-mdpi/urlbar_stop.png',
|
||||||
|
'resources/drawable-mdpi/validation_arrow.png',
|
||||||
|
'resources/drawable-mdpi/validation_arrow_inverted.png',
|
||||||
|
'resources/drawable-mdpi/validation_bg.9.png',
|
||||||
|
'resources/drawable-mdpi/warning.png',
|
||||||
|
'resources/drawable-mdpi/warning_doorhanger.png',
|
||||||
|
'resources/drawable-xhdpi-v11/alert_addon.png',
|
||||||
|
'resources/drawable-xhdpi-v11/alert_app.png',
|
||||||
|
'resources/drawable-xhdpi-v11/alert_camera.png',
|
||||||
|
'resources/drawable-xhdpi-v11/alert_download.png',
|
||||||
|
'resources/drawable-xhdpi-v11/alert_mic.png',
|
||||||
|
'resources/drawable-xhdpi-v11/alert_mic_camera.png',
|
||||||
|
'resources/drawable-xhdpi-v11/firefox_settings_alert.png',
|
||||||
|
'resources/drawable-xhdpi-v11/ic_menu_addons.png',
|
||||||
|
'resources/drawable-xhdpi-v11/ic_menu_apps.png',
|
||||||
|
'resources/drawable-xhdpi-v11/ic_menu_back.png',
|
||||||
|
'resources/drawable-xhdpi-v11/ic_menu_bookmark_add.png',
|
||||||
|
'resources/drawable-xhdpi-v11/ic_menu_bookmark_remove.png',
|
||||||
|
'resources/drawable-xhdpi-v11/ic_menu_desktop_mode_off.png',
|
||||||
|
'resources/drawable-xhdpi-v11/ic_menu_desktop_mode_on.png',
|
||||||
|
'resources/drawable-xhdpi-v11/ic_menu_downloads.png',
|
||||||
|
'resources/drawable-xhdpi-v11/ic_menu_find_in_page.png',
|
||||||
|
'resources/drawable-xhdpi-v11/ic_menu_forward.png',
|
||||||
|
'resources/drawable-xhdpi-v11/ic_menu_new_private_tab.png',
|
||||||
|
'resources/drawable-xhdpi-v11/ic_menu_new_tab.png',
|
||||||
|
'resources/drawable-xhdpi-v11/ic_menu_quit.png',
|
||||||
|
'resources/drawable-xhdpi-v11/ic_menu_reload.png',
|
||||||
|
'resources/drawable-xhdpi-v11/ic_menu_save_as_pdf.png',
|
||||||
|
'resources/drawable-xhdpi-v11/ic_menu_settings.png',
|
||||||
|
'resources/drawable-xhdpi-v11/ic_menu_share.png',
|
||||||
|
'resources/drawable-xhdpi-v11/ic_menu_tools.png',
|
||||||
|
'resources/drawable-xhdpi-v11/ic_status_logo.png',
|
||||||
|
'resources/drawable-xhdpi/abouthome_thumbnail.png',
|
||||||
|
'resources/drawable-xhdpi/alert_addon.png',
|
||||||
|
'resources/drawable-xhdpi/alert_app.png',
|
||||||
|
'resources/drawable-xhdpi/alert_camera.png',
|
||||||
|
'resources/drawable-xhdpi/alert_download.png',
|
||||||
|
'resources/drawable-xhdpi/alert_mic.png',
|
||||||
|
'resources/drawable-xhdpi/alert_mic_camera.png',
|
||||||
|
'resources/drawable-xhdpi/arrow_popup_bg.9.png',
|
||||||
|
'resources/drawable-xhdpi/blank.png',
|
||||||
|
'resources/drawable-xhdpi/bookmark_folder_closed.png',
|
||||||
|
'resources/drawable-xhdpi/bookmark_folder_opened.png',
|
||||||
|
'resources/drawable-xhdpi/close.png',
|
||||||
|
'resources/drawable-xhdpi/favicon.png',
|
||||||
|
'resources/drawable-xhdpi/find_close.png',
|
||||||
|
'resources/drawable-xhdpi/find_next.png',
|
||||||
|
'resources/drawable-xhdpi/find_prev.png',
|
||||||
|
'resources/drawable-xhdpi/folder.png',
|
||||||
|
'resources/drawable-xhdpi/grid_icon_bg_activated.9.png',
|
||||||
|
'resources/drawable-xhdpi/grid_icon_bg_focused.9.png',
|
||||||
|
'resources/drawable-xhdpi/handle_end.png',
|
||||||
|
'resources/drawable-xhdpi/handle_middle.png',
|
||||||
|
'resources/drawable-xhdpi/handle_start.png',
|
||||||
|
'resources/drawable-xhdpi/history_tabs_indicator_selected.9.png',
|
||||||
|
'resources/drawable-xhdpi/home_tab_menu_strip.9.png',
|
||||||
|
'resources/drawable-xhdpi/ic_menu_addons_filler.png',
|
||||||
|
'resources/drawable-xhdpi/ic_menu_bookmark_add.png',
|
||||||
|
'resources/drawable-xhdpi/ic_menu_bookmark_remove.png',
|
||||||
|
'resources/drawable-xhdpi/ic_menu_character_encoding.png',
|
||||||
|
'resources/drawable-xhdpi/ic_menu_forward.png',
|
||||||
|
'resources/drawable-xhdpi/ic_menu_guest.png',
|
||||||
|
'resources/drawable-xhdpi/ic_menu_new_private_tab.png',
|
||||||
|
'resources/drawable-xhdpi/ic_menu_new_tab.png',
|
||||||
|
'resources/drawable-xhdpi/ic_menu_reload.png',
|
||||||
|
'resources/drawable-xhdpi/ic_status_logo.png',
|
||||||
|
'resources/drawable-xhdpi/ic_url_bar_go.png',
|
||||||
|
'resources/drawable-xhdpi/ic_url_bar_reader.png',
|
||||||
|
'resources/drawable-xhdpi/ic_url_bar_search.png',
|
||||||
|
'resources/drawable-xhdpi/ic_url_bar_star.png',
|
||||||
|
'resources/drawable-xhdpi/ic_url_bar_tab.png',
|
||||||
|
'resources/drawable-xhdpi/icon_bookmarks_empty.png',
|
||||||
|
'resources/drawable-xhdpi/icon_last_tabs.png',
|
||||||
|
'resources/drawable-xhdpi/icon_last_tabs_empty.png',
|
||||||
|
'resources/drawable-xhdpi/icon_most_recent.png',
|
||||||
|
'resources/drawable-xhdpi/icon_most_recent_empty.png',
|
||||||
|
'resources/drawable-xhdpi/icon_most_visited.png',
|
||||||
|
'resources/drawable-xhdpi/icon_openinapp.png',
|
||||||
|
'resources/drawable-xhdpi/icon_pageaction.png',
|
||||||
|
'resources/drawable-xhdpi/icon_reading_list_empty.png',
|
||||||
|
'resources/drawable-xhdpi/larry.png',
|
||||||
|
'resources/drawable-xhdpi/lock_identified.png',
|
||||||
|
'resources/drawable-xhdpi/lock_verified.png',
|
||||||
|
'resources/drawable-xhdpi/menu.png',
|
||||||
|
'resources/drawable-xhdpi/menu_item_check.png',
|
||||||
|
'resources/drawable-xhdpi/menu_item_more.png',
|
||||||
|
'resources/drawable-xhdpi/menu_item_uncheck.png',
|
||||||
|
'resources/drawable-xhdpi/menu_panel_bg.9.png',
|
||||||
|
'resources/drawable-xhdpi/menu_pb.png',
|
||||||
|
'resources/drawable-xhdpi/menu_popup_arrow_bottom.png',
|
||||||
|
'resources/drawable-xhdpi/menu_popup_arrow_top.png',
|
||||||
|
'resources/drawable-xhdpi/menu_popup_bg.9.png',
|
||||||
|
'resources/drawable-xhdpi/pause.png',
|
||||||
|
'resources/drawable-xhdpi/pin.png',
|
||||||
|
'resources/drawable-xhdpi/play.png',
|
||||||
|
'resources/drawable-xhdpi/reader.png',
|
||||||
|
'resources/drawable-xhdpi/reader_active.png',
|
||||||
|
'resources/drawable-xhdpi/reader_cropped.png',
|
||||||
|
'resources/drawable-xhdpi/reading_list.png',
|
||||||
|
'resources/drawable-xhdpi/shield.png',
|
||||||
|
'resources/drawable-xhdpi/shield_doorhanger.png',
|
||||||
|
'resources/drawable-xhdpi/spinner_default.9.png',
|
||||||
|
'resources/drawable-xhdpi/spinner_focused.9.png',
|
||||||
|
'resources/drawable-xhdpi/spinner_pressed.9.png',
|
||||||
|
'resources/drawable-xhdpi/tab_close.png',
|
||||||
|
'resources/drawable-xhdpi/tab_indicator_divider.9.png',
|
||||||
|
'resources/drawable-xhdpi/tab_indicator_selected.9.png',
|
||||||
|
'resources/drawable-xhdpi/tab_indicator_selected_focused.9.png',
|
||||||
|
'resources/drawable-xhdpi/tab_new.png',
|
||||||
|
'resources/drawable-xhdpi/tab_new_pb.png',
|
||||||
|
'resources/drawable-xhdpi/tab_thumbnail_default.png',
|
||||||
|
'resources/drawable-xhdpi/tab_thumbnail_shadow.png',
|
||||||
|
'resources/drawable-xhdpi/tabs_count.png',
|
||||||
|
'resources/drawable-xhdpi/tabs_count_foreground.png',
|
||||||
|
'resources/drawable-xhdpi/tabs_normal.png',
|
||||||
|
'resources/drawable-xhdpi/tabs_private.png',
|
||||||
|
'resources/drawable-xhdpi/tabs_synced.png',
|
||||||
|
'resources/drawable-xhdpi/tip_addsearch.png',
|
||||||
|
'resources/drawable-xhdpi/top_site_add.png',
|
||||||
|
'resources/drawable-xhdpi/url_bar_entry_default.9.png',
|
||||||
|
'resources/drawable-xhdpi/url_bar_entry_default_pb.9.png',
|
||||||
|
'resources/drawable-xhdpi/url_bar_entry_pressed.9.png',
|
||||||
|
'resources/drawable-xhdpi/url_bar_entry_pressed_pb.9.png',
|
||||||
|
'resources/drawable-xhdpi/urlbar_stop.png',
|
||||||
|
'resources/drawable-xhdpi/validation_arrow.png',
|
||||||
|
'resources/drawable-xhdpi/validation_arrow_inverted.png',
|
||||||
|
'resources/drawable-xhdpi/validation_bg.9.png',
|
||||||
|
'resources/drawable-xhdpi/warning.png',
|
||||||
|
'resources/drawable-xhdpi/warning_doorhanger.png',
|
||||||
|
'resources/drawable-xlarge-hdpi-v11/ic_menu_bookmark_add.png',
|
||||||
|
'resources/drawable-xlarge-hdpi-v11/ic_menu_bookmark_remove.png',
|
||||||
|
'resources/drawable-xlarge-mdpi-v11/ic_menu_bookmark_add.png',
|
||||||
|
'resources/drawable-xlarge-mdpi-v11/ic_menu_bookmark_remove.png',
|
||||||
|
'resources/drawable-xlarge-v11/home_history_tabs_indicator.xml',
|
||||||
|
'resources/drawable-xlarge-xhdpi-v11/ic_menu_bookmark_add.png',
|
||||||
|
'resources/drawable-xlarge-xhdpi-v11/ic_menu_bookmark_remove.png',
|
||||||
|
'resources/drawable/action_bar_button.xml',
|
||||||
|
'resources/drawable/action_bar_button_inverse.xml',
|
||||||
|
'resources/drawable/bookmark_folder.xml',
|
||||||
|
'resources/drawable/divider_horizontal.xml',
|
||||||
|
'resources/drawable/divider_vertical.xml',
|
||||||
|
'resources/drawable/favicon_bg.xml',
|
||||||
|
'resources/drawable/handle_end_level.xml',
|
||||||
|
'resources/drawable/handle_start_level.xml',
|
||||||
|
'resources/drawable/home_banner.xml',
|
||||||
|
'resources/drawable/home_history_tabs_indicator.xml',
|
||||||
|
'resources/drawable/home_page_title_background.xml',
|
||||||
|
'resources/drawable/ic_menu_back.xml',
|
||||||
|
'resources/drawable/ic_menu_desktop_mode_off.xml',
|
||||||
|
'resources/drawable/ic_menu_desktop_mode_on.xml',
|
||||||
|
'resources/drawable/ic_menu_quit.xml',
|
||||||
|
'resources/drawable/icon_grid_item_bg.xml',
|
||||||
|
'resources/drawable/menu_item_state.xml',
|
||||||
|
'resources/drawable/menu_level.xml',
|
||||||
|
'resources/drawable/remote_tabs_child_divider.xml',
|
||||||
|
'resources/drawable/shaped_button.xml',
|
||||||
|
'resources/drawable/site_security_level.xml',
|
||||||
|
'resources/drawable/spinner.xml',
|
||||||
|
'resources/drawable/suggestion_selector.xml',
|
||||||
|
'resources/drawable/tab_new_level.xml',
|
||||||
|
'resources/drawable/tab_row.xml',
|
||||||
|
'resources/drawable/tab_thumbnail.xml',
|
||||||
|
'resources/drawable/tabs_panel_indicator.xml',
|
||||||
|
'resources/drawable/textbox_bg.xml',
|
||||||
|
'resources/drawable/toast_button.xml',
|
||||||
|
'resources/drawable/top_sites_thumbnail_bg.xml',
|
||||||
|
'resources/drawable/url_bar_bg.xml',
|
||||||
|
'resources/drawable/url_bar_entry.xml',
|
||||||
|
'resources/drawable/url_bar_nav_button.xml',
|
||||||
|
'resources/drawable/url_bar_right_edge.xml',
|
||||||
|
'resources/drawable/webapp_titlebar_bg.xml',
|
||||||
|
'resources/layout-large-land-v11/home_history_list.xml',
|
||||||
|
'resources/layout-large-land-v11/home_history_page.xml',
|
||||||
|
'resources/layout-large-land-v11/home_history_tabs_indicator.xml',
|
||||||
|
'resources/layout-large-land-v11/tabs_panel.xml',
|
||||||
|
'resources/layout-large-land-v11/tabs_panel_footer.xml',
|
||||||
|
'resources/layout-large-land-v11/tabs_panel_header.xml',
|
||||||
|
'resources/layout-large-v11/browser_toolbar.xml',
|
||||||
|
'resources/layout-large-v11/home_pager.xml',
|
||||||
|
'resources/layout-xlarge-v11/font_size_preference.xml',
|
||||||
|
'resources/layout-xlarge-v11/home_history_list.xml',
|
||||||
|
'resources/layout-xlarge-v11/home_history_page.xml',
|
||||||
|
'resources/layout-xlarge-v11/home_history_tabs_indicator.xml',
|
||||||
|
'resources/layout-xlarge-v11/remote_tabs_child.xml',
|
||||||
|
'resources/layout-xlarge-v11/remote_tabs_group.xml',
|
||||||
|
'resources/layout/arrow_popup.xml',
|
||||||
|
'resources/layout/autocomplete_list.xml',
|
||||||
|
'resources/layout/autocomplete_list_item.xml',
|
||||||
|
'resources/layout/bookmark_edit.xml',
|
||||||
|
'resources/layout/bookmark_folder_row.xml',
|
||||||
|
'resources/layout/bookmark_item_row.xml',
|
||||||
|
'resources/layout/browser_search.xml',
|
||||||
|
'resources/layout/browser_toolbar.xml',
|
||||||
|
'resources/layout/datetime_picker.xml',
|
||||||
|
'resources/layout/doorhanger.xml',
|
||||||
|
'resources/layout/doorhanger_button.xml',
|
||||||
|
'resources/layout/find_in_page_content.xml',
|
||||||
|
'resources/layout/font_size_preference.xml',
|
||||||
|
'resources/layout/gecko_app.xml',
|
||||||
|
'resources/layout/home_banner.xml',
|
||||||
|
'resources/layout/home_bookmarks_page.xml',
|
||||||
|
'resources/layout/home_empty_page.xml',
|
||||||
|
'resources/layout/home_empty_reading_page.xml',
|
||||||
|
'resources/layout/home_header_row.xml',
|
||||||
|
'resources/layout/home_history_list.xml',
|
||||||
|
'resources/layout/home_history_page.xml',
|
||||||
|
'resources/layout/home_history_tabs_indicator.xml',
|
||||||
|
'resources/layout/home_item_row.xml',
|
||||||
|
'resources/layout/home_last_tabs_page.xml',
|
||||||
|
'resources/layout/home_most_recent_page.xml',
|
||||||
|
'resources/layout/home_pager.xml',
|
||||||
|
'resources/layout/home_reading_list_page.xml',
|
||||||
|
'resources/layout/home_search_item_row.xml',
|
||||||
|
'resources/layout/home_suggestion_prompt.xml',
|
||||||
|
'resources/layout/home_top_sites_page.xml',
|
||||||
|
'resources/layout/icon_grid.xml',
|
||||||
|
'resources/layout/icon_grid_item.xml',
|
||||||
|
'resources/layout/launch_app_list.xml',
|
||||||
|
'resources/layout/launch_app_listitem.xml',
|
||||||
|
'resources/layout/list_item_header.xml',
|
||||||
|
'resources/layout/menu_action_bar.xml',
|
||||||
|
'resources/layout/menu_item_action_view.xml',
|
||||||
|
'resources/layout/menu_popup.xml',
|
||||||
|
'resources/layout/notification_icon_text.xml',
|
||||||
|
'resources/layout/notification_progress.xml',
|
||||||
|
'resources/layout/notification_progress_text.xml',
|
||||||
|
'resources/layout/pin_site_dialog.xml',
|
||||||
|
'resources/layout/preference_rightalign_icon.xml',
|
||||||
|
'resources/layout/preference_search_engine.xml',
|
||||||
|
'resources/layout/preference_search_tip.xml',
|
||||||
|
'resources/layout/remote_tabs_child.xml',
|
||||||
|
'resources/layout/remote_tabs_group.xml',
|
||||||
|
'resources/layout/search_engine_row.xml',
|
||||||
|
'resources/layout/select_dialog_list.xml',
|
||||||
|
'resources/layout/select_dialog_multichoice.xml',
|
||||||
|
'resources/layout/select_dialog_singlechoice.xml',
|
||||||
|
'resources/layout/shared_ui_components.xml',
|
||||||
|
'resources/layout/simple_dropdown_item_1line.xml',
|
||||||
|
'resources/layout/site_identity.xml',
|
||||||
|
'resources/layout/site_setting_item.xml',
|
||||||
|
'resources/layout/site_setting_title.xml',
|
||||||
|
'resources/layout/suggestion_item.xml',
|
||||||
|
'resources/layout/tab_menu_strip.xml',
|
||||||
|
'resources/layout/tabs_counter.xml',
|
||||||
|
'resources/layout/tabs_item_cell.xml',
|
||||||
|
'resources/layout/tabs_item_row.xml',
|
||||||
|
'resources/layout/tabs_panel.xml',
|
||||||
|
'resources/layout/tabs_panel_header.xml',
|
||||||
|
'resources/layout/tabs_panel_indicator.xml',
|
||||||
|
'resources/layout/text_selection_handles.xml',
|
||||||
|
'resources/layout/top_sites_grid_item_view.xml',
|
||||||
|
'resources/layout/two_line_page_row.xml',
|
||||||
|
'resources/layout/validation_message.xml',
|
||||||
|
'resources/layout/videoplayer.xml',
|
||||||
|
'resources/layout/web_app.xml',
|
||||||
|
'resources/menu-large-v11/browser_app_menu.xml',
|
||||||
|
'resources/menu-v11/browser_app_menu.xml',
|
||||||
|
'resources/menu-xlarge-v11/browser_app_menu.xml',
|
||||||
|
'resources/menu/browser_app_menu.xml',
|
||||||
|
'resources/menu/gecko_app_menu.xml',
|
||||||
|
'resources/menu/home_contextmenu.xml',
|
||||||
|
'resources/menu/titlebar_contextmenu.xml',
|
||||||
|
'resources/menu/top_sites_contextmenu.xml',
|
||||||
|
'resources/values-land/integers.xml',
|
||||||
|
'resources/values-land/layout.xml',
|
||||||
|
'resources/values-land/styles.xml',
|
||||||
|
'resources/values-large-land-v11/dimens.xml',
|
||||||
|
'resources/values-large-land-v11/styles.xml',
|
||||||
|
'resources/values-large-v11/dimens.xml',
|
||||||
|
'resources/values-large-v11/layout.xml',
|
||||||
|
'resources/values-large-v11/styles.xml',
|
||||||
|
'resources/values-large-v11/themes.xml',
|
||||||
|
'resources/values-v11/colors.xml',
|
||||||
|
'resources/values-v11/dimens.xml',
|
||||||
|
'resources/values-v11/styles.xml',
|
||||||
|
'resources/values-v11/themes.xml',
|
||||||
|
'resources/values-v14/styles.xml',
|
||||||
|
'resources/values-v16/styles.xml',
|
||||||
|
'resources/values-xlarge-land-v11/dimens.xml',
|
||||||
|
'resources/values-xlarge-land-v11/styles.xml',
|
||||||
|
'resources/values-xlarge-v11/dimens.xml',
|
||||||
|
'resources/values-xlarge-v11/integers.xml',
|
||||||
|
'resources/values-xlarge-v11/styles.xml',
|
||||||
|
'resources/values/arrays.xml',
|
||||||
|
'resources/values/attrs.xml',
|
||||||
|
'resources/values/colors.xml',
|
||||||
|
'resources/values/dimens.xml',
|
||||||
|
'resources/values/integers.xml',
|
||||||
|
'resources/values/layout.xml',
|
||||||
|
'resources/values/styles.xml',
|
||||||
|
'resources/values/themes.xml',
|
||||||
|
'resources/xml-v11/preference_headers.xml',
|
||||||
|
'resources/xml-v11/preferences.xml',
|
||||||
|
'resources/xml-v11/preferences_customize.xml',
|
||||||
|
'resources/xml-v11/preferences_customize_tablet.xml',
|
||||||
|
'resources/xml/preferences.xml',
|
||||||
|
'resources/xml/preferences_customize.xml',
|
||||||
|
'resources/xml/preferences_devtools.xml',
|
||||||
|
'resources/xml/preferences_display.xml',
|
||||||
|
'resources/xml/preferences_privacy.xml',
|
||||||
|
'resources/xml/preferences_search.xml',
|
||||||
|
'resources/xml/preferences_vendor.xml',
|
||||||
|
'resources/xml/searchable.xml',
|
||||||
|
]
|
||||||
|
|
||||||
|
if CONFIG['MOZ_CRASHREPORTER']:
|
||||||
|
ANDROID_RESFILES += [
|
||||||
|
'resources/drawable-mdpi/crash_reporter.png',
|
||||||
|
'resources/layout/crash_reporter.xml',
|
||||||
|
]
|
||||||
|
|
|
@ -4,6 +4,7 @@ package @ANDROID_PACKAGE_NAME@.tests;
|
||||||
import @ANDROID_PACKAGE_NAME@.*;
|
import @ANDROID_PACKAGE_NAME@.*;
|
||||||
|
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
|
import android.content.Context;
|
||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FileOutputStream;
|
import java.io.FileOutputStream;
|
||||||
|
@ -38,11 +39,50 @@ public class testDistribution extends ContentProviderTest {
|
||||||
return TEST_MOCHITEST;
|
return TEST_MOCHITEST;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is a hack.
|
||||||
|
*
|
||||||
|
* Startup results in us writing prefs -- we fetch the Distribution, which
|
||||||
|
* caches its state. Our tests try to wipe those prefs, but apparently
|
||||||
|
* sometimes race with startup, which leads to us not getting one of our
|
||||||
|
* expected messages. The test fails.
|
||||||
|
*
|
||||||
|
* This hack waits for any existing background tasks -- such as the one that
|
||||||
|
* writes prefs -- to finish before we begin the test.
|
||||||
|
*/
|
||||||
|
private void waitForBackgroundHappiness() {
|
||||||
|
try {
|
||||||
|
ClassLoader classLoader = mActivity.getClassLoader();
|
||||||
|
Class threadUtilsClass = classLoader.loadClass("org.mozilla.gecko.util.ThreadUtils");
|
||||||
|
Method postToBackgroundThread = threadUtilsClass.getMethod("postToBackgroundThread", Runnable.class);
|
||||||
|
final Object signal = new Object();
|
||||||
|
final Runnable done = new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
synchronized (signal) {
|
||||||
|
signal.notify();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
synchronized (signal) {
|
||||||
|
postToBackgroundThread.invoke(null, done);
|
||||||
|
signal.wait();
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
mAsserter.ok(false, "Exception waiting on background thread.", e.toString());
|
||||||
|
}
|
||||||
|
mAsserter.dumpLog("Background task completed. Proceeding.");
|
||||||
|
}
|
||||||
|
|
||||||
public void testDistribution() {
|
public void testDistribution() {
|
||||||
mActivity = getActivity();
|
mActivity = getActivity();
|
||||||
|
|
||||||
String mockPackagePath = getMockPackagePath();
|
String mockPackagePath = getMockPackagePath();
|
||||||
|
|
||||||
|
// Wait for any startup-related background distribution shenanigans to
|
||||||
|
// finish. This reduces the chance of us racing with startup pref writes.
|
||||||
|
waitForBackgroundHappiness();
|
||||||
|
|
||||||
// Pre-clear distribution pref, run basic preferences and en-US localized preferences Tests
|
// Pre-clear distribution pref, run basic preferences and en-US localized preferences Tests
|
||||||
clearDistributionPref();
|
clearDistributionPref();
|
||||||
setTestLocale("en-US");
|
setTestLocale("en-US");
|
||||||
|
@ -64,11 +104,10 @@ public class testDistribution extends ContentProviderTest {
|
||||||
// Call Distribution.init with the mock package.
|
// Call Distribution.init with the mock package.
|
||||||
ClassLoader classLoader = mActivity.getClassLoader();
|
ClassLoader classLoader = mActivity.getClassLoader();
|
||||||
Class distributionClass = classLoader.loadClass("org.mozilla.gecko.Distribution");
|
Class distributionClass = classLoader.loadClass("org.mozilla.gecko.Distribution");
|
||||||
Class contextClass = classLoader.loadClass("android.content.Context");
|
Method init = distributionClass.getMethod("init", Context.class, String.class, String.class);
|
||||||
Method init = distributionClass.getMethod("init", contextClass, String.class);
|
|
||||||
|
|
||||||
Actions.EventExpecter distributionSetExpecter = mActions.expectGeckoEvent("Distribution:Set:OK");
|
Actions.EventExpecter distributionSetExpecter = mActions.expectGeckoEvent("Distribution:Set:OK");
|
||||||
init.invoke(null, mActivity, aPackagePath);
|
init.invoke(null, mActivity, aPackagePath, "prefs-" + System.currentTimeMillis());
|
||||||
distributionSetExpecter.blockForEvent();
|
distributionSetExpecter.blockForEvent();
|
||||||
distributionSetExpecter.unregisterListener();
|
distributionSetExpecter.unregisterListener();
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
|
@ -268,6 +307,7 @@ public class testDistribution extends ContentProviderTest {
|
||||||
|
|
||||||
// Clears the distribution pref to return distribution state to STATE_UNKNOWN
|
// Clears the distribution pref to return distribution state to STATE_UNKNOWN
|
||||||
private void clearDistributionPref() {
|
private void clearDistributionPref() {
|
||||||
|
mAsserter.dumpLog("Clearing distribution pref.");
|
||||||
SharedPreferences settings = mActivity.getSharedPreferences("GeckoApp", Activity.MODE_PRIVATE);
|
SharedPreferences settings = mActivity.getSharedPreferences("GeckoApp", Activity.MODE_PRIVATE);
|
||||||
String keyName = mActivity.getPackageName() + ".distribution_state";
|
String keyName = mActivity.getPackageName() + ".distribution_state";
|
||||||
settings.edit().remove(keyName).commit();
|
settings.edit().remove(keyName).commit();
|
||||||
|
|
|
@ -5316,7 +5316,10 @@ var FormAssistant = {
|
||||||
* -- and reflect them back to Java.
|
* -- and reflect them back to Java.
|
||||||
*/
|
*/
|
||||||
let HealthReportStatusListener = {
|
let HealthReportStatusListener = {
|
||||||
TELEMETRY_PREF:
|
PREF_ACCEPT_LANG: "intl.accept_languages",
|
||||||
|
PREF_BLOCKLIST_ENABLED: "extensions.blocklist.enabled",
|
||||||
|
|
||||||
|
PREF_TELEMETRY_ENABLED:
|
||||||
#ifdef MOZ_TELEMETRY_REPORTING
|
#ifdef MOZ_TELEMETRY_REPORTING
|
||||||
// Telemetry pref differs based on build.
|
// Telemetry pref differs based on build.
|
||||||
#ifdef MOZ_TELEMETRY_ON_BY_DEFAULT
|
#ifdef MOZ_TELEMETRY_ON_BY_DEFAULT
|
||||||
|
@ -5335,18 +5338,21 @@ let HealthReportStatusListener = {
|
||||||
console.log("Failed to initialize add-on status listener. FHR cannot report add-on state. " + ex);
|
console.log("Failed to initialize add-on status listener. FHR cannot report add-on state. " + ex);
|
||||||
}
|
}
|
||||||
|
|
||||||
Services.obs.addObserver(this, "Addons:FetchAll", false);
|
console.log("Adding HealthReport:RequestSnapshot observer.");
|
||||||
Services.prefs.addObserver("extensions.blocklist.enabled", this, false);
|
Services.obs.addObserver(this, "HealthReport:RequestSnapshot", false);
|
||||||
if (this.TELEMETRY_PREF) {
|
Services.prefs.addObserver(this.PREF_ACCEPT_LANG, this, false);
|
||||||
Services.prefs.addObserver(this.TELEMETRY_PREF, this, false);
|
Services.prefs.addObserver(this.PREF_BLOCKLIST_ENABLED, this, false);
|
||||||
|
if (this.PREF_TELEMETRY_ENABLED) {
|
||||||
|
Services.prefs.addObserver(this.PREF_TELEMETRY_ENABLED, this, false);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
uninit: function () {
|
uninit: function () {
|
||||||
Services.obs.removeObserver(this, "Addons:FetchAll");
|
Services.obs.removeObserver(this, "HealthReport:RequestSnapshot");
|
||||||
Services.prefs.removeObserver("extensions.blocklist.enabled", this);
|
Services.prefs.removeObserver(this.PREF_ACCEPT_LANG, this);
|
||||||
if (this.TELEMETRY_PREF) {
|
Services.prefs.removeObserver(this.PREF_BLOCKLIST_ENABLED, this);
|
||||||
Services.prefs.removeObserver(this.TELEMETRY_PREF, this);
|
if (this.PREF_TELEMETRY_ENABLED) {
|
||||||
|
Services.prefs.removeObserver(this.PREF_TELEMETRY_ENABLED, this);
|
||||||
}
|
}
|
||||||
|
|
||||||
AddonManager.removeAddonListener(this);
|
AddonManager.removeAddonListener(this);
|
||||||
|
@ -5354,11 +5360,30 @@ let HealthReportStatusListener = {
|
||||||
|
|
||||||
observe: function (aSubject, aTopic, aData) {
|
observe: function (aSubject, aTopic, aData) {
|
||||||
switch (aTopic) {
|
switch (aTopic) {
|
||||||
case "Addons:FetchAll":
|
case "HealthReport:RequestSnapshot":
|
||||||
HealthReportStatusListener.sendAllAddonsToJava();
|
HealthReportStatusListener.sendSnapshotToJava();
|
||||||
break;
|
break;
|
||||||
case "nsPref:changed":
|
case "nsPref:changed":
|
||||||
sendMessageToJava({ type: "Pref:Change", pref: aData, value: Services.prefs.getBoolPref(aData) });
|
let response = {
|
||||||
|
type: "Pref:Change",
|
||||||
|
pref: aData,
|
||||||
|
isUserSet: Services.prefs.prefHasUserValue(aData),
|
||||||
|
};
|
||||||
|
|
||||||
|
switch (aData) {
|
||||||
|
case this.PREF_ACCEPT_LANG:
|
||||||
|
response.value = Services.prefs.getCharPref(aData);
|
||||||
|
break;
|
||||||
|
case this.PREF_TELEMETRY_ENABLED:
|
||||||
|
case this.PREF_BLOCKLIST_ENABLED:
|
||||||
|
response.value = Services.prefs.getBoolPref(aData);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
console.log("Unexpected pref in HealthReportStatusListener: " + aData);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
sendMessageToJava(response);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -5440,9 +5465,9 @@ let HealthReportStatusListener = {
|
||||||
this.notifyJava(aAddon);
|
this.notifyJava(aAddon);
|
||||||
},
|
},
|
||||||
|
|
||||||
sendAllAddonsToJava: function () {
|
sendSnapshotToJava: function () {
|
||||||
AddonManager.getAllAddons(function (aAddons) {
|
AddonManager.getAllAddons(function (aAddons) {
|
||||||
let json = {};
|
let jsonA = {};
|
||||||
if (aAddons) {
|
if (aAddons) {
|
||||||
for (let i = 0; i < aAddons.length; ++i) {
|
for (let i = 0; i < aAddons.length; ++i) {
|
||||||
let addon = aAddons[i];
|
let addon = aAddons[i];
|
||||||
|
@ -5451,14 +5476,43 @@ let HealthReportStatusListener = {
|
||||||
if (HealthReportStatusListener._shouldIgnore(addon)) {
|
if (HealthReportStatusListener._shouldIgnore(addon)) {
|
||||||
addonJSON.ignore = true;
|
addonJSON.ignore = true;
|
||||||
}
|
}
|
||||||
json[addon.id] = addonJSON;
|
jsonA[addon.id] = addonJSON;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// Just skip this add-on.
|
// Just skip this add-on.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
sendMessageToJava({ type: "Addons:All", json: json });
|
|
||||||
});
|
// Now add prefs.
|
||||||
|
let jsonP = {};
|
||||||
|
for (let pref of [this.PREF_BLOCKLIST_ENABLED, this.PREF_TELEMETRY_ENABLED]) {
|
||||||
|
if (!pref) {
|
||||||
|
// This will be the case for PREF_TELEMETRY_ENABLED in developer builds.
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
jsonP[pref] = {
|
||||||
|
pref: pref,
|
||||||
|
value: Services.prefs.getBoolPref(pref),
|
||||||
|
isUserSet: Services.prefs.prefHasUserValue(pref),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
for (let pref of [this.PREF_ACCEPT_LANG]) {
|
||||||
|
jsonP[pref] = {
|
||||||
|
pref: pref,
|
||||||
|
value: Services.prefs.getCharPref(pref),
|
||||||
|
isUserSet: Services.prefs.prefHasUserValue(pref),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("Sending snapshot message.");
|
||||||
|
sendMessageToJava({
|
||||||
|
type: "HealthReport:Snapshot",
|
||||||
|
json: {
|
||||||
|
addons: jsonA,
|
||||||
|
prefs: jsonP,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}.bind(this));
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -27,6 +27,7 @@ background/db/CursorDumper.java
|
||||||
background/db/Tab.java
|
background/db/Tab.java
|
||||||
background/healthreport/Environment.java
|
background/healthreport/Environment.java
|
||||||
background/healthreport/EnvironmentBuilder.java
|
background/healthreport/EnvironmentBuilder.java
|
||||||
|
background/healthreport/EnvironmentV1.java
|
||||||
background/healthreport/HealthReportBroadcastReceiver.java
|
background/healthreport/HealthReportBroadcastReceiver.java
|
||||||
background/healthreport/HealthReportBroadcastService.java
|
background/healthreport/HealthReportBroadcastService.java
|
||||||
background/healthreport/HealthReportDatabases.java
|
background/healthreport/HealthReportDatabases.java
|
||||||
|
|
|
@ -21,7 +21,6 @@ include $(srcdir)/android-services-files.mk
|
||||||
|
|
||||||
# BACKGROUND_TESTS_{JAVA,RES}_FILES are defined in android-services-files.mk.
|
# BACKGROUND_TESTS_{JAVA,RES}_FILES are defined in android-services-files.mk.
|
||||||
JAVAFILES := $(BACKGROUND_TESTS_JAVA_FILES)
|
JAVAFILES := $(BACKGROUND_TESTS_JAVA_FILES)
|
||||||
ANDROID_RESFILES := $(BACKGROUND_TESTS_RES_FILES)
|
|
||||||
|
|
||||||
# The test APK needs to know the contents of the target APK while not
|
# The test APK needs to know the contents of the target APK while not
|
||||||
# being linked against them. This is a best effort to avoid getting
|
# being linked against them. This is a best effort to avoid getting
|
||||||
|
|
|
@ -100,11 +100,3 @@ BACKGROUND_TESTS_JAVA_FILES := \
|
||||||
src/testhelpers/WBORepository.java \
|
src/testhelpers/WBORepository.java \
|
||||||
$(NULL)
|
$(NULL)
|
||||||
|
|
||||||
BACKGROUND_TESTS_RES_FILES := \
|
|
||||||
res/drawable-hdpi/icon.png \
|
|
||||||
res/drawable-ldpi/icon.png \
|
|
||||||
res/drawable-mdpi/icon.png \
|
|
||||||
res/layout/main.xml \
|
|
||||||
res/values/strings.xml \
|
|
||||||
$(NULL)
|
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
ANDROID_RESFILES += [
|
||||||
|
'res/drawable-hdpi/icon.png',
|
||||||
|
'res/drawable-ldpi/icon.png',
|
||||||
|
'res/drawable-mdpi/icon.png',
|
||||||
|
'res/layout/main.xml',
|
||||||
|
'res/values/strings.xml',
|
||||||
|
]
|
|
@ -3,3 +3,5 @@
|
||||||
# This Source Code Form is subject to the terms of the Mozilla Public
|
# 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
|
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
|
|
||||||
|
include('android-services.mozbuild')
|
||||||
|
|
|
@ -38,7 +38,7 @@ public class MockDatabaseEnvironment extends DatabaseEnvironment {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public MockDatabaseEnvironment mockInit(String version) {
|
public MockDatabaseEnvironment mockInit(String appVersion) {
|
||||||
profileCreation = 1234;
|
profileCreation = 1234;
|
||||||
cpuCount = 2;
|
cpuCount = 2;
|
||||||
memoryMB = 512;
|
memoryMB = 512;
|
||||||
|
@ -55,7 +55,7 @@ public class MockDatabaseEnvironment extends DatabaseEnvironment {
|
||||||
vendor = "";
|
vendor = "";
|
||||||
appName = "";
|
appName = "";
|
||||||
appID = "";
|
appID = "";
|
||||||
appVersion = version;
|
this.appVersion = appVersion;
|
||||||
appBuildID = "";
|
appBuildID = "";
|
||||||
platformVersion = "";
|
platformVersion = "";
|
||||||
platformBuildID = "";
|
platformBuildID = "";
|
||||||
|
@ -63,6 +63,14 @@ public class MockDatabaseEnvironment extends DatabaseEnvironment {
|
||||||
xpcomabi = "";
|
xpcomabi = "";
|
||||||
updateChannel = "";
|
updateChannel = "";
|
||||||
|
|
||||||
|
// v2 fields.
|
||||||
|
distribution = "";
|
||||||
|
appLocale = "";
|
||||||
|
osLocale = "";
|
||||||
|
acceptLangSet = 0;
|
||||||
|
|
||||||
|
version = Environment.CURRENT_VERSION;
|
||||||
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,6 +15,10 @@ import org.mozilla.gecko.background.healthreport.HealthReportStorage.Field;
|
||||||
import org.mozilla.gecko.background.healthreport.HealthReportStorage.MeasurementFields;
|
import org.mozilla.gecko.background.healthreport.HealthReportStorage.MeasurementFields;
|
||||||
import org.mozilla.gecko.background.helpers.FakeProfileTestCase;
|
import org.mozilla.gecko.background.helpers.FakeProfileTestCase;
|
||||||
|
|
||||||
|
import android.database.Cursor;
|
||||||
|
import android.database.sqlite.SQLiteDatabase;
|
||||||
|
import android.util.SparseArray;
|
||||||
|
|
||||||
public class TestHealthReportGenerator extends FakeProfileTestCase {
|
public class TestHealthReportGenerator extends FakeProfileTestCase {
|
||||||
@SuppressWarnings("static-method")
|
@SuppressWarnings("static-method")
|
||||||
public void testOptObject() throws JSONException {
|
public void testOptObject() throws JSONException {
|
||||||
|
@ -57,9 +61,14 @@ public class TestHealthReportGenerator extends FakeProfileTestCase {
|
||||||
assertFalse(bar.has("b"));
|
assertFalse(bar.has("b"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// We don't initialize the env in testHashing, so these are just the default
|
||||||
|
// values for the Java types, in order.
|
||||||
private static final String EXPECTED_MOCK_BASE_HASH = "000nullnullnullnullnullnullnull"
|
private static final String EXPECTED_MOCK_BASE_HASH = "000nullnullnullnullnullnullnull"
|
||||||
+ "nullnullnullnullnullnull00000";
|
+ "nullnullnullnullnullnull00000";
|
||||||
|
|
||||||
|
// v2 fields.
|
||||||
|
private static final String EXPECTED_MOCK_BASE_HASH_SUFFIX = "null" + "null" + 0 + "null";
|
||||||
|
|
||||||
public void testHashing() throws JSONException {
|
public void testHashing() throws JSONException {
|
||||||
MockHealthReportDatabaseStorage storage = new MockHealthReportDatabaseStorage(context, fakeProfileDirectory);
|
MockHealthReportDatabaseStorage storage = new MockHealthReportDatabaseStorage(context, fakeProfileDirectory);
|
||||||
MockDatabaseEnvironment env = new MockDatabaseEnvironment(storage, MockDatabaseEnvironment.MockEnvironmentAppender.class);
|
MockDatabaseEnvironment env = new MockDatabaseEnvironment(storage, MockDatabaseEnvironment.MockEnvironmentAppender.class);
|
||||||
|
@ -96,10 +105,10 @@ public class TestHealthReportGenerator extends FakeProfileTestCase {
|
||||||
"}");
|
"}");
|
||||||
env.addons.put("{addonA}", addonA1);
|
env.addons.put("{addonA}", addonA1);
|
||||||
|
|
||||||
assertEquals(EXPECTED_MOCK_BASE_HASH + addonAHash, env.getHash());
|
assertEquals(EXPECTED_MOCK_BASE_HASH + addonAHash + EXPECTED_MOCK_BASE_HASH_SUFFIX, env.getHash());
|
||||||
|
|
||||||
env.addons.put("{addonA}", addonA1rev);
|
env.addons.put("{addonA}", addonA1rev);
|
||||||
assertEquals(EXPECTED_MOCK_BASE_HASH + addonAHash, env.getHash());
|
assertEquals(EXPECTED_MOCK_BASE_HASH + addonAHash + EXPECTED_MOCK_BASE_HASH_SUFFIX, env.getHash());
|
||||||
}
|
}
|
||||||
|
|
||||||
private void assertJSONDiff(JSONObject source, JSONObject diff) throws JSONException {
|
private void assertJSONDiff(JSONObject source, JSONObject diff) throws JSONException {
|
||||||
|
@ -406,4 +415,103 @@ public class TestHealthReportGenerator extends FakeProfileTestCase {
|
||||||
protected String getCacheSuffix() {
|
protected String getCacheSuffix() {
|
||||||
return File.separator + "health-" + System.currentTimeMillis() + ".profile";
|
return File.separator + "health-" + System.currentTimeMillis() + ".profile";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public void testEnvironmentDiffing() throws JSONException {
|
||||||
|
// Manually insert a v1 environment.
|
||||||
|
final MockHealthReportDatabaseStorage storage = new MockHealthReportDatabaseStorage(context, fakeProfileDirectory);
|
||||||
|
final SQLiteDatabase db = storage.getDB();
|
||||||
|
storage.deleteEverything();
|
||||||
|
final MockDatabaseEnvironment v1env = storage.getEnvironment();
|
||||||
|
v1env.mockInit("27.0a1");
|
||||||
|
v1env.version = 1;
|
||||||
|
v1env.appLocale = "";
|
||||||
|
v1env.osLocale = "";
|
||||||
|
v1env.distribution = "";
|
||||||
|
v1env.acceptLangSet = 0;
|
||||||
|
final int v1ID = v1env.register();
|
||||||
|
|
||||||
|
// Verify.
|
||||||
|
final String[] cols = new String[] {
|
||||||
|
"id", "version", "hash",
|
||||||
|
"osLocale", "acceptLangSet", "appLocale", "distribution"
|
||||||
|
};
|
||||||
|
|
||||||
|
final Cursor c1 = db.query("environments", cols, "id = " + v1ID, null, null, null, null);
|
||||||
|
String v1envHash;
|
||||||
|
try {
|
||||||
|
assertTrue(c1.moveToFirst());
|
||||||
|
assertEquals(1, c1.getCount());
|
||||||
|
|
||||||
|
assertEquals(v1ID, c1.getInt(0));
|
||||||
|
assertEquals(1, c1.getInt(1));
|
||||||
|
|
||||||
|
v1envHash = c1.getString(2);
|
||||||
|
assertNotNull(v1envHash);
|
||||||
|
assertEquals("", c1.getString(3));
|
||||||
|
assertEquals(0, c1.getInt(4));
|
||||||
|
assertEquals("", c1.getString(5));
|
||||||
|
assertEquals("", c1.getString(6));
|
||||||
|
} finally {
|
||||||
|
c1.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Insert a v2 environment.
|
||||||
|
final MockDatabaseEnvironment v2env = storage.getEnvironment();
|
||||||
|
v2env.mockInit("27.0a1");
|
||||||
|
v2env.appLocale = v2env.osLocale = "en_us";
|
||||||
|
v2env.acceptLangSet = 1;
|
||||||
|
|
||||||
|
final int v2ID = v2env.register();
|
||||||
|
assertFalse(v1ID == v2ID);
|
||||||
|
final Cursor c2 = db.query("environments", cols, "id = " + v2ID, null, null, null, null);
|
||||||
|
String v2envHash;
|
||||||
|
try {
|
||||||
|
assertTrue(c2.moveToFirst());
|
||||||
|
assertEquals(1, c2.getCount());
|
||||||
|
|
||||||
|
assertEquals(v2ID, c2.getInt(0));
|
||||||
|
assertEquals(2, c2.getInt(1));
|
||||||
|
|
||||||
|
v2envHash = c2.getString(2);
|
||||||
|
assertNotNull(v2envHash);
|
||||||
|
assertEquals("en_us", c2.getString(3));
|
||||||
|
assertEquals(1, c2.getInt(4));
|
||||||
|
assertEquals("en_us", c2.getString(5));
|
||||||
|
assertEquals("", c2.getString(6));
|
||||||
|
} finally {
|
||||||
|
c2.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
assertFalse(v1envHash.equals(v2envHash));
|
||||||
|
|
||||||
|
// Now let's diff based on DB contents.
|
||||||
|
SparseArray<Environment> envs = storage.getEnvironmentRecordsByID();
|
||||||
|
|
||||||
|
JSONObject oldEnv = HealthReportGenerator.jsonify(envs.get(v1ID), null).getJSONObject("org.mozilla.appInfo.appinfo");
|
||||||
|
JSONObject newEnv = HealthReportGenerator.jsonify(envs.get(v2ID), null).getJSONObject("org.mozilla.appInfo.appinfo");
|
||||||
|
|
||||||
|
// Generate the new env as if the old were the current. This should rarely happen in practice.
|
||||||
|
// Fields supported by the new env but not the old will appear, even if the 'default' for the
|
||||||
|
// old implementation is equal to the new env's value.
|
||||||
|
JSONObject newVsOld = HealthReportGenerator.jsonify(envs.get(v2ID), envs.get(v1ID)).getJSONObject("org.mozilla.appInfo.appinfo");
|
||||||
|
|
||||||
|
// Generate the old env as if the new were the current. This is normal. Fields not supported by the old
|
||||||
|
// environment version should not appear in the output.
|
||||||
|
JSONObject oldVsNew = HealthReportGenerator.jsonify(envs.get(v1ID), envs.get(v2ID)).getJSONObject("org.mozilla.appInfo.appinfo");
|
||||||
|
assertEquals(2, oldEnv.getInt("_v"));
|
||||||
|
assertEquals(3, newEnv.getInt("_v"));
|
||||||
|
assertEquals(2, oldVsNew.getInt("_v"));
|
||||||
|
assertEquals(3, newVsOld.getInt("_v"));
|
||||||
|
|
||||||
|
assertFalse(oldVsNew.has("osLocale"));
|
||||||
|
assertFalse(oldVsNew.has("appLocale"));
|
||||||
|
assertFalse(oldVsNew.has("distribution"));
|
||||||
|
assertFalse(oldVsNew.has("acceptLangIsUserSet"));
|
||||||
|
|
||||||
|
assertTrue(newVsOld.has("osLocale"));
|
||||||
|
assertTrue(newVsOld.has("appLocale"));
|
||||||
|
assertTrue(newVsOld.has("distribution"));
|
||||||
|
assertTrue(newVsOld.has("acceptLangIsUserSet"));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -146,7 +146,7 @@ public class TestHealthReportProvider extends DBProviderTestCase<HealthReportPro
|
||||||
Cursor envCursor = resolver.query(envURI, null, null, null, null);
|
Cursor envCursor = resolver.query(envURI, null, null, null, null);
|
||||||
try {
|
try {
|
||||||
assertTrue(envCursor.moveToFirst());
|
assertTrue(envCursor.moveToFirst());
|
||||||
envHash = envCursor.getString(1);
|
envHash = envCursor.getString(2); // id, version, hash, ...
|
||||||
} finally {
|
} finally {
|
||||||
envCursor.close();
|
envCursor.close();
|
||||||
}
|
}
|
||||||
|
@ -249,6 +249,13 @@ public class TestHealthReportProvider extends DBProviderTestCase<HealthReportPro
|
||||||
v.put("os", "");
|
v.put("os", "");
|
||||||
v.put("xpcomabi", "");
|
v.put("xpcomabi", "");
|
||||||
v.put("updateChannel", "");
|
v.put("updateChannel", "");
|
||||||
|
|
||||||
|
// v2.
|
||||||
|
v.put("distribution", "");
|
||||||
|
v.put("osLocale", "en_us");
|
||||||
|
v.put("appLocale", "en_us");
|
||||||
|
v.put("acceptLangSet", 0);
|
||||||
|
|
||||||
return v;
|
return v;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -139,6 +139,8 @@ class TreeMetadataEmitter(LoggingMixin):
|
||||||
passthru = VariablePassthru(sandbox)
|
passthru = VariablePassthru(sandbox)
|
||||||
varmap = dict(
|
varmap = dict(
|
||||||
# Makefile.in : moz.build
|
# Makefile.in : moz.build
|
||||||
|
ANDROID_GENERATED_RESFILES='ANDROID_GENERATED_RESFILES',
|
||||||
|
ANDROID_RESFILES='ANDROID_RESFILES',
|
||||||
ASFILES='ASFILES',
|
ASFILES='ASFILES',
|
||||||
CMMSRCS='CMMSRCS',
|
CMMSRCS='CMMSRCS',
|
||||||
CPPSRCS='CPP_SOURCES',
|
CPPSRCS='CPP_SOURCES',
|
||||||
|
|
|
@ -43,6 +43,22 @@ from mozbuild.util import (
|
||||||
|
|
||||||
VARIABLES = {
|
VARIABLES = {
|
||||||
# Variables controlling reading of other frontend files.
|
# Variables controlling reading of other frontend files.
|
||||||
|
'ANDROID_GENERATED_RESFILES': (StrictOrderingOnAppendList, list, [],
|
||||||
|
"""Android resource files generated as part of the build.
|
||||||
|
|
||||||
|
This variable contains a list of files that are expected to be
|
||||||
|
generated (often by preprocessing) into a 'res' directory as
|
||||||
|
part of the build process, and subsequently merged into an APK
|
||||||
|
file.
|
||||||
|
""", 'export'),
|
||||||
|
|
||||||
|
'ANDROID_RESFILES': (StrictOrderingOnAppendList, list, [],
|
||||||
|
"""Android resource files.
|
||||||
|
|
||||||
|
This variable contains a list of files to package into a 'res'
|
||||||
|
directory and merge into an APK file.
|
||||||
|
""", 'export'),
|
||||||
|
|
||||||
'ASFILES': (StrictOrderingOnAppendList, list, [],
|
'ASFILES': (StrictOrderingOnAppendList, list, [],
|
||||||
"""Assembly file sources.
|
"""Assembly file sources.
|
||||||
|
|
||||||
|
|
Загрузка…
Ссылка в новой задаче