Skip to content

Commit 85505f8

Browse files
authored
User profile service (#116)
1 parent 55a75a1 commit 85505f8

File tree

17 files changed

+1411
-887
lines changed

17 files changed

+1411
-887
lines changed

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

Lines changed: 14 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
import android.support.test.runner.AndroidJUnit4;
2626

2727
import com.optimizely.ab.android.shared.ServiceScheduler;
28-
import com.optimizely.ab.android.user_profile.AndroidUserProfile;
28+
import com.optimizely.ab.android.user_profile.AndroidUserProfileService;
2929
import com.optimizely.ab.config.parser.ConfigParseException;
3030

3131
import org.junit.Before;
@@ -77,7 +77,6 @@ public void setup() {
7777
optimizelyManager = new OptimizelyManager(testProjectId, 1L, TimeUnit.HOURS, 1L, TimeUnit.HOURS, executor, logger);
7878
}
7979

80-
8180
@SuppressWarnings("WrongConstant")
8281
@Test
8382
public void initialize() {
@@ -158,19 +157,19 @@ public void stop() {
158157
@Test
159158
public void injectOptimizely() {
160159
Context context = mock(Context.class);
161-
AndroidUserProfile userProfile = mock(AndroidUserProfile.class);
160+
AndroidUserProfileService userProfileService = mock(AndroidUserProfileService.class);
162161
ServiceScheduler serviceScheduler = mock(ServiceScheduler.class);
163162
ArgumentCaptor<Intent> captor = ArgumentCaptor.forClass(Intent.class);
164163
OptimizelyStartListener startListener = mock(OptimizelyStartListener.class);
165164
optimizelyManager.setOptimizelyStartListener(startListener);
166-
optimizelyManager.injectOptimizely(context, userProfile, serviceScheduler, minDataFile);
165+
optimizelyManager.injectOptimizely(context, userProfileService, serviceScheduler, minDataFile);
167166
try {
168167
executor.awaitTermination(5, TimeUnit.SECONDS);
169168
} catch (InterruptedException e) {
170169
fail("Timed out");
171170
}
172171

173-
verify(userProfile).start();
172+
verify(userProfileService).start();
174173
verify(serviceScheduler).schedule(captor.capture(), eq(TimeUnit.HOURS.toMillis(1L)));
175174
verify(logger).info("Sending Optimizely instance to listener");
176175
verify(startListener).onStart(any(OptimizelyClient.class));
@@ -181,18 +180,18 @@ public void injectOptimizely() {
181180
public void injectOptimizelyNullListener() {
182181
Context context = mock(Context.class);
183182
when(context.getPackageName()).thenReturn("com.optly");
184-
AndroidUserProfile userProfile = mock(AndroidUserProfile.class);
183+
AndroidUserProfileService userProfileService = mock(AndroidUserProfileService.class);
185184
ServiceScheduler serviceScheduler = mock(ServiceScheduler.class);
186185
ArgumentCaptor<Intent> captor = ArgumentCaptor.forClass(Intent.class);
187186
optimizelyManager.setOptimizelyStartListener(null);
188-
optimizelyManager.injectOptimizely(context, userProfile, serviceScheduler, minDataFile);
187+
optimizelyManager.injectOptimizely(context, userProfileService, serviceScheduler, minDataFile);
189188
try {
190189
executor.awaitTermination(5, TimeUnit.SECONDS);
191190
} catch (InterruptedException e) {
192191
fail("Timed out");
193192
}
194193

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

@@ -206,18 +205,18 @@ public void injectOptimizelyNullListener() {
206205
public void injectOptimizelyHandlesInvalidDataFile() {
207206
Context context = mock(Context.class);
208207
when(context.getPackageName()).thenReturn("com.optly");
209-
AndroidUserProfile userProfile = mock(AndroidUserProfile.class);
208+
AndroidUserProfileService userProfileService = mock(AndroidUserProfileService.class);
210209
ServiceScheduler serviceScheduler = mock(ServiceScheduler.class);
211210
ArgumentCaptor<Intent> captor = ArgumentCaptor.forClass(Intent.class);
212211
optimizelyManager.setOptimizelyStartListener(null);
213-
optimizelyManager.injectOptimizely(context, userProfile, serviceScheduler, "{}");
212+
optimizelyManager.injectOptimizely(context, userProfileService, serviceScheduler, "{}");
214213
try {
215214
executor.awaitTermination(5, TimeUnit.SECONDS);
216215
} catch (InterruptedException e) {
217216
fail("Timed out");
218217
}
219218

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

@@ -231,25 +230,25 @@ public void injectOptimizelyHandlesInvalidDataFile() {
231230
public void injectOptimizelyDoesNotDuplicateCallback() {
232231
Context context = mock(Context.class);
233232
when(context.getPackageName()).thenReturn("com.optly");
234-
AndroidUserProfile userProfile = mock(AndroidUserProfile.class);
233+
AndroidUserProfileService userProfileService = mock(AndroidUserProfileService.class);
235234
ServiceScheduler serviceScheduler = mock(ServiceScheduler.class);
236235
ArgumentCaptor<Intent> captor = ArgumentCaptor.forClass(Intent.class);
237236
OptimizelyStartListener startListener = mock(OptimizelyStartListener.class);
238237
optimizelyManager.setOptimizelyStartListener(startListener);
239-
optimizelyManager.injectOptimizely(context, userProfile, serviceScheduler, minDataFile);
238+
optimizelyManager.injectOptimizely(context, userProfileService, serviceScheduler, minDataFile);
240239
try {
241240
executor.awaitTermination(5, TimeUnit.SECONDS);
242241
} catch (InterruptedException e) {
243242
fail("Timed out");
244243
}
245244

246-
verify(userProfile).start();
245+
verify(userProfileService).start();
247246
verify(serviceScheduler).schedule(captor.capture(), eq(TimeUnit.HOURS.toMillis(1L)));
248247

249248
verify(logger).info("Sending Optimizely instance to listener");
250249
verify(startListener).onStart(any(OptimizelyClient.class));
251250

252-
optimizelyManager.injectOptimizely(context, userProfile, serviceScheduler, minDataFile);
251+
optimizelyManager.injectOptimizely(context, userProfileService, serviceScheduler, minDataFile);
253252
try {
254253
executor.awaitTermination(5, TimeUnit.SECONDS);
255254
} catch (InterruptedException e) {

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

Lines changed: 43 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@
2424
import android.content.Context;
2525
import android.content.Intent;
2626
import android.content.ServiceConnection;
27-
import android.content.pm.PackageInfo;
2827
import android.content.res.Resources;
2928
import android.os.AsyncTask;
3029
import android.os.Build;
@@ -42,8 +41,8 @@
4241
import com.optimizely.ab.android.shared.Client;
4342
import com.optimizely.ab.android.shared.OptlyStorage;
4443
import com.optimizely.ab.android.shared.ServiceScheduler;
45-
import com.optimizely.ab.android.user_profile.AndroidUserProfile;
46-
import com.optimizely.ab.bucketing.UserProfile;
44+
import com.optimizely.ab.android.user_profile.AndroidUserProfileService;
45+
import com.optimizely.ab.bucketing.UserProfileService;
4746
import com.optimizely.ab.config.parser.ConfigParseException;
4847
import com.optimizely.ab.event.internal.payload.Event;
4948

@@ -61,6 +60,7 @@
6160
* Handles loading the Optimizely data file
6261
*/
6362
public class OptimizelyManager {
63+
6464
@NonNull private OptimizelyClient optimizelyClient = new OptimizelyClient(null,
6565
LoggerFactory.getLogger(OptimizelyClient.class));
6666
@NonNull private final String projectId;
@@ -72,7 +72,7 @@ public class OptimizelyManager {
7272
@NonNull private final Logger logger;
7373
@Nullable private DataFileServiceConnection dataFileServiceConnection;
7474
@Nullable private OptimizelyStartListener optimizelyStartListener;
75-
@Nullable private UserProfile userProfile;
75+
@Nullable private UserProfileService userProfileService;
7676

7777
OptimizelyManager(@NonNull String projectId,
7878
@NonNull Long eventHandlerDispatchInterval,
@@ -88,7 +88,6 @@ public class OptimizelyManager {
8888
this.dataFileDownloadIntervalTimeUnit = dataFileDownloadIntervalTimeUnit;
8989
this.executor = executor;
9090
this.logger = logger;
91-
9291
}
9392

9493
@NonNull
@@ -145,14 +144,14 @@ public OptimizelyClient initialize(@NonNull Context context, @NonNull String dat
145144
return optimizelyClient;
146145
}
147146

148-
AndroidUserProfile userProfile =
149-
(AndroidUserProfile) AndroidUserProfile.newInstance(getProjectId(), context);
147+
AndroidUserProfileService userProfileService =
148+
(AndroidUserProfileService) AndroidUserProfileService.newInstance(getProjectId(), context);
150149
// The User Profile is started on the main thread on an asynchronous start.
151150
// Starting simply creates the file if it doesn't exist so it's not
152151
// terribly expensive. Blocking the UI thread prevents touch input...
153-
userProfile.start();
152+
userProfileService.start();
154153
try {
155-
optimizelyClient = buildOptimizely(context, datafile, userProfile);
154+
optimizelyClient = buildOptimizely(context, datafile, userProfileService);
156155
} catch (ConfigParseException e) {
157156
logger.error("Unable to parse compiled data file", e);
158157
} catch (Exception e) {
@@ -301,6 +300,7 @@ public void stop(@NonNull Context context) {
301300
*/
302301
@NonNull
303302
public OptimizelyClient getOptimizely() {
303+
// Check version and log warning if version is less than what is required.
304304
isAndroidVersionSupported();
305305
return optimizelyClient;
306306
}
@@ -348,24 +348,25 @@ String getProjectId() {
348348
}
349349

350350
@RequiresApi(api = Build.VERSION_CODES.HONEYCOMB)
351-
void injectOptimizely(@NonNull final Context context, final @NonNull AndroidUserProfile userProfile, @NonNull final ServiceScheduler serviceScheduler, @NonNull final String dataFile) {
352-
AsyncTask<Void, Void, UserProfile> initUserProfileTask = new AsyncTask<Void, Void, UserProfile>() {
351+
void injectOptimizely(@NonNull final Context context, final @NonNull AndroidUserProfileService userProfileService,
352+
@NonNull final ServiceScheduler serviceScheduler, @NonNull final String dataFile) {
353+
AsyncTask<Void, Void, UserProfileService> initUserProfileTask = new AsyncTask<Void, Void, UserProfileService>() {
353354
@Override
354-
protected UserProfile doInBackground(Void[] params) {
355-
userProfile.start();
356-
return userProfile;
355+
protected UserProfileService doInBackground(Void[] params) {
356+
userProfileService.start();
357+
return userProfileService;
357358
}
358359

359360
@Override
360-
protected void onPostExecute(UserProfile userProfile) {
361+
protected void onPostExecute(UserProfileService userProfileService) {
361362
Intent intent = new Intent(context, DataFileService.class);
362363
intent.putExtra(DataFileService.EXTRA_PROJECT_ID, projectId);
363364
serviceScheduler.schedule(intent, dataFileDownloadIntervalTimeUnit.toMillis(dataFileDownloadInterval));
364365

365366
try {
366-
OptimizelyManager.this.optimizelyClient = buildOptimizely(context, dataFile, userProfile);
367+
OptimizelyManager.this.optimizelyClient = buildOptimizely(context, dataFile, userProfileService);
368+
OptimizelyManager.this.userProfileService = userProfileService;
367369
optimizelyClient.setDefaultAttributes(OptimizelyDefaultAttributes.buildDefaultAttributesMap(context, logger));
368-
OptimizelyManager.this.userProfile = userProfile;
369370
logger.info("Sending Optimizely instance to listener");
370371

371372
if (optimizelyStartListener != null) {
@@ -378,38 +379,40 @@ protected void onPostExecute(UserProfile userProfile) {
378379
}
379380
}
380381
};
382+
381383
try {
382384
initUserProfileTask.executeOnExecutor(executor);
383385
} catch (Exception e) {
384386
logger.error("Unable to initialize the user profile while injecting Optimizely", e);
385387
}
386388
}
387389

388-
private OptimizelyClient buildOptimizely(@NonNull Context context, @NonNull String dataFile, @NonNull UserProfile userProfile) throws ConfigParseException {
390+
private OptimizelyClient buildOptimizely(@NonNull Context context, @NonNull String dataFile, @NonNull
391+
UserProfileService userProfileService) throws ConfigParseException {
389392
OptlyEventHandler eventHandler = OptlyEventHandler.getInstance(context);
390393
eventHandler.setDispatchInterval(eventHandlerDispatchInterval, eventHandlerDispatchIntervalTimeUnit);
391394

392395
Event.ClientEngine clientEngine = OptimizelyClientEngine.getClientEngineFromContext(context);
393396

394397
Optimizely optimizely = Optimizely.builder(dataFile, eventHandler)
395-
.withUserProfile(userProfile)
398+
.withUserProfileService(userProfileService)
396399
.withClientEngine(clientEngine)
397400
.withClientVersion(BuildConfig.CLIENT_VERSION)
398401
.build();
399402
return new OptimizelyClient(optimizely, LoggerFactory.getLogger(OptimizelyClient.class));
400403
}
401404

402405
@VisibleForTesting
403-
public UserProfile getUserProfile() {
404-
return userProfile;
406+
public UserProfileService getUserProfileService() {
407+
return userProfileService;
405408
}
406409

407410
private boolean isAndroidVersionSupported() {
408411
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
409412
return true;
410413
} else {
411-
logger.warn("Optimizely will not work on this phone. It's Android version {} is less the minimum supported" +
412-
"version {}", Build.VERSION.SDK_INT, Build.VERSION_CODES.ICE_CREAM_SANDWICH);
414+
logger.warn("Optimizely will not work on this phone. It's Android version {} is less the minimum " +
415+
"supported version {}", Build.VERSION.SDK_INT, Build.VERSION_CODES.ICE_CREAM_SANDWICH);
413416
return false;
414417
}
415418
}
@@ -515,7 +518,8 @@ public void onServiceConnected(ComponentName className,
515518
final DataFileService dataFileService = binder.getService();
516519
if (dataFileService != null) {
517520
DataFileClient dataFileClient = new DataFileClient(
518-
new Client(new OptlyStorage(dataFileService.getApplicationContext()), LoggerFactory.getLogger(OptlyStorage.class)),
521+
new Client(new OptlyStorage(dataFileService.getApplicationContext()),
522+
LoggerFactory.getLogger(OptlyStorage.class)),
519523
LoggerFactory.getLogger(DataFileClient.class));
520524

521525
DataFileCache dataFileCache = new DataFileCache(
@@ -529,22 +533,29 @@ public void onServiceConnected(ComponentName className,
529533
Executors.newSingleThreadExecutor(),
530534
LoggerFactory.getLogger(DataFileLoader.class));
531535

532-
dataFileService.getDataFile(optimizelyManager.getProjectId(), dataFileLoader, new DataFileLoadedListener() {
536+
dataFileService.getDataFile(optimizelyManager.getProjectId(), dataFileLoader, new
537+
DataFileLoadedListener() {
533538
@Override
534539
public void onDataFileLoaded(@Nullable String dataFile) {
535540
// App is being used, i.e. in the foreground
536-
AlarmManager alarmManager = (AlarmManager) dataFileService.getApplicationContext().getSystemService(Context.ALARM_SERVICE);
537-
ServiceScheduler.PendingIntentFactory pendingIntentFactory = new ServiceScheduler.PendingIntentFactory(dataFileService.getApplicationContext());
538-
ServiceScheduler serviceScheduler = new ServiceScheduler(alarmManager, pendingIntentFactory, LoggerFactory.getLogger(ServiceScheduler.class));
541+
AlarmManager alarmManager = (AlarmManager) dataFileService.getApplicationContext()
542+
.getSystemService(Context.ALARM_SERVICE);
543+
ServiceScheduler.PendingIntentFactory pendingIntentFactory = new ServiceScheduler
544+
.PendingIntentFactory(dataFileService.getApplicationContext());
545+
ServiceScheduler serviceScheduler = new ServiceScheduler(alarmManager, pendingIntentFactory,
546+
LoggerFactory.getLogger(ServiceScheduler.class));
539547
if (dataFile != null) {
540-
AndroidUserProfile userProfile =
541-
(AndroidUserProfile) AndroidUserProfile.newInstance(optimizelyManager.getProjectId(), dataFileService.getApplicationContext());
542-
optimizelyManager.injectOptimizely(dataFileService.getApplicationContext(), userProfile, serviceScheduler, dataFile);
548+
AndroidUserProfileService userProfileService = (AndroidUserProfileService)
549+
AndroidUserProfileService.newInstance(optimizelyManager.getProjectId(),
550+
dataFileService.getApplicationContext());
551+
optimizelyManager.injectOptimizely(dataFileService.getApplicationContext(),
552+
userProfileService, serviceScheduler, dataFile);
543553
} else {
544554
// We should always call the callback even with the dummy
545555
// instances. Devs might gate the rest of their app
546556
// based on the loading of Optimizely
547-
OptimizelyStartListener optimizelyStartListener = optimizelyManager.getOptimizelyStartListener();
557+
OptimizelyStartListener optimizelyStartListener = optimizelyManager
558+
.getOptimizelyStartListener();
548559
if (optimizelyStartListener != null) {
549560
optimizelyStartListener.onStart(optimizelyManager.getOptimizely());
550561
}

android-sdk/src/test/java/com/optimizely/ab/android/sdk/OptimizelyManagerDataFileServiceConnectionTest.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
import android.support.annotation.RequiresApi;
2222

2323
import com.optimizely.ab.android.shared.ServiceScheduler;
24-
import com.optimizely.ab.android.user_profile.AndroidUserProfile;
24+
import com.optimizely.ab.android.user_profile.AndroidUserProfileService;
2525

2626
import org.junit.Before;
2727
import org.junit.Test;
@@ -66,7 +66,8 @@ public void onServiceConnected() {
6666
verify(service).getDataFile(same("1"), any(DataFileLoader.class), captor.capture());
6767
DataFileLoadedListener listener = captor.getValue();
6868
listener.onDataFileLoaded("");
69-
verify(optimizelyManager).injectOptimizely(any(Context.class), any(AndroidUserProfile.class), any(ServiceScheduler.class), eq(""));
69+
verify(optimizelyManager).injectOptimizely(any(Context.class), any(AndroidUserProfileService.class),
70+
any(ServiceScheduler.class), eq(""));
7071
}
7172

7273
@RequiresApi(api = Build.VERSION_CODES.HONEYCOMB)

build.gradle

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ buildscript {
3333
jcenter()
3434
}
3535
dependencies {
36-
classpath 'com.android.tools.build:gradle:2.3.1'
36+
classpath 'com.android.tools.build:gradle:2.3.3'
3737

3838
// NOTE: Do not place your application dependencies here; they belong
3939
// in the individual module build.gradle files
@@ -52,7 +52,7 @@ ext {
5252
min_sdk_version = 10
5353
target_sdk_version = 24
5454

55-
java_core_ver = "1.6.0"
55+
java_core_ver = "2.0.0-alpha"
5656
android_logger_ver = "1.3.1"
5757
support_annotations_ver = "24.2.1"
5858
junit_ver = "4.12"

0 commit comments

Comments
 (0)