зеркало из 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
|
||||
var submission = this.currentEngine.getSubmission(aData);
|
||||
var submission = this.currentEngine.getSubmission(aData, null, "searchbar");
|
||||
BrowserSearch.recordSearchInHealthReport(this.currentEngine.name, "searchbar");
|
||||
openUILinkIn(submission.uri.spec, aWhere, null, submission.postData);
|
||||
]]></body>
|
||||
|
@ -531,14 +531,14 @@
|
|||
var engine = this.currentEngine;
|
||||
var connector =
|
||||
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)
|
||||
.getInterface(Components.interfaces.nsIWebNavigation)
|
||||
.QueryInterface(Components.interfaces.nsILoadContext);
|
||||
connector.speculativeConnect(searchURI, callbacks);
|
||||
|
||||
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)
|
||||
connector.speculativeConnect(suggestURI, callbacks);
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
ifdef ENABLE_TESTS
|
||||
pp_mochitest_browser_files := \
|
||||
browser_google.js \
|
||||
browser_google_behavior.js \
|
||||
$(NULL)
|
||||
pp_mochitest_browser_files_PATH := $(DEPTH)/_tests/testing/mochitest/browser/$(relativesrcdir)
|
||||
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'");
|
||||
url = engine.getSubmission("foo", null, "keyword").uri.spec;
|
||||
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.
|
||||
url = engine.getSubmission("foo", "application/x-suggestions+json").uri.spec;
|
||||
|
@ -147,6 +151,11 @@ function test() {
|
|||
"value": "fflb",
|
||||
"purpose": "keyword",
|
||||
},
|
||||
{
|
||||
"name": "channel",
|
||||
"value": "sb",
|
||||
"purpose": "searchbar",
|
||||
},
|
||||
{
|
||||
"name": "channel",
|
||||
"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
|
||||
<MozParam name="channel" condition="purpose" purpose="contextmenu" value="rcs"/>
|
||||
<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="source" condition="purpose" purpose="homepage" value="hp"/>
|
||||
</Url>
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
* link parameter/model object expected to have a .url property, and optionally .title
|
||||
*/
|
||||
function Site(aLink) {
|
||||
if(!aLink.url) {
|
||||
if (!aLink.url) {
|
||||
throw Cr.NS_ERROR_INVALID_ARG;
|
||||
}
|
||||
this._link = aLink;
|
||||
|
@ -64,7 +64,7 @@ Site.prototype = {
|
|||
}
|
||||
}
|
||||
// is binding already applied?
|
||||
if (aNode.refresh) {
|
||||
if ('refresh' in aNode) {
|
||||
// just update it
|
||||
aNode.refresh();
|
||||
} else {
|
||||
|
|
|
@ -27,7 +27,7 @@
|
|||
<field name="controller">null</field>
|
||||
|
||||
<!-- 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;"/>
|
||||
|
||||
<!-- nsIDOMXULMultiSelectControlElement (not fully implemented) -->
|
||||
|
@ -96,7 +96,7 @@
|
|||
<parameter name="aEvent"/>
|
||||
<body>
|
||||
<![CDATA[
|
||||
if(!this.isBound)
|
||||
if (!this.isBound)
|
||||
return;
|
||||
|
||||
if ("single" == this.getAttribute("seltype")) {
|
||||
|
@ -116,7 +116,7 @@
|
|||
<parameter name="aEvent"/>
|
||||
<body>
|
||||
<![CDATA[
|
||||
if(!this.isBound || this.suppressOnSelect)
|
||||
if (!this.isBound || this.suppressOnSelect)
|
||||
return;
|
||||
// we'll republish this as a selectionchange event on the grid
|
||||
aEvent.stopPropagation();
|
||||
|
@ -175,7 +175,7 @@
|
|||
<property name="selectedItems">
|
||||
<getter>
|
||||
<![CDATA[
|
||||
return this.querySelectorAll("richgriditem[selected]");
|
||||
return this.querySelectorAll("richgriditem[value][selected]");
|
||||
]]>
|
||||
</getter>
|
||||
</property>
|
||||
|
@ -204,28 +204,105 @@
|
|||
<parameter name="aSkipArrange"/>
|
||||
<body>
|
||||
<![CDATA[
|
||||
let addition = this._createItemElement(aLabel, aValue);
|
||||
this.appendChild(addition);
|
||||
let item = this.nextSlot();
|
||||
item.setAttribute("value", aValue);
|
||||
item.setAttribute("label", aLabel);
|
||||
|
||||
if (!aSkipArrange)
|
||||
this.arrangeItems();
|
||||
return addition;
|
||||
return item;
|
||||
]]>
|
||||
</body>
|
||||
</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">
|
||||
<parameter name="aSkipArrange"/>
|
||||
<body>
|
||||
<![CDATA[
|
||||
while (this.firstChild) {
|
||||
this.removeChild(this.firstChild);
|
||||
const ELEMENT_NODE_TYPE = Components.interfaces.nsIDOMNode.ELEMENT_NODE;
|
||||
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)
|
||||
this.arrangeItems();
|
||||
]]>
|
||||
</body>
|
||||
</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">
|
||||
<parameter name="anIndex"/>
|
||||
<parameter name="aLabel"/>
|
||||
|
@ -233,19 +310,30 @@
|
|||
<parameter name="aSkipArrange"/>
|
||||
<body>
|
||||
<![CDATA[
|
||||
anIndex = Math.min(this.itemCount, anIndex);
|
||||
let insertedItem;
|
||||
let existing = this.getItemAtIndex(anIndex);
|
||||
let addition = this._createItemElement(aLabel, aValue);
|
||||
if (existing) {
|
||||
this.insertBefore(addition, existing);
|
||||
// use an empty slot if we have one, otherwise insert it
|
||||
let childIndex = Array.indexOf(this.children, existing);
|
||||
if (childIndex > 0 && !this.children[childIndex-1].hasAttribute("value")) {
|
||||
insertedItem = this.children[childIndex-1];
|
||||
} else {
|
||||
this.appendChild(addition);
|
||||
insertedItem = this.insertBefore(this._createItemElement(),existing);
|
||||
}
|
||||
}
|
||||
if (!insertedItem) {
|
||||
insertedItem = this._slotAt(anIndex);
|
||||
}
|
||||
insertedItem.setAttribute("value", aValue);
|
||||
insertedItem.setAttribute("label", aLabel);
|
||||
if (!aSkipArrange)
|
||||
this.arrangeItems();
|
||||
return addition;
|
||||
return insertedItem;
|
||||
]]>
|
||||
</body>
|
||||
</method>
|
||||
|
||||
<method name="removeItemAt">
|
||||
<parameter name="anIndex"/>
|
||||
<parameter name="aSkipArrange"/>
|
||||
|
@ -266,7 +354,13 @@
|
|||
<![CDATA[
|
||||
if (!aItem || Array.indexOf(this.items, aItem) < 0)
|
||||
return null;
|
||||
|
||||
let removal = this.removeChild(aItem);
|
||||
// replace the slot if necessary
|
||||
if (this.childElementCount < this.minSlots) {
|
||||
this.nextSlot();
|
||||
}
|
||||
|
||||
if (removal && !aSkipArrange)
|
||||
this.arrangeItems();
|
||||
|
||||
|
@ -422,6 +516,7 @@
|
|||
<field name="_scheduledArrangeItemsTimerId">null</field>
|
||||
<field name="_scheduledArrangeItemsTries">0</field>
|
||||
<field name="_maxArrangeItemsRetries">5</field>
|
||||
|
||||
<method name="_scheduleArrangeItems">
|
||||
<parameter name="aTime"/>
|
||||
<body>
|
||||
|
@ -453,6 +548,7 @@
|
|||
|
||||
let itemDims = this._itemSize;
|
||||
let containerDims = this._containerSize;
|
||||
let slotsCount = this.childElementCount;
|
||||
|
||||
// reset the flags
|
||||
if (this._scheduledArrangeItemsTimerId) {
|
||||
|
@ -468,25 +564,25 @@
|
|||
|
||||
if (this.hasAttribute("vertical")) {
|
||||
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 {
|
||||
// We favor overflowing horizontally, not vertically (rows then colums)
|
||||
// rows attribute = max rows
|
||||
let maxRowCount = Math.min(this.getAttribute("rows") || Infinity, Math.floor(containerDims.height / itemDims.height));
|
||||
this._rowCount = Math.min(this.itemCount, maxRowCount);
|
||||
// rows attribute is fixed number of rows
|
||||
let maxRows = Math.floor(containerDims.height / itemDims.height);
|
||||
this._rowCount = this.getAttribute("rows") ?
|
||||
// fit indicated rows when possible
|
||||
Math.min(maxRows, this.getAttribute("rows")) :
|
||||
// at least 1 row
|
||||
Math.min(maxRows, slotsCount) || 1;
|
||||
|
||||
// columns attribute = min cols
|
||||
this._columnCount = this.itemCount ?
|
||||
Math.max(
|
||||
// at least 1 column when there are items
|
||||
this.getAttribute("columns") || 1,
|
||||
Math.ceil(this.itemCount / this._rowCount)
|
||||
) : this.getAttribute("columns") || 0;
|
||||
// columns attribute is min number of cols
|
||||
this._columnCount = Math.ceil(slotsCount / this._rowCount) || 1;
|
||||
if (this.getAttribute("columns")) {
|
||||
this._columnCount = Math.max(this._columnCount, this.getAttribute("columns"));
|
||||
}
|
||||
}
|
||||
|
||||
// width is typically auto, cap max columns by truncating items collection
|
||||
// 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) {
|
||||
gridStyle.MozColumnCount = this._columnCount;
|
||||
}
|
||||
|
@ -521,6 +617,12 @@
|
|||
<field name="_xslideHandler"/>
|
||||
<constructor>
|
||||
<![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)
|
||||
this.controller.gridBoundCallback();
|
||||
|
||||
|
@ -605,6 +707,7 @@
|
|||
]]>
|
||||
</body>
|
||||
</method>
|
||||
|
||||
<method name="_isIndexInBounds">
|
||||
<parameter name="anIndex"/>
|
||||
<body>
|
||||
|
@ -626,7 +729,7 @@
|
|||
if (aLabel) {
|
||||
item.setAttribute("label", aLabel);
|
||||
}
|
||||
if(this.hasAttribute("tiletype")) {
|
||||
if (this.hasAttribute("tiletype")) {
|
||||
item.setAttribute("tiletype", this.getAttribute("tiletype"));
|
||||
}
|
||||
return item;
|
||||
|
@ -704,6 +807,7 @@
|
|||
aItem.setAttribute("bending", true);
|
||||
]]></body>
|
||||
</method>
|
||||
|
||||
<method name="unbendItem">
|
||||
<parameter name="aItem"/>
|
||||
<body><![CDATA[
|
||||
|
@ -749,7 +853,7 @@
|
|||
let state = event.crossSlidingState;
|
||||
let thresholds = this._xslideHandler.thresholds;
|
||||
let transformValue;
|
||||
switch(state) {
|
||||
switch (state) {
|
||||
case "cancelled":
|
||||
this.unbendItem(event.target);
|
||||
event.target.removeAttribute('crosssliding');
|
||||
|
@ -844,7 +948,7 @@
|
|||
<body>
|
||||
<![CDATA[
|
||||
// Prevent an exception in case binding is not done yet.
|
||||
if(!this.isBound)
|
||||
if (!this.isBound)
|
||||
return;
|
||||
|
||||
// Seed the binding properties from bound-node attribute values
|
||||
|
@ -914,7 +1018,7 @@
|
|||
|
||||
<method name="refreshBackgroundImage">
|
||||
<body><![CDATA[
|
||||
if(!this.isBound)
|
||||
if (!this.isBound)
|
||||
return;
|
||||
if (this.backgroundImage) {
|
||||
this._top.style.removeProperty("background-image");
|
||||
|
@ -927,7 +1031,7 @@
|
|||
<property name="contextActions">
|
||||
<getter>
|
||||
<![CDATA[
|
||||
if(!this._contextActions) {
|
||||
if (!this._contextActions) {
|
||||
this._contextActions = new Set();
|
||||
let actionSet = this._contextActions;
|
||||
let actions = this.getAttribute("data-contextactions");
|
||||
|
@ -959,7 +1063,7 @@
|
|||
// fires for right-click, long-click and (keyboard) contextmenu input
|
||||
// toggle the selected state of tiles in a grid
|
||||
let gridParent = this.control;
|
||||
if(!this.isBound || !gridParent)
|
||||
if (!this.isBound || !gridParent)
|
||||
return;
|
||||
gridParent.handleItemContextMenu(this, event);
|
||||
]]>
|
||||
|
@ -967,4 +1071,10 @@
|
|||
</handlers>
|
||||
</binding>
|
||||
|
||||
<binding id="richgrid-empty-item">
|
||||
<content>
|
||||
<html:div anonid="anon-tile" class="tile-content"></html:div>
|
||||
</content>
|
||||
</binding>
|
||||
|
||||
</bindings>
|
||||
|
|
|
@ -119,6 +119,9 @@ richgrid {
|
|||
}
|
||||
|
||||
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");
|
||||
}
|
||||
|
||||
|
|
|
@ -73,11 +73,11 @@ BookmarksView.prototype = Util.extend(Object.create(View.prototype), {
|
|||
},
|
||||
|
||||
_getItemForBookmarkId: function bv__getItemForBookmark(aBookmarkId) {
|
||||
return this._set.querySelector("richgriditem[bookmarkId='" + aBookmarkId + "']");
|
||||
return this._set.querySelector("richgriditem[anonid='" + aBookmarkId + "']");
|
||||
},
|
||||
|
||||
_getBookmarkIdForItem: function bv__getBookmarkForItem(aItem) {
|
||||
return +aItem.getAttribute("bookmarkId");
|
||||
return +aItem.getAttribute("anonid");
|
||||
},
|
||||
|
||||
_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.arrangeItems();
|
||||
this._set.removeAttribute("fade");
|
||||
this._inBatch = false;
|
||||
rootNode.containerOpen = false;
|
||||
},
|
||||
|
@ -154,6 +155,7 @@ BookmarksView.prototype = Util.extend(Object.create(View.prototype), {
|
|||
},
|
||||
|
||||
clearBookmarks: function bv_clearBookmarks() {
|
||||
if ('clearAll' in this._set)
|
||||
this._set.clearAll();
|
||||
},
|
||||
|
||||
|
@ -162,7 +164,7 @@ BookmarksView.prototype = Util.extend(Object.create(View.prototype), {
|
|||
let uri = this._bookmarkService.getBookmarkURI(aBookmarkId);
|
||||
let title = this._bookmarkService.getItemTitle(aBookmarkId) || uri.spec;
|
||||
let item = this._set.insertItemAt(aPos || index, title, uri.spec, this._inBatch);
|
||||
item.setAttribute("bookmarkId", aBookmarkId);
|
||||
item.setAttribute("anonid", aBookmarkId);
|
||||
this._setContextActions(item);
|
||||
this._updateFavicon(item, uri);
|
||||
},
|
||||
|
@ -198,6 +200,7 @@ BookmarksView.prototype = Util.extend(Object.create(View.prototype), {
|
|||
let uri = this._bookmarkService.getBookmarkURI(aBookmarkId);
|
||||
let title = this._bookmarkService.getItemTitle(aBookmarkId) || uri.spec;
|
||||
|
||||
item.setAttribute("anonid", aBookmarkId);
|
||||
item.setAttribute("value", uri.spec);
|
||||
item.setAttribute("label", title);
|
||||
|
||||
|
|
|
@ -95,6 +95,7 @@ HistoryView.prototype = Util.extend(Object.create(View.prototype), {
|
|||
|
||||
rootNode.containerOpen = false;
|
||||
this._set.arrangeItems();
|
||||
this._set.removeAttribute("fade");
|
||||
if (this._inBatch > 0)
|
||||
this._inBatch--;
|
||||
},
|
||||
|
@ -130,6 +131,9 @@ HistoryView.prototype = Util.extend(Object.create(View.prototype), {
|
|||
let tileGroup = this._set;
|
||||
let selectedTiles = tileGroup.selectedItems;
|
||||
|
||||
// just arrange the grid once at the end of any action handling
|
||||
this._inBatch = true;
|
||||
|
||||
switch (aActionName){
|
||||
case "delete":
|
||||
Array.forEach(selectedTiles, function(aNode) {
|
||||
|
@ -182,9 +186,11 @@ HistoryView.prototype = Util.extend(Object.create(View.prototype), {
|
|||
break;
|
||||
|
||||
default:
|
||||
this._inBatch = false;
|
||||
return;
|
||||
}
|
||||
|
||||
this._inBatch = false;
|
||||
// Send refresh event so all view are in sync.
|
||||
this._sendNeedsRefresh();
|
||||
},
|
||||
|
@ -254,6 +260,7 @@ HistoryView.prototype = Util.extend(Object.create(View.prototype), {
|
|||
},
|
||||
|
||||
onClearHistory: function() {
|
||||
if ('clearAll' in this._set)
|
||||
this._set.clearAll();
|
||||
},
|
||||
|
||||
|
@ -264,7 +271,7 @@ HistoryView.prototype = Util.extend(Object.create(View.prototype), {
|
|||
let currIcon = item.getAttribute("iconURI");
|
||||
if (currIcon != aValue) {
|
||||
item.setAttribute("iconURI", aValue);
|
||||
if("refresh" in item)
|
||||
if ("refresh" in item)
|
||||
item.refresh();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -93,6 +93,7 @@ RemoteTabsView.prototype = Util.extend(Object.create(View.prototype), {
|
|||
}
|
||||
this.setUIAccessVisible(show);
|
||||
this._set.arrangeItems();
|
||||
this._set.removeAttribute("fade");
|
||||
},
|
||||
|
||||
destruct: function destruct() {
|
||||
|
|
|
@ -48,7 +48,17 @@
|
|||
<html:div class="meta-section-title narrow-title" onclick="StartUI.onNarrowTitleClick('start-topsites')">
|
||||
&narrowTopSitesHeader.label;
|
||||
</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 id="start-bookmarks" class="meta-section">
|
||||
|
@ -56,7 +66,10 @@
|
|||
<html:div class="meta-section-title narrow-title" onclick="StartUI.onNarrowTitleClick('start-bookmarks')">
|
||||
&narrowBookmarksHeader.label;
|
||||
</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 id="start-history" class="meta-section">
|
||||
|
@ -64,7 +77,11 @@
|
|||
<html:div class="meta-section-title narrow-title" onclick="StartUI.onNarrowTitleClick('start-history')">
|
||||
&narrowRecentHistoryHeader.label;
|
||||
</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>
|
||||
|
||||
#ifdef MOZ_SERVICES_SYNC
|
||||
|
@ -73,7 +90,12 @@
|
|||
<html:div id="snappedRemoteTabsLabel" class="meta-section-title narrow-title" onclick="StartUI.onNarrowTitleClick('start-remotetabs')">
|
||||
&narrowRemoteTabsHeader.label;
|
||||
</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>
|
||||
#endif
|
||||
</hbox>
|
||||
|
|
|
@ -158,6 +158,9 @@ TopSitesView.prototype = Util.extend(Object.create(View.prototype), {
|
|||
},
|
||||
|
||||
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));
|
||||
|
||||
Task.spawn(function() {
|
||||
|
@ -192,14 +195,11 @@ TopSitesView.prototype = Util.extend(Object.create(View.prototype), {
|
|||
tileset.clearAll(true);
|
||||
|
||||
for (let site of sites) {
|
||||
// call to private _createItemElement is a temp measure
|
||||
// we'll eventually just request the next slot
|
||||
let item = tileset._createItemElement(site.title, site.url);
|
||||
|
||||
this.updateTile(item, site);
|
||||
tileset.appendChild(item);
|
||||
let slot = tileset.nextSlot();
|
||||
this.updateTile(slot, site);
|
||||
}
|
||||
tileset.arrangeItems();
|
||||
tileset.removeAttribute("fade");
|
||||
this.isUpdating = false;
|
||||
},
|
||||
|
||||
|
@ -244,7 +244,7 @@ TopSitesView.prototype = Util.extend(Object.create(View.prototype), {
|
|||
|
||||
// nsIObservers
|
||||
observe: function (aSubject, aTopic, aState) {
|
||||
switch(aTopic) {
|
||||
switch (aTopic) {
|
||||
case "Metro:RefreshTopsiteThumbnail":
|
||||
this.forceReloadOfThumbnail(aState);
|
||||
break;
|
||||
|
@ -269,6 +269,7 @@ TopSitesView.prototype = Util.extend(Object.create(View.prototype), {
|
|||
},
|
||||
|
||||
onClearHistory: function() {
|
||||
if ('clearAll' in this._set)
|
||||
this._set.clearAll();
|
||||
},
|
||||
|
||||
|
|
|
@ -67,7 +67,7 @@ gTests.push({
|
|||
|
||||
ok(!item, "Item not in grid");
|
||||
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
|
||||
|
||||
|
@ -124,7 +124,7 @@ gTests.push({
|
|||
item = gStartView._set.getItemsByUrl(uriFromIndex(2))[0];
|
||||
|
||||
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(gStartView._set.itemCount === gStartView._limit, "Grid repopulated");
|
||||
|
||||
|
@ -150,9 +150,13 @@ gTests.push({
|
|||
ok(!deleteButton.hidden, "Delete button is visible.");
|
||||
|
||||
let promise = waitForCondition(() => !restoreButton.hidden);
|
||||
let populateGridSpy = spyOnMethod(gStartView, "populateGrid");
|
||||
|
||||
EventUtils.synthesizeMouse(deleteButton, 10, 10, {}, window);
|
||||
yield promise;
|
||||
|
||||
is(populateGridSpy.callCount, 1, "populateGrid was called in response to the deleting a tile");
|
||||
|
||||
item = gStartView._set.getItemsByUrl(uriFromIndex(2))[0];
|
||||
|
||||
ok(!item, "Item not in grid");
|
||||
|
@ -163,11 +167,14 @@ gTests.push({
|
|||
Elements.contextappbar.dismiss();
|
||||
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];
|
||||
|
||||
ok(!item, "Item not in grid");
|
||||
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
|
||||
|
||||
|
|
|
@ -17,6 +17,9 @@
|
|||
<richgrid id="grid_layout" seltype="single" flex="1">
|
||||
</richgrid>
|
||||
</vbox>
|
||||
<vbox>
|
||||
<richgrid id="slots_grid" seltype="single" minSlots="6" flex="1"/>
|
||||
</vbox>
|
||||
<vbox style="height:600px">
|
||||
<hbox>
|
||||
<richgrid id="clearGrid" seltype="single" flex="1" rows="2">
|
||||
|
@ -26,7 +29,7 @@
|
|||
</richgrid>
|
||||
</hbox>
|
||||
<hbox>
|
||||
<richgrid id="emptyGrid" seltype="single" flex="1" rows="2">
|
||||
<richgrid id="emptyGrid" seltype="single" flex="1" rows="2" minSlots="6">
|
||||
</richgrid>
|
||||
</hbox>
|
||||
<hbox>
|
||||
|
|
|
@ -9,6 +9,14 @@ function test() {
|
|||
}).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({
|
||||
desc: "richgrid binding is applied",
|
||||
run: function() {
|
||||
|
@ -17,9 +25,9 @@ gTests.push({
|
|||
let grid = doc.querySelector("#grid1");
|
||||
ok(grid, "#grid1 is found");
|
||||
is(typeof grid.clearSelection, "function", "#grid1 has the binding applied");
|
||||
|
||||
is(grid.items.length, 2, "#grid1 has a 2 items");
|
||||
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({
|
||||
desc: "empty grid",
|
||||
run: function() {
|
||||
// XXX grids have minSlots and may not be ever truly empty
|
||||
|
||||
let grid = doc.getElementById("emptyGrid");
|
||||
grid.arrangeItems();
|
||||
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");
|
||||
is(grid.itemCount, 0, "empty grid has 0 items");
|
||||
is(grid.rowCount, 0, "empty grid has 0 rows");
|
||||
is(grid.columnCount, 0, "empty grid has 0 cols");
|
||||
// minSlots attr. creates unpopulated slots
|
||||
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;
|
||||
let cStyle = doc.defaultView.getComputedStyle(columnsNode);
|
||||
is(cStyle.getPropertyValue("-moz-column-count"), "auto", "empty grid has -moz-column-count: auto");
|
||||
// remove rows attribute and allow space for the grid to find its own height
|
||||
// for its number of slots
|
||||
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");
|
||||
|
||||
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");
|
||||
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");
|
||||
ok(insertedAt0 && insertedAt00, "insertItemAt gives back an item");
|
||||
|
||||
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();
|
||||
}
|
||||
});
|
||||
|
@ -417,3 +443,172 @@ gTests.push({
|
|||
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;
|
||||
}
|
||||
|
||||
/* 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
|
||||
We use the compact, single-column grid treatment for <=320px */
|
||||
|
||||
|
|
|
@ -169,7 +169,7 @@ let AboutHome = {
|
|||
window.BrowserSearch.recordSearchInHealthReport(data.engineName, "abouthome");
|
||||
#endif
|
||||
// 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);
|
||||
break;
|
||||
}
|
||||
|
|
|
@ -9,11 +9,7 @@ dir-tests := $(DEPTH)/$(mobile-tests)
|
|||
ANDROID_APK_NAME := robocop-debug
|
||||
|
||||
ANDROID_EXTRA_JARS += \
|
||||
$(srcdir)/robotium-solo-4.2.jar \
|
||||
$(NULL)
|
||||
|
||||
ANDROID_RESFILES = \
|
||||
res/values/strings.xml \
|
||||
$(srcdir)/robotium-solo-4.3.jar \
|
||||
$(NULL)
|
||||
|
||||
ANDROID_ASSETS_DIR := $(TESTPATH)/assets
|
||||
|
|
|
@ -4,7 +4,7 @@ Robotium is an open source tool licensed under the Apache 2.0 license and the or
|
|||
source can be found here:
|
||||
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:
|
||||
http://code.google.com/p/robotium/
|
||||
|
||||
|
|
|
@ -6,3 +6,6 @@
|
|||
|
||||
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 \
|
||||
$(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 = \
|
||||
$(srcdir)/network-libs/commons-net-2.0.jar \
|
||||
$(srcdir)/network-libs/jmdns.jar \
|
||||
|
|
|
@ -11,14 +11,6 @@ JAVAFILES = \
|
|||
FileCursor.java \
|
||||
$(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
|
||||
|
||||
tools:: $(ANDROID_APK_NAME).apk
|
||||
|
|
|
@ -6,3 +6,10 @@
|
|||
|
||||
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 \
|
||||
$(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
|
||||
|
||||
tools:: $(ANDROID_APK_NAME).apk
|
||||
|
|
|
@ -6,3 +6,10 @@
|
|||
|
||||
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'
|
||||
|
||||
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 \
|
||||
$(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
|
||||
|
||||
tools:: $(ANDROID_APK_NAME).apk
|
||||
|
|
|
@ -6,3 +6,13 @@
|
|||
|
||||
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 #{
|
||||
|
||||
ifdef ANDROID_RESFILES #{
|
||||
ifndef IGNORE_ANDROID_RESFILES #{
|
||||
res-dep := .deps-copy-java-res
|
||||
|
||||
GENERATED_DIRS += res
|
||||
|
@ -25,6 +26,7 @@ res-dep-preqs := \
|
|||
$(res-dep): $(res-dep-preqs)
|
||||
$(call copy_dir,$(srcdir)/res,$(CURDIR)/res)
|
||||
@$(TOUCH) $@
|
||||
endif #} IGNORE_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
|
||||
# responsibility between Makefile.in and mozbuild files.
|
||||
_MOZBUILD_EXTERNAL_VARIABLES := \
|
||||
ANDROID_GENERATED_RESFILES \
|
||||
ANDROID_RESFILES \
|
||||
CMMSRCS \
|
||||
CPP_UNIT_TESTS \
|
||||
DIRS \
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
ifndef INCLUDED_JAVA_BUILD_MK #{
|
||||
|
||||
ifdef ANDROID_RESFILES #{
|
||||
ifndef IGNORE_ANDROID_RESFILES #{
|
||||
res-dep := .deps-copy-java-res
|
||||
|
||||
GENERATED_DIRS += res
|
||||
|
@ -25,6 +26,7 @@ res-dep-preqs := \
|
|||
$(res-dep): $(res-dep-preqs)
|
||||
$(call copy_dir,$(srcdir)/res,$(CURDIR)/res)
|
||||
@$(TOUCH) $@
|
||||
endif #} IGNORE_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
|
||||
# responsibility between Makefile.in and mozbuild files.
|
||||
_MOZBUILD_EXTERNAL_VARIABLES := \
|
||||
ANDROID_GENERATED_RESFILES \
|
||||
ANDROID_RESFILES \
|
||||
CMMSRCS \
|
||||
CPP_UNIT_TESTS \
|
||||
DIRS \
|
||||
|
|
|
@ -505,7 +505,7 @@ abstract public class BrowserApp extends GeckoApp
|
|||
registerEventListener("Updater:Launch");
|
||||
registerEventListener("Reader:GoToReadingList");
|
||||
|
||||
Distribution.init(this, getPackageResourcePath());
|
||||
Distribution.init(this);
|
||||
JavaAddonManager.getInstance().init(getApplicationContext());
|
||||
mSharedPreferencesHelper = new SharedPreferencesHelper(getApplicationContext());
|
||||
mOrderedBroadcastHelper = new OrderedBroadcastHelper(getApplicationContext());
|
||||
|
|
|
@ -1,11 +1,7 @@
|
|||
/* -*- 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
|
||||
* 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/.
|
||||
*
|
||||
* ***** END LICENSE BLOCK ***** */
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
package org.mozilla.gecko;
|
||||
|
||||
|
@ -13,113 +9,224 @@ import org.mozilla.gecko.util.ThreadUtils;
|
|||
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.util.Log;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.OutputStream;
|
||||
import java.util.Collections;
|
||||
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.ZipFile;
|
||||
|
||||
public final class Distribution {
|
||||
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_NONE = 1;
|
||||
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.
|
||||
ThreadUtils.postToBackgroundThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
// Bail if we've already initialized the distribution.
|
||||
SharedPreferences settings = context.getSharedPreferences(GeckoApp.PREFS_NAME, Activity.MODE_PRIVATE);
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
Distribution dist = new Distribution(context, packagePath, prefsPath);
|
||||
boolean distributionSet = dist.doInit();
|
||||
if (distributionSet) {
|
||||
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.
|
||||
* 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);
|
||||
ZipFile zip = new ZipFile(applicationPackage);
|
||||
|
||||
boolean distributionSet = false;
|
||||
Enumeration<? extends ZipEntry> zipEntries = zip.entries();
|
||||
|
||||
byte[] buffer = new byte[1024];
|
||||
while (zipEntries.hasMoreElements()) {
|
||||
ZipEntry fileEntry = zipEntries.nextElement();
|
||||
String name = fileEntry.getName();
|
||||
|
||||
if (!name.startsWith("distribution/"))
|
||||
if (!name.startsWith("distribution/")) {
|
||||
continue;
|
||||
}
|
||||
|
||||
distributionSet = true;
|
||||
|
||||
File dataDir = new File(context.getApplicationInfo().dataDir);
|
||||
File outFile = new File(dataDir, name);
|
||||
|
||||
File outFile = new File(getDataDir(), name);
|
||||
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);
|
||||
OutputStream outStream = new FileOutputStream(outFile);
|
||||
|
||||
int b;
|
||||
while ((b = fileStream.read()) != -1)
|
||||
outStream.write(b);
|
||||
int count;
|
||||
while ((count = fileStream.read(buffer)) != -1) {
|
||||
outStream.write(buffer, 0, count);
|
||||
}
|
||||
|
||||
fileStream.close();
|
||||
outStream.close();
|
||||
|
@ -132,77 +239,125 @@ public final class Distribution {
|
|||
}
|
||||
|
||||
/**
|
||||
* Returns parsed contents of bookmarks.json.
|
||||
* This method should only be called from a background thread.
|
||||
* After calling this method, either <code>distributionDir</code>
|
||||
* will be set, or there is no distribution in use.
|
||||
*
|
||||
* Only call after init.
|
||||
*/
|
||||
public static JSONArray getBookmarks(Context context) {
|
||||
SharedPreferences settings = context.getSharedPreferences(GeckoApp.PREFS_NAME, Activity.MODE_PRIVATE);
|
||||
String keyName = context.getPackageName() + ".distribution_state";
|
||||
int state = settings.getInt(keyName, STATE_UNKNOWN);
|
||||
if (state == STATE_NONE) {
|
||||
private File ensureDistributionDir() {
|
||||
if (this.distributionDir != null) {
|
||||
return this.distributionDir;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
ZipFile zip = null;
|
||||
InputStream inputStream = null;
|
||||
try {
|
||||
if (state == STATE_UNKNOWN) {
|
||||
// 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) {
|
||||
JSONObject all = new JSONObject(getFileContents(descFile));
|
||||
|
||||
if (!all.has("Global")) {
|
||||
Log.e(LOGTAG, "Distribution preferences.json has no Global entry!");
|
||||
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 new DistributionDescriptor(all.getJSONObject("Global"));
|
||||
|
||||
} catch (IOException e) {
|
||||
Log.e(LOGTAG, "Error getting distribution descriptor file.", e);
|
||||
return null;
|
||||
} catch (JSONException e) {
|
||||
Log.e(LOGTAG, "Error parsing preferences.json", e);
|
||||
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");
|
||||
inputStream = new FileInputStream(file);
|
||||
public JSONArray getBookmarks() {
|
||||
File bookmarks = getDistributionFile("bookmarks.json");
|
||||
if (bookmarks == null) {
|
||||
// Logging and existence checks are handled in getDistributionFile.
|
||||
return null;
|
||||
}
|
||||
|
||||
// Convert input stream to JSONArray
|
||||
BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
|
||||
StringBuilder stringBuilder = new StringBuilder();
|
||||
String s;
|
||||
while ((s = reader.readLine()) != null) {
|
||||
stringBuilder.append(s);
|
||||
}
|
||||
return new JSONArray(stringBuilder.toString());
|
||||
try {
|
||||
return new JSONArray(getFileContents(bookmarks));
|
||||
} catch (IOException e) {
|
||||
Log.e(LOGTAG, "Error getting bookmarks", e);
|
||||
} catch (JSONException 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;
|
||||
}
|
||||
|
||||
// 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.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.regex.Matcher;
|
||||
|
@ -1291,7 +1292,16 @@ abstract public class GeckoApp
|
|||
final String profilePath = getProfile().getDir().getAbsolutePath();
|
||||
final EventDispatcher dispatcher = GeckoAppShell.getEventDispatcher();
|
||||
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);
|
||||
}
|
||||
});
|
||||
|
@ -1555,8 +1565,15 @@ abstract public class GeckoApp
|
|||
GeckoPreferences.broadcastHealthReportUploadPref(context);
|
||||
|
||||
/*
|
||||
XXXX see bug 635342
|
||||
We want to disable this code if possible. It is about 145ms in runtime
|
||||
XXXX see Bug 635342.
|
||||
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);
|
||||
String localeCode = settings.getString(getPackageName() + ".locale", "");
|
||||
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.
|
||||
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)
|
||||
SharedPreferences settings =
|
||||
getContext().getPreferences(Activity.MODE_PRIVATE);
|
||||
|
|
|
@ -313,10 +313,15 @@ public final class GeckoProfile {
|
|||
}
|
||||
|
||||
public synchronized File getDir() {
|
||||
if (mDir != null) {
|
||||
forceCreate();
|
||||
return mDir;
|
||||
}
|
||||
|
||||
public synchronized GeckoProfile forceCreate() {
|
||||
if (mDir != null) {
|
||||
return this;
|
||||
}
|
||||
|
||||
try {
|
||||
// Check if a profile with this name already exists.
|
||||
File mozillaDir = ensureMozillaDirectory(mContext);
|
||||
|
@ -330,7 +335,7 @@ public final class GeckoProfile {
|
|||
} catch (IOException ioe) {
|
||||
Log.e(LOGTAG, "Error getting profile dir", ioe);
|
||||
}
|
||||
return mDir;
|
||||
return this;
|
||||
}
|
||||
|
||||
public File getFile(String aFile) {
|
||||
|
|
|
@ -55,6 +55,7 @@ public class GeckoView extends LayerView
|
|||
|
||||
Clipboard.init(context);
|
||||
HardwareUtils.init(context);
|
||||
GeckoNetworkManager.getInstance().init(context);
|
||||
|
||||
GeckoLoader.loadMozGlue();
|
||||
BrowserDB.setEnableContentProviders(false);
|
||||
|
@ -75,7 +76,7 @@ public class GeckoView extends LayerView
|
|||
ThreadUtils.setUiThread(Thread.currentThread(), new Handler());
|
||||
initializeView(GeckoAppShell.getEventDispatcher());
|
||||
|
||||
GeckoProfile profile = GeckoProfile.get(context);
|
||||
GeckoProfile profile = GeckoProfile.get(context).forceCreate();
|
||||
BrowserDB.initialize(profile.getName());
|
||||
|
||||
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
|
||||
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
|
||||
|
||||
ifdef MOZ_CRASHREPORTER
|
||||
FENNEC_JAVA_FILES += CrashReporter.java
|
||||
RES_DRAWABLE_MDPI += res/drawable-mdpi/crash_reporter.png
|
||||
RES_LAYOUT += res/layout/crash_reporter.xml
|
||||
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 = \
|
||||
jars/gecko-browser.jar \
|
||||
jars/gecko-mozglue.jar \
|
||||
|
@ -1280,6 +503,10 @@ endif
|
|||
|
||||
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
|
||||
|
||||
# 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
|
||||
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)
|
||||
$(NSINSTALL) -D $(@D)
|
||||
$(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 $@"
|
||||
$(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.
|
||||
MULTILOCALE_STRINGS_XML_FILES := $(wildcard res/values-*/strings.xml)
|
||||
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) \
|
||||
AndroidManifest.xml \
|
||||
$(RESOURCES) \
|
||||
$(subst resources/,res/,$(ANDROID_RESFILES)) \
|
||||
$(ANDROID_GENERATED_RESFILES) \
|
||||
$(NULL)
|
||||
|
||||
R.java: $(all_resources)
|
||||
|
|
|
@ -40,6 +40,7 @@ SYNC_JAVA_FILES := \
|
|||
background/db/Tab.java \
|
||||
background/healthreport/Environment.java \
|
||||
background/healthreport/EnvironmentBuilder.java \
|
||||
background/healthreport/EnvironmentV1.java \
|
||||
background/healthreport/HealthReportBroadcastReceiver.java \
|
||||
background/healthreport/HealthReportBroadcastService.java \
|
||||
background/healthreport/HealthReportDatabases.java \
|
||||
|
@ -301,53 +302,6 @@ SYNC_JAVA_FILES := \
|
|||
sync/Utils.java \
|
||||
$(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 := \
|
||||
httpclientandroidlib/androidextra/HttpClientAndroidLog.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;
|
||||
|
||||
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.
|
||||
* 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.
|
||||
*
|
||||
*/
|
||||
public abstract class Environment {
|
||||
private static final String LOG_TAG = "GeckoEnvironment";
|
||||
public abstract class Environment extends EnvironmentV1 {
|
||||
// Version 2 adds osLocale, appLocale, acceptLangSet, and distribution.
|
||||
public static final int CURRENT_VERSION = 2;
|
||||
|
||||
public static int VERSION = 1;
|
||||
|
||||
protected final Class<? extends EnvironmentAppender> appenderClass;
|
||||
|
||||
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 String osLocale; // The Android OS "Locale" value.
|
||||
public String appLocale;
|
||||
public int acceptLangSet;
|
||||
public String distribution; // ID + version. Typically empty.
|
||||
|
||||
public Environment() {
|
||||
this(Environment.HashAppender.class);
|
||||
}
|
||||
|
||||
public Environment(Class<? extends EnvironmentAppender> appenderClass) {
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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");
|
||||
super(appenderClass);
|
||||
version = CURRENT_VERSION;
|
||||
}
|
||||
|
||||
@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.
|
||||
}
|
||||
}
|
||||
protected void appendHash(EnvironmentAppender appender) {
|
||||
super.appendHash(appender);
|
||||
|
||||
@Override
|
||||
public void append(int profileCreation) {
|
||||
append(Integer.toString(profileCreation, 10));
|
||||
// v2.
|
||||
appender.append(osLocale);
|
||||
appender.append(appLocale);
|
||||
appender.append(acceptLangSet);
|
||||
appender.append(distribution);
|
||||
}
|
||||
|
||||
@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 boolean isBlocklistEnabled();
|
||||
public boolean isTelemetryEnabled();
|
||||
public boolean isAcceptLangUserSet();
|
||||
public long getProfileCreationTime();
|
||||
|
||||
public String getDistributionString();
|
||||
public String getOSLocale();
|
||||
public String getAppLocale();
|
||||
|
||||
public JSONObject getAddonsJSON();
|
||||
}
|
||||
|
||||
|
@ -124,6 +130,12 @@ public class EnvironmentBuilder {
|
|||
}
|
||||
|
||||
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[] {
|
||||
"id", "hash",
|
||||
"id", "version", "hash",
|
||||
"profileCreation", "cpuCount", "memoryMB",
|
||||
|
||||
"isBlocklistEnabled", "isTelemetryEnabled", "extensionCount",
|
||||
|
@ -138,6 +138,8 @@ public class HealthReportDatabaseStorage implements HealthReportStorage {
|
|||
"appVersion", "appBuildID", "platformVersion", "platformBuildID", "os",
|
||||
"xpcomabi", "updateChannel",
|
||||
|
||||
"distribution", "osLocale", "appLocale", "acceptLangSet",
|
||||
|
||||
// Joined to the add-ons table.
|
||||
"addonsBody"
|
||||
};
|
||||
|
@ -188,7 +190,7 @@ public class HealthReportDatabaseStorage implements HealthReportStorage {
|
|||
protected final HealthReportSQLiteOpenHelper helper;
|
||||
|
||||
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";
|
||||
|
||||
/**
|
||||
|
@ -252,7 +254,10 @@ public class HealthReportDatabaseStorage implements HealthReportStorage {
|
|||
" 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, " +
|
||||
" version INTEGER, " +
|
||||
" hash TEXT, " +
|
||||
" profileCreation INTEGER, " +
|
||||
" cpuCount INTEGER, " +
|
||||
|
@ -275,6 +280,12 @@ public class HealthReportDatabaseStorage implements HealthReportStorage {
|
|||
" os TEXT, " +
|
||||
" xpcomabi TEXT, " +
|
||||
" updateChannel TEXT, " +
|
||||
|
||||
" distribution TEXT, " +
|
||||
" osLocale TEXT, " +
|
||||
" appLocale TEXT, " +
|
||||
" acceptLangSet INTEGER, " +
|
||||
|
||||
" addonsID INTEGER, " +
|
||||
" FOREIGN KEY (addonsID) REFERENCES addons(id) ON DELETE RESTRICT, " +
|
||||
" UNIQUE (hash) " +
|
||||
|
@ -357,6 +368,7 @@ public class HealthReportDatabaseStorage implements HealthReportStorage {
|
|||
private void createAddonsEnvironmentsView(SQLiteDatabase db) {
|
||||
db.execSQL("CREATE VIEW environments_with_addons AS " +
|
||||
"SELECT e.id AS id, " +
|
||||
" e.version AS version, " +
|
||||
" e.hash AS hash, " +
|
||||
" e.profileCreation AS profileCreation, " +
|
||||
" e.cpuCount AS cpuCount, " +
|
||||
|
@ -379,6 +391,10 @@ public class HealthReportDatabaseStorage implements HealthReportStorage {
|
|||
" e.os AS os, " +
|
||||
" e.xpcomabi AS xpcomabi, " +
|
||||
" e.updateChannel AS updateChannel, " +
|
||||
" e.distribution AS distribution, " +
|
||||
" e.osLocale AS osLocale, " +
|
||||
" e.appLocale AS appLocale, " +
|
||||
" e.acceptLangSet AS acceptLangSet, " +
|
||||
" addons.body AS addonsBody " +
|
||||
"FROM environments AS e, addons " +
|
||||
"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);
|
||||
}
|
||||
|
||||
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
|
||||
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
|
||||
if (oldVersion >= newVersion) {
|
||||
|
@ -432,6 +464,8 @@ public class HealthReportDatabaseStorage implements HealthReportStorage {
|
|||
upgradeDatabaseFrom3To4(db);
|
||||
case 4:
|
||||
upgradeDatabaseFrom4to5(db);
|
||||
case 5:
|
||||
upgradeDatabaseFrom5to6(db);
|
||||
}
|
||||
} catch (Exception 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.
|
||||
ContentValues v = new ContentValues();
|
||||
v.put("version", version);
|
||||
v.put("hash", h);
|
||||
v.put("profileCreation", profileCreation);
|
||||
v.put("cpuCount", cpuCount);
|
||||
|
@ -558,6 +593,10 @@ public class HealthReportDatabaseStorage implements HealthReportStorage {
|
|||
v.put("os", os);
|
||||
v.put("xpcomabi", xpcomabi);
|
||||
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();
|
||||
|
||||
|
@ -643,6 +682,7 @@ public class HealthReportDatabaseStorage implements HealthReportStorage {
|
|||
}
|
||||
|
||||
public void init(ContentValues v) {
|
||||
version = v.containsKey("version") ? v.getAsInteger("version") : Environment.CURRENT_VERSION;
|
||||
profileCreation = v.getAsInteger("profileCreation");
|
||||
cpuCount = v.getAsInteger("cpuCount");
|
||||
memoryMB = v.getAsInteger("memoryMB");
|
||||
|
@ -667,6 +707,11 @@ public class HealthReportDatabaseStorage implements HealthReportStorage {
|
|||
xpcomabi = v.getAsString("xpcomabi");
|
||||
updateChannel = v.getAsString("updateChannel");
|
||||
|
||||
distribution = v.getAsString("distribution");
|
||||
osLocale = v.getAsString("osLocale");
|
||||
appLocale = v.getAsString("appLocale");
|
||||
acceptLangSet = v.getAsInteger("acceptLangSet");
|
||||
|
||||
try {
|
||||
setJSONForAddons(v.getAsString("addonsBody"));
|
||||
} catch (Exception e) {
|
||||
|
@ -686,6 +731,7 @@ public class HealthReportDatabaseStorage implements HealthReportStorage {
|
|||
public boolean init(Cursor cursor) {
|
||||
int i = 0;
|
||||
this.id = cursor.getInt(i++);
|
||||
this.version = cursor.getInt(i++);
|
||||
this.hash = cursor.getString(i++);
|
||||
|
||||
profileCreation = cursor.getInt(i++);
|
||||
|
@ -712,6 +758,11 @@ public class HealthReportDatabaseStorage implements HealthReportStorage {
|
|||
xpcomabi = cursor.getString(i++);
|
||||
updateChannel = cursor.getString(i++);
|
||||
|
||||
distribution = cursor.getString(i++);
|
||||
osLocale = cursor.getString(i++);
|
||||
appLocale = cursor.getString(i++);
|
||||
acceptLangSet = cursor.getInt(i++);
|
||||
|
||||
try {
|
||||
setJSONForAddons(cursor.getBlob(i++));
|
||||
} catch (Exception e) {
|
||||
|
@ -1339,6 +1390,7 @@ public class HealthReportDatabaseStorage implements HealthReportStorage {
|
|||
}
|
||||
|
||||
// Called internally only to ensure the same db instance is used.
|
||||
@SuppressWarnings("static-method")
|
||||
protected int deleteOrphanedEnv(final SQLiteDatabase db, final int curEnv) {
|
||||
final String whereClause =
|
||||
"id != ? AND " +
|
||||
|
@ -1353,6 +1405,7 @@ public class HealthReportDatabaseStorage implements HealthReportStorage {
|
|||
}
|
||||
|
||||
// Called internally only to ensure the same db instance is used.
|
||||
@SuppressWarnings("static-method")
|
||||
protected int deleteEventsBefore(final SQLiteDatabase db, final String dayString) {
|
||||
final String whereClause = "date < ?";
|
||||
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.
|
||||
@SuppressWarnings("static-method")
|
||||
protected int deleteOrphanedAddons(final SQLiteDatabase db) {
|
||||
final String whereClause = "id NOT IN (SELECT addonsID FROM environments)";
|
||||
return db.delete("addons", whereClause, null);
|
||||
|
|
|
@ -388,24 +388,117 @@ public class HealthReportGenerator {
|
|||
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 {
|
||||
JSONObject appinfo = new JSONObject();
|
||||
int changes = 0;
|
||||
if (current == null || current.isBlocklistEnabled != e.isBlocklistEnabled) {
|
||||
appinfo.put("isBlocklistEnabled", e.isBlocklistEnabled);
|
||||
changes++;
|
||||
|
||||
Logger.debug(LOG_TAG, "Generating appinfo for v" + e.version + " env " + e.hash);
|
||||
|
||||
// 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);
|
||||
changes++;
|
||||
|
||||
switch (e.version) {
|
||||
case 2:
|
||||
if (populateAppInfoV2(appinfo, e, current, outdated)) {
|
||||
changed = true;
|
||||
}
|
||||
if (current != null && changes == 0) {
|
||||
// Fall through.
|
||||
|
||||
case 1:
|
||||
// There is no older version than v1, so don't check outdated.
|
||||
if (populateAppInfoV1(e, current, appinfo)) {
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!changed) {
|
||||
return null;
|
||||
}
|
||||
appinfo.put("_v", 2);
|
||||
|
||||
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 {
|
||||
JSONObject counts = new JSONObject();
|
||||
int changes = 0;
|
||||
|
|
|
@ -10,6 +10,7 @@ import java.io.FileOutputStream;
|
|||
import java.io.IOException;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.Locale;
|
||||
import java.util.Scanner;
|
||||
|
||||
import org.json.JSONException;
|
||||
|
@ -32,8 +33,9 @@ public class ProfileInformationCache implements ProfileInformationProvider {
|
|||
* -: No version number; implicit v1.
|
||||
* 1: Add versioning (Bug 878670).
|
||||
* 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 needsWrite = false;
|
||||
|
@ -42,7 +44,29 @@ public class ProfileInformationCache implements ProfileInformationProvider {
|
|||
|
||||
private volatile boolean blocklistEnabled = true;
|
||||
private volatile boolean telemetryEnabled = false;
|
||||
private volatile boolean isAcceptLangUserSet = false;
|
||||
|
||||
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;
|
||||
|
||||
|
@ -62,7 +86,11 @@ public class ProfileInformationCache implements ProfileInformationProvider {
|
|||
object.put("version", FORMAT_VERSION);
|
||||
object.put("blocklist", blocklistEnabled);
|
||||
object.put("telemetry", telemetryEnabled);
|
||||
object.put("isAcceptLangUserSet", isAcceptLangUserSet);
|
||||
object.put("profileCreated", profileCreationTime);
|
||||
object.put("osLocale", osLocale);
|
||||
object.put("appLocale", appLocale);
|
||||
object.put("distribution", distribution);
|
||||
object.put("addons", addons);
|
||||
} catch (JSONException e) {
|
||||
// There isn't much we can do about this.
|
||||
|
@ -86,8 +114,12 @@ public class ProfileInformationCache implements ProfileInformationProvider {
|
|||
case FORMAT_VERSION:
|
||||
blocklistEnabled = object.getBoolean("blocklist");
|
||||
telemetryEnabled = object.getBoolean("telemetry");
|
||||
isAcceptLangUserSet = object.getBoolean("isAcceptLangUserSet");
|
||||
profileCreationTime = object.getLong("profileCreated");
|
||||
addons = object.getJSONObject("addons");
|
||||
distribution = object.getString("distribution");
|
||||
osLocale = object.getString("osLocale");
|
||||
appLocale = object.getString("appLocale");
|
||||
return true;
|
||||
default:
|
||||
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;
|
||||
}
|
||||
|
||||
@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
|
||||
public long getProfileCreationTime() {
|
||||
ensureInitialized();
|
||||
|
@ -218,17 +262,83 @@ public class ProfileInformationCache implements ProfileInformationProvider {
|
|||
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
|
||||
public JSONObject getAddonsJSON() {
|
||||
ensureInitialized();
|
||||
return addons;
|
||||
}
|
||||
|
||||
public void updateJSONForAddon(String id, String json) throws Exception {
|
||||
addons.put(id, new JSONObject(json));
|
||||
needsWrite = true;
|
||||
}
|
||||
|
||||
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 {
|
||||
addons.put(id, json);
|
||||
needsWrite = true;
|
||||
} catch (Exception e) {
|
||||
// Why would this happen?
|
||||
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 {
|
||||
addons = new JSONObject(json);
|
||||
needsWrite = true;
|
||||
}
|
||||
|
||||
public void setJSONForAddons(JSONObject json) {
|
||||
addons = json;
|
||||
needsWrite = true;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,11 +13,11 @@ import android.content.SharedPreferences;
|
|||
import android.util.Log;
|
||||
|
||||
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.GeckoAppShell;
|
||||
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.HealthReportDatabaseStorage;
|
||||
|
@ -38,6 +38,7 @@ import java.io.FileOutputStream;
|
|||
import java.io.OutputStreamWriter;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.Scanner;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
|
@ -50,8 +51,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
|||
* Keep an instance of this class around.
|
||||
*
|
||||
* Tell it when an environment attribute has changed: call {@link
|
||||
* #onBlocklistPrefChanged(boolean)} or {@link
|
||||
* #onTelemetryPrefChanged(boolean)}, followed by {@link
|
||||
* #onAppLocaleChanged(String)} followed by {@link
|
||||
* #onEnvironmentChanged()}.
|
||||
*
|
||||
* Use it to record events: {@link #recordSearch(String, String)}.
|
||||
|
@ -60,8 +60,9 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
|||
*/
|
||||
public class BrowserHealthRecorder implements GeckoEventListener {
|
||||
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 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_UNINSTALLING = "Addons:Uninstalling";
|
||||
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.
|
||||
*
|
||||
* 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);
|
||||
this.dispatcher = dispatcher;
|
||||
this.previousSession = previousSession;
|
||||
|
@ -263,9 +271,12 @@ public class BrowserHealthRecorder implements GeckoEventListener {
|
|||
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);
|
||||
try {
|
||||
this.initialize(context, profilePath);
|
||||
this.initialize(context, profilePath, osLocale, appLocale);
|
||||
} catch (Exception e) {
|
||||
Log.e(LOG_TAG, "Exception initializing.", e);
|
||||
}
|
||||
|
@ -299,7 +310,7 @@ public class BrowserHealthRecorder implements GeckoEventListener {
|
|||
}
|
||||
|
||||
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_UNINSTALLING, this);
|
||||
this.dispatcher.unregisterEventListener(EVENT_PREF_CHANGE, this);
|
||||
|
@ -307,14 +318,9 @@ public class BrowserHealthRecorder implements GeckoEventListener {
|
|||
this.dispatcher.unregisterEventListener(EVENT_SEARCH, this);
|
||||
}
|
||||
|
||||
public void onBlocklistPrefChanged(boolean to) {
|
||||
public void onAppLocaleChanged(String to) {
|
||||
this.profileCache.beginInitialization();
|
||||
this.profileCache.setBlocklistEnabled(to);
|
||||
}
|
||||
|
||||
public void onTelemetryPrefChanged(boolean to) {
|
||||
this.profileCache.beginInitialization();
|
||||
this.profileCache.setTelemetryEnabled(to);
|
||||
this.profileCache.setAppLocale(to);
|
||||
}
|
||||
|
||||
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
|
||||
* for use in future events.
|
||||
*
|
||||
* Invoke this method after calls that mutate the environment, such as
|
||||
* {@link #onBlocklistPrefChanged(boolean)}.
|
||||
* Invoke this method after calls that mutate the environment.
|
||||
*
|
||||
* If this change resulted in a transition between two environments, {@link
|
||||
* #onEnvironmentTransition(int, int)} will be invoked on the background
|
||||
|
@ -491,14 +496,36 @@ public class BrowserHealthRecorder implements GeckoEventListener {
|
|||
return time;
|
||||
}
|
||||
|
||||
private void handlePrefValue(final String pref, final boolean value) {
|
||||
Log.d(LOG_TAG, "Incorporating environment: " + pref + " = " + value);
|
||||
if (AppConstants.TELEMETRY_PREF_NAME.equals(pref)) {
|
||||
profileCache.setTelemetryEnabled(value);
|
||||
private void onPrefMessage(final String pref, final JSONObject message) {
|
||||
Log.d(LOG_TAG, "Incorporating environment: " + pref);
|
||||
if (PREF_ACCEPT_LANG.equals(pref)) {
|
||||
// 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;
|
||||
}
|
||||
|
||||
// (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)) {
|
||||
profileCache.setBlocklistEnabled(value);
|
||||
this.profileCache.beginInitialization();
|
||||
this.profileCache.setBlocklistEnabled(value);
|
||||
return;
|
||||
}
|
||||
} catch (JSONException ex) {
|
||||
Log.w(LOG_TAG, "Unexpected JSONException fetching boolean value for " + pref);
|
||||
return;
|
||||
}
|
||||
Log.w(LOG_TAG, "Unexpected pref: " + pref);
|
||||
|
@ -571,7 +598,9 @@ public class BrowserHealthRecorder implements GeckoEventListener {
|
|||
* Add provider-specific initialization in this method.
|
||||
*/
|
||||
private synchronized void initialize(final Context context,
|
||||
final String profilePath)
|
||||
final String profilePath,
|
||||
final String osLocale,
|
||||
final String appLocale)
|
||||
throws java.io.IOException {
|
||||
|
||||
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 (this.profileCache.restoreUnlessInitialized()) {
|
||||
this.profileCache.updateLocales(osLocale, appLocale);
|
||||
this.profileCache.completeInitialization();
|
||||
|
||||
Log.d(LOG_TAG, "Successfully restored state. Initializing storage.");
|
||||
initializeStorage();
|
||||
return;
|
||||
|
@ -587,31 +619,24 @@ public class BrowserHealthRecorder implements GeckoEventListener {
|
|||
// Otherwise, let's initialize it from scratch.
|
||||
this.profileCache.beginInitialization();
|
||||
this.profileCache.setProfileCreationTime(getAndPersistProfileInitTime(context, profilePath));
|
||||
this.profileCache.setOSLocale(osLocale);
|
||||
this.profileCache.setAppLocale(appLocale);
|
||||
|
||||
final BrowserHealthRecorder self = this;
|
||||
|
||||
PrefHandler handler = new PrefsHelper.PrefHandlerBase() {
|
||||
// Because the distribution lookup can take some time, do it at the end of
|
||||
// our background startup work, along with the Gecko snapshot fetch.
|
||||
final GeckoEventListener self = this;
|
||||
ThreadUtils.postToBackgroundThread(new Runnable() {
|
||||
@Override
|
||||
public void prefValue(String pref, boolean value) {
|
||||
handlePrefValue(pref, value);
|
||||
public void run() {
|
||||
final DistributionDescriptor desc = new Distribution(context).getDescriptor();
|
||||
if (desc != null && desc.valid) {
|
||||
profileCache.setDistributionString(desc.id, desc.version);
|
||||
}
|
||||
|
||||
@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.
|
||||
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));
|
||||
}
|
||||
};
|
||||
|
||||
// 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
|
||||
public void handleMessage(String event, JSONObject message) {
|
||||
try {
|
||||
if (EVENT_ADDONS_ALL.equals(event)) {
|
||||
Log.d(LOG_TAG, "Got all add-ons.");
|
||||
if (EVENT_SNAPSHOT.equals(event)) {
|
||||
Log.d(LOG_TAG, "Got all add-ons and prefs.");
|
||||
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.");
|
||||
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();
|
||||
} catch (java.io.IOException 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)) {
|
||||
final String pref = message.getString("pref");
|
||||
Log.d(LOG_TAG, "Pref changed: " + pref);
|
||||
handlePrefValue(pref, message.getBoolean("value"));
|
||||
this.onPrefMessage(pref, message);
|
||||
this.onEnvironmentChanged();
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -5,3 +5,609 @@
|
|||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
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.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
|
@ -38,11 +39,50 @@ public class testDistribution extends ContentProviderTest {
|
|||
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() {
|
||||
mActivity = getActivity();
|
||||
|
||||
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
|
||||
clearDistributionPref();
|
||||
setTestLocale("en-US");
|
||||
|
@ -64,11 +104,10 @@ public class testDistribution extends ContentProviderTest {
|
|||
// Call Distribution.init with the mock package.
|
||||
ClassLoader classLoader = mActivity.getClassLoader();
|
||||
Class distributionClass = classLoader.loadClass("org.mozilla.gecko.Distribution");
|
||||
Class contextClass = classLoader.loadClass("android.content.Context");
|
||||
Method init = distributionClass.getMethod("init", contextClass, String.class);
|
||||
Method init = distributionClass.getMethod("init", Context.class, String.class, String.class);
|
||||
|
||||
Actions.EventExpecter distributionSetExpecter = mActions.expectGeckoEvent("Distribution:Set:OK");
|
||||
init.invoke(null, mActivity, aPackagePath);
|
||||
init.invoke(null, mActivity, aPackagePath, "prefs-" + System.currentTimeMillis());
|
||||
distributionSetExpecter.blockForEvent();
|
||||
distributionSetExpecter.unregisterListener();
|
||||
} catch (Exception e) {
|
||||
|
@ -268,6 +307,7 @@ public class testDistribution extends ContentProviderTest {
|
|||
|
||||
// Clears the distribution pref to return distribution state to STATE_UNKNOWN
|
||||
private void clearDistributionPref() {
|
||||
mAsserter.dumpLog("Clearing distribution pref.");
|
||||
SharedPreferences settings = mActivity.getSharedPreferences("GeckoApp", Activity.MODE_PRIVATE);
|
||||
String keyName = mActivity.getPackageName() + ".distribution_state";
|
||||
settings.edit().remove(keyName).commit();
|
||||
|
|
|
@ -5316,7 +5316,10 @@ var FormAssistant = {
|
|||
* -- and reflect them back to Java.
|
||||
*/
|
||||
let HealthReportStatusListener = {
|
||||
TELEMETRY_PREF:
|
||||
PREF_ACCEPT_LANG: "intl.accept_languages",
|
||||
PREF_BLOCKLIST_ENABLED: "extensions.blocklist.enabled",
|
||||
|
||||
PREF_TELEMETRY_ENABLED:
|
||||
#ifdef MOZ_TELEMETRY_REPORTING
|
||||
// Telemetry pref differs based on build.
|
||||
#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);
|
||||
}
|
||||
|
||||
Services.obs.addObserver(this, "Addons:FetchAll", false);
|
||||
Services.prefs.addObserver("extensions.blocklist.enabled", this, false);
|
||||
if (this.TELEMETRY_PREF) {
|
||||
Services.prefs.addObserver(this.TELEMETRY_PREF, this, false);
|
||||
console.log("Adding HealthReport:RequestSnapshot observer.");
|
||||
Services.obs.addObserver(this, "HealthReport:RequestSnapshot", false);
|
||||
Services.prefs.addObserver(this.PREF_ACCEPT_LANG, 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 () {
|
||||
Services.obs.removeObserver(this, "Addons:FetchAll");
|
||||
Services.prefs.removeObserver("extensions.blocklist.enabled", this);
|
||||
if (this.TELEMETRY_PREF) {
|
||||
Services.prefs.removeObserver(this.TELEMETRY_PREF, this);
|
||||
Services.obs.removeObserver(this, "HealthReport:RequestSnapshot");
|
||||
Services.prefs.removeObserver(this.PREF_ACCEPT_LANG, this);
|
||||
Services.prefs.removeObserver(this.PREF_BLOCKLIST_ENABLED, this);
|
||||
if (this.PREF_TELEMETRY_ENABLED) {
|
||||
Services.prefs.removeObserver(this.PREF_TELEMETRY_ENABLED, this);
|
||||
}
|
||||
|
||||
AddonManager.removeAddonListener(this);
|
||||
|
@ -5354,11 +5360,30 @@ let HealthReportStatusListener = {
|
|||
|
||||
observe: function (aSubject, aTopic, aData) {
|
||||
switch (aTopic) {
|
||||
case "Addons:FetchAll":
|
||||
HealthReportStatusListener.sendAllAddonsToJava();
|
||||
case "HealthReport:RequestSnapshot":
|
||||
HealthReportStatusListener.sendSnapshotToJava();
|
||||
break;
|
||||
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;
|
||||
}
|
||||
},
|
||||
|
@ -5440,9 +5465,9 @@ let HealthReportStatusListener = {
|
|||
this.notifyJava(aAddon);
|
||||
},
|
||||
|
||||
sendAllAddonsToJava: function () {
|
||||
sendSnapshotToJava: function () {
|
||||
AddonManager.getAllAddons(function (aAddons) {
|
||||
let json = {};
|
||||
let jsonA = {};
|
||||
if (aAddons) {
|
||||
for (let i = 0; i < aAddons.length; ++i) {
|
||||
let addon = aAddons[i];
|
||||
|
@ -5451,14 +5476,43 @@ let HealthReportStatusListener = {
|
|||
if (HealthReportStatusListener._shouldIgnore(addon)) {
|
||||
addonJSON.ignore = true;
|
||||
}
|
||||
json[addon.id] = addonJSON;
|
||||
jsonA[addon.id] = addonJSON;
|
||||
} catch (e) {
|
||||
// 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/healthreport/Environment.java
|
||||
background/healthreport/EnvironmentBuilder.java
|
||||
background/healthreport/EnvironmentV1.java
|
||||
background/healthreport/HealthReportBroadcastReceiver.java
|
||||
background/healthreport/HealthReportBroadcastService.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.
|
||||
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
|
||||
# being linked against them. This is a best effort to avoid getting
|
||||
|
|
|
@ -100,11 +100,3 @@ BACKGROUND_TESTS_JAVA_FILES := \
|
|||
src/testhelpers/WBORepository.java \
|
||||
$(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
|
||||
# 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/.
|
||||
|
||||
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;
|
||||
cpuCount = 2;
|
||||
memoryMB = 512;
|
||||
|
@ -55,7 +55,7 @@ public class MockDatabaseEnvironment extends DatabaseEnvironment {
|
|||
vendor = "";
|
||||
appName = "";
|
||||
appID = "";
|
||||
appVersion = version;
|
||||
this.appVersion = appVersion;
|
||||
appBuildID = "";
|
||||
platformVersion = "";
|
||||
platformBuildID = "";
|
||||
|
@ -63,6 +63,14 @@ public class MockDatabaseEnvironment extends DatabaseEnvironment {
|
|||
xpcomabi = "";
|
||||
updateChannel = "";
|
||||
|
||||
// v2 fields.
|
||||
distribution = "";
|
||||
appLocale = "";
|
||||
osLocale = "";
|
||||
acceptLangSet = 0;
|
||||
|
||||
version = Environment.CURRENT_VERSION;
|
||||
|
||||
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.helpers.FakeProfileTestCase;
|
||||
|
||||
import android.database.Cursor;
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
import android.util.SparseArray;
|
||||
|
||||
public class TestHealthReportGenerator extends FakeProfileTestCase {
|
||||
@SuppressWarnings("static-method")
|
||||
public void testOptObject() throws JSONException {
|
||||
|
@ -57,9 +61,14 @@ public class TestHealthReportGenerator extends FakeProfileTestCase {
|
|||
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"
|
||||
+ "nullnullnullnullnullnull00000";
|
||||
|
||||
// v2 fields.
|
||||
private static final String EXPECTED_MOCK_BASE_HASH_SUFFIX = "null" + "null" + 0 + "null";
|
||||
|
||||
public void testHashing() throws JSONException {
|
||||
MockHealthReportDatabaseStorage storage = new MockHealthReportDatabaseStorage(context, fakeProfileDirectory);
|
||||
MockDatabaseEnvironment env = new MockDatabaseEnvironment(storage, MockDatabaseEnvironment.MockEnvironmentAppender.class);
|
||||
|
@ -96,10 +105,10 @@ public class TestHealthReportGenerator extends FakeProfileTestCase {
|
|||
"}");
|
||||
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);
|
||||
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 {
|
||||
|
@ -406,4 +415,103 @@ public class TestHealthReportGenerator extends FakeProfileTestCase {
|
|||
protected String getCacheSuffix() {
|
||||
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);
|
||||
try {
|
||||
assertTrue(envCursor.moveToFirst());
|
||||
envHash = envCursor.getString(1);
|
||||
envHash = envCursor.getString(2); // id, version, hash, ...
|
||||
} finally {
|
||||
envCursor.close();
|
||||
}
|
||||
|
@ -249,6 +249,13 @@ public class TestHealthReportProvider extends DBProviderTestCase<HealthReportPro
|
|||
v.put("os", "");
|
||||
v.put("xpcomabi", "");
|
||||
v.put("updateChannel", "");
|
||||
|
||||
// v2.
|
||||
v.put("distribution", "");
|
||||
v.put("osLocale", "en_us");
|
||||
v.put("appLocale", "en_us");
|
||||
v.put("acceptLangSet", 0);
|
||||
|
||||
return v;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -139,6 +139,8 @@ class TreeMetadataEmitter(LoggingMixin):
|
|||
passthru = VariablePassthru(sandbox)
|
||||
varmap = dict(
|
||||
# Makefile.in : moz.build
|
||||
ANDROID_GENERATED_RESFILES='ANDROID_GENERATED_RESFILES',
|
||||
ANDROID_RESFILES='ANDROID_RESFILES',
|
||||
ASFILES='ASFILES',
|
||||
CMMSRCS='CMMSRCS',
|
||||
CPPSRCS='CPP_SOURCES',
|
||||
|
|
|
@ -43,6 +43,22 @@ from mozbuild.util import (
|
|||
|
||||
VARIABLES = {
|
||||
# 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, [],
|
||||
"""Assembly file sources.
|
||||
|
||||
|
|
Загрузка…
Ссылка в новой задаче