Skip to content

Commit c32700f

Browse files
authored
Provide app keys from xml file (#414)
* Read app keys from Android string resources * Add unit tests
1 parent 852544d commit c32700f

File tree

4 files changed

+302
-1
lines changed

4 files changed

+302
-1
lines changed

AndroidSDKCore/src/main/java/com/leanplum/Leanplum.java

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
import com.leanplum.callbacks.VariablesChangedCallback;
3535
import com.leanplum.internal.APIConfig;
3636
import com.leanplum.internal.ActionManager;
37+
import com.leanplum.internal.ApiConfigLoader;
3738
import com.leanplum.internal.Constants;
3839
import com.leanplum.internal.CountAggregator;
3940
import com.leanplum.internal.FeatureFlagManager;
@@ -277,6 +278,16 @@ public static void setAppIdForProductionMode(String appId, String accessKey) {
277278
APIConfig.getInstance().setAppId(appId, accessKey);
278279
}
279280

281+
/**
282+
* Loads appId and accessKey from Android resources.
283+
*/
284+
private static void loadApiConfigFromResources() {
285+
ApiConfigLoader loader = new ApiConfigLoader(getContext());
286+
loader.loadFromResources(
287+
Leanplum::setAppIdForProductionMode,
288+
Leanplum::setAppIdForDevelopmentMode);
289+
}
290+
280291
/**
281292
* Enable screen tracking.
282293
*/
@@ -521,6 +532,11 @@ public static synchronized void start(final Context context, String userId,
521532
static synchronized void start(final Context context, final String userId,
522533
final Map<String, ?> attributes, StartCallback response, final Boolean isBackground) {
523534
try {
535+
boolean appIdNotSet = TextUtils.isEmpty(APIConfig.getInstance().appId());
536+
if (appIdNotSet) {
537+
loadApiConfigFromResources();
538+
}
539+
524540
LeanplumActivityHelper.setCurrentActivity(context);
525541

526542
// Detect if app is in background automatically if isBackground is not set.
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
/*
2+
* Copyright 2020, Leanplum, Inc. All rights reserved.
3+
*
4+
* Licensed to the Apache Software Foundation (ASF) under one
5+
* or more contributor license agreements. See the NOTICE file
6+
* distributed with this work for additional information
7+
* regarding copyright ownership. The ASF licenses this file
8+
* to you under the Apache License, Version 2.0 (the
9+
* "License"); you may not use this file except in compliance
10+
* with the License. You may obtain a copy of the License at
11+
*
12+
* http://www.apache.org/licenses/LICENSE-2.0
13+
*
14+
* Unless required by applicable law or agreed to in writing,
15+
* software distributed under the License is distributed on an
16+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17+
* KIND, either express or implied. See the License for the
18+
* specific language governing permissions and limitations
19+
* under the License.
20+
*/
21+
22+
package com.leanplum.internal;
23+
24+
import android.content.Context;
25+
import android.text.TextUtils;
26+
27+
/**
28+
* Loads appId, accessKey and type of environment from the Android string resources.
29+
*/
30+
public class ApiConfigLoader {
31+
private static final String STRING_APPID = "leanplum_app_id";
32+
private static final String STRING_PROD_KEY = "leanplum_prod_key";
33+
private static final String STRING_DEV_KEY = "leanplum_dev_key";
34+
private static final String STRING_ENV = "leanplum_environment";
35+
36+
private static final String ENV_DEV = "development";
37+
private static final String ENV_PROD = "production";
38+
39+
private Context context;
40+
41+
@FunctionalInterface
42+
public interface KeyListener {
43+
void onKeysLoaded(String appId, String accessKey);
44+
}
45+
46+
public ApiConfigLoader(Context context) {
47+
this.context = context;
48+
}
49+
50+
private String getStringResource(String key) {
51+
try {
52+
String packageName = context.getPackageName();
53+
int id = context.getResources().getIdentifier(key, "string", packageName);
54+
return context.getString(id);
55+
} catch (Throwable t) { // Resources.NotFoundException
56+
Log.e("Cannot load string for key = %s. Message = %s", key, t.getMessage());
57+
return null;
58+
}
59+
}
60+
61+
public void loadFromResources(KeyListener prodKeyListener, KeyListener devKeyListener) {
62+
String appId = getStringResource(STRING_APPID);
63+
String prodKey = getStringResource(STRING_PROD_KEY);
64+
String devKey = getStringResource(STRING_DEV_KEY);
65+
String environment = getStringResource(STRING_ENV);
66+
67+
if (TextUtils.isEmpty(appId))
68+
return;
69+
70+
boolean devEnv = ENV_DEV.equals(environment);
71+
boolean hasProdKey = !TextUtils.isEmpty(prodKey);
72+
boolean hasDevKey = !TextUtils.isEmpty(devKey);
73+
74+
if (devEnv && hasDevKey) {
75+
devKeyListener.onKeysLoaded(appId, devKey);
76+
Log.i("Using appId and accessKey from Android resources for development environment.");
77+
}
78+
else if (!devEnv && hasProdKey) {
79+
prodKeyListener.onKeysLoaded(appId, prodKey);
80+
Log.i("Using appId and accessKey from Android resources for production environment.");
81+
}
82+
}
83+
}

AndroidSDKTests/src/test/java/com/leanplum/LeanplumTest.java

Lines changed: 68 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2014, Leanplum, Inc. All rights reserved.
2+
* Copyright 2020, Leanplum, Inc. All rights reserved.
33
*
44
* Licensed to the Apache Software Foundation (ASF) under one
55
* or more contributor license agreements. See the NOTICE file
@@ -36,6 +36,7 @@
3636
import com.leanplum.callbacks.StartCallback;
3737
import com.leanplum.callbacks.VariablesChangedCallback;
3838
import com.leanplum.internal.APIConfig;
39+
import com.leanplum.internal.ApiConfigLoader;
3940
import com.leanplum.internal.CollectionUtil;
4041
import com.leanplum.internal.Constants;
4142
import com.leanplum.internal.FeatureFlagManager;
@@ -57,6 +58,7 @@
5758
import org.json.JSONObject;
5859
import org.junit.Test;
5960
import org.mockito.Mockito;
61+
import org.powermock.api.mockito.PowerMockito;
6062
import org.robolectric.RuntimeEnvironment;
6163

6264
import java.lang.reflect.Method;
@@ -1740,4 +1742,69 @@ public void variablesChanged() {
17401742
});
17411743

17421744
}
1745+
1746+
/**
1747+
* Tests if appId and accessKey are loaded from Android resources
1748+
* if they aren't presented before calling start.
1749+
*/
1750+
@Test
1751+
public void testApiConfigLoadFromResources() throws Exception {
1752+
// assure appId and accessKey are not set
1753+
TestClassUtil.setField(APIConfig.getInstance(), "appId", null);
1754+
TestClassUtil.setField(APIConfig.getInstance(), "accessKey", null);
1755+
1756+
String appId = "app_id";
1757+
String accessKey = "access_key";
1758+
1759+
class MockedLoader extends ApiConfigLoader {
1760+
private MockedLoader(Context context) {
1761+
super(context);
1762+
}
1763+
@Override
1764+
public void loadFromResources(KeyListener prodKeyListener, KeyListener devKeyListener) {
1765+
prodKeyListener.onKeysLoaded(appId, accessKey);
1766+
}
1767+
}
1768+
1769+
PowerMockito
1770+
.whenNew(ApiConfigLoader.class)
1771+
.withAnyArguments()
1772+
.thenReturn(new MockedLoader(mContext));
1773+
1774+
Leanplum.start(mContext);
1775+
1776+
assertTrue(Leanplum.hasStarted());
1777+
assertEquals(appId, APIConfig.getInstance().appId());
1778+
assertEquals(accessKey, APIConfig.getInstance().accessKey());
1779+
}
1780+
1781+
/**
1782+
* Tests if appId and accessKey are not overridden if they are presented before calling Start.
1783+
*/
1784+
@Test
1785+
public void testApiConfigNotOverridden() throws Exception {
1786+
String appId = APIConfig.getInstance().appId();
1787+
String accessKey = APIConfig.getInstance().accessKey();
1788+
1789+
class MockedLoader extends ApiConfigLoader {
1790+
private MockedLoader(Context context) {
1791+
super(context);
1792+
}
1793+
@Override
1794+
public void loadFromResources(KeyListener prodKeyListener, KeyListener devKeyListener) {
1795+
prodKeyListener.onKeysLoaded("arbitrary_app_id", "arbitrary_access_key");
1796+
}
1797+
}
1798+
1799+
PowerMockito
1800+
.whenNew(ApiConfigLoader.class)
1801+
.withAnyArguments()
1802+
.thenReturn(new MockedLoader(mContext));
1803+
1804+
Leanplum.start(mContext);
1805+
1806+
assertTrue(Leanplum.hasStarted());
1807+
assertEquals(appId, APIConfig.getInstance().appId());
1808+
assertEquals(accessKey, APIConfig.getInstance().accessKey());
1809+
}
17431810
}
Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
/*
2+
* Copyright 2020, Leanplum, Inc. All rights reserved.
3+
*
4+
* Licensed to the Apache Software Foundation (ASF) under one
5+
* or more contributor license agreements. See the NOTICE file
6+
* distributed with this work for additional information
7+
* regarding copyright ownership. The ASF licenses this file
8+
* to you under the Apache License, Version 2.0 (the
9+
* "License"); you may not use this file except in compliance
10+
* with the License. You may obtain a copy of the License at
11+
*
12+
* http://www.apache.org/licenses/LICENSE-2.0
13+
*
14+
* Unless required by applicable law or agreed to in writing,
15+
* software distributed under the License is distributed on an
16+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17+
* KIND, either express or implied. See the License for the
18+
* specific language governing permissions and limitations
19+
* under the License.
20+
*/
21+
22+
package com.leanplum.internal;
23+
24+
import static org.junit.Assert.assertEquals;
25+
import static org.junit.Assert.assertFalse;
26+
import static org.junit.Assert.assertTrue;
27+
28+
import android.content.Context;
29+
import android.content.res.Resources;
30+
import java.util.Random;
31+
import org.junit.Before;
32+
import org.junit.Test;
33+
import org.junit.runner.RunWith;
34+
import org.mockito.Mockito;
35+
import org.robolectric.RobolectricTestRunner;
36+
import org.robolectric.annotation.Config;
37+
38+
import static org.mockito.Matchers.anyString;
39+
import static org.mockito.Matchers.eq;
40+
import static org.mockito.Mockito.when;
41+
42+
@RunWith(RobolectricTestRunner.class)
43+
@Config(
44+
sdk = 16
45+
)
46+
public class ApiConfigLoaderTest {
47+
private String appId;
48+
private String accessKey;
49+
private boolean prodEnvironment;
50+
private boolean devEnvironment;
51+
52+
private Context context;
53+
private Resources resources;
54+
55+
@Before
56+
public void setup() {
57+
appId = null;
58+
accessKey = null;
59+
prodEnvironment = false;
60+
devEnvironment = false;
61+
62+
resources = Mockito.mock(Resources.class);
63+
context = Mockito.mock(Context.class);
64+
when(context.getResources()).thenReturn(resources);
65+
}
66+
67+
private void mockStringResource(String key, String value) {
68+
int resourceId = new Random().nextInt();
69+
when(resources.getIdentifier(eq(key), eq("string"), anyString())).thenReturn(resourceId);
70+
when(context.getString(resourceId)).thenReturn(value);
71+
}
72+
73+
private void callLoadMethod(ApiConfigLoader loader) {
74+
loader.loadFromResources(
75+
(appId, prodKey) -> {
76+
this.appId = appId;
77+
this.accessKey = prodKey;
78+
this.prodEnvironment = true;
79+
},
80+
(appId, devKey) -> {
81+
this.appId = appId;
82+
this.accessKey = devKey;
83+
this.devEnvironment = true;
84+
}
85+
);
86+
}
87+
88+
@Test
89+
public void testProdEnvironment() {
90+
String wantedAppId = "app_id";
91+
String wantedAccessKey = "access_key";
92+
93+
mockStringResource("leanplum_app_id", wantedAppId);
94+
mockStringResource("leanplum_prod_key", wantedAccessKey);
95+
mockStringResource("leanplum_environment", "production");
96+
97+
ApiConfigLoader loader = new ApiConfigLoader(context);
98+
callLoadMethod(loader);
99+
100+
assertTrue(prodEnvironment);
101+
assertEquals(appId, wantedAppId);
102+
assertEquals(accessKey, wantedAccessKey);
103+
}
104+
105+
@Test
106+
public void testDevEnvironment() {
107+
String wantedAppId = "app_id";
108+
String wantedAccessKey = "access_key";
109+
110+
mockStringResource("leanplum_app_id", wantedAppId);
111+
mockStringResource("leanplum_dev_key", wantedAccessKey);
112+
mockStringResource("leanplum_environment", "development");
113+
114+
ApiConfigLoader loader = new ApiConfigLoader(context);
115+
callLoadMethod(loader);
116+
117+
assertTrue(devEnvironment);
118+
assertEquals(appId, wantedAppId);
119+
assertEquals(accessKey, wantedAccessKey);
120+
}
121+
122+
@Test
123+
public void testAccessKeyMissing() {
124+
String wantedAppId = "app_id";
125+
126+
mockStringResource("leanplum_app_id", wantedAppId);
127+
mockStringResource("leanplum_environment", "development");
128+
129+
ApiConfigLoader loader = new ApiConfigLoader(context);
130+
callLoadMethod(loader);
131+
132+
assertFalse(prodEnvironment);
133+
assertFalse(devEnvironment);
134+
}
135+
}

0 commit comments

Comments
 (0)