mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-10-14 22:05:44 +00:00
merge fx-team to mozilla-central
This commit is contained in:
commit
18ea819f3d
@ -36,13 +36,20 @@ function checkPreferences(prefsWin) {
|
||||
});
|
||||
});
|
||||
}
|
||||
// Same as the other one, but for in-content preferences
|
||||
function checkInContentPreferences(win) {
|
||||
let sel = win.history.state;
|
||||
let doc = win.document;
|
||||
let tab = doc.getElementById("advancedPrefs").selectedTab.id;
|
||||
is(gBrowser.currentURI.spec, "about:preferences", "about:preferences loaded");
|
||||
is(sel, "paneAdvanced", "Advanced pane was selected");
|
||||
is(tab, "networkTab", "Network tab is selected");
|
||||
// all good, we are done.
|
||||
win.close();
|
||||
finish();
|
||||
}
|
||||
|
||||
function test() {
|
||||
if (Services.prefs.getBoolPref("browser.preferences.inContent")) {
|
||||
// Bug 881576 - ensure this works with inContent prefs.
|
||||
todo(false, "Bug 881576 - this test needs to be updated for inContent prefs");
|
||||
return;
|
||||
}
|
||||
waitForExplicitFinish();
|
||||
gBrowser.selectedBrowser.addEventListener("load", function onload() {
|
||||
gBrowser.selectedBrowser.removeEventListener("load", onload, true);
|
||||
@ -55,13 +62,22 @@ function test() {
|
||||
// window to open - which we track either via a window watcher (for
|
||||
// the window-based prefs) or via an "Initialized" event (for
|
||||
// in-content prefs.)
|
||||
Services.ww.registerNotification(function wwobserver(aSubject, aTopic, aData) {
|
||||
if (aTopic != "domwindowopened")
|
||||
return;
|
||||
Services.ww.unregisterNotification(wwobserver);
|
||||
checkPreferences(aSubject);
|
||||
});
|
||||
if (!Services.prefs.getBoolPref("browser.preferences.inContent")) {
|
||||
Services.ww.registerNotification(function wwobserver(aSubject, aTopic, aData) {
|
||||
if (aTopic != "domwindowopened")
|
||||
return;
|
||||
Services.ww.unregisterNotification(wwobserver);
|
||||
checkPreferences(aSubject);
|
||||
});
|
||||
}
|
||||
PopupNotifications.panel.firstElementChild.button.click();
|
||||
if (Services.prefs.getBoolPref("browser.preferences.inContent")) {
|
||||
let newTabBrowser = gBrowser.getBrowserForTab(gBrowser.selectedTab);
|
||||
newTabBrowser.addEventListener("Initialized", function PrefInit() {
|
||||
newTabBrowser.removeEventListener("Initialized", PrefInit, true);
|
||||
checkInContentPreferences(newTabBrowser.contentWindow);
|
||||
}, true);
|
||||
}
|
||||
});
|
||||
};
|
||||
Services.prefs.setIntPref("offline-apps.quota.warn", 1);
|
||||
|
@ -100,6 +100,25 @@ function fillSubviewFromMenuItems(aMenuItems, aSubview) {
|
||||
} else if (menuChild.localName == "menuitem") {
|
||||
subviewItem = doc.createElementNS(kNSXUL, "toolbarbutton");
|
||||
CustomizableUI.addShortcut(menuChild, subviewItem);
|
||||
|
||||
let item = menuChild;
|
||||
if (!item.hasAttribute("onclick")) {
|
||||
subviewItem.addEventListener("click", event => {
|
||||
let newEvent = new doc.defaultView.MouseEvent(event.type, event);
|
||||
item.dispatchEvent(newEvent);
|
||||
});
|
||||
}
|
||||
|
||||
if (!item.hasAttribute("oncommand")) {
|
||||
subviewItem.addEventListener("command", event => {
|
||||
let newEvent = doc.createEvent("XULCommandEvent");
|
||||
newEvent.initCommandEvent(
|
||||
event.type, event.bubbles, event.cancelable, event.view,
|
||||
event.detail, event.ctrlKey, event.altKey, event.shiftKey,
|
||||
event.metaKey, event.sourceEvent);
|
||||
item.dispatchEvent(newEvent);
|
||||
});
|
||||
}
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
@ -937,4 +956,4 @@ if (Services.prefs.getBoolPref("browser.tabs.remote")) {
|
||||
onCommand: getCommandFunction(openRemote),
|
||||
});
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
|
@ -99,6 +99,7 @@ skip-if = os == "linux"
|
||||
|
||||
[browser_985815_propagate_setToolbarVisibility.js]
|
||||
[browser_981305_separator_insertion.js]
|
||||
[browser_988072_sidebar_events.js]
|
||||
[browser_989751_subviewbutton_class.js]
|
||||
[browser_987177_destroyWidget_xul.js]
|
||||
[browser_987177_xul_wrapper_updating.js]
|
||||
|
@ -0,0 +1,371 @@
|
||||
/* 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/. */
|
||||
|
||||
"use strict";
|
||||
|
||||
let gSidebarMenu = document.getElementById("viewSidebarMenu");
|
||||
let gTestSidebarItem = null;
|
||||
|
||||
let EVENTS = {
|
||||
click: 0, command: 0,
|
||||
onclick: 0, oncommand: 0
|
||||
};
|
||||
|
||||
window.sawEvent = function(event, isattr) {
|
||||
let type = (isattr ? "on" : "") + event.type
|
||||
EVENTS[type]++;
|
||||
};
|
||||
|
||||
registerCleanupFunction(() => {
|
||||
delete window.sawEvent;
|
||||
});
|
||||
|
||||
function checkExpectedEvents(expected) {
|
||||
for (let type of Object.keys(EVENTS)) {
|
||||
let count = (type in expected ? expected[type] : 0);
|
||||
is(EVENTS[type], count, "Should have seen the right number of " + type + " events");
|
||||
EVENTS[type] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
function createSidebarItem() {
|
||||
gTestSidebarItem = document.createElement("menuitem");
|
||||
gTestSidebarItem.id = "testsidebar";
|
||||
gTestSidebarItem.setAttribute("label", "Test Sidebar");
|
||||
gSidebarMenu.insertBefore(gTestSidebarItem, gSidebarMenu.firstChild);
|
||||
}
|
||||
|
||||
// Filters out the trailing menuseparators from the sidebar list
|
||||
function getSidebarList() {
|
||||
let sidebars = [...gSidebarMenu.children];
|
||||
while (sidebars[sidebars.length - 1].localName == "menuseparator")
|
||||
sidebars.pop();
|
||||
return sidebars;
|
||||
}
|
||||
|
||||
function compareElements(original, displayed) {
|
||||
let attrs = ["label", "key", "disabled", "hidden", "origin", "image", "checked"];
|
||||
for (let attr of attrs) {
|
||||
is(displayed.getAttribute(attr), original.getAttribute(attr), "Should have the same " + attr + " attribute");
|
||||
}
|
||||
}
|
||||
|
||||
function compareList(original, displayed) {
|
||||
is(displayed.length, original.length, "Should have the same number of children");
|
||||
|
||||
for (let i = 0; i < Math.min(original.length, displayed.length); i++) {
|
||||
compareElements(displayed[i], original[i]);
|
||||
}
|
||||
}
|
||||
|
||||
let showSidebarPopup = Task.async(function*() {
|
||||
let button = document.getElementById("sidebar-button");
|
||||
let subview = document.getElementById("PanelUI-sidebar");
|
||||
|
||||
let subviewShownPromise = subviewShown(subview);
|
||||
EventUtils.synthesizeMouseAtCenter(button, {});
|
||||
yield subviewShownPromise;
|
||||
return waitForCondition(() => !subview.panelMultiView.hasAttribute("transitioning"));
|
||||
});
|
||||
|
||||
// Check the sidebar widget shows the default items
|
||||
add_task(function*() {
|
||||
CustomizableUI.addWidgetToArea("sidebar-button", "nav-bar");
|
||||
|
||||
yield showSidebarPopup();
|
||||
|
||||
let sidebars = getSidebarList();
|
||||
let displayed = [...document.getElementById("PanelUI-sidebarItems").children];
|
||||
compareList(sidebars, displayed);
|
||||
|
||||
let subview = document.getElementById("PanelUI-sidebar");
|
||||
let subviewHiddenPromise = subviewHidden(subview);
|
||||
document.getElementById("customizationui-widget-panel").hidePopup();
|
||||
yield subviewHiddenPromise;
|
||||
|
||||
yield resetCustomization();
|
||||
});
|
||||
|
||||
function add_sidebar_task(description, setup, teardown) {
|
||||
add_task(function*() {
|
||||
info(description);
|
||||
createSidebarItem();
|
||||
yield setup();
|
||||
|
||||
CustomizableUI.addWidgetToArea("sidebar-button", "nav-bar");
|
||||
|
||||
yield showSidebarPopup();
|
||||
|
||||
let sidebars = getSidebarList();
|
||||
let displayed = [...document.getElementById("PanelUI-sidebarItems").children];
|
||||
compareList(sidebars, displayed);
|
||||
|
||||
is(displayed[0].label, "Test Sidebar", "Should have the right element at the top");
|
||||
let subview = document.getElementById("PanelUI-sidebar");
|
||||
let subviewHiddenPromise = subviewHidden(subview);
|
||||
EventUtils.synthesizeMouseAtCenter(displayed[0], {});
|
||||
yield subviewHiddenPromise;
|
||||
|
||||
yield teardown();
|
||||
gTestSidebarItem.remove();
|
||||
return resetCustomization();
|
||||
});
|
||||
}
|
||||
|
||||
add_sidebar_task(
|
||||
"Check that a sidebar that uses a command event listener works",
|
||||
function*() {
|
||||
gTestSidebarItem.addEventListener("command", sawEvent);
|
||||
}, function*() {
|
||||
checkExpectedEvents({ command: 1 });
|
||||
});
|
||||
|
||||
add_sidebar_task(
|
||||
"Check that a sidebar that uses a click event listener works",
|
||||
function*() {
|
||||
gTestSidebarItem.addEventListener("click", sawEvent);
|
||||
}, function*() {
|
||||
checkExpectedEvents({ click: 1 });
|
||||
});
|
||||
|
||||
add_sidebar_task(
|
||||
"Check that a sidebar that uses both click and command event listeners works",
|
||||
function*() {
|
||||
gTestSidebarItem.addEventListener("command", sawEvent);
|
||||
gTestSidebarItem.addEventListener("click", sawEvent);
|
||||
}, function*() {
|
||||
checkExpectedEvents({ command: 1, click: 1 });
|
||||
});
|
||||
|
||||
add_sidebar_task(
|
||||
"Check that a sidebar that uses an oncommand attribute works",
|
||||
function*() {
|
||||
gTestSidebarItem.setAttribute("oncommand", "sawEvent(event, true)");
|
||||
}, function*() {
|
||||
checkExpectedEvents({ oncommand: 1 });
|
||||
});
|
||||
|
||||
add_sidebar_task(
|
||||
"Check that a sidebar that uses an onclick attribute works",
|
||||
function*() {
|
||||
gTestSidebarItem.setAttribute("onclick", "sawEvent(event, true)");
|
||||
}, function*() {
|
||||
checkExpectedEvents({ onclick: 1 });
|
||||
});
|
||||
|
||||
add_sidebar_task(
|
||||
"Check that a sidebar that uses both onclick and oncommand attributes works",
|
||||
function*() {
|
||||
gTestSidebarItem.setAttribute("onclick", "sawEvent(event, true)");
|
||||
gTestSidebarItem.setAttribute("oncommand", "sawEvent(event, true)");
|
||||
}, function*() {
|
||||
checkExpectedEvents({ onclick: 1, oncommand: 1 });
|
||||
});
|
||||
|
||||
add_sidebar_task(
|
||||
"Check that a sidebar that uses an onclick attribute and a command listener works",
|
||||
function*() {
|
||||
gTestSidebarItem.setAttribute("onclick", "sawEvent(event, true)");
|
||||
gTestSidebarItem.addEventListener("command", sawEvent);
|
||||
}, function*() {
|
||||
checkExpectedEvents({ onclick: 1, command: 1 });
|
||||
});
|
||||
|
||||
add_sidebar_task(
|
||||
"Check that a sidebar that uses an oncommand attribute and a click listener works",
|
||||
function*() {
|
||||
gTestSidebarItem.setAttribute("oncommand", "sawEvent(event, true)");
|
||||
gTestSidebarItem.addEventListener("click", sawEvent);
|
||||
}, function*() {
|
||||
checkExpectedEvents({ click: 1, oncommand: 1 });
|
||||
});
|
||||
|
||||
add_sidebar_task(
|
||||
"A sidebar with both onclick attribute and click listener sees only one event :(",
|
||||
function*() {
|
||||
gTestSidebarItem.setAttribute("onclick", "sawEvent(event, true)");
|
||||
gTestSidebarItem.addEventListener("click", sawEvent);
|
||||
}, function*() {
|
||||
checkExpectedEvents({ onclick: 1 });
|
||||
});
|
||||
|
||||
add_sidebar_task(
|
||||
"A sidebar with both oncommand attribute and command listener sees only one event :(",
|
||||
function*() {
|
||||
gTestSidebarItem.setAttribute("oncommand", "sawEvent(event, true)");
|
||||
gTestSidebarItem.addEventListener("command", sawEvent);
|
||||
}, function*() {
|
||||
checkExpectedEvents({ oncommand: 1 });
|
||||
});
|
||||
|
||||
add_sidebar_task(
|
||||
"Check that a sidebar that uses a broadcaster with an oncommand attribute works",
|
||||
function*() {
|
||||
let broadcaster = document.createElement("broadcaster");
|
||||
broadcaster.setAttribute("id", "testbroadcaster");
|
||||
broadcaster.setAttribute("oncommand", "sawEvent(event, true)");
|
||||
broadcaster.setAttribute("label", "Test Sidebar");
|
||||
document.getElementById("mainBroadcasterSet").appendChild(broadcaster);
|
||||
|
||||
gTestSidebarItem.setAttribute("observes", "testbroadcaster");
|
||||
}, function*() {
|
||||
checkExpectedEvents({ oncommand: 1 });
|
||||
document.getElementById("testbroadcaster").remove();
|
||||
});
|
||||
|
||||
add_sidebar_task(
|
||||
"Check that a sidebar that uses a broadcaster with an onclick attribute works",
|
||||
function*() {
|
||||
let broadcaster = document.createElement("broadcaster");
|
||||
broadcaster.setAttribute("id", "testbroadcaster");
|
||||
broadcaster.setAttribute("onclick", "sawEvent(event, true)");
|
||||
broadcaster.setAttribute("label", "Test Sidebar");
|
||||
document.getElementById("mainBroadcasterSet").appendChild(broadcaster);
|
||||
|
||||
gTestSidebarItem.setAttribute("observes", "testbroadcaster");
|
||||
}, function*() {
|
||||
checkExpectedEvents({ onclick: 1 });
|
||||
document.getElementById("testbroadcaster").remove();
|
||||
});
|
||||
|
||||
add_sidebar_task(
|
||||
"Check that a sidebar that uses a broadcaster with both onclick and oncommand attributes works",
|
||||
function*() {
|
||||
let broadcaster = document.createElement("broadcaster");
|
||||
broadcaster.setAttribute("id", "testbroadcaster");
|
||||
broadcaster.setAttribute("onclick", "sawEvent(event, true)");
|
||||
broadcaster.setAttribute("oncommand", "sawEvent(event, true)");
|
||||
broadcaster.setAttribute("label", "Test Sidebar");
|
||||
document.getElementById("mainBroadcasterSet").appendChild(broadcaster);
|
||||
|
||||
gTestSidebarItem.setAttribute("observes", "testbroadcaster");
|
||||
}, function*() {
|
||||
checkExpectedEvents({ onclick: 1, oncommand: 1 });
|
||||
document.getElementById("testbroadcaster").remove();
|
||||
});
|
||||
|
||||
add_sidebar_task(
|
||||
"Check that a sidebar with a click listener and a broadcaster with an oncommand attribute works",
|
||||
function*() {
|
||||
let broadcaster = document.createElement("broadcaster");
|
||||
broadcaster.setAttribute("id", "testbroadcaster");
|
||||
broadcaster.setAttribute("oncommand", "sawEvent(event, true)");
|
||||
broadcaster.setAttribute("label", "Test Sidebar");
|
||||
document.getElementById("mainBroadcasterSet").appendChild(broadcaster);
|
||||
|
||||
gTestSidebarItem.setAttribute("observes", "testbroadcaster");
|
||||
gTestSidebarItem.addEventListener("click", sawEvent);
|
||||
}, function*() {
|
||||
checkExpectedEvents({ click: 1, oncommand: 1 });
|
||||
document.getElementById("testbroadcaster").remove();
|
||||
});
|
||||
|
||||
add_sidebar_task(
|
||||
"Check that a sidebar with a command listener and a broadcaster with an onclick attribute works",
|
||||
function*() {
|
||||
let broadcaster = document.createElement("broadcaster");
|
||||
broadcaster.setAttribute("id", "testbroadcaster");
|
||||
broadcaster.setAttribute("onclick", "sawEvent(event, true)");
|
||||
broadcaster.setAttribute("label", "Test Sidebar");
|
||||
document.getElementById("mainBroadcasterSet").appendChild(broadcaster);
|
||||
|
||||
gTestSidebarItem.setAttribute("observes", "testbroadcaster");
|
||||
gTestSidebarItem.addEventListener("command", sawEvent);
|
||||
}, function*() {
|
||||
checkExpectedEvents({ onclick: 1, command: 1 });
|
||||
document.getElementById("testbroadcaster").remove();
|
||||
});
|
||||
|
||||
add_sidebar_task(
|
||||
"Check that a sidebar with a click listener and a broadcaster with an onclick " +
|
||||
"attribute only sees one event :(",
|
||||
function*() {
|
||||
let broadcaster = document.createElement("broadcaster");
|
||||
broadcaster.setAttribute("id", "testbroadcaster");
|
||||
broadcaster.setAttribute("onclick", "sawEvent(event, true)");
|
||||
broadcaster.setAttribute("label", "Test Sidebar");
|
||||
document.getElementById("mainBroadcasterSet").appendChild(broadcaster);
|
||||
|
||||
gTestSidebarItem.setAttribute("observes", "testbroadcaster");
|
||||
gTestSidebarItem.addEventListener("click", sawEvent);
|
||||
}, function*() {
|
||||
checkExpectedEvents({ onclick: 1 });
|
||||
document.getElementById("testbroadcaster").remove();
|
||||
});
|
||||
|
||||
add_sidebar_task(
|
||||
"Check that a sidebar with a command listener and a broadcaster with an oncommand " +
|
||||
"attribute only sees one event :(",
|
||||
function*() {
|
||||
let broadcaster = document.createElement("broadcaster");
|
||||
broadcaster.setAttribute("id", "testbroadcaster");
|
||||
broadcaster.setAttribute("oncommand", "sawEvent(event, true)");
|
||||
broadcaster.setAttribute("label", "Test Sidebar");
|
||||
document.getElementById("mainBroadcasterSet").appendChild(broadcaster);
|
||||
|
||||
gTestSidebarItem.setAttribute("observes", "testbroadcaster");
|
||||
gTestSidebarItem.addEventListener("command", sawEvent);
|
||||
}, function*() {
|
||||
checkExpectedEvents({ oncommand: 1 });
|
||||
document.getElementById("testbroadcaster").remove();
|
||||
});
|
||||
|
||||
add_sidebar_task(
|
||||
"Check that a sidebar that uses a command element with a command event listener works",
|
||||
function*() {
|
||||
let command = document.createElement("command");
|
||||
command.setAttribute("id", "testcommand");
|
||||
document.getElementById("mainCommandSet").appendChild(command);
|
||||
command.addEventListener("command", sawEvent);
|
||||
|
||||
gTestSidebarItem.setAttribute("command", "testcommand");
|
||||
}, function*() {
|
||||
checkExpectedEvents({ command: 1 });
|
||||
document.getElementById("testcommand").remove();
|
||||
});
|
||||
|
||||
add_sidebar_task(
|
||||
"Check that a sidebar that uses a command element with an oncommand attribute works",
|
||||
function*() {
|
||||
let command = document.createElement("command");
|
||||
command.setAttribute("id", "testcommand");
|
||||
command.setAttribute("oncommand", "sawEvent(event, true)");
|
||||
document.getElementById("mainCommandSet").appendChild(command);
|
||||
|
||||
gTestSidebarItem.setAttribute("command", "testcommand");
|
||||
}, function*() {
|
||||
checkExpectedEvents({ oncommand: 1 });
|
||||
document.getElementById("testcommand").remove();
|
||||
});
|
||||
|
||||
add_sidebar_task("Check that a sidebar that uses a command element with a " +
|
||||
"command event listener and oncommand attribute works",
|
||||
function*() {
|
||||
let command = document.createElement("command");
|
||||
command.setAttribute("id", "testcommand");
|
||||
command.setAttribute("oncommand", "sawEvent(event, true)");
|
||||
document.getElementById("mainCommandSet").appendChild(command);
|
||||
command.addEventListener("command", sawEvent);
|
||||
|
||||
gTestSidebarItem.setAttribute("command", "testcommand");
|
||||
}, function*() {
|
||||
checkExpectedEvents({ command: 1, oncommand: 1 });
|
||||
document.getElementById("testcommand").remove();
|
||||
});
|
||||
|
||||
add_sidebar_task(
|
||||
"A sidebar with a command element will still see click events",
|
||||
function*() {
|
||||
let command = document.createElement("command");
|
||||
command.setAttribute("id", "testcommand");
|
||||
command.setAttribute("oncommand", "sawEvent(event, true)");
|
||||
document.getElementById("mainCommandSet").appendChild(command);
|
||||
command.addEventListener("command", sawEvent);
|
||||
|
||||
gTestSidebarItem.setAttribute("command", "testcommand");
|
||||
gTestSidebarItem.addEventListener("click", sawEvent);
|
||||
}, function*() {
|
||||
checkExpectedEvents({ click: 1, command: 1, oncommand: 1 });
|
||||
document.getElementById("testcommand").remove();
|
||||
});
|
@ -59,6 +59,9 @@
|
||||
title="&prefWindow.titleGNOME;">
|
||||
#endif
|
||||
|
||||
<html:link rel="shortcut icon"
|
||||
href="chrome://browser/skin/preferences/in-content/favicon.ico"/>
|
||||
|
||||
<script type="application/javascript"
|
||||
src="chrome://browser/content/utilityOverlay.js"/>
|
||||
<script type="application/javascript"
|
||||
|
@ -21,7 +21,7 @@
|
||||
<content>
|
||||
<xul:hbox class="notification-inner outset" flex="1" xbl:inherits="type">
|
||||
<xul:hbox anonid="details" align="center" flex="1">
|
||||
<xul:image anonid="messageImage" class="messageImage" xbl:inherits="src=image,type,value"/>
|
||||
<xul:image anonid="messageImage" class="messageImage"/>
|
||||
<xul:deck anonid="translationStates" selectedIndex="0">
|
||||
|
||||
<!-- offer to translate -->
|
||||
@ -107,6 +107,15 @@
|
||||
if (activeElt && deck.contains(activeElt))
|
||||
activeElt.blur();
|
||||
|
||||
let stateName;
|
||||
for (let name of ["OFFER", "TRANSLATING", "TRANSLATED", "ERROR"]) {
|
||||
if (this.translation["STATE_" + name] == val) {
|
||||
stateName = name.toLowerCase();
|
||||
break;
|
||||
}
|
||||
}
|
||||
this.setAttribute("state", stateName);
|
||||
|
||||
deck.selectedIndex = val;
|
||||
]]>
|
||||
</setter>
|
||||
|
@ -1223,6 +1223,7 @@ Toolbox.prototype = {
|
||||
if (this.target.isLocalTab) {
|
||||
this._requisition.destroy();
|
||||
}
|
||||
this._telemetry.toolClosed("toolbox");
|
||||
this._telemetry.destroy();
|
||||
|
||||
return this._destroyer = promise.all(outstanding).then(() => {
|
||||
|
@ -61,6 +61,8 @@ let {XPCOMUtils} = Cu.import("resource://gre/modules/XPCOMUtils.jsm", {});
|
||||
Telemetry.prototype = {
|
||||
_histograms: {
|
||||
toolbox: {
|
||||
histogram: "DEVTOOLS_TOOLBOX_OPENED_BOOLEAN",
|
||||
userHistogram: "DEVTOOLS_TOOLBOX_OPENED_PER_USER_FLAG",
|
||||
timerHistogram: "DEVTOOLS_TOOLBOX_TIME_ACTIVE_SECONDS"
|
||||
},
|
||||
options: {
|
||||
@ -212,8 +214,6 @@ Telemetry.prototype = {
|
||||
*/
|
||||
log: function(histogramId, value) {
|
||||
if (histogramId) {
|
||||
let histogram;
|
||||
|
||||
try {
|
||||
let histogram = Services.telemetry.getHistogramById(histogramId);
|
||||
histogram.add(value);
|
||||
|
@ -20,6 +20,7 @@ support-files =
|
||||
[browser_telemetry_button_scratchpad.js]
|
||||
[browser_telemetry_button_tilt.js]
|
||||
[browser_telemetry_sidebar.js]
|
||||
[browser_telemetry_toolbox.js]
|
||||
[browser_telemetry_toolboxtabs_inspector.js]
|
||||
[browser_telemetry_toolboxtabs_jsdebugger.js]
|
||||
[browser_telemetry_toolboxtabs_jsprofiler.js]
|
||||
|
@ -73,6 +73,8 @@ function checkResults() {
|
||||
if (histId.endsWith("OPENED_PER_USER_FLAG")) {
|
||||
ok(value.length === 1 && value[0] === true,
|
||||
"Per user value " + histId + " has a single value of true");
|
||||
} else if (histId === "DEVTOOLS_TOOLBOX_OPENED_BOOLEAN") {
|
||||
is(value.length, 1, histId + " has only one entry");
|
||||
} else if (histId.endsWith("OPENED_BOOLEAN")) {
|
||||
ok(value.length > 1, histId + " has more than one entry");
|
||||
|
||||
|
103
browser/devtools/shared/test/browser_telemetry_toolbox.js
Normal file
103
browser/devtools/shared/test/browser_telemetry_toolbox.js
Normal file
@ -0,0 +1,103 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
const TEST_URI = "data:text/html;charset=utf-8,<p>browser_telemetry_toolbox.js</p>";
|
||||
|
||||
// Because we need to gather stats for the period of time that a tool has been
|
||||
// opened we make use of setTimeout() to create tool active times.
|
||||
const TOOL_DELAY = 200;
|
||||
|
||||
let {Promise: promise} = Cu.import("resource://gre/modules/commonjs/sdk/core/promise.js", {});
|
||||
let {Services} = Cu.import("resource://gre/modules/Services.jsm", {});
|
||||
|
||||
let require = Cu.import("resource://gre/modules/devtools/Loader.jsm", {}).devtools.require;
|
||||
let Telemetry = require("devtools/shared/telemetry");
|
||||
|
||||
function init() {
|
||||
Telemetry.prototype.telemetryInfo = {};
|
||||
Telemetry.prototype._oldlog = Telemetry.prototype.log;
|
||||
Telemetry.prototype.log = function(histogramId, value) {
|
||||
if (histogramId) {
|
||||
if (!this.telemetryInfo[histogramId]) {
|
||||
this.telemetryInfo[histogramId] = [];
|
||||
}
|
||||
|
||||
this.telemetryInfo[histogramId].push(value);
|
||||
}
|
||||
};
|
||||
|
||||
openToolboxThreeTimes();
|
||||
}
|
||||
|
||||
let pass = 0;
|
||||
function openToolboxThreeTimes() {
|
||||
let target = TargetFactory.forTab(gBrowser.selectedTab);
|
||||
|
||||
gDevTools.showToolbox(target, "inspector").then(function(toolbox) {
|
||||
info("Toolbox opened");
|
||||
|
||||
toolbox.once("destroyed", function() {
|
||||
if (pass++ === 3) {
|
||||
checkResults();
|
||||
} else {
|
||||
openToolboxThreeTimes();
|
||||
}
|
||||
});
|
||||
// We use a timeout to check the toolbox's active time
|
||||
setTimeout(function() {
|
||||
gDevTools.closeToolbox(target);
|
||||
}, TOOL_DELAY);
|
||||
}).then(null, console.error);
|
||||
}
|
||||
|
||||
function checkResults() {
|
||||
let result = Telemetry.prototype.telemetryInfo;
|
||||
|
||||
for (let [histId, value] of Iterator(result)) {
|
||||
if (histId.endsWith("OPENED_PER_USER_FLAG")) {
|
||||
ok(value.length === 1 && value[0] === true,
|
||||
"Per user value " + histId + " has a single value of true");
|
||||
} else if (histId.endsWith("OPENED_BOOLEAN")) {
|
||||
ok(value.length > 1, histId + " has more than one entry");
|
||||
|
||||
let okay = value.every(function(element) {
|
||||
return element === true;
|
||||
});
|
||||
|
||||
ok(okay, "All " + histId + " entries are === true");
|
||||
} else if (histId.endsWith("TIME_ACTIVE_SECONDS")) {
|
||||
ok(value.length > 1, histId + " has more than one entry");
|
||||
|
||||
let okay = value.every(function(element) {
|
||||
return element > 0;
|
||||
});
|
||||
|
||||
ok(okay, "All " + histId + " entries have time > 0");
|
||||
}
|
||||
}
|
||||
|
||||
finishUp();
|
||||
}
|
||||
|
||||
function finishUp() {
|
||||
gBrowser.removeCurrentTab();
|
||||
|
||||
Telemetry.prototype.log = Telemetry.prototype._oldlog;
|
||||
delete Telemetry.prototype._oldlog;
|
||||
delete Telemetry.prototype.telemetryInfo;
|
||||
|
||||
TargetFactory = Services = promise = require = null;
|
||||
|
||||
finish();
|
||||
}
|
||||
|
||||
function test() {
|
||||
waitForExplicitFinish();
|
||||
gBrowser.selectedTab = gBrowser.addTab();
|
||||
gBrowser.selectedBrowser.addEventListener("load", function() {
|
||||
gBrowser.selectedBrowser.removeEventListener("load", arguments.callee, true);
|
||||
waitForFocus(init, content);
|
||||
}, true);
|
||||
|
||||
content.location = TEST_URI;
|
||||
}
|
@ -135,6 +135,7 @@ browser.jar:
|
||||
#endif
|
||||
* skin/classic/browser/preferences/preferences.css (preferences/preferences.css)
|
||||
* skin/classic/browser/preferences/in-content/preferences.css (preferences/in-content/preferences.css)
|
||||
skin/classic/browser/preferences/in-content/favicon.ico (../shared/incontentprefs/favicon.ico)
|
||||
skin/classic/browser/preferences/in-content/check.png (../shared/incontentprefs/check.png)
|
||||
skin/classic/browser/preferences/in-content/check@2x.png (../shared/incontentprefs/check@2x.png)
|
||||
skin/classic/browser/preferences/in-content/icons.png (../shared/incontentprefs/icons.png)
|
||||
@ -180,6 +181,7 @@ browser.jar:
|
||||
skin/classic/browser/tabview/stack-expander.png (tabview/stack-expander.png)
|
||||
skin/classic/browser/tabview/tabview.png (tabview/tabview.png)
|
||||
skin/classic/browser/tabview/tabview.css (tabview/tabview.css)
|
||||
skin/classic/browser/translating-16.png (../shared/translation/translating-16.png)
|
||||
skin/classic/browser/translation-16.png (../shared/translation/translation-16.png)
|
||||
* skin/classic/browser/devtools/common.css (../shared/devtools/common.css)
|
||||
* skin/classic/browser/devtools/dark-theme.css (../shared/devtools/dark-theme.css)
|
||||
|
@ -225,6 +225,7 @@ browser.jar:
|
||||
skin/classic/browser/preferences/saveFile.png (preferences/saveFile.png)
|
||||
* skin/classic/browser/preferences/preferences.css (preferences/preferences.css)
|
||||
* skin/classic/browser/preferences/in-content/preferences.css (preferences/in-content/preferences.css)
|
||||
skin/classic/browser/preferences/in-content/favicon.ico (../shared/incontentprefs/favicon.ico)
|
||||
skin/classic/browser/preferences/in-content/check.png (../shared/incontentprefs/check.png)
|
||||
skin/classic/browser/preferences/in-content/check@2x.png (../shared/incontentprefs/check@2x.png)
|
||||
skin/classic/browser/preferences/in-content/icons.png (../shared/incontentprefs/icons.png)
|
||||
@ -297,6 +298,8 @@ browser.jar:
|
||||
skin/classic/browser/tabview/stack-expander.png (tabview/stack-expander.png)
|
||||
skin/classic/browser/tabview/tabview.png (tabview/tabview.png)
|
||||
skin/classic/browser/tabview/tabview.css (tabview/tabview.css)
|
||||
skin/classic/browser/translating-16.png (../shared/translation/translating-16.png)
|
||||
skin/classic/browser/translating-16@2x.png (../shared/translation/translating-16@2x.png)
|
||||
skin/classic/browser/translation-16.png (../shared/translation/translation-16.png)
|
||||
skin/classic/browser/translation-16@2x.png (../shared/translation/translation-16@2x.png)
|
||||
* skin/classic/browser/devtools/common.css (../shared/devtools/common.css)
|
||||
|
BIN
browser/themes/shared/incontentprefs/favicon.ico
Normal file
BIN
browser/themes/shared/incontentprefs/favicon.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 933 B |
@ -15,6 +15,17 @@ notification[value="translation"] .messageImage {
|
||||
}
|
||||
}
|
||||
|
||||
notification[value="translation"][state="translating"] .messageImage {
|
||||
list-style-image: url(chrome://browser/skin/translating-16.png);
|
||||
-moz-image-region: auto;
|
||||
}
|
||||
|
||||
@media (min-resolution: 1.25dppx) {
|
||||
notification[value="translation"][state="translating"] .messageImage {
|
||||
list-style-image: url(chrome://browser/skin/translating-16@2x.png);
|
||||
}
|
||||
}
|
||||
|
||||
notification[value="translation"] {
|
||||
min-height: 40px;
|
||||
}
|
||||
|
BIN
browser/themes/shared/translation/translating-16.png
Normal file
BIN
browser/themes/shared/translation/translating-16.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 21 KiB |
BIN
browser/themes/shared/translation/translating-16@2x.png
Normal file
BIN
browser/themes/shared/translation/translating-16@2x.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 29 KiB |
@ -160,6 +160,7 @@ browser.jar:
|
||||
skin/classic/browser/preferences/saveFile.png (preferences/saveFile.png)
|
||||
* skin/classic/browser/preferences/preferences.css (preferences/preferences.css)
|
||||
* skin/classic/browser/preferences/in-content/preferences.css (preferences/in-content/preferences.css)
|
||||
skin/classic/browser/preferences/in-content/favicon.ico (../shared/incontentprefs/favicon.ico)
|
||||
skin/classic/browser/preferences/in-content/check.png (../shared/incontentprefs/check.png)
|
||||
skin/classic/browser/preferences/in-content/check@2x.png (../shared/incontentprefs/check@2x.png)
|
||||
skin/classic/browser/preferences/in-content/icons.png (../shared/incontentprefs/icons.png)
|
||||
@ -217,6 +218,7 @@ browser.jar:
|
||||
skin/classic/browser/tabview/tabview.png (tabview/tabview.png)
|
||||
skin/classic/browser/tabview/tabview-inverted.png (tabview/tabview-inverted.png)
|
||||
skin/classic/browser/tabview/tabview.css (tabview/tabview.css)
|
||||
skin/classic/browser/translating-16.png (../shared/translation/translating-16.png)
|
||||
skin/classic/browser/translation-16.png (../shared/translation/translation-16.png)
|
||||
* skin/classic/browser/devtools/common.css (../shared/devtools/common.css)
|
||||
* skin/classic/browser/devtools/dark-theme.css (../shared/devtools/dark-theme.css)
|
||||
@ -551,6 +553,7 @@ browser.jar:
|
||||
skin/classic/aero/browser/preferences/saveFile.png (preferences/saveFile-aero.png)
|
||||
* skin/classic/aero/browser/preferences/preferences.css (preferences/preferences.css)
|
||||
* skin/classic/aero/browser/preferences/in-content/preferences.css (preferences/in-content/preferences.css)
|
||||
skin/classic/aero/browser/preferences/in-content/favicon.ico (../shared/incontentprefs/favicon.ico)
|
||||
skin/classic/aero/browser/preferences/in-content/check.png (../shared/incontentprefs/check.png)
|
||||
skin/classic/aero/browser/preferences/in-content/check@2x.png (../shared/incontentprefs/check@2x.png)
|
||||
skin/classic/aero/browser/preferences/in-content/icons.png (../shared/incontentprefs/icons.png)
|
||||
@ -607,6 +610,7 @@ browser.jar:
|
||||
skin/classic/aero/browser/tabview/tabview.png (tabview/tabview.png)
|
||||
skin/classic/aero/browser/tabview/tabview-inverted.png (tabview/tabview-inverted.png)
|
||||
skin/classic/aero/browser/tabview/tabview.css (tabview/tabview.css)
|
||||
skin/classic/aero/browser/translating-16.png (../shared/translation/translating-16.png)
|
||||
skin/classic/aero/browser/translation-16.png (../shared/translation/translation-16.png)
|
||||
* skin/classic/aero/browser/devtools/common.css (../shared/devtools/common.css)
|
||||
* skin/classic/aero/browser/devtools/dark-theme.css (../shared/devtools/dark-theme.css)
|
||||
|
@ -15,18 +15,16 @@ import java.util.Vector;
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
import org.mozilla.gecko.AndroidGamepadManager;
|
||||
import org.mozilla.gecko.DynamicToolbar.PinReason;
|
||||
import org.mozilla.gecko.DynamicToolbar.VisibilityTransition;
|
||||
import org.mozilla.gecko.GeckoProfileDirectories.NoMozillaDirectoryException;
|
||||
import org.mozilla.gecko.Telemetry;
|
||||
import org.mozilla.gecko.TelemetryContract;
|
||||
import org.mozilla.gecko.animation.PropertyAnimator;
|
||||
import org.mozilla.gecko.animation.ViewHelper;
|
||||
import org.mozilla.gecko.db.BrowserContract.Combined;
|
||||
import org.mozilla.gecko.db.BrowserContract.ReadingListItems;
|
||||
import org.mozilla.gecko.db.BrowserDB;
|
||||
import org.mozilla.gecko.db.SuggestedSites;
|
||||
import org.mozilla.gecko.distribution.Distribution;
|
||||
import org.mozilla.gecko.favicons.Favicons;
|
||||
import org.mozilla.gecko.favicons.LoadFaviconTask;
|
||||
import org.mozilla.gecko.favicons.OnFaviconLoadedListener;
|
||||
@ -43,9 +41,9 @@ import org.mozilla.gecko.health.HealthRecorder;
|
||||
import org.mozilla.gecko.health.SessionInformation;
|
||||
import org.mozilla.gecko.home.BrowserSearch;
|
||||
import org.mozilla.gecko.home.HomeBanner;
|
||||
import org.mozilla.gecko.home.HomePanelsManager;
|
||||
import org.mozilla.gecko.home.HomePager;
|
||||
import org.mozilla.gecko.home.HomePager.OnUrlOpenListener;
|
||||
import org.mozilla.gecko.home.HomePanelsManager;
|
||||
import org.mozilla.gecko.home.SearchEngine;
|
||||
import org.mozilla.gecko.menu.GeckoMenu;
|
||||
import org.mozilla.gecko.menu.GeckoMenuItem;
|
||||
@ -108,8 +106,8 @@ import android.view.ViewStub;
|
||||
import android.view.ViewTreeObserver;
|
||||
import android.view.Window;
|
||||
import android.view.animation.Interpolator;
|
||||
import android.widget.RelativeLayout;
|
||||
import android.widget.ListView;
|
||||
import android.widget.RelativeLayout;
|
||||
import android.widget.Toast;
|
||||
import android.widget.ViewFlipper;
|
||||
|
||||
@ -1306,8 +1304,10 @@ abstract public class BrowserApp extends GeckoApp
|
||||
ThreadUtils.postToUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (menu != null)
|
||||
if (menu != null) {
|
||||
menu.findItem(R.id.settings).setEnabled(true);
|
||||
menu.findItem(R.id.help).setEnabled(true);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@ -2282,8 +2282,10 @@ abstract public class BrowserApp extends GeckoApp
|
||||
if (aMenu == null)
|
||||
return false;
|
||||
|
||||
if (!GeckoThread.checkLaunchState(GeckoThread.LaunchState.GeckoRunning))
|
||||
if (!GeckoThread.checkLaunchState(GeckoThread.LaunchState.GeckoRunning)) {
|
||||
aMenu.findItem(R.id.settings).setEnabled(false);
|
||||
aMenu.findItem(R.id.help).setEnabled(false);
|
||||
}
|
||||
|
||||
Tab tab = Tabs.getInstance().getSelectedTab();
|
||||
MenuItem bookmark = aMenu.findItem(R.id.bookmark);
|
||||
@ -2496,6 +2498,16 @@ abstract public class BrowserApp extends GeckoApp
|
||||
return true;
|
||||
}
|
||||
|
||||
if (itemId == R.id.help) {
|
||||
final String VERSION = AppConstants.MOZ_APP_VERSION;
|
||||
final String OS = AppConstants.OS_TARGET;
|
||||
final String LOCALE = BrowserLocaleManager.getLanguageTag(Locale.getDefault());
|
||||
|
||||
final String URL = getResources().getString(R.string.help_link, VERSION, OS, LOCALE);
|
||||
Tabs.getInstance().loadUrlInTab(URL);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (itemId == R.id.addons) {
|
||||
Tabs.getInstance().loadUrlInTab(AboutPages.ADDONS);
|
||||
return true;
|
||||
|
@ -5,15 +5,6 @@
|
||||
|
||||
package org.mozilla.gecko;
|
||||
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.res.Configuration;
|
||||
import android.content.res.Resources;
|
||||
import android.util.Log;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
@ -27,6 +18,15 @@ import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
import org.mozilla.gecko.util.GeckoJarReader;
|
||||
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.res.Configuration;
|
||||
import android.content.res.Resources;
|
||||
import android.util.Log;
|
||||
|
||||
/**
|
||||
* This class manages persistence, application, and otherwise handling of
|
||||
* user-specified locales.
|
||||
@ -132,6 +132,8 @@ public class BrowserLocaleManager implements LocaleManager {
|
||||
receiver = new BroadcastReceiver() {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
final Locale current = systemLocale;
|
||||
|
||||
// We don't trust Locale.getDefault() here, because we make a
|
||||
// habit of mutating it! Use the one Android supplies, because
|
||||
// that gets regularly reset.
|
||||
@ -139,6 +141,8 @@ public class BrowserLocaleManager implements LocaleManager {
|
||||
// yet swizzled Locale during static initialization.
|
||||
systemLocale = context.getResources().getConfiguration().locale;
|
||||
systemLocaleDidChange = true;
|
||||
|
||||
Log.d(LOG_TAG, "System locale changed from " + current + " to " + systemLocale);
|
||||
}
|
||||
};
|
||||
context.registerReceiver(receiver, new IntentFilter(Intent.ACTION_LOCALE_CHANGED));
|
||||
@ -157,6 +161,7 @@ public class BrowserLocaleManager implements LocaleManager {
|
||||
public void correctLocale(Context context, Resources res, Configuration config) {
|
||||
final Locale current = getCurrentLocale(context);
|
||||
if (current == null) {
|
||||
Log.d(LOG_TAG, "No selected locale. No correction needed.");
|
||||
return;
|
||||
}
|
||||
|
||||
@ -180,6 +185,48 @@ public class BrowserLocaleManager implements LocaleManager {
|
||||
res.updateConfiguration(config, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* We can be in one of two states.
|
||||
*
|
||||
* If the user has not explicitly chosen a Firefox-specific locale, we say
|
||||
* we are "mirroring" the system locale.
|
||||
*
|
||||
* When we are not mirroring, system locale changes do not impact Firefox
|
||||
* and are essentially ignored; the user's locale selection is the only
|
||||
* thing we care about, and we actively correct incoming configuration
|
||||
* changes to reflect the user's chosen locale.
|
||||
*
|
||||
* By contrast, when we are mirroring, system locale changes cause Firefox
|
||||
* to reflect the new system locale, as if the user picked the new locale.
|
||||
*
|
||||
* If we're currently mirroring the system locale, this method returns the
|
||||
* supplied configuration's locale, unless the current activity locale is
|
||||
* correct. , If we're not currently mirroring, this methodupdates the
|
||||
* configuration object to match the user's currently selected locale, and
|
||||
* returns that, unless the current activity locale is correct.
|
||||
*
|
||||
* If the current activity locale is correct, returns null.
|
||||
*
|
||||
* The caller is expected to redisplay themselves accordingly.
|
||||
*
|
||||
* This method is intended to be called from inside
|
||||
* <code>onConfigurationChanged(Configuration)</code> as part of a strategy
|
||||
* to detect and either apply or undo system locale changes.
|
||||
*/
|
||||
@Override
|
||||
public Locale onSystemConfigurationChanged(final Context context, final Resources resources, final Configuration configuration, final Locale currentActivityLocale) {
|
||||
if (!isMirroringSystemLocale(context)) {
|
||||
correctLocale(context, resources, configuration);
|
||||
}
|
||||
|
||||
final Locale changed = configuration.locale;
|
||||
if (changed.equals(currentActivityLocale)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return changed;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getAndApplyPersistedLocale(Context context) {
|
||||
initialize(context);
|
||||
@ -323,6 +370,10 @@ public class BrowserLocaleManager implements LocaleManager {
|
||||
return locale.toString();
|
||||
}
|
||||
|
||||
private boolean isMirroringSystemLocale(final Context context) {
|
||||
return getPersistedLocale(context) == null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Examines <code>multilocale.json</code>, returning the included list of
|
||||
* locale codes.
|
||||
|
@ -16,7 +16,6 @@ import java.net.URL;
|
||||
import java.text.DateFormat;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
@ -209,6 +208,7 @@ public abstract class GeckoApp
|
||||
private String mPrivateBrowsingSession;
|
||||
|
||||
private volatile HealthRecorder mHealthRecorder = null;
|
||||
private volatile Locale mLastLocale = null;
|
||||
|
||||
private int mSignalStrenth;
|
||||
private PhoneStateListener mPhoneStateListener = null;
|
||||
@ -1277,9 +1277,10 @@ public abstract class GeckoApp
|
||||
public void run() {
|
||||
final SharedPreferences prefs = GeckoApp.this.getSharedPreferences();
|
||||
|
||||
// Wait until now to set this, because we'd rather throw an exception than
|
||||
// Wait until now to set this, because we'd rather throw an exception than
|
||||
// have a caller of BrowserLocaleManager regress startup.
|
||||
BrowserLocaleManager.getInstance().initialize(getApplicationContext());
|
||||
final LocaleManager localeManager = BrowserLocaleManager.getInstance();
|
||||
localeManager.initialize(getApplicationContext());
|
||||
|
||||
SessionInformation previousSession = SessionInformation.fromSharedPrefs(prefs);
|
||||
if (previousSession.wasKilled()) {
|
||||
@ -1303,7 +1304,7 @@ public abstract class GeckoApp
|
||||
Log.i(LOGTAG, "Creating HealthRecorder.");
|
||||
|
||||
final String osLocale = Locale.getDefault().toString();
|
||||
String appLocale = BrowserLocaleManager.getInstance().getAndApplyPersistedLocale(GeckoApp.this);
|
||||
String appLocale = localeManager.getAndApplyPersistedLocale(GeckoApp.this);
|
||||
Log.d(LOGTAG, "OS locale is " + osLocale + ", app locale is " + appLocale);
|
||||
|
||||
if (appLocale == null) {
|
||||
@ -1351,6 +1352,11 @@ public abstract class GeckoApp
|
||||
throw new RuntimeException("onLocaleReady must always be called from the UI thread.");
|
||||
}
|
||||
|
||||
final Locale loc = BrowserLocaleManager.parseLocaleCode(locale);
|
||||
if (loc.equals(mLastLocale)) {
|
||||
Log.d(LOGTAG, "New locale same as old; onLocaleReady has nothing to do.");
|
||||
}
|
||||
|
||||
// The URL bar hint needs to be populated.
|
||||
TextView urlBar = (TextView) findViewById(R.id.url_bar_title);
|
||||
if (urlBar != null) {
|
||||
@ -1360,8 +1366,13 @@ public abstract class GeckoApp
|
||||
Log.d(LOGTAG, "No URL bar in GeckoApp. Not loading localized hint string.");
|
||||
}
|
||||
|
||||
mLastLocale = loc;
|
||||
|
||||
// Allow onConfigurationChanged to take care of the rest.
|
||||
onConfigurationChanged(getResources().getConfiguration());
|
||||
// We don't call this.onConfigurationChanged, because (a) that does
|
||||
// work that's unnecessary after this locale action, and (b) it can
|
||||
// cause a loop! See Bug 1011008, Comment 12.
|
||||
super.onConfigurationChanged(getResources().getConfiguration());
|
||||
}
|
||||
|
||||
protected void initializeChrome() {
|
||||
@ -2155,7 +2166,12 @@ public abstract class GeckoApp
|
||||
@Override
|
||||
public void onConfigurationChanged(Configuration newConfig) {
|
||||
Log.d(LOGTAG, "onConfigurationChanged: " + newConfig.locale);
|
||||
BrowserLocaleManager.getInstance().correctLocale(this, getResources(), newConfig);
|
||||
|
||||
final LocaleManager localeManager = BrowserLocaleManager.getInstance();
|
||||
final Locale changed = localeManager.onSystemConfigurationChanged(this, getResources(), newConfig, mLastLocale);
|
||||
if (changed != null) {
|
||||
onLocaleChanged(BrowserLocaleManager.getLanguageTag(changed));
|
||||
}
|
||||
|
||||
// onConfigurationChanged is not called for 180 degree orientation changes,
|
||||
// we will miss such rotations and the screen orientation will not be
|
||||
|
@ -10,6 +10,12 @@ import android.content.Context;
|
||||
import android.content.res.Configuration;
|
||||
import android.content.res.Resources;
|
||||
|
||||
/**
|
||||
* Implement this interface to provide Fennec's locale switching functionality.
|
||||
*
|
||||
* The LocaleManager is responsible for persisting and applying selected locales,
|
||||
* and correcting configurations after Android has changed them.
|
||||
*/
|
||||
public interface LocaleManager {
|
||||
void initialize(Context context);
|
||||
Locale getCurrentLocale(Context context);
|
||||
@ -19,4 +25,12 @@ public interface LocaleManager {
|
||||
String setSelectedLocale(Context context, String localeCode);
|
||||
boolean systemLocaleDidChange();
|
||||
void resetToSystemLocale(Context context);
|
||||
|
||||
/**
|
||||
* Call this in your onConfigurationChanged handler. This method is expected
|
||||
* to do the appropriate thing: if the user has selected a locale, it
|
||||
* corrects the incoming configuration; if not, it signals the new locale to
|
||||
* use.
|
||||
*/
|
||||
Locale onSystemConfigurationChanged(Context context, Resources resources, Configuration configuration, Locale currentActivityLocale);
|
||||
}
|
||||
|
@ -557,7 +557,6 @@ sync_java_files = [
|
||||
'fxa/activities/FxAccountCreateAccountActivity.java',
|
||||
'fxa/activities/FxAccountCreateAccountNotAllowedActivity.java',
|
||||
'fxa/activities/FxAccountGetStartedActivity.java',
|
||||
'fxa/activities/FxAccountSetupTask.java',
|
||||
'fxa/activities/FxAccountSignInActivity.java',
|
||||
'fxa/activities/FxAccountStatusActivity.java',
|
||||
'fxa/activities/FxAccountStatusFragment.java',
|
||||
@ -591,6 +590,10 @@ sync_java_files = [
|
||||
'fxa/sync/FxAccountSyncService.java',
|
||||
'fxa/sync/FxAccountSyncStatusHelper.java',
|
||||
'fxa/sync/SchedulePolicy.java',
|
||||
'fxa/tasks/FxAccountCodeResender.java',
|
||||
'fxa/tasks/FxAccountCreateAccountTask.java',
|
||||
'fxa/tasks/FxAccountSetupTask.java',
|
||||
'fxa/tasks/FxAccountSignInTask.java',
|
||||
'sync/AlreadySyncingException.java',
|
||||
'sync/BackoffHandler.java',
|
||||
'sync/BadRequiredFieldJSONException.java',
|
||||
|
@ -6,7 +6,6 @@ package org.mozilla.gecko.background.healthreport;
|
||||
|
||||
import org.mozilla.gecko.background.healthreport.Environment.UIType;
|
||||
import org.mozilla.gecko.background.healthreport.EnvironmentBuilder.ConfigurationProvider;
|
||||
import org.mozilla.gecko.sync.jpake.stage.GetRequestStage.GetStepTimerTask;
|
||||
import org.mozilla.gecko.util.HardwareUtils;
|
||||
|
||||
import android.content.Context;
|
||||
|
@ -18,7 +18,6 @@ import org.json.JSONArray;
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
import org.mozilla.gecko.AppConstants;
|
||||
import org.mozilla.gecko.Distribution;
|
||||
import org.mozilla.gecko.R;
|
||||
import org.mozilla.gecko.db.BrowserContract.Bookmarks;
|
||||
import org.mozilla.gecko.db.BrowserContract.Combined;
|
||||
@ -28,6 +27,7 @@ import org.mozilla.gecko.db.BrowserContract.History;
|
||||
import org.mozilla.gecko.db.BrowserContract.Obsolete;
|
||||
import org.mozilla.gecko.db.BrowserContract.ReadingListItems;
|
||||
import org.mozilla.gecko.db.BrowserContract.Thumbnails;
|
||||
import org.mozilla.gecko.distribution.Distribution;
|
||||
import org.mozilla.gecko.gfx.BitmapUtils;
|
||||
import org.mozilla.gecko.sync.Utils;
|
||||
import org.mozilla.gecko.util.GeckoJarReader;
|
||||
|
@ -3,21 +3,10 @@
|
||||
* 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;
|
||||
|
||||
import org.mozilla.gecko.mozglue.RobocopTarget;
|
||||
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;
|
||||
package org.mozilla.gecko.distribution;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
@ -27,10 +16,30 @@ import java.util.Enumeration;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
import java.util.Queue;
|
||||
import java.util.Scanner;
|
||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipFile;
|
||||
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
import org.mozilla.gecko.GeckoAppShell;
|
||||
import org.mozilla.gecko.GeckoEvent;
|
||||
import org.mozilla.gecko.GeckoSharedPrefs;
|
||||
import org.mozilla.gecko.mozglue.RobocopTarget;
|
||||
import org.mozilla.gecko.util.ThreadUtils;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.util.Log;
|
||||
|
||||
/**
|
||||
* Handles distribution file loading and fetching,
|
||||
* and the corresponding hand-offs to Gecko.
|
||||
*/
|
||||
public final class Distribution {
|
||||
private static final String LOGTAG = "GeckoDistribution";
|
||||
|
||||
@ -38,6 +47,29 @@ public final class Distribution {
|
||||
private static final int STATE_NONE = 1;
|
||||
private static final int STATE_SET = 2;
|
||||
|
||||
private static Distribution instance;
|
||||
|
||||
private final Context context;
|
||||
private final String packagePath;
|
||||
private final String prefsBranch;
|
||||
|
||||
private volatile int state = STATE_UNKNOWN;
|
||||
private File distributionDir = null;
|
||||
|
||||
private final Queue<Runnable> onDistributionReady = new ConcurrentLinkedQueue<Runnable>();
|
||||
|
||||
/**
|
||||
* This is a little bit of a bad singleton, because in principle a Distribution
|
||||
* can be created with arbitrary paths. So we only have one path to get here, and
|
||||
* it uses the default arguments. Watch out if you're creating your own instances!
|
||||
*/
|
||||
public static synchronized Distribution getInstance(Context context) {
|
||||
if (instance == null) {
|
||||
instance = new Distribution(context);
|
||||
}
|
||||
return instance;
|
||||
}
|
||||
|
||||
public static class DistributionDescriptor {
|
||||
public final boolean valid;
|
||||
public final String id;
|
||||
@ -80,20 +112,12 @@ public final class Distribution {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes distribution if it hasn't already been initalized. Sends
|
||||
* messages to Gecko as appropriate.
|
||||
*
|
||||
* @param packagePath where to look for the distribution directory.
|
||||
*/
|
||||
@RobocopTarget
|
||||
public static void init(final Context context, final String packagePath, final String prefsPath) {
|
||||
private static void init(final Distribution distribution) {
|
||||
// Read/write preferences and files on the background thread.
|
||||
ThreadUtils.postToBackgroundThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
Distribution dist = new Distribution(context, packagePath, prefsPath);
|
||||
boolean distributionSet = dist.doInit();
|
||||
boolean distributionSet = distribution.doInit();
|
||||
if (distributionSet) {
|
||||
GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("Distribution:Set", ""));
|
||||
}
|
||||
@ -101,12 +125,23 @@ public final class Distribution {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes distribution if it hasn't already been initialized. Sends
|
||||
* messages to Gecko as appropriate.
|
||||
*
|
||||
* @param packagePath where to look for the distribution directory.
|
||||
*/
|
||||
@RobocopTarget
|
||||
public static void init(final Context context, final String packagePath, final String prefsPath) {
|
||||
init(new Distribution(context, packagePath, prefsPath));
|
||||
}
|
||||
|
||||
/**
|
||||
* Use <code>Context.getPackageResourcePath</code> to find an implicit
|
||||
* package path.
|
||||
* package path. Reuses the existing Distribution if one exists.
|
||||
*/
|
||||
public static void init(final Context context) {
|
||||
Distribution.init(context, context.getPackageResourcePath(), null);
|
||||
Distribution.init(Distribution.getInstance(context));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -118,13 +153,6 @@ public final class Distribution {
|
||||
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.
|
||||
*/
|
||||
@ -138,142 +166,6 @@ public final class Distribution {
|
||||
this(context, context.getPackageResourcePath(), null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
final SharedPreferences settings;
|
||||
if (prefsBranch == null) {
|
||||
settings = GeckoSharedPrefs.forApp(context);
|
||||
} else {
|
||||
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 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/")) {
|
||||
continue;
|
||||
}
|
||||
|
||||
distributionSet = true;
|
||||
|
||||
File outFile = new File(getDataDir(), name);
|
||||
File dir = outFile.getParentFile();
|
||||
|
||||
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 count;
|
||||
while ((count = fileStream.read(buffer)) != -1) {
|
||||
outStream.write(buffer, 0, count);
|
||||
}
|
||||
|
||||
fileStream.close();
|
||||
outStream.close();
|
||||
outFile.setLastModified(fileEntry.getTime());
|
||||
}
|
||||
|
||||
zip.close();
|
||||
|
||||
return distributionSet;
|
||||
}
|
||||
|
||||
/**
|
||||
* After calling this method, either <code>distributionDir</code>
|
||||
* will be set, or there is no distribution in use.
|
||||
*
|
||||
* Only call after init.
|
||||
*/
|
||||
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.
|
||||
*
|
||||
@ -281,7 +173,8 @@ public final class Distribution {
|
||||
* doesn't exist. Ensures init first.
|
||||
*/
|
||||
public File getDistributionFile(String name) {
|
||||
Log.i(LOGTAG, "Getting file from distribution.");
|
||||
Log.d(LOGTAG, "Getting file from distribution.");
|
||||
|
||||
if (this.state == STATE_UNKNOWN) {
|
||||
if (!this.doInit()) {
|
||||
return null;
|
||||
@ -346,6 +239,210 @@ public final class Distribution {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Don't call from the main thread.
|
||||
*
|
||||
* Postcondition: if this returns true, distributionDir will have been
|
||||
* set and populated.
|
||||
*
|
||||
* @return true if we've set a distribution.
|
||||
*/
|
||||
private boolean doInit() {
|
||||
ThreadUtils.assertNotOnUiThread();
|
||||
|
||||
// Bail if we've already tried to initialize the distribution, and
|
||||
// there wasn't one.
|
||||
final SharedPreferences settings;
|
||||
if (prefsBranch == null) {
|
||||
settings = GeckoSharedPrefs.forApp(context);
|
||||
} else {
|
||||
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) {
|
||||
runReadyQueue();
|
||||
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.
|
||||
runReadyQueue();
|
||||
return true;
|
||||
}
|
||||
|
||||
// We try the APK, then the system directory.
|
||||
final boolean distributionSet =
|
||||
checkAPKDistribution() ||
|
||||
checkSystemDistribution();
|
||||
|
||||
this.state = distributionSet ? STATE_SET : STATE_NONE;
|
||||
settings.edit().putInt(keyName, this.state).commit();
|
||||
|
||||
runReadyQueue();
|
||||
return distributionSet;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute tasks that wanted to run when we were done loading
|
||||
* the distribution. These tasks are expected to call {@link #exists()}
|
||||
* to find out whether there's a distribution or not.
|
||||
*/
|
||||
private void runReadyQueue() {
|
||||
Runnable task;
|
||||
while ((task = onDistributionReady.poll()) != null) {
|
||||
ThreadUtils.postToBackgroundThread(task);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return true if we copied files out of the APK. Sets distributionDir in that case.
|
||||
*/
|
||||
private boolean checkAPKDistribution() {
|
||||
try {
|
||||
// First, try copying distribution files out of the APK.
|
||||
if (copyFiles()) {
|
||||
// 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/");
|
||||
return true;
|
||||
}
|
||||
} catch (IOException e) {
|
||||
Log.e(LOGTAG, "Error copying distribution files from APK.", e);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return true if we found a system distribution. Sets distributionDir in that case.
|
||||
*/
|
||||
private boolean checkSystemDistribution() {
|
||||
// If there aren't any distribution files in the APK, look in the /system directory.
|
||||
final File distDir = getSystemDistributionDir();
|
||||
if (distDir.exists()) {
|
||||
this.distributionDir = distDir;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 boolean copyFiles() throws IOException {
|
||||
final File applicationPackage = new File(packagePath);
|
||||
final ZipFile zip = new ZipFile(applicationPackage);
|
||||
|
||||
boolean distributionSet = false;
|
||||
try {
|
||||
final byte[] buffer = new byte[1024];
|
||||
|
||||
final Enumeration<? extends ZipEntry> zipEntries = zip.entries();
|
||||
while (zipEntries.hasMoreElements()) {
|
||||
final ZipEntry fileEntry = zipEntries.nextElement();
|
||||
final String name = fileEntry.getName();
|
||||
|
||||
if (fileEntry.isDirectory()) {
|
||||
// We'll let getDataFile deal with creating the directory hierarchy.
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!name.startsWith("distribution/")) {
|
||||
continue;
|
||||
}
|
||||
|
||||
final File outFile = getDataFile(name);
|
||||
if (outFile == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
distributionSet = true;
|
||||
|
||||
final InputStream fileStream = zip.getInputStream(fileEntry);
|
||||
try {
|
||||
writeStream(fileStream, outFile, fileEntry.getTime(), buffer);
|
||||
} finally {
|
||||
fileStream.close();
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
zip.close();
|
||||
}
|
||||
|
||||
return distributionSet;
|
||||
}
|
||||
|
||||
private void writeStream(InputStream fileStream, File outFile, final long modifiedTime, byte[] buffer)
|
||||
throws FileNotFoundException, IOException {
|
||||
final OutputStream outStream = new FileOutputStream(outFile);
|
||||
try {
|
||||
int count;
|
||||
while ((count = fileStream.read(buffer)) > 0) {
|
||||
outStream.write(buffer, 0, count);
|
||||
}
|
||||
|
||||
outFile.setLastModified(modifiedTime);
|
||||
} finally {
|
||||
outStream.close();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a File instance in the data directory, ensuring
|
||||
* that the parent exists.
|
||||
*
|
||||
* @return null if the parents could not be created.
|
||||
*/
|
||||
private File getDataFile(final String name) {
|
||||
File outFile = new File(getDataDir(), name);
|
||||
File dir = outFile.getParentFile();
|
||||
|
||||
if (!dir.exists()) {
|
||||
Log.d(LOGTAG, "Creating " + dir.getAbsolutePath());
|
||||
if (!dir.mkdirs()) {
|
||||
Log.e(LOGTAG, "Unable to create directories: " + dir.getAbsolutePath());
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
return outFile;
|
||||
}
|
||||
|
||||
/**
|
||||
* After calling this method, either <code>distributionDir</code>
|
||||
* will be set, or there is no distribution in use.
|
||||
*
|
||||
* Only call after init.
|
||||
*/
|
||||
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;
|
||||
}
|
||||
|
||||
// Shortcut to slurp a file without messing around with streams.
|
||||
private String getFileContents(File file) throws IOException {
|
||||
Scanner scanner = null;
|
||||
@ -366,4 +463,27 @@ public final class Distribution {
|
||||
private File getSystemDistributionDir() {
|
||||
return new File("/system/" + context.getPackageName() + "/distribution");
|
||||
}
|
||||
|
||||
/**
|
||||
* The provided <code>Runnable</code> will be executed after the distribution
|
||||
* is ready, or discarded if the distribution has already been processed.
|
||||
*
|
||||
* Each <code>Runnable</code> will be executed on the background thread.
|
||||
*/
|
||||
public void addOnDistributionReadyCallback(Runnable runnable) {
|
||||
if (state == STATE_UNKNOWN) {
|
||||
this.onDistributionReady.add(runnable);
|
||||
} else {
|
||||
// If we're already initialized, just queue up the runnable.
|
||||
ThreadUtils.postToBackgroundThread(runnable);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A safe way for callers to determine if this Distribution instance
|
||||
* represents a real live distribution.
|
||||
*/
|
||||
public boolean exists() {
|
||||
return state == STATE_SET;
|
||||
}
|
||||
}
|
@ -14,8 +14,10 @@ import org.mozilla.gecko.R;
|
||||
import org.mozilla.gecko.background.common.log.Logger;
|
||||
import org.mozilla.gecko.fxa.authenticator.AccountPickler;
|
||||
import org.mozilla.gecko.fxa.authenticator.AndroidFxAccount;
|
||||
import org.mozilla.gecko.fxa.login.State;
|
||||
import org.mozilla.gecko.fxa.sync.FxAccountSyncAdapter;
|
||||
import org.mozilla.gecko.fxa.sync.FxAccountSyncStatusHelper;
|
||||
import org.mozilla.gecko.fxa.tasks.FxAccountCodeResender;
|
||||
import org.mozilla.gecko.sync.ThreadPool;
|
||||
import org.mozilla.gecko.sync.Utils;
|
||||
|
||||
@ -156,6 +158,38 @@ public class FirefoxAccounts {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return
|
||||
* the {@link State} instance associated with the current account, or <code>null</code> if
|
||||
* no accounts exist.
|
||||
*/
|
||||
public static State getFirefoxAccountState(final Context context) {
|
||||
final Account account = getFirefoxAccount(context);
|
||||
if (account == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
final AndroidFxAccount fxAccount = new AndroidFxAccount(context, account);
|
||||
try {
|
||||
return fxAccount.getState();
|
||||
} catch (final Exception ex) {
|
||||
Logger.warn(LOG_TAG, "Could not get FX account state.", ex);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* @param context Android context
|
||||
* @return the email address associated with the configured Firefox account if one exists; null otherwise.
|
||||
*/
|
||||
public static String getFirefoxAccountEmail(final Context context) {
|
||||
final Account account = getFirefoxAccount(context);
|
||||
if (account == null) {
|
||||
return null;
|
||||
}
|
||||
return account.name;
|
||||
}
|
||||
|
||||
protected static void putHintsToSync(final Bundle extras, EnumSet<SyncHint> syncHints) {
|
||||
// stagesToSync and stagesToSkip are allowed to be null.
|
||||
if (syncHints == null) {
|
||||
@ -275,4 +309,26 @@ public class FirefoxAccounts {
|
||||
final String LOCALE = Utils.getLanguageTag(locale);
|
||||
return res.getString(R.string.fxaccount_link_old_firefox, VERSION, OS, LOCALE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resends the account verification email, and displays an appropriate
|
||||
* toast on both send success and failure. Note that because the underlying implementation
|
||||
* uses {@link AsyncTask}, the provided context must be UI-capable, and this
|
||||
* method called from the UI thread (see
|
||||
* {@link org.mozilla.gecko.fxa.tasks.FxAccountCodeResender#resendCode(Context, AndroidFxAccount)}
|
||||
* for more).
|
||||
*
|
||||
* @param context a UI-capable Android context.
|
||||
* @return true if an account exists, false otherwise.
|
||||
*/
|
||||
public static boolean resendVerificationEmail(final Context context) {
|
||||
final Account account = getFirefoxAccount(context);
|
||||
if (account == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final AndroidFxAccount fxAccount = new AndroidFxAccount(context, account);
|
||||
FxAccountCodeResender.resendCode(context, fxAccount);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
@ -19,10 +19,10 @@ import org.mozilla.gecko.background.fxa.FxAccountUtils;
|
||||
import org.mozilla.gecko.background.fxa.PasswordStretcher;
|
||||
import org.mozilla.gecko.background.fxa.QuickPasswordStretcher;
|
||||
import org.mozilla.gecko.fxa.FxAccountConstants;
|
||||
import org.mozilla.gecko.fxa.activities.FxAccountSetupTask.ProgressDisplay;
|
||||
import org.mozilla.gecko.fxa.authenticator.AndroidFxAccount;
|
||||
import org.mozilla.gecko.fxa.login.Engaged;
|
||||
import org.mozilla.gecko.fxa.login.State;
|
||||
import org.mozilla.gecko.fxa.tasks.FxAccountSetupTask.ProgressDisplay;
|
||||
import org.mozilla.gecko.sync.SyncConfiguration;
|
||||
import org.mozilla.gecko.sync.setup.Constants;
|
||||
import org.mozilla.gecko.sync.setup.activities.ActivityUtils;
|
||||
|
@ -4,21 +4,15 @@
|
||||
|
||||
package org.mozilla.gecko.fxa.activities;
|
||||
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
import org.mozilla.gecko.R;
|
||||
import org.mozilla.gecko.background.common.log.Logger;
|
||||
import org.mozilla.gecko.background.fxa.FxAccountClient;
|
||||
import org.mozilla.gecko.background.fxa.FxAccountClient10.RequestDelegate;
|
||||
import org.mozilla.gecko.background.fxa.FxAccountClient20;
|
||||
import org.mozilla.gecko.background.fxa.FxAccountClientException.FxAccountClientRemoteException;
|
||||
import org.mozilla.gecko.fxa.FirefoxAccounts;
|
||||
import org.mozilla.gecko.fxa.authenticator.AndroidFxAccount;
|
||||
import org.mozilla.gecko.fxa.login.Engaged;
|
||||
import org.mozilla.gecko.fxa.login.State;
|
||||
import org.mozilla.gecko.fxa.login.State.Action;
|
||||
import org.mozilla.gecko.fxa.sync.FxAccountSyncStatusHelper;
|
||||
import org.mozilla.gecko.fxa.tasks.FxAccountCodeResender;
|
||||
import org.mozilla.gecko.sync.setup.activities.ActivityUtils;
|
||||
|
||||
import android.accounts.Account;
|
||||
@ -28,7 +22,6 @@ import android.os.Bundle;
|
||||
import android.view.View;
|
||||
import android.view.View.OnClickListener;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
/**
|
||||
* Activity which displays account created successfully screen to the user, and
|
||||
@ -164,76 +157,8 @@ public class FxAccountConfirmAccountActivity extends FxAccountAbstractActivity i
|
||||
resendLink.setClickable(resendLinkShouldBeEnabled);
|
||||
}
|
||||
|
||||
public static class FxAccountResendCodeTask extends FxAccountSetupTask<Void> {
|
||||
protected static final String LOG_TAG = FxAccountResendCodeTask.class.getSimpleName();
|
||||
|
||||
protected final byte[] sessionToken;
|
||||
|
||||
public FxAccountResendCodeTask(Context context, byte[] sessionToken, FxAccountClient client, RequestDelegate<Void> delegate) {
|
||||
super(context, null, client, delegate);
|
||||
this.sessionToken = sessionToken;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected InnerRequestDelegate<Void> doInBackground(Void... arg0) {
|
||||
try {
|
||||
client.resendCode(sessionToken, innerDelegate);
|
||||
latch.await();
|
||||
return innerDelegate;
|
||||
} catch (Exception e) {
|
||||
Logger.error(LOG_TAG, "Got exception signing in.", e);
|
||||
delegate.handleError(e);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
protected static class ResendCodeDelegate implements RequestDelegate<Void> {
|
||||
public final Context context;
|
||||
|
||||
public ResendCodeDelegate(Context context) {
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleError(Exception e) {
|
||||
Logger.warn(LOG_TAG, "Got exception requesting fresh confirmation link; ignoring.", e);
|
||||
Toast.makeText(context, R.string.fxaccount_confirm_account_verification_link_not_sent, Toast.LENGTH_LONG).show();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleFailure(FxAccountClientRemoteException e) {
|
||||
handleError(e);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleSuccess(Void result) {
|
||||
Toast.makeText(context, R.string.fxaccount_confirm_account_verification_link_sent, Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
}
|
||||
|
||||
public static void resendCode(Context context, AndroidFxAccount fxAccount) {
|
||||
RequestDelegate<Void> delegate = new ResendCodeDelegate(context);
|
||||
|
||||
byte[] sessionToken;
|
||||
try {
|
||||
sessionToken = ((Engaged) fxAccount.getState()).getSessionToken();
|
||||
} catch (Exception e) {
|
||||
delegate.handleError(e);
|
||||
return;
|
||||
}
|
||||
if (sessionToken == null) {
|
||||
delegate.handleError(new IllegalStateException("sessionToken should not be null"));
|
||||
return;
|
||||
}
|
||||
|
||||
Executor executor = Executors.newSingleThreadExecutor();
|
||||
FxAccountClient client = new FxAccountClient20(fxAccount.getAccountServerURI(), executor);
|
||||
new FxAccountResendCodeTask(context, sessionToken, client, delegate).execute();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
resendCode(this, fxAccount);
|
||||
FxAccountCodeResender.resendCode(this, fxAccount);
|
||||
}
|
||||
}
|
||||
|
@ -21,7 +21,7 @@ import org.mozilla.gecko.background.fxa.FxAccountClient20.LoginResponse;
|
||||
import org.mozilla.gecko.background.fxa.FxAccountClientException.FxAccountClientRemoteException;
|
||||
import org.mozilla.gecko.background.fxa.PasswordStretcher;
|
||||
import org.mozilla.gecko.fxa.FxAccountConstants;
|
||||
import org.mozilla.gecko.fxa.activities.FxAccountSetupTask.FxAccountCreateAccountTask;
|
||||
import org.mozilla.gecko.fxa.tasks.FxAccountCreateAccountTask;
|
||||
|
||||
import android.app.AlertDialog;
|
||||
import android.app.Dialog;
|
||||
|
@ -16,7 +16,7 @@ import org.mozilla.gecko.background.fxa.FxAccountClient20.LoginResponse;
|
||||
import org.mozilla.gecko.background.fxa.FxAccountClientException.FxAccountClientRemoteException;
|
||||
import org.mozilla.gecko.background.fxa.PasswordStretcher;
|
||||
import org.mozilla.gecko.fxa.FxAccountConstants;
|
||||
import org.mozilla.gecko.fxa.activities.FxAccountSetupTask.FxAccountSignInTask;
|
||||
import org.mozilla.gecko.fxa.tasks.FxAccountSignInTask;
|
||||
import org.mozilla.gecko.sync.setup.activities.ActivityUtils;
|
||||
|
||||
import android.content.Intent;
|
||||
|
@ -17,6 +17,7 @@ import org.mozilla.gecko.fxa.authenticator.AndroidFxAccount;
|
||||
import org.mozilla.gecko.fxa.login.Married;
|
||||
import org.mozilla.gecko.fxa.login.State;
|
||||
import org.mozilla.gecko.fxa.sync.FxAccountSyncStatusHelper;
|
||||
import org.mozilla.gecko.fxa.tasks.FxAccountCodeResender;
|
||||
import org.mozilla.gecko.sync.SyncConfiguration;
|
||||
|
||||
import android.accounts.Account;
|
||||
@ -142,7 +143,7 @@ public class FxAccountStatusFragment extends PreferenceFragment implements OnPre
|
||||
}
|
||||
|
||||
if (preference == needsVerificationPreference) {
|
||||
FxAccountConfirmAccountActivity.resendCode(getActivity().getApplicationContext(), fxAccount);
|
||||
FxAccountCodeResender.resendCode(getActivity().getApplicationContext(), fxAccount);
|
||||
|
||||
Intent intent = new Intent(getActivity(), FxAccountConfirmAccountActivity.class);
|
||||
// Per http://stackoverflow.com/a/8992365, this triggers a known bug with
|
||||
|
@ -18,11 +18,11 @@ import org.mozilla.gecko.background.fxa.FxAccountUtils;
|
||||
import org.mozilla.gecko.background.fxa.PasswordStretcher;
|
||||
import org.mozilla.gecko.fxa.FirefoxAccounts;
|
||||
import org.mozilla.gecko.fxa.FxAccountConstants;
|
||||
import org.mozilla.gecko.fxa.activities.FxAccountSetupTask.FxAccountSignInTask;
|
||||
import org.mozilla.gecko.fxa.authenticator.AndroidFxAccount;
|
||||
import org.mozilla.gecko.fxa.login.Engaged;
|
||||
import org.mozilla.gecko.fxa.login.State;
|
||||
import org.mozilla.gecko.fxa.login.State.StateLabel;
|
||||
import org.mozilla.gecko.fxa.tasks.FxAccountSignInTask;
|
||||
import org.mozilla.gecko.sync.setup.activities.ActivityUtils;
|
||||
|
||||
import android.os.Bundle;
|
||||
|
108
mobile/android/base/fxa/tasks/FxAccountCodeResender.java
Normal file
108
mobile/android/base/fxa/tasks/FxAccountCodeResender.java
Normal file
@ -0,0 +1,108 @@
|
||||
/* 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.fxa.tasks;
|
||||
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
import org.mozilla.gecko.R;
|
||||
import org.mozilla.gecko.background.common.log.Logger;
|
||||
import org.mozilla.gecko.background.fxa.FxAccountClient;
|
||||
import org.mozilla.gecko.background.fxa.FxAccountClient10.RequestDelegate;
|
||||
import org.mozilla.gecko.background.fxa.FxAccountClient20;
|
||||
import org.mozilla.gecko.background.fxa.FxAccountClientException.FxAccountClientRemoteException;
|
||||
import org.mozilla.gecko.fxa.authenticator.AndroidFxAccount;
|
||||
import org.mozilla.gecko.fxa.login.Engaged;
|
||||
|
||||
import android.content.Context;
|
||||
import android.widget.Toast;
|
||||
|
||||
/**
|
||||
* A helper class that provides a simple interface for requesting
|
||||
* a Firefox Account verification email to be resent.
|
||||
*/
|
||||
public class FxAccountCodeResender {
|
||||
private static final String LOG_TAG = FxAccountCodeResender.class.getSimpleName();
|
||||
|
||||
private static class FxAccountResendCodeTask extends FxAccountSetupTask<Void> {
|
||||
protected static final String LOG_TAG = FxAccountResendCodeTask.class.getSimpleName();
|
||||
|
||||
protected final byte[] sessionToken;
|
||||
|
||||
public FxAccountResendCodeTask(Context context, byte[] sessionToken, FxAccountClient client, RequestDelegate<Void> delegate) {
|
||||
super(context, null, client, delegate);
|
||||
this.sessionToken = sessionToken;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected InnerRequestDelegate<Void> doInBackground(Void... arg0) {
|
||||
try {
|
||||
client.resendCode(sessionToken, innerDelegate);
|
||||
latch.await();
|
||||
return innerDelegate;
|
||||
} catch (Exception e) {
|
||||
Logger.error(LOG_TAG, "Got exception signing in.", e);
|
||||
delegate.handleError(e);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static class ResendCodeDelegate implements RequestDelegate<Void> {
|
||||
public final Context context;
|
||||
|
||||
public ResendCodeDelegate(Context context) {
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleError(Exception e) {
|
||||
Logger.warn(LOG_TAG, "Got exception requesting fresh confirmation link; ignoring.", e);
|
||||
Toast.makeText(context, R.string.fxaccount_confirm_account_verification_link_not_sent, Toast.LENGTH_LONG).show();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleFailure(FxAccountClientRemoteException e) {
|
||||
handleError(e);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleSuccess(Void result) {
|
||||
Toast.makeText(context, R.string.fxaccount_confirm_account_verification_link_sent, Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Resends the account verification email, and displays an appropriate
|
||||
* toast on both send success and failure. Note that because the underlying implementation
|
||||
* uses {@link AsyncTask}, the provided context must be UI-capable and
|
||||
* this method called from the UI thread.
|
||||
*
|
||||
* Note that it may actually be possible to run this (and the {@link AsyncTask}) method
|
||||
* from a background thread - but this hasn't been tested.
|
||||
*
|
||||
* @param context A UI-capable Android context.
|
||||
* @param fxAccount The Firefox Account to resend the code to.
|
||||
*/
|
||||
public static void resendCode(Context context, AndroidFxAccount fxAccount) {
|
||||
RequestDelegate<Void> delegate = new ResendCodeDelegate(context);
|
||||
|
||||
byte[] sessionToken;
|
||||
try {
|
||||
sessionToken = ((Engaged) fxAccount.getState()).getSessionToken();
|
||||
} catch (Exception e) {
|
||||
delegate.handleError(e);
|
||||
return;
|
||||
}
|
||||
if (sessionToken == null) {
|
||||
delegate.handleError(new IllegalStateException("sessionToken should not be null"));
|
||||
return;
|
||||
}
|
||||
|
||||
Executor executor = Executors.newSingleThreadExecutor();
|
||||
FxAccountClient client = new FxAccountClient20(fxAccount.getAccountServerURI(), executor);
|
||||
new FxAccountResendCodeTask(context, sessionToken, client, delegate).execute();
|
||||
}
|
||||
}
|
@ -0,0 +1,41 @@
|
||||
/* 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.fxa.tasks;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
|
||||
import org.mozilla.gecko.background.common.log.Logger;
|
||||
import org.mozilla.gecko.background.fxa.FxAccountClient;
|
||||
import org.mozilla.gecko.background.fxa.FxAccountClient10.RequestDelegate;
|
||||
import org.mozilla.gecko.background.fxa.FxAccountClient20.LoginResponse;
|
||||
import org.mozilla.gecko.background.fxa.PasswordStretcher;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
public class FxAccountCreateAccountTask extends FxAccountSetupTask<LoginResponse> {
|
||||
private static final String LOG_TAG = FxAccountCreateAccountTask.class.getSimpleName();
|
||||
|
||||
protected final byte[] emailUTF8;
|
||||
protected final PasswordStretcher passwordStretcher;
|
||||
|
||||
public FxAccountCreateAccountTask(Context context, ProgressDisplay progressDisplay, String email, PasswordStretcher passwordStretcher, FxAccountClient client, RequestDelegate<LoginResponse> delegate) throws UnsupportedEncodingException {
|
||||
super(context, progressDisplay, client, delegate);
|
||||
this.emailUTF8 = email.getBytes("UTF-8");
|
||||
this.passwordStretcher = passwordStretcher;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected InnerRequestDelegate<LoginResponse> doInBackground(Void... arg0) {
|
||||
try {
|
||||
client.createAccountAndGetKeys(emailUTF8, passwordStretcher, innerDelegate);
|
||||
latch.await();
|
||||
return innerDelegate;
|
||||
} catch (Exception e) {
|
||||
Logger.error(LOG_TAG, "Got exception logging in.", e);
|
||||
delegate.handleError(e);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
@ -2,18 +2,15 @@
|
||||
* 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.fxa.activities;
|
||||
package org.mozilla.gecko.fxa.tasks;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
|
||||
import org.mozilla.gecko.background.common.log.Logger;
|
||||
import org.mozilla.gecko.background.fxa.FxAccountClient;
|
||||
import org.mozilla.gecko.background.fxa.FxAccountClient10.RequestDelegate;
|
||||
import org.mozilla.gecko.background.fxa.FxAccountClient20.LoginResponse;
|
||||
import org.mozilla.gecko.background.fxa.FxAccountClientException.FxAccountClientRemoteException;
|
||||
import org.mozilla.gecko.background.fxa.PasswordStretcher;
|
||||
import org.mozilla.gecko.fxa.activities.FxAccountSetupTask.InnerRequestDelegate;
|
||||
import org.mozilla.gecko.fxa.tasks.FxAccountSetupTask.InnerRequestDelegate;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.AsyncTask;
|
||||
@ -27,7 +24,7 @@ import android.os.AsyncTask;
|
||||
* We really want to avoid making a threading mistake that brings down the whole
|
||||
* process.
|
||||
*/
|
||||
abstract class FxAccountSetupTask<T> extends AsyncTask<Void, Void, InnerRequestDelegate<T>> {
|
||||
public abstract class FxAccountSetupTask<T> extends AsyncTask<Void, Void, InnerRequestDelegate<T>> {
|
||||
private static final String LOG_TAG = FxAccountSetupTask.class.getSimpleName();
|
||||
|
||||
public interface ProgressDisplay {
|
||||
@ -117,56 +114,4 @@ abstract class FxAccountSetupTask<T> extends AsyncTask<Void, Void, InnerRequestD
|
||||
latch.countDown();
|
||||
}
|
||||
}
|
||||
|
||||
public static class FxAccountCreateAccountTask extends FxAccountSetupTask<LoginResponse> {
|
||||
private static final String LOG_TAG = FxAccountCreateAccountTask.class.getSimpleName();
|
||||
|
||||
protected final byte[] emailUTF8;
|
||||
protected final PasswordStretcher passwordStretcher;
|
||||
|
||||
public FxAccountCreateAccountTask(Context context, ProgressDisplay progressDisplay, String email, PasswordStretcher passwordStretcher, FxAccountClient client, RequestDelegate<LoginResponse> delegate) throws UnsupportedEncodingException {
|
||||
super(context, progressDisplay, client, delegate);
|
||||
this.emailUTF8 = email.getBytes("UTF-8");
|
||||
this.passwordStretcher = passwordStretcher;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected InnerRequestDelegate<LoginResponse> doInBackground(Void... arg0) {
|
||||
try {
|
||||
client.createAccountAndGetKeys(emailUTF8, passwordStretcher, innerDelegate);
|
||||
latch.await();
|
||||
return innerDelegate;
|
||||
} catch (Exception e) {
|
||||
Logger.error(LOG_TAG, "Got exception logging in.", e);
|
||||
delegate.handleError(e);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static class FxAccountSignInTask extends FxAccountSetupTask<LoginResponse> {
|
||||
protected static final String LOG_TAG = FxAccountSignInTask.class.getSimpleName();
|
||||
|
||||
protected final byte[] emailUTF8;
|
||||
protected final PasswordStretcher passwordStretcher;
|
||||
|
||||
public FxAccountSignInTask(Context context, ProgressDisplay progressDisplay, String email, PasswordStretcher passwordStretcher, FxAccountClient client, RequestDelegate<LoginResponse> delegate) throws UnsupportedEncodingException {
|
||||
super(context, progressDisplay, client, delegate);
|
||||
this.emailUTF8 = email.getBytes("UTF-8");
|
||||
this.passwordStretcher = passwordStretcher;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected InnerRequestDelegate<LoginResponse> doInBackground(Void... arg0) {
|
||||
try {
|
||||
client.loginAndGetKeys(emailUTF8, passwordStretcher, innerDelegate);
|
||||
latch.await();
|
||||
return innerDelegate;
|
||||
} catch (Exception e) {
|
||||
Logger.error(LOG_TAG, "Got exception signing in.", e);
|
||||
delegate.handleError(e);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
41
mobile/android/base/fxa/tasks/FxAccountSignInTask.java
Normal file
41
mobile/android/base/fxa/tasks/FxAccountSignInTask.java
Normal file
@ -0,0 +1,41 @@
|
||||
/* 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.fxa.tasks;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
|
||||
import org.mozilla.gecko.background.common.log.Logger;
|
||||
import org.mozilla.gecko.background.fxa.FxAccountClient;
|
||||
import org.mozilla.gecko.background.fxa.FxAccountClient10.RequestDelegate;
|
||||
import org.mozilla.gecko.background.fxa.FxAccountClient20.LoginResponse;
|
||||
import org.mozilla.gecko.background.fxa.PasswordStretcher;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
public class FxAccountSignInTask extends FxAccountSetupTask<LoginResponse> {
|
||||
protected static final String LOG_TAG = FxAccountSignInTask.class.getSimpleName();
|
||||
|
||||
protected final byte[] emailUTF8;
|
||||
protected final PasswordStretcher passwordStretcher;
|
||||
|
||||
public FxAccountSignInTask(Context context, ProgressDisplay progressDisplay, String email, PasswordStretcher passwordStretcher, FxAccountClient client, RequestDelegate<LoginResponse> delegate) throws UnsupportedEncodingException {
|
||||
super(context, progressDisplay, client, delegate);
|
||||
this.emailUTF8 = email.getBytes("UTF-8");
|
||||
this.passwordStretcher = passwordStretcher;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected InnerRequestDelegate<LoginResponse> doInBackground(Void... arg0) {
|
||||
try {
|
||||
client.loginAndGetKeys(emailUTF8, passwordStretcher, innerDelegate);
|
||||
latch.await();
|
||||
return innerDelegate;
|
||||
} catch (Exception e) {
|
||||
Logger.error(LOG_TAG, "Got exception signing in.", e);
|
||||
delegate.handleError(e);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
@ -22,8 +22,6 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
import org.mozilla.gecko.AppConstants;
|
||||
import org.mozilla.gecko.Distribution;
|
||||
import org.mozilla.gecko.Distribution.DistributionDescriptor;
|
||||
import org.mozilla.gecko.EventDispatcher;
|
||||
import org.mozilla.gecko.GeckoAppShell;
|
||||
import org.mozilla.gecko.GeckoEvent;
|
||||
@ -34,6 +32,8 @@ import org.mozilla.gecko.background.healthreport.HealthReportDatabaseStorage;
|
||||
import org.mozilla.gecko.background.healthreport.HealthReportStorage.Field;
|
||||
import org.mozilla.gecko.background.healthreport.HealthReportStorage.MeasurementFields;
|
||||
import org.mozilla.gecko.background.healthreport.ProfileInformationCache;
|
||||
import org.mozilla.gecko.distribution.Distribution;
|
||||
import org.mozilla.gecko.distribution.Distribution.DistributionDescriptor;
|
||||
import org.mozilla.gecko.util.GeckoEventListener;
|
||||
import org.mozilla.gecko.util.ThreadUtils;
|
||||
|
||||
|
@ -229,6 +229,9 @@ size. -->
|
||||
<!ENTITY pref_update_autodownload_never "Never">
|
||||
<!ENTITY pref_update_autodownload_always "Always">
|
||||
|
||||
<!-- Localization note (help_menu) : This string is used in the main menu-->
|
||||
<!ENTITY help_menu "Help">
|
||||
|
||||
<!ENTITY quit "Quit">
|
||||
|
||||
<!ENTITY addons "Add-ons">
|
||||
|
@ -145,7 +145,7 @@ gbjar.sources += [
|
||||
'db/SuggestedSites.java',
|
||||
'db/TabsProvider.java',
|
||||
'db/TopSitesCursorWrapper.java',
|
||||
'Distribution.java',
|
||||
'distribution/Distribution.java',
|
||||
'DoorHangerPopup.java',
|
||||
'DynamicToolbar.java',
|
||||
'EditBookmarkDialog.java',
|
||||
@ -361,8 +361,11 @@ gbjar.sources += [
|
||||
'Tab.java',
|
||||
'Tabs.java',
|
||||
'TabsAccessor.java',
|
||||
'tabspanel/RemoteTabsContainer.java',
|
||||
'tabspanel/RemoteTabsContainerPanel.java',
|
||||
'tabspanel/RemoteTabsList.java',
|
||||
'tabspanel/RemoteTabsPanel.java',
|
||||
'tabspanel/RemoteTabsSetupPanel.java',
|
||||
'tabspanel/RemoteTabsVerificationPanel.java',
|
||||
'tabspanel/TabsPanel.java',
|
||||
'tabspanel/TabsTray.java',
|
||||
'Telemetry.java',
|
||||
|
@ -10,12 +10,14 @@ import java.util.Locale;
|
||||
|
||||
import org.mozilla.gecko.BrowserLocaleManager;
|
||||
import org.mozilla.gecko.GeckoSharedPrefs;
|
||||
import org.mozilla.gecko.LocaleManager;
|
||||
import org.mozilla.gecko.PrefsHelper;
|
||||
import org.mozilla.gecko.R;
|
||||
|
||||
import android.app.ActionBar;
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.res.Configuration;
|
||||
import android.content.res.Resources;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
@ -33,6 +35,20 @@ import android.view.ViewConfiguration;
|
||||
*/
|
||||
public class GeckoPreferenceFragment extends PreferenceFragment {
|
||||
|
||||
@Override
|
||||
public void onConfigurationChanged(Configuration newConfig) {
|
||||
super.onConfigurationChanged(newConfig);
|
||||
Log.d(LOGTAG, "onConfigurationChanged: " + newConfig.locale);
|
||||
|
||||
final Activity context = getActivity();
|
||||
|
||||
final LocaleManager localeManager = BrowserLocaleManager.getInstance();
|
||||
final Locale changed = localeManager.onSystemConfigurationChanged(context, getResources(), newConfig, lastLocale);
|
||||
if (changed != null) {
|
||||
applyLocale(changed);
|
||||
}
|
||||
}
|
||||
|
||||
private static final String LOGTAG = "GeckoPreferenceFragment";
|
||||
private int mPrefsRequestId = 0;
|
||||
private Locale lastLocale = Locale.getDefault();
|
||||
@ -112,7 +128,13 @@ public class GeckoPreferenceFragment extends PreferenceFragment {
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
final Locale currentLocale = Locale.getDefault();
|
||||
// This is a little delicate. Ensure that you do nothing prior to
|
||||
// super.onResume that you wouldn't do in onCreate.
|
||||
applyLocale(Locale.getDefault());
|
||||
super.onResume();
|
||||
}
|
||||
|
||||
private void applyLocale(final Locale currentLocale) {
|
||||
final Context context = getActivity().getApplicationContext();
|
||||
|
||||
BrowserLocaleManager.getInstance().updateConfiguration(context, currentLocale);
|
||||
@ -129,8 +151,6 @@ public class GeckoPreferenceFragment extends PreferenceFragment {
|
||||
|
||||
// Fix the parent title regardless.
|
||||
updateTitle();
|
||||
|
||||
super.onResume();
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -45,6 +45,7 @@ import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
|
||||
import android.content.res.Configuration;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.preference.CheckBoxPreference;
|
||||
@ -866,6 +867,24 @@ OnSharedPreferenceChangeListener
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onConfigurationChanged(Configuration newConfig) {
|
||||
super.onConfigurationChanged(newConfig);
|
||||
|
||||
Log.d(LOGTAG, "onConfigurationChanged: " + newConfig.locale);
|
||||
|
||||
if (lastLocale.equals(newConfig.locale)) {
|
||||
Log.d(LOGTAG, "Old locale same as new locale. Short-circuiting.");
|
||||
return;
|
||||
}
|
||||
|
||||
final LocaleManager localeManager = BrowserLocaleManager.getInstance();
|
||||
final Locale changed = localeManager.onSystemConfigurationChanged(this, getResources(), newConfig, lastLocale);
|
||||
if (changed != null) {
|
||||
onLocaleChanged(changed);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation for the {@link OnSharedPreferenceChangeListener} interface,
|
||||
* which we use to watch changes in our prefs file.
|
||||
|
@ -0,0 +1,20 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- This Source Code Form is subject to the terms of the Mozilla Public
|
||||
- License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
|
||||
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:state_pressed="true">
|
||||
<shape android:shape="rectangle">
|
||||
<solid android:color="@color/remote_tabs_setup_button_background_hit"/>
|
||||
<corners android:radius="@dimen/fxaccount_corner_radius"/>
|
||||
</shape>
|
||||
</item>
|
||||
|
||||
<item>
|
||||
<shape android:shape="rectangle">
|
||||
<solid android:color="@color/remote_tabs_setup_button_background"/>
|
||||
<corners android:radius="@dimen/fxaccount_corner_radius"/>
|
||||
</shape>
|
||||
</item>
|
||||
</selector>
|
@ -48,20 +48,11 @@
|
||||
android:visibility="gone"
|
||||
gecko:tabs="tabs_private"/>
|
||||
|
||||
<org.mozilla.gecko.tabspanel.RemoteTabsContainer android:id="@+id/synced_tabs"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="fill_parent"
|
||||
android:visibility="gone">
|
||||
|
||||
<org.mozilla.gecko.tabspanel.RemoteTabsList android:id="@+id/synced_tabs_list"
|
||||
style="@style/RemoteTabsList"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="fill_parent"
|
||||
android:paddingLeft="@dimen/tabs_panel_list_padding"
|
||||
android:paddingRight="@dimen/tabs_panel_list_padding"
|
||||
android:scrollbarStyle="outsideOverlay"/>
|
||||
|
||||
</org.mozilla.gecko.tabspanel.RemoteTabsContainer>
|
||||
<org.mozilla.gecko.tabspanel.RemoteTabsPanel
|
||||
android:id="@+id/remote_tabs"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_width="match_parent"
|
||||
android:visibility="gone"/>
|
||||
|
||||
</view>
|
||||
|
||||
|
@ -0,0 +1,20 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- This Source Code Form is subject to the terms of the Mozilla Public
|
||||
- License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
|
||||
|
||||
<org.mozilla.gecko.tabspanel.RemoteTabsContainerPanel
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="fill_parent"
|
||||
android:visibility="gone">
|
||||
|
||||
<org.mozilla.gecko.tabspanel.RemoteTabsList
|
||||
style="@style/RemoteTabsList"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="fill_parent"
|
||||
android:paddingLeft="@dimen/tabs_panel_list_padding"
|
||||
android:paddingRight="@dimen/tabs_panel_list_padding"
|
||||
android:scrollbarStyle="outsideOverlay"/>
|
||||
|
||||
</org.mozilla.gecko.tabspanel.RemoteTabsContainerPanel>
|
@ -0,0 +1,48 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- This Source Code Form is subject to the terms of the Mozilla Public
|
||||
- License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
|
||||
|
||||
<org.mozilla.gecko.tabspanel.RemoteTabsSetupPanel
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
style="@style/RemoteTabsPanelChild"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:visibility="gone">
|
||||
|
||||
<LinearLayout style="@style/RemoteTabsSection"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<TextView style="@style/RemoteTabsItem.TextAppearance.Header"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/fxaccount_getting_started_welcome_to_sync"/>
|
||||
|
||||
<TextView style="@style/RemoteTabsItem.TextAppearance"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/fxaccount_getting_started_description"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout style="@style/RemoteTabsSection"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<Button android:id="@+id/remote_tabs_setup_get_started"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
style="@style/RemoteTabsItem.Button"
|
||||
android:text="@string/fxaccount_getting_started_get_started"
|
||||
android:layout_marginBottom="15dp"/>
|
||||
|
||||
<TextView android:id="@+id/remote_tabs_setup_old_sync_link"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
style="@style/RemoteTabsItem.TextAppearance.Linkified"
|
||||
android:text="@string/fxaccount_getting_started_old_firefox"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</org.mozilla.gecko.tabspanel.RemoteTabsSetupPanel>
|
@ -0,0 +1,48 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- This Source Code Form is subject to the terms of the Mozilla Public
|
||||
- License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
|
||||
|
||||
<org.mozilla.gecko.tabspanel.RemoteTabsVerificationPanel
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
style="@style/RemoteTabsPanelChild"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:visibility="gone">
|
||||
|
||||
<LinearLayout style="@style/RemoteTabsSection"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<TextView style="@style/RemoteTabsItem.TextAppearance.Header.FXAccounts"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/fxaccount_full_label"
|
||||
android:layout_marginBottom="0dp"/>
|
||||
|
||||
<TextView style="@style/RemoteTabsItem.TextAppearance.Header"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/fxaccount_confirm_account_header"
|
||||
android:layout_marginTop="0dp"/>
|
||||
|
||||
<TextView android:id="@+id/remote_tabs_confirm_verification"
|
||||
style="@style/RemoteTabsItem.TextAppearance"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/fxaccount_confirm_account_verification_link"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout style="@style/RemoteTabsSection"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<TextView android:id="@+id/remote_tabs_confirm_resend"
|
||||
style="@style/RemoteTabsItem.TextAppearance.Linkified.Resend"
|
||||
android:layout_width="match_parent"
|
||||
android:text="@string/fxaccount_confirm_account_resend_email"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</org.mozilla.gecko.tabspanel.RemoteTabsVerificationPanel>
|
@ -47,20 +47,11 @@
|
||||
android:visibility="gone"
|
||||
gecko:tabs="tabs_private"/>
|
||||
|
||||
<org.mozilla.gecko.tabspanel.RemoteTabsContainer android:id="@+id/synced_tabs"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="fill_parent"
|
||||
android:visibility="gone">
|
||||
|
||||
<org.mozilla.gecko.tabspanel.RemoteTabsList android:id="@+id/synced_tabs"
|
||||
style="@style/RemoteTabsList"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="fill_parent"
|
||||
android:paddingLeft="@dimen/tabs_panel_list_padding"
|
||||
android:paddingRight="@dimen/tabs_panel_list_padding"
|
||||
android:scrollbarStyle="outsideOverlay"/>
|
||||
|
||||
</org.mozilla.gecko.tabspanel.RemoteTabsContainer>
|
||||
<org.mozilla.gecko.tabspanel.RemoteTabsPanel
|
||||
android:id="@+id/remote_tabs"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_width="match_parent"
|
||||
android:visibility="gone"/>
|
||||
|
||||
</view>
|
||||
|
||||
|
@ -108,6 +108,9 @@
|
||||
android:icon="@drawable/ic_menu_settings"
|
||||
android:title="@string/settings" />
|
||||
|
||||
<item android:id="@+id/help"
|
||||
android:title="@string/help_menu" />
|
||||
|
||||
<item android:id="@+id/exit_guest_session"
|
||||
android:icon="@drawable/ic_menu_guest"
|
||||
android:visible="false"
|
||||
|
@ -108,6 +108,9 @@
|
||||
android:icon="@drawable/ic_menu_settings"
|
||||
android:title="@string/settings" />
|
||||
|
||||
<item android:id="@+id/help"
|
||||
android:title="@string/help_menu" />
|
||||
|
||||
<item android:id="@+id/exit_guest_session"
|
||||
android:icon="@drawable/ic_menu_guest"
|
||||
android:visible="false"
|
||||
|
@ -109,6 +109,9 @@
|
||||
android:icon="@drawable/ic_menu_settings"
|
||||
android:title="@string/settings" />
|
||||
|
||||
<item android:id="@+id/help"
|
||||
android:title="@string/help_menu" />
|
||||
|
||||
<item android:id="@+id/exit_guest_session"
|
||||
android:icon="@drawable/ic_menu_guest"
|
||||
android:visible="false"
|
||||
|
@ -59,6 +59,9 @@
|
||||
<item android:id="@+id/settings"
|
||||
android:title="@string/settings" />
|
||||
|
||||
<item android:id="@+id/help"
|
||||
android:title="@string/help_menu" />
|
||||
|
||||
<item android:id="@+id/new_guest_session"
|
||||
android:icon="@drawable/ic_menu_guest"
|
||||
android:visible="false"
|
||||
|
@ -17,4 +17,37 @@
|
||||
<item name="android:nextFocusUp">@+id/info</item>
|
||||
</style>
|
||||
|
||||
<!-- Remote tabs panel -->
|
||||
<style name="RemoteTabsPanelChild" parent="RemoteTabsPanelChildBase">
|
||||
<item name="android:orientation">horizontal</item>
|
||||
<item name="android:paddingTop">24dp</item>
|
||||
</style>
|
||||
|
||||
<style name="RemoteTabsSection" parent="RemoteTabsSectionBase">
|
||||
<item name="android:layout_weight">1</item>
|
||||
</style>
|
||||
|
||||
<style name="RemoteTabsItem">
|
||||
<item name="android:layout_marginBottom">20dp</item>
|
||||
<item name="android:layout_gravity">left</item>
|
||||
<item name="android:gravity">left</item>
|
||||
</style>
|
||||
|
||||
<style name="RemoteTabsItem.Button" parent="RemoteTabsItem.ButtonBase">
|
||||
<item name="android:paddingTop">12dp</item>
|
||||
<item name="android:paddingBottom">12dp</item>
|
||||
<item name="android:paddingLeft">64dp</item>
|
||||
<item name="android:paddingRight">64dp</item>
|
||||
</style>
|
||||
|
||||
<style name="RemoteTabsItem.TextAppearance.Header.FXAccounts">
|
||||
<item name="android:visibility">gone</item>
|
||||
</style>
|
||||
|
||||
<style name="RemoteTabsItem.TextAppearance.Linkified.Resend">
|
||||
<item name="android:layout_height">match_parent</item>
|
||||
<item name="android:gravity">center</item>
|
||||
<item name="android:layout_gravity">center</item>
|
||||
</style>
|
||||
|
||||
</resources>
|
||||
|
@ -96,4 +96,8 @@
|
||||
<color name="swipe_refresh_orange">#FFFFC26C</color>
|
||||
<color name="swipe_refresh_white">#FFFFFFFF</color>
|
||||
<color name="swipe_refresh_orange_dark">#FF9500</color>
|
||||
|
||||
<!-- Remote tabs setup -->
|
||||
<color name="remote_tabs_setup_button_background">#E66000</color>
|
||||
<color name="remote_tabs_setup_button_background_hit">#D95300</color>
|
||||
</resources>
|
||||
|
@ -448,6 +448,73 @@
|
||||
<item name="android:groupIndicator">@android:color/transparent</item>
|
||||
</style>
|
||||
|
||||
<!-- Remote tabs panel -->
|
||||
<style name="RemoteTabsPanelChildBase">
|
||||
<item name="android:paddingLeft">16dp</item>
|
||||
<item name="android:paddingRight">16dp</item>
|
||||
</style>
|
||||
|
||||
<style name="RemoteTabsPanelChild" parent="RemoteTabsPanelChildBase">
|
||||
<item name="android:orientation">vertical</item>
|
||||
<item name="android:paddingTop">62dp</item>
|
||||
</style>
|
||||
|
||||
<style name="RemoteTabsSectionBase">
|
||||
<item name="android:orientation">vertical</item>
|
||||
<item name="android:layout_marginLeft">16dp</item>
|
||||
<item name="android:layout_marginRight">16dp</item>
|
||||
</style>
|
||||
|
||||
<style name="RemoteTabsSection" parent="RemoteTabsSectionBase">
|
||||
<!-- We set values in landscape. -->
|
||||
</style>
|
||||
|
||||
<style name="RemoteTabsSection.Resend" parent="RemoteTabsSectionBase">
|
||||
<!-- We set values in landscape. -->
|
||||
</style>
|
||||
|
||||
<style name="RemoteTabsItem">
|
||||
<item name="android:layout_marginBottom">28dp</item>
|
||||
<item name="android:layout_gravity">center</item>
|
||||
<item name="android:gravity">center</item>
|
||||
</style>
|
||||
|
||||
<style name="RemoteTabsItem.ButtonBase">
|
||||
<item name="android:background">@drawable/remote_tabs_setup_button_background</item>
|
||||
<item name="android:textColor">#FFFEFF</item>
|
||||
<item name="android:textSize">20sp</item>
|
||||
</style>
|
||||
|
||||
<style name="RemoteTabsItem.Button" parent="RemoteTabsItem.ButtonBase">
|
||||
<item name="android:paddingTop">18dp</item>
|
||||
<item name="android:paddingBottom">18dp</item>
|
||||
<item name="android:paddingLeft">72dp</item>
|
||||
<item name="android:paddingRight">72dp</item>
|
||||
</style>
|
||||
|
||||
<style name="RemoteTabsItem.TextAppearance">
|
||||
<item name="android:textColor">#C0C9D0</item>
|
||||
<item name="android:textSize">16sp</item>
|
||||
</style>
|
||||
|
||||
<style name="RemoteTabsItem.TextAppearance.Header">
|
||||
<item name="android:textSize">20sp</item>
|
||||
</style>
|
||||
|
||||
<style name="RemoteTabsItem.TextAppearance.Header.FXAccounts">
|
||||
<!-- We change these values on landscape. -->
|
||||
</style>
|
||||
|
||||
<style name="RemoteTabsItem.TextAppearance.Linkified">
|
||||
<item name="android:clickable">true</item>
|
||||
<item name="android:focusable">true</item>
|
||||
<item name="android:textColor">#0292D6</item>
|
||||
</style>
|
||||
|
||||
<style name="RemoteTabsItem.TextAppearance.Linkified.Resend">
|
||||
<item name="android:layout_height">wrap_content</item>
|
||||
</style>
|
||||
|
||||
<!-- TabsTray Row -->
|
||||
<style name="TabRowTextAppearance">
|
||||
<item name="android:textColor">#FFFFFFFF</item>
|
||||
|
@ -60,6 +60,10 @@
|
||||
|
||||
<string name="url_bar_default_text">&url_bar_default_text;</string>
|
||||
|
||||
<!-- https://support.mozilla.org/1/mobile/%VERSION%/%OS%/%LOCALE%/ -->
|
||||
<string name="help_link">https://support.mozilla.org/1/mobile/&formatS1;/&formatS2;/&formatS3;/</string>
|
||||
<string name="help_menu">&help_menu;</string>
|
||||
|
||||
<string name="quit">&quit;</string>
|
||||
<string name="bookmark">&bookmark;</string>
|
||||
<string name="bookmark_added">&bookmark_added;</string>
|
||||
|
@ -21,7 +21,7 @@ import android.view.ViewGroup;
|
||||
* Provides a container to wrap the list of synced tabs and provide swipe-to-refresh support. The
|
||||
* only child view should be an instance of {@link RemoteTabsList}.
|
||||
*/
|
||||
public class RemoteTabsContainer extends GeckoSwipeRefreshLayout
|
||||
public class RemoteTabsContainerPanel extends GeckoSwipeRefreshLayout
|
||||
implements TabsPanel.PanelView {
|
||||
private static final String[] STAGES_TO_SYNC_ON_REFRESH = new String[] { "tabs" };
|
||||
|
||||
@ -29,7 +29,10 @@ public class RemoteTabsContainer extends GeckoSwipeRefreshLayout
|
||||
private final RemoteTabsSyncObserver syncListener;
|
||||
private RemoteTabsList list;
|
||||
|
||||
public RemoteTabsContainer(Context context, AttributeSet attrs) {
|
||||
// Whether or not a sync status listener is attached.
|
||||
private boolean isListening = false;
|
||||
|
||||
public RemoteTabsContainerPanel(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
this.context = context;
|
||||
this.syncListener = new RemoteTabsSyncObserver();
|
||||
@ -67,15 +70,21 @@ public class RemoteTabsContainer extends GeckoSwipeRefreshLayout
|
||||
|
||||
@Override
|
||||
public void show() {
|
||||
setVisibility(VISIBLE);
|
||||
TabsAccessor.getTabs(context, list);
|
||||
FirefoxAccounts.addSyncStatusListener(syncListener);
|
||||
if (!isListening) {
|
||||
isListening = true;
|
||||
FirefoxAccounts.addSyncStatusListener(syncListener);
|
||||
}
|
||||
setVisibility(View.VISIBLE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void hide() {
|
||||
setVisibility(GONE);
|
||||
FirefoxAccounts.removeSyncStatusListener(syncListener);
|
||||
setVisibility(View.GONE);
|
||||
if (isListening) {
|
||||
isListening = false;
|
||||
FirefoxAccounts.removeSyncStatusListener(syncListener);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -96,7 +105,7 @@ public class RemoteTabsContainer extends GeckoSwipeRefreshLayout
|
||||
private class RemoteTabsSyncObserver implements FirefoxAccounts.SyncStatusListener {
|
||||
@Override
|
||||
public Context getContext() {
|
||||
return RemoteTabsContainer.this.getContext();
|
||||
return RemoteTabsContainerPanel.this.getContext();
|
||||
}
|
||||
|
||||
@Override
|
@ -23,7 +23,7 @@ import android.widget.ExpandableListView;
|
||||
import android.widget.SimpleExpandableListAdapter;
|
||||
|
||||
/**
|
||||
* The actual list of synced tabs. This serves as the only child view of {@link RemoteTabsContainer}
|
||||
* The actual list of synced tabs. This serves as the only child view of {@link RemoteTabsContainerPanel}
|
||||
* so it can be refreshed using a swipe-to-refresh gesture.
|
||||
*/
|
||||
class RemoteTabsList extends ExpandableListView
|
||||
|
116
mobile/android/base/tabspanel/RemoteTabsPanel.java
Normal file
116
mobile/android/base/tabspanel/RemoteTabsPanel.java
Normal file
@ -0,0 +1,116 @@
|
||||
/* 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.tabspanel;
|
||||
|
||||
import org.mozilla.gecko.R;
|
||||
|
||||
import org.mozilla.gecko.fxa.FirefoxAccounts;
|
||||
import org.mozilla.gecko.fxa.login.State;
|
||||
import org.mozilla.gecko.tabspanel.TabsPanel.PanelView;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.widget.FrameLayout;
|
||||
|
||||
/**
|
||||
* This panel, which is a {@link TabsPanel.PanelView}, chooses which underlying
|
||||
* PanelView to show based on the current account state, and forwards the appropriate
|
||||
* calls to the currently visible panel.
|
||||
*/
|
||||
class RemoteTabsPanel extends FrameLayout implements PanelView {
|
||||
private enum RemotePanelType {
|
||||
SETUP,
|
||||
VERIFICATION,
|
||||
CONTAINER
|
||||
}
|
||||
|
||||
private PanelView currentPanel;
|
||||
private RemotePanelType currentPanelType;
|
||||
|
||||
private TabsPanel tabsPanel;
|
||||
|
||||
public RemoteTabsPanel(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
updateCurrentPanel();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setTabsPanel(TabsPanel panel) {
|
||||
tabsPanel = panel;
|
||||
currentPanel.setTabsPanel(panel);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void show() {
|
||||
updateCurrentPanel();
|
||||
currentPanel.show();
|
||||
setVisibility(View.VISIBLE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void hide() {
|
||||
setVisibility(View.GONE);
|
||||
currentPanel.hide();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean shouldExpand() {
|
||||
return currentPanel.shouldExpand();
|
||||
}
|
||||
|
||||
private void updateCurrentPanel() {
|
||||
final RemotePanelType newPanelType = getPanelTypeFromAccountState();
|
||||
if (newPanelType != currentPanelType) {
|
||||
// The current panel should be null the first time this is called.
|
||||
if (currentPanel != null) {
|
||||
currentPanel.hide();
|
||||
}
|
||||
removeAllViews();
|
||||
|
||||
currentPanelType = newPanelType;
|
||||
currentPanel = inflatePanel(currentPanelType);
|
||||
currentPanel.setTabsPanel(tabsPanel);
|
||||
addView((View) currentPanel);
|
||||
}
|
||||
}
|
||||
|
||||
private RemotePanelType getPanelTypeFromAccountState() {
|
||||
final State accountState = FirefoxAccounts.getFirefoxAccountState(getContext());
|
||||
if (accountState == null) {
|
||||
return RemotePanelType.SETUP;
|
||||
}
|
||||
|
||||
if (accountState.getNeededAction() == State.Action.NeedsVerification) {
|
||||
return RemotePanelType.VERIFICATION;
|
||||
}
|
||||
|
||||
return RemotePanelType.CONTAINER;
|
||||
}
|
||||
|
||||
private PanelView inflatePanel(final RemotePanelType panelType) {
|
||||
final LayoutInflater inflater = LayoutInflater.from(getContext());
|
||||
final View inflatedView;
|
||||
switch (panelType) {
|
||||
case SETUP:
|
||||
inflatedView = inflater.inflate(R.layout.remote_tabs_setup_panel, null);
|
||||
break;
|
||||
|
||||
case VERIFICATION:
|
||||
inflatedView = inflater.inflate(R.layout.remote_tabs_verification_panel, null);
|
||||
break;
|
||||
|
||||
case CONTAINER:
|
||||
inflatedView = inflater.inflate(R.layout.remote_tabs_container_panel, null);
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new IllegalArgumentException("Unknown panelType, " + panelType);
|
||||
}
|
||||
|
||||
return (PanelView) inflatedView;
|
||||
}
|
||||
}
|
89
mobile/android/base/tabspanel/RemoteTabsSetupPanel.java
Normal file
89
mobile/android/base/tabspanel/RemoteTabsSetupPanel.java
Normal file
@ -0,0 +1,89 @@
|
||||
/* 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.tabspanel;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
import org.mozilla.gecko.R;
|
||||
import org.mozilla.gecko.Tabs;
|
||||
import org.mozilla.gecko.fxa.FirefoxAccounts;
|
||||
import org.mozilla.gecko.fxa.activities.FxAccountCreateAccountActivity;
|
||||
import org.mozilla.gecko.tabspanel.TabsPanel.PanelView;
|
||||
import org.mozilla.gecko.util.HardwareUtils;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.View;
|
||||
import android.widget.LinearLayout;
|
||||
|
||||
/**
|
||||
* A tabs panel which allows a user to get started setting up a Firefox
|
||||
* Accounts account. Currently used as one sub-panel in a sequence
|
||||
* contained by the {@link RemoteTabsPanel}.
|
||||
*/
|
||||
class RemoteTabsSetupPanel extends LinearLayout implements PanelView {
|
||||
private TabsPanel tabsPanel;
|
||||
|
||||
public RemoteTabsSetupPanel(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onFinishInflate() {
|
||||
super.onFinishInflate();
|
||||
|
||||
final View setupGetStartedButton = findViewById(R.id.remote_tabs_setup_get_started);
|
||||
setupGetStartedButton.setOnClickListener(new OnClickListener() {
|
||||
@Override
|
||||
public void onClick(final View v) {
|
||||
final Context context = getContext();
|
||||
// This Activity will redirect to the correct Activity if the
|
||||
// account is no longer in the setup state.
|
||||
final Intent intent = new Intent(context, FxAccountCreateAccountActivity.class);
|
||||
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
context.startActivity(intent);
|
||||
}
|
||||
});
|
||||
|
||||
final View setupOlderVersionLink = findViewById(R.id.remote_tabs_setup_old_sync_link);
|
||||
setupOlderVersionLink.setOnClickListener(new OnClickListener() {
|
||||
@Override
|
||||
public void onClick(final View v) {
|
||||
final String url = FirefoxAccounts.getOldSyncUpgradeURL(
|
||||
getResources(), Locale.getDefault());
|
||||
Tabs.getInstance().loadUrlInTab(url);
|
||||
if (tabsPanel != null) {
|
||||
tabsPanel.autoHidePanel();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setTabsPanel(TabsPanel panel) {
|
||||
tabsPanel = panel;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void show() {
|
||||
// We don't have a tablet implementation of this panel.
|
||||
if (HardwareUtils.isTablet()) {
|
||||
return;
|
||||
}
|
||||
|
||||
setVisibility(View.VISIBLE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void hide() {
|
||||
setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean shouldExpand() {
|
||||
return getOrientation() == LinearLayout.VERTICAL;
|
||||
}
|
||||
}
|
119
mobile/android/base/tabspanel/RemoteTabsVerificationPanel.java
Normal file
119
mobile/android/base/tabspanel/RemoteTabsVerificationPanel.java
Normal file
@ -0,0 +1,119 @@
|
||||
/* 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.tabspanel;
|
||||
|
||||
import org.mozilla.gecko.R;
|
||||
import org.mozilla.gecko.fxa.FirefoxAccounts;
|
||||
import org.mozilla.gecko.fxa.login.State;
|
||||
import org.mozilla.gecko.tabspanel.TabsPanel.PanelView;
|
||||
import org.mozilla.gecko.util.HardwareUtils;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.TextView;
|
||||
|
||||
/**
|
||||
* A tabs panel which allows a user to get resend the verification email
|
||||
* to confirm a Firefox Account. Currently used as one sub-panel in a sequence
|
||||
* contained by the {@link RemoteTabsPanel}.
|
||||
*/
|
||||
class RemoteTabsVerificationPanel extends LinearLayout implements PanelView {
|
||||
private static final String LOG_TAG = RemoteTabsVerificationPanel.class.getSimpleName();
|
||||
|
||||
private TabsPanel tabsPanel;
|
||||
|
||||
public RemoteTabsVerificationPanel(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onFinishInflate() {
|
||||
super.onFinishInflate();
|
||||
|
||||
final View resendLink = findViewById(R.id.remote_tabs_confirm_resend);
|
||||
resendLink.setOnClickListener(new OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
final State accountState = FirefoxAccounts.getFirefoxAccountState(getContext());
|
||||
final State.Action neededAction = accountState.getNeededAction();
|
||||
if (accountState.getNeededAction() != State.Action.NeedsVerification) {
|
||||
autoHideTabsPanelOnUnexpectedState("Account expected to need verification " +
|
||||
"on resend, but action was " + neededAction + " instead.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!FirefoxAccounts.resendVerificationEmail(getContext())) {
|
||||
autoHideTabsPanelOnUnexpectedState("Account DNE when resending verification email.");
|
||||
return;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void refresh() {
|
||||
final TextView verificationView =
|
||||
(TextView) findViewById(R.id.remote_tabs_confirm_verification);
|
||||
final String email = FirefoxAccounts.getFirefoxAccountEmail(getContext());
|
||||
if (email == null) {
|
||||
autoHideTabsPanelOnUnexpectedState("Account email DNE on View refresh.");
|
||||
return;
|
||||
}
|
||||
|
||||
final String text = getResources().getString(
|
||||
R.string.fxaccount_confirm_account_verification_link, email);
|
||||
verificationView.setText(text);
|
||||
}
|
||||
|
||||
/**
|
||||
* Hides the tabs panel and logs the given String.
|
||||
*
|
||||
* As the name suggests, this method should be only be used for unexpected states!
|
||||
* We hide the tabs panel on unexpected states as the best of several evils - hiding
|
||||
* the tabs panel communicates to the user, "Hey, that's a strange bug!" and, if they're
|
||||
* curious enough, will reopen the RemoteTabsPanel, refreshing its contents. Since we're
|
||||
* in a strange state, we may already be screwed, but it's better than some alternatives like:
|
||||
* * Crashing
|
||||
* * Hiding the resources which allow invalid state (e.g. resend link, email text)
|
||||
* * Attempting to refresh the RemoteTabsPanel, possibly starting an infinite loop.
|
||||
*
|
||||
* @param log The message to log.
|
||||
*/
|
||||
private void autoHideTabsPanelOnUnexpectedState(final String log) {
|
||||
Log.w(LOG_TAG, "Unexpected state: " + log + " Closing the tabs panel.");
|
||||
|
||||
if (tabsPanel != null) {
|
||||
tabsPanel.autoHidePanel();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setTabsPanel(TabsPanel panel) {
|
||||
tabsPanel = panel;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void show() {
|
||||
// We don't have a tablet implementation of this panel.
|
||||
if (HardwareUtils.isTablet()) {
|
||||
return;
|
||||
}
|
||||
|
||||
refresh();
|
||||
setVisibility(View.VISIBLE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void hide() {
|
||||
setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean shouldExpand() {
|
||||
return getOrientation() == LinearLayout.VERTICAL;
|
||||
}
|
||||
}
|
@ -7,6 +7,7 @@ package org.mozilla.gecko.tabspanel;
|
||||
|
||||
import org.mozilla.gecko.GeckoApp;
|
||||
import org.mozilla.gecko.GeckoAppShell;
|
||||
import org.mozilla.gecko.GeckoAppShell.AppStateListener;
|
||||
import org.mozilla.gecko.GeckoApplication;
|
||||
import org.mozilla.gecko.GeckoProfile;
|
||||
import org.mozilla.gecko.LightweightTheme;
|
||||
@ -23,7 +24,6 @@ import android.os.Build;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.Button;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.ImageButton;
|
||||
@ -63,6 +63,7 @@ public class TabsPanel extends LinearLayout
|
||||
private PanelView mPanelRemote;
|
||||
private RelativeLayout mFooter;
|
||||
private TabsLayoutChangeListener mLayoutChangeListener;
|
||||
private AppStateListener mAppStateListener;
|
||||
|
||||
private IconTabWidget mTabWidget;
|
||||
private static ImageButton mAddTab;
|
||||
@ -90,6 +91,24 @@ public class TabsPanel extends LinearLayout
|
||||
|
||||
LayoutInflater.from(context).inflate(R.layout.tabs_panel, this);
|
||||
initialize();
|
||||
|
||||
mAppStateListener = new AppStateListener() {
|
||||
@Override
|
||||
public void onResume() {
|
||||
if (mPanel == mPanelRemote) {
|
||||
// Refresh the remote panel.
|
||||
mPanelRemote.show();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onOrientationChanged() {
|
||||
// Remote panel is already refreshed by chrome refresh.
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPause() {}
|
||||
};
|
||||
}
|
||||
|
||||
private void initialize() {
|
||||
@ -102,7 +121,7 @@ public class TabsPanel extends LinearLayout
|
||||
mPanelPrivate = (PanelView) findViewById(R.id.private_tabs);
|
||||
mPanelPrivate.setTabsPanel(this);
|
||||
|
||||
mPanelRemote = (PanelView) findViewById(R.id.synced_tabs);
|
||||
mPanelRemote = (PanelView) findViewById(R.id.remote_tabs);
|
||||
mPanelRemote.setTabsPanel(this);
|
||||
|
||||
mFooter = (RelativeLayout) findViewById(R.id.tabs_panel_footer);
|
||||
@ -174,12 +193,14 @@ public class TabsPanel extends LinearLayout
|
||||
public void onAttachedToWindow() {
|
||||
super.onAttachedToWindow();
|
||||
mTheme.addListener(this);
|
||||
mActivity.addAppStateListener(mAppStateListener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDetachedFromWindow() {
|
||||
super.onDetachedFromWindow();
|
||||
mTheme.removeListener(this);
|
||||
mActivity.removeAppStateListener(mAppStateListener);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -9,8 +9,8 @@ import org.json.JSONArray;
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
import org.mozilla.gecko.Actions;
|
||||
import org.mozilla.gecko.Distribution;
|
||||
import org.mozilla.gecko.db.BrowserContract;
|
||||
import org.mozilla.gecko.distribution.Distribution;
|
||||
import org.mozilla.gecko.util.ThreadUtils;
|
||||
|
||||
import android.app.Activity;
|
||||
|
@ -120,6 +120,10 @@ public final class ThreadUtils {
|
||||
assertOnThread(getUiThread(), AssertBehavior.THROW);
|
||||
}
|
||||
|
||||
public static void assertNotOnUiThread() {
|
||||
assertNotOnThread(getUiThread(), AssertBehavior.THROW);
|
||||
}
|
||||
|
||||
@RobocopTarget
|
||||
public static void assertOnGeckoThread() {
|
||||
assertOnThread(sGeckoThread, AssertBehavior.THROW);
|
||||
@ -134,11 +138,19 @@ public final class ThreadUtils {
|
||||
}
|
||||
|
||||
public static void assertOnThread(final Thread expectedThread, AssertBehavior behavior) {
|
||||
assertOnThreadComparison(expectedThread, behavior, true);
|
||||
}
|
||||
|
||||
public static void assertNotOnThread(final Thread expectedThread, AssertBehavior behavior) {
|
||||
assertOnThreadComparison(expectedThread, behavior, false);
|
||||
}
|
||||
|
||||
private static void assertOnThreadComparison(final Thread expectedThread, AssertBehavior behavior, boolean expected) {
|
||||
final Thread currentThread = Thread.currentThread();
|
||||
final long currentThreadId = currentThread.getId();
|
||||
final long expectedThreadId = expectedThread.getId();
|
||||
|
||||
if (currentThreadId == expectedThreadId) {
|
||||
if ((currentThreadId == expectedThreadId) == expected) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -21,7 +21,7 @@
|
||||
package org.mozilla.gecko.widget;
|
||||
|
||||
// Mozilla: New import
|
||||
import org.mozilla.gecko.Distribution;
|
||||
import org.mozilla.gecko.distribution.Distribution;
|
||||
import org.mozilla.gecko.GeckoProfile;
|
||||
import java.io.File;
|
||||
|
||||
|
@ -1435,11 +1435,19 @@ var BrowserApp = {
|
||||
|
||||
case "Tab:Load": {
|
||||
let data = JSON.parse(aData);
|
||||
let url = data.url;
|
||||
let flags;
|
||||
|
||||
if (/^[0-9]+$/.test(url)) {
|
||||
// If the query is a number, force a search (see bug 993705; workaround for bug 693808).
|
||||
url = URIFixup.keywordToURI(url).spec;
|
||||
} else {
|
||||
flags |= Ci.nsIWebNavigation.LOAD_FLAGS_ALLOW_THIRD_PARTY_FIXUP |
|
||||
Ci.nsIWebNavigation.LOAD_FLAGS_FIXUP_SCHEME_TYPOS;
|
||||
}
|
||||
|
||||
// Pass LOAD_FLAGS_DISALLOW_INHERIT_OWNER to prevent any loads from
|
||||
// inheriting the currently loaded document's principal.
|
||||
let flags = Ci.nsIWebNavigation.LOAD_FLAGS_ALLOW_THIRD_PARTY_FIXUP |
|
||||
Ci.nsIWebNavigation.LOAD_FLAGS_FIXUP_SCHEME_TYPOS;
|
||||
if (data.userEntered) {
|
||||
flags |= Ci.nsIWebNavigation.LOAD_FLAGS_DISALLOW_INHERIT_OWNER;
|
||||
}
|
||||
@ -1456,7 +1464,6 @@ var BrowserApp = {
|
||||
desktopMode: (data.desktopMode === true)
|
||||
};
|
||||
|
||||
let url = data.url;
|
||||
if (data.engine) {
|
||||
let engine = Services.search.getEngineByName(data.engine);
|
||||
if (engine) {
|
||||
|
@ -4,6 +4,7 @@
|
||||
android:clearTaskOnLaunch="true"
|
||||
android:taskAffinity="@ANDROID_PACKAGE_NAME@.FXA"
|
||||
android:name="org.mozilla.gecko.fxa.activities.FxAccountStatusActivity"
|
||||
android:configChanges="locale|layoutDirection"
|
||||
android:windowSoftInputMode="adjustResize">
|
||||
<!-- Adding a launcher will make this activity appear on the
|
||||
Apps screen, which we only want when testing. -->
|
||||
@ -19,6 +20,7 @@
|
||||
android:clearTaskOnLaunch="true"
|
||||
android:taskAffinity="@ANDROID_PACKAGE_NAME@.FXA"
|
||||
android:name="org.mozilla.gecko.fxa.activities.FxAccountGetStartedActivity"
|
||||
android:configChanges="locale|layoutDirection"
|
||||
android:windowSoftInputMode="adjustResize">
|
||||
<!-- Adding a launcher will make this activity appear on the
|
||||
Apps screen, which we only want when testing. -->
|
||||
@ -31,12 +33,14 @@
|
||||
<activity
|
||||
android:theme="@style/FxAccountTheme"
|
||||
android:name="org.mozilla.gecko.fxa.activities.FxAccountCreateAccountActivity"
|
||||
android:configChanges="locale|layoutDirection"
|
||||
android:windowSoftInputMode="adjustResize">
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
android:theme="@style/FxAccountTheme"
|
||||
android:name="org.mozilla.gecko.fxa.activities.FxAccountConfirmAccountActivity"
|
||||
android:configChanges="locale|layoutDirection"
|
||||
android:noHistory="true"
|
||||
android:windowSoftInputMode="adjustResize">
|
||||
</activity>
|
||||
@ -44,12 +48,14 @@
|
||||
<activity
|
||||
android:theme="@style/FxAccountTheme"
|
||||
android:name="org.mozilla.gecko.fxa.activities.FxAccountSignInActivity"
|
||||
android:configChanges="locale|layoutDirection"
|
||||
android:windowSoftInputMode="adjustResize">
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
android:theme="@style/FxAccountTheme"
|
||||
android:name="org.mozilla.gecko.fxa.activities.FxAccountVerifiedAccountActivity"
|
||||
android:configChanges="locale|layoutDirection"
|
||||
android:noHistory="true"
|
||||
android:windowSoftInputMode="adjustResize">
|
||||
</activity>
|
||||
@ -57,12 +63,14 @@
|
||||
<activity
|
||||
android:theme="@style/FxAccountTheme"
|
||||
android:name="org.mozilla.gecko.fxa.activities.FxAccountUpdateCredentialsActivity"
|
||||
android:configChanges="locale|layoutDirection"
|
||||
android:windowSoftInputMode="adjustResize">
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
android:theme="@style/FxAccountTheme"
|
||||
android:name="org.mozilla.gecko.fxa.activities.FxAccountCreateAccountNotAllowedActivity"
|
||||
android:configChanges="locale|layoutDirection"
|
||||
android:noHistory="true"
|
||||
android:windowSoftInputMode="adjustResize">
|
||||
</activity>
|
||||
|
@ -79,7 +79,7 @@
|
||||
android:excludeFromRecents="true"
|
||||
android:icon="@drawable/icon"
|
||||
android:label="@string/sync_title_send_tab"
|
||||
android:configChanges="keyboardHidden|orientation|screenSize"
|
||||
android:configChanges="keyboardHidden|orientation|screenSize|locale|layoutDirection"
|
||||
android:windowSoftInputMode="adjustResize|stateHidden"
|
||||
android:taskAffinity="org.mozilla.gecko.sync.setup"
|
||||
android:name="org.mozilla.gecko.sync.setup.activities.SendTabActivity" >
|
||||
|
@ -8,6 +8,11 @@
|
||||
|
||||
var kSignonBundle;
|
||||
var showingPasswords = false;
|
||||
var dateFormatter = new Intl.DateTimeFormat(undefined,
|
||||
{ day: "numeric", month: "short", year: "numeric" });
|
||||
var dateAndTimeFormatter = new Intl.DateTimeFormat(undefined,
|
||||
{ day: "numeric", month: "short", year: "numeric",
|
||||
hour: "numeric", minute: "numeric" });
|
||||
|
||||
function SignonsStartup() {
|
||||
kSignonBundle = document.getElementById("signonBundle");
|
||||
@ -41,6 +46,7 @@ var signonsTreeView = {
|
||||
getProgressMode : function(row,column) {},
|
||||
getCellValue : function(row,column) {},
|
||||
getCellText : function(row,column) {
|
||||
var time;
|
||||
var signon = this._filterSet.length ? this._filterSet[row] : signons[row];
|
||||
switch (column.id) {
|
||||
case "siteCol":
|
||||
@ -51,6 +57,17 @@ var signonsTreeView = {
|
||||
return signon.username || "";
|
||||
case "passwordCol":
|
||||
return signon.password || "";
|
||||
case "timeCreatedCol":
|
||||
time = new Date(signon.timeCreated);
|
||||
return dateFormatter.format(time);
|
||||
case "timeLastUsedCol":
|
||||
time = new Date(signon.timeLastUsed);
|
||||
return dateAndTimeFormatter.format(time);
|
||||
case "timePasswordChangedCol":
|
||||
time = new Date(signon.timePasswordChanged);
|
||||
return dateFormatter.format(time);
|
||||
case "timesUsedCol":
|
||||
return signon.timesUsed;
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
@ -77,6 +94,7 @@ function LoadSignons() {
|
||||
} catch (e) {
|
||||
signons = [];
|
||||
}
|
||||
signons.forEach(login => login.QueryInterface(Components.interfaces.nsILoginMetaInfo));
|
||||
signonsTreeView.rowCount = signons.length;
|
||||
|
||||
// sort and display the table
|
||||
@ -197,6 +215,14 @@ function getColumnByName(column) {
|
||||
return document.getElementById("userCol");
|
||||
case "password":
|
||||
return document.getElementById("passwordCol");
|
||||
case "timeCreated":
|
||||
return document.getElementById("timeCreatedCol");
|
||||
case "timeLastUsed":
|
||||
return document.getElementById("timeLastUsedCol");
|
||||
case "timePasswordChanged":
|
||||
return document.getElementById("timePasswordChangedCol");
|
||||
case "timesUsed":
|
||||
return document.getElementById("timesUsedCol");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -54,21 +54,41 @@
|
||||
|
||||
<label control="signonsTree" id="signonsIntro"/>
|
||||
<separator class="thin"/>
|
||||
<tree id="signonsTree" flex="1" style="height: 20em;" hidecolumnpicker="true"
|
||||
<tree id="signonsTree" flex="1"
|
||||
width="750"
|
||||
style="height: 20em;"
|
||||
onkeypress="HandleSignonKeyPress(event)"
|
||||
onselect="SignonSelected();"
|
||||
context="signonsTreeContextMenu">
|
||||
<treecols>
|
||||
<treecol id="siteCol" label="&treehead.site.label;" flex="5"
|
||||
<treecol id="siteCol" label="&treehead.site.label;" flex="40"
|
||||
onclick="SignonColumnSort('hostname');" persist="width"
|
||||
ignoreincolumnpicker="true"
|
||||
sortDirection="ascending"/>
|
||||
<splitter class="tree-splitter"/>
|
||||
<treecol id="userCol" label="&treehead.username.label;" flex="2"
|
||||
<treecol id="userCol" label="&treehead.username.label;" flex="25"
|
||||
ignoreincolumnpicker="true"
|
||||
onclick="SignonColumnSort('username');" persist="width"/>
|
||||
<splitter class="tree-splitter"/>
|
||||
<treecol id="passwordCol" label="&treehead.password.label;" flex="2"
|
||||
<treecol id="passwordCol" label="&treehead.password.label;" flex="15"
|
||||
ignoreincolumnpicker="true"
|
||||
onclick="SignonColumnSort('password');" persist="width"
|
||||
hidden="true"/>
|
||||
<splitter class="tree-splitter"/>
|
||||
<treecol id="timeCreatedCol" label="&treehead.timeCreated.label;" flex="10"
|
||||
onclick="SignonColumnSort('timeCreated');" persist="width hidden"
|
||||
hidden="true"/>
|
||||
<splitter class="tree-splitter"/>
|
||||
<treecol id="timeLastUsedCol" label="&treehead.timeLastUsed.label;" flex="20"
|
||||
onclick="SignonColumnSort('timeLastUsed');" persist="width hidden"/>
|
||||
<splitter class="tree-splitter"/>
|
||||
<treecol id="timePasswordChangedCol" label="&treehead.timePasswordChanged.label;" flex="10"
|
||||
onclick="SignonColumnSort('timePasswordChanged');" persist="width hidden"/>
|
||||
<splitter class="tree-splitter"/>
|
||||
<treecol id="timesUsedCol" label="&treehead.timesUsed.label;" flex="1"
|
||||
onclick="SignonColumnSort('timesUsed');" persist="width hidden"
|
||||
hidden="true"/>
|
||||
<splitter class="tree-splitter"/>
|
||||
</treecols>
|
||||
<treechildren/>
|
||||
</tree>
|
||||
|
@ -169,18 +169,40 @@ function SortTree(tree, view, table, column, lastSortColumn, lastSortAscending,
|
||||
// determine if sort is to be ascending or descending
|
||||
var ascending = (column == lastSortColumn) ? !lastSortAscending : true;
|
||||
|
||||
// do the sort
|
||||
var compareFunc;
|
||||
if (ascending) {
|
||||
compareFunc = function compare(first, second) {
|
||||
return CompareLowerCase(first[column], second[column]);
|
||||
}
|
||||
} else {
|
||||
compareFunc = function compare(first, second) {
|
||||
return CompareLowerCase(second[column], first[column]);
|
||||
function compareFunc(a, b) {
|
||||
var valA, valB;
|
||||
switch (column) {
|
||||
case "hostname":
|
||||
var realmA = a.httpRealm;
|
||||
var realmB = b.httpRealm;
|
||||
realmA = realmA == null ? "" : realmA.toLowerCase();
|
||||
realmB = realmB == null ? "" : realmB.toLowerCase();
|
||||
|
||||
valA = a[column].toLowerCase() + realmA;
|
||||
valB = b[column].toLowerCase() + realmB;
|
||||
break;
|
||||
case "username":
|
||||
case "password":
|
||||
valA = a[column].toLowerCase();
|
||||
valB = b[column].toLowerCase();
|
||||
break;
|
||||
|
||||
default:
|
||||
valA = a[column];
|
||||
valB = b[column];
|
||||
}
|
||||
|
||||
if (valA < valB)
|
||||
return -1;
|
||||
if (valA > valB)
|
||||
return 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
// do the sort
|
||||
table.sort(compareFunc);
|
||||
if (!ascending)
|
||||
table.reverse();
|
||||
|
||||
// restore the selection
|
||||
var selectedRow = -1;
|
||||
@ -206,28 +228,3 @@ function SortTree(tree, view, table, column, lastSortColumn, lastSortAscending,
|
||||
return ascending;
|
||||
}
|
||||
|
||||
/**
|
||||
* Case insensitive string comparator.
|
||||
*/
|
||||
function CompareLowerCase(first, second) {
|
||||
var firstLower, secondLower;
|
||||
|
||||
// Are we sorting nsILoginInfo entries or just strings?
|
||||
if (first.hostname) {
|
||||
firstLower = first.hostname.toLowerCase();
|
||||
secondLower = second.hostname.toLowerCase();
|
||||
} else {
|
||||
firstLower = first.toLowerCase();
|
||||
secondLower = second.toLowerCase();
|
||||
}
|
||||
|
||||
if (firstLower < secondLower) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (firstLower > secondLower) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
@ -5392,6 +5392,11 @@
|
||||
"n_buckets": "1000",
|
||||
"description": "The time (in milliseconds) that it took an 'assign' request to go round trip."
|
||||
},
|
||||
"DEVTOOLS_TOOLBOX_OPENED_BOOLEAN": {
|
||||
"expires_in_version": "never",
|
||||
"kind": "boolean",
|
||||
"description": "How many times has the devtool's toolbox been opened?"
|
||||
},
|
||||
"DEVTOOLS_OPTIONS_OPENED_BOOLEAN": {
|
||||
"expires_in_version": "never",
|
||||
"kind": "boolean",
|
||||
@ -5492,6 +5497,11 @@
|
||||
"kind": "boolean",
|
||||
"description": "How many times has a custom developer tool been opened via the toolbox button?"
|
||||
},
|
||||
"DEVTOOLS_TOOLBOX_OPENED_PER_USER_FLAG": {
|
||||
"expires_in_version": "never",
|
||||
"kind": "flag",
|
||||
"description": "How many times has the devtool's toolbox been opened?"
|
||||
},
|
||||
"DEVTOOLS_OPTIONS_OPENED_PER_USER_FLAG": {
|
||||
"expires_in_version": "never",
|
||||
"kind": "flag",
|
||||
|
@ -12,6 +12,11 @@
|
||||
<!ENTITY treehead.site.label "Site">
|
||||
<!ENTITY treehead.username.label "Username">
|
||||
<!ENTITY treehead.password.label "Password">
|
||||
<!ENTITY treehead.timeCreated.label "First Used">
|
||||
<!ENTITY treehead.timeLastUsed.label "Last Used">
|
||||
<!ENTITY treehead.timePasswordChanged.label "Last Changed">
|
||||
<!ENTITY treehead.timesUsed.label "Times Used">
|
||||
|
||||
<!ENTITY remove.label "Remove">
|
||||
<!ENTITY remove.accesskey "R">
|
||||
<!ENTITY removeall.label "Remove All">
|
||||
|
Loading…
Reference in New Issue
Block a user