/* 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.telemetry;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RestrictTo;
import androidx.annotation.VisibleForTesting;
import org.mozilla.telemetry.config.TelemetryConfiguration;
import org.mozilla.telemetry.event.TelemetryEvent;
import org.mozilla.telemetry.measurement.ClientIdMeasurement;
import org.mozilla.telemetry.measurement.DefaultSearchMeasurement;
import org.mozilla.telemetry.measurement.EventsMeasurement;
import org.mozilla.telemetry.measurement.ExperimentsMapMeasurement;
import org.mozilla.telemetry.net.TelemetryClient;
import org.mozilla.telemetry.ping.TelemetryCorePingBuilder;
import org.mozilla.telemetry.ping.TelemetryEventPingBuilder;
import org.mozilla.telemetry.ping.TelemetryMobileEventPingBuilder;
import org.mozilla.telemetry.ping.TelemetryPing;
import org.mozilla.telemetry.ping.TelemetryPingBuilder;
import org.mozilla.telemetry.ping.TelemetryPocketEventPingBuilder;
import org.mozilla.telemetry.schedule.TelemetryScheduler;
import org.mozilla.telemetry.storage.TelemetryStorage;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import kotlin.Unit;
import kotlin.jvm.functions.Function0;
/**
* @deprecated The whole service-telemetry library is deprecated. Please use the
* Glean SDK instead.
*/
//@Deprecated
public class Telemetry {
private final TelemetryConfiguration configuration;
private final TelemetryStorage storage;
private final TelemetryClient client;
private final TelemetryScheduler scheduler;
private final Map pingBuilders;
private final ExecutorService executor = Executors.newSingleThreadExecutor();
public Telemetry(TelemetryConfiguration configuration, TelemetryStorage storage,
TelemetryClient client, TelemetryScheduler scheduler) {
this.configuration = configuration;
this.storage = storage;
this.client = client;
this.scheduler = scheduler;
pingBuilders = new HashMap<>();
}
public Telemetry addPingBuilder(TelemetryPingBuilder builder) {
pingBuilders.put(builder.getType(), builder);
return this;
}
/**
* Returns a previously added ping builder or null if no ping builder of the given type has been added.
*/
@Nullable
public TelemetryPingBuilder getPingBuilder(final String pingType) {
return pingBuilders.get(pingType);
}
public Telemetry queuePing(final String pingType) {
if (!configuration.isCollectionEnabled()) {
return this;
}
executor.submit(new Runnable() {
@Override
public void run() {
final TelemetryPingBuilder pingBuilder = pingBuilders.get(pingType);
if (!pingBuilder.canBuild()) {
// We do not always want to build a ping. Sometimes we want to collect enough data so that
// it is worth sending a ping. Here we exit early if the ping builder implementation
// signals that it's not time to build a ping yet.
return;
}
final TelemetryPing ping = pingBuilder.build();
storage.store(ping);
}
});
return this;
}
public Telemetry queueEvent(final TelemetryEvent event) {
if (!configuration.isCollectionEnabled()) {
return this;
}
executor.submit(new Runnable() {
@Override
public void run() {
// We migrated from focus-event to mobile-event and unfortunately, this code was hard-coded to expect
// a focus-event ping builder. We work around this by checking our new hardcoded code first for the new
// ping type and then falling back on the legacy ping type.
final TelemetryPingBuilder mobileEventBuilder = pingBuilders.get(TelemetryMobileEventPingBuilder.TYPE);
final TelemetryPingBuilder focusEventBuilder = pingBuilders.get(TelemetryEventPingBuilder.TYPE);
final EventsMeasurement measurement;
final String addedPingType;
if (mobileEventBuilder != null) {
measurement = ((TelemetryMobileEventPingBuilder) mobileEventBuilder).getEventsMeasurement();
addedPingType = mobileEventBuilder.getType();
} else if (focusEventBuilder != null) {
measurement = ((TelemetryEventPingBuilder) focusEventBuilder).getEventsMeasurement();
addedPingType = focusEventBuilder.getType();
} else {
throw new IllegalStateException("Expect either TelemetryEventPingBuilder or " +
"TelemetryMobileEventPingBuilder to be added to queue events");
}
measurement.add(event);
if (measurement.getEventCount() >= configuration.getMaximumNumberOfEventsPerPing()) {
queuePing(addedPingType);
}
}
});
return this;
}
public Collection getBuilders() {
return pingBuilders.values();
}
public Telemetry scheduleUpload() {
if (!configuration.isUploadEnabled()) {
return this;
}
executor.submit(new Runnable() {
@Override
public void run() {
scheduler.scheduleUpload(configuration);
}
});
return this;
}
public void recordSessionStart() {
if (!configuration.isCollectionEnabled()) {
return;
}
if (!pingBuilders.containsKey(TelemetryCorePingBuilder.TYPE)) {
throw new IllegalStateException("This configuration does not contain a core ping builder");
}
final TelemetryCorePingBuilder builder = (TelemetryCorePingBuilder) pingBuilders.get(TelemetryCorePingBuilder.TYPE);
builder.getSessionDurationMeasurement().recordSessionStart();
builder.getSessionCountMeasurement().countSession();
}
public Telemetry recordSessionEnd(Function0 onFailure) {
if (!configuration.isCollectionEnabled()) {
return this;
}
if (!pingBuilders.containsKey(TelemetryCorePingBuilder.TYPE)) {
throw new IllegalStateException("This configuration does not contain a core ping builder");
}
final TelemetryCorePingBuilder builder = (TelemetryCorePingBuilder) pingBuilders.get(TelemetryCorePingBuilder.TYPE);
boolean endedSuccessfully = builder.getSessionDurationMeasurement().recordSessionEnd();
if (!endedSuccessfully) {
onFailure.invoke();
}
return this;
}
public Telemetry recordSessionEnd() {
return recordSessionEnd(new Function0() {
@Override
public Unit invoke() {
throw new IllegalStateException("Expected session to be started before session end is called");
}
});
}
/**
* Record a search for the given location and search engine identifier.
*
* Common location values used by Fennec and Focus:
*
* actionbar: the user types in the url bar and hits enter to use the default search engine
* listitem: the user selects a search engine from the list of secondary search engines at
* the bottom of the screen
* suggestion: the user clicks on a search suggestion or, in the case that suggestions are
* disabled, the row corresponding with the main engine
*
* @param location where search was started.
* @param identifier of the used search engine.
*/
public Telemetry recordSearch(@NonNull String location, @NonNull String identifier) {
if (!configuration.isCollectionEnabled()) {
return this;
}
if (!pingBuilders.containsKey(TelemetryCorePingBuilder.TYPE)) {
throw new IllegalStateException("This configuration does not contain a core ping builder");
}
final TelemetryCorePingBuilder builder = (TelemetryCorePingBuilder) pingBuilders.get(TelemetryCorePingBuilder.TYPE);
builder.getSearchesMeasurement()
.recordSearch(location, identifier);
return this;
}
/**
* Records the list of active experiments
*
* @param activeExperimentsIds list of active experiments ids
*/
public Telemetry recordActiveExperiments(List activeExperimentsIds) {
if (!configuration.isCollectionEnabled()) {
return this;
}
if (!pingBuilders.containsKey(TelemetryCorePingBuilder.TYPE)) {
throw new IllegalStateException("This configuration does not contain a core ping builder");
}
final TelemetryCorePingBuilder builder = (TelemetryCorePingBuilder) pingBuilders.get(TelemetryCorePingBuilder.TYPE);
builder.getExperimentsMeasurement()
.setActiveExperiments(activeExperimentsIds);
return this;
}
/**
* Records all experiments the client knows of in the event ping.
*
* @param experiments A map of experiments the client knows of. Mapping experiment name to a Boolean value that is
* true if the client is part of the experiment and false if the client is not part of the
* experiment.
*/
public Telemetry recordExperiments(Map experiments) {
if (!configuration.isCollectionEnabled()) {
return this;
}
final TelemetryPingBuilder mobileEventBuilder = pingBuilders.get(TelemetryMobileEventPingBuilder.TYPE);
final TelemetryPingBuilder focusEventBuilder = pingBuilders.get(TelemetryEventPingBuilder.TYPE);
final ExperimentsMapMeasurement measurement;
if (mobileEventBuilder != null) {
measurement = ((TelemetryMobileEventPingBuilder) mobileEventBuilder).getExperimentsMapMeasurement();
} else if (focusEventBuilder != null) {
measurement = ((TelemetryEventPingBuilder) focusEventBuilder).getExperimentsMapMeasurement();
} else {
throw new IllegalStateException("Expect either TelemetryEventPingBuilder or " +
"TelemetryMobileEventPingBuilder to be record experiments");
}
measurement.setExperiments(experiments);
return this;
}
public Telemetry setDefaultSearchProvider(DefaultSearchMeasurement.DefaultSearchEngineProvider provider) {
if (!pingBuilders.containsKey(TelemetryCorePingBuilder.TYPE)) {
throw new IllegalStateException("This configuration does not contain a core ping builder");
}
final TelemetryCorePingBuilder builder = (TelemetryCorePingBuilder) pingBuilders.get(TelemetryCorePingBuilder.TYPE);
builder.getDefaultSearchMeasurement()
.setDefaultSearchEngineProvider(provider);
return this;
}
public TelemetryClient getClient() {
return client;
}
public TelemetryStorage getStorage() {
return storage;
}
public TelemetryConfiguration getConfiguration() {
return configuration;
}
/**
* Returns the unique client id for this installation (UUID).
*/
public String getClientId() {
return (String) new ClientIdMeasurement(configuration).flush();
}
/**
* @hide
*/
@RestrictTo(RestrictTo.Scope.LIBRARY)
@VisibleForTesting ExecutorService getExecutor() {
return executor;
}
}