Merge fx-team to mozilla-central

This commit is contained in:
Carsten "Tomcat" Book 2013-10-17 13:42:23 +02:00
Родитель ef3b3325fc 2973051823
Коммит 348406bdbc
64 изменённых файлов: 2567 добавлений и 1412 удалений

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

@ -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.3.jar Normal file

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

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

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