зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1534451 - Send Mobile Activation Telemetry ping. r=JanH
Differential Revision: https://phabricator.services.mozilla.com/D29668 --HG-- extra : moz-landing-system : lando
This commit is contained in:
Родитель
99e0852267
Коммит
b597c1dbab
|
@ -177,6 +177,7 @@ android {
|
|||
if (!mozconfig.substs.MOZ_ANDROID_GCM) {
|
||||
exclude 'org/mozilla/gecko/gcm/**/*.java'
|
||||
exclude 'org/mozilla/gecko/push/**/*.java'
|
||||
exclude 'org/mozilla/gecko/advertising/**'
|
||||
}
|
||||
}
|
||||
resources {
|
||||
|
@ -244,6 +245,8 @@ dependencies {
|
|||
implementation "com.google.android.gms:play-services-basement:$google_play_services_version"
|
||||
implementation "com.google.android.gms:play-services-base:$google_play_services_version"
|
||||
implementation "com.google.android.gms:play-services-gcm:$google_play_services_version"
|
||||
implementation "com.google.android.gms:play-services-ads-identifier:$google_play_services_version"
|
||||
implementation "org.mindrot:jbcrypt:0.4"
|
||||
}
|
||||
|
||||
if (mozconfig.substs.MOZ_ANDROID_GOOGLE_PLAY_SERVICES) {
|
||||
|
|
|
@ -138,6 +138,7 @@ import org.mozilla.gecko.tabs.TabsPanel;
|
|||
import org.mozilla.gecko.telemetry.TelemetryCorePingDelegate;
|
||||
import org.mozilla.gecko.telemetry.TelemetryUploadService;
|
||||
import org.mozilla.gecko.telemetry.measurements.SearchCountMeasurements;
|
||||
import org.mozilla.gecko.telemetry.TelemetryActivationPingDelegate;
|
||||
import org.mozilla.gecko.toolbar.AutocompleteHandler;
|
||||
import org.mozilla.gecko.toolbar.BrowserToolbar;
|
||||
import org.mozilla.gecko.toolbar.BrowserToolbar.CommitEventSource;
|
||||
|
@ -318,6 +319,7 @@ public class BrowserApp extends GeckoApp
|
|||
private final DynamicToolbar mDynamicToolbar = new DynamicToolbar();
|
||||
|
||||
private final TelemetryCorePingDelegate mTelemetryCorePingDelegate = new TelemetryCorePingDelegate();
|
||||
private final TelemetryActivationPingDelegate mTelemetryActivationPingDelegate = new TelemetryActivationPingDelegate();
|
||||
|
||||
private final List<BrowserAppDelegate> delegates = Collections.unmodifiableList(Arrays.asList(
|
||||
new ScreenshotDelegate(),
|
||||
|
@ -325,6 +327,7 @@ public class BrowserApp extends GeckoApp
|
|||
new ReaderViewBookmarkPromotion(),
|
||||
new PostUpdateHandler(),
|
||||
mTelemetryCorePingDelegate,
|
||||
mTelemetryActivationPingDelegate,
|
||||
new OfflineTabStatusDelegate(),
|
||||
new AdjustBrowserAppDelegate(mTelemetryCorePingDelegate)
|
||||
));
|
||||
|
|
|
@ -0,0 +1,32 @@
|
|||
package org.mozilla.gecko.advertising;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import com.google.android.gms.ads.identifier.AdvertisingIdClient;
|
||||
import com.leanplum.internal.Log;
|
||||
|
||||
import org.mindrot.jbcrypt.BCrypt;
|
||||
import org.mozilla.gecko.annotation.ReflectionTarget;
|
||||
|
||||
@ReflectionTarget
|
||||
public class AdvertisingUtil {
|
||||
/* Use the same SALT for all BCrypt hashings. We want the SALT to be stable for all Fennec users but it should differ from the one from Fenix.
|
||||
* Generated using Bcrypt.gensalt(). */
|
||||
private static final String BCRYPT_SALT = "$2a$10$ZfglUfcbmTyaBbAQ7SL9OO";
|
||||
|
||||
/**
|
||||
* Retrieves the advertising ID hashed with BCrypt. Requires Google Play Services. Note: This method must not run on
|
||||
* the main thread.
|
||||
*/
|
||||
@ReflectionTarget
|
||||
public static String getAdvertisingId(Context caller) {
|
||||
try {
|
||||
AdvertisingIdClient.Info info = AdvertisingIdClient.getAdvertisingIdInfo(caller);
|
||||
String advertisingId = info.getId();
|
||||
return advertisingId != null ? BCrypt.hashpw(advertisingId, BCRYPT_SALT) : null;
|
||||
} catch (Throwable t) {
|
||||
Log.e("Error retrieving advertising ID.", t);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,99 @@
|
|||
package org.mozilla.gecko.telemetry;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.WorkerThread;
|
||||
import android.util.Log;
|
||||
|
||||
import org.mozilla.gecko.AppConstants;
|
||||
import org.mozilla.gecko.BrowserApp;
|
||||
import org.mozilla.gecko.GeckoProfile;
|
||||
import org.mozilla.gecko.GeckoThread;
|
||||
import org.mozilla.gecko.delegates.BrowserAppDelegate;
|
||||
import org.mozilla.gecko.telemetry.pingbuilders.TelemetryActivationPingBuilder;
|
||||
import org.mozilla.gecko.util.StringUtils;
|
||||
import org.mozilla.gecko.util.ThreadUtils;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
/**
|
||||
* An activity-lifecycle delegate for uploading the activation ping.
|
||||
*/
|
||||
public class TelemetryActivationPingDelegate extends BrowserAppDelegate {
|
||||
private static final String LOGTAG = StringUtils.safeSubstring(
|
||||
"Gecko" + TelemetryActivationPingDelegate.class.getSimpleName(), 0, 23);
|
||||
|
||||
private TelemetryDispatcher telemetryDispatcher; // lazy
|
||||
|
||||
|
||||
@Override
|
||||
public void onCreate(BrowserApp browserApp, Bundle savedInstanceState) {
|
||||
super.onCreate(browserApp, savedInstanceState);
|
||||
uploadActivationPing(browserApp);
|
||||
}
|
||||
|
||||
private void uploadActivationPing(final BrowserApp activity) {
|
||||
if (!AppConstants.MOZ_ANDROID_GCM) {
|
||||
return;
|
||||
}
|
||||
|
||||
if(TelemetryActivationPingBuilder.activationPingAlreadySent(activity)) {
|
||||
return;
|
||||
}
|
||||
|
||||
ThreadUtils.postToBackgroundThread(() -> {
|
||||
if (activity == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!TelemetryUploadService.isUploadEnabledByAppConfig(activity)) {
|
||||
Log.d(LOGTAG, "Activation ping upload disabled by app config. Returning.");
|
||||
return;
|
||||
}
|
||||
|
||||
String identifier = null;
|
||||
|
||||
try {
|
||||
final Class<?> clazz = Class.forName("org.mozilla.gecko.advertising.AdvertisingUtil");
|
||||
final Method getAdvertisingId = clazz.getMethod("getAdvertisingId", Context.class);
|
||||
identifier = (String) getAdvertisingId.invoke(null, activity);
|
||||
} catch (Exception e) {
|
||||
Log.w(LOGTAG, "Unable to get identifier: " + e);
|
||||
}
|
||||
|
||||
final GeckoProfile profile = GeckoThread.getActiveProfile();
|
||||
String clientID = null;
|
||||
try {
|
||||
clientID = profile.getClientId();
|
||||
} catch (final IOException e) {
|
||||
Log.w(LOGTAG, "Unable to get client ID: " + e);
|
||||
if (identifier == null) {
|
||||
//Activation ping is mandatory to be sent with either the identifier or the clientID.
|
||||
Log.d(LOGTAG, "Activation ping failed to send - both identifier and clientID were unable to be retrieved.");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
final TelemetryActivationPingBuilder pingBuilder = new TelemetryActivationPingBuilder(activity);
|
||||
if (identifier != null) {
|
||||
pingBuilder.setIdentifier(identifier);
|
||||
} else {
|
||||
pingBuilder.setClientID(clientID);
|
||||
}
|
||||
|
||||
getTelemetryDispatcher().queuePingForUpload(activity, pingBuilder);
|
||||
});
|
||||
}
|
||||
|
||||
@WorkerThread // via constructor
|
||||
private TelemetryDispatcher getTelemetryDispatcher() {
|
||||
if (telemetryDispatcher == null) {
|
||||
final GeckoProfile profile = GeckoThread.getActiveProfile();
|
||||
final String profilePath = profile.getDir().getAbsolutePath();
|
||||
final String profileName = profile.getName();
|
||||
telemetryDispatcher = new TelemetryDispatcher(profilePath, profileName);
|
||||
}
|
||||
return telemetryDispatcher;
|
||||
}
|
||||
}
|
|
@ -21,11 +21,11 @@ import org.mozilla.gecko.GeckoThread;
|
|||
import org.mozilla.gecko.Telemetry;
|
||||
import org.mozilla.gecko.TelemetryContract;
|
||||
import org.mozilla.gecko.adjust.AttributionHelperListener;
|
||||
import org.mozilla.gecko.telemetry.measurements.CampaignIdMeasurements;
|
||||
import org.mozilla.gecko.delegates.BrowserAppDelegateWithReference;
|
||||
import org.mozilla.gecko.distribution.DistributionStoreCallback;
|
||||
import org.mozilla.gecko.search.SearchEngineManager;
|
||||
import org.mozilla.gecko.sync.ExtendedJSONObject;
|
||||
import org.mozilla.gecko.telemetry.measurements.CampaignIdMeasurements;
|
||||
import org.mozilla.gecko.telemetry.measurements.SearchCountMeasurements;
|
||||
import org.mozilla.gecko.telemetry.measurements.SessionMeasurements;
|
||||
import org.mozilla.gecko.telemetry.pingbuilders.TelemetryCorePingBuilder;
|
||||
|
@ -139,10 +139,7 @@ public class TelemetryCorePingDelegate extends BrowserAppDelegateWithReference
|
|||
// the first launch of the activity doesn't trigger profile init too early.
|
||||
//
|
||||
// Additionally, getAndIncrementSequenceNumber must be called from a worker thread.
|
||||
ThreadUtils.postToBackgroundThread(new Runnable() {
|
||||
@WorkerThread
|
||||
@Override
|
||||
public void run() {
|
||||
ThreadUtils.postToBackgroundThread(() -> {
|
||||
final BrowserApp activity = getBrowserApp();
|
||||
if (activity == null) {
|
||||
return;
|
||||
|
@ -179,7 +176,6 @@ public class TelemetryCorePingDelegate extends BrowserAppDelegateWithReference
|
|||
maybeSetOptionalMeasurements(activity, sharedPrefs, pingBuilder);
|
||||
|
||||
getTelemetryDispatcher(activity).queuePingForUpload(activity, pingBuilder);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -9,6 +9,8 @@ package org.mozilla.gecko.telemetry;
|
|||
import android.content.Context;
|
||||
import android.support.annotation.WorkerThread;
|
||||
import android.util.Log;
|
||||
|
||||
import org.mozilla.gecko.telemetry.pingbuilders.TelemetryActivationPingBuilder;
|
||||
import org.mozilla.gecko.telemetry.pingbuilders.TelemetryCorePingBuilder;
|
||||
import org.mozilla.gecko.telemetry.pingbuilders.TelemetryCrashPingBuilder;
|
||||
import org.mozilla.gecko.telemetry.schedulers.TelemetryUploadScheduler;
|
||||
|
@ -90,6 +92,14 @@ public class TelemetryDispatcher {
|
|||
queuePingForUpload(context, ping, coreStore, uploadAllPingsImmediatelyScheduler);
|
||||
}
|
||||
|
||||
/**
|
||||
* Queues the given ping for upload and potentially schedules upload. This method can be called from any thread.
|
||||
*/
|
||||
public void queuePingForUpload(final Context context, final TelemetryActivationPingBuilder pingBuilder) {
|
||||
final TelemetryOutgoingPing ping = pingBuilder.build();
|
||||
queuePingForUpload(context, ping, coreStore, uploadAllPingsImmediatelyScheduler);
|
||||
}
|
||||
|
||||
/**
|
||||
* Queues the given crash ping for upload and potentially schedules upload. This method can be called from any thread.
|
||||
*/
|
||||
|
|
|
@ -19,6 +19,7 @@ import org.mozilla.gecko.sync.ExtendedJSONObject;
|
|||
import org.mozilla.gecko.sync.net.BaseResource;
|
||||
import org.mozilla.gecko.sync.net.BaseResourceDelegate;
|
||||
import org.mozilla.gecko.sync.net.Resource;
|
||||
import org.mozilla.gecko.telemetry.pingbuilders.TelemetryActivationPingBuilder;
|
||||
import org.mozilla.gecko.telemetry.stores.TelemetryPingStore;
|
||||
import org.mozilla.gecko.util.DateUtil;
|
||||
import org.mozilla.gecko.util.NetworkUtils;
|
||||
|
@ -125,6 +126,8 @@ public class TelemetryUploadService extends JobIntentService {
|
|||
if (delegate.hadConnectionError()) {
|
||||
break;
|
||||
}
|
||||
|
||||
checkPingsPersistence(context, ping.getDocID());
|
||||
}
|
||||
|
||||
final boolean wereAllUploadsSuccessful = !delegate.hadConnectionError();
|
||||
|
@ -136,6 +139,23 @@ public class TelemetryUploadService extends JobIntentService {
|
|||
return wereAllUploadsSuccessful;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if we have any pings that need to persist their succesful upload status in order to prevent further attempts.
|
||||
* E.g. {@link TelemetryActivationPingBuilder}
|
||||
* @param context
|
||||
*/
|
||||
private static void checkPingsPersistence(Context context, String successfulUploadID) {
|
||||
final String activationID = TelemetryActivationPingBuilder.getActivationPingId(context);
|
||||
|
||||
if(activationID == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if(activationID.equals(successfulUploadID)) {
|
||||
TelemetryActivationPingBuilder.setActivationPingSent(context, true);
|
||||
}
|
||||
}
|
||||
|
||||
private static void uploadPayload(final String url, final ExtendedJSONObject payload, final ResultDelegate delegate) {
|
||||
final BaseResource resource;
|
||||
try {
|
||||
|
|
|
@ -0,0 +1,128 @@
|
|||
package org.mozilla.gecko.telemetry.pingbuilders;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.os.Build;
|
||||
import android.support.annotation.NonNull;
|
||||
|
||||
import org.mozilla.gecko.AppConstants;
|
||||
import org.mozilla.gecko.GeckoSharedPrefs;
|
||||
import org.mozilla.gecko.Locales;
|
||||
import org.mozilla.gecko.distribution.DistributionStoreCallback;
|
||||
import org.mozilla.gecko.telemetry.TelemetryOutgoingPing;
|
||||
import org.mozilla.gecko.util.DateUtil;
|
||||
import org.mozilla.gecko.util.StringUtils;
|
||||
|
||||
import java.text.DateFormat;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Calendar;
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
* Builds a {@link TelemetryOutgoingPing} representing a activation ping.
|
||||
*
|
||||
*/
|
||||
public class TelemetryActivationPingBuilder extends TelemetryPingBuilder {
|
||||
private static final String LOGTAG = StringUtils.safeSubstring(TelemetryActivationPingBuilder.class.getSimpleName(), 0, 23);
|
||||
|
||||
//Using MOZ_APP_BASENAME would be more elegant but according to the server side schema we need to be sure that we always send the "Fennec" value.
|
||||
private static final String APP_NAME_VALUE = "Fennec";
|
||||
|
||||
private static final String PREFS_ACTIVATION_ID = "activation_ping_id";
|
||||
private static final String PREFS_ACTIVATION_SENT = "activation_ping_sent";
|
||||
|
||||
private static final String NAME = "activation";
|
||||
private static final int VERSION_VALUE = 1;
|
||||
|
||||
private static final String IDENTIFIER = "identifier";
|
||||
private static final String CLIENT_ID = "clientId";
|
||||
private static final String MANUFACTURER = "manufacturer";
|
||||
private static final String MODEL = "model";
|
||||
private static final String DISTRIBUTION_ID = "distribution_id";
|
||||
private static final String LOCALE = "locale";
|
||||
private static final String OS_ATTR = "os";
|
||||
private static final String OS_VERSION = "osversion";
|
||||
private static final String PING_CREATION_DATE = "created";
|
||||
private static final String TIMEZONE_OFFSET = "tz";
|
||||
private static final String APP_NAME = "app_name";
|
||||
private static final String CHANNEL = "channel";
|
||||
|
||||
public TelemetryActivationPingBuilder(final Context context) {
|
||||
super(VERSION_VALUE, true);
|
||||
initPayloadConstants(context);
|
||||
}
|
||||
|
||||
private void initPayloadConstants(final Context context) {
|
||||
payload.put(MANUFACTURER, Build.MANUFACTURER);
|
||||
payload.put(MODEL, Build.MODEL);
|
||||
payload.put(LOCALE, Locales.getLanguageTag(Locale.getDefault()));
|
||||
payload.put(OS_ATTR, TelemetryPingBuilder.OS_NAME);
|
||||
payload.put(OS_VERSION, Integer.toString(Build.VERSION.SDK_INT)); // A String for cross-platform reasons.
|
||||
|
||||
final Calendar nowCalendar = Calendar.getInstance();
|
||||
final DateFormat pingCreationDateFormat = new SimpleDateFormat("yyyy-MM-dd", Locale.US);
|
||||
payload.put(PING_CREATION_DATE, pingCreationDateFormat.format(nowCalendar.getTime()));
|
||||
payload.put(TIMEZONE_OFFSET, DateUtil.getTimezoneOffsetInMinutesForGivenDate(nowCalendar));
|
||||
payload.put(APP_NAME, APP_NAME_VALUE);
|
||||
payload.put(CHANNEL, AppConstants.ANDROID_PACKAGE_NAME);
|
||||
|
||||
SharedPreferences prefs = GeckoSharedPrefs.forApp(context);
|
||||
final String distributionId = prefs.getString(DistributionStoreCallback.PREF_DISTRIBUTION_ID, null);
|
||||
if (distributionId != null) {
|
||||
payload.put(DISTRIBUTION_ID, distributionId);
|
||||
}
|
||||
|
||||
prefs.edit().putString(PREFS_ACTIVATION_ID, docID).apply();
|
||||
}
|
||||
|
||||
public static boolean activationPingAlreadySent(Context context) {
|
||||
return GeckoSharedPrefs.forApp(context).getBoolean(PREFS_ACTIVATION_SENT, false);
|
||||
}
|
||||
|
||||
public static void setActivationPingSent(Context context, boolean value) {
|
||||
SharedPreferences prefs = GeckoSharedPrefs.forApp(context);
|
||||
prefs.edit().putBoolean(PREFS_ACTIVATION_SENT, value).apply();
|
||||
prefs.edit().remove(PREFS_ACTIVATION_ID).apply();
|
||||
}
|
||||
|
||||
public static String getActivationPingId(Context context) {
|
||||
return GeckoSharedPrefs.forApp(context).getString(PREFS_ACTIVATION_ID, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDocType() {
|
||||
return NAME;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] getMandatoryFields() {
|
||||
return new String[] {
|
||||
MANUFACTURER,
|
||||
MODEL,
|
||||
LOCALE,
|
||||
OS_ATTR,
|
||||
OS_VERSION,
|
||||
PING_CREATION_DATE,
|
||||
TIMEZONE_OFFSET,
|
||||
APP_NAME,
|
||||
CHANNEL
|
||||
};
|
||||
}
|
||||
|
||||
public TelemetryActivationPingBuilder setIdentifier(@NonNull final String identifier) {
|
||||
if (identifier == null) {
|
||||
throw new IllegalArgumentException("Expected non-null identifier");
|
||||
}
|
||||
|
||||
payload.put(IDENTIFIER, identifier);
|
||||
return this;
|
||||
}
|
||||
|
||||
public TelemetryActivationPingBuilder setClientID(@NonNull final String clientID) {
|
||||
if (clientID == null) {
|
||||
throw new IllegalArgumentException("Expected non-null clientID");
|
||||
}
|
||||
payload.put(CLIENT_ID, clientID);
|
||||
return this;
|
||||
}
|
||||
}
|
|
@ -25,6 +25,9 @@ abstract class TelemetryPingBuilder {
|
|||
// In the server url, the initial path directly after the "scheme://host:port/"
|
||||
private static final String SERVER_INITIAL_PATH = "submit/telemetry";
|
||||
|
||||
// Modern pings now use a structured ingestion where we capture the schema version as one of the URI parameters.
|
||||
private static final String SERVER_INITIAL_PATH_MODERN = "submit/mobile";
|
||||
|
||||
// By default Fennec ping's use the old telemetry version, this can be overridden
|
||||
private static final int DEFAULT_TELEMETRY_VERSION = 1;
|
||||
|
||||
|
@ -48,6 +51,12 @@ abstract class TelemetryPingBuilder {
|
|||
payload = new ExtendedJSONObject();
|
||||
}
|
||||
|
||||
public TelemetryPingBuilder(int version, boolean modernPing) {
|
||||
docID = UUID.randomUUID().toString();
|
||||
serverPath = modernPing ? getModernTelemetryServerPath(getDocType(), docID, version) : getTelemetryServerPath(getDocType(), docID, version);
|
||||
payload = new ExtendedJSONObject();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the name of the ping (e.g. "core")
|
||||
*/
|
||||
|
@ -100,4 +109,22 @@ abstract class TelemetryPingBuilder {
|
|||
appBuildId +
|
||||
(version == UNIFIED_TELEMETRY_VERSION ? "?v=4" : "");
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a url of the format:
|
||||
* http://hostname/submit/mobile/docType/appVersion/docId/
|
||||
*
|
||||
* User for modern structured ingestion.
|
||||
*
|
||||
* @param docType The name of the ping (e.g. "main")
|
||||
* @param docID A UUID that identifies the ping
|
||||
* @param version The ping format version
|
||||
* @return a url at which to POST the telemetry data to
|
||||
*/
|
||||
private static String getModernTelemetryServerPath(final String docType, final String docID, int version) {
|
||||
return SERVER_INITIAL_PATH_MODERN + '/' +
|
||||
docType + '/' +
|
||||
version + '/' +
|
||||
docID;
|
||||
}
|
||||
}
|
||||
|
|
Загрузка…
Ссылка в новой задаче