Bug 1205835 - Create telemetry upload service and upload in onStart. r=rnewman
--- a/mobile/android/base/AndroidManifest.xml.in
+++ b/mobile/android/base/AndroidManifest.xml.in
@@ -456,16 +456,19 @@
android:name="org.mozilla.gecko.NotificationService">
</service>
<service
android:exported="false"
android:name="org.mozilla.gecko.dlc.DownloadContentService">
</service>
+ <service
+ android:name="org.mozilla.gecko.telemetry.TelemetryUploadService"
+ android:exported="false"/>
#include ../services/manifests/FxAccountAndroidManifest_services.xml.in
#include ../services/manifests/SyncAndroidManifest_services.xml.in
<service
android:name="org.mozilla.gecko.tabqueue.TabReceivedService"
android:exported="false" />
--- a/mobile/android/base/java/org/mozilla/gecko/BrowserApp.java
+++ b/mobile/android/base/java/org/mozilla/gecko/BrowserApp.java
@@ -55,16 +55,18 @@ import org.mozilla.gecko.restrictions.Re
import org.mozilla.gecko.sync.repositories.android.FennecTabsRepository;
import org.mozilla.gecko.tabqueue.TabQueueHelper;
import org.mozilla.gecko.tabqueue.TabQueuePrompt;
import org.mozilla.gecko.tabs.TabHistoryController;
import org.mozilla.gecko.tabs.TabHistoryController.OnShowTabHistory;
import org.mozilla.gecko.tabs.TabHistoryFragment;
import org.mozilla.gecko.tabs.TabHistoryPage;
import org.mozilla.gecko.tabs.TabsPanel;
+import org.mozilla.gecko.telemetry.TelemetryConstants;
+import org.mozilla.gecko.telemetry.TelemetryUploadService;
import org.mozilla.gecko.toolbar.AutocompleteHandler;
import org.mozilla.gecko.toolbar.BrowserToolbar;
import org.mozilla.gecko.toolbar.BrowserToolbar.TabEditingState;
import org.mozilla.gecko.toolbar.ToolbarProgressView;
import org.mozilla.gecko.trackingprotection.TrackingProtectionPrompt;
import org.mozilla.gecko.util.ActivityUtils;
import org.mozilla.gecko.util.Clipboard;
import org.mozilla.gecko.util.EventCallback;
@@ -963,16 +965,18 @@ public class BrowserApp extends GeckoApp
} else {
// If we're restarting, we won't destroy the activity.
// Make sure we remove any guest notifications that might
// have been shown.
GuestSession.hideNotification(BrowserApp.this);
}
}
});
+
+ uploadTelemetry();
}
@Override
public void onStop() {
super.onStop();
// We only show the guest mode notification when our activity is in the foreground.
GuestSession.hideNotification(this);
@@ -3857,16 +3861,27 @@ public class BrowserApp extends GeckoApp
mActionBarFlipper.showPrevious();
// Only slide the urlbar out if it was hidden when the action mode started
// Don't animate hiding it so that there's no flash as we switch back to url mode
mDynamicToolbar.setTemporarilyVisible(false, VisibilityTransition.IMMEDIATE);
}
+ private void uploadTelemetry() {
+ if (!TelemetryConstants.UPLOAD_ENABLED || getProfile().inGuestMode()) {
+ return;
+ }
+
+ final Intent i = new Intent(TelemetryConstants.ACTION_UPLOAD_CORE);
+ i.setClass(this, TelemetryUploadService.class);
+ startService(i);
+ Log.d("GeckoTelemetry", "Upload service started");
+ }
+
public static interface Refreshable {
public void refresh();
}
@Override
protected StartupAction getStartupAction(final String passedURL) {
final boolean inGuestMode = GeckoProfile.get(this).inGuestMode();
if (inGuestMode) {
--- a/mobile/android/base/java/org/mozilla/gecko/telemetry/TelemetryConstants.java
+++ b/mobile/android/base/java/org/mozilla/gecko/telemetry/TelemetryConstants.java
@@ -1,16 +1,26 @@
/* 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.telemetry;
+import org.mozilla.gecko.AppConstants;
+
public class TelemetryConstants {
+ public static final boolean UPLOAD_ENABLED = AppConstants.MOZILLA_OFFICIAL; // Disabled for developer builds.
+ public static final String DEFAULT_SERVER_URL = "https://incoming.telemetry.mozilla.org";
+
+ public static final String ACTION_UPLOAD_CORE = "uploadCore";
+
+ public static final String PREF_SERVER_URL = "telemetry-serverUrl";
+ public static final String PREF_PING_COUNT = "telemetry-pingCount";
+
public static class CorePing {
private CorePing() { /* To prevent instantiation */ }
public static final String NAME = "core";
public static final int VERSION_VALUE = 1;
public static final String OS_VALUE = "Android";
public static final String ARCHITECTURE = "arch";
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/java/org/mozilla/gecko/telemetry/TelemetryUploadService.java
@@ -0,0 +1,171 @@
+/* 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.telemetry;
+
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.util.Log;
+import ch.boye.httpclientandroidlib.HttpResponse;
+import ch.boye.httpclientandroidlib.client.ClientProtocolException;
+import org.mozilla.gecko.GeckoProfile;
+import org.mozilla.gecko.GeckoSharedPrefs;
+import org.mozilla.gecko.background.BackgroundService;
+import org.mozilla.gecko.sync.net.BaseResource;
+import org.mozilla.gecko.sync.net.BaseResourceDelegate;
+import org.mozilla.gecko.sync.net.Resource;
+
+import java.io.IOException;
+import java.net.URISyntaxException;
+import java.security.GeneralSecurityException;
+
+/**
+ * The service that handles uploading telemetry payloads to the server.
+ */
+public class TelemetryUploadService extends BackgroundService {
+ private static final String LOGTAG = "Gecko" + TelemetryUploadService.class.getSimpleName();
+ private static final String WORKER_THREAD_NAME = LOGTAG + "Worker";
+
+ public TelemetryUploadService() {
+ super(WORKER_THREAD_NAME);
+ // We may upload to the server more than once but we have no metrics
+ // that can double count at the moment so it doesn't matter.
+ setIntentRedelivery(true);
+ }
+
+ @Override
+ public void onHandleIntent(final Intent intent) {
+ Log.d(LOGTAG, "Service started");
+
+ if (!TelemetryConstants.UPLOAD_ENABLED) {
+ Log.d(LOGTAG, "Health report upload feature is compile-time disabled; not handling upload intent.");
+ return;
+ }
+
+ if (!isReadyToUpload(intent)) {
+ return;
+ }
+
+ if (!TelemetryConstants.ACTION_UPLOAD_CORE.equals(intent.getAction())) {
+ Log.w(LOGTAG, "Unknown action: " + intent.getAction() + ". Returning");
+ return;
+ }
+
+ uploadCorePing();
+ }
+
+ private boolean isReadyToUpload(final Intent intent) {
+ // Intent can be null. Bug 1025937.
+ if (intent == null) {
+ Log.d(LOGTAG, "Received null intent. Returning.");
+ return false;
+ }
+
+ // Don't do anything if the device can't talk to the server.
+ if (!backgroundDataIsEnabled()) {
+ Log.d(LOGTAG, "Background data is not enabled; skipping.");
+ return false;
+ }
+
+ return true;
+ }
+
+ private void uploadCorePing() {
+ final String clientId;
+ try {
+ clientId = GeckoProfile.get(this).getClientId();
+ } catch (final IOException e) {
+ Log.w(LOGTAG, "Unable to get client ID to generate core ping: returning.", e);
+ return;
+ }
+
+ // Each profile can have different telemetry data so we intentionally grab the shared prefs for the profile.
+ final SharedPreferences sharedPrefs = GeckoSharedPrefs.forProfile(this);
+ // TODO (bug 1241685): Sync this preference with the gecko preference.
+ final String serverUrlSchemeHostPort =
+ sharedPrefs.getString(TelemetryConstants.PREF_SERVER_URL, TelemetryConstants.DEFAULT_SERVER_URL);
+ final int pingCount = sharedPrefs.getInt(TelemetryConstants.PREF_PING_COUNT, 1);
+
+ final TelemetryPing corePing = TelemetryPingGenerator.createCorePing(clientId, serverUrlSchemeHostPort, pingCount);
+ final CorePingResultDelegate resultDelegate = new CorePingResultDelegate(sharedPrefs, pingCount);
+ uploadPing(corePing, resultDelegate);
+ }
+
+ private void uploadPing(final TelemetryPing ping, final ResultDelegate delegate) {
+ final BaseResource resource;
+ try {
+ resource = new BaseResource(ping.getUrl());
+ } catch (final URISyntaxException e) {
+ Log.w(LOGTAG, "URISyntaxException for server url when creating BaseResource: returning.");
+ return;
+ }
+
+ delegate.setResource(resource);
+ resource.delegate = delegate;
+ resource.post(ping.getPayload());
+ Log.d(LOGTAG, "Ping upload initiated.");
+ }
+
+ private static class CorePingResultDelegate extends ResultDelegate {
+ final SharedPreferences sharedPrefs;
+ final int pingCount;
+
+ public CorePingResultDelegate(final SharedPreferences sharedPrefs, final int pingCount) {
+ super();
+ this.sharedPrefs = sharedPrefs;
+ this.pingCount = pingCount;
+ }
+
+ @Override
+ public String getUserAgent() {
+ return null;
+ }
+
+ @Override
+ public void handleHttpResponse(final HttpResponse response) {
+ final int status = response.getStatusLine().getStatusCode();
+ switch (status) {
+ case 200:
+ case 201:
+ Log.d(LOGTAG, "Telemetry upload success.");
+ sharedPrefs.edit().putInt(TelemetryConstants.PREF_PING_COUNT, pingCount + 1).apply();
+ break;
+ default:
+ Log.w(LOGTAG, "Telemetry upload failure. HTTP status: " + status);
+ }
+ }
+
+ @Override
+ public void handleHttpProtocolException(final ClientProtocolException e) {
+ // We don't log the exception to prevent leaking user data.
+ Log.w(LOGTAG, "HttpProtocolException when trying to upload telemetry");
+ }
+
+ @Override
+ public void handleHttpIOException(final IOException e) {
+ // We don't log the exception to prevent leaking user data.
+ Log.w(LOGTAG, "HttpIOException when trying to upload telemetry");
+ }
+
+ @Override
+ public void handleTransportException(final GeneralSecurityException e) {
+ // We don't log the exception to prevent leaking user data.
+ Log.w(LOGTAG, "HttpTransportException when trying to upload telemetry");
+ }
+ }
+
+ /**
+ * A hack because I want to set the resource after the Delegate is constructed.
+ * Be sure to call {@link #setResource(Resource)}!
+ */
+ private static abstract class ResultDelegate extends BaseResourceDelegate {
+ public ResultDelegate() {
+ super(null);
+ }
+
+ protected void setResource(final Resource resource) {
+ this.resource = resource;
+ }
+ }
+}
--- a/mobile/android/base/moz.build
+++ b/mobile/android/base/moz.build
@@ -537,16 +537,17 @@ gbjar.sources += ['java/org/mozilla/geck
'tabs/TabsLayoutItemView.java',
'tabs/TabsListLayout.java',
'tabs/TabsPanel.java',
'tabs/TabsPanelThumbnailView.java',
'Telemetry.java',
'telemetry/TelemetryConstants.java',
'telemetry/TelemetryPing.java',
'telemetry/TelemetryPingGenerator.java',
+ 'telemetry/TelemetryUploadService.java',
'TelemetryContract.java',
'TextSelection.java',
'TextSelectionHandle.java',
'ThumbnailHelper.java',
'toolbar/AutocompleteHandler.java',
'toolbar/BackButton.java',
'toolbar/BrowserToolbar.java',
'toolbar/BrowserToolbarPhone.java',