diff --git a/mobile/android/base/android-services.mozbuild b/mobile/android/base/android-services.mozbuild index dbb1454ec598..f6eb74a4a189 100644 --- a/mobile/android/base/android-services.mozbuild +++ b/mobile/android/base/android-services.mozbuild @@ -780,6 +780,7 @@ sync_java_files = [ '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', @@ -1132,6 +1133,7 @@ sync_java_files = [ 'sync/synchronizer/UnbundleError.java', 'sync/synchronizer/UnexpectedSessionException.java', 'sync/SynchronizerConfiguration.java', + 'sync/telemetry/TelemetryContract.java', 'sync/ThreadPool.java', 'sync/UnexpectedJSONException.java', 'sync/UnknownSynchronizerConfigurationVersionException.java', diff --git a/mobile/android/base/background/common/telemetry/TelemetryWrapper.java b/mobile/android/base/background/common/telemetry/TelemetryWrapper.java new file mode 100644 index 000000000000..6639b817d51b --- /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. + *
+ * 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. There is no guarantee that all Telemetry + * events will be uploaded! 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!"); + } + } + } +} diff --git a/mobile/android/base/fxa/activities/FxAccountAbstractUpdateCredentialsActivity.java b/mobile/android/base/fxa/activities/FxAccountAbstractUpdateCredentialsActivity.java index a97675b46c16..c0a8e6d67813 100644 --- a/mobile/android/base/fxa/activities/FxAccountAbstractUpdateCredentialsActivity.java +++ b/mobile/android/base/fxa/activities/FxAccountAbstractUpdateCredentialsActivity.java @@ -9,6 +9,7 @@ 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; @@ -22,6 +23,7 @@ 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; @@ -150,6 +152,8 @@ public abstract class FxAccountAbstractUpdateCredentialsActivity extends FxAccou startActivity(successIntent); } finish(); + + TelemetryWrapper.addToHistogram(TelemetryContract.SYNC11_MIGRATIONS_COMPLETED, 1); } } diff --git a/mobile/android/base/fxa/receivers/FxAccountDeletedService.java b/mobile/android/base/fxa/receivers/FxAccountDeletedService.java index c9ef08a94aca..14768cfb1e71 100644 --- a/mobile/android/base/fxa/receivers/FxAccountDeletedService.java +++ b/mobile/android/base/fxa/receivers/FxAccountDeletedService.java @@ -6,6 +6,8 @@ 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; @@ -63,6 +65,9 @@ public class FxAccountDeletedService extends IntentService { // 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) { diff --git a/mobile/android/base/fxa/sync/FxAccountNotificationManager.java b/mobile/android/base/fxa/sync/FxAccountNotificationManager.java index 8044ba44a28d..f467df8f9ed5 100644 --- a/mobile/android/base/fxa/sync/FxAccountNotificationManager.java +++ b/mobile/android/base/fxa/sync/FxAccountNotificationManager.java @@ -7,12 +7,14 @@ 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; @@ -43,6 +45,17 @@ public class FxAccountNotificationManager { 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. @@ -72,6 +85,8 @@ public class FxAccountNotificationManager { 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); diff --git a/mobile/android/base/fxa/sync/FxAccountSyncAdapter.java b/mobile/android/base/fxa/sync/FxAccountSyncAdapter.java index 43ac65c07914..e1dc7190e101 100644 --- a/mobile/android/base/fxa/sync/FxAccountSyncAdapter.java +++ b/mobile/android/base/fxa/sync/FxAccountSyncAdapter.java @@ -67,7 +67,7 @@ public class FxAccountSyncAdapter extends AbstractThreadedSyncAdapter { 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"; diff --git a/mobile/android/base/sync/MigrationSentinelSyncStage.java b/mobile/android/base/sync/MigrationSentinelSyncStage.java index 1e5f8e9a8199..a035b5b2c754 100644 --- a/mobile/android/base/sync/MigrationSentinelSyncStage.java +++ b/mobile/android/base/sync/MigrationSentinelSyncStage.java @@ -7,6 +7,7 @@ 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; @@ -18,6 +19,7 @@ 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 @@ -152,6 +154,7 @@ public class MigrationSentinelSyncStage extends AbstractNonRepositorySyncStage { 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."); } @@ -163,6 +166,7 @@ public class MigrationSentinelSyncStage extends AbstractNonRepositorySyncStage { 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); } @@ -181,6 +185,9 @@ public class MigrationSentinelSyncStage extends AbstractNonRepositorySyncStage { 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); diff --git a/mobile/android/base/sync/telemetry/TelemetryContract.java b/mobile/android/base/sync/telemetry/TelemetryContract.java new file mode 100644 index 000000000000..c35ada5f53d0 --- /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. + *
+ * 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. + *
+ * 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. + *
+ * 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"; +}