From a0e7e298822a80d301b9cf78cfaf7c88c8e8972c Mon Sep 17 00:00:00 2001 From: vcolin7 Date: Tue, 15 Sep 2020 18:18:46 -0700 Subject: [PATCH] Modified UserAgentInterceptor and added TelemetryOptions. Modified unit tests to reflect this. (#345) --- .../interceptor/UserAgentInterceptor.java | 101 ++++++++++-------- .../core/http/options/TelemetryOptions.java | 49 +++++++++ .../ApplicationInformationProvider.java | 4 +- .../provider/LocaleInformationProvider.java | 4 +- .../interceptor/UserAgentInterceptorTest.java | 59 +++++++--- 5 files changed, 156 insertions(+), 61 deletions(-) create mode 100644 sdk/core/azure-core/src/main/java/com/azure/android/core/http/options/TelemetryOptions.java diff --git a/sdk/core/azure-core/src/main/java/com/azure/android/core/http/interceptor/UserAgentInterceptor.java b/sdk/core/azure-core/src/main/java/com/azure/android/core/http/interceptor/UserAgentInterceptor.java index 31a2a1fd8d..4da56cff40 100644 --- a/sdk/core/azure-core/src/main/java/com/azure/android/core/http/interceptor/UserAgentInterceptor.java +++ b/sdk/core/azure-core/src/main/java/com/azure/android/core/http/interceptor/UserAgentInterceptor.java @@ -6,8 +6,10 @@ import android.content.Context; import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import com.azure.android.core.http.HttpHeader; +import com.azure.android.core.http.options.TelemetryOptions; import com.azure.android.core.provider.ApplicationInformationProvider; import com.azure.android.core.provider.LocaleInformationProvider; import com.azure.android.core.provider.PlatformInformationProvider; @@ -29,11 +31,15 @@ public class UserAgentInterceptor implements Interceptor { private static final String DEFAULT_USER_AGENT = "azsdk-android"; + // From the design guidelines, the basic user agent header format is: + // azsdk-android-/ + private static final String USER_AGENT_FORMAT = DEFAULT_USER_AGENT + "-%s/%s"; + // From the design guidelines, the default user agent header format is: // azsdk-android-/ (; application_info>; - private static final String USER_AGENT_FORMAT = DEFAULT_USER_AGENT + "-%s/%s (%s; %s; %s)"; + private static final String USER_AGENT_FORMAT_WITH_TELEMETRY = "%s (%s; %s; %s)"; - // From the design guidelines, the application ID user agent header format is: + // From the design guidelines, the full user agent header format is: // [] azsdk-android-/ (; ; // private static final String USER_AGENT_FORMAT_WITH_APPLICATION_ID = "[%s] %s"; @@ -50,19 +56,19 @@ public class UserAgentInterceptor implements Interceptor { // _ private static final String LOCALE_INFO_FORMAT = "%s_%s"; - private final String userAgent; + private String userAgent; /** * Creates a {@link UserAgentInterceptor} with the {@code sdkName} and {@code sdkVersion} in the User-Agent * header value. * - * @param context The application's context. - * @param sdkName Name of the client library. - * @param sdkVersion Version of the client library. + * @param context The application's context. + * @param sdkName Name of the client library. + * @param sdkVersion Version of the client library. */ - public UserAgentInterceptor(Context context, - String sdkName, - String sdkVersion) { + public UserAgentInterceptor(@NonNull Context context, + @Nullable String sdkName, + @Nullable String sdkVersion) { this(null, sdkName, sdkVersion, @@ -75,16 +81,16 @@ public UserAgentInterceptor(Context context, * Creates a {@link UserAgentInterceptor} with the {@code sdkName} and {@code sdkVersion} in the User-Agent * header value. * - * @param context The application's context. - * @param applicationId User specified application ID. - * @param sdkName Name of the client library. - * @param sdkVersion Version of the client library. + * @param context The application's context. + * @param telemetryOptions Telemetry options for calls made by the pipeline. + * @param sdkName Name of the client library. + * @param sdkVersion Version of the client library. */ - public UserAgentInterceptor(Context context, - String applicationId, - String sdkName, - String sdkVersion) { - this(applicationId, + public UserAgentInterceptor(@NonNull Context context, + @Nullable TelemetryOptions telemetryOptions, + @Nullable String sdkName, + @Nullable String sdkVersion) { + this(telemetryOptions, sdkName, sdkVersion, PlatformInformationProvider.getDefault(), @@ -96,46 +102,52 @@ public UserAgentInterceptor(Context context, * Creates a {@link UserAgentInterceptor} with the {@code sdkName} and {@code sdkVersion} in the User-Agent * header value. * - * @param applicationId User specified application ID. + * @param telemetryOptions Telemetry options for calls made by the pipeline. * @param sdkName Name of the client library. * @param sdkVersion Version of the client library. * @param platformInformationProvider Provider that contains platform information. * @param applicationInformationProvider Provider that contains application information. * @param localeInformationProvider Provider that contains system locale information. */ - public UserAgentInterceptor(String applicationId, - String sdkName, - String sdkVersion, - PlatformInformationProvider platformInformationProvider, - ApplicationInformationProvider applicationInformationProvider, - LocaleInformationProvider localeInformationProvider) { + public UserAgentInterceptor(@Nullable TelemetryOptions telemetryOptions, + @Nullable String sdkName, + @Nullable String sdkVersion, + @Nullable PlatformInformationProvider platformInformationProvider, + @Nullable ApplicationInformationProvider applicationInformationProvider, + @Nullable LocaleInformationProvider localeInformationProvider) { sdkName = sdkName == null ? "" : sdkName; sdkVersion = sdkVersion == null ? "" : sdkVersion; - String userAgentBase = String.format( + userAgent = String.format( USER_AGENT_FORMAT, sdkName, - sdkVersion, - getPlatformInfo(platformInformationProvider), - getApplicationInfo(applicationInformationProvider), - getLocaleInfo(localeInformationProvider)); + sdkVersion); + + if (telemetryOptions != null) { + if (!telemetryOptions.isTelemetryDisabled()) { + userAgent = String.format( + USER_AGENT_FORMAT_WITH_TELEMETRY, + userAgent, + getPlatformInfo(platformInformationProvider), + getApplicationInfo(applicationInformationProvider), + getLocaleInfo(localeInformationProvider)); + } - if (isNullOrEmpty(applicationId)) { - userAgent = userAgentBase; - } else { - // Based on the design guidelines, applicationId must not contain a space. - applicationId = applicationId.replaceAll("[\\n\\t ]", ""); + String applicationId = telemetryOptions.getApplicationId(); - // Based on the design guidelines, applicationId must not be more than 24 characters in length. - if (applicationId.length() > 24) { - applicationId = applicationId.substring(0, 24); - } + if (!isNullOrEmpty(applicationId)) { + // Based on the design guidelines, applicationId must not contain a space. + applicationId = applicationId.replaceAll("[\\n\\t ]", ""); + + // Based on the design guidelines, applicationId must not be more than 24 characters in length. + if (applicationId.length() > 24) { + applicationId = applicationId.substring(0, 24); + } - // Don't use the applicationId if it's empty after applying the validations above - if (applicationId.isEmpty()) { - userAgent = userAgentBase; - } else { - userAgent = String.format(USER_AGENT_FORMAT_WITH_APPLICATION_ID, applicationId, userAgentBase); + // Don't use the applicationId if it's empty after applying the validations above. + if (!applicationId.isEmpty()) { + userAgent = String.format(USER_AGENT_FORMAT_WITH_APPLICATION_ID, applicationId, userAgent); + } } } } @@ -148,7 +160,6 @@ public UserAgentInterceptor(String applicationId, * User-Agent header is updated by prepending the value in this interceptor. * * @param chain Provide access to the request to apply the "User-Agent" header. - * * @return Response from the next interceptor in the pipeline. * @throws IOException If an IO error occurs while processing the request and response. */ diff --git a/sdk/core/azure-core/src/main/java/com/azure/android/core/http/options/TelemetryOptions.java b/sdk/core/azure-core/src/main/java/com/azure/android/core/http/options/TelemetryOptions.java new file mode 100644 index 0000000000..da2322fd3f --- /dev/null +++ b/sdk/core/azure-core/src/main/java/com/azure/android/core/http/options/TelemetryOptions.java @@ -0,0 +1,49 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.android.core.http.options; + +import androidx.annotation.Nullable; + +import com.azure.android.core.http.ServiceClient; + +/** + * Options for configuring telemetry sent by a {@link ServiceClient}. + */ +public class TelemetryOptions { + boolean telemetryDisabled; + @Nullable + String applicationId; + + /** + * Creates an instance of {@link TelemetryOptions}. + * + * @param telemetryDisabled Whether platform information will be omitted from the user agent string sent by the + * {@link ServiceClient}. + * @param applicationId An optional user-specified application ID included in the user agent string sent by the + * {@link ServiceClient}. + */ + public TelemetryOptions(boolean telemetryDisabled, @Nullable String applicationId) { + this.telemetryDisabled = telemetryDisabled; + this.applicationId = applicationId; + } + + /** + * Gets a flag indicating if telemetry is disabled for calls made with the {@link ServiceClient}. + * + * @return {@code true} if telemetry is disabled. + */ + public boolean isTelemetryDisabled() { + return telemetryDisabled; + } + + /** + * Gets the application ID used in calls made by the {@link ServiceClient}. + * + * @return The application ID. + */ + @Nullable + public String getApplicationId() { + return applicationId; + } +} diff --git a/sdk/core/azure-core/src/main/java/com/azure/android/core/provider/ApplicationInformationProvider.java b/sdk/core/azure-core/src/main/java/com/azure/android/core/provider/ApplicationInformationProvider.java index 214feab8f6..d3ecae41ba 100644 --- a/sdk/core/azure-core/src/main/java/com/azure/android/core/provider/ApplicationInformationProvider.java +++ b/sdk/core/azure-core/src/main/java/com/azure/android/core/provider/ApplicationInformationProvider.java @@ -5,6 +5,8 @@ import android.content.Context; +import androidx.annotation.NonNull; + import com.azure.android.core.internal.provider.AndroidApplicationInformationProvider; /** @@ -17,7 +19,7 @@ public interface ApplicationInformationProvider { * @param context Android {@link Context} object to extract data from. * @return A default {@link ApplicationInformationProvider}. */ - static ApplicationInformationProvider getDefault(Context context) { + static ApplicationInformationProvider getDefault(@NonNull Context context) { return new AndroidApplicationInformationProvider(context); } diff --git a/sdk/core/azure-core/src/main/java/com/azure/android/core/provider/LocaleInformationProvider.java b/sdk/core/azure-core/src/main/java/com/azure/android/core/provider/LocaleInformationProvider.java index 77f5dae5f9..646dab519c 100644 --- a/sdk/core/azure-core/src/main/java/com/azure/android/core/provider/LocaleInformationProvider.java +++ b/sdk/core/azure-core/src/main/java/com/azure/android/core/provider/LocaleInformationProvider.java @@ -5,6 +5,8 @@ import android.content.Context; +import androidx.annotation.NonNull; + import com.azure.android.core.internal.provider.AndroidLocaleInformationProvider; /** @@ -17,7 +19,7 @@ public interface LocaleInformationProvider { * @param context Android {@link Context} object to extract data from. * @return A default {@link LocaleInformationProvider}. */ - static LocaleInformationProvider getDefault(Context context) { + static LocaleInformationProvider getDefault(@NonNull Context context) { return new AndroidLocaleInformationProvider(context); } diff --git a/sdk/core/azure-core/src/test/java/com/azure/android/core/http/interceptor/UserAgentInterceptorTest.java b/sdk/core/azure-core/src/test/java/com/azure/android/core/http/interceptor/UserAgentInterceptorTest.java index 83f8775cd2..c8eca4ee25 100644 --- a/sdk/core/azure-core/src/test/java/com/azure/android/core/http/interceptor/UserAgentInterceptorTest.java +++ b/sdk/core/azure-core/src/test/java/com/azure/android/core/http/interceptor/UserAgentInterceptorTest.java @@ -5,6 +5,7 @@ import com.azure.android.core.http.HttpHeader; import com.azure.android.core.common.EnqueueMockResponse; +import com.azure.android.core.http.options.TelemetryOptions; import com.azure.android.core.provider.ApplicationInformationProvider; import com.azure.android.core.provider.LocaleInformationProvider; import com.azure.android.core.provider.PlatformInformationProvider; @@ -68,7 +69,7 @@ public void userAgentHeader_isPrependedToNonEmptyHeader_onRequest() throws Inter // Then the 'User-Agent' header should be contain the result of prepending the generated user agent string to // the existing value. - assertEquals("azsdk-android-/ ( - ; : -> ; _) " + userAgent, + assertEquals("azsdk-android-/ " + userAgent, mockWebServer.takeRequest().getHeader(HttpHeader.USER_AGENT)); } @@ -89,7 +90,7 @@ public void userAgentHeader_hasCorrectFormatWithoutApplicationId_onRequest() thr // Then the 'User-Agent' header should be populated following the format specified by the guidelines while not // including the applicationId as a prefix. - assertEquals("azsdk-android-/ ( - ; : -> ; _)", + assertEquals("azsdk-android-/", mockWebServer.takeRequest().getHeader(HttpHeader.USER_AGENT)); } @@ -97,7 +98,8 @@ public void userAgentHeader_hasCorrectFormatWithoutApplicationId_onRequest() thr public void userAgentHeader_hasCorrectFormatWithApplicationId_onRequest() throws InterruptedException, IOException { // Given a client with a UserAgentInterceptor with a user-provided applicationId. String userApplicationId = "UserApplicationId"; - UserAgentInterceptor userAgentInterceptor = new UserAgentInterceptor(userApplicationId, + UserAgentInterceptor userAgentInterceptor = new UserAgentInterceptor( + new TelemetryOptions(true, userApplicationId), null, null, null, @@ -111,7 +113,7 @@ public void userAgentHeader_hasCorrectFormatWithApplicationId_onRequest() throws // Then the 'User-Agent' header should be populated following the format specified by the guidelines while // including the applicationId as a prefix. - assertEquals("[" + userApplicationId + "] azsdk-android-/ ( - ; : -> ; _)", + assertEquals("[" + userApplicationId + "] azsdk-android-/", mockWebServer.takeRequest().getHeader(HttpHeader.USER_AGENT)); } @@ -120,7 +122,8 @@ public void userAgentHeader_hasTrimmedApplicationId_onRequest() throws Interrupt // Given a client with a UserAgentInterceptor with a user-provided applicationId containing spaces. String userApplicationId = "User Application Id"; String trimmedUserApplicationId = "UserApplicationId"; - UserAgentInterceptor userAgentInterceptor = new UserAgentInterceptor(userApplicationId, + UserAgentInterceptor userAgentInterceptor = new UserAgentInterceptor( + new TelemetryOptions(true, userApplicationId), null, null, null, @@ -134,7 +137,7 @@ public void userAgentHeader_hasTrimmedApplicationId_onRequest() throws Interrupt // Then the 'User-Agent' header should be populated following the format specified by the guidelines while // including a trimmed applicationId as a prefix. - assertEquals("[" + trimmedUserApplicationId + "] azsdk-android-/ ( - ; : -> ; _)", + assertEquals("[" + trimmedUserApplicationId + "] azsdk-android-/", mockWebServer.takeRequest().getHeader(HttpHeader.USER_AGENT)); } @@ -143,7 +146,8 @@ public void userAgentHeader_hasTruncatedApplicationId_onRequest() throws Interru // Given a client with a UserAgentInterceptor with a user-provided applicationId longer than 24 characters. String userApplicationId = "UserApplicationIdThatIsVeryLong"; String truncatedUserApplicationId = "UserApplicationIdThatIsV"; - UserAgentInterceptor userAgentInterceptor = new UserAgentInterceptor(userApplicationId, + UserAgentInterceptor userAgentInterceptor = new UserAgentInterceptor( + new TelemetryOptions(true, userApplicationId), null, null, null, @@ -157,17 +161,43 @@ public void userAgentHeader_hasTruncatedApplicationId_onRequest() throws Interru // Then the 'User-Agent' header should be populated following the format specified by the guidelines while // including a truncated applicationId as a prefix. - assertEquals("[" + truncatedUserApplicationId + "] azsdk-android-/ ( - ; : -> ; _)", + assertEquals("[" + truncatedUserApplicationId + "] azsdk-android-/", mockWebServer.takeRequest().getHeader(HttpHeader.USER_AGENT)); } @Test - public void userAgentHeader_includesBasicInfo_onRequest() throws InterruptedException, IOException { + public void userAgentHeader_includesBasicInfoWithoutTelemetry_onRequest() throws InterruptedException, IOException { // Given a client with a UserAgentInterceptor with a user-provided applicationId, sdkName and sdkVersion. String userApplicationId = "UserApplicationId"; String sdkName = "SDK Name"; String sdkVersion = "SDK Version"; - UserAgentInterceptor userAgentInterceptor = new UserAgentInterceptor(userApplicationId, + UserAgentInterceptor userAgentInterceptor = new UserAgentInterceptor( + new TelemetryOptions(true, userApplicationId), + sdkName, + sdkVersion, + null, + null, + null); + OkHttpClient okHttpClient = buildOkHttpClientWithInterceptor(userAgentInterceptor); + + // When executing a request. + Request request = getSimpleRequest(mockWebServer); + okHttpClient.newCall(request).execute(); + + // Then the 'User-Agent' header should be populated following the format specified by the guidelines while + // including the given applicationId as a prefix. + assertEquals("[" + userApplicationId + "] azsdk-android-" + sdkName + "/" + sdkVersion, + mockWebServer.takeRequest().getHeader(HttpHeader.USER_AGENT)); + } + + @Test + public void userAgentHeader_includesBasicInfoWithTelemetry_onRequest() throws InterruptedException, IOException { + // Given a client with a UserAgentInterceptor with a user-provided applicationId, sdkName and sdkVersion. + String userApplicationId = "UserApplicationId"; + String sdkName = "SDK Name"; + String sdkVersion = "SDK Version"; + UserAgentInterceptor userAgentInterceptor = new UserAgentInterceptor( + new TelemetryOptions(false, userApplicationId), sdkName, sdkVersion, null, @@ -190,7 +220,7 @@ public void userAgentHeader_includesPlatformInfo_onRequest() throws InterruptedE // Given a client with a UserAgentInterceptor with an PlatformInformationProvider. String deviceName = "Test Device"; int osVersion = 123; - UserAgentInterceptor userAgentInterceptor = new UserAgentInterceptor(null, + UserAgentInterceptor userAgentInterceptor = new UserAgentInterceptor(new TelemetryOptions(false, null), null, null, new TestPlatformInformationProvider(deviceName, osVersion), @@ -214,7 +244,7 @@ public void userAgentHeader_includesApplicationInfo_onRequest() throws Interrupt String applicationId = "Application ID"; String applicationVersion = "1.0"; int targetSdkVersion = 21; - UserAgentInterceptor userAgentInterceptor = new UserAgentInterceptor(null, + UserAgentInterceptor userAgentInterceptor = new UserAgentInterceptor(new TelemetryOptions(false, null), null, null, null, @@ -237,7 +267,7 @@ public void userAgentHeader_includesLocaleInfo_onRequest() throws InterruptedExc // Given a client with a UserAgentInterceptor with an LocaleInformationProvider. String defaultSystemLanguage = "en"; String systemRegion = "US"; - UserAgentInterceptor userAgentInterceptor = new UserAgentInterceptor(null, + UserAgentInterceptor userAgentInterceptor = new UserAgentInterceptor(new TelemetryOptions(false, null), null, null, null, @@ -268,7 +298,8 @@ public void userAgentHeader_includesAllInfo_onRequest() throws InterruptedExcept int targetSdkVersion = 21; String defaultSystemLanguage = "en"; String systemRegion = "US"; - UserAgentInterceptor userAgentInterceptor = new UserAgentInterceptor(userApplicationId, + UserAgentInterceptor userAgentInterceptor = new UserAgentInterceptor( + new TelemetryOptions(false, userApplicationId), sdkName, sdkVersion, new TestPlatformInformationProvider(deviceName, osVersion),