mirror of
https://github.com/mozilla/gecko-dev.git
synced 2025-01-10 05:47:04 +00:00
Bug 876473 - Provide Java-generated Firefox Health Report to about:healthreport. r=rnewman
This commit is contained in:
parent
5eb58c3d2a
commit
23648dc3f2
@ -13,6 +13,7 @@ import org.mozilla.gecko.gfx.GeckoLayerClient;
|
||||
import org.mozilla.gecko.gfx.ImmutableViewportMetrics;
|
||||
import org.mozilla.gecko.gfx.LayerView;
|
||||
import org.mozilla.gecko.gfx.PanZoomController;
|
||||
import org.mozilla.gecko.health.BrowserHealthReporter;
|
||||
import org.mozilla.gecko.menu.GeckoMenu;
|
||||
import org.mozilla.gecko.util.FloatUtils;
|
||||
import org.mozilla.gecko.util.GamepadUtils;
|
||||
@ -146,6 +147,8 @@ abstract public class BrowserApp extends GeckoApp
|
||||
|
||||
private OrderedBroadcastHelper mOrderedBroadcastHelper;
|
||||
|
||||
private BrowserHealthReporter mBrowserHealthReporter;
|
||||
|
||||
@Override
|
||||
public void onTabChanged(Tab tab, Tabs.TabEvents msg, Object data) {
|
||||
switch(msg) {
|
||||
@ -426,6 +429,7 @@ abstract public class BrowserApp extends GeckoApp
|
||||
JavaAddonManager.getInstance().init(getApplicationContext());
|
||||
mSharedPreferencesHelper = new SharedPreferencesHelper(getApplicationContext());
|
||||
mOrderedBroadcastHelper = new OrderedBroadcastHelper(getApplicationContext());
|
||||
mBrowserHealthReporter = new BrowserHealthReporter();
|
||||
|
||||
if (AppConstants.MOZ_ANDROID_BEAM && Build.VERSION.SDK_INT >= 14) {
|
||||
NfcAdapter nfc = NfcAdapter.getDefaultAdapter(this);
|
||||
@ -680,6 +684,11 @@ abstract public class BrowserApp extends GeckoApp
|
||||
mOrderedBroadcastHelper = null;
|
||||
}
|
||||
|
||||
if (mBrowserHealthReporter != null) {
|
||||
mBrowserHealthReporter.uninit();
|
||||
mBrowserHealthReporter = null;
|
||||
}
|
||||
|
||||
unregisterEventListener("CharEncoding:Data");
|
||||
unregisterEventListener("CharEncoding:State");
|
||||
unregisterEventListener("Feedback:LastUrl");
|
||||
|
@ -113,6 +113,7 @@ FENNEC_JAVA_FILES = \
|
||||
GeckoViewsFactory.java \
|
||||
GeckoView.java \
|
||||
health/BrowserHealthRecorder.java \
|
||||
health/BrowserHealthReporter.java \
|
||||
InputMethods.java \
|
||||
JavaAddonManager.java \
|
||||
LightweightTheme.java \
|
||||
|
136
mobile/android/base/health/BrowserHealthReporter.java
Normal file
136
mobile/android/base/health/BrowserHealthReporter.java
Normal file
@ -0,0 +1,136 @@
|
||||
/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
|
||||
* 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.health;
|
||||
|
||||
import android.content.ContentProviderClient;
|
||||
import android.content.Context;
|
||||
import android.util.Log;
|
||||
|
||||
import org.mozilla.gecko.GeckoAppShell;
|
||||
import org.mozilla.gecko.GeckoEvent;
|
||||
import org.mozilla.gecko.GeckoProfile;
|
||||
|
||||
import org.mozilla.gecko.background.healthreport.EnvironmentBuilder;
|
||||
import org.mozilla.gecko.background.healthreport.HealthReportDatabaseStorage;
|
||||
import org.mozilla.gecko.background.healthreport.HealthReportGenerator;
|
||||
|
||||
import org.mozilla.gecko.util.GeckoEventListener;
|
||||
import org.mozilla.gecko.util.ThreadUtils;
|
||||
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
/**
|
||||
* BrowserHealthReporter is the browser's interface to the Firefox Health
|
||||
* Report report generator.
|
||||
*
|
||||
* Each instance registers Gecko event listeners, so keep a single instance
|
||||
* around for the life of the browser. Java callers should use this globally
|
||||
* available singleton.
|
||||
*/
|
||||
public class BrowserHealthReporter implements GeckoEventListener {
|
||||
private static final String LOGTAG = "GeckoHealthRep";
|
||||
|
||||
public static final long MILLISECONDS_PER_DAY = 24 * 60 * 60 * 1000;
|
||||
public static final long MILLISECONDS_PER_SIX_MONTHS = 180 * MILLISECONDS_PER_DAY;
|
||||
|
||||
public static final String EVENT_REQUEST = "HealthReport:Request";
|
||||
public static final String EVENT_RESPONSE = "HealthReport:Response";
|
||||
|
||||
public BrowserHealthReporter() {
|
||||
GeckoAppShell.registerEventListener(EVENT_REQUEST, this);
|
||||
|
||||
final Context context = GeckoAppShell.getContext();
|
||||
if (context == null) {
|
||||
throw new IllegalStateException("Null Gecko context");
|
||||
}
|
||||
}
|
||||
|
||||
public void uninit() {
|
||||
GeckoAppShell.unregisterEventListener(EVENT_REQUEST, this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a new Health Report.
|
||||
*
|
||||
* This method performs IO, so call it from a background thread.
|
||||
*
|
||||
* @param since timestamp of first day to report (milliseconds since epoch).
|
||||
* @param lastPingTime timestamp when last health report was uploaded
|
||||
* (milliseconds since epoch).
|
||||
* @param profilePath path of the profile to generate report for.
|
||||
*/
|
||||
public JSONObject generateReport(long since, long lastPingTime, String profilePath) throws JSONException {
|
||||
final Context context = GeckoAppShell.getContext();
|
||||
if (context == null) {
|
||||
Log.e(LOGTAG, "Null Gecko context; returning null report.", new RuntimeException());
|
||||
return null;
|
||||
}
|
||||
|
||||
// We abuse the life-cycle of an Android ContentProvider slightly by holding
|
||||
// onto a ContentProviderClient while we generate a payload. This keeps
|
||||
// our database storage alive, while also allowing us to share a database
|
||||
// connection with BrowserHealthRecorder and the uploader.
|
||||
// The ContentProvider owns all underlying Storage instances, so we don't
|
||||
// need to explicitly close them.
|
||||
ContentProviderClient client = EnvironmentBuilder.getContentProviderClient(context);
|
||||
if (client == null) {
|
||||
throw new IllegalStateException("Could not fetch Health Report content provider.");
|
||||
}
|
||||
|
||||
try {
|
||||
// Storage instance is owned by HealthReportProvider, so we don't need
|
||||
// to close it.
|
||||
HealthReportDatabaseStorage storage = EnvironmentBuilder.getStorage(client, profilePath);
|
||||
if (storage == null) {
|
||||
Log.e(LOGTAG, "No storage in health reporter; returning null report.", new RuntimeException());
|
||||
return null;
|
||||
}
|
||||
|
||||
HealthReportGenerator generator = new HealthReportGenerator(storage);
|
||||
return generator.generateDocument(since, lastPingTime, profilePath);
|
||||
} finally {
|
||||
client.release();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a new Health Report for the current Gecko profile.
|
||||
*
|
||||
* This method performs IO, so call it from a background thread.
|
||||
*/
|
||||
public JSONObject generateReport() throws JSONException {
|
||||
GeckoProfile profile = GeckoAppShell.getGeckoInterface().getProfile();
|
||||
String profilePath = profile.getDir().getAbsolutePath();
|
||||
|
||||
long since = System.currentTimeMillis() - MILLISECONDS_PER_SIX_MONTHS;
|
||||
// TODO: read this from per-profile SharedPreference owned by background uploader.
|
||||
long lastPingTime = since;
|
||||
return generateReport(since, lastPingTime, profilePath);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleMessage(String event, JSONObject message) {
|
||||
try {
|
||||
ThreadUtils.postToBackgroundThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
JSONObject report = new JSONObject();
|
||||
try {
|
||||
report = generateReport();
|
||||
} catch (Exception e) {
|
||||
Log.e(LOGTAG, "Generating report failed; responding with null.", e);
|
||||
}
|
||||
|
||||
GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent(EVENT_RESPONSE, report.toString()));
|
||||
}
|
||||
});
|
||||
} catch (Exception e) {
|
||||
Log.e(LOGTAG, "Exception handling message \"" + event + "\":", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -8,8 +8,6 @@
|
||||
|
||||
const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
|
||||
|
||||
Cu.import("resource://gre/modules/commonjs/sdk/core/promise.js");
|
||||
Cu.import("resource://gre/modules/OrderedBroadcast.jsm");
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
Cu.import("resource://gre/modules/SharedPreferences.jsm");
|
||||
|
||||
@ -17,86 +15,44 @@ Cu.import("resource://gre/modules/SharedPreferences.jsm");
|
||||
// health reports.
|
||||
const PREF_UPLOAD_ENABLED = "android.not_a_preference.healthreport.uploadEnabled";
|
||||
|
||||
// Action sent via Android Ordered Broadcast to background service.
|
||||
const BROADCAST_ACTION_HEALTH_REPORT = "@ANDROID_PACKAGE_NAME@" + ".healthreport.request";
|
||||
|
||||
// Name of Gecko Pref specifying report content location.
|
||||
const PREF_REPORTURL = "datareporting.healthreport.about.reportUrl";
|
||||
|
||||
const EVENT_HEALTH_REQUEST = "HealthReport:Request";
|
||||
const EVENT_HEALTH_RESPONSE = "HealthReport:Response";
|
||||
|
||||
function sendMessageToJava(message) {
|
||||
return Cc["@mozilla.org/android/bridge;1"]
|
||||
.getService(Ci.nsIAndroidBridge)
|
||||
.handleGeckoMessage(JSON.stringify(message));
|
||||
}
|
||||
|
||||
// Default preferences for the application.
|
||||
// about:healthreport prefs are stored in Firefox's default Android
|
||||
// SharedPreferences.
|
||||
let sharedPrefs = new SharedPreferences();
|
||||
|
||||
let reporter = {
|
||||
onInit: function () {
|
||||
let deferred = Promise.defer();
|
||||
deferred.resolve();
|
||||
|
||||
return deferred.promise;
|
||||
},
|
||||
|
||||
collectAndObtainJSONPayload: function () {
|
||||
let deferred = Promise.defer();
|
||||
|
||||
let callback = function (data, token, action) {
|
||||
if (data) {
|
||||
// Bug 870992: the FHR report content expects FHR report data
|
||||
// in string form. This costs us a JSON parsing round trip,
|
||||
// since the ordered broadcast module parses the stringified
|
||||
// JSON returned from Java. Since the FHR report content
|
||||
// expects updates to preferences as a Javascript object, we
|
||||
// cannot handle the situation uniformly, and we pay the price
|
||||
// here, stringifying a huge chunk of JSON.
|
||||
deferred.resolve(JSON.stringify(data));
|
||||
} else {
|
||||
deferred.reject();
|
||||
}
|
||||
};
|
||||
|
||||
sendOrderedBroadcast(BROADCAST_ACTION_HEALTH_REPORT, null, callback);
|
||||
|
||||
return deferred.promise;
|
||||
},
|
||||
};
|
||||
|
||||
let policy = {
|
||||
get healthReportUploadEnabled() {
|
||||
return sharedPrefs.getBoolPref(PREF_UPLOAD_ENABLED);
|
||||
},
|
||||
|
||||
recordHealthReportUploadEnabled: function (enabled) {
|
||||
sharedPrefs.setBoolPref(PREF_UPLOAD_ENABLED, !!enabled);
|
||||
},
|
||||
};
|
||||
|
||||
let healthReportWrapper = {
|
||||
init: function () {
|
||||
reporter.onInit().then(healthReportWrapper.refreshPayload,
|
||||
healthReportWrapper.handleInitFailure);
|
||||
|
||||
let iframe = document.getElementById("remote-report");
|
||||
iframe.addEventListener("load", healthReportWrapper.initRemotePage, false);
|
||||
let report = this._getReportURI();
|
||||
iframe.src = report.spec;
|
||||
|
||||
sharedPrefs.addObserver(PREF_UPLOAD_ENABLED, this, false);
|
||||
Services.obs.addObserver(this, EVENT_HEALTH_RESPONSE, false);
|
||||
},
|
||||
|
||||
observe: function (subject, topic, data) {
|
||||
if (topic != PREF_UPLOAD_ENABLED) {
|
||||
return;
|
||||
if (topic == PREF_UPLOAD_ENABLED) {
|
||||
this.updatePrefState();
|
||||
} else if (topic == EVENT_HEALTH_RESPONSE) {
|
||||
this.updatePayload(data);
|
||||
}
|
||||
|
||||
subject.updatePrefState();
|
||||
},
|
||||
|
||||
uninit: function () {
|
||||
sharedPrefs.removeObserver(PREF_UPLOAD_ENABLED, this);
|
||||
Services.obs.removeObserver(this, EVENT_HEALTH_RESPONSE);
|
||||
},
|
||||
|
||||
_getReportURI: function () {
|
||||
@ -105,21 +61,22 @@ let healthReportWrapper = {
|
||||
},
|
||||
|
||||
onOptIn: function () {
|
||||
policy.recordHealthReportUploadEnabled(true,
|
||||
"Health report page sent opt-in command.");
|
||||
console.log("AboutHealthReport: page sent opt-in command.");
|
||||
sharedPrefs.setBoolPref(PREF_UPLOAD_ENABLED, true);
|
||||
this.updatePrefState();
|
||||
},
|
||||
|
||||
onOptOut: function () {
|
||||
policy.recordHealthReportUploadEnabled(false,
|
||||
"Health report page sent opt-out command.");
|
||||
console.log("AboutHealthReport: page sent opt-out command.");
|
||||
sharedPrefs.setBoolPref(PREF_UPLOAD_ENABLED, false);
|
||||
this.updatePrefState();
|
||||
},
|
||||
|
||||
updatePrefState: function () {
|
||||
console.log("AboutHealthReport: page requested pref state.");
|
||||
try {
|
||||
let prefs = {
|
||||
enabled: policy.healthReportUploadEnabled,
|
||||
enabled: sharedPrefs.getBoolPref(PREF_UPLOAD_ENABLED),
|
||||
};
|
||||
this.injectData("prefs", prefs);
|
||||
} catch (e) {
|
||||
@ -128,21 +85,27 @@ let healthReportWrapper = {
|
||||
},
|
||||
|
||||
refreshPayload: function () {
|
||||
reporter.collectAndObtainJSONPayload().then(healthReportWrapper.updatePayload,
|
||||
healthReportWrapper.handlePayloadFailure);
|
||||
console.log("AboutHealthReport: page requested fresh payload.");
|
||||
sendMessageToJava({
|
||||
type: EVENT_HEALTH_REQUEST,
|
||||
});
|
||||
},
|
||||
|
||||
updatePayload: function (data) {
|
||||
healthReportWrapper.injectData("payload", data);
|
||||
// Data is supposed to be a string, so the length should be
|
||||
// defined. Just in case, we do this after injecting the data.
|
||||
console.log("AboutHealthReport: sending payload to page " +
|
||||
"(" + typeof(data) + " of length " + data.length + ").");
|
||||
},
|
||||
|
||||
injectData: function (type, content) {
|
||||
let report = this._getReportURI();
|
||||
|
||||
// file URIs can't be used for targetOrigin, so we use "*" for this special case
|
||||
// in all other cases, pass in the URL to the report so we properly restrict the message dispatch
|
||||
|
||||
let reportUrl = report.scheme == "file" ? "*" : report.spec;
|
||||
// file: URIs can't be used for targetOrigin, so we use "*" for
|
||||
// this special case. In all other cases, pass in the URL to the
|
||||
// report so we properly restrict the message dispatch.
|
||||
let reportUrl = (report.scheme == "file") ? "*" : report.spec;
|
||||
|
||||
let data = {
|
||||
type: type,
|
||||
|
Loading…
Reference in New Issue
Block a user