Skip to content

Commit 65a57bc

Browse files
make android sdk configurable for logger, EventHandler, DatafileHandler, and ErrorHandler (#118)
* initial commit to refactor datafile-handler into its own package. * fix all the unit tests and gradle build version name. * update so that default attributes is always passed in * refactor out any dependencies with android-sdk. use reflection to load classes and call methods for default modules. THIS IS A WORK IN PROGRESS * make sure tests are working using reflection * update to not use reflection for tests. cleanup manager. create a data file handler interface which will be the sole way to interact with the datafile-handler package * create default DatafileHandler. create AndroidUserProfileService interface and default implementation derives from it * refactor to allow datafile handler to be used. along with android user profile service and event handler. next add build options * fix event handler * remove the evidence of reflection used.:) * add tests for DatafileHandler. cleanup tests * cleanup unit tests * fix broken espresso test. we stop the service in the app right now * rename everything to Datafile from DataFile * cleanup comments and use DatafileService.EXTRA_PROJECT_ID * cleanup comments * allow override of datafilehandler, logger, event handler, and error handler. * fix elliot comments. * remove empty application tag from android-sdk manifest * refactor background cache so that it is enabled and disabled with background updates for data files. * refactor to remove AndroidUserProfileService interface in place of UserProfileService and cleanup background watchers cache * refactor to use -1 dispatch interval to not do backgrounding * refactor out completeInject * fix unit tests after refactor * Update and add licenses * Rename dataFile to datafile Remove time units for intervals * remove connection change from itent filter * Rename DatafileHandlerDefault to DefaultDatafileHandler * Rename OptlyEventHandler to DefaultEventHandler * Rename DefaultAndroidUserProfileService to DefaultUserProfileService * slight refactor for creating user profile service and better java docs * cleanup and fix unit tests * some cleanup. canidate for 1.4.0-alpha * update change log * fix version in change log * fix changelog message * intents are still defined in the appropriate manifest. but, intent filters have all been taken out and must be declared in the application manifest if wanted * update changelog * change java core version to release 1.7.0
1 parent 85505f8 commit 65a57bc

File tree

51 files changed

+1879
-804
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

51 files changed

+1879
-804
lines changed

CHANGELOG.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,21 @@
11
# Optimizely Android X SDK Changelog
2+
### 1.4.0-alpha
3+
July 11, 2017
4+
5+
- Allow configure background tasks to run or not.
6+
7+
*Bug Fixes*
8+
9+
- Close cursor on SQLlite.
10+
11+
*Breaking Changes*
12+
13+
- Must include intent filter for EventRescheduler and DatafileRescheduler in the application manifest if the devloper wants to use them (see the test-app manifest for an example).
14+
- Pass context into OptimizelyManager.Builder.
15+
- UserProfileService added.
16+
- Background processes are not running by default.
17+
- Various handlers (EventHandler, DatafileHandler, ErrorHandler) can be overridden.
18+
219
### 1.3.1
320
April 25, 2017
421

android-sdk/build.gradle

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,8 +57,11 @@ android {
5757
}
5858

5959
dependencies {
60+
compile project(':shared')
61+
compile project(':datafile-handler')
6062
compile project(':event-handler')
6163
compile project(':user-profile')
64+
compile "com.optimizely.ab:core-api:$java_core_ver"
6265
provided "com.android.support:support-annotations:$support_annotations_ver"
6366

6467
testCompile "junit:junit:$junit_ver"
@@ -79,7 +82,7 @@ dependencies {
7982

8083
uploadArchives {
8184
dependsOn = [':android-sdk:clean', ':android-sdk:releaseJavadocJar', ':android-sdk:releaseSourcesJar']
82-
shouldRunAfter = [':event-handler:uploadArchives', ':user-profile:uploadArchives']
85+
shouldRunAfter = [':datafile-handler:uploadArchives', ':event-handler:uploadArchives', ':user-profile:uploadArchives']
8386
repositories {
8487
mavenDeployer {
8588
repository(url: "https://api.bintray.com/maven/optimizely/optimizely/optimizely-sdk-android") {

android-sdk/src/androidTest/java/com/optimizely/ab/android/sdk/OptimizelyManagerTest.java

Lines changed: 77 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -16,18 +16,28 @@
1616

1717
package com.optimizely.ab.android.sdk;
1818

19+
import android.app.AlarmManager;
1920
import android.content.Context;
2021
import android.content.Intent;
22+
import android.content.pm.PackageInfo;
23+
import android.content.pm.PackageManager;
2124
import android.os.Build;
2225
import android.support.annotation.RequiresApi;
2326
import android.support.test.espresso.core.deps.guava.util.concurrent.ListeningExecutorService;
2427
import android.support.test.espresso.core.deps.guava.util.concurrent.MoreExecutors;
2528
import android.support.test.runner.AndroidJUnit4;
2629

30+
import com.optimizely.ab.android.datafile_handler.DatafileHandler;
31+
import com.optimizely.ab.android.datafile_handler.DefaultDatafileHandler;
32+
import com.optimizely.ab.android.datafile_handler.DatafileService;
33+
import com.optimizely.ab.android.event_handler.DefaultEventHandler;
2734
import com.optimizely.ab.android.shared.ServiceScheduler;
28-
import com.optimizely.ab.android.user_profile.AndroidUserProfileService;
35+
import com.optimizely.ab.android.user_profile.DefaultUserProfileService;
2936
import com.optimizely.ab.config.parser.ConfigParseException;
3037

38+
import com.optimizely.ab.bucketing.UserProfileService;
39+
import com.optimizely.ab.event.EventHandler;
40+
3141
import org.junit.Before;
3242
import org.junit.Test;
3343
import org.junit.runner.RunWith;
@@ -58,7 +68,7 @@ public class OptimizelyManagerTest {
5868
private Logger logger;
5969
private OptimizelyManager optimizelyManager;
6070

61-
private String minDataFile = "{\n" +
71+
private String minDatafile = "{\n" +
6272
"experiments: [ ],\n" +
6373
"version: \"2\",\n" +
6474
"audiences: [ ],\n" +
@@ -74,7 +84,10 @@ public class OptimizelyManagerTest {
7484
public void setup() {
7585
logger = mock(Logger.class);
7686
executor = MoreExecutors.newDirectExecutorService();
77-
optimizelyManager = new OptimizelyManager(testProjectId, 1L, TimeUnit.HOURS, 1L, TimeUnit.HOURS, executor, logger);
87+
DatafileHandler datafileHandler = mock(DefaultDatafileHandler.class);
88+
EventHandler eventHandler = mock(DefaultEventHandler.class);
89+
optimizelyManager = new OptimizelyManager(testProjectId, logger, 3600L, datafileHandler, null, 3600L,
90+
eventHandler, null);
7891
}
7992

8093
@SuppressWarnings("WrongConstant")
@@ -87,15 +100,11 @@ public void initialize() {
87100
when(appContext.getPackageName()).thenReturn("com.optly");
88101
ArgumentCaptor<Intent> captor = ArgumentCaptor.forClass(Intent.class);
89102

90-
optimizelyManager.initialize(context, startListener);
103+
optimizelyManager.initialize(appContext, startListener);
91104

92105
assertNotNull(optimizelyManager.getOptimizelyStartListener());
93-
assertNotNull(optimizelyManager.getDataFileServiceConnection());
94-
95-
verify(appContext).bindService(captor.capture(), any(OptimizelyManager.DataFileServiceConnection.class), eq(Context.BIND_AUTO_CREATE));
106+
assertNotNull(optimizelyManager.getDatafileHandler());
96107

97-
Intent intent = captor.getValue();
98-
assertTrue(intent.getComponent().getShortClassName().contains("DataFileService"));
99108
}
100109

101110
@Test
@@ -143,34 +152,28 @@ public void stop() {
143152
Context appContext = mock(Context.class);
144153
when(context.getApplicationContext()).thenReturn(appContext);
145154

146-
OptimizelyManager.DataFileServiceConnection dataFileServiceConnection = new OptimizelyManager.DataFileServiceConnection(optimizelyManager, context);
147-
dataFileServiceConnection.setBound(true);
148-
optimizelyManager.setDataFileServiceConnection(dataFileServiceConnection);
155+
optimizelyManager.getDatafileHandler().downloadDatafile(context, optimizelyManager.getProjectId(), null);
149156

150157
optimizelyManager.stop(context);
151158

152159
assertNull(optimizelyManager.getOptimizelyStartListener());
153-
verify(appContext).unbindService(dataFileServiceConnection);
154160
}
155161

156162
@RequiresApi(api = Build.VERSION_CODES.HONEYCOMB)
157163
@Test
158164
public void injectOptimizely() {
159165
Context context = mock(Context.class);
160-
AndroidUserProfileService userProfileService = mock(AndroidUserProfileService.class);
161-
ServiceScheduler serviceScheduler = mock(ServiceScheduler.class);
162-
ArgumentCaptor<Intent> captor = ArgumentCaptor.forClass(Intent.class);
166+
UserProfileService userProfileService = mock(UserProfileService.class);
163167
OptimizelyStartListener startListener = mock(OptimizelyStartListener.class);
168+
164169
optimizelyManager.setOptimizelyStartListener(startListener);
165-
optimizelyManager.injectOptimizely(context, userProfileService, serviceScheduler, minDataFile);
170+
optimizelyManager.injectOptimizely(context, userProfileService, minDatafile);
166171
try {
167172
executor.awaitTermination(5, TimeUnit.SECONDS);
168173
} catch (InterruptedException e) {
169174
fail("Timed out");
170175
}
171176

172-
verify(userProfileService).start();
173-
verify(serviceScheduler).schedule(captor.capture(), eq(TimeUnit.HOURS.toMillis(1L)));
174177
verify(logger).info("Sending Optimizely instance to listener");
175178
verify(startListener).onStart(any(OptimizelyClient.class));
176179
}
@@ -179,80 +182,105 @@ public void injectOptimizely() {
179182
@Test
180183
public void injectOptimizelyNullListener() {
181184
Context context = mock(Context.class);
185+
PackageManager packageManager = mock(PackageManager.class);
186+
182187
when(context.getPackageName()).thenReturn("com.optly");
183-
AndroidUserProfileService userProfileService = mock(AndroidUserProfileService.class);
188+
when(context.getApplicationContext()).thenReturn(context);
189+
when(context.getApplicationContext().getPackageManager()).thenReturn(packageManager);
190+
try {
191+
when(packageManager.getPackageInfo("com.optly", 0)).thenReturn(mock(PackageInfo.class));
192+
} catch (PackageManager.NameNotFoundException e) {
193+
e.printStackTrace();
194+
}
195+
UserProfileService userProfileService = mock(UserProfileService.class);
184196
ServiceScheduler serviceScheduler = mock(ServiceScheduler.class);
185197
ArgumentCaptor<Intent> captor = ArgumentCaptor.forClass(Intent.class);
198+
ArgumentCaptor<DefaultUserProfileService.StartCallback> callbackArgumentCaptor =
199+
ArgumentCaptor.forClass(DefaultUserProfileService.StartCallback.class);
186200
optimizelyManager.setOptimizelyStartListener(null);
187-
optimizelyManager.injectOptimizely(context, userProfileService, serviceScheduler, minDataFile);
201+
202+
optimizelyManager.injectOptimizely(context, userProfileService, minDatafile);
203+
AlarmManager alarmManager = (AlarmManager) context
204+
.getSystemService(Context.ALARM_SERVICE);
205+
ServiceScheduler.PendingIntentFactory pendingIntentFactory = new ServiceScheduler
206+
.PendingIntentFactory(context);
207+
208+
Intent intent = new Intent(context, DatafileService.class);
209+
intent.putExtra(DatafileService.EXTRA_PROJECT_ID, optimizelyManager.getProjectId());
210+
serviceScheduler.schedule(intent, optimizelyManager.getDatafileDownloadInterval() * 1000);
211+
188212
try {
189213
executor.awaitTermination(5, TimeUnit.SECONDS);
190214
} catch (InterruptedException e) {
191215
fail("Timed out");
192216
}
193217

194-
verify(userProfileService).start();
195-
verify(serviceScheduler).schedule(captor.capture(), eq(TimeUnit.HOURS.toMillis(1L)));
196218
verify(logger).info("No listener to send Optimizely to");
219+
verify(serviceScheduler).schedule(captor.capture(), eq(TimeUnit.HOURS.toMillis(1L)));
197220

198-
Intent intent = captor.getValue();
199-
assertTrue(intent.getComponent().getShortClassName().contains("DataFileService"));
200-
assertEquals(optimizelyManager.getProjectId(), intent.getStringExtra(DataFileService.EXTRA_PROJECT_ID));
221+
Intent intent2 = captor.getValue();
222+
assertTrue(intent2.getComponent().getShortClassName().contains("DatafileService"));
223+
assertEquals(optimizelyManager.getProjectId(), intent2.getStringExtra(DatafileService.EXTRA_PROJECT_ID));
201224
}
202225

203226
@RequiresApi(api = Build.VERSION_CODES.HONEYCOMB)
204227
@Test
205-
public void injectOptimizelyHandlesInvalidDataFile() {
228+
public void injectOptimizelyHandlesInvalidDatafile() {
206229
Context context = mock(Context.class);
230+
PackageManager packageManager = mock(PackageManager.class);
231+
207232
when(context.getPackageName()).thenReturn("com.optly");
208-
AndroidUserProfileService userProfileService = mock(AndroidUserProfileService.class);
209-
ServiceScheduler serviceScheduler = mock(ServiceScheduler.class);
233+
when(context.getApplicationContext()).thenReturn(context);
234+
when(context.getApplicationContext().getPackageManager()).thenReturn(packageManager);
235+
try {
236+
when(packageManager.getPackageInfo("com.optly", 0)).thenReturn(mock(PackageInfo.class));
237+
} catch (PackageManager.NameNotFoundException e) {
238+
e.printStackTrace();
239+
}
240+
DefaultUserProfileService userProfileService = mock(DefaultUserProfileService.class);
210241
ArgumentCaptor<Intent> captor = ArgumentCaptor.forClass(Intent.class);
242+
ArgumentCaptor<DefaultUserProfileService.StartCallback> callbackArgumentCaptor =
243+
ArgumentCaptor.forClass(DefaultUserProfileService.StartCallback.class);
244+
211245
optimizelyManager.setOptimizelyStartListener(null);
212-
optimizelyManager.injectOptimizely(context, userProfileService, serviceScheduler, "{}");
246+
optimizelyManager.injectOptimizely(context, userProfileService, "{}");
213247
try {
214248
executor.awaitTermination(5, TimeUnit.SECONDS);
215249
} catch (InterruptedException e) {
216250
fail("Timed out");
217251
}
218252

219-
verify(userProfileService).start();
220-
verify(serviceScheduler).schedule(captor.capture(), eq(TimeUnit.HOURS.toMillis(1L)));
221253
verify(logger).error(eq("Unable to build optimizely instance"), any(Exception.class));
222254

223-
Intent intent = captor.getValue();
224-
assertTrue(intent.getComponent().getShortClassName().contains("DataFileService"));
225-
assertEquals(optimizelyManager.getProjectId(), intent.getStringExtra(DataFileService.EXTRA_PROJECT_ID));
226255
}
227256

228257
@RequiresApi(api = Build.VERSION_CODES.HONEYCOMB)
229258
@Test
230259
public void injectOptimizelyDoesNotDuplicateCallback() {
231260
Context context = mock(Context.class);
261+
PackageManager packageManager = mock(PackageManager.class);
262+
232263
when(context.getPackageName()).thenReturn("com.optly");
233-
AndroidUserProfileService userProfileService = mock(AndroidUserProfileService.class);
234-
ServiceScheduler serviceScheduler = mock(ServiceScheduler.class);
235-
ArgumentCaptor<Intent> captor = ArgumentCaptor.forClass(Intent.class);
264+
when(context.getApplicationContext()).thenReturn(context);
265+
when(context.getApplicationContext().getPackageManager()).thenReturn(packageManager);
266+
try {
267+
when(packageManager.getPackageInfo("com.optly", 0)).thenReturn(mock(PackageInfo.class));
268+
} catch (PackageManager.NameNotFoundException e) {
269+
e.printStackTrace();
270+
}
271+
272+
UserProfileService userProfileService = mock(UserProfileService.class);
273+
236274
OptimizelyStartListener startListener = mock(OptimizelyStartListener.class);
237275
optimizelyManager.setOptimizelyStartListener(startListener);
238-
optimizelyManager.injectOptimizely(context, userProfileService, serviceScheduler, minDataFile);
276+
optimizelyManager.injectOptimizely(context, userProfileService, minDatafile);
239277
try {
240278
executor.awaitTermination(5, TimeUnit.SECONDS);
241279
} catch (InterruptedException e) {
242280
fail("Timed out");
243281
}
244282

245-
verify(userProfileService).start();
246-
verify(serviceScheduler).schedule(captor.capture(), eq(TimeUnit.HOURS.toMillis(1L)));
247-
248283
verify(logger).info("Sending Optimizely instance to listener");
249284
verify(startListener).onStart(any(OptimizelyClient.class));
250-
251-
optimizelyManager.injectOptimizely(context, userProfileService, serviceScheduler, minDataFile);
252-
try {
253-
executor.awaitTermination(5, TimeUnit.SECONDS);
254-
} catch (InterruptedException e) {
255-
fail("Timed out");
256-
}
257285
}
258286
}
Lines changed: 21 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,25 @@
11
<?xml version="1.0" encoding="utf-8"?>
2-
<manifest package="com.optimizely.ab.android.sdk"
3-
xmlns:android="http://schemas.android.com/apk/res/android">
2+
<!--
3+
/****************************************************************************
4+
* Copyright 2017, Optimizely, Inc. and contributors *
5+
* *
6+
* Licensed under the Apache License, Version 2.0 (the "License"); *
7+
* you may not use this file except in compliance with the License. *
8+
* You may obtain a copy of the License at *
9+
* *
10+
* http://www.apache.org/licenses/LICENSE-2.0 *
11+
* *
12+
* Unless required by applicable law or agreed to in writing, software *
13+
* distributed under the License is distributed on an "AS IS" BASIS, *
14+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. *
15+
* See the License for the specific language governing permissions and *
16+
* limitations under the License. *
17+
***************************************************************************/
18+
-->
419

5-
<uses-permission android:name="android.permission.INTERNET"/>
6-
7-
<application>
8-
<service
9-
android:name=".DataFileService"
10-
android:enabled="true"
11-
android:exported="false">
12-
</service>
20+
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
21+
package="com.optimizely.ab.android.sdk">
1322

14-
<receiver
15-
android:name=".DataFileRescheduler"
16-
android:enabled="true"
17-
android:exported="false">
18-
<intent-filter>
19-
<action android:name="android.intent.action.MY_PACKAGE_REPLACED" />
20-
<action android:name="android.intent.action.BOOT_COMPLETED" />
21-
</intent-filter>
22-
</receiver>
23-
</application>
23+
<uses-permission android:name="android.permission.INTERNET"/>
2424

25-
</manifest>
25+
</manifest>

0 commit comments

Comments
 (0)