diff --git a/CHANGELOG.md b/CHANGELOG.md index f723edc5..0f279581 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ All notable changes to the LaunchDarkly Android SDK will be documented in this file. This project adheres to [Semantic Versioning](http://semver.org). +## [3.6.0] - 2023-01-11 +### Added: +- `LDConfig.Builder.applicationInfo()`, for configuration of application metadata that may be used in LaunchDarkly analytics or other product features. This does not affect feature flag evaluations. + ## [4.1.1] - 2023-01-06 ### Fixed: - The fix for unnecessarily long-lived polling connections in the [3.2.2](https://github.com/launchdarkly/android-client-sdk/releases/tag/3.2.2) release was incomplete: rather than turning off the keep-alive behavior, it only reduced it from 10 minutes to 5 minutes. It should now close the connection immediately after each request as intended. diff --git a/contract-tests/src/main/java/com/launchdarkly/sdktest/SdkClientEntity.java b/contract-tests/src/main/java/com/launchdarkly/sdktest/SdkClientEntity.java index 045883c7..fbcb71f3 100644 --- a/contract-tests/src/main/java/com/launchdarkly/sdktest/SdkClientEntity.java +++ b/contract-tests/src/main/java/com/launchdarkly/sdktest/SdkClientEntity.java @@ -12,6 +12,8 @@ import com.launchdarkly.sdk.android.LaunchDarklyException; import com.launchdarkly.sdk.android.LDClient; import com.launchdarkly.sdk.android.LDConfig; + +import com.launchdarkly.sdk.android.integrations.ApplicationInfoBuilder; import com.launchdarkly.sdk.android.integrations.EventProcessorBuilder; import com.launchdarkly.sdk.android.integrations.PollingDataSourceBuilder; import com.launchdarkly.sdk.android.integrations.StreamingDataSourceBuilder; @@ -310,6 +312,17 @@ private LDConfig buildSdkConfig(SdkConfigParams params, LDLogAdapter logAdapter, Components.httpConfiguration().useReport(params.clientSide.useReport) ); + if (params.tags != null) { + ApplicationInfoBuilder ab = Components.applicationInfo(); + if (params.tags.applicationId != null) { + ab.applicationId(params.tags.applicationId); + } + if (params.tags.applicationVersion != null) { + ab.applicationVersion(params.tags.applicationVersion); + } + builder.applicationInfo(ab); + } + if (params.serviceEndpoints != null) { if (params.serviceEndpoints.streaming != null) { endpoints.streaming(params.serviceEndpoints.streaming); diff --git a/contract-tests/src/main/java/com/launchdarkly/sdktest/TestService.java b/contract-tests/src/main/java/com/launchdarkly/sdktest/TestService.java index e4bd626e..69f9b55f 100644 --- a/contract-tests/src/main/java/com/launchdarkly/sdktest/TestService.java +++ b/contract-tests/src/main/java/com/launchdarkly/sdktest/TestService.java @@ -32,6 +32,7 @@ public class TestService extends NanoHTTPD { "service-endpoints", "singleton", "strongly-typed", + "tags" }; private static final String MIME_JSON = "application/json"; static final Gson gson = new GsonBuilder() diff --git a/launchdarkly-android-client-sdk/src/main/java/com/launchdarkly/sdk/android/ClientContextImpl.java b/launchdarkly-android-client-sdk/src/main/java/com/launchdarkly/sdk/android/ClientContextImpl.java index 4f2fb7aa..80678cc2 100644 --- a/launchdarkly-android-client-sdk/src/main/java/com/launchdarkly/sdk/android/ClientContextImpl.java +++ b/launchdarkly-android-client-sdk/src/main/java/com/launchdarkly/sdk/android/ClientContextImpl.java @@ -2,7 +2,6 @@ import com.launchdarkly.logging.LDLogger; import com.launchdarkly.sdk.LDContext; -import com.launchdarkly.sdk.android.interfaces.ServiceEndpoints; import com.launchdarkly.sdk.android.subsystems.ClientContext; import com.launchdarkly.sdk.android.subsystems.HttpConfiguration; import com.launchdarkly.sdk.android.subsystems.DataSourceUpdateSink; @@ -58,12 +57,13 @@ static ClientContextImpl fromConfig( TaskExecutor taskExecutor ) { boolean initiallyInBackground = platformState != null && !platformState.isForeground(); - ClientContext minimalContext = new ClientContext(mobileKey, logger, config, + ClientContext minimalContext = new ClientContext(mobileKey, config.applicationInfo, logger, config, null, environmentName, config.isEvaluationReasons(), initialContext, null, initiallyInBackground, null, config.serviceEndpoints, config.isOffline()); HttpConfiguration httpConfig = config.http.build(minimalContext); ClientContext baseClientContext = new ClientContext( mobileKey, + config.applicationInfo, logger, config, null, @@ -101,6 +101,7 @@ public static ClientContextImpl forDataSource( return new ClientContextImpl( new ClientContext( baseClientContext.getMobileKey(), + baseClientContext.getApplicationInfo(), baseClientContext.getBaseLogger(), baseClientContext.getConfig(), dataSourceUpdateSink, diff --git a/launchdarkly-android-client-sdk/src/main/java/com/launchdarkly/sdk/android/Components.java b/launchdarkly-android-client-sdk/src/main/java/com/launchdarkly/sdk/android/Components.java index c41382d8..eea95f08 100644 --- a/launchdarkly-android-client-sdk/src/main/java/com/launchdarkly/sdk/android/Components.java +++ b/launchdarkly-android-client-sdk/src/main/java/com/launchdarkly/sdk/android/Components.java @@ -1,5 +1,6 @@ package com.launchdarkly.sdk.android; +import com.launchdarkly.sdk.android.integrations.ApplicationInfoBuilder; import com.launchdarkly.sdk.android.integrations.EventProcessorBuilder; import com.launchdarkly.sdk.android.integrations.HttpConfigurationBuilder; import com.launchdarkly.sdk.android.integrations.PollingDataSourceBuilder; @@ -25,6 +26,29 @@ public abstract class Components { private Components() {} + /** + * Returns a configuration builder for the SDK's application metadata. + *
+ * Passing this to {@link LDConfig.Builder#applicationInfo(com.launchdarkly.sdk.android.integrations.ApplicationInfoBuilder)}, + * after setting any desired properties on the builder, applies this configuration to the SDK. + *
+ * LDConfig config = new LDConfig.Builder()
+ * .applicationInfo(
+ * Components.applicationInfo()
+ * .applicationId("authentication-service")
+ * .applicationVersion("1.0.0")
+ * )
+ * .build();
+ *
+ *
+ * @return a builder object
+ * @see LDConfig.Builder#applicationInfo(com.launchdarkly.sdk.android.integrations.ApplicationInfoBuilder)
+ * @since 4.1.0
+ */
+ public static ApplicationInfoBuilder applicationInfo() {
+ return new ApplicationInfoBuilder();
+ }
+
/**
* Returns a configuration builder for the SDK's networking configuration.
*
diff --git a/launchdarkly-android-client-sdk/src/main/java/com/launchdarkly/sdk/android/ComponentsImpl.java b/launchdarkly-android-client-sdk/src/main/java/com/launchdarkly/sdk/android/ComponentsImpl.java
index 367a2343..92df3580 100644
--- a/launchdarkly-android-client-sdk/src/main/java/com/launchdarkly/sdk/android/ComponentsImpl.java
+++ b/launchdarkly-android-client-sdk/src/main/java/com/launchdarkly/sdk/android/ComponentsImpl.java
@@ -211,6 +211,13 @@ public HttpConfiguration build(ClientContext clientContext) {
Map
+ * This object is normally a configuration builder obtained from {@link Components#applicationInfo()},
+ * which has methods for setting individual metadata properties.
+ *
+ * @param applicationInfoBuilder a configuration builder object returned by {@link Components#applicationInfo()}
+ * @return the builder
+ * @since 4.1.0
+ */
+ public Builder applicationInfo(ApplicationInfoBuilder applicationInfoBuilder) {
+ this.applicationInfoBuilder = applicationInfoBuilder;
+ return this;
+ }
+
/**
* Sets the configuration of the component that receives feature flag data from LaunchDarkly.
*
@@ -574,9 +596,14 @@ public LDConfig build() {
serviceEndpointsBuilder)
.createServiceEndpoints();
+ ApplicationInfo applicationInfo = this.applicationInfoBuilder == null ?
+ Components.applicationInfo().createApplicationInfo() :
+ applicationInfoBuilder.createApplicationInfo();
+
return new LDConfig(
mobileKeys,
serviceEndpoints,
+ applicationInfo,
this.dataSource == null ? Components.streamingDataSource() : this.dataSource,
this.events == null ? Components.sendEvents() : this.events,
this.http == null ? Components.httpConfiguration() : this.http,
diff --git a/launchdarkly-android-client-sdk/src/main/java/com/launchdarkly/sdk/android/LDUtil.java b/launchdarkly-android-client-sdk/src/main/java/com/launchdarkly/sdk/android/LDUtil.java
index 19636b1c..c23166c2 100644
--- a/launchdarkly-android-client-sdk/src/main/java/com/launchdarkly/sdk/android/LDUtil.java
+++ b/launchdarkly-android-client-sdk/src/main/java/com/launchdarkly/sdk/android/LDUtil.java
@@ -7,6 +7,7 @@
import com.launchdarkly.logging.LDLogger;
import com.launchdarkly.logging.LogValues;
import com.launchdarkly.sdk.LDContext;
+import com.launchdarkly.sdk.android.subsystems.ApplicationInfo;
import com.launchdarkly.sdk.android.subsystems.Callback;
import com.launchdarkly.sdk.android.subsystems.ClientContext;
import com.launchdarkly.sdk.android.subsystems.HttpConfiguration;
@@ -18,9 +19,12 @@
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
+import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
+import java.util.List;
import java.util.Map;
+import java.util.regex.Pattern;
import okhttp3.Headers;
@@ -31,11 +35,48 @@ class LDUtil {
static
+ * Application metadata may be used in LaunchDarkly analytics or other product features, but does not affect feature flag evaluations.
+ *
+ * If you want to set non-default values for any of these fields, create a builder with
+ * {@link Components#applicationInfo()}, change its properties with the methods of this class,
+ * and pass it to {@link com.launchdarkly.sdk.android.LDConfig.Builder#applicationInfo(ApplicationInfoBuilder)}:
+ *
+ *
+ * @since 4.1.0
+ */
+public final class ApplicationInfoBuilder {
+ private String applicationId;
+ private String applicationVersion;
+
+ /**
+ * Create an empty ApplicationInfoBuilder.
+ *
+ * @see Components#applicationInfo()
+ */
+ public ApplicationInfoBuilder() {}
+
+ /**
+ * Sets a unique identifier representing the application where the LaunchDarkly SDK is running.
+ *
+ * This can be specified as any string value as long as it only uses the following characters: ASCII
+ * letters, ASCII digits, period, hyphen, underscore. A string containing any other characters will be
+ * ignored.
+ *
+ * @param applicationId the application identifier
+ * @return the builder
+ */
+ public ApplicationInfoBuilder applicationId(String applicationId) {
+ this.applicationId = applicationId;
+ return this;
+ }
+
+ /**
+ * Sets a unique identifier representing the version of the application where the LaunchDarkly SDK
+ * is running.
+ *
+ * This can be specified as any string value as long as it only uses the following characters: ASCII
+ * letters, ASCII digits, period, hyphen, underscore. A string containing any other characters will be
+ * ignored.
+ *
+ * @param applicationVersion the application version
+ * @return the builder
+ */
+ public ApplicationInfoBuilder applicationVersion(String applicationVersion) {
+ this.applicationVersion = applicationVersion;
+ return this;
+ }
+
+ /**
+ * Called internally by the SDK to create the configuration object.
+ *
+ * @return the configuration object
+ */
+ public ApplicationInfo createApplicationInfo() {
+ return new ApplicationInfo(applicationId, applicationVersion);
+ }
+}
diff --git a/launchdarkly-android-client-sdk/src/main/java/com/launchdarkly/sdk/android/subsystems/ApplicationInfo.java b/launchdarkly-android-client-sdk/src/main/java/com/launchdarkly/sdk/android/subsystems/ApplicationInfo.java
new file mode 100644
index 00000000..d66f20db
--- /dev/null
+++ b/launchdarkly-android-client-sdk/src/main/java/com/launchdarkly/sdk/android/subsystems/ApplicationInfo.java
@@ -0,0 +1,46 @@
+package com.launchdarkly.sdk.android.subsystems;
+
+import com.launchdarkly.sdk.android.integrations.ApplicationInfoBuilder;
+
+/**
+ * Encapsulates the SDK's application metadata.
+ *
+ * See {@link ApplicationInfoBuilder} for more details on these properties.
+ *
+ * @since 4.1.0
+ */
+public final class ApplicationInfo {
+ private String applicationId;
+ private String applicationVersion;
+
+ /**
+ * Used internally by the SDK to store application metadata.
+ *
+ * @param applicationId the application ID
+ * @param applicationVersion the application version
+ * @see ApplicationInfoBuilder
+ */
+ public ApplicationInfo(String applicationId, String applicationVersion) {
+ this.applicationId = applicationId;
+ this.applicationVersion = applicationVersion;
+ }
+
+ /**
+ * A unique identifier representing the application where the LaunchDarkly SDK is running.
+ *
+ * @return the application identifier, or null
+ */
+ public String getApplicationId() {
+ return applicationId;
+ }
+
+ /**
+ * A unique identifier representing the version of the application where the
+ * LaunchDarkly SDK is running.
+ *
+ * @return the application version, or null
+ */
+ public String getApplicationVersion() {
+ return applicationVersion;
+ }
+}
diff --git a/launchdarkly-android-client-sdk/src/main/java/com/launchdarkly/sdk/android/subsystems/ClientContext.java b/launchdarkly-android-client-sdk/src/main/java/com/launchdarkly/sdk/android/subsystems/ClientContext.java
index f5b01589..58ab0298 100644
--- a/launchdarkly-android-client-sdk/src/main/java/com/launchdarkly/sdk/android/subsystems/ClientContext.java
+++ b/launchdarkly-android-client-sdk/src/main/java/com/launchdarkly/sdk/android/subsystems/ClientContext.java
@@ -22,6 +22,7 @@
* @since 3.3.0
*/
public class ClientContext {
+ private final ApplicationInfo applicationInfo;
private final LDLogger baseLogger;
private final LDConfig config;
private final DataSourceUpdateSink dataSourceUpdateSink;
@@ -53,6 +54,7 @@ public class ClientContext {
*/
public ClientContext(
String mobileKey,
+ ApplicationInfo applicationInfo,
LDLogger baseLogger,
LDConfig config,
DataSourceUpdateSink dataSourceUpdateSink,
@@ -66,6 +68,7 @@ public ClientContext(
boolean setOffline
) {
this.mobileKey = mobileKey;
+ this.applicationInfo = applicationInfo;
this.baseLogger = baseLogger;
this.config = config;
this.dataSourceUpdateSink = dataSourceUpdateSink;
@@ -79,6 +82,42 @@ public ClientContext(
this.setOffline = setOffline;
}
+ /**
+ * Deprecated constructor overload.
+ *
+ * @param mobileKey see {@link #getMobileKey()}
+ * @param baseLogger see {@link #getBaseLogger()}
+ * @param config see {@link #getConfig()}
+ * @param dataSourceUpdateSink see {@link #getDataSourceUpdateSink()}
+ * @param environmentName see {@link #getEnvironmentName()}
+ * @param evaluationReasons see {@link #isEvaluationReasons()}
+ * @param evaluationContext see {@link #getEvaluationContext()}
+ * @param http see {@link #getHttp()}
+ * @param inBackground see {@link #isInBackground()}
+ * @param serviceEndpoints see {@link #getServiceEndpoints()}
+ * @param setOffline see {@link #isSetOffline()}
+ * @deprecated use newer constructor
+ */
+ @Deprecated
+ public ClientContext(
+ String mobileKey,
+ LDLogger baseLogger,
+ LDConfig config,
+ DataSourceUpdateSink dataSourceUpdateSink,
+ String environmentName,
+ boolean evaluationReasons,
+ LDContext evaluationContext,
+ HttpConfiguration http,
+ boolean inBackground,
+ Boolean previouslyInBackground,
+ ServiceEndpoints serviceEndpoints,
+ boolean setOffline
+ ) {
+ this(mobileKey, null, baseLogger, config, dataSourceUpdateSink, environmentName,
+ evaluationReasons, evaluationContext, http, inBackground, previouslyInBackground,
+ serviceEndpoints, setOffline);
+ }
+
/**
* Deprecated constructor overload.
*
@@ -109,7 +148,7 @@ public ClientContext(
ServiceEndpoints serviceEndpoints,
boolean setOffline
) {
- this(mobileKey, baseLogger, config, dataSourceUpdateSink, environmentName,
+ this(mobileKey, null, baseLogger, config, dataSourceUpdateSink, environmentName,
evaluationReasons, evaluationContext, http, inBackground,
null,
serviceEndpoints, setOffline);
@@ -118,6 +157,7 @@ public ClientContext(
protected ClientContext(ClientContext copyFrom) {
this(
copyFrom.mobileKey,
+ copyFrom.applicationInfo,
copyFrom.baseLogger,
copyFrom.config,
copyFrom.dataSourceUpdateSink,
@@ -132,6 +172,14 @@ protected ClientContext(ClientContext copyFrom) {
);
}
+ /**
+ * The application metadata object.
+ * @return the application metadata
+ */
+ public ApplicationInfo getApplicationInfo() {
+ return applicationInfo;
+ }
+
/**
* The base logger for the SDK.
* @return a logger instance
diff --git a/launchdarkly-android-client-sdk/src/test/java/com/launchdarkly/sdk/android/HttpConfigurationBuilderTest.java b/launchdarkly-android-client-sdk/src/test/java/com/launchdarkly/sdk/android/HttpConfigurationBuilderTest.java
new file mode 100644
index 00000000..559b0711
--- /dev/null
+++ b/launchdarkly-android-client-sdk/src/test/java/com/launchdarkly/sdk/android/HttpConfigurationBuilderTest.java
@@ -0,0 +1,76 @@
+package com.launchdarkly.sdk.android;
+
+import com.launchdarkly.sdk.android.subsystems.ApplicationInfo;
+import com.launchdarkly.sdk.android.subsystems.ClientContext;
+import com.launchdarkly.sdk.android.subsystems.HttpConfiguration;
+
+import org.junit.Test;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import static com.launchdarkly.sdk.android.integrations.HttpConfigurationBuilder.DEFAULT_CONNECT_TIMEOUT_MILLIS;
+import static org.junit.Assert.assertEquals;
+
+public class HttpConfigurationBuilderTest {
+ private static final String MOBILE_KEY = "mobile-key";
+ private static final ClientContext BASIC_CONTEXT = new ClientContext(MOBILE_KEY, null, null, null, null, "",
+ false, null, null, false, null, null, false);
+
+ private static Map
+ *
+ * LDConfig config = new LDConfig.Builder()
+ * .applicationInfo(
+ * Components.applicationInfo()
+ * .applicationId("authentication-service")
+ * .applicationVersion("1.0.0")
+ * )
+ * .build();
+ *