Bug 1119061 - Part 2: Add Sync 1.1 -> Sync 1.5 migration telemetry. r=rnewman
========
https://github.com/mozilla-services/android-sync/commit/f7eaef78c18b75f4e03b1f2d897de8de72474399
Author: Nick Alexander <nalexander@mozilla.com>
Date: Wed Jan 14 17:36:29 2015 -0800
Bug 1119061 - Part 2: Add Sync 1.1 to Sync 1.5 migration telemetry.
========
https://github.com/mozilla-services/android-sync/commit/e64f9687038a9198a0c67d80d328f08be235311c
Author: Nick Alexander <nalexander@mozilla.com>
Date: Mon Jan 12 16:30:09 2015 -0800
Bug 1119061 - Part 1: Add TelemetryWrapper.
This is cribbed, more or less directly, from the stumbler.
========
https://github.com/mozilla-services/android-sync/commit/51299e74e4e7b9f3228ff163ed2566cca9efcd3a
Author: Nick Alexander <nalexander@mozilla.com>
Date: Mon Jan 12 17:26:41 2015 -0800
Bug 1119061 - Pre: Clear Firefox Account notifications when Android Account is removed.
--- a/mobile/android/base/android-services.mozbuild
+++ b/mobile/android/base/android-services.mozbuild
@@ -775,16 +775,17 @@ sync_java_files = [
'background/common/log/writers/AndroidLogWriter.java',
'background/common/log/writers/LevelFilteringLogWriter.java',
'background/common/log/writers/LogWriter.java',
'background/common/log/writers/PrintLogWriter.java',
'background/common/log/writers/SimpleTagLogWriter.java',
'background/common/log/writers/StringLogWriter.java',
'background/common/log/writers/TagLogWriter.java',
'background/common/log/writers/ThreadLocalTagLogWriter.java',
+ 'background/common/telemetry/TelemetryWrapper.java',
'background/datareporting/TelemetryRecorder.java',
'background/db/CursorDumper.java',
'background/db/Tab.java',
'background/fxa/FxAccount10AuthDelegate.java',
'background/fxa/FxAccount10CreateDelegate.java',
'background/fxa/FxAccount20CreateDelegate.java',
'background/fxa/FxAccount20LoginDelegate.java',
'background/fxa/FxAccountAgeLockoutHelper.java',
@@ -1127,16 +1128,17 @@ sync_java_files = [
'sync/synchronizer/SessionNotBegunException.java',
'sync/synchronizer/Synchronizer.java',
'sync/synchronizer/SynchronizerDelegate.java',
'sync/synchronizer/SynchronizerSession.java',
'sync/synchronizer/SynchronizerSessionDelegate.java',
'sync/synchronizer/UnbundleError.java',
'sync/synchronizer/UnexpectedSessionException.java',
'sync/SynchronizerConfiguration.java',
+ 'sync/telemetry/TelemetryContract.java',
'sync/ThreadPool.java',
'sync/UnexpectedJSONException.java',
'sync/UnknownSynchronizerConfigurationVersionException.java',
'sync/Utils.java',
'tokenserver/TokenServerClient.java',
'tokenserver/TokenServerClientDelegate.java',
'tokenserver/TokenServerException.java',
'tokenserver/TokenServerToken.java',
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/background/common/telemetry/TelemetryWrapper.java
@@ -0,0 +1,56 @@
+/* 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.background.common.telemetry;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+
+import org.mozilla.gecko.background.common.log.Logger;
+
+/**
+ * Android Background Services are normally built into Fennec, but can also be
+ * built as a stand-alone APK for rapid local development. The current Telemetry
+ * implementation is coupled to Gecko, and Background Services should not
+ * interact with Gecko directly. To maintain this independence, Background
+ * Services lazily introspects the relevant Telemetry class from the enclosing
+ * package, warning but otherwise ignoring failures during introspection or
+ * invocation.
+ * <p>
+ * It is possible that Background Services will introspect and invoke the
+ * Telemetry implementation while Gecko is not running. In this case, the Fennec
+ * process itself buffers Telemetry events until such time as they can be
+ * flushed to disk and uploaded. <b>There is no guarantee that all Telemetry
+ * events will be uploaded!</b> Depending on the volume of data and the
+ * application lifecycle, Telemetry events may be dropped.
+ */
+public class TelemetryWrapper {
+ private static final String LOG_TAG = TelemetryWrapper.class.getSimpleName();
+
+ // Marking this volatile maintains thread safety cheaply.
+ private static volatile Method mAddToHistogram;
+
+ public static void addToHistogram(String key, int value) {
+ if (mAddToHistogram == null) {
+ try {
+ final Class<?> telemetry = Class.forName("org.mozilla.gecko.Telemetry");
+ mAddToHistogram = telemetry.getMethod("addToHistogram", String.class, int.class);
+ } catch (ClassNotFoundException e) {
+ Logger.warn(LOG_TAG, "org.mozilla.gecko.Telemetry class found!");
+ return;
+ } catch (NoSuchMethodException e) {
+ Logger.warn(LOG_TAG, "org.mozilla.gecko.Telemetry.addToHistogram(String, int) method not found!");
+ return;
+ }
+ }
+
+ if (mAddToHistogram != null) {
+ try {
+ mAddToHistogram.invoke(null, key, value);
+ } catch (IllegalArgumentException | InvocationTargetException | IllegalAccessException e) {
+ Logger.warn(LOG_TAG, "Got exception invoking telemetry!");
+ }
+ }
+ }
+}
--- a/mobile/android/base/fxa/activities/FxAccountAbstractUpdateCredentialsActivity.java
+++ b/mobile/android/base/fxa/activities/FxAccountAbstractUpdateCredentialsActivity.java
@@ -4,29 +4,31 @@
package org.mozilla.gecko.fxa.activities;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import org.mozilla.gecko.R;
import org.mozilla.gecko.background.common.log.Logger;
+import org.mozilla.gecko.background.common.telemetry.TelemetryWrapper;
import org.mozilla.gecko.background.fxa.FxAccountClient;
import org.mozilla.gecko.background.fxa.FxAccountClient10.RequestDelegate;
import org.mozilla.gecko.background.fxa.FxAccountClient20;
import org.mozilla.gecko.background.fxa.FxAccountClient20.LoginResponse;
import org.mozilla.gecko.background.fxa.FxAccountClientException.FxAccountClientRemoteException;
import org.mozilla.gecko.background.fxa.FxAccountUtils;
import org.mozilla.gecko.background.fxa.PasswordStretcher;
import org.mozilla.gecko.fxa.FirefoxAccounts;
import org.mozilla.gecko.fxa.authenticator.AndroidFxAccount;
import org.mozilla.gecko.fxa.login.Engaged;
import org.mozilla.gecko.fxa.login.State;
import org.mozilla.gecko.fxa.tasks.FxAccountSignInTask;
import org.mozilla.gecko.sync.setup.activities.ActivityUtils;
+import org.mozilla.gecko.sync.telemetry.TelemetryContract;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.AutoCompleteTextView;
import android.widget.Button;
import android.widget.EditText;
@@ -145,16 +147,18 @@ public abstract class FxAccountAbstractU
setResult(RESULT_OK);
// Maybe show success activity.
final Intent successIntent = makeSuccessIntent(email, result);
if (successIntent != null) {
startActivity(successIntent);
}
finish();
+
+ TelemetryWrapper.addToHistogram(TelemetryContract.SYNC11_MIGRATIONS_COMPLETED, 1);
}
}
public void updateCredentials(String email, String password) {
String serverURI = fxAccount.getAccountServerURI();
Executor executor = Executors.newSingleThreadExecutor();
FxAccountClient client = new FxAccountClient20(serverURI, executor);
PasswordStretcher passwordStretcher = makePasswordStretcher(password);
--- a/mobile/android/base/fxa/receivers/FxAccountDeletedService.java
+++ b/mobile/android/base/fxa/receivers/FxAccountDeletedService.java
@@ -1,16 +1,18 @@
/* 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.receivers;
import org.mozilla.gecko.background.common.log.Logger;
import org.mozilla.gecko.fxa.FxAccountConstants;
+import org.mozilla.gecko.fxa.sync.FxAccountNotificationManager;
+import org.mozilla.gecko.fxa.sync.FxAccountSyncAdapter;
import org.mozilla.gecko.sync.config.AccountPickler;
import org.mozilla.gecko.sync.repositories.android.FennecTabsRepository;
import android.app.IntentService;
import android.content.Context;
import android.content.Intent;
/**
@@ -58,16 +60,19 @@ public class FxAccountDeletedService ext
Logger.info(LOG_TAG, "Firefox account named " + accountName + " being removed; " +
"deleting saved pickle file '" + FxAccountConstants.ACCOUNT_PICKLE_FILENAME + "'.");
deletePickle(context);
// Delete client database and non-local tabs.
Logger.info(LOG_TAG, "Deleting the entire clients database and non-local tabs");
FennecTabsRepository.deleteNonLocalClientsAndTabs(context);
+
+ // Remove any displayed notifications.
+ new FxAccountNotificationManager(FxAccountSyncAdapter.NOTIFICATION_ID).clear(context);
}
public static void deletePickle(final Context context) {
try {
AccountPickler.deletePickle(context, FxAccountConstants.ACCOUNT_PICKLE_FILENAME);
} catch (Exception e) {
// This should never happen, but we really don't want to die in a background thread.
Logger.warn(LOG_TAG, "Got exception deleting saved pickle file; ignoring.", e);
--- a/mobile/android/base/fxa/sync/FxAccountNotificationManager.java
+++ b/mobile/android/base/fxa/sync/FxAccountNotificationManager.java
@@ -2,22 +2,24 @@
* 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.BrowserLocaleManager;
import org.mozilla.gecko.R;
import org.mozilla.gecko.background.common.log.Logger;
+import org.mozilla.gecko.background.common.telemetry.TelemetryWrapper;
import org.mozilla.gecko.background.fxa.FxAccountUtils;
import org.mozilla.gecko.fxa.activities.FxAccountFinishMigratingActivity;
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 org.mozilla.gecko.sync.telemetry.TelemetryContract;
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;
@@ -39,16 +41,27 @@ public class FxAccountNotificationManage
// We're lazy about updating our locale info, because most syncs don't notify.
private volatile boolean localeUpdated;
public FxAccountNotificationManager(int notificationId) {
this.notificationId = notificationId;
}
/**
+ * Remove all Firefox Account related notifications from the notification manager.
+ *
+ * @param context
+ * Android context.
+ */
+ public void clear(Context context) {
+ final NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
+ notificationManager.cancel(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.
*/
@@ -67,16 +80,18 @@ public class FxAccountNotificationManage
localeUpdated = true;
BrowserLocaleManager.getInstance().getAndApplyPersistedLocale(context);
}
final String title;
final String text;
final Intent notificationIntent;
if (action == Action.NeedsFinishMigrating) {
+ TelemetryWrapper.addToHistogram(TelemetryContract.SYNC11_MIGRATION_NOTIFICATIONS_OFFERED, 1);
+
title = context.getResources().getString(R.string.fxaccount_sync_finish_migrating_notification_title);
text = context.getResources().getString(R.string.fxaccount_sync_finish_migrating_notification_text, state.email);
notificationIntent = new Intent(context, FxAccountFinishMigratingActivity.class);
} else {
title = context.getResources().getString(R.string.fxaccount_sync_sign_in_error_notification_title);
text = context.getResources().getString(R.string.fxaccount_sync_sign_in_error_notification_text, state.email);
notificationIntent = new Intent(context, FxAccountStatusActivity.class);
}
--- a/mobile/android/base/fxa/sync/FxAccountSyncAdapter.java
+++ b/mobile/android/base/fxa/sync/FxAccountSyncAdapter.java
@@ -62,17 +62,17 @@ import android.os.Bundle;
import android.os.SystemClock;
public class FxAccountSyncAdapter extends AbstractThreadedSyncAdapter {
private static final String LOG_TAG = FxAccountSyncAdapter.class.getSimpleName();
public static final String SYNC_EXTRAS_RESPECT_LOCAL_RATE_LIMIT = "respect_local_rate_limit";
public static final String SYNC_EXTRAS_RESPECT_REMOTE_SERVER_BACKOFF = "respect_remote_server_backoff";
- protected static final int NOTIFICATION_ID = LOG_TAG.hashCode();
+ public static final int NOTIFICATION_ID = LOG_TAG.hashCode();
// Tracks the last seen storage hostname for backoff purposes.
private static final String PREF_BACKOFF_STORAGE_HOST = "backoffStorageHost";
// Used to do cheap in-memory rate limiting. Don't sync again if we
// successfully synced within this duration.
private static final int MINIMUM_SYNC_DELAY_MILLIS = 15 * 1000; // 15 seconds.
private volatile long lastSyncRealtimeMillis;
--- a/mobile/android/base/sync/MigrationSentinelSyncStage.java
+++ b/mobile/android/base/sync/MigrationSentinelSyncStage.java
@@ -2,27 +2,29 @@
* 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.sync;
import java.net.URISyntaxException;
import org.mozilla.gecko.background.common.log.Logger;
+import org.mozilla.gecko.background.common.telemetry.TelemetryWrapper;
import org.mozilla.gecko.background.fxa.FxAccountUtils;
import org.mozilla.gecko.fxa.FxAccountConstants;
import org.mozilla.gecko.fxa.authenticator.AndroidFxAccount;
import org.mozilla.gecko.fxa.login.MigratedFromSync11;
import org.mozilla.gecko.fxa.login.State;
import org.mozilla.gecko.sync.net.AuthHeaderProvider;
import org.mozilla.gecko.sync.net.SyncStorageRecordRequest;
import org.mozilla.gecko.sync.net.SyncStorageRequestDelegate;
import org.mozilla.gecko.sync.net.SyncStorageResponse;
import org.mozilla.gecko.sync.stage.AbstractNonRepositorySyncStage;
import org.mozilla.gecko.sync.stage.NoSuchStageException;
+import org.mozilla.gecko.sync.telemetry.TelemetryContract;
/**
* The purpose of this class is to talk to a Sync 1.1 server and check
* for a Firefox Accounts migration sentinel.
*
* If one is found, a Firefox Account is created, and the existing
* Firefox Sync account disabled (or deleted).
*/
@@ -147,27 +149,29 @@ public class MigrationSentinelSyncStage
onMigrated();
} else {
onError(null, "Could not add Android account.");
}
}
private void onMigrated() {
Logger.info(LOG_TAG, "Account migrated!");
+ TelemetryWrapper.addToHistogram(TelemetryContract.SYNC11_MIGRATIONS_SUCCEEDED, 1);
session.config.persistLastMigrationSentinelCheckTimestamp(fetchTimestamp);
session.abort(null, "Account migrated.");
}
private void onCompletedUneventfully() {
session.config.persistLastMigrationSentinelCheckTimestamp(fetchTimestamp);
session.advance();
}
private void onError(Exception ex, String reason) {
Logger.info(LOG_TAG, "Could not migrate: " + reason, ex);
+ TelemetryWrapper.addToHistogram(TelemetryContract.SYNC11_MIGRATIONS_FAILED, 1);
session.abort(ex, reason);
}
public void check() {
final String url = session.config.storageURL() + META_FXA_CREDENTIALS;
try {
final SyncStorageRecordRequest request = new SyncStorageRecordRequest(url);
request.delegate = new SyncStorageRequestDelegate() {
@@ -176,16 +180,19 @@ public class MigrationSentinelSyncStage
public String ifUnmodifiedSince() {
return null;
}
@Override
public void handleRequestSuccess(SyncStorageResponse response) {
Logger.info(LOG_TAG, "Found " + META_FXA_CREDENTIALS + " record; attempting migration.");
setTimestamp(response.normalizedWeaveTimestamp());
+
+ TelemetryWrapper.addToHistogram(TelemetryContract.SYNC11_MIGRATION_SENTINELS_SEEN, 1);
+
try {
final ExtendedJSONObject body = response.jsonObjectBody();
final CryptoRecord cryptoRecord = CryptoRecord.fromJSONRecord(body);
migrate(cryptoRecord);
} catch (Exception e) {
onError(e, "Unable to parse credential response.");
}
}
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/sync/telemetry/TelemetryContract.java
@@ -0,0 +1,48 @@
+/* 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.sync.telemetry;
+
+public class TelemetryContract {
+ /**
+ * We are a Sync 1.1 (legacy) client, and we downloaded a migration sentinel.
+ */
+ public static final String SYNC11_MIGRATION_SENTINELS_SEEN = "FENNEC_SYNC11_MIGRATION_SENTINELS_SEEN";
+
+ /**
+ * We are a Sync 1.1 (legacy) client and we have downloaded a migration
+ * sentinel, but there was an error creating a Firefox Account from that
+ * sentinel.
+ * <p>
+ * We have logged the error and are ignoring that sentinel.
+ */
+ public static final String SYNC11_MIGRATIONS_FAILED = "FENNEC_SYNC11_MIGRATIONS_FAILED";
+
+ /**
+ * We are a Sync 1.1 (legacy) client and we have downloaded a migration
+ * sentinel, and there was no reported error creating a Firefox Account from
+ * that sentinel.
+ * <p>
+ * We have created a Firefox Account corresponding to the sentinel and have
+ * queued the existing Old Sync account for removal.
+ */
+ public static final String SYNC11_MIGRATIONS_SUCCEEDED = "FENNEC_SYNC11_MIGRATIONS_SUCCEEDED";
+
+ /**
+ * We are (now) a Sync 1.5 (Firefox Accounts-based) client that migrated from
+ * Sync 1.1. We have presented the user the "complete upgrade" notification.
+ * <p>
+ * We will offer every time a sync is triggered, including when a notification
+ * is already pending.
+ */
+ public static final String SYNC11_MIGRATION_NOTIFICATIONS_OFFERED = "FENNEC_SYNC11_MIGRATION_NOTIFICATIONS_OFFERED";
+
+ /**
+ * We are (now) a Sync 1.5 (Firefox Accounts-based) client that migrated from
+ * Sync 1.1. We have presented the user the "complete upgrade" notification
+ * and they have successfully completed the upgrade process by entering their
+ * Firefox Account credentials.
+ */
+ public static final String SYNC11_MIGRATIONS_COMPLETED = "FENNEC_SYNC11_MIGRATIONS_COMPLETED";
+}