diff --git a/README.md b/README.md index 74f7143..28a721e 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ Clickstream Android SDK supports Android 4.1 (API level 16) and later. ## Integrate SDK -**1.Include SDK** +### 1. Include SDK Add the following dependency to your `app` module's `build.gradle` file. @@ -28,7 +28,7 @@ dependencies { then sync your project, if you have problem to build your app, please check [troubleshooting](#Troubleshooting) -**2.Parameter configuration** +### 2. Parameter configuration Find the res directory under your `project/app/src/main` , and manually create a raw folder in the res directory. @@ -41,7 +41,7 @@ Download your `amplifyconfiguration.json` file from your clickstream control pla "analytics": { "plugins": { "awsClickstreamPlugin": { - "appId": "appId", + "appId": "your appId", "endpoint": "https://example.com/collect", "isCompressEvents": true, "autoFlushEventsInterval": 10000, @@ -60,16 +60,16 @@ Your `appId` and `endpoint` are already set up in it, here's an explanation of e - **autoFlushEventsInterval**: event sending interval, the default is `10s` - **isTrackAppExceptionEvents**: whether auto track exception event in app, default is `false` -**3.Initialize the SDK** +### 3. Initialize the SDK -It is recommended that you initialize the SDK in the Application `onCreate()` method. Please note that the initialization code needs to run in the main thread. +It is recommended that you initialize the SDK in your application's `onCreate()` method. Please note that the initialization code needs to run in the main thread. +#### 3.1 Initialize the SDK with default configuration ```java import software.aws.solution.clickstream.ClickstreamAnalytics; public void onCreate() { super.onCreate(); - try{ ClickstreamAnalytics.init(getApplicationContext()); Log.i("MyApp", "Initialized ClickstreamAnalytics"); @@ -78,10 +78,36 @@ public void onCreate() { } } ``` +#### 3.2 Initialize the SDK with global attributes and custom configuration +```java +import software.aws.solution.clickstream.ClickstreamAnalytics; + +public void onCreate() { + super.onCreate(); + try{ + ClickstreamAttribute globalAttributes = ClickstreamAttribute.builder() + .add("_traffic_source_name", "Summer promotion") + .add("_traffic_source_medium", "Search engine") + .build(); + ClickstreamConfiguration configuration = new ClickstreamConfiguration() + .withAppId("your appId") + .withEndpoint("http://example.com/collect") + .withLogEvents(true) + .withInitialGlobalAttributes(globalAttributes); + ClickstreamAnalytics.init(getApplicationContext(), configuration); + Log.i("MyApp", "Initialized ClickstreamAnalytics"); + } catch (AmplifyException error){ + Log.e("MyApp", "Could not initialize ClickstreamAnalytics", error); + } +} +``` +By default, we will use the configurations in `amplifyconfiguration.json` file. If you add a custom configuration, the added configuration items will override the default values. -**4.Config the SDK** +You can also add all the configuration parameters you need in the `init` method without using the `amplifyconfiguration.json` file. -After initial the SDK we can use the following code to custom configure it. +### 4. Update Configuration + +After initial the SDK we can use the following code to up configure it. ```java import software.aws.solution.clickstream.ClickstreamAnalytics; @@ -146,6 +172,8 @@ ClickstreamAnalytics.addGlobalAttributes(globalAttribute); ClickstreamAnalytics.deleteGlobalAttributes("level"); ``` +It is recommended to set global attributes when initializing the SDK, global attributes will be included in all events that occur after it is set. + #### Login and logout ```java diff --git a/build.gradle b/build.gradle index 08e5c22..17a11a2 100644 --- a/build.gradle +++ b/build.gradle @@ -66,6 +66,7 @@ ext { okhttp: 'com.squareup.okhttp3:okhttp:4.9.1', junit: 'junit:junit:4.13.2', mockito: 'org.mockito:mockito-core:4.11.0', + mockitoinline: 'org.mockito:mockito-inline:4.11.0', moco: 'com.github.dreamhead:moco-core:1.4.0', robolectric: 'org.robolectric:robolectric:4.9.2', ] diff --git a/clickstream/build.gradle b/clickstream/build.gradle index 8b3ed90..c3a31e3 100644 --- a/clickstream/build.gradle +++ b/clickstream/build.gradle @@ -25,6 +25,7 @@ dependencies { testImplementation dependency.junit testImplementation dependency.mockito + testImplementation dependency.mockitoinline testImplementation dependency.robolectric testImplementation dependency.androidx.test testImplementation dependency.moco diff --git a/clickstream/src/main/java/software/aws/solution/clickstream/AWSClickstreamPlugin.java b/clickstream/src/main/java/software/aws/solution/clickstream/AWSClickstreamPlugin.java index 072eaab..9ba13f6 100644 --- a/clickstream/src/main/java/software/aws/solution/clickstream/AWSClickstreamPlugin.java +++ b/clickstream/src/main/java/software/aws/solution/clickstream/AWSClickstreamPlugin.java @@ -37,12 +37,13 @@ import software.aws.solution.clickstream.client.Event; import java.util.Map; +import java.util.Objects; /** * The plugin implementation for Clickstream in Analytics category. */ public final class AWSClickstreamPlugin extends AnalyticsPlugin { - + static final String PLUGIN_KEY = "awsClickstreamPlugin"; private static final Log LOG = LogFactory.getLog(AWSClickstreamPlugin.class); private final Context context; private AnalyticsClient analyticsClient; @@ -63,12 +64,10 @@ public AWSClickstreamPlugin(final Context context) { @Override public void identifyUser(@NonNull String userId, @Nullable UserProfile profile) { if (userId.equals(Event.ReservedAttribute.USER_ID_UNSET)) { - if (profile instanceof ClickstreamUserAttribute) { - for (Map.Entry> entry : - ((ClickstreamUserAttribute) profile).getUserAttributes()) { - AnalyticsPropertyBehavior property = entry.getValue(); - analyticsClient.addUserAttribute(entry.getKey(), property.getValue()); - } + for (Map.Entry> entry : + ((ClickstreamUserAttribute) Objects.requireNonNull(profile)).getUserAttributes()) { + AnalyticsPropertyBehavior property = entry.getValue(); + analyticsClient.addUserAttribute(entry.getKey(), property.getValue()); } } else { analyticsClient.updateUserId(userId); @@ -114,11 +113,9 @@ public void recordEvent(@NonNull AnalyticsEventBehavior analyticsEvent) { analyticsClient.createEvent(event.getName()); if (clickstreamEvent != null) { - if (analyticsEvent.getProperties() != null) { - for (Map.Entry> entry : analyticsEvent.getProperties()) { - AnalyticsPropertyBehavior property = entry.getValue(); - clickstreamEvent.addAttribute(entry.getKey(), property.getValue()); - } + for (Map.Entry> entry : analyticsEvent.getProperties()) { + AnalyticsPropertyBehavior property = entry.getValue(); + clickstreamEvent.addAttribute(entry.getKey(), property.getValue()); } clickstreamEvent.addItems(event.getItems()); recordAnalyticsEvent(clickstreamEvent); @@ -156,7 +153,7 @@ public void flushEvents() { @NonNull @Override public String getPluginKey() { - return "awsClickstreamPlugin"; + return PLUGIN_KEY; } @Override @@ -171,31 +168,46 @@ public void configure( getPluginKey() + " under the analytics category." ); } - - AWSClickstreamPluginConfiguration.Builder configurationBuilder = - AWSClickstreamPluginConfiguration.builder(); - + ClickstreamConfiguration configuration = ClickstreamConfiguration.getDefaultConfiguration(); // Read all the data from the configuration object to be used for record event try { - configurationBuilder.withAppId(pluginConfiguration - .getString(ConfigurationKey.APP_ID.getConfigurationKey())); + configuration.withAppId(pluginConfiguration.getString(ConfigurationKey.APP_ID)); + configuration.withEndpoint(pluginConfiguration + .getString(ConfigurationKey.ENDPOINT)); - configurationBuilder.withEndpoint(pluginConfiguration - .getString(ConfigurationKey.ENDPOINT.getConfigurationKey())); - - if (pluginConfiguration.has(ConfigurationKey.SEND_EVENTS_INTERVAL.getConfigurationKey())) { - configurationBuilder.withSendEventsInterval(pluginConfiguration - .getLong(ConfigurationKey.SEND_EVENTS_INTERVAL.getConfigurationKey())); + if (pluginConfiguration.has(ConfigurationKey.SEND_EVENTS_INTERVAL)) { + configuration.withSendEventsInterval(pluginConfiguration + .getLong(ConfigurationKey.SEND_EVENTS_INTERVAL)); } - - if (pluginConfiguration.has(ConfigurationKey.COMPRESS_EVENTS.getConfigurationKey())) { - configurationBuilder.withCompressEvents( - pluginConfiguration.getBoolean(ConfigurationKey.COMPRESS_EVENTS.getConfigurationKey())); + if (pluginConfiguration.has(ConfigurationKey.IS_COMPRESS_EVENTS)) { + configuration.withCompressEvents( + pluginConfiguration.getBoolean(ConfigurationKey.IS_COMPRESS_EVENTS)); } - - if (pluginConfiguration.has(ConfigurationKey.TRACK_APP_EXCEPTION_EVENTS.getConfigurationKey())) { - configurationBuilder.withTrackAppExceptionEvents(pluginConfiguration - .getBoolean(ConfigurationKey.TRACK_APP_EXCEPTION_EVENTS.getConfigurationKey())); + if (pluginConfiguration.has(ConfigurationKey.IS_TRACK_APP_EXCEPTION_EVENTS)) { + configuration.withTrackAppExceptionEvents(pluginConfiguration + .getBoolean(ConfigurationKey.IS_TRACK_APP_EXCEPTION_EVENTS)); + } + if (pluginConfiguration.has(ConfigurationKey.IS_LOG_EVENTS)) { + configuration.withLogEvents(pluginConfiguration.getBoolean(ConfigurationKey.IS_LOG_EVENTS)); + } + if (pluginConfiguration.has(ConfigurationKey.IS_TRACK_SCREEN_VIEW_EVENTS)) { + configuration.withTrackScreenViewEvents( + pluginConfiguration.getBoolean(ConfigurationKey.IS_TRACK_SCREEN_VIEW_EVENTS)); + } + if (pluginConfiguration.has(ConfigurationKey.IS_TRACK_USER_ENGAGEMENT_EVENTS)) { + configuration.withTrackUserEngagementEvents( + pluginConfiguration.getBoolean(ConfigurationKey.IS_TRACK_USER_ENGAGEMENT_EVENTS)); + } + if (pluginConfiguration.has(ConfigurationKey.SESSION_TIMEOUT_DURATION)) { + configuration.withSessionTimeoutDuration( + pluginConfiguration.getLong(ConfigurationKey.SESSION_TIMEOUT_DURATION)); + } + if (pluginConfiguration.has(ConfigurationKey.AUTH_COOKIE)) { + configuration.withAuthCookie(pluginConfiguration.getString(ConfigurationKey.AUTH_COOKIE)); + } + if (pluginConfiguration.has(ConfigurationKey.GLOBAL_ATTRIBUTES)) { + configuration.withInitialGlobalAttributes( + (ClickstreamAttribute) pluginConfiguration.get(ConfigurationKey.GLOBAL_ATTRIBUTES)); } } catch (JSONException exception) { throw new AnalyticsException( @@ -204,16 +216,10 @@ public void configure( "Please take a look at the documentation for expected format of amplifyconfiguration.json." ); } - - AWSClickstreamPluginConfiguration clickstreamPluginConfiguration = configurationBuilder.build(); - clickstreamManager = ClickstreamManagerFactory.create( - context, - clickstreamPluginConfiguration - ); + clickstreamManager = new ClickstreamManager(context, configuration); this.analyticsClient = clickstreamManager.getAnalyticsClient(); - LOG.debug("AWSClickstreamPlugin create AutoEventSubmitter."); - autoEventSubmitter = new AutoEventSubmitter(clickstreamPluginConfiguration.getSendEventsInterval()); + autoEventSubmitter = new AutoEventSubmitter(configuration.getSendEventsInterval()); autoEventSubmitter.start(); activityLifecycleManager = new ActivityLifecycleManager(clickstreamManager); @@ -232,57 +238,20 @@ public String getVersion() { } /** - * Clickstream configuration in amplifyconfiguration.json contains following values. + * The Clickstream configuration keys. */ - public enum ConfigurationKey { - - /** - * The Clickstream appId. - */ - APP_ID("appId"), - - /** - * the Clickstream Endpoint. - */ - ENDPOINT("endpoint"), - - /** - * Time interval after which the events are automatically submitted to server. - */ - SEND_EVENTS_INTERVAL("sendEventsInterval"), - - /** - * Whether to compress events. - */ - COMPRESS_EVENTS("isCompressEvents"), - - /** - * Whether to track app exception events automatically. - */ - TRACK_APP_EXCEPTION_EVENTS("isTrackAppExceptionEvents"); - - /** - * The key this property is listed under in the config JSON. - */ - private final String configurationKey; - - /** - * Construct the enum with the config key. - * - * @param configurationKey The key this property is listed under in the config JSON. - */ - ConfigurationKey(final String configurationKey) { - this.configurationKey = configurationKey; - } - - /** - * Returns the key this property is listed under in the config JSON. - * - * @return The key as a string - */ - public String getConfigurationKey() { - return configurationKey; - } + static class ConfigurationKey { + static final String APP_ID = "appId"; + static final String ENDPOINT = "endpoint"; + static final String SEND_EVENTS_INTERVAL = "autoFlushEventsInterval"; + static final String IS_COMPRESS_EVENTS = "isCompressEvents"; + static final String IS_LOG_EVENTS = "isLogEvents"; + static final String AUTH_COOKIE = "authCookie"; + static final String SESSION_TIMEOUT_DURATION = "sessionTimeoutDuration"; + static final String IS_TRACK_APP_EXCEPTION_EVENTS = "isTrackAppExceptionEvents"; + static final String IS_TRACK_SCREEN_VIEW_EVENTS = "isTrackScreenViewEvents"; + static final String IS_TRACK_USER_ENGAGEMENT_EVENTS = "isTrackUserEngagementEvents"; + static final String GLOBAL_ATTRIBUTES = "globalAttributes"; } } diff --git a/clickstream/src/main/java/software/aws/solution/clickstream/AWSClickstreamPluginConfiguration.java b/clickstream/src/main/java/software/aws/solution/clickstream/AWSClickstreamPluginConfiguration.java deleted file mode 100644 index a36e3a4..0000000 --- a/clickstream/src/main/java/software/aws/solution/clickstream/AWSClickstreamPluginConfiguration.java +++ /dev/null @@ -1,200 +0,0 @@ -/* - * Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://aws.amazon.com/apache2.0 - * - * or in the "license" file accompanying this file. This file is distributed - * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language governing - * permissions and limitations under the License. - */ - -package software.aws.solution.clickstream; - -/** - * Configuration options for Amplify Analytics Clickstream plugin. - */ -public final class AWSClickstreamPluginConfiguration { - private static final long DEFAULT_SEND_EVENTS_INTERVAL = 10000L; - private static final long DEFAULT_CALL_TIME_OUT = 15000L; - private static final long DEFAULT_SESSION_TIME_OUT = 1800000L; - - // Clickstream configuration options - private final String appId; - private final String endpoint; - private final long sendEventsInterval; - private final long callTimeOut; - private final boolean isTrackScreenViewEvents; - private final boolean isTrackUserEngagementEvents; - private final boolean isTrackAppExceptionEvents; - private final boolean isCompressEvents; - private final long sessionTimeOut; - - private AWSClickstreamPluginConfiguration(Builder builder) { - this.appId = builder.appId; - this.isTrackScreenViewEvents = builder.isTrackScreenViewEvents; - this.isTrackUserEngagementEvents = builder.isTrackUserEngagementEvents; - this.isTrackAppExceptionEvents = builder.isTrackAppExceptionEvents; - this.callTimeOut = builder.callTimeOut; - this.sendEventsInterval = builder.sendEventsInterval; - this.endpoint = builder.endpoint; - this.isCompressEvents = builder.isCompressEvents; - this.sessionTimeOut = builder.sessionTimeOut; - } - - /** - * AppId getter. - * - * @return appId - */ - String getAppId() { - return appId; - } - - /** - * Accessor for auto event flush interval. - * - * @return auto event flush interval. - */ - long getSendEventsInterval() { - return sendEventsInterval; - } - - /** - * Accessor for http call time out. - * - * @return callTimeOut. - */ - long getCallTimeOut() { - return callTimeOut; - } - - /** - * Is auto screen view tracking enabled. - * - * @return Is auto screen view tracking enabled. - */ - boolean isTrackScreenViewEvents() { - return isTrackScreenViewEvents; - } - - /** - * Is auto user engagement tracking enabled. - * - * @return Is auto user engagement tracking enabled. - */ - boolean isTrackUserEngagementEvents() { - return isTrackUserEngagementEvents; - } - - /** - * Is auto exception tracking enabled. - * - * @return Is auto exception tracking enabled. - */ - boolean isTrackAppExceptionEvents() { - return isTrackAppExceptionEvents; - } - - /** - * Accessor for endpoint. - * - * @return The endpoint. - */ - String getEndpoint() { - return endpoint; - } - - /** - * Is compress events enabled. - * - * @return Is compress events enabled. - */ - boolean isCompressEvents() { - return isCompressEvents; - } - - /** - * Accessor for session time out. - * - * @return sessionTimeOut - */ - long getSessionTimeOut() { - return sessionTimeOut; - } - - /** - * Return a builder that can be used to construct a new instance of - * {@link AWSClickstreamPluginConfiguration}. - * - * @return An {@link Builder} instance - */ - static Builder builder() { - return new Builder(); - } - - /** - * Used for fluent construction of an immutable {@link AWSClickstreamPluginConfiguration} object. - */ - static final class Builder { - private String appId; - private String endpoint; - private long sendEventsInterval = DEFAULT_SEND_EVENTS_INTERVAL; - private final long callTimeOut = DEFAULT_CALL_TIME_OUT; - private boolean isCompressEvents = true; - private boolean isTrackScreenViewEvents = true; - private boolean isTrackUserEngagementEvents = true; - private boolean isTrackAppExceptionEvents = false; - - private long sessionTimeOut = DEFAULT_SESSION_TIME_OUT; - - Builder withAppId(final String appId) { - this.appId = appId; - return this; - } - - Builder withEndpoint(final String endpoint) { - this.endpoint = endpoint; - return this; - } - - Builder withSendEventsInterval(final long sendEventsInterval) { - this.sendEventsInterval = sendEventsInterval; - return this; - } - - Builder withCompressEvents(final boolean compressEvents) { - this.isCompressEvents = compressEvents; - return this; - } - - Builder withTrackScreenViewEvents(final boolean trackScreenViewEvents) { - this.isTrackScreenViewEvents = trackScreenViewEvents; - return this; - } - - Builder withTrackUserEngagementEvents(final boolean trackUserEngagementEvents) { - this.isTrackUserEngagementEvents = trackUserEngagementEvents; - return this; - } - - Builder withTrackAppExceptionEvents(final boolean trackAppExceptionEvents) { - this.isTrackAppExceptionEvents = trackAppExceptionEvents; - return this; - } - - Builder withSessionTimeOut(final long sessionTimeOut) { - this.sessionTimeOut = sessionTimeOut; - return this; - } - - AWSClickstreamPluginConfiguration build() { - return new AWSClickstreamPluginConfiguration(this); - } - } -} - diff --git a/clickstream/src/main/java/software/aws/solution/clickstream/ClickstreamAnalytics.java b/clickstream/src/main/java/software/aws/solution/clickstream/ClickstreamAnalytics.java index edef764..9910284 100644 --- a/clickstream/src/main/java/software/aws/solution/clickstream/ClickstreamAnalytics.java +++ b/clickstream/src/main/java/software/aws/solution/clickstream/ClickstreamAnalytics.java @@ -20,11 +20,16 @@ import com.amplifyframework.AmplifyException; import com.amplifyframework.core.Amplify; +import com.amplifyframework.core.AmplifyConfiguration; +import com.amplifyframework.core.category.CategoryConfiguration; +import com.amplifyframework.core.category.CategoryType; import com.amazonaws.logging.Log; import com.amazonaws.logging.LogFactory; +import org.json.JSONException; +import org.json.JSONObject; +import software.aws.solution.clickstream.AWSClickstreamPlugin.ConfigurationKey; import software.aws.solution.clickstream.client.AnalyticsClient; -import software.aws.solution.clickstream.client.ClickstreamConfiguration; import software.aws.solution.clickstream.client.Event.PresetEvent; import software.aws.solution.clickstream.client.Event.ReservedAttribute; import software.aws.solution.clickstream.client.util.ThreadUtil; @@ -46,11 +51,24 @@ private ClickstreamAnalytics() { * @throws AmplifyException Exception of init. */ public static void init(@NonNull Context context) throws AmplifyException { + init(context, new ClickstreamConfiguration()); + } + + /** + * Init ClickstreamAnalytics Plugin. + * + * @param context ApplicationContext + * @param configuration ClickstreamConfiguration + * @throws AmplifyException Exception of init. + */ + public static void init(@NonNull Context context, ClickstreamConfiguration configuration) + throws AmplifyException { if (ThreadUtil.notInMainThread()) { throw new AmplifyException("Clickstream SDK initialization failed", "Please initialize in the main thread"); } + AmplifyConfiguration configure = getAmplifyConfigurationObject(context, configuration); Amplify.addPlugin(new AWSClickstreamPlugin(context)); - Amplify.configure(context); + Amplify.configure(configure, context); } /** @@ -83,7 +101,7 @@ public static void flushEvents() { * * @param clickstreamAttribute the global clickstreamAttribute. */ - public static void addGlobalAttributes(ClickstreamAttribute clickstreamAttribute) { + public static void addGlobalAttributes(@NonNull ClickstreamAttribute clickstreamAttribute) { Amplify.Analytics.registerGlobalProperties(clickstreamAttribute.getAttributes()); } @@ -101,7 +119,7 @@ public static void deleteGlobalAttributes(@NonNull String... attributeName) { * * @param userProfile user */ - public static void addUserAttributes(ClickstreamUserAttribute userProfile) { + public static void addUserAttributes(@NonNull ClickstreamUserAttribute userProfile) { Amplify.Analytics.identifyUser(ReservedAttribute.USER_ID_UNSET, userProfile); } @@ -141,18 +159,86 @@ public static void disable() { } /** - * Get clickstream configuration - * please config it after initialize. + * Get clickstream configuration please config it after SDK initialize. * * @return ClickstreamConfiguration configurationF */ public static ClickstreamConfiguration getClickStreamConfiguration() { AnalyticsClient client = - ((AWSClickstreamPlugin) Amplify.Analytics.getPlugin("awsClickstreamPlugin")).getEscapeHatch(); + ((AWSClickstreamPlugin) Amplify.Analytics.getPlugin(AWSClickstreamPlugin.PLUGIN_KEY)).getEscapeHatch(); assert client != null; return client.getClickstreamConfiguration(); } + private static AmplifyConfiguration getAmplifyConfigurationObject(Context context, + ClickstreamConfiguration configuration) + throws AmplifyException { + AmplifyConfiguration amplifyConfiguration; + JSONObject configureObject; + try { + amplifyConfiguration = AmplifyConfiguration.fromConfigFile(context); + CategoryConfiguration categoryConfiguration = amplifyConfiguration.forCategoryType(CategoryType.ANALYTICS); + configureObject = categoryConfiguration.getPluginConfig(AWSClickstreamPlugin.PLUGIN_KEY); + } catch (AmplifyException exception) { + LOG.info("Clickstream SDK can not find the amplifyconfiguration.json file, " + + "The SDK will initialize using the configuration you set"); + JSONObject amplifyObject = new JSONObject(); + JSONObject analyticsObject = new JSONObject(); + JSONObject pluginsObject = new JSONObject(); + configureObject = new JSONObject(); + try { + pluginsObject.put(AWSClickstreamPlugin.PLUGIN_KEY, configureObject); + analyticsObject.put("plugins", pluginsObject); + amplifyObject.put("analytics", analyticsObject); + } catch (JSONException jsonException) { + throw new AmplifyException("JSONException", "JSONException occurred while constructing a JSON object"); + } + amplifyConfiguration = AmplifyConfiguration.fromJson(amplifyObject); + } + try { + if (configuration.getAppId() != null) { + configureObject.put(ConfigurationKey.APP_ID, configuration.getAppId()); + } + if (configuration.getEndpoint() != null) { + configureObject.put(ConfigurationKey.ENDPOINT, configuration.getEndpoint()); + } + if (configuration.isLogEvents() != null) { + configureObject.put(ConfigurationKey.IS_LOG_EVENTS, configuration.isLogEvents()); + } + if (configuration.isCompressEvents() != null) { + configureObject.put(ConfigurationKey.IS_COMPRESS_EVENTS, configuration.isCompressEvents()); + } + if (configuration.isTrackAppExceptionEvents() != null) { + configureObject.put(ConfigurationKey.IS_TRACK_APP_EXCEPTION_EVENTS, + configuration.isTrackAppExceptionEvents()); + } + if (configuration.isTrackScreenViewEvents() != null) { + configureObject.put(ConfigurationKey.IS_TRACK_SCREEN_VIEW_EVENTS, + configuration.isTrackScreenViewEvents()); + } + if (configuration.isTrackUserEngagementEvents() != null) { + configureObject.put(ConfigurationKey.IS_TRACK_USER_ENGAGEMENT_EVENTS, + configuration.isTrackUserEngagementEvents()); + } + if (configuration.getSessionTimeoutDuration() > 0) { + configureObject.put(ConfigurationKey.SESSION_TIMEOUT_DURATION, + configuration.getSessionTimeoutDuration()); + } + if (configuration.getSendEventsInterval() > 0) { + configureObject.put(ConfigurationKey.SEND_EVENTS_INTERVAL, configuration.getSendEventsInterval()); + } + if (configuration.getAuthCookie() != null) { + configureObject.put(ConfigurationKey.AUTH_COOKIE, configuration.getAuthCookie()); + } + if (configuration.getInitialGlobalAttributes() != null) { + configureObject.put(ConfigurationKey.GLOBAL_ATTRIBUTES, configuration.getInitialGlobalAttributes()); + } + } catch (Exception exception) { + LOG.error("Parse JSON exception, you may need to check your initial configuration"); + } + return amplifyConfiguration; + } + /** * Item attributes. */ diff --git a/clickstream/src/main/java/software/aws/solution/clickstream/client/ClickstreamConfiguration.java b/clickstream/src/main/java/software/aws/solution/clickstream/ClickstreamConfiguration.java similarity index 78% rename from clickstream/src/main/java/software/aws/solution/clickstream/client/ClickstreamConfiguration.java rename to clickstream/src/main/java/software/aws/solution/clickstream/ClickstreamConfiguration.java index ee1b4c4..66bf5a0 100644 --- a/clickstream/src/main/java/software/aws/solution/clickstream/client/ClickstreamConfiguration.java +++ b/clickstream/src/main/java/software/aws/solution/clickstream/ClickstreamConfiguration.java @@ -13,9 +13,7 @@ * permissions and limitations under the License. */ -package software.aws.solution.clickstream.client; - -import android.content.Context; +package software.aws.solution.clickstream; import okhttp3.Dns; @@ -23,44 +21,40 @@ * Clickstream Configuration. */ public class ClickstreamConfiguration { - - private Context context; + private static final long DEFAULT_SEND_EVENTS_INTERVAL = 10000L; + private static final long DEFAULT_CALL_TIME_OUT = 15000L; + private static final long DEFAULT_SESSION_TIME_OUT = 1800000L; private String appId; private String endpoint; private Dns dns; private long sendEventsInterval; private long callTimeOut; - private boolean isCompressEvents; - private boolean isTrackScreenViewEvents; - private boolean isTrackUserEngagementEvents; - private boolean isTrackAppExceptionEvents; - private boolean isLogEvents; + private Boolean isCompressEvents; + private Boolean isTrackScreenViewEvents; + private Boolean isTrackUserEngagementEvents; + private Boolean isTrackAppExceptionEvents; + private Boolean isLogEvents; private String authCookie; private long sessionTimeoutDuration; + private ClickstreamAttribute initialGlobalAttributes; /** - * Create an {@link ClickstreamConfiguration} object with the specified parameters. - * - * @param context the android context object. - * @param appId the Clickstream appId. - * @param endpoint the Clickstream endpoint. + * Create an {@link ClickstreamConfiguration} object. */ - public ClickstreamConfiguration(final Context context, final String appId, final String endpoint) { - this.context = context; - this.appId = appId; - this.endpoint = endpoint; + public ClickstreamConfiguration() { } - /** - * The Android Context. Interface to global information about an application environment. - * This is an abstract class whose implementation is provided by the Android system. - * It allows access to application-specific resources and classes, as well as up-calls for application-level - * operations such as launching activities, broadcasting and receiving intents, etc. - * - * @return the Android Context object. - */ - public Context getAppContext() { - return this.context; + static ClickstreamConfiguration getDefaultConfiguration() { + ClickstreamConfiguration configuration = new ClickstreamConfiguration(); + configuration.sendEventsInterval = DEFAULT_SEND_EVENTS_INTERVAL; + configuration.sessionTimeoutDuration = DEFAULT_SESSION_TIME_OUT; + configuration.callTimeOut = DEFAULT_CALL_TIME_OUT; + configuration.isCompressEvents = true; + configuration.isTrackScreenViewEvents = true; + configuration.isTrackUserEngagementEvents = true; + configuration.isTrackAppExceptionEvents = false; + configuration.isLogEvents = false; + return configuration; } /** @@ -153,23 +147,12 @@ public Long getCallTimeOut() { return this.callTimeOut; } - /** - * The http call time out. - * - * @param callTimeOut call time out. - * @return the current ClickstreamConfiguration instance. - */ - public ClickstreamConfiguration withCallTimeOut(final long callTimeOut) { - this.callTimeOut = callTimeOut; - return this; - } - /** * Is compress events. * * @return Is compress events. */ - public boolean isCompressEvents() { + public Boolean isCompressEvents() { return this.isCompressEvents; } @@ -189,7 +172,7 @@ public ClickstreamConfiguration withCompressEvents(final boolean compressEvents) * * @return Is track appLifecycle events. */ - public boolean isTrackScreenViewEvents() { + public Boolean isTrackScreenViewEvents() { return this.isTrackScreenViewEvents; } @@ -198,7 +181,7 @@ public boolean isTrackScreenViewEvents() { * * @return Is track user engagement events. */ - public boolean isTrackUserEngagementEvents() { + public Boolean isTrackUserEngagementEvents() { return this.isTrackUserEngagementEvents; } @@ -229,7 +212,7 @@ public ClickstreamConfiguration withTrackUserEngagementEvents(final boolean isTr * * @return Is track app exception events. */ - public boolean isTrackAppExceptionEvents() { + public Boolean isTrackAppExceptionEvents() { return this.isTrackAppExceptionEvents; } @@ -249,7 +232,7 @@ public ClickstreamConfiguration withTrackAppExceptionEvents(final boolean isTrac * * @return Is log events json when record event. */ - public boolean isLogEvents() { + public Boolean isLogEvents() { return this.isLogEvents; } @@ -303,5 +286,25 @@ public ClickstreamConfiguration withSessionTimeoutDuration(final long sessionTim this.sessionTimeoutDuration = sessionTimeoutDuration; return this; } + + /** + * Set the global attribute when initialize the SDK. + * + * @param attribute global attributes. + * @return the current ClickstreamConfiguration instance. + */ + public ClickstreamConfiguration withInitialGlobalAttributes(ClickstreamAttribute attribute) { + this.initialGlobalAttributes = attribute; + return this; + } + + /** + * Get the global attribute. + * + * @return the global attribute instance. + */ + public ClickstreamAttribute getInitialGlobalAttributes() { + return this.initialGlobalAttributes; + } } diff --git a/clickstream/src/main/java/software/aws/solution/clickstream/ClickstreamManagerFactory.java b/clickstream/src/main/java/software/aws/solution/clickstream/ClickstreamManagerFactory.java deleted file mode 100644 index 90c54d5..0000000 --- a/clickstream/src/main/java/software/aws/solution/clickstream/ClickstreamManagerFactory.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://aws.amazon.com/apache2.0 - * - * or in the "license" file accompanying this file. This file is distributed - * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language governing - * permissions and limitations under the License. - */ - -package software.aws.solution.clickstream; - -import android.content.Context; - -import software.aws.solution.clickstream.client.ClickstreamConfiguration; -import software.aws.solution.clickstream.client.ClickstreamManager; - -/** - * Factory class to vend out clickstream client. - */ -final class ClickstreamManagerFactory { - private ClickstreamManagerFactory() { - } - - static ClickstreamManager create(Context context, - AWSClickstreamPluginConfiguration clickstreamPluginConfiguration) { - // Construct configuration using information from the configure method - ClickstreamConfiguration clickstreamConfiguration = - new ClickstreamConfiguration(context, clickstreamPluginConfiguration.getAppId(), - clickstreamPluginConfiguration.getEndpoint()) - .withSendEventsInterval(clickstreamPluginConfiguration.getSendEventsInterval()) - .withCallTimeOut(clickstreamPluginConfiguration.getCallTimeOut()) - .withCompressEvents(clickstreamPluginConfiguration.isCompressEvents()) - .withTrackScreenViewEvents(clickstreamPluginConfiguration.isTrackScreenViewEvents()) - .withTrackUserEngagementEvents(clickstreamPluginConfiguration.isTrackUserEngagementEvents()) - .withTrackAppExceptionEvents(clickstreamPluginConfiguration.isTrackAppExceptionEvents()) - .withSessionTimeoutDuration(clickstreamPluginConfiguration.getSessionTimeOut()); - - return new ClickstreamManager(clickstreamConfiguration); - } -} - diff --git a/clickstream/src/main/java/software/aws/solution/clickstream/client/AnalyticsClient.java b/clickstream/src/main/java/software/aws/solution/clickstream/client/AnalyticsClient.java index b4d83f5..13c426d 100644 --- a/clickstream/src/main/java/software/aws/solution/clickstream/client/AnalyticsClient.java +++ b/clickstream/src/main/java/software/aws/solution/clickstream/client/AnalyticsClient.java @@ -22,6 +22,7 @@ import com.amazonaws.logging.LogFactory; import org.json.JSONException; import org.json.JSONObject; +import software.aws.solution.clickstream.ClickstreamConfiguration; import software.aws.solution.clickstream.client.util.PreferencesUtil; import software.aws.solution.clickstream.client.util.StringUtil; diff --git a/clickstream/src/main/java/software/aws/solution/clickstream/client/AnalyticsEvent.java b/clickstream/src/main/java/software/aws/solution/clickstream/client/AnalyticsEvent.java index d29394b..f173e90 100644 --- a/clickstream/src/main/java/software/aws/solution/clickstream/client/AnalyticsEvent.java +++ b/clickstream/src/main/java/software/aws/solution/clickstream/client/AnalyticsEvent.java @@ -78,10 +78,8 @@ private AnalyticsEvent(final String eventId, final String eventType, final Map kvp : globalAttributes.entrySet()) { - this.addGlobalAttribute(kvp.getKey(), kvp.getValue()); - } + for (final Map.Entry kvp : globalAttributes.entrySet()) { + this.addGlobalAttribute(kvp.getKey(), kvp.getValue()); } this.userAttributes = userAttributes; } diff --git a/clickstream/src/main/java/software/aws/solution/clickstream/client/ClickstreamContext.java b/clickstream/src/main/java/software/aws/solution/clickstream/client/ClickstreamContext.java index f1af320..7241804 100644 --- a/clickstream/src/main/java/software/aws/solution/clickstream/client/ClickstreamContext.java +++ b/clickstream/src/main/java/software/aws/solution/clickstream/client/ClickstreamContext.java @@ -17,6 +17,7 @@ import android.content.Context; +import software.aws.solution.clickstream.ClickstreamConfiguration; import software.aws.solution.clickstream.client.system.AndroidSystem; import software.aws.solution.clickstream.client.uniqueid.SharedPrefsDeviceIdService; diff --git a/clickstream/src/main/java/software/aws/solution/clickstream/client/ClickstreamManager.java b/clickstream/src/main/java/software/aws/solution/clickstream/client/ClickstreamManager.java index c9a51e3..0faef7b 100644 --- a/clickstream/src/main/java/software/aws/solution/clickstream/client/ClickstreamManager.java +++ b/clickstream/src/main/java/software/aws/solution/clickstream/client/ClickstreamManager.java @@ -18,13 +18,17 @@ import android.content.Context; import androidx.annotation.NonNull; +import com.amplifyframework.analytics.AnalyticsPropertyBehavior; + import com.amazonaws.AmazonClientException; import com.amazonaws.logging.Log; import com.amazonaws.logging.LogFactory; import software.aws.solution.clickstream.AWSClickstreamPlugin; import software.aws.solution.clickstream.BuildConfig; +import software.aws.solution.clickstream.ClickstreamConfiguration; import java.util.Locale; +import java.util.Map; /** * Clickstream Manager. @@ -44,13 +48,12 @@ public class ClickstreamManager { /** * Constructor. * - * @param config {@link ClickstreamConfiguration} object. + * @param config {@link Context} object. + * @param appContext {@link ClickstreamConfiguration} object. * @throws AmazonClientException When RuntimeException occur. - * @throws NullPointerException When the config or appId is null. */ - public ClickstreamManager(@NonNull final ClickstreamConfiguration config) { + public ClickstreamManager(@NonNull Context appContext, @NonNull final ClickstreamConfiguration config) { try { - final Context appContext = config.getAppContext(); this.clickstreamContext = new ClickstreamContext(appContext, SDK_INFO, config); this.analyticsClient = new AnalyticsClient(this.clickstreamContext); this.clickstreamContext.setAnalyticsClient(this.analyticsClient); @@ -61,6 +64,7 @@ public ClickstreamManager(@NonNull final ClickstreamConfiguration config) { exceptionHandler = ClickstreamExceptionHandler.init(this.clickstreamContext); enableTrackAppException(); } + setInitialGlobalAttributes(this.analyticsClient, config); LOG.debug(String.format(Locale.US, "Clickstream SDK(%s) initialization successfully completed", BuildConfig.VERSION_NAME)); this.autoRecordEventClient.handleAppStart(); @@ -72,6 +76,17 @@ public ClickstreamManager(@NonNull final ClickstreamConfiguration config) { } } + private void setInitialGlobalAttributes(AnalyticsClient analyticsClient, ClickstreamConfiguration config) { + if (config.getInitialGlobalAttributes() != null) { + for (Map.Entry> entry : config.getInitialGlobalAttributes() + .getAttributes()) { + AnalyticsPropertyBehavior property = entry.getValue(); + analyticsClient.addGlobalAttribute(entry.getKey(), property.getValue()); + } + config.withInitialGlobalAttributes(null); + } + } + /** * handle session start after SDK initialize. */ diff --git a/clickstream/src/main/java/software/aws/solution/clickstream/client/network/NetRequest.java b/clickstream/src/main/java/software/aws/solution/clickstream/client/network/NetRequest.java index bf62ab0..f495ff8 100644 --- a/clickstream/src/main/java/software/aws/solution/clickstream/client/network/NetRequest.java +++ b/clickstream/src/main/java/software/aws/solution/clickstream/client/network/NetRequest.java @@ -21,7 +21,7 @@ import com.amazonaws.logging.Log; import com.amazonaws.logging.LogFactory; -import software.aws.solution.clickstream.client.ClickstreamConfiguration; +import software.aws.solution.clickstream.ClickstreamConfiguration; import software.aws.solution.clickstream.client.util.StringUtil; import java.io.IOException; diff --git a/clickstream/src/test/java/software/aws/solution/clickstream/AnalyticsClientTest.java b/clickstream/src/test/java/software/aws/solution/clickstream/AnalyticsClientTest.java index d6a4a47..82a8bf2 100644 --- a/clickstream/src/test/java/software/aws/solution/clickstream/AnalyticsClientTest.java +++ b/clickstream/src/test/java/software/aws/solution/clickstream/AnalyticsClientTest.java @@ -68,13 +68,11 @@ public class AnalyticsClientTest { @Before public void setup() throws Exception { Context context = ApplicationProvider.getApplicationContext(); - AWSClickstreamPluginConfiguration.Builder configurationBuilder = AWSClickstreamPluginConfiguration.builder(); - configurationBuilder.withAppId("demo-app") + ClickstreamConfiguration configuration = ClickstreamConfiguration.getDefaultConfiguration() + .withAppId("demo-app") .withEndpoint("http://example.com/collect") .withSendEventsInterval(10000); - AWSClickstreamPluginConfiguration clickstreamPluginConfiguration = configurationBuilder.build(); - ClickstreamManager clickstreamManager = - ClickstreamManagerFactory.create(context, clickstreamPluginConfiguration); + ClickstreamManager clickstreamManager = new ClickstreamManager(context, configuration); analyticsClient = clickstreamManager.getAnalyticsClient(); globalAttributes = (Map) ReflectUtil.getFiled(analyticsClient, "globalAttributes"); @@ -361,9 +359,11 @@ public void testUserAttributeForStorage() throws Exception { analyticsClient.updateUserAttribute(); Context context = ApplicationProvider.getApplicationContext(); - ClickstreamManager clickstreamManager = - ClickstreamManagerFactory.create(context, AWSClickstreamPluginConfiguration - .builder().withAppId("demo-app").withEndpoint("http://example.com/collect").build()); + ClickstreamManager clickstreamManager = new ClickstreamManager(context, + ClickstreamConfiguration.getDefaultConfiguration() + .withAppId("demo-app") + .withEndpoint("http://example.com/collect") + ); JSONObject userAttributesFromStorage = (JSONObject) ReflectUtil.getFiled(clickstreamManager.getAnalyticsClient(), "allUserAttributes"); Assert.assertEquals(6, userAttributesFromStorage.length()); diff --git a/clickstream/src/test/java/software/aws/solution/clickstream/AnalyticsEventTest.java b/clickstream/src/test/java/software/aws/solution/clickstream/AnalyticsEventTest.java index 7b90cdf..13f7a56 100644 --- a/clickstream/src/test/java/software/aws/solution/clickstream/AnalyticsEventTest.java +++ b/clickstream/src/test/java/software/aws/solution/clickstream/AnalyticsEventTest.java @@ -51,13 +51,10 @@ public void init() { * @return AnalyticsClient */ public static AnalyticsClient getAnalyticsClient() { - AWSClickstreamPluginConfiguration.Builder configurationBuilder = AWSClickstreamPluginConfiguration.builder(); - configurationBuilder.withEndpoint( - "http://click-serve-HCJIDWGD3S9F-1166279006.ap-southeast-1.elb.amazonaws.com/collect"); - AWSClickstreamPluginConfiguration clickstreamPluginConfiguration = configurationBuilder.build(); + ClickstreamConfiguration configuration = ClickstreamConfiguration.getDefaultConfiguration() + .withEndpoint("http://example.com/collect"); Context context = ApplicationProvider.getApplicationContext(); - ClickstreamManager clickstreamManager = - ClickstreamManagerFactory.create(context, clickstreamPluginConfiguration); + ClickstreamManager clickstreamManager = new ClickstreamManager(context, configuration); return clickstreamManager.getAnalyticsClient(); } @@ -249,4 +246,15 @@ public void testReachedTheMaxLengthOfCustomItemAttributeValue() throws JSONExcep Assert.assertEquals(0, eventItems.length()); } + /** + * test add null and invalid value. + */ + @Test + public void testAddNullAndInvalidValue() { + AnalyticsEvent event = analyticsClient.createEvent("testEvent"); + ClickstreamItem[] items = new ClickstreamItem[0]; + event.addItems(null); + event.addItems(items); + event.addAttribute(null, null); + } } diff --git a/clickstream/src/test/java/software/aws/solution/clickstream/AutoRecordEventClientTest.java b/clickstream/src/test/java/software/aws/solution/clickstream/AutoRecordEventClientTest.java index 309dc6f..e3ce94b 100644 --- a/clickstream/src/test/java/software/aws/solution/clickstream/AutoRecordEventClientTest.java +++ b/clickstream/src/test/java/software/aws/solution/clickstream/AutoRecordEventClientTest.java @@ -83,14 +83,13 @@ public class AutoRecordEventClientTest { public void setup() { Context context = ApplicationProvider.getApplicationContext(); dbUtil = new ClickstreamDBUtil(context); - AWSClickstreamPluginConfiguration.Builder configurationBuilder = AWSClickstreamPluginConfiguration.builder(); - configurationBuilder.withAppId("demo-app") + ClickstreamConfiguration configuration = ClickstreamConfiguration.getDefaultConfiguration(); + configuration.withAppId("demo-app") .withEndpoint("http://example.com/collect") .withSendEventsInterval(10000) .withTrackScreenViewEvents(true) .withTrackUserEngagementEvents(true); - AWSClickstreamPluginConfiguration clickstreamPluginConfiguration = configurationBuilder.build(); - clickstreamManager = ClickstreamManagerFactory.create(context, clickstreamPluginConfiguration); + clickstreamManager = new ClickstreamManager(context, configuration); client = clickstreamManager.getAutoRecordEventClient(); clickstreamContext = clickstreamManager.getClickstreamContext(); callbacks = new ActivityLifecycleManager(clickstreamManager); diff --git a/clickstream/src/test/java/software/aws/solution/clickstream/EventRecorderTest.java b/clickstream/src/test/java/software/aws/solution/clickstream/EventRecorderTest.java index 0137697..93d067f 100644 --- a/clickstream/src/test/java/software/aws/solution/clickstream/EventRecorderTest.java +++ b/clickstream/src/test/java/software/aws/solution/clickstream/EventRecorderTest.java @@ -36,7 +36,6 @@ import org.robolectric.annotation.Config; import software.aws.solution.clickstream.client.AnalyticsClient; import software.aws.solution.clickstream.client.AnalyticsEvent; -import software.aws.solution.clickstream.client.ClickstreamConfiguration; import software.aws.solution.clickstream.client.ClickstreamContext; import software.aws.solution.clickstream.client.ClickstreamManager; import software.aws.solution.clickstream.client.Event; @@ -81,12 +80,10 @@ public class EventRecorderTest { private static Runner runner; private static String jsonString; private static HttpServer server; - private ClickstreamDBUtil dbUtil; private EventRecorder eventRecorder; private ClickstreamContext clickstreamContext; private AnalyticsEvent event; - private ExecutorService executorService; private Log log; @@ -119,13 +116,11 @@ public void setup() throws Exception { Context context = ApplicationProvider.getApplicationContext(); dbUtil = new ClickstreamDBUtil(context); - AWSClickstreamPluginConfiguration.Builder configurationBuilder = AWSClickstreamPluginConfiguration.builder(); - configurationBuilder.withAppId("demo-app") + ClickstreamConfiguration configuration = ClickstreamConfiguration.getDefaultConfiguration() + .withAppId("demo-app") .withEndpoint("http://example.com/collect") .withSendEventsInterval(10000); - AWSClickstreamPluginConfiguration clickstreamPluginConfiguration = configurationBuilder.build(); - ClickstreamManager clickstreamManager = - ClickstreamManagerFactory.create(context, clickstreamPluginConfiguration); + ClickstreamManager clickstreamManager = new ClickstreamManager(context, configuration); AnalyticsClient analyticsClient = clickstreamManager.getAnalyticsClient(); event = analyticsClient.createEvent("testEvent"); clickstreamContext = clickstreamManager.getClickstreamContext(); @@ -438,7 +433,7 @@ public void testSubmitAllEventForOneRequest() throws Exception { assertEquals(20, dbUtil.getTotalNumber()); eventRecorder.submitEvents(); assertTrue(((ThreadPoolExecutor) executorService).getActiveCount() < 2); - Thread.sleep(1500); + Thread.sleep(2000); assertEquals(0, dbUtil.getTotalNumber()); } @@ -459,7 +454,7 @@ public void testSubmitPartOfEventForMultiRequest() throws Exception { assertEquals(40, dbUtil.getTotalNumber()); eventRecorder.submitEvents(); assertEquals(1, ((ThreadPoolExecutor) executorService).getTaskCount()); - Thread.sleep(1500); + Thread.sleep(2000); verify(log, times(3)).debug("Send event number: 12"); verify(log).debug("Reached maxSubmissions: 3"); assertEquals(4, dbUtil.getTotalNumber()); @@ -484,7 +479,7 @@ public void testSubmitAllEventForMultiRequest() throws Exception { eventRecorder.submitEvents(); assertEquals(2, ((ThreadPoolExecutor) executorService).getTaskCount()); assertTrue(((ThreadPoolExecutor) executorService).getActiveCount() < 2); - Thread.sleep(1500); + Thread.sleep(2000); verify(log, times(3)).debug("Send event number: 12"); verify(log).debug("Reached maxSubmissions: 3"); verify(log).debug("Send event number: 4"); @@ -511,7 +506,7 @@ public void testTimerThreeTimesSubmitAllEventForMultiRequest() throws Exception Thread.sleep(100); eventRecorder.submitEvents(); } - Thread.sleep(1500); + Thread.sleep(2000); verify(log, times(3)).debug("Send event number: 12"); verify(log).debug("Reached maxSubmissions: 3"); verify(log).debug("Send event number: 4"); diff --git a/clickstream/src/test/java/software/aws/solution/clickstream/ExceptionHandlerTest.java b/clickstream/src/test/java/software/aws/solution/clickstream/ExceptionHandlerTest.java index ae0f370..de0241c 100644 --- a/clickstream/src/test/java/software/aws/solution/clickstream/ExceptionHandlerTest.java +++ b/clickstream/src/test/java/software/aws/solution/clickstream/ExceptionHandlerTest.java @@ -54,12 +54,12 @@ public void setup() throws Exception { }); dbUtil = new ClickstreamDBUtil(context); - AWSClickstreamPluginConfiguration.Builder configurationBuilder = AWSClickstreamPluginConfiguration.builder(); - configurationBuilder.withAppId("demo-app") + ClickstreamConfiguration configuration = ClickstreamConfiguration.getDefaultConfiguration() + .withAppId("demo-app") .withTrackAppExceptionEvents(true) .withEndpoint("http://example.com/collect") .withSendEventsInterval(10000); - clickstreamManager = ClickstreamManagerFactory.create(context, configurationBuilder.build()); + clickstreamManager = new ClickstreamManager(context, configuration); dbUtil.deleteBatchEvents(3); } diff --git a/clickstream/src/test/java/software/aws/solution/clickstream/InitClientTest.java b/clickstream/src/test/java/software/aws/solution/clickstream/InitClientTest.java index 6f9d56f..c76a850 100644 --- a/clickstream/src/test/java/software/aws/solution/clickstream/InitClientTest.java +++ b/clickstream/src/test/java/software/aws/solution/clickstream/InitClientTest.java @@ -20,43 +20,78 @@ import androidx.test.core.app.ApplicationProvider; import com.amplifyframework.AmplifyException; +import com.amplifyframework.analytics.AnalyticsException; +import com.amplifyframework.core.Amplify; +import com.amplifyframework.core.Resources; +import org.json.JSONObject; +import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.MockedStatic; +import org.mockito.Mockito; import org.robolectric.RobolectricTestRunner; import software.aws.solution.clickstream.client.AnalyticsClient; import software.aws.solution.clickstream.client.ClickstreamContext; import software.aws.solution.clickstream.client.ClickstreamManager; +import software.aws.solution.clickstream.util.ReflectUtil; +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.util.Objects; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.when; @RunWith(RobolectricTestRunner.class) public class InitClientTest { - private AWSClickstreamPluginConfiguration clickstreamPluginConfiguration = null; + private ClickstreamConfiguration clickstreamPluginConfiguration = null; /** * init the clickstreamPluginConfiguration. + * + * @throws Exception Exception */ @Before - public void init() { - AWSClickstreamPluginConfiguration.Builder configurationBuilder = AWSClickstreamPluginConfiguration.builder(); - configurationBuilder.withEndpoint( - "http://click-serve-HCJIDWGD3S9F-1166279006.ap-southeast-1.elb.amazonaws.com/collect") + public void init() throws Exception { + ReflectUtil.makeAmplifyNotConfigured(); + clickstreamPluginConfiguration = ClickstreamConfiguration.getDefaultConfiguration(); + clickstreamPluginConfiguration.withEndpoint( + "http://example.com/collect") .withSendEventsInterval(15000); - clickstreamPluginConfiguration = configurationBuilder.build(); } + /** + * test invoke ClickstreamAnalytics constructor will throw UnsupportedOperationException. + * + * @throws Exception Exception + */ + @Test + public void testNewConstructorException() throws Exception { + try { + Constructor constructor = ClickstreamAnalytics.class.getDeclaredConstructor(); + constructor.setAccessible(true); + constructor.newInstance(); + } catch (InvocationTargetException exception) { + Throwable cause = exception.getCause(); + Assert.assertTrue(cause instanceof UnsupportedOperationException); + } + } + + /** * test the init process. * with the clickstreamPluginConfiguration to init analyticsClient and clickstreamContext. */ @Test - public void initClientParam() { + public void testInitClientParam() { Context context = ApplicationProvider.getApplicationContext(); - ClickstreamManager clickstreamManager = - ClickstreamManagerFactory.create(context, clickstreamPluginConfiguration); + ClickstreamManager clickstreamManager = new ClickstreamManager(context, clickstreamPluginConfiguration); AnalyticsClient analyticsClient = clickstreamManager.getAnalyticsClient(); ClickstreamContext clickstreamContext = clickstreamManager.getClickstreamContext(); @@ -64,7 +99,7 @@ public void initClientParam() { Assert.assertNotNull(clickstreamContext); Assert.assertEquals(clickstreamContext.getClickstreamConfiguration().getEndpoint(), - "http://click-serve-HCJIDWGD3S9F-1166279006.ap-southeast-1.elb.amazonaws.com/collect"); + "http://example.com/collect"); Assert.assertEquals(clickstreamContext.getClickstreamConfiguration().getSendEventsInterval(), 15000); Assert.assertTrue(clickstreamContext.getClickstreamConfiguration().isCompressEvents()); Assert.assertTrue(clickstreamContext.getClickstreamConfiguration().isTrackScreenViewEvents()); @@ -75,22 +110,157 @@ public void initClientParam() { } /** - * Attempting to call {@link ClickstreamAnalytics#init(android.app.Application)} - * without a generated configuration file throws an AmplifyException. + * Attempting to call {@link ClickstreamAnalytics#init(Context)} + * without a null Context throws an NullPointerException. * * @throws Exception if the premise of the test is incorrect */ - @Test(expected = AmplifyException.class) - public void testMissingConfigurationFileThrowsAmplifyException() throws Exception { + @Test(expected = NullPointerException.class) + public void testContextIsNullWhenInitialize() throws Exception { Activity activity = mock(Activity.class); ClickstreamAnalytics.init(activity.getApplication()); } + /** + * Attempting to call {@link ClickstreamAnalytics#init(Context)} + * and test AWSClickstreamPlugin. + * + * @throws Exception if the premise of the test is incorrect + */ + @Test + public void testPluginAfterSDKInit() throws Exception { + Context context = ApplicationProvider.getApplicationContext(); + ClickstreamAnalytics.init(context); + AWSClickstreamPlugin plugin = (AWSClickstreamPlugin) Amplify.Analytics.getPlugin("awsClickstreamPlugin"); + Assert.assertEquals(plugin.getVersion(), BuildConfig.VERSION_NAME); + try { + plugin.configure(null, context); + } catch (AnalyticsException exception) { + exception.printStackTrace(); + } + } + + /** + * Test Plugin with JSONException. + * + * @throws Exception if the premise of the test is incorrect + */ + @Test + public void testPluginWithJSONException() throws Exception { + Context context = ApplicationProvider.getApplicationContext(); + ClickstreamAnalytics.init(context); + AWSClickstreamPlugin plugin = (AWSClickstreamPlugin) Amplify.Analytics.getPlugin("awsClickstreamPlugin"); + try { + JSONObject configure = new JSONObject(); + plugin.configure(configure, context); + } catch (AnalyticsException exception) { + Assert.assertEquals("No value for appId", Objects.requireNonNull(exception.getCause()).getMessage()); + } + } + + /** + * Attempting to call {@link ClickstreamAnalytics#init(Context, ClickstreamConfiguration)} + * without ClickstreamConfigurations. + */ + @Test + public void testInitSDKWithDefaultConfigurations() { + try { + ClickstreamAnalytics.init(ApplicationProvider.getApplicationContext(), new ClickstreamConfiguration()); + } catch (AmplifyException exception) { + Assert.fail(); + } + } + + /** + * Attempting to call {@link ClickstreamAnalytics#init(Context, ClickstreamConfiguration)} + * without amplifyconfiguration.json file. + */ + @Test + public void testInitSDKWithoutConfigurationFile() { + try { + MockedStatic mockedResources = Mockito.mockStatic(Resources.class); + mockedResources.when(() -> Resources.getRawResourceId(any(Context.class), eq("amplifyconfiguration"))) + .thenThrow(new Resources.ResourceLoadingException("no amplifyconfiguration file", null)); + ClickstreamConfiguration configuration = new ClickstreamConfiguration() + .withAppId("test123") + .withEndpoint("http://example.com/collect123"); + ClickstreamAnalytics.init(ApplicationProvider.getApplicationContext(), configuration); + ClickstreamConfiguration config = ClickstreamAnalytics.getClickStreamConfiguration(); + Assert.assertEquals("test123", config.getAppId()); + Assert.assertEquals("http://example.com/collect123", config.getEndpoint()); + mockedResources.reset(); + mockedResources.clearInvocations(); + mockedResources.close(); + } catch (Exception exception) { + Assert.fail("test failed with exception:" + exception.getMessage()); + } + } + + /** + * Attempting to call {@link ClickstreamAnalytics#init(Context, ClickstreamConfiguration)} + * with all ClickstreamConfigurations. + */ + @Test + public void testInitSDKWithAllConfigurations() { + ClickstreamAttribute globalAttributes = ClickstreamAttribute.builder() + .add("testKey", "testValue") + .add("intKey", 12) + .add("boolKey", true) + .add("doubleKey", 23.22) + .build(); + ClickstreamConfiguration configuration = new ClickstreamConfiguration() + .withAppId("test123") + .withEndpoint("http://example.com/collect123") + .withLogEvents(true) + .withCompressEvents(false) + .withTrackAppExceptionEvents(false) + .withTrackUserEngagementEvents(false) + .withTrackScreenViewEvents(false) + .withSendEventsInterval(12000) + .withSessionTimeoutDuration(100000) + .withAuthCookie("testAutoCookie") + .withInitialGlobalAttributes(globalAttributes); + try { + Context context = ApplicationProvider.getApplicationContext(); + ClickstreamAnalytics.init(context, configuration); + ClickstreamConfiguration config = ClickstreamAnalytics.getClickStreamConfiguration(); + Assert.assertEquals("test123", config.getAppId()); + Assert.assertEquals("http://example.com/collect123", config.getEndpoint()); + Assert.assertEquals(true, config.isLogEvents()); + Assert.assertEquals(false, config.isCompressEvents()); + Assert.assertEquals(false, config.isTrackAppExceptionEvents()); + Assert.assertEquals(false, config.isTrackUserEngagementEvents()); + Assert.assertEquals(false, config.isTrackScreenViewEvents()); + Assert.assertEquals(12000, config.getSendEventsInterval()); + Assert.assertEquals(100000, config.getSessionTimeoutDuration()); + Assert.assertEquals("testAutoCookie", config.getAuthCookie()); + } catch (Exception exception) { + Assert.fail(); + } + } + + /** + * Attempting to call {@link ClickstreamAnalytics#init(Context, ClickstreamConfiguration)} + * with JSONException. + * + * @throws Exception exception + */ + @Test + public void testInitSDKWithException() throws Exception { + ClickstreamConfiguration configuration = spy(new ClickstreamConfiguration()); + configuration.withAppId("test123") + .withEndpoint("http://example.com/collect123"); + when(configuration.getAppId()).thenThrow(new RuntimeException("Test RuntimeException")); + ClickstreamAnalytics.init(ApplicationProvider.getApplicationContext(), configuration); + } + /** * test init SDK not in main thread. + * + * @throws Exception Exception for thread sleep. */ @Test - public void testInitSDKNotInMainThreadThrowsAmplifyException() { + public void testInitSDKNotInMainThreadThrowsAmplifyException() throws Exception { new Thread(() -> { Activity activity = mock(Activity.class); try { @@ -101,5 +271,16 @@ public void testInitSDKNotInMainThreadThrowsAmplifyException() { Assert.assertEquals("Please initialize in the main thread", exception.getRecoverySuggestion()); } }).start(); + Thread.sleep(500); + } + + /** + * the tear down method for test. + * + * @throws Exception exception + */ + @After + public void tearDown() throws Exception { + ReflectUtil.makeAmplifyNotConfigured(); } } diff --git a/clickstream/src/test/java/software/aws/solution/clickstream/IntegrationTest.java b/clickstream/src/test/java/software/aws/solution/clickstream/IntegrationTest.java index cba0c38..79ac38a 100644 --- a/clickstream/src/test/java/software/aws/solution/clickstream/IntegrationTest.java +++ b/clickstream/src/test/java/software/aws/solution/clickstream/IntegrationTest.java @@ -40,7 +40,6 @@ import org.junit.runner.RunWith; import org.robolectric.RobolectricTestRunner; import software.aws.solution.clickstream.client.AnalyticsClient; -import software.aws.solution.clickstream.client.ClickstreamConfiguration; import software.aws.solution.clickstream.client.ClickstreamContext; import software.aws.solution.clickstream.client.Event; import software.aws.solution.clickstream.client.EventRecorder; @@ -62,7 +61,6 @@ import static org.junit.Assert.assertEquals; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyLong; -import static org.mockito.Mockito.atLeast; import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; @@ -107,25 +105,18 @@ public static void beforeClass() { */ @Before public void setup() throws Exception { - Boolean isConfigured = (Boolean) ReflectUtil.invokeSuperMethod(Amplify.Analytics, "isConfigured"); - if (!isConfigured) { - Context context = ApplicationProvider.getApplicationContext(); - application = mock(Application.class); - plugin = new AWSClickstreamPlugin(application); - Amplify.addPlugin(plugin); - Amplify.configure(context); - } else { - plugin = (AWSClickstreamPlugin) Amplify.Analytics.getPlugin("awsClickstreamPlugin"); - application = (Application) ReflectUtil.getFiled(plugin, "context"); - } + ReflectUtil.makeAmplifyNotConfigured(); + Context context = ApplicationProvider.getApplicationContext(); + application = mock(Application.class); + plugin = new AWSClickstreamPlugin(application); + Amplify.addPlugin(plugin); + Amplify.configure(context); analyticsClient = plugin.getEscapeHatch(); assert analyticsClient != null; eventRecorder = (EventRecorder) ReflectUtil.getFiled(analyticsClient, "eventRecorder"); dbUtil = (ClickstreamDBUtil) ReflectUtil.getFiled(eventRecorder, "dbUtil"); - if (!isConfigured) { - assertEquals(3, dbUtil.getTotalNumber()); - dbUtil.deleteBatchEvents(3); - } + assertEquals(3, dbUtil.getTotalNumber()); + dbUtil.deleteBatchEvents(3); } /** @@ -148,13 +139,15 @@ public void testRecordEventWithName() { public void testRecordEventWithInvalidName() throws Exception { executeBackground(); ClickstreamAnalytics.recordEvent("01TestEvent"); + ClickstreamEvent invalidNameEvent = ClickstreamEvent.builder().name("02TestEvent").build(); + ClickstreamAnalytics.recordEvent(invalidNameEvent); try (Cursor cursor = dbUtil.queryAllEvents()) { cursor.moveToFirst(); String eventString = cursor.getString(2); JSONObject jsonObject = new JSONObject(eventString); assertEquals(Event.PresetEvent.CLICKSTREAM_ERROR, jsonObject.getString("event_type")); } - assertEquals(1, dbUtil.getTotalNumber()); + assertEquals(2, dbUtil.getTotalNumber()); } /** @@ -516,7 +509,7 @@ public void testRecordEventWithSubmitterTwice() throws Exception { ClickstreamAnalytics.recordEvent(event); } assertEquals(20, dbUtil.getTotalNumber()); - Thread.sleep(1000); + Thread.sleep(1500); assertEquals(0, dbUtil.getTotalNumber()); } @@ -629,7 +622,7 @@ public void testCustomDnsResolutionTimeoutFail() throws Exception { dns.setIsResolutionTimeout(false); setHttpRequestTimeOut(15L); ClickstreamAnalytics.flushEvents(); - Thread.sleep(1000); + Thread.sleep(1500); assertEquals(0, dbUtil.getTotalNumber()); } @@ -655,7 +648,7 @@ public void testCustomDnsForUnKnowHostFail() throws Exception { dns.setIsUnKnowHost(false); ClickstreamAnalytics.flushEvents(); - Thread.sleep(1000); + Thread.sleep(1500); assertEquals(0, dbUtil.getTotalNumber()); } @@ -676,7 +669,7 @@ public void testSetAuthCookieSuccess() throws Exception { ClickstreamAnalytics.recordEvent("testRecordEventForAuth"); assertEquals(1, dbUtil.getTotalNumber()); ClickstreamAnalytics.flushEvents(); - Thread.sleep(1000); + Thread.sleep(1500); assertEquals(0, dbUtil.getTotalNumber()); } @@ -702,7 +695,7 @@ public void testSetAuthCookieFail() throws Exception { ClickstreamAnalytics.getClickStreamConfiguration().withAuthCookie("testCookie"); ClickstreamAnalytics.flushEvents(); - Thread.sleep(1000); + Thread.sleep(1500); assertEquals(0, dbUtil.getTotalNumber()); } @@ -794,7 +787,7 @@ public void testDisableAndEnableActivityLifecycle() throws Exception { ActivityLifecycleManager lifecycleManager = (ActivityLifecycleManager) ReflectUtil.getFiled(plugin, "activityLifecycleManager"); ClickstreamAnalytics.disable(); - verify(application, atLeast(1)).unregisterActivityLifecycleCallbacks(lifecycleManager); + verify(application, atLeastOnce()).unregisterActivityLifecycleCallbacks(lifecycleManager); ClickstreamAnalytics.enable(); } @@ -865,6 +858,7 @@ private void setHttpRequestTimeOut(long timeOutSecond) throws Exception { * * @throws Exception exception */ + @SuppressWarnings("unchecked") @After public void tearDown() throws Exception { dbUtil.deleteBatchEvents(1000); @@ -876,6 +870,7 @@ public void tearDown() throws Exception { ReflectUtil.modifyFiled(analyticsClient, "simpleUserAttributes", new JSONObject()); ReflectUtil.modifyFiled(analyticsClient, "allUserAttributes", new JSONObject()); globalAttribute.clear(); + ReflectUtil.makeAmplifyNotConfigured(); } /** @@ -885,5 +880,4 @@ public void tearDown() throws Exception { public static void afterClass() { runner.stop(); } - } diff --git a/clickstream/src/test/java/software/aws/solution/clickstream/SessionClientTest.java b/clickstream/src/test/java/software/aws/solution/clickstream/SessionClientTest.java index b21aa6d..675ff78 100644 --- a/clickstream/src/test/java/software/aws/solution/clickstream/SessionClientTest.java +++ b/clickstream/src/test/java/software/aws/solution/clickstream/SessionClientTest.java @@ -48,14 +48,12 @@ public class SessionClientTest { public void setup() { Context context = ApplicationProvider.getApplicationContext(); - AWSClickstreamPluginConfiguration.Builder configurationBuilder = AWSClickstreamPluginConfiguration.builder(); - configurationBuilder.withAppId("demo-app") + ClickstreamConfiguration configuration = ClickstreamConfiguration.getDefaultConfiguration() + .withAppId("demo-app") .withEndpoint("http://example.com/collect") .withSendEventsInterval(10000) - .withSessionTimeOut(1800000L); - AWSClickstreamPluginConfiguration clickstreamPluginConfiguration = configurationBuilder.build(); - ClickstreamManager clickstreamManager = - ClickstreamManagerFactory.create(context, clickstreamPluginConfiguration); + .withSessionTimeoutDuration(1800000L); + ClickstreamManager clickstreamManager = new ClickstreamManager(context, configuration); clickstreamContext = clickstreamManager.getClickstreamContext(); analyticsClient = clickstreamManager.getAnalyticsClient(); client = new SessionClient(clickstreamContext); diff --git a/clickstream/src/test/java/software/aws/solution/clickstream/db/DBUtilTest.java b/clickstream/src/test/java/software/aws/solution/clickstream/db/DBUtilTest.java index f865694..7735ae5 100644 --- a/clickstream/src/test/java/software/aws/solution/clickstream/db/DBUtilTest.java +++ b/clickstream/src/test/java/software/aws/solution/clickstream/db/DBUtilTest.java @@ -129,7 +129,7 @@ public void testGetTotalDbSize() { assertNotNull(c); assertEquals(c.getCount(), 2); c.close(); - Assert.assertEquals(dbUtil.getTotalSize(), eventLength1 + eventLength2); + Assert.assertTrue(dbUtil.getTotalSize() - (eventLength1 + eventLength2) < 10); } /** diff --git a/clickstream/src/test/java/software/aws/solution/clickstream/util/ReflectUtil.java b/clickstream/src/test/java/software/aws/solution/clickstream/util/ReflectUtil.java index 4e0fbf0..d9af2f8 100644 --- a/clickstream/src/test/java/software/aws/solution/clickstream/util/ReflectUtil.java +++ b/clickstream/src/test/java/software/aws/solution/clickstream/util/ReflectUtil.java @@ -15,11 +15,17 @@ package software.aws.solution.clickstream.util; +import com.amplifyframework.analytics.AnalyticsPlugin; +import com.amplifyframework.core.Amplify; +import com.amplifyframework.util.UserAgent; + import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.Objects; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicReference; /** * reflect Util for test. @@ -124,4 +130,46 @@ public static Object newInstance(Class clazz, Object... params) throws Except declaredConstructor.setAccessible(true); return declaredConstructor.newInstance(params); } + + /** + * Method for make Amplify analytics plugin Not configured. + * + * @throws Exception exception. + */ + @SuppressWarnings({"unchecked", "Enum"}) + public static void makeAmplifyNotConfigured() throws Exception { + // remove plugin + if ((Boolean) ReflectUtil.invokeSuperMethod(Amplify.Analytics, "isConfigured")) { + try { + AnalyticsPlugin plugin = Amplify.Analytics.getPlugin("awsClickstreamPlugin"); + Amplify.Analytics.removePlugin(plugin); + } catch (IllegalStateException exception) { + exception.printStackTrace(); + } + } + // change the CONFIGURATION_LOCK + Field configurationLockField = Amplify.class.getDeclaredField("CONFIGURATION_LOCK"); + configurationLockField.setAccessible(true); + AtomicBoolean configurationLock = (AtomicBoolean) configurationLockField.get(null); + assert configurationLock != null; + configurationLock.set(false); + + // Set UserAgent to null + Field field = UserAgent.class.getDeclaredField("instance"); + field.setAccessible(true); + field.set(UserAgent.class, null); + + // set Analytics not configured + Class categoryClass = Amplify.Analytics.getClass().getSuperclass(); + assert categoryClass != null; + Field stateField = categoryClass.getDeclaredField("state"); + stateField.setAccessible(true); + AtomicReference state = (AtomicReference) stateField.get(Amplify.Analytics); + Class stateEnumClass = Class.forName(categoryClass.getName() + "$State"); + + Enum notConfiguredEnumConstant = (Enum) stateEnumClass.cast(Enum.valueOf( + stateEnumClass.asSubclass(Enum.class), "NOT_CONFIGURED")); + assert state != null; + state.set(notConfiguredEnumConstant); + } }