Bug 875400 - Part 2: provide add-ons and pref changes to FHR. r=mfinkle

* * *
Bug 875400 - Part 2a: review comments.
* * *
Bug 875400 - Part 2b: add flag to ignore add-ons.
This commit is contained in:
Richard Newman 2013-05-30 17:42:56 -07:00
Родитель 203e8b3ef2
Коммит 8a4af7f535
3 изменённых файлов: 294 добавлений и 69 удалений

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

@ -1860,7 +1860,7 @@ abstract public class GeckoApp
Tabs.unregisterOnTabsChangedListener(this);
if (mHealthRecorder != null) {
mHealthRecorder.close(GeckoAppShell.getEventDispatcher());
mHealthRecorder.close();
mHealthRecorder = null;
}
}

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

@ -12,6 +12,8 @@ import android.content.ContentProviderClient;
import android.util.Log;
import org.mozilla.gecko.AppConstants;
import org.mozilla.gecko.GeckoAppShell;
import org.mozilla.gecko.GeckoEvent;
import org.mozilla.gecko.PrefsHelper;
import org.mozilla.gecko.PrefsHelper.PrefHandler;
@ -54,6 +56,9 @@ import java.util.Scanner;
public class BrowserHealthRecorder implements GeckoEventListener {
private static final String LOG_TAG = "GeckoHealthRec";
private static final String PREF_BLOCKLIST_ENABLED = "extensions.blocklist.enabled";
private static final String EVENT_ADDONS_ALL = "Addons:All";
private static final String EVENT_ADDONS_CHANGE = "Addons:Change";
private static final String EVENT_PREF_CHANGE = "Pref:Change";
public enum State {
NOT_INITIALIZED,
@ -69,6 +74,7 @@ public class BrowserHealthRecorder implements GeckoEventListener {
private volatile HealthReportDatabaseStorage storage;
private final ProfileInformationCache profileCache;
private ContentProviderClient client;
private final EventDispatcher dispatcher;
/**
* Persist the opaque identifier for the current Firefox Health Report environment.
@ -83,6 +89,7 @@ public class BrowserHealthRecorder implements GeckoEventListener {
*/
public BrowserHealthRecorder(final Context context, final String profilePath, final EventDispatcher dispatcher) {
Log.d(LOG_TAG, "Initializing. Dispatcher is " + dispatcher);
this.dispatcher = dispatcher;
this.client = EnvironmentBuilder.getContentProviderClient(context);
if (this.client == null) {
throw new IllegalStateException("Could not fetch Health Report content provider.");
@ -95,7 +102,7 @@ public class BrowserHealthRecorder implements GeckoEventListener {
this.profileCache = new ProfileInformationCache(profilePath);
try {
this.initialize(context, profilePath, dispatcher);
this.initialize(context, profilePath);
} catch (Exception e) {
Log.e(LOG_TAG, "Exception initializing.", e);
}
@ -107,7 +114,7 @@ public class BrowserHealthRecorder implements GeckoEventListener {
* Shut down database connections, unregister event listeners, and perform
* provider-specific uninitialization.
*/
public synchronized void close(final EventDispatcher dispatcher) {
public synchronized void close() {
switch (this.state) {
case CLOSED:
Log.w(LOG_TAG, "Ignoring attempt to double-close closed BrowserHealthRecorder.");
@ -120,7 +127,7 @@ public class BrowserHealthRecorder implements GeckoEventListener {
}
this.state = State.CLOSED;
this.unregisterEventListeners(dispatcher);
this.unregisterEventListeners();
// Add any necessary provider uninitialization here.
this.storage = null;
@ -130,7 +137,10 @@ public class BrowserHealthRecorder implements GeckoEventListener {
}
}
private void unregisterEventListeners(EventDispatcher dispatcher) {
private void unregisterEventListeners() {
this.dispatcher.unregisterEventListener(EVENT_ADDONS_ALL, this);
this.dispatcher.unregisterEventListener(EVENT_ADDONS_CHANGE, this);
this.dispatcher.unregisterEventListener(EVENT_PREF_CHANGE, this);
}
public void onBlocklistPrefChanged(boolean to) {
@ -143,6 +153,15 @@ public class BrowserHealthRecorder implements GeckoEventListener {
this.profileCache.setTelemetryEnabled(to);
}
public void onAddonChanged(String id, JSONObject json) {
this.profileCache.beginInitialization();
try {
this.profileCache.updateJSONForAddon(id, json);
} catch (IllegalStateException e) {
Log.w(LOG_TAG, "Attempted to update add-on cache prior to full init.", e);
}
}
/**
* Call this when a material change has occurred in the running environment,
* such that a new environment should be computed and prepared for use in
@ -162,6 +181,7 @@ public class BrowserHealthRecorder implements GeckoEventListener {
this.state = State.INITIALIZATION_FAILED;
return;
}
ensureEnvironment();
}
protected synchronized int ensureEnvironment() {
@ -247,7 +267,7 @@ public class BrowserHealthRecorder implements GeckoEventListener {
// Let's look in the profile.
long time = getProfileInitTimeFromFile(profilePath);
if (time > 0) {
Log.i(LOG_TAG, "Incorporating environment: times.json profile creation = " + time);
Log.d(LOG_TAG, "Incorporating environment: times.json profile creation = " + time);
return time;
}
@ -270,23 +290,95 @@ public class BrowserHealthRecorder implements GeckoEventListener {
}
}
Log.i(LOG_TAG, "Incorporating environment: profile creation = " + time);
Log.d(LOG_TAG, "Incorporating environment: profile creation = " + time);
return time;
}
private void handlePrefValue(final String pref, final boolean value) {
Log.d(LOG_TAG, "Incorporating environment: " + pref + " = " + value);
if (AppConstants.TELEMETRY_PREF_NAME.equals(pref)) {
profileCache.setTelemetryEnabled(value);
return;
}
if (PREF_BLOCKLIST_ENABLED.equals(pref)) {
profileCache.setBlocklistEnabled(value);
return;
}
Log.w(LOG_TAG, "Unexpected pref: " + pref);
}
/**
* Background init helper.
*/
private void initializeStorage() {
Log.d(LOG_TAG, "Done initializing profile cache. Beginning storage init.");
final BrowserHealthRecorder self = this;
ThreadUtils.postToBackgroundThread(new Runnable() {
@Override
public void run() {
synchronized (self) {
if (state != State.INITIALIZING) {
Log.w(LOG_TAG, "Unexpected state during init: " + state);
return;
}
// Belt and braces.
if (storage == null) {
Log.w(LOG_TAG, "Storage is null during init; shutting down?");
return;
}
try {
storage.beginInitialization();
} catch (Exception e) {
Log.e(LOG_TAG, "Failed to init storage.", e);
state = State.INITIALIZATION_FAILED;
return;
}
try {
// Listen for add-ons and prefs changes.
dispatcher.registerEventListener(EVENT_ADDONS_CHANGE, self);
dispatcher.registerEventListener(EVENT_PREF_CHANGE, self);
// Initialize each provider here.
Log.d(LOG_TAG, "Ensuring environment.");
ensureEnvironment();
Log.d(LOG_TAG, "Finishing init.");
storage.finishInitialization();
state = State.INITIALIZED;
} catch (Exception e) {
state = State.INITIALIZATION_FAILED;
storage.abortInitialization();
Log.e(LOG_TAG, "Initialization failed.", e);
}
}
}
});
}
/**
* Add provider-specific initialization in this method.
*/
private synchronized void initialize(final Context context,
final String profilePath,
final EventDispatcher dispatcher)
final String profilePath)
throws java.io.IOException {
Log.d(LOG_TAG, "Initializing profile cache.");
this.state = State.INITIALIZING;
this.profileCache.beginInitialization();
// If we can restore state from last time, great.
if (this.profileCache.restoreUnlessInitialized()) {
Log.i(LOG_TAG, "Successfully restored state. Initializing storage.");
initializeStorage();
return;
}
// Otherwise, let's initialize it from scratch.
this.profileCache.beginInitialization();
this.profileCache.setProfileCreationTime(getAndPersistProfileInitTime(context, profilePath));
final BrowserHealthRecorder self = this;
@ -294,69 +386,15 @@ public class BrowserHealthRecorder implements GeckoEventListener {
PrefHandler handler = new PrefsHelper.PrefHandlerBase() {
@Override
public void prefValue(String pref, boolean value) {
Log.i(LOG_TAG, "Incorporating environment: " + pref + " = " + value);
if (AppConstants.TELEMETRY_PREF_NAME.equals(pref)) {
profileCache.setTelemetryEnabled(value);
return;
}
if (PREF_BLOCKLIST_ENABLED.equals(pref)) {
profileCache.setBlocklistEnabled(value);
return;
}
Log.w(LOG_TAG, "Unexpected pref: " + pref);
handlePrefValue(pref, value);
}
@Override
public void finish() {
try {
profileCache.completeInitialization();
} catch (java.io.IOException e) {
Log.e(LOG_TAG, "Error completing profile cache initialization.", e);
return;
}
Log.d(LOG_TAG, "Done initializing profile cache. Beginning storage init.");
ThreadUtils.postToBackgroundThread(new Runnable() {
@Override
public void run() {
synchronized (self) {
if (state != State.INITIALIZING) {
Log.w(LOG_TAG, "Unexpected state during init: " + state);
return;
}
// Belt and braces.
if (storage == null) {
Log.w(LOG_TAG, "Storage is null during init; shutting down?");
return;
}
try {
storage.beginInitialization();
} catch (Exception e) {
Log.e(LOG_TAG, "Failed to init storage.", e);
state = State.INITIALIZATION_FAILED;
return;
}
try {
// Initialize each provider here.
Log.d(LOG_TAG, "Ensuring environment.");
ensureEnvironment();
Log.d(LOG_TAG, "Finishing init.");
storage.finishInitialization();
state = State.INITIALIZED;
} catch (Exception e) {
state = State.INITIALIZATION_FAILED;
storage.abortInitialization();
Log.e(LOG_TAG, "Initialization failed.", e);
}
}
}
});
Log.d(LOG_TAG, "Requesting all add-ons from Gecko.");
dispatcher.registerEventListener(EVENT_ADDONS_ALL, self);
GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("Addons:FetchAll", null));
// Wait for the broadcast event which completes our initialization.
}
};
@ -372,6 +410,40 @@ public class BrowserHealthRecorder implements GeckoEventListener {
@Override
public void handleMessage(String event, JSONObject message) {
try {
if (EVENT_ADDONS_ALL.equals(event)) {
Log.d(LOG_TAG, "Got all add-ons.");
try {
JSONObject addons = message.getJSONObject("json");
Log.d(LOG_TAG, "Persisting " + addons.length() + " add-ons.");
profileCache.setJSONForAddons(addons);
profileCache.completeInitialization();
} catch (java.io.IOException e) {
Log.e(LOG_TAG, "Error completing profile cache initialization.", e);
state = State.INITIALIZATION_FAILED;
return;
}
if (state == State.INITIALIZING) {
initializeStorage();
} else {
this.onEnvironmentChanged();
}
return;
}
if (EVENT_ADDONS_CHANGE.equals(event)) {
Log.d(LOG_TAG, "Add-on changed: " + message.getString("id"));
this.onAddonChanged(message.getString("id"), message.getJSONObject("json"));
this.onEnvironmentChanged();
return;
}
if (EVENT_PREF_CHANGE.equals(event)) {
final String pref = message.getString("pref");
Log.d(LOG_TAG, "Pref changed: " + pref);
handlePrefValue(pref, message.getBoolean("value"));
this.onEnvironmentChanged();
return;
}
} catch (Exception e) {
Log.e(LOG_TAG, "Exception handling message \"" + event + "\":", e);
}

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

@ -330,6 +330,7 @@ var BrowserApp = {
Downloads.init();
FormAssistant.init();
IndexedDB.init();
HealthReportStatusListener.init();
XPInstallObserver.init();
ClipboardHelper.init();
CharacterEncoding.init();
@ -616,6 +617,7 @@ var BrowserApp = {
IndexedDB.uninit();
ViewportHandler.uninit();
XPInstallObserver.uninit();
HealthReportStatusListener.uninit();
CharacterEncoding.uninit();
SearchEngines.uninit();
WebappsUI.uninit();
@ -5095,6 +5097,157 @@ var FormAssistant = {
}
};
/**
* An object to watch for Gecko status changes -- add-on installs, pref changes
* -- and reflect them back to Java.
*/
let HealthReportStatusListener = {
TELEMETRY_PREF:
#ifdef MOZ_TELEMETRY_REPORTING
// Telemetry pref differs based on build.
#ifdef MOZ_TELEMETRY_ON_BY_DEFAULT
"toolkit.telemetry.enabledPreRelease",
#else
"toolkit.telemetry.enabled",
#endif
#else
null,
#endif
init: function () {
try {
AddonManager.addAddonListener(this);
} catch (ex) {
console.log("Failed to initialize add-on status listener. FHR cannot report add-on state. " + ex);
}
Services.obs.addObserver(this, "Addons:FetchAll", false);
Services.prefs.addObserver("extensions.blocklist.enabled", this, false);
if (this.TELEMETRY_PREF) {
Services.prefs.addObserver(this.TELEMETRY_PREF, this, false);
}
},
uninit: function () {
Services.obs.removeObserver(this, "Addons:FetchAll");
Services.prefs.removeObserver("extensions.blocklist.enabled", this);
if (this.TELEMETRY_PREF) {
Services.prefs.removeObserver(this.TELEMETRY_PREF, this);
}
AddonManager.removeAddonListener(this);
},
observe: function (aSubject, aTopic, aData) {
switch (aTopic) {
case "Addons:FetchAll":
HealthReportStatusListener.sendAllAddonsToJava();
break;
case "nsPref:changed":
sendMessageToJava({ type: "Pref:Change", pref: aData, value: Services.prefs.getBoolPref(aData) });
break
}
},
MILLISECONDS_PER_DAY: 24 * 60 * 60 * 1000,
COPY_FIELDS: [
"userDisabled",
"appDisabled",
"version",
"type",
"scope",
"foreignInstall",
"hasBinaryComponents",
],
// Add-on types for which full details are recorded in FHR.
// All other types are ignored.
FULL_DETAIL_TYPES: [
"plugin",
"extension",
"service",
],
/**
* Return true if either the add-on has opted out of AMO updates, and thus
* we shouldn't provide details to FHR, or it's an add-on type that we
* don't want to report details for.
* These add-ons will still make it over to Java, but will be filtered out.
*/
_shouldIgnore: function (aAddon) {
// TODO: check this pref. If it's false, the add-on has opted out of
// AMO updates, and should not be reported.
let optOutPref = "extensions." + aAddon.id + ".getAddons.cache.enabled";
if (this.FULL_DETAIL_TYPES.indexOf(aAddon.type) == -1) {
return true;
}
return false;
},
_dateToDays: function (aDate) {
return Math.floor(aDate.getTime() / this.MILLISECONDS_PER_DAY);
},
jsonForAddon: function (aAddon) {
let o = {};
if (aAddon.installDate) {
o.installDay = this._dateToDays(aAddon.installDate);
}
if (aAddon.updateDate) {
o.updateDay = this._dateToDays(aAddon.updateDate);
}
for (let field of this.COPY_FIELDS) {
o[field] = aAddon[field];
}
return o;
},
notifyJava: function (aAddon, aNeedsRestart) {
let json = this.jsonForAddon(aAddon);
if (this._shouldIgnore(aAddon)) {
json.ignore = true;
}
sendMessageToJava({ type: "Addons:Change", id: aAddon.id, json: json });
},
// Add-on listeners.
onEnabling: function (aAddon, aNeedsRestart) {
this.notifyJava(aAddon, aNeedsRestart);
},
onDisabling: function (aAddon, aNeedsRestart) {
this.notifyJava(aAddon, aNeedsRestart);
},
onInstalling: function (aAddon, aNeedsRestart) {
this.notifyJava(aAddon, aNeedsRestart);
},
onUninstalling: function (aAddon, aNeedsRestart) {
this.notifyJava(aAddon, aNeedsRestart);
},
onPropertyChanged: function (aAddon, aProperties) {
this.notifyJava(aAddon);
},
sendAllAddonsToJava: function () {
AddonManager.getAllAddons(function (aAddons) {
let json = {};
if (aAddons) {
for (let i = 0; i < aAddons.length; ++i) {
let addon = aAddons[i];
let addonJSON = HealthReportStatusListener.jsonForAddon(addon);
if (HealthReportStatusListener._shouldIgnore(addon)) {
addonJSON.ignore = true;
}
json[addon.id] = addonJSON;
}
}
sendMessageToJava({ type: "Addons:All", json: json });
});
},
};
var XPInstallObserver = {
init: function xpi_init() {
Services.obs.addObserver(XPInstallObserver, "addon-install-blocked", false);