mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
Merge mozilla-central to mozilla-inbound
This commit is contained in:
commit
c98ef60cc1
@ -19,7 +19,7 @@
|
||||
<copyfile dest="Makefile" src="core/root.mk"/>
|
||||
</project>
|
||||
<project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
|
||||
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="6df4533f14ec2645bb13d8a803a5151583ca13a8"/>
|
||||
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="71f78f7f68fcf5e23cc5965fee0dad45289c438b"/>
|
||||
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="15e8982284c4560f9c74c2b9fe8bb361ebfe0cb6"/>
|
||||
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
|
||||
<project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="d11f524d00cacf5ba0dfbf25e4aa2158b1c3a036"/>
|
||||
|
@ -17,7 +17,7 @@
|
||||
</project>
|
||||
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
|
||||
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
|
||||
<project name="gaia" path="gaia" remote="mozillaorg" revision="6df4533f14ec2645bb13d8a803a5151583ca13a8"/>
|
||||
<project name="gaia" path="gaia" remote="mozillaorg" revision="71f78f7f68fcf5e23cc5965fee0dad45289c438b"/>
|
||||
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="15e8982284c4560f9c74c2b9fe8bb361ebfe0cb6"/>
|
||||
<project name="moztt" path="external/moztt" remote="b2g" revision="3d5c964015967ca8c86abe6dbbebee3cb82b1609"/>
|
||||
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="b2f773d8320d30648b89767dfe5b25ef94bc7e62"/>
|
||||
|
@ -19,7 +19,7 @@
|
||||
<copyfile dest="Makefile" src="core/root.mk"/>
|
||||
</project>
|
||||
<project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
|
||||
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="6df4533f14ec2645bb13d8a803a5151583ca13a8"/>
|
||||
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="71f78f7f68fcf5e23cc5965fee0dad45289c438b"/>
|
||||
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="15e8982284c4560f9c74c2b9fe8bb361ebfe0cb6"/>
|
||||
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
|
||||
<project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="d11f524d00cacf5ba0dfbf25e4aa2158b1c3a036"/>
|
||||
|
@ -4,6 +4,6 @@
|
||||
"branch": "",
|
||||
"revision": ""
|
||||
},
|
||||
"revision": "ac8b988b122a95d657c730dcdfabbebf03385b43",
|
||||
"revision": "020bf0df084e3e80e51f9dfd5fdeef2e8ab90452",
|
||||
"repo_path": "/integration/gaia-central"
|
||||
}
|
||||
|
@ -17,7 +17,7 @@
|
||||
<copyfile dest="Makefile" src="core/root.mk"/>
|
||||
</project>
|
||||
<project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
|
||||
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="6df4533f14ec2645bb13d8a803a5151583ca13a8"/>
|
||||
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="71f78f7f68fcf5e23cc5965fee0dad45289c438b"/>
|
||||
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="15e8982284c4560f9c74c2b9fe8bb361ebfe0cb6"/>
|
||||
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
|
||||
<project name="librecovery" path="librecovery" remote="b2g" revision="84f2f2fce22605e17d511ff1767e54770067b5b5"/>
|
||||
|
@ -15,7 +15,7 @@
|
||||
<copyfile dest="Makefile" src="core/root.mk"/>
|
||||
</project>
|
||||
<project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
|
||||
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="6df4533f14ec2645bb13d8a803a5151583ca13a8"/>
|
||||
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="71f78f7f68fcf5e23cc5965fee0dad45289c438b"/>
|
||||
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="15e8982284c4560f9c74c2b9fe8bb361ebfe0cb6"/>
|
||||
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
|
||||
<project name="librecovery" path="librecovery" remote="b2g" revision="84f2f2fce22605e17d511ff1767e54770067b5b5"/>
|
||||
|
@ -19,7 +19,7 @@
|
||||
<copyfile dest="Makefile" src="core/root.mk"/>
|
||||
</project>
|
||||
<project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
|
||||
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="6df4533f14ec2645bb13d8a803a5151583ca13a8"/>
|
||||
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="71f78f7f68fcf5e23cc5965fee0dad45289c438b"/>
|
||||
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="15e8982284c4560f9c74c2b9fe8bb361ebfe0cb6"/>
|
||||
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
|
||||
<project name="librecovery" path="librecovery" remote="b2g" revision="84f2f2fce22605e17d511ff1767e54770067b5b5"/>
|
||||
|
@ -17,7 +17,7 @@
|
||||
<copyfile dest="Makefile" src="core/root.mk"/>
|
||||
</project>
|
||||
<project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
|
||||
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="6df4533f14ec2645bb13d8a803a5151583ca13a8"/>
|
||||
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="71f78f7f68fcf5e23cc5965fee0dad45289c438b"/>
|
||||
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="15e8982284c4560f9c74c2b9fe8bb361ebfe0cb6"/>
|
||||
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
|
||||
<project name="librecovery" path="librecovery" remote="b2g" revision="84f2f2fce22605e17d511ff1767e54770067b5b5"/>
|
||||
|
@ -17,7 +17,7 @@
|
||||
</project>
|
||||
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
|
||||
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
|
||||
<project name="gaia" path="gaia" remote="mozillaorg" revision="6df4533f14ec2645bb13d8a803a5151583ca13a8"/>
|
||||
<project name="gaia" path="gaia" remote="mozillaorg" revision="71f78f7f68fcf5e23cc5965fee0dad45289c438b"/>
|
||||
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="15e8982284c4560f9c74c2b9fe8bb361ebfe0cb6"/>
|
||||
<project name="moztt" path="external/moztt" remote="b2g" revision="3d5c964015967ca8c86abe6dbbebee3cb82b1609"/>
|
||||
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="b2f773d8320d30648b89767dfe5b25ef94bc7e62"/>
|
||||
|
@ -17,7 +17,7 @@
|
||||
<copyfile dest="Makefile" src="core/root.mk"/>
|
||||
</project>
|
||||
<project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
|
||||
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="6df4533f14ec2645bb13d8a803a5151583ca13a8"/>
|
||||
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="71f78f7f68fcf5e23cc5965fee0dad45289c438b"/>
|
||||
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="15e8982284c4560f9c74c2b9fe8bb361ebfe0cb6"/>
|
||||
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
|
||||
<project name="librecovery" path="librecovery" remote="b2g" revision="84f2f2fce22605e17d511ff1767e54770067b5b5"/>
|
||||
|
@ -51,6 +51,7 @@ this.UITour = {
|
||||
pinnedTabs: new WeakMap(),
|
||||
urlbarCapture: new WeakMap(),
|
||||
appMenuOpenForAnnotation: new Set(),
|
||||
availableTargetsCache: new WeakMap(),
|
||||
|
||||
_detachingTab: false,
|
||||
_queuedEvents: [],
|
||||
@ -105,7 +106,7 @@ this.UITour = {
|
||||
let element = aDocument.getAnonymousElementByAttribute(selectedtab,
|
||||
"anonid",
|
||||
"tab-icon-image");
|
||||
if (!element || !this.isElementVisible(element)) {
|
||||
if (!element || !UITour.isElementVisible(element)) {
|
||||
return null;
|
||||
}
|
||||
return element;
|
||||
@ -128,6 +129,19 @@ this.UITour = {
|
||||
|
||||
UITelemetry.addSimpleMeasureFunction("UITour",
|
||||
this.getTelemetry.bind(this));
|
||||
|
||||
// Clear the availableTargetsCache on widget changes.
|
||||
let listenerMethods = [
|
||||
"onWidgetAdded",
|
||||
"onWidgetMoved",
|
||||
"onWidgetRemoved",
|
||||
"onWidgetReset",
|
||||
"onAreaReset",
|
||||
];
|
||||
CustomizableUI.addListener(listenerMethods.reduce((listener, method) => {
|
||||
listener[method] = () => this.availableTargetsCache.clear();
|
||||
return listener;
|
||||
}, {}));
|
||||
},
|
||||
|
||||
restoreSeenPageIDs: function() {
|
||||
@ -920,8 +934,8 @@ this.UITour = {
|
||||
},
|
||||
|
||||
showMenu: function(aWindow, aMenuName, aOpenCallback = null) {
|
||||
function openMenuButton(aId) {
|
||||
let menuBtn = aWindow.document.getElementById(aId);
|
||||
function openMenuButton(aID) {
|
||||
let menuBtn = aWindow.document.getElementById(aID);
|
||||
if (!menuBtn || !menuBtn.boxObject) {
|
||||
aOpenCallback();
|
||||
return;
|
||||
@ -953,8 +967,8 @@ this.UITour = {
|
||||
},
|
||||
|
||||
hideMenu: function(aWindow, aMenuName) {
|
||||
function closeMenuButton(aId) {
|
||||
let menuBtn = aWindow.document.getElementById(aId);
|
||||
function closeMenuButton(aID) {
|
||||
let menuBtn = aWindow.document.getElementById(aID);
|
||||
if (menuBtn && menuBtn.boxObject)
|
||||
menuBtn.boxObject.QueryInterface(Ci.nsIMenuBoxObject).openMenu(false);
|
||||
}
|
||||
@ -1042,19 +1056,53 @@ this.UITour = {
|
||||
aWindow.gBrowser.selectedTab = tab;
|
||||
},
|
||||
|
||||
getConfiguration: function(aContentDocument, aConfiguration, aCallbackId) {
|
||||
let config = null;
|
||||
getConfiguration: function(aContentDocument, aConfiguration, aCallbackID) {
|
||||
switch (aConfiguration) {
|
||||
case "availableTargets":
|
||||
this.getAvailableTargets(aContentDocument, aCallbackID);
|
||||
break;
|
||||
case "sync":
|
||||
config = {
|
||||
this.sendPageCallback(aContentDocument, aCallbackID, {
|
||||
setup: Services.prefs.prefHasUserValue("services.sync.username"),
|
||||
};
|
||||
});
|
||||
break;
|
||||
default:
|
||||
Cu.reportError("getConfiguration: Unknown configuration requested: " + aConfiguration);
|
||||
break;
|
||||
}
|
||||
this.sendPageCallback(aContentDocument, aCallbackId, config);
|
||||
},
|
||||
|
||||
getAvailableTargets: function(aContentDocument, aCallbackID) {
|
||||
let window = this.getChromeWindow(aContentDocument);
|
||||
let data = this.availableTargetsCache.get(window);
|
||||
if (data) {
|
||||
this.sendPageCallback(aContentDocument, aCallbackID, data);
|
||||
return;
|
||||
}
|
||||
|
||||
let promises = [];
|
||||
for (let targetName of this.targets.keys()) {
|
||||
promises.push(this.getTarget(window, targetName));
|
||||
}
|
||||
Promise.all(promises).then((targetObjects) => {
|
||||
let targetNames = [
|
||||
"pinnedTab",
|
||||
];
|
||||
for (let targetObject of targetObjects) {
|
||||
if (targetObject.node)
|
||||
targetNames.push(targetObject.targetName);
|
||||
}
|
||||
let data = {
|
||||
targets: targetNames,
|
||||
};
|
||||
this.availableTargetsCache.set(window, data);
|
||||
this.sendPageCallback(aContentDocument, aCallbackID, data);
|
||||
}, (err) => {
|
||||
Cu.reportError(err);
|
||||
this.sendPageCallback(aContentDocument, aCallbackID, {
|
||||
targets: [],
|
||||
});
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
|
@ -11,6 +11,7 @@ support-files =
|
||||
skip-if = os == "linux" # Intermittent failures, bug 951965
|
||||
[browser_UITour2.js]
|
||||
[browser_UITour3.js]
|
||||
[browser_UITour_availableTargets.js]
|
||||
[browser_UITour_panel_close_annotation.js]
|
||||
[browser_UITour_detach_tab.js]
|
||||
[browser_UITour_registerPageID.js]
|
||||
|
83
browser/modules/test/browser_UITour_availableTargets.js
Normal file
83
browser/modules/test/browser_UITour_availableTargets.js
Normal file
@ -0,0 +1,83 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
let gTestTab;
|
||||
let gContentAPI;
|
||||
let gContentWindow;
|
||||
|
||||
Components.utils.import("resource:///modules/UITour.jsm");
|
||||
|
||||
function test() {
|
||||
UITourTest();
|
||||
}
|
||||
|
||||
let tests = [
|
||||
function test_availableTargets(done) {
|
||||
gContentAPI.getConfiguration("availableTargets", (data) => {
|
||||
ok_targets(data, [
|
||||
"accountStatus",
|
||||
"addons",
|
||||
"appMenu",
|
||||
"backForward",
|
||||
"bookmarks",
|
||||
"customize",
|
||||
"help",
|
||||
"home",
|
||||
"pinnedTab",
|
||||
"quit",
|
||||
"search",
|
||||
"searchProvider",
|
||||
"urlbar",
|
||||
]);
|
||||
ok(UITour.availableTargetsCache.has(window),
|
||||
"Targets should now be cached");
|
||||
done();
|
||||
});
|
||||
},
|
||||
|
||||
function test_availableTargets_changeWidgets(done) {
|
||||
CustomizableUI.removeWidgetFromArea("bookmarks-menu-button");
|
||||
ok(!UITour.availableTargetsCache.has(window),
|
||||
"Targets should be evicted from cache after widget change");
|
||||
gContentAPI.getConfiguration("availableTargets", (data) => {
|
||||
ok_targets(data, [
|
||||
"accountStatus",
|
||||
"addons",
|
||||
"appMenu",
|
||||
"backForward",
|
||||
"customize",
|
||||
"help",
|
||||
"home",
|
||||
"pinnedTab",
|
||||
"quit",
|
||||
"search",
|
||||
"searchProvider",
|
||||
"urlbar",
|
||||
]);
|
||||
ok(UITour.availableTargetsCache.has(window),
|
||||
"Targets should now be cached again");
|
||||
CustomizableUI.reset();
|
||||
ok(!UITour.availableTargetsCache.has(window),
|
||||
"Targets should not be cached after reset");
|
||||
done();
|
||||
});
|
||||
},
|
||||
];
|
||||
|
||||
function ok_targets(actualData, expectedTargets) {
|
||||
// Depending on how soon after page load this is called, the selected tab icon
|
||||
// may or may not be showing the loading throbber. Check for its presence and
|
||||
// insert it into expectedTargets if it's visible.
|
||||
let selectedTabIcon =
|
||||
document.getAnonymousElementByAttribute(gBrowser.selectedTab,
|
||||
"anonid",
|
||||
"tab-icon-image");
|
||||
if (selectedTabIcon && UITour.isElementVisible(selectedTabIcon))
|
||||
expectedTargets.push("selectedTabIcon");
|
||||
|
||||
ok(Array.isArray(actualData.targets), "data.targets should be an array");
|
||||
is(actualData.targets.sort().toString(), expectedTargets.sort().toString(),
|
||||
"Targets should be as expected");
|
||||
}
|
@ -23,7 +23,7 @@ let tests = [
|
||||
done();
|
||||
}
|
||||
|
||||
gContentAPI.getSyncConfiguration(callback);
|
||||
gContentAPI.getConfiguration("sync", callback);
|
||||
},
|
||||
|
||||
function test_checkSyncSetup_enabled(done) {
|
||||
@ -33,6 +33,6 @@ let tests = [
|
||||
}
|
||||
|
||||
Services.prefs.setCharPref("services.sync.username", "uitour@tests.mozilla.org");
|
||||
gContentAPI.getSyncConfiguration(callback);
|
||||
gContentAPI.getConfiguration("sync", callback);
|
||||
},
|
||||
];
|
||||
|
@ -160,10 +160,10 @@ if (typeof Mozilla == 'undefined') {
|
||||
});
|
||||
};
|
||||
|
||||
Mozilla.UITour.getSyncConfiguration = function(callback) {
|
||||
Mozilla.UITour.getConfiguration = function(configName, callback) {
|
||||
_sendEvent('getConfiguration', {
|
||||
callbackID: _waitForCallback(callback),
|
||||
configuration: "sync",
|
||||
configuration: configName,
|
||||
});
|
||||
};
|
||||
})();
|
||||
|
@ -167,7 +167,7 @@ toolbarpaletteitem[place="palette"] > #personal-bookmarks > #bookmarks-toolbar-p
|
||||
#bookmarked-notification-anchor[notification="finish"] > #bookmarked-notification {
|
||||
background-image: url("chrome://browser/skin/places/bookmarks-notification-finish.png");
|
||||
animation: animation-bookmarkAdded 800ms;
|
||||
animation-timing-function: ease ease ease linear;
|
||||
animation-timing-function: ease, ease, ease;
|
||||
}
|
||||
|
||||
#bookmarked-notification-anchor[notification="finish"][in-bookmarks-toolbar=true] > #bookmarked-notification {
|
||||
|
@ -404,7 +404,7 @@ toolbarpaletteitem[place="palette"] > #personal-bookmarks > #bookmarks-toolbar-p
|
||||
#bookmarked-notification-anchor[notification="finish"] > #bookmarked-notification {
|
||||
background-image: url("chrome://browser/skin/places/bookmarks-notification-finish.png");
|
||||
animation: animation-bookmarkAdded 800ms;
|
||||
animation-timing-function: ease ease ease linear;
|
||||
animation-timing-function: ease, ease, ease;
|
||||
}
|
||||
|
||||
#bookmarked-notification-anchor[notification="finish"][in-bookmarks-toolbar=true] > #bookmarked-notification {
|
||||
|
@ -354,7 +354,7 @@ toolbarpaletteitem[place="palette"] > #personal-bookmarks > #bookmarks-toolbar-p
|
||||
#bookmarked-notification-anchor[notification="finish"] > #bookmarked-notification {
|
||||
background-image: url("chrome://browser/skin/places/bookmarks-notification-finish.png");
|
||||
animation: animation-bookmarkAdded 800ms;
|
||||
animation-timing-function: ease ease ease linear;
|
||||
animation-timing-function: ease, ease, ease;
|
||||
}
|
||||
|
||||
#bookmarks-menu-button[notification="finish"] > .toolbarbutton-menubutton-dropmarker > .dropmarker-icon {
|
||||
|
@ -511,6 +511,16 @@ char* Pickle::BeginWrite(uint32_t length, uint32_t alignment) {
|
||||
DCHECK(intptr_t(buffer) % alignment == 0);
|
||||
|
||||
header_->payload_size = new_size;
|
||||
|
||||
#ifdef MOZ_VALGRIND
|
||||
// pad the trailing end as well, so that valgrind
|
||||
// doesn't complain when we write the buffer
|
||||
padding = AlignInt(length) - length;
|
||||
if (padding) {
|
||||
memset(buffer + length, kBytePaddingMarker, padding);
|
||||
}
|
||||
#endif
|
||||
|
||||
return buffer;
|
||||
}
|
||||
|
||||
|
@ -14,6 +14,7 @@ import java.util.Vector;
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
import org.mozilla.gecko.GeckoProfileDirectories.NoMozillaDirectoryException;
|
||||
import org.mozilla.gecko.animation.PropertyAnimator;
|
||||
import org.mozilla.gecko.animation.ViewHelper;
|
||||
import org.mozilla.gecko.db.BrowserContract.Combined;
|
||||
@ -2107,7 +2108,7 @@ abstract public class BrowserApp extends GeckoApp
|
||||
MenuItem desktopMode = aMenu.findItem(R.id.desktop_mode);
|
||||
MenuItem enterGuestMode = aMenu.findItem(R.id.new_guest_session);
|
||||
MenuItem exitGuestMode = aMenu.findItem(R.id.exit_guest_session);
|
||||
MenuItem subscribe = aMenu.findItem(R.id.subscribe);
|
||||
MenuItem subscribe = aMenu.findItem(R.id.save_subscribe);
|
||||
MenuItem addToReadingList = aMenu.findItem(R.id.reading_list_add);
|
||||
MenuItem save = aMenu.findItem(R.id.save);
|
||||
|
||||
@ -2383,7 +2384,7 @@ abstract public class BrowserApp extends GeckoApp
|
||||
return true;
|
||||
}
|
||||
|
||||
if (itemId == R.id.subscribe) {
|
||||
if (itemId == R.id.subscribe || itemId == R.id.save_subscribe) {
|
||||
subscribeToFeeds(tab);
|
||||
return true;
|
||||
}
|
||||
@ -2615,9 +2616,8 @@ abstract public class BrowserApp extends GeckoApp
|
||||
public int getLayout() { return R.layout.gecko_app; }
|
||||
|
||||
@Override
|
||||
protected String getDefaultProfileName() {
|
||||
String profile = GeckoProfile.findDefaultProfile(this);
|
||||
return (profile != null ? profile : GeckoProfile.DEFAULT_PROFILE);
|
||||
protected String getDefaultProfileName() throws NoMozillaDirectoryException {
|
||||
return GeckoProfile.getDefaultProfileName(this);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -30,6 +30,7 @@ import java.util.regex.Pattern;
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
import org.mozilla.gecko.GeckoProfileDirectories.NoMozillaDirectoryException;
|
||||
import org.mozilla.gecko.background.announcements.AnnouncementsBroadcastService;
|
||||
import org.mozilla.gecko.db.BrowserDB;
|
||||
import org.mozilla.gecko.favicons.Favicons;
|
||||
@ -208,7 +209,7 @@ public abstract class GeckoApp
|
||||
|
||||
abstract public int getLayout();
|
||||
abstract public boolean hasTabsSideBar();
|
||||
abstract protected String getDefaultProfileName();
|
||||
abstract protected String getDefaultProfileName() throws NoMozillaDirectoryException;
|
||||
|
||||
private static final String RESTARTER_ACTION = "org.mozilla.gecko.restart";
|
||||
private static final String RESTARTER_CLASS = "org.mozilla.gecko.Restarter";
|
||||
@ -1199,7 +1200,15 @@ public abstract class GeckoApp
|
||||
profilePath = m.group(1);
|
||||
}
|
||||
if (profileName == null) {
|
||||
profileName = getDefaultProfileName();
|
||||
try {
|
||||
profileName = getDefaultProfileName();
|
||||
} catch (NoMozillaDirectoryException e) {
|
||||
Log.wtf(LOGTAG, "Unable to fetch default profile name!", e);
|
||||
// There's nothing at all we can do now. If the Mozilla directory
|
||||
// didn't exist, then we're screwed.
|
||||
// Crash here so we can fix the bug.
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
if (profileName == null)
|
||||
profileName = GeckoProfile.DEFAULT_PROFILE;
|
||||
}
|
||||
|
@ -5,6 +5,8 @@
|
||||
|
||||
package org.mozilla.gecko;
|
||||
|
||||
import org.mozilla.gecko.GeckoProfileDirectories.NoMozillaDirectoryException;
|
||||
import org.mozilla.gecko.GeckoProfileDirectories.NoSuchProfileException;
|
||||
import org.mozilla.gecko.util.INIParser;
|
||||
import org.mozilla.gecko.util.INISection;
|
||||
|
||||
@ -24,6 +26,7 @@ import java.util.Hashtable;
|
||||
|
||||
public final class GeckoProfile {
|
||||
private static final String LOGTAG = "GeckoProfile";
|
||||
|
||||
// Used to "lock" the guest profile, so that we'll always restart in it
|
||||
private static final String LOCK_FILE_NAME = ".active_lock";
|
||||
public static final String DEFAULT_PROFILE = "default";
|
||||
@ -31,9 +34,10 @@ public final class GeckoProfile {
|
||||
private static HashMap<String, GeckoProfile> sProfileCache = new HashMap<String, GeckoProfile>();
|
||||
private static String sDefaultProfileName = null;
|
||||
|
||||
private final String mName;
|
||||
private File mProfileDir;
|
||||
public static boolean sIsUsingCustomProfile = false;
|
||||
private final String mName;
|
||||
private final File mMozillaDir;
|
||||
private File mProfileDir; // Not final because this is lazily computed.
|
||||
|
||||
// Constants to cache whether or not a profile is "locked".
|
||||
private enum LockState {
|
||||
@ -41,36 +45,30 @@ public final class GeckoProfile {
|
||||
UNLOCKED,
|
||||
UNDEFINED
|
||||
};
|
||||
|
||||
// Caches whether or not a profile is "locked". Only used by the guest profile to determine if it should
|
||||
// be reused or deleted on startup
|
||||
private LockState mLocked = LockState.UNDEFINED;
|
||||
|
||||
// Caches the guest profile dir
|
||||
private static File mGuestDir = null;
|
||||
// Caches the guest profile dir.
|
||||
private static File sGuestDir = null;
|
||||
private static GeckoProfile sGuestProfile = null;
|
||||
|
||||
private boolean mInGuestMode = false;
|
||||
private static GeckoProfile mGuestProfile = null;
|
||||
|
||||
private static final String MOZILLA_DIR_NAME = "mozilla";
|
||||
private static File sMozillaDir;
|
||||
|
||||
private static INIParser getProfilesINI(File mozillaDir) {
|
||||
File profilesIni = new File(mozillaDir, "profiles.ini");
|
||||
return new INIParser(profilesIni);
|
||||
}
|
||||
|
||||
public static GeckoProfile get(Context context) {
|
||||
boolean isGeckoApp = false;
|
||||
try {
|
||||
isGeckoApp = context instanceof GeckoApp;
|
||||
} catch (NoClassDefFoundError ex) {}
|
||||
|
||||
|
||||
if (isGeckoApp) {
|
||||
// Check for a cached profile on this context already
|
||||
// TODO: We should not be caching profile information on the Activity context
|
||||
if (((GeckoApp)context).mProfile != null) {
|
||||
return ((GeckoApp)context).mProfile;
|
||||
final GeckoApp geckoApp = (GeckoApp) context;
|
||||
if (geckoApp.mProfile != null) {
|
||||
return geckoApp.mProfile;
|
||||
}
|
||||
}
|
||||
|
||||
@ -81,8 +79,18 @@ public final class GeckoProfile {
|
||||
}
|
||||
|
||||
if (isGeckoApp) {
|
||||
// Otherwise, get the default profile for the Activity
|
||||
return get(context, ((GeckoApp)context).getDefaultProfileName());
|
||||
final GeckoApp geckoApp = (GeckoApp) context;
|
||||
String defaultProfileName;
|
||||
try {
|
||||
defaultProfileName = geckoApp.getDefaultProfileName();
|
||||
} catch (NoMozillaDirectoryException e) {
|
||||
// If this failed, we're screwed. But there are so many callers that
|
||||
// we'll just throw a RuntimeException.
|
||||
Log.wtf(LOGTAG, "Unable to get default profile name.", e);
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
// Otherwise, get the default profile for the Activity.
|
||||
return get(context, defaultProfileName);
|
||||
}
|
||||
|
||||
return get(context, "");
|
||||
@ -112,16 +120,24 @@ public final class GeckoProfile {
|
||||
// if no profile was passed in, look for the default profile listed in profiles.ini
|
||||
// if that doesn't exist, look for a profile called 'default'
|
||||
if (TextUtils.isEmpty(profileName) && profileDir == null) {
|
||||
profileName = GeckoProfile.findDefaultProfile(context);
|
||||
if (profileName == null)
|
||||
profileName = DEFAULT_PROFILE;
|
||||
try {
|
||||
profileName = GeckoProfile.getDefaultProfileName(context);
|
||||
} catch (NoMozillaDirectoryException e) {
|
||||
// We're unable to do anything sane here.
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
// actually try to look up the profile
|
||||
synchronized (sProfileCache) {
|
||||
GeckoProfile profile = sProfileCache.get(profileName);
|
||||
if (profile == null) {
|
||||
profile = new GeckoProfile(context, profileName);
|
||||
try {
|
||||
profile = new GeckoProfile(context, profileName);
|
||||
} catch (NoMozillaDirectoryException e) {
|
||||
// We're unable to do anything sane here.
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
profile.setDir(profileDir);
|
||||
sProfileCache.put(profileName, profile);
|
||||
} else {
|
||||
@ -131,22 +147,13 @@ public final class GeckoProfile {
|
||||
}
|
||||
}
|
||||
|
||||
private static File getMozillaDirectory(Context context) {
|
||||
return new File(context.getFilesDir(), MOZILLA_DIR_NAME);
|
||||
}
|
||||
|
||||
private synchronized File ensureMozillaDirectory() throws IOException {
|
||||
if (sMozillaDir.exists() || sMozillaDir.mkdirs()) {
|
||||
return sMozillaDir;
|
||||
}
|
||||
|
||||
// Although this leaks a path to the system log, the path is
|
||||
// predictable (unlike a profile directory), so this is fine.
|
||||
throw new IOException("Unable to create mozilla directory at " + sMozillaDir.getAbsolutePath());
|
||||
}
|
||||
|
||||
public static boolean removeProfile(Context context, String profileName) {
|
||||
return new GeckoProfile(context, profileName).remove();
|
||||
try {
|
||||
return new GeckoProfile(context, profileName).remove();
|
||||
} catch (NoMozillaDirectoryException e) {
|
||||
Log.w(LOGTAG, "Unable to remove profile: no Mozilla directory.", e);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public static GeckoProfile createGuestProfile(Context context) {
|
||||
@ -172,22 +179,22 @@ public final class GeckoProfile {
|
||||
}
|
||||
|
||||
private static File getGuestDir(Context context) {
|
||||
if (mGuestDir == null) {
|
||||
mGuestDir = context.getFileStreamPath("guest");
|
||||
if (sGuestDir == null) {
|
||||
sGuestDir = context.getFileStreamPath("guest");
|
||||
}
|
||||
return mGuestDir;
|
||||
return sGuestDir;
|
||||
}
|
||||
|
||||
private static GeckoProfile getGuestProfile(Context context) {
|
||||
if (mGuestProfile == null) {
|
||||
if (sGuestProfile == null) {
|
||||
File guestDir = getGuestDir(context);
|
||||
if (guestDir.exists()) {
|
||||
mGuestProfile = get(context, "guest", guestDir);
|
||||
mGuestProfile.mInGuestMode = true;
|
||||
sGuestProfile = get(context, "guest", guestDir);
|
||||
sGuestProfile.mInGuestMode = true;
|
||||
}
|
||||
}
|
||||
|
||||
return mGuestProfile;
|
||||
return sGuestProfile;
|
||||
}
|
||||
|
||||
public static boolean maybeCleanupGuestProfile(final Context context) {
|
||||
@ -235,6 +242,11 @@ public final class GeckoProfile {
|
||||
return file.delete();
|
||||
}
|
||||
|
||||
private GeckoProfile(Context context, String profileName) throws NoMozillaDirectoryException {
|
||||
mName = profileName;
|
||||
mMozillaDir = GeckoProfileDirectories.getMozillaDirectory(context);
|
||||
}
|
||||
|
||||
// Warning, Changing the lock file state from outside apis will cause this to become out of sync
|
||||
public boolean locked() {
|
||||
if (mLocked != LockState.UNDEFINED) {
|
||||
@ -293,13 +305,6 @@ public final class GeckoProfile {
|
||||
return false;
|
||||
}
|
||||
|
||||
private GeckoProfile(Context context, String profileName) {
|
||||
mName = profileName;
|
||||
if (sMozillaDir == null) {
|
||||
sMozillaDir = getMozillaDirectory(context);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean inGuestMode() {
|
||||
return mInGuestMode;
|
||||
}
|
||||
@ -328,13 +333,12 @@ public final class GeckoProfile {
|
||||
|
||||
try {
|
||||
// Check if a profile with this name already exists.
|
||||
File mozillaDir = ensureMozillaDirectory();
|
||||
mProfileDir = findProfileDir(mozillaDir);
|
||||
if (mProfileDir == null) {
|
||||
// otherwise create it
|
||||
mProfileDir = createProfileDir(mozillaDir);
|
||||
} else {
|
||||
try {
|
||||
mProfileDir = findProfileDir();
|
||||
Log.d(LOGTAG, "Found profile dir.");
|
||||
} catch (NoSuchProfileException noSuchProfile) {
|
||||
// If it doesn't exist, create it.
|
||||
mProfileDir = createProfileDir();
|
||||
}
|
||||
} catch (IOException ioe) {
|
||||
Log.e(LOGTAG, "Error getting profile dir", ioe);
|
||||
@ -420,25 +424,27 @@ public final class GeckoProfile {
|
||||
|
||||
private boolean remove() {
|
||||
try {
|
||||
File dir = getDir();
|
||||
if (dir.exists())
|
||||
final File dir = getDir();
|
||||
if (dir.exists()) {
|
||||
delete(dir);
|
||||
}
|
||||
|
||||
File mozillaDir = ensureMozillaDirectory();
|
||||
mProfileDir = findProfileDir(mozillaDir);
|
||||
if (mProfileDir == null) {
|
||||
try {
|
||||
mProfileDir = findProfileDir();
|
||||
} catch (NoSuchProfileException noSuchProfile) {
|
||||
// If the profile doesn't exist, there's nothing left for us to do.
|
||||
return false;
|
||||
}
|
||||
|
||||
INIParser parser = getProfilesINI(mozillaDir);
|
||||
|
||||
Hashtable<String, INISection> sections = parser.getSections();
|
||||
final INIParser parser = GeckoProfileDirectories.getProfilesINI(mMozillaDir);
|
||||
final Hashtable<String, INISection> sections = parser.getSections();
|
||||
for (Enumeration<INISection> e = sections.elements(); e.hasMoreElements();) {
|
||||
INISection section = e.nextElement();
|
||||
final INISection section = e.nextElement();
|
||||
String name = section.getStringProperty("Name");
|
||||
|
||||
if (name == null || !name.equals(mName))
|
||||
if (name == null || !name.equals(mName)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (section.getName().startsWith("Profile")) {
|
||||
// ok, we have stupid Profile#-named things. Rename backwards.
|
||||
@ -452,7 +458,7 @@ public final class GeckoProfile {
|
||||
while (sections.containsKey(nextSection)) {
|
||||
parser.renameSection(nextSection, curSection);
|
||||
sectionNumber++;
|
||||
|
||||
|
||||
curSection = nextSection;
|
||||
nextSection = "Profile" + (sectionNumber+1);
|
||||
}
|
||||
@ -476,7 +482,15 @@ public final class GeckoProfile {
|
||||
}
|
||||
}
|
||||
|
||||
public static String findDefaultProfile(Context context) {
|
||||
/**
|
||||
* @return the default profile name for this application, or
|
||||
* {@link GeckoProfile#DEFAULT_PROFILE} if none could be found.
|
||||
*
|
||||
* @throws NoMozillaDirectoryException
|
||||
* if the Mozilla directory did not exist and could not be
|
||||
* created.
|
||||
*/
|
||||
public static String getDefaultProfileName(final Context context) throws NoMozillaDirectoryException {
|
||||
// Have we read the default profile from the INI already?
|
||||
// Changing the default profile requires a restart, so we don't
|
||||
// need to worry about runtime changes.
|
||||
@ -484,58 +498,30 @@ public final class GeckoProfile {
|
||||
return sDefaultProfileName;
|
||||
}
|
||||
|
||||
// Open profiles.ini to find the correct path
|
||||
INIParser parser = getProfilesINI(getMozillaDirectory(context));
|
||||
|
||||
for (Enumeration<INISection> e = parser.getSections().elements(); e.hasMoreElements();) {
|
||||
INISection section = e.nextElement();
|
||||
if (section.getIntProperty("Default") == 1) {
|
||||
sDefaultProfileName = section.getStringProperty("Name");
|
||||
return sDefaultProfileName;
|
||||
}
|
||||
final String profileName = GeckoProfileDirectories.findDefaultProfileName(context);
|
||||
if (profileName == null) {
|
||||
// Note that we don't persist this back to profiles.ini.
|
||||
sDefaultProfileName = DEFAULT_PROFILE;
|
||||
return DEFAULT_PROFILE;
|
||||
}
|
||||
|
||||
return null;
|
||||
sDefaultProfileName = profileName;
|
||||
return sDefaultProfileName;
|
||||
}
|
||||
|
||||
private File findProfileDir(File mozillaDir) {
|
||||
// Open profiles.ini to find the correct path
|
||||
INIParser parser = getProfilesINI(mozillaDir);
|
||||
|
||||
for (Enumeration<INISection> e = parser.getSections().elements(); e.hasMoreElements();) {
|
||||
INISection section = e.nextElement();
|
||||
String name = section.getStringProperty("Name");
|
||||
if (name != null && name.equals(mName)) {
|
||||
if (section.getIntProperty("IsRelative") == 1) {
|
||||
return new File(mozillaDir, section.getStringProperty("Path"));
|
||||
}
|
||||
return new File(section.getStringProperty("Path"));
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
private File findProfileDir() throws NoSuchProfileException {
|
||||
return GeckoProfileDirectories.findProfileDir(mMozillaDir, mName);
|
||||
}
|
||||
|
||||
private static String saltProfileName(String name) {
|
||||
String allowedChars = "abcdefghijklmnopqrstuvwxyz0123456789";
|
||||
StringBuilder salt = new StringBuilder(16);
|
||||
for (int i = 0; i < 8; i++) {
|
||||
salt.append(allowedChars.charAt((int)(Math.random() * allowedChars.length())));
|
||||
}
|
||||
salt.append('.');
|
||||
salt.append(name);
|
||||
return salt.toString();
|
||||
}
|
||||
|
||||
private File createProfileDir(File mozillaDir) throws IOException {
|
||||
INIParser parser = getProfilesINI(mozillaDir);
|
||||
private File createProfileDir() throws IOException {
|
||||
INIParser parser = GeckoProfileDirectories.getProfilesINI(mMozillaDir);
|
||||
|
||||
// Salt the name of our requested profile
|
||||
String saltedName = saltProfileName(mName);
|
||||
File profileDir = new File(mozillaDir, saltedName);
|
||||
String saltedName = GeckoProfileDirectories.saltProfileName(mName);
|
||||
File profileDir = new File(mMozillaDir, saltedName);
|
||||
while (profileDir.exists()) {
|
||||
saltedName = saltProfileName(mName);
|
||||
profileDir = new File(mozillaDir, saltedName);
|
||||
saltedName = GeckoProfileDirectories.saltProfileName(mName);
|
||||
profileDir = new File(mMozillaDir, saltedName);
|
||||
}
|
||||
|
||||
// Attempt to create the salted profile dir
|
||||
|
229
mobile/android/base/GeckoProfileDirectories.java
Normal file
229
mobile/android/base/GeckoProfileDirectories.java
Normal file
@ -0,0 +1,229 @@
|
||||
/* 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;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.Enumeration;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.mozilla.gecko.util.INIParser;
|
||||
import org.mozilla.gecko.util.INISection;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
/**
|
||||
* <code>GeckoProfileDirectories</code> manages access to mappings from profile
|
||||
* names to salted profile directory paths, as well as the default profile name.
|
||||
*
|
||||
* This class will eventually come to encapsulate the remaining logic embedded
|
||||
* in profiles.ini; for now it's a read-only wrapper.
|
||||
*/
|
||||
public class GeckoProfileDirectories {
|
||||
@SuppressWarnings("serial")
|
||||
public static class NoMozillaDirectoryException extends Exception {
|
||||
public NoMozillaDirectoryException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
|
||||
public NoMozillaDirectoryException(String reason) {
|
||||
super(reason);
|
||||
}
|
||||
|
||||
public NoMozillaDirectoryException(String reason, Throwable cause) {
|
||||
super(reason, cause);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("serial")
|
||||
public static class NoSuchProfileException extends Exception {
|
||||
public NoSuchProfileException(String detailMessage, Throwable cause) {
|
||||
super(detailMessage, cause);
|
||||
}
|
||||
|
||||
public NoSuchProfileException(String detailMessage) {
|
||||
super(detailMessage);
|
||||
}
|
||||
}
|
||||
|
||||
private interface INISectionPredicate {
|
||||
public boolean matches(INISection section);
|
||||
}
|
||||
|
||||
private static final String MOZILLA_DIR_NAME = "mozilla";
|
||||
|
||||
/**
|
||||
* Returns true if the supplied profile entry represents the default profile.
|
||||
*/
|
||||
private static INISectionPredicate sectionIsDefault = new INISectionPredicate() {
|
||||
@Override
|
||||
public boolean matches(INISection section) {
|
||||
return section.getIntProperty("Default") == 1;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns true if the supplied profile entry has a 'Name' field.
|
||||
*/
|
||||
private static INISectionPredicate sectionHasName = new INISectionPredicate() {
|
||||
@Override
|
||||
public boolean matches(INISection section) {
|
||||
final String name = section.getStringProperty("Name");
|
||||
return name != null;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Package-scoped because GeckoProfile needs to dig into this in order to do writes.
|
||||
* This will be fixed in Bug 975212.
|
||||
*/
|
||||
static INIParser getProfilesINI(File mozillaDir) {
|
||||
return new INIParser(new File(mozillaDir, "profiles.ini"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility method to compute a salted profile name: eight random alphanumeric
|
||||
* characters, followed by a period, followed by the profile name.
|
||||
*/
|
||||
public static String saltProfileName(final String name) {
|
||||
if (name == null) {
|
||||
throw new IllegalArgumentException("Cannot salt null profile name.");
|
||||
}
|
||||
|
||||
final String allowedChars = "abcdefghijklmnopqrstuvwxyz0123456789";
|
||||
final int scale = allowedChars.length();
|
||||
final int saltSize = 8;
|
||||
|
||||
final StringBuilder saltBuilder = new StringBuilder(saltSize + 1 + name.length());
|
||||
for (int i = 0; i < saltSize; i++) {
|
||||
saltBuilder.append(allowedChars.charAt((int)(Math.random() * scale)));
|
||||
}
|
||||
saltBuilder.append('.');
|
||||
saltBuilder.append(name);
|
||||
return saltBuilder.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the Mozilla directory within the files directory of the provided
|
||||
* context. This should always be the same within a running application.
|
||||
*
|
||||
* This method is package-scoped so that new {@link GeckoProfile} instances can
|
||||
* contextualize themselves.
|
||||
*
|
||||
* @return a new File object for the Mozilla directory.
|
||||
* @throws NoMozillaDirectoryException
|
||||
* if the directory did not exist and could not be created.
|
||||
*/
|
||||
static File getMozillaDirectory(Context context) throws NoMozillaDirectoryException {
|
||||
final File mozillaDir = new File(context.getFilesDir(), MOZILLA_DIR_NAME);
|
||||
if (mozillaDir.exists() || mozillaDir.mkdirs()) {
|
||||
return mozillaDir;
|
||||
}
|
||||
|
||||
// Although this leaks a path to the system log, the path is
|
||||
// predictable (unlike a profile directory), so this is fine.
|
||||
throw new NoMozillaDirectoryException("Unable to create mozilla directory at " + mozillaDir.getAbsolutePath());
|
||||
}
|
||||
|
||||
/**
|
||||
* Discover the default profile name by examining profiles.ini.
|
||||
*
|
||||
* Package-scoped because {@link GeckoProfile} needs access to it.
|
||||
*
|
||||
* @return null if there is no "Default" entry in profiles.ini, or the profile
|
||||
* name if there is.
|
||||
* @throws NoMozillaDirectoryException
|
||||
* if the Mozilla directory did not exist and could not be created.
|
||||
*/
|
||||
static String findDefaultProfileName(final Context context) throws NoMozillaDirectoryException {
|
||||
final INIParser parser = GeckoProfileDirectories.getProfilesINI(getMozillaDirectory(context));
|
||||
|
||||
for (Enumeration<INISection> e = parser.getSections().elements(); e.hasMoreElements();) {
|
||||
final INISection section = e.nextElement();
|
||||
if (section.getIntProperty("Default") == 1) {
|
||||
return section.getStringProperty("Name");
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
static Map<String, String> getDefaultProfile(final File mozillaDir) {
|
||||
return getMatchingProfiles(mozillaDir, sectionIsDefault, true);
|
||||
}
|
||||
|
||||
static Map<String, String> getProfilesNamed(final File mozillaDir, final String name) {
|
||||
final INISectionPredicate predicate = new INISectionPredicate() {
|
||||
@Override
|
||||
public boolean matches(final INISection section) {
|
||||
return name.equals(section.getStringProperty("Name"));
|
||||
}
|
||||
};
|
||||
return getMatchingProfiles(mozillaDir, predicate, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls {@link GeckoProfileDirectories#getMatchingProfiles(File, INISectionPredicate, boolean)}
|
||||
* with a filter to ensure that all profiles are named.
|
||||
*/
|
||||
static Map<String, String> getAllProfiles(final File mozillaDir) {
|
||||
return getMatchingProfiles(mozillaDir, sectionHasName, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a mapping from the names of all matching profiles (that is,
|
||||
* profiles appearing in profiles.ini that match the supplied predicate) to
|
||||
* their absolute paths on disk.
|
||||
*
|
||||
* @param mozillaDir
|
||||
* a directory containing profiles.ini.
|
||||
* @param predicate
|
||||
* a predicate to use when evaluating whether to include a
|
||||
* particular INI section.
|
||||
* @param stopOnSuccess
|
||||
* if true, this method will return with the first result that
|
||||
* matches the predicate; if false, all matching results are
|
||||
* included.
|
||||
* @return a {@link Map} from name to path.
|
||||
*/
|
||||
public static Map<String, String> getMatchingProfiles(final File mozillaDir, INISectionPredicate predicate, boolean stopOnSuccess) {
|
||||
final HashMap<String, String> result = new HashMap<String, String>();
|
||||
final INIParser parser = GeckoProfileDirectories.getProfilesINI(mozillaDir);
|
||||
|
||||
for (Enumeration<INISection> e = parser.getSections().elements(); e.hasMoreElements();) {
|
||||
final INISection section = e.nextElement();
|
||||
if (predicate == null || predicate.matches(section)) {
|
||||
final String name = section.getStringProperty("Name");
|
||||
final String pathString = section.getStringProperty("Path");
|
||||
final boolean isRelative = section.getIntProperty("IsRelative") == 1;
|
||||
final File path = isRelative ? new File(mozillaDir, pathString) : new File(pathString);
|
||||
result.put(name, path.getAbsolutePath());
|
||||
|
||||
if (stopOnSuccess) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public static File findProfileDir(final File mozillaDir, final String profileName) throws NoSuchProfileException {
|
||||
// Open profiles.ini to find the correct path.
|
||||
final INIParser parser = GeckoProfileDirectories.getProfilesINI(mozillaDir);
|
||||
|
||||
for (Enumeration<INISection> e = parser.getSections().elements(); e.hasMoreElements();) {
|
||||
final INISection section = e.nextElement();
|
||||
final String name = section.getStringProperty("Name");
|
||||
if (name != null && name.equals(profileName)) {
|
||||
if (section.getIntProperty("IsRelative") == 1) {
|
||||
return new File(mozillaDir, section.getStringProperty("Path"));
|
||||
}
|
||||
return new File(section.getStringProperty("Path"));
|
||||
}
|
||||
}
|
||||
|
||||
throw new NoSuchProfileException("No profile " + profileName);
|
||||
}
|
||||
}
|
@ -534,6 +534,8 @@ sync_java_files = [
|
||||
'background/healthreport/upload/ObsoleteDocumentTracker.java',
|
||||
'background/healthreport/upload/SubmissionClient.java',
|
||||
'background/healthreport/upload/SubmissionPolicy.java',
|
||||
'background/preferences/PreferenceFragment.java',
|
||||
'background/preferences/PreferenceManagerCompat.java',
|
||||
'browserid/ASNUtils.java',
|
||||
'browserid/BrowserIDKeyPair.java',
|
||||
'browserid/DSACryptoImplementation.java',
|
||||
@ -550,12 +552,12 @@ sync_java_files = [
|
||||
'fxa/activities/FxAccountAbstractSetupActivity.java',
|
||||
'fxa/activities/FxAccountConfirmAccountActivity.java',
|
||||
'fxa/activities/FxAccountCreateAccountActivity.java',
|
||||
'fxa/activities/FxAccountCreateAccountFragment.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',
|
||||
'fxa/activities/FxAccountUpdateCredentialsActivity.java',
|
||||
'fxa/activities/FxAccountVerifiedAccountActivity.java',
|
||||
'fxa/authenticator/AndroidFxAccount.java',
|
||||
@ -576,6 +578,7 @@ sync_java_files = [
|
||||
'fxa/login/StateFactory.java',
|
||||
'fxa/login/TokensAndKeysState.java',
|
||||
'fxa/sync/FxAccountGlobalSession.java',
|
||||
'fxa/sync/FxAccountNotificationManager.java',
|
||||
'fxa/sync/FxAccountSchedulePolicy.java',
|
||||
'fxa/sync/FxAccountSyncAdapter.java',
|
||||
'fxa/sync/FxAccountSyncService.java',
|
||||
|
@ -0,0 +1,318 @@
|
||||
/*
|
||||
* Copyright (C) 2013 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.mozilla.gecko.background.preferences;
|
||||
|
||||
import org.mozilla.gecko.R;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.Message;
|
||||
import android.preference.Preference;
|
||||
import android.preference.PreferenceGroup;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.preference.PreferenceScreen;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.View.OnKeyListener;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ListView;
|
||||
|
||||
public abstract class PreferenceFragment extends Fragment implements PreferenceManagerCompat.OnPreferenceTreeClickListener {
|
||||
private static final String PREFERENCES_TAG = "android:preferences";
|
||||
|
||||
private PreferenceManager mPreferenceManager;
|
||||
private ListView mList;
|
||||
private boolean mHavePrefs;
|
||||
private boolean mInitDone;
|
||||
|
||||
/**
|
||||
* The starting request code given out to preference framework.
|
||||
*/
|
||||
private static final int FIRST_REQUEST_CODE = 100;
|
||||
|
||||
private static final int MSG_BIND_PREFERENCES = 1;
|
||||
|
||||
// This triggers "This Handler class should be static or leaks might occur".
|
||||
// The issue is that the Handler references the Fragment; messages targeting
|
||||
// the Handler reference it; and if such messages are long lived, the Fragment
|
||||
// cannot be GCed. This is not an issue for us; our messages are short-lived.
|
||||
private Handler mHandler = new Handler() {
|
||||
@Override
|
||||
public void handleMessage(Message msg) {
|
||||
switch (msg.what) {
|
||||
|
||||
case MSG_BIND_PREFERENCES:
|
||||
bindPreferences();
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
final private Runnable mRequestFocus = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
mList.focusableViewAvailable(mList);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Interface that PreferenceFragment's containing activity should
|
||||
* implement to be able to process preference items that wish to
|
||||
* switch to a new fragment.
|
||||
*/
|
||||
public interface OnPreferenceStartFragmentCallback {
|
||||
/**
|
||||
* Called when the user has clicked on a Preference that has
|
||||
* a fragment class name associated with it. The implementation
|
||||
* to should instantiate and switch to an instance of the given
|
||||
* fragment.
|
||||
*/
|
||||
boolean onPreferenceStartFragment(PreferenceFragment caller, Preference pref);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle paramBundle) {
|
||||
super.onCreate(paramBundle);
|
||||
mPreferenceManager = PreferenceManagerCompat.newInstance(getActivity(), FIRST_REQUEST_CODE);
|
||||
PreferenceManagerCompat.setFragment(mPreferenceManager, this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater paramLayoutInflater, ViewGroup paramViewGroup, Bundle paramBundle) {
|
||||
return paramLayoutInflater.inflate(R.layout.fxaccount_preference_list_fragment, paramViewGroup,
|
||||
false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityCreated(Bundle savedInstanceState) {
|
||||
super.onActivityCreated(savedInstanceState);
|
||||
|
||||
if (mHavePrefs) {
|
||||
bindPreferences();
|
||||
}
|
||||
|
||||
mInitDone = true;
|
||||
|
||||
if (savedInstanceState != null) {
|
||||
Bundle container = savedInstanceState.getBundle(PREFERENCES_TAG);
|
||||
if (container != null) {
|
||||
final PreferenceScreen preferenceScreen = getPreferenceScreen();
|
||||
if (preferenceScreen != null) {
|
||||
preferenceScreen.restoreHierarchyState(container);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStart() {
|
||||
super.onStart();
|
||||
PreferenceManagerCompat.setOnPreferenceTreeClickListener(mPreferenceManager, this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStop() {
|
||||
super.onStop();
|
||||
PreferenceManagerCompat.dispatchActivityStop(mPreferenceManager);
|
||||
PreferenceManagerCompat.setOnPreferenceTreeClickListener(mPreferenceManager, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroyView() {
|
||||
mList = null;
|
||||
mHandler.removeCallbacks(mRequestFocus);
|
||||
mHandler.removeMessages(MSG_BIND_PREFERENCES);
|
||||
super.onDestroyView();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
super.onDestroy();
|
||||
PreferenceManagerCompat.dispatchActivityDestroy(mPreferenceManager);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSaveInstanceState(Bundle outState) {
|
||||
super.onSaveInstanceState(outState);
|
||||
|
||||
final PreferenceScreen preferenceScreen = getPreferenceScreen();
|
||||
if (preferenceScreen != null) {
|
||||
Bundle container = new Bundle();
|
||||
preferenceScreen.saveHierarchyState(container);
|
||||
outState.putBundle(PREFERENCES_TAG, container);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
super.onActivityResult(requestCode, resultCode, data);
|
||||
|
||||
PreferenceManagerCompat.dispatchActivityResult(mPreferenceManager, requestCode, resultCode, data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@link PreferenceManager} used by this fragment.
|
||||
* @return The {@link PreferenceManager}.
|
||||
*/
|
||||
public PreferenceManager getPreferenceManager() {
|
||||
return mPreferenceManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the root of the preference hierarchy that this fragment is showing.
|
||||
*
|
||||
* @param preferenceScreen The root {@link PreferenceScreen} of the preference hierarchy.
|
||||
*/
|
||||
public void setPreferenceScreen(PreferenceScreen preferenceScreen) {
|
||||
if (PreferenceManagerCompat.setPreferences(mPreferenceManager, preferenceScreen) && preferenceScreen != null) {
|
||||
mHavePrefs = true;
|
||||
if (mInitDone) {
|
||||
postBindPreferences();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the root of the preference hierarchy that this fragment is showing.
|
||||
*
|
||||
* @return The {@link PreferenceScreen} that is the root of the preference
|
||||
* hierarchy.
|
||||
*/
|
||||
public PreferenceScreen getPreferenceScreen() {
|
||||
return PreferenceManagerCompat.getPreferenceScreen(mPreferenceManager);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds preferences from activities that match the given {@link Intent}.
|
||||
*
|
||||
* @param intent The {@link Intent} to query activities.
|
||||
*/
|
||||
public void addPreferencesFromIntent(Intent intent) {
|
||||
requirePreferenceManager();
|
||||
|
||||
setPreferenceScreen(PreferenceManagerCompat.inflateFromIntent(mPreferenceManager, intent, getPreferenceScreen()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Inflates the given XML resource and adds the preference hierarchy to the current
|
||||
* preference hierarchy.
|
||||
*
|
||||
* @param preferencesResId The XML resource ID to inflate.
|
||||
*/
|
||||
public void addPreferencesFromResource(int preferencesResId) {
|
||||
requirePreferenceManager();
|
||||
|
||||
setPreferenceScreen(PreferenceManagerCompat.inflateFromResource(mPreferenceManager, getActivity(),
|
||||
preferencesResId, getPreferenceScreen()));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen,
|
||||
Preference preference) {
|
||||
//if (preference.getFragment() != null &&
|
||||
if (
|
||||
getActivity() instanceof OnPreferenceStartFragmentCallback) {
|
||||
return ((OnPreferenceStartFragmentCallback)getActivity()).onPreferenceStartFragment(
|
||||
this, preference);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds a {@link Preference} based on its key.
|
||||
*
|
||||
* @param key The key of the preference to retrieve.
|
||||
* @return The {@link Preference} with the key, or null.
|
||||
* @see PreferenceGroup#findPreference(CharSequence)
|
||||
*/
|
||||
public Preference findPreference(CharSequence key) {
|
||||
if (mPreferenceManager == null) {
|
||||
return null;
|
||||
}
|
||||
return mPreferenceManager.findPreference(key);
|
||||
}
|
||||
|
||||
private void requirePreferenceManager() {
|
||||
if (mPreferenceManager == null) {
|
||||
throw new RuntimeException("This should be called after super.onCreate.");
|
||||
}
|
||||
}
|
||||
|
||||
private void postBindPreferences() {
|
||||
if (mHandler.hasMessages(MSG_BIND_PREFERENCES)) return;
|
||||
mHandler.obtainMessage(MSG_BIND_PREFERENCES).sendToTarget();
|
||||
}
|
||||
|
||||
private void bindPreferences() {
|
||||
final PreferenceScreen preferenceScreen = getPreferenceScreen();
|
||||
if (preferenceScreen != null) {
|
||||
preferenceScreen.bind(getListView());
|
||||
}
|
||||
}
|
||||
|
||||
public ListView getListView() {
|
||||
ensureList();
|
||||
return mList;
|
||||
}
|
||||
|
||||
private void ensureList() {
|
||||
if (mList != null) {
|
||||
return;
|
||||
}
|
||||
View root = getView();
|
||||
if (root == null) {
|
||||
throw new IllegalStateException("Content view not yet created");
|
||||
}
|
||||
View rawListView = root.findViewById(android.R.id.list);
|
||||
if (!(rawListView instanceof ListView)) {
|
||||
throw new RuntimeException(
|
||||
"Content has view with id attribute 'android.R.id.list' "
|
||||
+ "that is not a ListView class");
|
||||
}
|
||||
mList = (ListView)rawListView;
|
||||
if (mList == null) {
|
||||
throw new RuntimeException(
|
||||
"Your content must have a ListView whose id attribute is " +
|
||||
"'android.R.id.list'");
|
||||
}
|
||||
mList.setOnKeyListener(mListOnKeyListener);
|
||||
mHandler.post(mRequestFocus);
|
||||
}
|
||||
|
||||
private OnKeyListener mListOnKeyListener = new OnKeyListener() {
|
||||
|
||||
@Override
|
||||
public boolean onKey(View v, int keyCode, KeyEvent event) {
|
||||
Object selectedItem = mList.getSelectedItem();
|
||||
if (selectedItem instanceof Preference) {
|
||||
@SuppressWarnings("unused")
|
||||
View selectedView = mList.getSelectedView();
|
||||
//return ((Preference)selectedItem).onKey(
|
||||
// selectedView, keyCode, event);
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
};
|
||||
}
|
@ -0,0 +1,231 @@
|
||||
/*
|
||||
* Copyright (C) 2013 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.mozilla.gecko.background.preferences;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.InvocationHandler;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Proxy;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.preference.Preference;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.preference.PreferenceScreen;
|
||||
import android.util.Log;
|
||||
|
||||
public class PreferenceManagerCompat {
|
||||
|
||||
private static final String TAG = PreferenceManagerCompat.class.getSimpleName();
|
||||
|
||||
/**
|
||||
* Interface definition for a callback to be invoked when a
|
||||
* {@link Preference} in the hierarchy rooted at this {@link PreferenceScreen} is
|
||||
* clicked.
|
||||
*/
|
||||
interface OnPreferenceTreeClickListener {
|
||||
/**
|
||||
* Called when a preference in the tree rooted at this
|
||||
* {@link PreferenceScreen} has been clicked.
|
||||
*
|
||||
* @param preferenceScreen The {@link PreferenceScreen} that the
|
||||
* preference is located in.
|
||||
* @param preference The preference that was clicked.
|
||||
* @return Whether the click was handled.
|
||||
*/
|
||||
boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, Preference preference);
|
||||
}
|
||||
|
||||
static PreferenceManager newInstance(Activity activity, int firstRequestCode) {
|
||||
try {
|
||||
Constructor<PreferenceManager> c = PreferenceManager.class.getDeclaredConstructor(Activity.class, int.class);
|
||||
c.setAccessible(true);
|
||||
return c.newInstance(activity, firstRequestCode);
|
||||
} catch (Exception e) {
|
||||
Log.w(TAG, "Couldn't call constructor PreferenceManager by reflection", e);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the owning preference fragment
|
||||
*/
|
||||
static void setFragment(PreferenceManager manager, PreferenceFragment fragment) {
|
||||
// stub
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the callback to be invoked when a {@link Preference} in the
|
||||
* hierarchy rooted at this {@link PreferenceManager} is clicked.
|
||||
*
|
||||
* @param listener The callback to be invoked.
|
||||
*/
|
||||
static void setOnPreferenceTreeClickListener(PreferenceManager manager, final OnPreferenceTreeClickListener listener) {
|
||||
try {
|
||||
Field onPreferenceTreeClickListener = PreferenceManager.class.getDeclaredField("mOnPreferenceTreeClickListener");
|
||||
onPreferenceTreeClickListener.setAccessible(true);
|
||||
if (listener != null) {
|
||||
Object proxy = Proxy.newProxyInstance(
|
||||
onPreferenceTreeClickListener.getType().getClassLoader(),
|
||||
new Class<?>[] { onPreferenceTreeClickListener.getType() },
|
||||
new InvocationHandler() {
|
||||
@Override
|
||||
public Object invoke(Object proxy, Method method, Object[] args) {
|
||||
if (method.getName().equals("onPreferenceTreeClick")) {
|
||||
return Boolean.valueOf(listener.onPreferenceTreeClick((PreferenceScreen) args[0], (Preference) args[1]));
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
});
|
||||
onPreferenceTreeClickListener.set(manager, proxy);
|
||||
} else {
|
||||
onPreferenceTreeClickListener.set(manager, null);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Log.w(TAG, "Couldn't set PreferenceManager.mOnPreferenceTreeClickListener by reflection", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Inflates a preference hierarchy from the preference hierarchies of
|
||||
* {@link Activity Activities} that match the given {@link Intent}. An
|
||||
* {@link Activity} defines its preference hierarchy with meta-data using
|
||||
* the {@link #METADATA_KEY_PREFERENCES} key.
|
||||
* <p>
|
||||
* If a preference hierarchy is given, the new preference hierarchies will
|
||||
* be merged in.
|
||||
*
|
||||
* @param queryIntent The intent to match activities.
|
||||
* @param rootPreferences Optional existing hierarchy to merge the new
|
||||
* hierarchies into.
|
||||
* @return The root hierarchy (if one was not provided, the new hierarchy's
|
||||
* root).
|
||||
*/
|
||||
static PreferenceScreen inflateFromIntent(PreferenceManager manager, Intent intent, PreferenceScreen screen) {
|
||||
try {
|
||||
Method m = PreferenceManager.class.getDeclaredMethod("inflateFromIntent", Intent.class, PreferenceScreen.class);
|
||||
m.setAccessible(true);
|
||||
PreferenceScreen prefScreen = (PreferenceScreen) m.invoke(manager, intent, screen);
|
||||
return prefScreen;
|
||||
} catch (Exception e) {
|
||||
Log.w(TAG, "Couldn't call PreferenceManager.inflateFromIntent by reflection", e);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Inflates a preference hierarchy from XML. If a preference hierarchy is
|
||||
* given, the new preference hierarchies will be merged in.
|
||||
*
|
||||
* @param context The context of the resource.
|
||||
* @param resId The resource ID of the XML to inflate.
|
||||
* @param rootPreferences Optional existing hierarchy to merge the new
|
||||
* hierarchies into.
|
||||
* @return The root hierarchy (if one was not provided, the new hierarchy's
|
||||
* root).
|
||||
* @hide
|
||||
*/
|
||||
static PreferenceScreen inflateFromResource(PreferenceManager manager, Activity activity, int resId, PreferenceScreen screen) {
|
||||
try {
|
||||
Method m = PreferenceManager.class.getDeclaredMethod("inflateFromResource", Context.class, int.class, PreferenceScreen.class);
|
||||
m.setAccessible(true);
|
||||
PreferenceScreen prefScreen = (PreferenceScreen) m.invoke(manager, activity, resId, screen);
|
||||
return prefScreen;
|
||||
} catch (Exception e) {
|
||||
Log.w(TAG, "Couldn't call PreferenceManager.inflateFromResource by reflection", e);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the root of the preference hierarchy managed by this class.
|
||||
*
|
||||
* @return The {@link PreferenceScreen} object that is at the root of the hierarchy.
|
||||
*/
|
||||
static PreferenceScreen getPreferenceScreen(PreferenceManager manager) {
|
||||
try {
|
||||
Method m = PreferenceManager.class.getDeclaredMethod("getPreferenceScreen");
|
||||
m.setAccessible(true);
|
||||
return (PreferenceScreen) m.invoke(manager);
|
||||
} catch (Exception e) {
|
||||
Log.w(TAG, "Couldn't call PreferenceManager.getPreferenceScreen by reflection", e);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by the {@link PreferenceManager} to dispatch a subactivity result.
|
||||
*/
|
||||
static void dispatchActivityResult(PreferenceManager manager, int requestCode, int resultCode, Intent data) {
|
||||
try {
|
||||
Method m = PreferenceManager.class.getDeclaredMethod("dispatchActivityResult", int.class, int.class, Intent.class);
|
||||
m.setAccessible(true);
|
||||
m.invoke(manager, requestCode, resultCode, data);
|
||||
} catch (Exception e) {
|
||||
Log.w(TAG, "Couldn't call PreferenceManager.dispatchActivityResult by reflection", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by the {@link PreferenceManager} to dispatch the activity stop
|
||||
* event.
|
||||
*/
|
||||
static void dispatchActivityStop(PreferenceManager manager) {
|
||||
try {
|
||||
Method m = PreferenceManager.class.getDeclaredMethod("dispatchActivityStop");
|
||||
m.setAccessible(true);
|
||||
m.invoke(manager);
|
||||
} catch (Exception e) {
|
||||
Log.w(TAG, "Couldn't call PreferenceManager.dispatchActivityStop by reflection", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by the {@link PreferenceManager} to dispatch the activity destroy
|
||||
* event.
|
||||
*/
|
||||
static void dispatchActivityDestroy(PreferenceManager manager) {
|
||||
try {
|
||||
Method m = PreferenceManager.class.getDeclaredMethod("dispatchActivityDestroy");
|
||||
m.setAccessible(true);
|
||||
m.invoke(manager);
|
||||
} catch (Exception e) {
|
||||
Log.w(TAG, "Couldn't call PreferenceManager.dispatchActivityDestroy by reflection", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the root of the preference hierarchy.
|
||||
*
|
||||
* @param preferenceScreen The root {@link PreferenceScreen} of the preference hierarchy.
|
||||
* @return Whether the {@link PreferenceScreen} given is different than the previous.
|
||||
*/
|
||||
static boolean setPreferences(PreferenceManager manager, PreferenceScreen screen) {
|
||||
try {
|
||||
Method m = PreferenceManager.class.getDeclaredMethod("setPreferences", PreferenceScreen.class);
|
||||
m.setAccessible(true);
|
||||
return ((Boolean) m.invoke(manager, screen));
|
||||
} catch (Exception e) {
|
||||
Log.w(TAG, "Couldn't call PreferenceManager.setPreferences by reflection", e);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
@ -28,6 +28,8 @@ import android.content.Intent;
|
||||
import android.text.Editable;
|
||||
import android.text.InputType;
|
||||
import android.text.TextWatcher;
|
||||
import android.text.method.PasswordTransformationMethod;
|
||||
import android.text.method.SingleLineTransformationMethod;
|
||||
import android.util.Patterns;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.View;
|
||||
@ -64,21 +66,24 @@ abstract public class FxAccountAbstractSetupActivity extends FxAccountAbstractAc
|
||||
@SuppressWarnings("deprecation")
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
boolean isShown = 0 == (passwordEdit.getInputType() & InputType.TYPE_TEXT_VARIATION_PASSWORD);
|
||||
boolean isShown = passwordEdit.getTransformationMethod() instanceof SingleLineTransformationMethod;
|
||||
|
||||
// Changing input type loses position in edit text; let's try to maintain it.
|
||||
int start = passwordEdit.getSelectionStart();
|
||||
int stop = passwordEdit.getSelectionEnd();
|
||||
passwordEdit.setInputType(passwordEdit.getInputType() ^ InputType.TYPE_TEXT_VARIATION_PASSWORD);
|
||||
passwordEdit.setSelection(start, stop);
|
||||
|
||||
if (isShown) {
|
||||
passwordEdit.setTransformationMethod(PasswordTransformationMethod.getInstance());
|
||||
showPasswordButton.setText(R.string.fxaccount_password_show);
|
||||
showPasswordButton.setBackgroundDrawable(getResources().getDrawable(R.drawable.fxaccount_password_button_show_background));
|
||||
showPasswordButton.setTextColor(getResources().getColor(R.color.fxaccount_password_show_textcolor));
|
||||
} else {
|
||||
passwordEdit.setTransformationMethod(SingleLineTransformationMethod.getInstance());
|
||||
showPasswordButton.setText(R.string.fxaccount_password_hide);
|
||||
showPasswordButton.setBackgroundDrawable(getResources().getDrawable(R.drawable.fxaccount_password_button_hide_background));
|
||||
showPasswordButton.setTextColor(getResources().getColor(R.color.fxaccount_password_hide_textcolor));
|
||||
}
|
||||
passwordEdit.setSelection(start, stop);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -4,375 +4,103 @@
|
||||
|
||||
package org.mozilla.gecko.fxa.activities;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.mozilla.gecko.R;
|
||||
import org.mozilla.gecko.background.common.log.Logger;
|
||||
import org.mozilla.gecko.db.BrowserContract;
|
||||
import org.mozilla.gecko.fxa.FxAccountConstants;
|
||||
import org.mozilla.gecko.fxa.FirefoxAccounts;
|
||||
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.sync.SyncConfiguration;
|
||||
import org.mozilla.gecko.sync.setup.activities.ActivityUtils;
|
||||
|
||||
import android.content.ContentResolver;
|
||||
import android.content.SharedPreferences;
|
||||
import android.accounts.Account;
|
||||
import android.annotation.TargetApi;
|
||||
import android.app.ActionBar;
|
||||
import android.content.Intent;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.view.View;
|
||||
import android.view.View.OnClickListener;
|
||||
import android.widget.Button;
|
||||
import android.widget.CheckBox;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.TextView;
|
||||
import android.widget.ViewFlipper;
|
||||
import android.support.v4.app.FragmentActivity;
|
||||
import android.view.MenuItem;
|
||||
|
||||
/**
|
||||
* Activity which displays account status.
|
||||
*/
|
||||
public class FxAccountStatusActivity extends FxAccountAbstractActivity implements OnClickListener {
|
||||
public class FxAccountStatusActivity extends FragmentActivity {
|
||||
private static final String LOG_TAG = FxAccountStatusActivity.class.getSimpleName();
|
||||
|
||||
// When a checkbox is toggled, wait 5 seconds (for other checkbox actions)
|
||||
// before trying to sync. Should we quit the activity before the sync request
|
||||
// happens, that's okay: the runnable will run if the UI thread is still
|
||||
// around to service it, and since we're not updating any UI, we'll just
|
||||
// schedule the sync as usual. See also comment below about garbage
|
||||
// collection.
|
||||
private static final long DELAY_IN_MILLISECONDS_BEFORE_REQUESTING_SYNC = 5 * 1000;
|
||||
protected FxAccountStatusFragment statusFragment;
|
||||
|
||||
// Set in onCreate.
|
||||
protected TextView syncStatusTextView;
|
||||
protected ViewFlipper connectionStatusViewFlipper;
|
||||
protected View connectionStatusUnverifiedView;
|
||||
protected View connectionStatusSignInView;
|
||||
protected TextView emailTextView;
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
protected CheckBox bookmarksCheckBox;
|
||||
protected CheckBox historyCheckBox;
|
||||
protected CheckBox passwordsCheckBox;
|
||||
protected CheckBox tabsCheckBox;
|
||||
// Display the fragment as the content.
|
||||
statusFragment = new FxAccountStatusFragment();
|
||||
getSupportFragmentManager()
|
||||
.beginTransaction()
|
||||
.replace(android.R.id.content, statusFragment)
|
||||
.commit();
|
||||
|
||||
// Used to post delayed sync requests.
|
||||
protected Handler handler;
|
||||
|
||||
// Set in onResume.
|
||||
protected AndroidFxAccount fxAccount;
|
||||
// Member variable so that re-posting pushes back the already posted instance.
|
||||
// This Runnable references the fxAccount above, so it is not specific a
|
||||
// single account. (That is, it does not capture a single account instance.)
|
||||
protected Runnable requestSyncRunnable;
|
||||
|
||||
public FxAccountStatusActivity() {
|
||||
super(CANNOT_RESUME_WHEN_NO_ACCOUNTS_EXIST);
|
||||
maybeSetHomeButtonEnabled();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
* Sufficiently recent Android versions need additional code to receive taps
|
||||
* on the status bar to go "up". See <a
|
||||
* href="http://stackoverflow.com/a/8953148">this stackoverflow answer</a> for
|
||||
* more information.
|
||||
*/
|
||||
@Override
|
||||
public void onCreate(Bundle icicle) {
|
||||
Logger.setThreadLogTag(FxAccountConstants.GLOBAL_LOG_TAG);
|
||||
Logger.debug(LOG_TAG, "onCreate(" + icicle + ")");
|
||||
|
||||
super.onCreate(icicle);
|
||||
setContentView(R.layout.fxaccount_status);
|
||||
|
||||
syncStatusTextView = (TextView) ensureFindViewById(null, R.id.sync_status_text, "sync status text");
|
||||
connectionStatusViewFlipper = (ViewFlipper) ensureFindViewById(null, R.id.connection_status_view, "connection status frame layout");
|
||||
connectionStatusUnverifiedView = ensureFindViewById(null, R.id.unverified_view, "unverified view");
|
||||
connectionStatusSignInView = ensureFindViewById(null, R.id.sign_in_view, "sign in view");
|
||||
|
||||
launchActivityOnClick(connectionStatusSignInView, FxAccountUpdateCredentialsActivity.class);
|
||||
|
||||
connectionStatusUnverifiedView.setOnClickListener(new OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
FxAccountConfirmAccountActivity.resendCode(getApplicationContext(), fxAccount);
|
||||
launchActivity(FxAccountConfirmAccountActivity.class);
|
||||
}
|
||||
});
|
||||
|
||||
emailTextView = (TextView) findViewById(R.id.email);
|
||||
|
||||
bookmarksCheckBox = (CheckBox) findViewById(R.id.bookmarks_checkbox);
|
||||
historyCheckBox = (CheckBox) findViewById(R.id.history_checkbox);
|
||||
passwordsCheckBox = (CheckBox) findViewById(R.id.passwords_checkbox);
|
||||
tabsCheckBox = (CheckBox) findViewById(R.id.tabs_checkbox);
|
||||
bookmarksCheckBox.setOnClickListener(this);
|
||||
historyCheckBox.setOnClickListener(this);
|
||||
passwordsCheckBox.setOnClickListener(this);
|
||||
tabsCheckBox.setOnClickListener(this);
|
||||
|
||||
handler = new Handler(); // Attached to current (UI) thread.
|
||||
// Runnable is not specific to one Firefox Account. This runnable will keep
|
||||
// a reference to this activity alive, but we expect posted runnables to be
|
||||
// serviced very quickly, so this is not an issue.
|
||||
requestSyncRunnable = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (fxAccount == null) {
|
||||
return;
|
||||
}
|
||||
Logger.info(LOG_TAG, "Requesting a sync sometime soon.");
|
||||
// Request a sync, but not necessarily an immediate sync.
|
||||
ContentResolver.requestSync(fxAccount.getAndroidAccount(), BrowserContract.AUTHORITY, Bundle.EMPTY);
|
||||
// SyncAdapter.requestImmediateSync(fxAccount.getAndroidAccount(), null);
|
||||
}
|
||||
};
|
||||
|
||||
TextView privacyView = (TextView) findViewById(R.id.fxaccount_status_linkprivacy);
|
||||
ActivityUtils.linkTextView(privacyView, R.string.fxaccount_policy_linkprivacy, R.string.fxaccount_link_pn);
|
||||
|
||||
TextView tosView = (TextView) findViewById(R.id.fxaccount_status_linktos);
|
||||
ActivityUtils.linkTextView(tosView, R.string.fxaccount_policy_linktos, R.string.fxaccount_link_tos);
|
||||
|
||||
if (FxAccountConstants.LOG_PERSONAL_INFORMATION) {
|
||||
createDebugButtons();
|
||||
@TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
|
||||
protected void maybeSetHomeButtonEnabled() {
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
|
||||
Logger.debug(LOG_TAG, "Not enabling home button; version too low.");
|
||||
return;
|
||||
}
|
||||
final ActionBar actionBar = getActionBar();
|
||||
if (actionBar != null) {
|
||||
Logger.debug(LOG_TAG, "Enabling home button.");
|
||||
actionBar.setHomeButtonEnabled(true);
|
||||
return;
|
||||
}
|
||||
Logger.debug(LOG_TAG, "Not enabling home button.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
this.fxAccount = getAndroidFxAccount();
|
||||
|
||||
final AndroidFxAccount fxAccount = getAndroidFxAccount();
|
||||
if (fxAccount == null) {
|
||||
Logger.warn(LOG_TAG, "Could not get Firefox Account.");
|
||||
|
||||
// Gracefully redirect to get started.
|
||||
Intent intent = new Intent(this, FxAccountGetStartedActivity.class);
|
||||
// Per http://stackoverflow.com/a/8992365, this triggers a known bug with
|
||||
// the soft keyboard not being shown for the started activity. Why, Android, why?
|
||||
intent.setFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION);
|
||||
startActivity(intent);
|
||||
|
||||
setResult(RESULT_CANCELED);
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
|
||||
refresh();
|
||||
statusFragment.refresh(fxAccount);
|
||||
}
|
||||
|
||||
protected void setCheckboxesEnabled(boolean enabled) {
|
||||
bookmarksCheckBox.setEnabled(enabled);
|
||||
historyCheckBox.setEnabled(enabled);
|
||||
passwordsCheckBox.setEnabled(enabled);
|
||||
tabsCheckBox.setEnabled(enabled);
|
||||
}
|
||||
|
||||
protected void showNeedsUpgrade() {
|
||||
syncStatusTextView.setText(R.string.fxaccount_status_sync);
|
||||
connectionStatusViewFlipper.setVisibility(View.VISIBLE);
|
||||
connectionStatusViewFlipper.setDisplayedChild(0);
|
||||
setCheckboxesEnabled(false);
|
||||
}
|
||||
|
||||
protected void showNeedsPassword() {
|
||||
syncStatusTextView.setText(R.string.fxaccount_status_sync);
|
||||
connectionStatusViewFlipper.setVisibility(View.VISIBLE);
|
||||
connectionStatusViewFlipper.setDisplayedChild(1);
|
||||
setCheckboxesEnabled(false);
|
||||
}
|
||||
|
||||
protected void showNeedsVerification() {
|
||||
syncStatusTextView.setText(R.string.fxaccount_status_sync);
|
||||
connectionStatusViewFlipper.setVisibility(View.VISIBLE);
|
||||
connectionStatusViewFlipper.setDisplayedChild(2);
|
||||
setCheckboxesEnabled(false);
|
||||
}
|
||||
|
||||
protected void showConnected() {
|
||||
syncStatusTextView.setText(R.string.fxaccount_status_sync_enabled);
|
||||
connectionStatusViewFlipper.setVisibility(View.GONE);
|
||||
setCheckboxesEnabled(true);
|
||||
}
|
||||
|
||||
protected void refresh() {
|
||||
emailTextView.setText(fxAccount.getEmail());
|
||||
|
||||
// Interrogate the Firefox Account's state.
|
||||
State state = fxAccount.getState();
|
||||
switch (state.getNeededAction()) {
|
||||
case NeedsUpgrade:
|
||||
showNeedsUpgrade();
|
||||
break;
|
||||
case NeedsPassword:
|
||||
showNeedsPassword();
|
||||
break;
|
||||
case NeedsVerification:
|
||||
showNeedsVerification();
|
||||
break;
|
||||
default:
|
||||
showConnected();
|
||||
}
|
||||
|
||||
updateSelectedEngines();
|
||||
}
|
||||
|
||||
protected void updateSelectedEngines() {
|
||||
try {
|
||||
SharedPreferences syncPrefs = fxAccount.getSyncPrefs();
|
||||
Map<String, Boolean> engines = SyncConfiguration.getUserSelectedEngines(syncPrefs);
|
||||
if (engines != null) {
|
||||
bookmarksCheckBox.setChecked(engines.containsKey("bookmarks") && engines.get("bookmarks"));
|
||||
historyCheckBox.setChecked(engines.containsKey("history") && engines.get("history"));
|
||||
passwordsCheckBox.setChecked(engines.containsKey("passwords") && engines.get("passwords"));
|
||||
tabsCheckBox.setChecked(engines.containsKey("tabs") && engines.get("tabs"));
|
||||
return;
|
||||
}
|
||||
|
||||
// We don't have user specified preferences. Perhaps we have seen a meta/global?
|
||||
Set<String> enabledNames = SyncConfiguration.getEnabledEngineNames(syncPrefs);
|
||||
if (enabledNames != null) {
|
||||
bookmarksCheckBox.setChecked(enabledNames.contains("bookmarks"));
|
||||
historyCheckBox.setChecked(enabledNames.contains("history"));
|
||||
passwordsCheckBox.setChecked(enabledNames.contains("passwords"));
|
||||
tabsCheckBox.setChecked(enabledNames.contains("tabs"));
|
||||
return;
|
||||
}
|
||||
|
||||
// Okay, we don't have userSelectedEngines or enabledEngines. That means
|
||||
// the user hasn't specified to begin with, we haven't specified here, and
|
||||
// we haven't already seen, Sync engines. We don't know our state, so
|
||||
// let's check everything (the default) and disable everything.
|
||||
bookmarksCheckBox.setChecked(true);
|
||||
historyCheckBox.setChecked(true);
|
||||
passwordsCheckBox.setChecked(true);
|
||||
tabsCheckBox.setChecked(true);
|
||||
setCheckboxesEnabled(false);
|
||||
} catch (Exception e) {
|
||||
Logger.warn(LOG_TAG, "Got exception getting engines to select; ignoring.", e);
|
||||
return;
|
||||
/**
|
||||
* Helper to fetch (unique) Android Firefox Account if one exists, or return null.
|
||||
*/
|
||||
protected AndroidFxAccount getAndroidFxAccount() {
|
||||
Account account = FirefoxAccounts.getFirefoxAccount(this);
|
||||
if (account == null) {
|
||||
return null;
|
||||
}
|
||||
return new AndroidFxAccount(this, account);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
if (view == bookmarksCheckBox ||
|
||||
view == historyCheckBox ||
|
||||
view == passwordsCheckBox ||
|
||||
view == tabsCheckBox) {
|
||||
saveEngineSelections();
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
int itemId = item.getItemId();
|
||||
switch (itemId) {
|
||||
case android.R.id.home:
|
||||
finish();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
protected void saveEngineSelections() {
|
||||
Map<String, Boolean> engineSelections = new HashMap<String, Boolean>();
|
||||
engineSelections.put("bookmarks", bookmarksCheckBox.isChecked());
|
||||
engineSelections.put("history", historyCheckBox.isChecked());
|
||||
engineSelections.put("passwords", passwordsCheckBox.isChecked());
|
||||
engineSelections.put("tabs", tabsCheckBox.isChecked());
|
||||
Logger.info(LOG_TAG, "Persisting engine selections: " + engineSelections.toString());
|
||||
|
||||
try {
|
||||
// No GlobalSession.config, so store directly to prefs. We'd like to do
|
||||
// this on a background thread to avoid IO on the main thread and strict
|
||||
// mode warnings, but all in good time.
|
||||
SyncConfiguration.storeSelectedEnginesToPrefs(fxAccount.getSyncPrefs(), engineSelections);
|
||||
requestDelayedSync();
|
||||
} catch (Exception e) {
|
||||
Logger.warn(LOG_TAG, "Got exception persisting selected engines; ignoring.", e);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
protected void requestDelayedSync() {
|
||||
Logger.info(LOG_TAG, "Posting a delayed request for a sync sometime soon.");
|
||||
handler.removeCallbacks(requestSyncRunnable);
|
||||
handler.postDelayed(requestSyncRunnable, DELAY_IN_MILLISECONDS_BEFORE_REQUESTING_SYNC);
|
||||
}
|
||||
|
||||
protected void createDebugButtons() {
|
||||
if (!FxAccountConstants.LOG_PERSONAL_INFORMATION) {
|
||||
return;
|
||||
}
|
||||
|
||||
final LinearLayout existingUserView = (LinearLayout) findViewById(R.id.existing_user);
|
||||
if (existingUserView == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
final LinearLayout debugButtonsView = new LinearLayout(this);
|
||||
debugButtonsView.setOrientation(LinearLayout.VERTICAL);
|
||||
debugButtonsView.setLayoutParams(new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT));
|
||||
existingUserView.addView(debugButtonsView, existingUserView.getChildCount());
|
||||
|
||||
Button button;
|
||||
|
||||
button = new Button(this);
|
||||
debugButtonsView.addView(button, debugButtonsView.getChildCount());
|
||||
button.setText("Refresh status view");
|
||||
button.setOnClickListener(new OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
Logger.info(LOG_TAG, "Refreshing.");
|
||||
refresh();
|
||||
}
|
||||
});
|
||||
|
||||
button = new Button(this);
|
||||
debugButtonsView.addView(button, debugButtonsView.getChildCount());
|
||||
button.setText("Dump account details");
|
||||
button.setOnClickListener(new OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
fxAccount.dump();
|
||||
}
|
||||
});
|
||||
|
||||
button = new Button(this);
|
||||
debugButtonsView.addView(button, debugButtonsView.getChildCount());
|
||||
button.setText("Force sync");
|
||||
button.setOnClickListener(new OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
Logger.info(LOG_TAG, "Syncing.");
|
||||
final Bundle extras = new Bundle();
|
||||
extras.putBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, true);
|
||||
ContentResolver.requestSync(fxAccount.getAndroidAccount(), BrowserContract.AUTHORITY, extras);
|
||||
// No sense refreshing, since the sync will complete in the future.
|
||||
}
|
||||
});
|
||||
|
||||
button = new Button(this);
|
||||
debugButtonsView.addView(button, debugButtonsView.getChildCount());
|
||||
button.setText("Forget certificate (if applicable)");
|
||||
button.setOnClickListener(new OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
State state = fxAccount.getState();
|
||||
try {
|
||||
Married married = (Married) state;
|
||||
Logger.info(LOG_TAG, "Moving to Cohabiting state: Forgetting certificate.");
|
||||
fxAccount.setState(married.makeCohabitingState());
|
||||
refresh();
|
||||
} catch (ClassCastException e) {
|
||||
Logger.info(LOG_TAG, "Not in Married state; can't forget certificate.");
|
||||
// Ignore.
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
button = new Button(this);
|
||||
debugButtonsView.addView(button, debugButtonsView.getChildCount());
|
||||
button.setText("Require password");
|
||||
button.setOnClickListener(new OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
Logger.info(LOG_TAG, "Moving to Separated state: Forgetting password.");
|
||||
State state = fxAccount.getState();
|
||||
fxAccount.setState(state.makeSeparatedState());
|
||||
refresh();
|
||||
}
|
||||
});
|
||||
|
||||
button = new Button(this);
|
||||
debugButtonsView.addView(button, debugButtonsView.getChildCount());
|
||||
button.setText("Require upgrade");
|
||||
button.setOnClickListener(new OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
Logger.info(LOG_TAG, "Moving to Doghouse state: Requiring upgrade.");
|
||||
State state = fxAccount.getState();
|
||||
fxAccount.setState(state.makeDoghouseState());
|
||||
refresh();
|
||||
}
|
||||
});
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
}
|
||||
|
468
mobile/android/base/fxa/activities/FxAccountStatusFragment.java
Normal file
468
mobile/android/base/fxa/activities/FxAccountStatusFragment.java
Normal file
@ -0,0 +1,468 @@
|
||||
/* 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.activities;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.mozilla.gecko.R;
|
||||
import org.mozilla.gecko.background.common.log.Logger;
|
||||
import org.mozilla.gecko.background.preferences.PreferenceFragment;
|
||||
import org.mozilla.gecko.db.BrowserContract;
|
||||
import org.mozilla.gecko.fxa.FxAccountConstants;
|
||||
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.sync.SyncConfiguration;
|
||||
|
||||
import android.content.ContentResolver;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.preference.CheckBoxPreference;
|
||||
import android.preference.Preference;
|
||||
import android.preference.Preference.OnPreferenceClickListener;
|
||||
import android.preference.PreferenceCategory;
|
||||
import android.preference.PreferenceScreen;
|
||||
|
||||
/**
|
||||
* A fragment that displays the status of an AndroidFxAccount.
|
||||
* <p>
|
||||
* The owning activity is responsible for providing an AndroidFxAccount at
|
||||
* appropriate times.
|
||||
*/
|
||||
public class FxAccountStatusFragment extends PreferenceFragment implements OnPreferenceClickListener {
|
||||
private static final String LOG_TAG = FxAccountStatusFragment.class.getSimpleName();
|
||||
|
||||
// When a checkbox is toggled, wait 5 seconds (for other checkbox actions)
|
||||
// before trying to sync. Should we kill off the fragment before the sync
|
||||
// request happens, that's okay: the runnable will run if the UI thread is
|
||||
// still around to service it, and since we're not updating any UI, we'll just
|
||||
// schedule the sync as usual. See also comment below about garbage
|
||||
// collection.
|
||||
private static final long DELAY_IN_MILLISECONDS_BEFORE_REQUESTING_SYNC = 5 * 1000;
|
||||
|
||||
protected Preference emailPreference;
|
||||
|
||||
protected Preference needsPasswordPreference;
|
||||
protected Preference needsUpgradePreference;
|
||||
protected Preference needsVerificationPreference;
|
||||
|
||||
protected PreferenceCategory syncCategory;
|
||||
|
||||
protected CheckBoxPreference bookmarksPreference;
|
||||
protected CheckBoxPreference historyPreference;
|
||||
protected CheckBoxPreference tabsPreference;
|
||||
protected CheckBoxPreference passwordsPreference;
|
||||
|
||||
protected volatile AndroidFxAccount fxAccount;
|
||||
|
||||
// Used to post delayed sync requests.
|
||||
protected Handler handler;
|
||||
|
||||
// Member variable so that re-posting pushes back the already posted instance.
|
||||
// This Runnable references the fxAccount above, but it is not specific to a
|
||||
// single account. (That is, it does not capture a single account instance.)
|
||||
protected Runnable requestSyncRunnable;
|
||||
|
||||
protected Preference ensureFindPreference(String key) {
|
||||
Preference preference = findPreference(key);
|
||||
if (preference == null) {
|
||||
throw new IllegalStateException("Could not find preference with key: " + key);
|
||||
}
|
||||
return preference;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
addPreferencesFromResource(R.xml.fxaccount_status_prefscreen);
|
||||
|
||||
emailPreference = ensureFindPreference("email");
|
||||
|
||||
needsPasswordPreference = ensureFindPreference("needs_credentials");
|
||||
needsUpgradePreference = ensureFindPreference("needs_upgrade");
|
||||
needsVerificationPreference = ensureFindPreference("needs_verification");
|
||||
|
||||
syncCategory = (PreferenceCategory) ensureFindPreference("sync_category");
|
||||
|
||||
bookmarksPreference = (CheckBoxPreference) ensureFindPreference("bookmarks");
|
||||
historyPreference = (CheckBoxPreference) ensureFindPreference("history");
|
||||
tabsPreference = (CheckBoxPreference) ensureFindPreference("tabs");
|
||||
passwordsPreference = (CheckBoxPreference) ensureFindPreference("passwords");
|
||||
|
||||
if (!FxAccountConstants.LOG_PERSONAL_INFORMATION) {
|
||||
removeDebugButtons();
|
||||
} else {
|
||||
connectDebugButtons();
|
||||
}
|
||||
|
||||
needsPasswordPreference.setOnPreferenceClickListener(this);
|
||||
needsVerificationPreference.setOnPreferenceClickListener(this);
|
||||
|
||||
bookmarksPreference.setOnPreferenceClickListener(this);
|
||||
historyPreference.setOnPreferenceClickListener(this);
|
||||
tabsPreference.setOnPreferenceClickListener(this);
|
||||
passwordsPreference.setOnPreferenceClickListener(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* We intentionally don't refresh here. Our owning activity is responsible for
|
||||
* providing an AndroidFxAccount to our refresh method in its onResume method.
|
||||
*/
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onPreferenceClick(Preference preference) {
|
||||
if (preference == needsPasswordPreference) {
|
||||
Intent intent = new Intent(getActivity(), FxAccountUpdateCredentialsActivity.class);
|
||||
// Per http://stackoverflow.com/a/8992365, this triggers a known bug with
|
||||
// the soft keyboard not being shown for the started activity. Why, Android, why?
|
||||
intent.setFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION);
|
||||
startActivity(intent);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
if (preference == needsVerificationPreference) {
|
||||
FxAccountConfirmAccountActivity.resendCode(getActivity().getApplicationContext(), fxAccount);
|
||||
|
||||
Intent intent = new Intent(getActivity(), FxAccountConfirmAccountActivity.class);
|
||||
// Per http://stackoverflow.com/a/8992365, this triggers a known bug with
|
||||
// the soft keyboard not being shown for the started activity. Why, Android, why?
|
||||
intent.setFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION);
|
||||
startActivity(intent);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
if (preference == bookmarksPreference ||
|
||||
preference == historyPreference ||
|
||||
preference == passwordsPreference ||
|
||||
preference == tabsPreference) {
|
||||
saveEngineSelections();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
protected void setCheckboxesEnabled(boolean enabled) {
|
||||
bookmarksPreference.setEnabled(enabled);
|
||||
historyPreference.setEnabled(enabled);
|
||||
tabsPreference.setEnabled(enabled);
|
||||
passwordsPreference.setEnabled(enabled);
|
||||
}
|
||||
|
||||
/**
|
||||
* Show at most one error preference, hiding all others.
|
||||
*
|
||||
* @param errorPreferenceToShow
|
||||
* single error preference to show; if null, hide all error preferences
|
||||
*/
|
||||
protected void showOnlyOneErrorPreference(Preference errorPreferenceToShow) {
|
||||
final Preference[] errorPreferences = new Preference[] {
|
||||
this.needsPasswordPreference,
|
||||
this.needsUpgradePreference,
|
||||
this.needsVerificationPreference };
|
||||
for (Preference errorPreference : errorPreferences) {
|
||||
final boolean currentlyShown = null != findPreference(errorPreference.getKey());
|
||||
final boolean shouldBeShown = errorPreference == errorPreferenceToShow;
|
||||
if (currentlyShown == shouldBeShown) {
|
||||
continue;
|
||||
}
|
||||
if (shouldBeShown) {
|
||||
syncCategory.addPreference(errorPreference);
|
||||
} else {
|
||||
syncCategory.removePreference(errorPreference);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void showNeedsPassword() {
|
||||
syncCategory.setTitle(R.string.fxaccount_status_sync);
|
||||
showOnlyOneErrorPreference(needsPasswordPreference);
|
||||
setCheckboxesEnabled(false);
|
||||
}
|
||||
|
||||
protected void showNeedsUpgrade() {
|
||||
syncCategory.setTitle(R.string.fxaccount_status_sync);
|
||||
showOnlyOneErrorPreference(needsUpgradePreference);
|
||||
setCheckboxesEnabled(false);
|
||||
}
|
||||
|
||||
protected void showNeedsVerification() {
|
||||
syncCategory.setTitle(R.string.fxaccount_status_sync);
|
||||
showOnlyOneErrorPreference(needsVerificationPreference);
|
||||
setCheckboxesEnabled(false);
|
||||
}
|
||||
|
||||
protected void showConnected() {
|
||||
syncCategory.setTitle(R.string.fxaccount_status_sync_enabled);
|
||||
showOnlyOneErrorPreference(null);
|
||||
setCheckboxesEnabled(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify the fragment that a new AndroidFxAccount instance is current.
|
||||
* <p>
|
||||
* <b>Important:</b> call this method on the UI thread!
|
||||
* <p>
|
||||
* In future, this might be a Loader.
|
||||
*
|
||||
* @param fxAccount new instance.
|
||||
*/
|
||||
public void refresh(AndroidFxAccount fxAccount) {
|
||||
if (fxAccount == null) {
|
||||
throw new IllegalArgumentException("fxAccount must not be null");
|
||||
}
|
||||
this.fxAccount = fxAccount;
|
||||
|
||||
handler = new Handler(); // Attached to current (assumed to be UI) thread.
|
||||
|
||||
// Runnable is not specific to one Firefox Account. This runnable will keep
|
||||
// a reference to this fragment alive, but we expect posted runnables to be
|
||||
// serviced very quickly, so this is not an issue.
|
||||
requestSyncRunnable = new RequestSyncRunnable();
|
||||
|
||||
refresh();
|
||||
}
|
||||
|
||||
protected void refresh() {
|
||||
// refresh is called from our onResume, which can happen before the owning
|
||||
// Activity tells us about an account (via our public
|
||||
// refresh(AndroidFxAccount) method).
|
||||
if (fxAccount == null) {
|
||||
throw new IllegalArgumentException("fxAccount must not be null");
|
||||
}
|
||||
|
||||
emailPreference.setTitle(fxAccount.getEmail());
|
||||
|
||||
// Interrogate the Firefox Account's state.
|
||||
State state = fxAccount.getState();
|
||||
switch (state.getNeededAction()) {
|
||||
case NeedsUpgrade:
|
||||
showNeedsUpgrade();
|
||||
break;
|
||||
case NeedsPassword:
|
||||
showNeedsPassword();
|
||||
break;
|
||||
case NeedsVerification:
|
||||
showNeedsVerification();
|
||||
break;
|
||||
default:
|
||||
showConnected();
|
||||
}
|
||||
|
||||
updateSelectedEngines();
|
||||
}
|
||||
|
||||
/**
|
||||
* Query shared prefs for the current engine state, and update the UI
|
||||
* accordingly.
|
||||
* <p>
|
||||
* In future, we might want this to be on a background thread, or implemented
|
||||
* as a Loader.
|
||||
*/
|
||||
protected void updateSelectedEngines() {
|
||||
try {
|
||||
SharedPreferences syncPrefs = fxAccount.getSyncPrefs();
|
||||
Map<String, Boolean> engines = SyncConfiguration.getUserSelectedEngines(syncPrefs);
|
||||
if (engines != null) {
|
||||
bookmarksPreference.setChecked(engines.containsKey("bookmarks") && engines.get("bookmarks"));
|
||||
historyPreference.setChecked(engines.containsKey("history") && engines.get("history"));
|
||||
passwordsPreference.setChecked(engines.containsKey("passwords") && engines.get("passwords"));
|
||||
tabsPreference.setChecked(engines.containsKey("tabs") && engines.get("tabs"));
|
||||
return;
|
||||
}
|
||||
|
||||
// We don't have user specified preferences. Perhaps we have seen a meta/global?
|
||||
Set<String> enabledNames = SyncConfiguration.getEnabledEngineNames(syncPrefs);
|
||||
if (enabledNames != null) {
|
||||
bookmarksPreference.setChecked(enabledNames.contains("bookmarks"));
|
||||
historyPreference.setChecked(enabledNames.contains("history"));
|
||||
passwordsPreference.setChecked(enabledNames.contains("passwords"));
|
||||
tabsPreference.setChecked(enabledNames.contains("tabs"));
|
||||
return;
|
||||
}
|
||||
|
||||
// Okay, we don't have userSelectedEngines or enabledEngines. That means
|
||||
// the user hasn't specified to begin with, we haven't specified here, and
|
||||
// we haven't already seen, Sync engines. We don't know our state, so
|
||||
// let's check everything (the default) and disable everything.
|
||||
bookmarksPreference.setChecked(true);
|
||||
historyPreference.setChecked(true);
|
||||
passwordsPreference.setChecked(true);
|
||||
tabsPreference.setChecked(true);
|
||||
setCheckboxesEnabled(false);
|
||||
} catch (Exception e) {
|
||||
Logger.warn(LOG_TAG, "Got exception getting engines to select; ignoring.", e);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Persist engine selections to local shared preferences, and request a sync
|
||||
* to persist selections to remote storage.
|
||||
*/
|
||||
protected void saveEngineSelections() {
|
||||
final Map<String, Boolean> engineSelections = new HashMap<String, Boolean>();
|
||||
engineSelections.put("bookmarks", bookmarksPreference.isChecked());
|
||||
engineSelections.put("history", historyPreference.isChecked());
|
||||
engineSelections.put("passwords", passwordsPreference.isChecked());
|
||||
engineSelections.put("tabs", tabsPreference.isChecked());
|
||||
|
||||
// No GlobalSession.config, so store directly to shared prefs. We do this on
|
||||
// a background thread to avoid IO on the main thread and strict mode
|
||||
// warnings.
|
||||
new Thread(new PersistEngineSelectionsRunnable(engineSelections)).start();
|
||||
}
|
||||
|
||||
protected void requestDelayedSync() {
|
||||
Logger.info(LOG_TAG, "Posting a delayed request for a sync sometime soon.");
|
||||
handler.removeCallbacks(requestSyncRunnable);
|
||||
handler.postDelayed(requestSyncRunnable, DELAY_IN_MILLISECONDS_BEFORE_REQUESTING_SYNC);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove all traces of debug buttons. By default, no debug buttons are shown.
|
||||
*/
|
||||
protected void removeDebugButtons() {
|
||||
final PreferenceScreen statusScreen = (PreferenceScreen) ensureFindPreference("status_screen");
|
||||
final PreferenceCategory debugCategory = (PreferenceCategory) ensureFindPreference("debug_category");
|
||||
statusScreen.removePreference(debugCategory);
|
||||
}
|
||||
|
||||
/**
|
||||
* A Runnable that persists engine selections to shared prefs, and then
|
||||
* requests a delayed sync.
|
||||
* <p>
|
||||
* References the member <code>fxAccount</code> and is specific to the Android
|
||||
* account associated to that account.
|
||||
*/
|
||||
protected class PersistEngineSelectionsRunnable implements Runnable {
|
||||
private final Map<String, Boolean> engineSelections;
|
||||
|
||||
protected PersistEngineSelectionsRunnable(Map<String, Boolean> engineSelections) {
|
||||
this.engineSelections = engineSelections;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
// Name shadowing -- do you like it, or do you love it?
|
||||
AndroidFxAccount fxAccount = FxAccountStatusFragment.this.fxAccount;
|
||||
if (fxAccount == null) {
|
||||
return;
|
||||
}
|
||||
Logger.info(LOG_TAG, "Persisting engine selections: " + engineSelections.toString());
|
||||
SyncConfiguration.storeSelectedEnginesToPrefs(fxAccount.getSyncPrefs(), engineSelections);
|
||||
requestDelayedSync();
|
||||
} catch (Exception e) {
|
||||
Logger.warn(LOG_TAG, "Got exception persisting selected engines; ignoring.", e);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A Runnable that requests a sync.
|
||||
* <p>
|
||||
* References the member <code>fxAccount</code>, but is not specific to the
|
||||
* Android account associated to that account.
|
||||
*/
|
||||
protected class RequestSyncRunnable implements Runnable {
|
||||
@Override
|
||||
public void run() {
|
||||
// Name shadowing -- do you like it, or do you love it?
|
||||
AndroidFxAccount fxAccount = FxAccountStatusFragment.this.fxAccount;
|
||||
if (fxAccount == null) {
|
||||
return;
|
||||
}
|
||||
Logger.info(LOG_TAG, "Requesting a sync sometime soon.");
|
||||
// Request a sync, but not necessarily an immediate sync.
|
||||
ContentResolver.requestSync(fxAccount.getAndroidAccount(), BrowserContract.AUTHORITY, Bundle.EMPTY);
|
||||
// SyncAdapter.requestImmediateSync(fxAccount.getAndroidAccount(), null);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A separate listener to separate debug logic from main code paths.
|
||||
*/
|
||||
protected class DebugPreferenceClickListener implements OnPreferenceClickListener {
|
||||
@Override
|
||||
public boolean onPreferenceClick(Preference preference) {
|
||||
final String key = preference.getKey();
|
||||
if ("debug_refresh".equals(key)) {
|
||||
Logger.info(LOG_TAG, "Refreshing.");
|
||||
refresh();
|
||||
} else if ("debug_dump".equals(key)) {
|
||||
fxAccount.dump();
|
||||
} else if ("debug_force_sync".equals(key)) {
|
||||
Logger.info(LOG_TAG, "Syncing.");
|
||||
final Bundle extras = new Bundle();
|
||||
extras.putBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, true);
|
||||
fxAccount.requestSync(extras);
|
||||
// No sense refreshing, since the sync will complete in the future.
|
||||
} else if ("debug_forget_certificate".equals(key)) {
|
||||
State state = fxAccount.getState();
|
||||
try {
|
||||
Married married = (Married) state;
|
||||
Logger.info(LOG_TAG, "Moving to Cohabiting state: Forgetting certificate.");
|
||||
fxAccount.setState(married.makeCohabitingState());
|
||||
refresh();
|
||||
} catch (ClassCastException e) {
|
||||
Logger.info(LOG_TAG, "Not in Married state; can't forget certificate.");
|
||||
// Ignore.
|
||||
}
|
||||
} else if ("debug_require_password".equals(key)) {
|
||||
Logger.info(LOG_TAG, "Moving to Separated state: Forgetting password.");
|
||||
State state = fxAccount.getState();
|
||||
fxAccount.setState(state.makeSeparatedState());
|
||||
refresh();
|
||||
} else if ("debug_require_upgrade".equals(key)) {
|
||||
Logger.info(LOG_TAG, "Moving to Doghouse state: Requiring upgrade.");
|
||||
State state = fxAccount.getState();
|
||||
fxAccount.setState(state.makeDoghouseState());
|
||||
refresh();
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Iterate through debug buttons, adding a special deubg preference click
|
||||
* listener to each of them.
|
||||
*/
|
||||
protected void connectDebugButtons() {
|
||||
// Separate listener to really separate debug logic from main code paths.
|
||||
final OnPreferenceClickListener listener = new DebugPreferenceClickListener();
|
||||
|
||||
// We don't want to use Android resource strings for debug UI, so we just
|
||||
// use the keys throughout.
|
||||
final Preference debugCategory = ensureFindPreference("debug_category");
|
||||
debugCategory.setTitle(debugCategory.getKey());
|
||||
|
||||
String[] debugKeys = new String[] {
|
||||
"debug_refresh",
|
||||
"debug_dump",
|
||||
"debug_force_sync",
|
||||
"debug_forget_certificate",
|
||||
"debug_require_password",
|
||||
"debug_require_upgrade" };
|
||||
for (String debugKey : debugKeys) {
|
||||
final Preference button = ensureFindPreference(debugKey);
|
||||
button.setTitle(debugKey); // Not very friendly, but this is for debugging only!
|
||||
button.setOnPreferenceClickListener(listener);
|
||||
}
|
||||
}
|
||||
}
|
@ -11,7 +11,9 @@ import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
|
||||
import org.mozilla.gecko.background.common.GlobalConstants;
|
||||
import org.mozilla.gecko.background.common.log.Logger;
|
||||
import org.mozilla.gecko.background.fxa.FxAccountUtils;
|
||||
import org.mozilla.gecko.db.BrowserContract;
|
||||
import org.mozilla.gecko.fxa.FxAccountConstants;
|
||||
import org.mozilla.gecko.fxa.login.State;
|
||||
import org.mozilla.gecko.fxa.login.State.StateLabel;
|
||||
@ -21,6 +23,7 @@ import org.mozilla.gecko.sync.Utils;
|
||||
|
||||
import android.accounts.Account;
|
||||
import android.accounts.AccountManager;
|
||||
import android.content.ContentResolver;
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.os.Bundle;
|
||||
@ -324,17 +327,34 @@ public class AndroidFxAccount {
|
||||
}
|
||||
|
||||
public void enableSyncing() {
|
||||
FxAccountAuthenticator.enableSyncing(context, account);
|
||||
Logger.info(LOG_TAG, "Disabling sync for account named like " + Utils.obfuscateEmail(getEmail()));
|
||||
for (String authority : new String[] { BrowserContract.AUTHORITY }) {
|
||||
ContentResolver.setSyncAutomatically(account, authority, true);
|
||||
ContentResolver.setIsSyncable(account, authority, 1);
|
||||
}
|
||||
}
|
||||
|
||||
public void disableSyncing() {
|
||||
FxAccountAuthenticator.disableSyncing(context, account);
|
||||
Logger.info(LOG_TAG, "Disabling sync for account named like " + Utils.obfuscateEmail(getEmail()));
|
||||
for (String authority : new String[] { BrowserContract.AUTHORITY }) {
|
||||
ContentResolver.setSyncAutomatically(account, authority, false);
|
||||
}
|
||||
}
|
||||
|
||||
public void requestSync(Bundle extras) {
|
||||
Logger.info(LOG_TAG, "Requesting sync for account named like " + Utils.obfuscateEmail(getEmail()) +
|
||||
(extras.isEmpty() ? "." : "; has extras."));
|
||||
for (String authority : new String[] { BrowserContract.AUTHORITY }) {
|
||||
ContentResolver.requestSync(account, authority, extras);
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized void setState(State state) {
|
||||
if (state == null) {
|
||||
throw new IllegalArgumentException("state must not be null");
|
||||
}
|
||||
Logger.info(LOG_TAG, "Moving account named like " + Utils.obfuscateEmail(getEmail()) +
|
||||
" to state " + state.getStateLabel().toString());
|
||||
updateBundleValue(BUNDLE_KEY_STATE_LABEL, state.getStateLabel().name());
|
||||
updateBundleValue(BUNDLE_KEY_STATE, state.toJSONObject().toJSONString());
|
||||
}
|
||||
|
@ -4,7 +4,6 @@
|
||||
|
||||
package org.mozilla.gecko.fxa.authenticator;
|
||||
|
||||
import org.mozilla.gecko.AppConstants;
|
||||
import org.mozilla.gecko.background.common.log.Logger;
|
||||
import org.mozilla.gecko.fxa.FxAccountConstants;
|
||||
import org.mozilla.gecko.fxa.activities.FxAccountGetStartedActivity;
|
||||
@ -14,7 +13,6 @@ import android.accounts.Account;
|
||||
import android.accounts.AccountAuthenticatorResponse;
|
||||
import android.accounts.AccountManager;
|
||||
import android.accounts.NetworkErrorException;
|
||||
import android.content.ContentResolver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
@ -31,23 +29,6 @@ public class FxAccountAuthenticator extends AbstractAccountAuthenticator {
|
||||
this.accountManager = AccountManager.get(context);
|
||||
}
|
||||
|
||||
protected static void enableSyncing(Context context, Account account) {
|
||||
for (String authority : new String[] {
|
||||
AppConstants.ANDROID_PACKAGE_NAME + ".db.browser",
|
||||
}) {
|
||||
ContentResolver.setSyncAutomatically(account, authority, true);
|
||||
ContentResolver.setIsSyncable(account, authority, 1);
|
||||
}
|
||||
}
|
||||
|
||||
protected static void disableSyncing(Context context, Account account) {
|
||||
for (String authority : new String[] {
|
||||
AppConstants.ANDROID_PACKAGE_NAME + ".db.browser",
|
||||
}) {
|
||||
ContentResolver.setSyncAutomatically(account, authority, false);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Bundle addAccount(AccountAuthenticatorResponse response,
|
||||
String accountType, String authTokenType, String[] requiredFeatures,
|
||||
|
@ -0,0 +1,78 @@
|
||||
/* 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.sync;
|
||||
|
||||
import org.mozilla.gecko.R;
|
||||
import org.mozilla.gecko.background.common.log.Logger;
|
||||
import org.mozilla.gecko.fxa.FxAccountConstants;
|
||||
import org.mozilla.gecko.fxa.activities.FxAccountStatusActivity;
|
||||
import org.mozilla.gecko.fxa.authenticator.AndroidFxAccount;
|
||||
import org.mozilla.gecko.fxa.login.State;
|
||||
import org.mozilla.gecko.fxa.login.State.Action;
|
||||
|
||||
import android.app.NotificationManager;
|
||||
import android.app.PendingIntent;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.support.v4.app.NotificationCompat;
|
||||
import android.support.v4.app.NotificationCompat.Builder;
|
||||
|
||||
/**
|
||||
* Abstraction that manages notifications shown or hidden for a Firefox Account.
|
||||
* <p>
|
||||
* In future, we anticipate this tracking things like:
|
||||
* <ul>
|
||||
* <li>new engines to offer to Sync;</li>
|
||||
* <li>service interruption updates;</li>
|
||||
* <li>messages from other clients.</li>
|
||||
* </ul>
|
||||
*/
|
||||
public class FxAccountNotificationManager {
|
||||
private static final String LOG_TAG = FxAccountNotificationManager.class.getSimpleName();
|
||||
|
||||
protected final int notificationId;
|
||||
|
||||
public FxAccountNotificationManager(int notificationId) {
|
||||
this.notificationId = notificationId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reflect new Firefox Account state to the notification manager: show or hide
|
||||
* notifications reflecting the state of a Firefox Account.
|
||||
*
|
||||
* @param context
|
||||
* Android context.
|
||||
* @param fxAccount
|
||||
* Firefox Account to reflect to the notification manager.
|
||||
*/
|
||||
protected void update(Context context, AndroidFxAccount fxAccount) {
|
||||
final NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
|
||||
|
||||
final State state = fxAccount.getState();
|
||||
final Action action = state.getNeededAction();
|
||||
if (action == Action.None) {
|
||||
Logger.info(LOG_TAG, "State " + state.getStateLabel() + " needs no action; cancelling any existing notification.");
|
||||
notificationManager.cancel(notificationId);
|
||||
return;
|
||||
}
|
||||
|
||||
final String title = context.getResources().getString(R.string.fxaccount_sync_sign_in_error_notification_title);
|
||||
final String text = context.getResources().getString(R.string.fxaccount_sync_sign_in_error_notification_text, state.email);
|
||||
Logger.info(LOG_TAG, "State " + state.getStateLabel() + " needs action; offering notification with title: " + title);
|
||||
FxAccountConstants.pii(LOG_TAG, "And text: " + text);
|
||||
|
||||
final Intent notificationIntent = new Intent(context, FxAccountStatusActivity.class);
|
||||
final PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, notificationIntent, 0);
|
||||
|
||||
final Builder builder = new NotificationCompat.Builder(context);
|
||||
builder
|
||||
.setContentTitle(title)
|
||||
.setContentText(text)
|
||||
.setSmallIcon(R.drawable.ic_status_logo)
|
||||
.setAutoCancel(true)
|
||||
.setContentIntent(pendingIntent);
|
||||
notificationManager.notify(notificationId, builder.build());
|
||||
}
|
||||
}
|
@ -11,7 +11,6 @@ import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
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.FxAccountClient20;
|
||||
@ -22,7 +21,6 @@ import org.mozilla.gecko.browserid.RSACryptoImplementation;
|
||||
import org.mozilla.gecko.browserid.verifier.BrowserIDRemoteVerifierClient;
|
||||
import org.mozilla.gecko.browserid.verifier.BrowserIDVerifierDelegate;
|
||||
import org.mozilla.gecko.fxa.FxAccountConstants;
|
||||
import org.mozilla.gecko.fxa.activities.FxAccountStatusActivity;
|
||||
import org.mozilla.gecko.fxa.authenticator.AndroidFxAccount;
|
||||
import org.mozilla.gecko.fxa.authenticator.FxAccountAuthenticator;
|
||||
import org.mozilla.gecko.fxa.login.FxAccountLoginStateMachine;
|
||||
@ -30,7 +28,6 @@ import org.mozilla.gecko.fxa.login.FxAccountLoginStateMachine.LoginStateMachineD
|
||||
import org.mozilla.gecko.fxa.login.FxAccountLoginTransition.Transition;
|
||||
import org.mozilla.gecko.fxa.login.Married;
|
||||
import org.mozilla.gecko.fxa.login.State;
|
||||
import org.mozilla.gecko.fxa.login.State.Action;
|
||||
import org.mozilla.gecko.fxa.login.State.StateLabel;
|
||||
import org.mozilla.gecko.sync.BackoffHandler;
|
||||
import org.mozilla.gecko.sync.ExtendedJSONObject;
|
||||
@ -51,19 +48,14 @@ import org.mozilla.gecko.tokenserver.TokenServerException;
|
||||
import org.mozilla.gecko.tokenserver.TokenServerToken;
|
||||
|
||||
import android.accounts.Account;
|
||||
import android.app.NotificationManager;
|
||||
import android.app.PendingIntent;
|
||||
import android.content.AbstractThreadedSyncAdapter;
|
||||
import android.content.ContentProviderClient;
|
||||
import android.content.ContentResolver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.SyncResult;
|
||||
import android.os.Bundle;
|
||||
import android.os.SystemClock;
|
||||
import android.support.v4.app.NotificationCompat;
|
||||
import android.support.v4.app.NotificationCompat.Builder;
|
||||
|
||||
public class FxAccountSyncAdapter extends AbstractThreadedSyncAdapter {
|
||||
private static final String LOG_TAG = FxAccountSyncAdapter.class.getSimpleName();
|
||||
@ -78,10 +70,12 @@ public class FxAccountSyncAdapter extends AbstractThreadedSyncAdapter {
|
||||
private volatile long lastSyncRealtimeMillis = 0L;
|
||||
|
||||
protected final ExecutorService executor;
|
||||
protected final FxAccountNotificationManager notificationManager;
|
||||
|
||||
public FxAccountSyncAdapter(Context context, boolean autoInitialize) {
|
||||
super(context, autoInitialize);
|
||||
this.executor = Executors.newSingleThreadExecutor();
|
||||
this.notificationManager = new FxAccountNotificationManager(NOTIFICATION_ID);
|
||||
}
|
||||
|
||||
protected static class SyncDelegate {
|
||||
@ -89,8 +83,10 @@ public class FxAccountSyncAdapter extends AbstractThreadedSyncAdapter {
|
||||
protected final CountDownLatch latch;
|
||||
protected final SyncResult syncResult;
|
||||
protected final AndroidFxAccount fxAccount;
|
||||
protected final FxAccountNotificationManager notificationManager;
|
||||
|
||||
public SyncDelegate(Context context, CountDownLatch latch, SyncResult syncResult, AndroidFxAccount fxAccount) {
|
||||
public SyncDelegate(Context context, CountDownLatch latch, SyncResult syncResult, AndroidFxAccount fxAccount,
|
||||
FxAccountNotificationManager notificationManager) {
|
||||
if (context == null) {
|
||||
throw new IllegalArgumentException("context must not be null");
|
||||
}
|
||||
@ -103,10 +99,14 @@ public class FxAccountSyncAdapter extends AbstractThreadedSyncAdapter {
|
||||
if (fxAccount == null) {
|
||||
throw new IllegalArgumentException("fxAccount must not be null");
|
||||
}
|
||||
if (notificationManager == null) {
|
||||
throw new IllegalArgumentException("notificationManager must not be null");
|
||||
}
|
||||
this.context = context;
|
||||
this.latch = latch;
|
||||
this.syncResult = syncResult;
|
||||
this.fxAccount = fxAccount;
|
||||
this.notificationManager = notificationManager;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -171,7 +171,6 @@ public class FxAccountSyncAdapter extends AbstractThreadedSyncAdapter {
|
||||
*/
|
||||
public void handleCannotSync(State finalState) {
|
||||
Logger.warn(LOG_TAG, "Cannot sync from state: " + finalState.getStateLabel());
|
||||
showNotification(context, finalState);
|
||||
setSyncResultSoftError();
|
||||
latch.countDown();
|
||||
}
|
||||
@ -395,34 +394,6 @@ public class FxAccountSyncAdapter extends AbstractThreadedSyncAdapter {
|
||||
tokenServerclient.getTokenFromBrowserIDAssertion(assertion, true, clientState, delegate);
|
||||
}
|
||||
|
||||
protected static void showNotification(Context context, State state) {
|
||||
final NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
|
||||
|
||||
final Action action = state.getNeededAction();
|
||||
if (action == Action.None) {
|
||||
Logger.info(LOG_TAG, "State " + state.getStateLabel() + " needs no action; cancelling any existing notification.");
|
||||
notificationManager.cancel(NOTIFICATION_ID);
|
||||
return;
|
||||
}
|
||||
|
||||
final String title = context.getResources().getString(R.string.fxaccount_sync_sign_in_error_notification_title);
|
||||
final String text = context.getResources().getString(R.string.fxaccount_sync_sign_in_error_notification_text, state.email);
|
||||
Logger.info(LOG_TAG, "State " + state.getStateLabel() + " needs action; offering notification with title: " + title);
|
||||
FxAccountConstants.pii(LOG_TAG, "And text: " + text);
|
||||
|
||||
final Intent notificationIntent = new Intent(context, FxAccountStatusActivity.class);
|
||||
final PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, notificationIntent, 0);
|
||||
|
||||
final Builder builder = new NotificationCompat.Builder(context);
|
||||
builder
|
||||
.setContentTitle(title)
|
||||
.setContentText(text)
|
||||
.setSmallIcon(R.drawable.ic_status_logo)
|
||||
.setAutoCancel(true)
|
||||
.setContentIntent(pendingIntent);
|
||||
notificationManager.notify(NOTIFICATION_ID, builder.build());
|
||||
}
|
||||
|
||||
/**
|
||||
* A trivial Sync implementation that does not cache client keys,
|
||||
* certificates, or tokens.
|
||||
@ -454,7 +425,7 @@ public class FxAccountSyncAdapter extends AbstractThreadedSyncAdapter {
|
||||
}
|
||||
|
||||
final CountDownLatch latch = new CountDownLatch(1);
|
||||
final SyncDelegate syncDelegate = new SyncDelegate(context, latch, syncResult, fxAccount);
|
||||
final SyncDelegate syncDelegate = new SyncDelegate(context, latch, syncResult, fxAccount, notificationManager);
|
||||
|
||||
try {
|
||||
final State state;
|
||||
@ -524,6 +495,7 @@ public class FxAccountSyncAdapter extends AbstractThreadedSyncAdapter {
|
||||
Logger.info(LOG_TAG, "handleFinal: in " + state.getStateLabel());
|
||||
fxAccount.setState(state);
|
||||
schedulePolicy.onHandleFinal(state.getNeededAction());
|
||||
notificationManager.update(context, fxAccount);
|
||||
try {
|
||||
if (state.getStateLabel() != StateLabel.Married) {
|
||||
syncDelegate.handleCannotSync(state);
|
||||
|
@ -174,6 +174,7 @@
|
||||
<!ENTITY fxaccount_update_credentials_unknown_error 'Could not sign in'>
|
||||
|
||||
<!ENTITY fxaccount_status_header 'Firefox account'>
|
||||
<!ENTITY fxaccount_status_signed_in_as 'Signed in as'>
|
||||
<!ENTITY fxaccount_status_sync '&syncBrand.shortName.label;'>
|
||||
<!ENTITY fxaccount_status_sync_enabled '&syncBrand.shortName.label;: enabled'>
|
||||
<!ENTITY fxaccount_status_needs_verification2 'Your account needs to be verified. Tap to resend verification email.'>
|
||||
|
@ -154,6 +154,7 @@ gbjar.sources += [
|
||||
'GeckoMessageReceiver.java',
|
||||
'GeckoNetworkManager.java',
|
||||
'GeckoProfile.java',
|
||||
'GeckoProfileDirectories.java',
|
||||
'GeckoScreenOrientation.java',
|
||||
'GeckoSmsManager.java',
|
||||
'GeckoThread.java',
|
||||
|
@ -0,0 +1,40 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
/*
|
||||
** Copyright 2010, The Android Open Source Project
|
||||
**
|
||||
** Licensed under the Apache License, Version 2.0 (the "License");
|
||||
** you may not use this file except in compliance with the License.
|
||||
** You may obtain a copy of the License at
|
||||
**
|
||||
** http://www.apache.org/licenses/LICENSE-2.0
|
||||
**
|
||||
** Unless required by applicable law or agreed to in writing, software
|
||||
** distributed under the License is distributed on an "AS IS" BASIS,
|
||||
** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
** See the License for the specific language governing permissions and
|
||||
** limitations under the License.
|
||||
*/
|
||||
-->
|
||||
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:orientation="vertical"
|
||||
android:layout_height="fill_parent"
|
||||
android:layout_width="fill_parent"
|
||||
android:background="@android:color/transparent">
|
||||
|
||||
<ListView android:id="@android:id/list"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="0px"
|
||||
android:layout_weight="1"
|
||||
android:paddingTop="0dip"
|
||||
android:paddingBottom="@dimen/preference_fragment_padding_bottom"
|
||||
android:paddingLeft="@dimen/preference_fragment_padding_side"
|
||||
android:paddingRight="@dimen/preference_fragment_padding_side"
|
||||
android:scrollbarStyle="@integer/preference_fragment_scrollbarStyle"
|
||||
android:clipToPadding="false"
|
||||
android:drawSelectorOnTop="false"
|
||||
android:cacheColorHint="@android:color/transparent"
|
||||
android:scrollbarAlwaysDrawVerticalTrack="true" />
|
||||
|
||||
</LinearLayout>
|
@ -0,0 +1,66 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@color/fxaccount_error_preference_backgroundcolor"
|
||||
android:gravity="center_vertical"
|
||||
android:minHeight="?android:attr/listPreferredItemHeight"
|
||||
android:paddingRight="?android:attr/scrollbarSize" >
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:gravity="center"
|
||||
android:minWidth="0dp"
|
||||
android:orientation="horizontal" >
|
||||
|
||||
<ImageView
|
||||
android:id="@+android:id/icon"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:minWidth="48dip"
|
||||
android:padding="10dip" />
|
||||
</LinearLayout>
|
||||
|
||||
<RelativeLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="6dip"
|
||||
android:layout_marginLeft="15dip"
|
||||
android:layout_marginRight="6dip"
|
||||
android:layout_marginTop="6dip"
|
||||
android:layout_weight="1" >
|
||||
|
||||
<TextView
|
||||
android:id="@+android:id/title"
|
||||
style="@style/FxAccountTextItem"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:gravity="center_vertical" >
|
||||
</TextView>
|
||||
</RelativeLayout>
|
||||
|
||||
<!-- We ignore summary and widget_frame, but they still need to be present. We set them to be gone. -->
|
||||
|
||||
<TextView
|
||||
android:id="@+android:id/summary"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:maxLines="4"
|
||||
android:textAppearance="?android:attr/textAppearanceSmall"
|
||||
android:textColor="?android:attr/textColorSecondary"
|
||||
android:visibility="gone" />
|
||||
|
||||
<!-- Preference should place its actual preference widget here. -->
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+android:id/widget_frame"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:gravity="center"
|
||||
android:orientation="vertical"
|
||||
android:visibility="gone" />
|
||||
|
||||
</LinearLayout>
|
@ -38,7 +38,7 @@
|
||||
<item android:id="@+id/launcher_add"
|
||||
android:title="@string/contextmenu_add_to_launcher" />
|
||||
|
||||
<item android:id="@+id/subscribe"
|
||||
<item android:id="@+id/save_subscribe"
|
||||
android:title="@string/contextmenu_subscribe" />
|
||||
|
||||
</menu>
|
||||
|
@ -38,7 +38,7 @@
|
||||
<item android:id="@+id/launcher_add"
|
||||
android:title="@string/contextmenu_add_to_launcher" />
|
||||
|
||||
<item android:id="@+id/subscribe"
|
||||
<item android:id="@+id/save_subscribe"
|
||||
android:title="@string/contextmenu_subscribe" />
|
||||
|
||||
</menu>
|
||||
|
@ -38,7 +38,7 @@
|
||||
<item android:id="@+id/launcher_add"
|
||||
android:title="@string/contextmenu_add_to_launcher" />
|
||||
|
||||
<item android:id="@+id/subscribe"
|
||||
<item android:id="@+id/save_subscribe"
|
||||
android:title="@string/contextmenu_subscribe" />
|
||||
|
||||
</menu>
|
||||
|
@ -35,7 +35,7 @@
|
||||
<item android:id="@+id/launcher_add"
|
||||
android:title="@string/contextmenu_add_to_launcher" />
|
||||
|
||||
<item android:id="@+id/subscribe"
|
||||
<item android:id="@+id/save_subscribe"
|
||||
android:title="@string/contextmenu_subscribe" />
|
||||
|
||||
</menu>
|
||||
|
@ -0,0 +1,23 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
This Source Code Form is subject to the terms of the Mozilla Public
|
||||
- License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
- file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
-->
|
||||
|
||||
<resources xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<!-- FxAccountStatusActivity ActionBar -->
|
||||
<style name="ActionBar.FxAccountStatusActivity">
|
||||
<item name="android:displayOptions">showHome|homeAsUp|showTitle</item>
|
||||
</style>
|
||||
|
||||
<style name="FxAccountTheme" parent="@style/Gecko" />
|
||||
|
||||
<style name="FxAccountTheme.FxAccountStatusActivity" parent="@FxAccountTheme">
|
||||
<item name="android:windowActionBar">true</item>
|
||||
<item name="android:windowNoTitle">false</item>
|
||||
<item name="android:actionBarStyle">@style/ActionBar.FxAccountStatusActivity</item>
|
||||
</style>
|
||||
|
||||
</resources>
|
@ -12,6 +12,9 @@
|
||||
<color name="fxaccount_linkified_textColorLinkSubdued">#c0c9d0</color>
|
||||
|
||||
<color name="fxaccount_error">#d63920</color>
|
||||
|
||||
<color name="fxaccount_error_preference_backgroundcolor">#fad4d2</color>
|
||||
|
||||
<color name="fxaccount_button_textColor">#ffffff</color>
|
||||
<color name="fxaccount_button_background_active">#e66000</color>
|
||||
<color name="fxaccount_button_background_hit">#fd9500</color>
|
||||
|
@ -7,4 +7,11 @@
|
||||
<dimen name="fxaccount_stroke_width">1dp</dimen>
|
||||
<dimen name="fxaccount_corner_radius">5dp</dimen>
|
||||
<dimen name="fxaccount_input_padding">10dp</dimen>
|
||||
|
||||
<!-- Preference fragment padding, bottom -->
|
||||
<dimen name="preference_fragment_padding_bottom">0dp</dimen>
|
||||
<!-- Preference fragment padding, sides -->
|
||||
<dimen name="preference_fragment_padding_side">16dp</dimen>
|
||||
|
||||
<integer name="preference_fragment_scrollbarStyle">0x02000000</integer> <!-- outsideOverlay -->
|
||||
</resources>
|
||||
|
@ -9,6 +9,10 @@
|
||||
|
||||
<style name="FxAccountTheme" parent="@style/Gecko" />
|
||||
|
||||
<style name="FxAccountTheme.FxAccountStatusActivity" parent="@style/FxAccountTheme">
|
||||
<item name="android:windowNoTitle">false</item>
|
||||
</style>
|
||||
|
||||
<style name="FxAccountMiddle">
|
||||
<item name="android:background">@android:color/white</item>
|
||||
<item name="android:orientation">vertical</item>
|
||||
|
@ -0,0 +1,84 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:key="status_screen" >
|
||||
|
||||
<PreferenceCategory
|
||||
android:key="signed_in_as_category"
|
||||
android:title="@string/fxaccount_status_signed_in_as" >
|
||||
<Preference
|
||||
android:editable="false"
|
||||
android:key="email"
|
||||
android:persistent="false"
|
||||
android:title="@string/fxaccount_email_hint" />
|
||||
</PreferenceCategory>
|
||||
<PreferenceCategory
|
||||
android:key="sync_category"
|
||||
android:title="@string/fxaccount_status_sync" >
|
||||
<Preference
|
||||
android:editable="false"
|
||||
android:icon="@drawable/fxaccount_sync_error"
|
||||
android:key="needs_credentials"
|
||||
android:layout="@layout/fxaccount_status_error_preference"
|
||||
android:persistent="false"
|
||||
android:title="@string/fxaccount_status_needs_credentials" />
|
||||
<Preference
|
||||
android:editable="false"
|
||||
android:icon="@drawable/fxaccount_sync_error"
|
||||
android:key="needs_upgrade"
|
||||
android:layout="@layout/fxaccount_status_error_preference"
|
||||
android:persistent="false"
|
||||
android:title="@string/fxaccount_status_needs_upgrade" />
|
||||
<Preference
|
||||
android:editable="false"
|
||||
android:icon="@drawable/fxaccount_sync_error"
|
||||
android:key="needs_verification"
|
||||
android:layout="@layout/fxaccount_status_error_preference"
|
||||
android:persistent="false"
|
||||
android:title="@string/fxaccount_status_needs_verification" />
|
||||
|
||||
<CheckBoxPreference
|
||||
android:key="bookmarks"
|
||||
android:persistent="false"
|
||||
android:title="@string/fxaccount_status_bookmarks" />
|
||||
<CheckBoxPreference
|
||||
android:key="history"
|
||||
android:persistent="false"
|
||||
android:title="@string/fxaccount_status_history" />
|
||||
<CheckBoxPreference
|
||||
android:key="tabs"
|
||||
android:persistent="false"
|
||||
android:title="@string/fxaccount_status_tabs" />
|
||||
<CheckBoxPreference
|
||||
android:key="passwords"
|
||||
android:persistent="false"
|
||||
android:title="@string/fxaccount_status_passwords" />
|
||||
</PreferenceCategory>
|
||||
<PreferenceCategory
|
||||
android:key="legal_category"
|
||||
android:title="@string/fxaccount_status_legal" >
|
||||
<Preference android:title="@string/fxaccount_policy_linktos" >
|
||||
<intent
|
||||
android:action="android.intent.action.VIEW"
|
||||
android:data="@string/fxaccount_link_tos"
|
||||
android:targetClass="@string/browser_intent_class"
|
||||
android:targetPackage="@string/browser_intent_package" />
|
||||
</Preference>
|
||||
<Preference android:title="@string/fxaccount_policy_linkprivacy" >
|
||||
<intent
|
||||
android:action="android.intent.action.VIEW"
|
||||
android:data="@string/fxaccount_link_pn"
|
||||
android:targetClass="@string/browser_intent_class"
|
||||
android:targetPackage="@string/browser_intent_package" />
|
||||
</Preference>
|
||||
</PreferenceCategory>
|
||||
<PreferenceCategory
|
||||
android:key="debug_category" >
|
||||
<Preference android:key="debug_refresh" />
|
||||
<Preference android:key="debug_dump" />
|
||||
<Preference android:key="debug_force_sync" />
|
||||
<Preference android:key="debug_forget_certificate" />
|
||||
<Preference android:key="debug_require_password" />
|
||||
<Preference android:key="debug_require_upgrade" />
|
||||
</PreferenceCategory>
|
||||
|
||||
</PreferenceScreen>
|
@ -99,7 +99,7 @@ skip-if = processor == "x86"
|
||||
#[testBrowserProviderPerf]
|
||||
|
||||
# Using UITest
|
||||
#[testAboutHomePageNavigation] # see bug 947550 and bug 977952
|
||||
#[testAboutHomePageNavigation] # see bug 947550, bug 979038 and bug 977952
|
||||
[testAboutHomeVisibility]
|
||||
[testInputConnection]
|
||||
[testSessionHistory]
|
||||
|
@ -1,7 +1,6 @@
|
||||
<activity
|
||||
android:theme="@style/FxAccountTheme"
|
||||
android:icon="@drawable/fxaccount_icon"
|
||||
android:label="Status"
|
||||
android:theme="@style/FxAccountTheme.FxAccountStatusActivity"
|
||||
android:label="@string/fxaccount_status_activity_label"
|
||||
android:clearTaskOnLaunch="true"
|
||||
android:taskAffinity="@ANDROID_PACKAGE_NAME@.FXA"
|
||||
android:name="org.mozilla.gecko.fxa.activities.FxAccountStatusActivity"
|
||||
@ -16,8 +15,7 @@
|
||||
|
||||
<activity
|
||||
android:theme="@style/FxAccountTheme"
|
||||
android:icon="@drawable/fxaccount_icon"
|
||||
android:label="Setup"
|
||||
android:label="@string/sync_app_name"
|
||||
android:clearTaskOnLaunch="true"
|
||||
android:taskAffinity="@ANDROID_PACKAGE_NAME@.FXA"
|
||||
android:name="org.mozilla.gecko.fxa.activities.FxAccountGetStartedActivity"
|
||||
@ -32,42 +30,36 @@
|
||||
|
||||
<activity
|
||||
android:theme="@style/FxAccountTheme"
|
||||
android:icon="@drawable/fxaccount_icon"
|
||||
android:name="org.mozilla.gecko.fxa.activities.FxAccountCreateAccountActivity"
|
||||
android:windowSoftInputMode="adjustResize">
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
android:theme="@style/FxAccountTheme"
|
||||
android:icon="@drawable/fxaccount_icon"
|
||||
android:name="org.mozilla.gecko.fxa.activities.FxAccountConfirmAccountActivity"
|
||||
android:windowSoftInputMode="adjustResize">
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
android:theme="@style/FxAccountTheme"
|
||||
android:icon="@drawable/fxaccount_icon"
|
||||
android:name="org.mozilla.gecko.fxa.activities.FxAccountSignInActivity"
|
||||
android:windowSoftInputMode="adjustResize">
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
android:theme="@style/FxAccountTheme"
|
||||
android:icon="@drawable/fxaccount_icon"
|
||||
android:name="org.mozilla.gecko.fxa.activities.FxAccountVerifiedAccountActivity"
|
||||
android:windowSoftInputMode="adjustResize">
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
android:theme="@style/FxAccountTheme"
|
||||
android:icon="@drawable/fxaccount_icon"
|
||||
android:name="org.mozilla.gecko.fxa.activities.FxAccountUpdateCredentialsActivity"
|
||||
android:windowSoftInputMode="adjustResize">
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
android:theme="@style/FxAccountTheme"
|
||||
android:icon="@drawable/fxaccount_icon"
|
||||
android:name="org.mozilla.gecko.fxa.activities.FxAccountCreateAccountNotAllowedActivity"
|
||||
android:windowSoftInputMode="adjustResize">
|
||||
</activity>
|
||||
|
@ -98,6 +98,9 @@
|
||||
<string name="sync_text_tab_sent">&sync.text.tab.sent.label;</string>
|
||||
<string name="sync_text_tab_not_sent">&sync.text.tab.not.sent.label;</string>
|
||||
|
||||
<string name="browser_intent_package">@ANDROID_PACKAGE_NAME@</string>
|
||||
<string name="browser_intent_class">@ANDROID_PACKAGE_NAME@.App</string>
|
||||
|
||||
<!-- Firefox account strings. -->
|
||||
|
||||
<!-- Firefox account links. -->
|
||||
@ -162,7 +165,9 @@
|
||||
<string name="fxaccount_update_credentials_button_label">&fxaccount_update_credentials_button_label;</string>
|
||||
<string name="fxaccount_update_credentials_unknown_error">&fxaccount_update_credentials_unknown_error;</string>
|
||||
|
||||
<string name="fxaccount_status_activity_label">&syncBrand.shortName.label;</string>
|
||||
<string name="fxaccount_status_header">&fxaccount_status_header;</string>
|
||||
<string name="fxaccount_status_signed_in_as">&fxaccount_status_signed_in_as;</string>
|
||||
<string name="fxaccount_status_sync">&fxaccount_status_sync;</string>
|
||||
<string name="fxaccount_status_sync_enabled">&fxaccount_status_sync_enabled;</string>
|
||||
<string name="fxaccount_status_needs_verification">&fxaccount_status_needs_verification2;</string>
|
||||
|
@ -7,7 +7,7 @@
|
||||
android:versionName="1.0" >
|
||||
|
||||
<uses-sdk android:minSdkVersion="8"
|
||||
android:targetSdkVersion="16" />
|
||||
android:targetSdkVersion="14" />
|
||||
|
||||
<uses-permission android:name="@ANDROID_BACKGROUND_TARGET_PACKAGE_NAME@.permissions.BROWSER_PROVIDER"/>
|
||||
<uses-permission android:name="@ANDROID_BACKGROUND_TARGET_PACKAGE_NAME@.permissions.FORMHISTORY_PROVIDER"/>
|
||||
|
@ -99,7 +99,7 @@ ScrollFrameTo(nsIScrollableFrame* aFrame, const CSSPoint& aPoint, bool& aSuccess
|
||||
aSuccessOut = false;
|
||||
|
||||
if (!aFrame) {
|
||||
return CSSPoint();
|
||||
return aPoint;
|
||||
}
|
||||
|
||||
// If the scrollable frame is currently in the middle of an async or smooth
|
||||
|
Loading…
Reference in New Issue
Block a user