Skip to content

Commit 55a75a1

Browse files
Oasis 435 (#115)
Add OptimizelyDefaultAttributes class that has a static member to create a default attribute list with android os version, sdk version, and app version. Also, added OptimizelyDefaultAttributesTest JUnit test. Differential Revision: https://phabricator.optimizely.com/D16084
1 parent 078fbb1 commit 55a75a1

File tree

5 files changed

+203
-8
lines changed

5 files changed

+203
-8
lines changed
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
/****************************************************************************
2+
* Copyright 2017, Optimizely, Inc. and contributors *
3+
* *
4+
* Licensed under the Apache License, Version 2.0 (the "License"); *
5+
* you may not use this file except in compliance with the License. *
6+
* You may obtain a copy of the License at *
7+
* *
8+
* http://www.apache.org/licenses/LICENSE-2.0 *
9+
* *
10+
* Unless required by applicable law or agreed to in writing, software *
11+
* distributed under the License is distributed on an "AS IS" BASIS, *
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. *
13+
* See the License for the specific language governing permissions and *
14+
* limitations under the License. *
15+
***************************************************************************/
16+
package com.optimizely.ab.android.sdk;
17+
18+
import java.util.HashMap;
19+
import java.util.Map;
20+
import android.content.Context;
21+
import android.content.pm.PackageInfo;
22+
import android.support.test.runner.AndroidJUnit4;
23+
24+
import org.junit.After;
25+
import org.junit.Before;
26+
import org.junit.Test;
27+
import org.junit.runner.RunWith;
28+
import org.slf4j.Logger;
29+
30+
import static org.mockito.Matchers.any;
31+
import static org.mockito.Matchers.eq;
32+
import static org.mockito.Mockito.mock;
33+
import static org.mockito.Mockito.verify;
34+
import static org.mockito.Mockito.when;
35+
36+
import static org.junit.Assert.*;
37+
38+
/**
39+
* Tests for {@link OptimizelyDefaultAttributes}
40+
*/
41+
@RunWith(AndroidJUnit4.class)
42+
public class OptimizelyDefaultAttributesTest {
43+
private Logger logger;
44+
45+
@Before
46+
public void setUp() throws Exception {
47+
logger = mock(Logger.class);
48+
}
49+
50+
@After
51+
public void tearDown() throws Exception {
52+
53+
}
54+
55+
@Test
56+
public void buildDefaultAttributesMap() throws Exception {
57+
Context context = mock(Context.class);
58+
Context appContext = mock(Context.class);
59+
when(context.getApplicationContext()).thenReturn(appContext);
60+
when(appContext.getPackageName()).thenReturn("com.optly");
61+
62+
Map<String, String> defaultAttributes = OptimizelyDefaultAttributes.buildDefaultAttributesMap(context, logger);
63+
64+
assertEquals(defaultAttributes.size(), 4);
65+
}
66+
67+
}

android-sdk/src/main/java/com/optimizely/ab/android/sdk/OptimizelyClient.java

Lines changed: 48 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
import org.slf4j.Logger;
3131

3232
import java.util.Collections;
33+
import java.util.HashMap;
3334
import java.util.Map;
3435

3536
/**
@@ -46,10 +47,51 @@ public class OptimizelyClient {
4647
private final Logger logger;
4748

4849
@Nullable private Optimizely optimizely;
50+
@Nullable private Map<String, String> defaultAttributes;
4951

5052
OptimizelyClient(@Nullable Optimizely optimizely, @NonNull Logger logger) {
5153
this.optimizely = optimizely;
5254
this.logger = logger;
55+
/*
56+
OptimizelyManager is initialized with an OptimizelyClient with a null optimizely property:
57+
https://github.com/optimizely/android-sdk/blob/master/android-sdk/src/main/java/com/optimizely/ab/android/sdk/OptimizelyManager.java#L63
58+
optimizely will remain null until OptimizelyManager#initialize has been called, so isValid checks for that. Otherwise apps would crash if
59+
the public methods here were called before initialize.
60+
So, we start with an empty map of default attributes until the manager is initialized.
61+
*/
62+
defaultAttributes = Collections.EMPTY_MAP;
63+
}
64+
65+
/**
66+
* Set default attributes to a non null attribute map.
67+
* This is set by the Optimizely manager and includes things like os version and sdk version.
68+
* @param attrs a map of default attributes.
69+
*/
70+
protected void setDefaultAttributes(@NonNull Map<String, String> attrs) {
71+
this.defaultAttributes = attrs;
72+
}
73+
74+
/**
75+
* Return the default attributes map
76+
* @return the map of default attributes
77+
*/
78+
public @NonNull Map<String, String> getDefaultAttributes() {
79+
return this.defaultAttributes;
80+
}
81+
82+
/**
83+
* Get the default attributes and combine them with the attributes passed in.
84+
* The attributes passed in take precedence over the default attributes. So, you can override default attributes.
85+
* @param attrs attributes that will be combined with default attributes.
86+
* @return a new map of both the default attributes and attributes passed in.
87+
*/
88+
private Map<String, String> getAllAttributes(@NonNull Map<String, String> attrs) {
89+
Map<String,String> combinedMap = new HashMap<String,String>(defaultAttributes);
90+
91+
// this essentially overrides defaultAttributes if the attrs passed in have the same key.
92+
combinedMap.putAll(attrs);
93+
94+
return combinedMap;
5395
}
5496

5597
/**
@@ -83,7 +125,7 @@ public class OptimizelyClient {
83125
@NonNull String userId,
84126
@NonNull Map<String, String> attributes) {
85127
if (isValid()) {
86-
return optimizely.activate(experimentKey, userId, attributes);
128+
return optimizely.activate(experimentKey, userId, getAllAttributes(attributes));
87129
} else {
88130
logger.warn("Optimizely is not initialized, could not activate experiment {} for user {} " +
89131
"with attributes", experimentKey, userId);
@@ -140,7 +182,7 @@ public void track(@NonNull String eventName,
140182
@NonNull String userId,
141183
@NonNull Map<String, String> attributes) throws UnknownEventTypeException {
142184
if (isValid()) {
143-
optimizely.track(eventName, userId, attributes);
185+
optimizely.track(eventName, userId, getAllAttributes(attributes));
144186

145187
} else {
146188
logger.warn("Optimizely is not initialized, could not track event {} for user {} with attributes",
@@ -160,7 +202,7 @@ public void track(@NonNull String eventName,
160202
@NonNull Map<String, String> attributes,
161203
@NonNull Map<String, ?> eventTags) throws UnknownEventTypeException {
162204
if (isValid()) {
163-
optimizely.track(eventName, userId, attributes, eventTags);
205+
optimizely.track(eventName, userId, getAllAttributes(attributes), eventTags);
164206

165207
} else {
166208
logger.warn("Optimizely is not initialized, could not track event {} for user {}" +
@@ -200,7 +242,7 @@ public void track(@NonNull String eventName,
200242
@NonNull Map<String, String> attributes,
201243
long eventValue) {
202244
if (isValid()) {
203-
optimizely.track(eventName, userId, attributes, eventValue);
245+
optimizely.track(eventName, userId, getAllAttributes(attributes), eventValue);
204246
} else {
205247
logger.warn("Optimizely is not initialized, could not track event {} for user {}" +
206248
" with value {} and attributes", eventName, userId, eventValue);
@@ -234,7 +276,7 @@ public void track(@NonNull String eventName,
234276
@NonNull Map<String, String> attributes,
235277
boolean activateExperiment) {
236278
if (isValid()) {
237-
return optimizely.getVariableString(variableKey, userId, attributes,
279+
return optimizely.getVariableString(variableKey, userId, getAllAttributes(attributes),
238280
activateExperiment);
239281
} else {
240282
logger.warn("Optimizely is not initialized, could not get live variable {} " +
@@ -383,7 +425,7 @@ public void track(@NonNull String eventName,
383425
@NonNull String userId,
384426
@NonNull Map<String, String> attributes) {
385427
if (isValid()) {
386-
return optimizely.getVariation(experimentKey, userId, attributes);
428+
return optimizely.getVariation(experimentKey, userId, getAllAttributes(attributes));
387429
} else {
388430
logger.warn("Optimizely is not initialized, could not get variation for experiment {} " +
389431
"for user {} with attributes", experimentKey, userId);
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
/****************************************************************************
2+
* Copyright 2016, Optimizely, Inc. and contributors *
3+
* *
4+
* Licensed under the Apache License, Version 2.0 (the "License"); *
5+
* you may not use this file except in compliance with the License. *
6+
* You may obtain a copy of the License at *
7+
* *
8+
* http://www.apache.org/licenses/LICENSE-2.0 *
9+
* *
10+
* Unless required by applicable law or agreed to in writing, software *
11+
* distributed under the License is distributed on an "AS IS" BASIS, *
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. *
13+
* See the License for the specific language governing permissions and *
14+
* limitations under the License. *
15+
***************************************************************************/
16+
17+
package com.optimizely.ab.android.sdk;
18+
19+
import java.util.HashMap;
20+
import java.util.Map;
21+
import android.content.Context;
22+
import android.content.pm.PackageInfo;
23+
24+
import org.slf4j.Logger;
25+
26+
/**
27+
* Class to encapsulate default attributes that will be added to attributes passed in
28+
* by the OptimizelyClient.
29+
*/
30+
public class OptimizelyDefaultAttributes {
31+
32+
static private final String DEVICE_MODEL_KEY = "optimizely_android_device_model";
33+
static private final String SDK_VERSION_KEY = "optimizely_android_sdk_version";
34+
static private final String OS_VERSION_KEY = "optimizely_android_os_version";
35+
static private final String APP_VERSION_KEY = "optimizely_android_app_version";
36+
37+
/**
38+
* Builds the default attributes lists which includes the device model, sdk version, app version,
39+
* and the os version.
40+
*
41+
* @param context context used to get the app version information.
42+
* @param logger logger passed in for logging any warnings.
43+
* @return a map that has the default attributes.
44+
*/
45+
static Map<String, String> buildDefaultAttributesMap(Context context, Logger logger) {
46+
String androidDeviceModel = android.os.Build.MODEL;
47+
String androidOSVersion = android.os.Build.VERSION.RELEASE;
48+
int androidSdkVersion = 0;
49+
String androidSdkVersionName = "";
50+
String androidAppVersionName = "";
51+
int androidAppVersion = 0;
52+
53+
// In case there is some problem with accessing the BuildConfig file....
54+
try {
55+
androidSdkVersion = BuildConfig.VERSION_CODE;
56+
androidSdkVersionName = BuildConfig.VERSION_NAME;
57+
}
58+
catch (Exception e) {
59+
logger.warn("Error getting BuildConfig version code and version name");
60+
}
61+
62+
try {
63+
PackageInfo pInfo = context.getApplicationContext().getPackageManager().getPackageInfo(
64+
context.getApplicationContext().getPackageName(), 0);
65+
androidAppVersionName = pInfo.versionName;
66+
androidAppVersion = pInfo.versionCode;
67+
}
68+
catch (Exception e) {
69+
logger.warn("Error getting app version from context.", e);
70+
}
71+
72+
73+
Map<String, String> attrMap = new HashMap<String, String>();
74+
75+
attrMap.put(DEVICE_MODEL_KEY, androidDeviceModel);
76+
attrMap.put(SDK_VERSION_KEY, androidSdkVersionName);
77+
attrMap.put(OS_VERSION_KEY, androidOSVersion);
78+
String appVersion = androidAppVersionName + Integer.toString(androidAppVersion);
79+
attrMap.put(APP_VERSION_KEY, appVersion);
80+
81+
return attrMap;
82+
}
83+
}

android-sdk/src/main/java/com/optimizely/ab/android/sdk/OptimizelyManager.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import android.content.Context;
2525
import android.content.Intent;
2626
import android.content.ServiceConnection;
27+
import android.content.pm.PackageInfo;
2728
import android.content.res.Resources;
2829
import android.os.AsyncTask;
2930
import android.os.Build;
@@ -87,6 +88,7 @@ public class OptimizelyManager {
8788
this.dataFileDownloadIntervalTimeUnit = dataFileDownloadIntervalTimeUnit;
8889
this.executor = executor;
8990
this.logger = logger;
91+
9092
}
9193

9294
@NonNull
@@ -362,6 +364,7 @@ protected void onPostExecute(UserProfile userProfile) {
362364

363365
try {
364366
OptimizelyManager.this.optimizelyClient = buildOptimizely(context, dataFile, userProfile);
367+
optimizelyClient.setDefaultAttributes(OptimizelyDefaultAttributes.buildDefaultAttributesMap(context, logger));
365368
OptimizelyManager.this.userProfile = userProfile;
366369
logger.info("Sending Optimizely instance to listener");
367370

build.gradle

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ buildscript {
2727
} else {
2828
rootProject.ext.bintray_user = ''
2929
rootProject.ext.bintray_api_key = ''
30-
rootProject.ext.version_name= ''
30+
rootProject.ext.version_name= '1.3.1'
3131
}
3232
repositories {
3333
jcenter()
@@ -92,4 +92,4 @@ testAllModulesTravis.dependsOn(':android-sdk:connectedAndroidTest', ':android-sd
9292
':event-handler:connectedAndroidTest', ':event-handler:test',
9393
':user-profile:connectedAndroidTest', ':shared:connectedAndroidTest')
9494

95-
testAllModules.dependsOn('testAllModulesTravis', ':test-app:connectedAndroidTest')
95+
testAllModules.dependsOn('testAllModulesTravis', ':test-app:connectedAndroidTest')

0 commit comments

Comments
 (0)