Skip to content

Commit 010771c

Browse files
Feature/androido updates (#155)
* refactor of code. tested. caught problem sending out events. * only cancel periodic jobs/scheduled * finally got everything working for Android O. Notice that I use a ScheduledJobService for scheduled services and JobWorkService for starting services. * added receive boot complete to permissions * update the changelog * fix tests to work with API 17,24,26
1 parent 297e649 commit 010771c

File tree

15 files changed

+411
-149
lines changed

15 files changed

+411
-149
lines changed

.circleci/config.yml

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -29,12 +29,12 @@ jobs:
2929
JAVA_OPTIONS: -Djava.io.tmpdir=/var/tmp
3030
steps:
3131
- checkout
32-
- run:
33-
name: List emulators
34-
command: avdmanager list
35-
- run:
36-
name: List SDK
37-
command: sdkmanager --list
32+
# - run:
33+
# name: List emulators
34+
# command: avdmanager list
35+
# - run:
36+
# name: List SDK
37+
# command: sdkmanager --list
3838
- run:
3939
name: Setup emulator
4040
command: sdkmanager "system-images;android-25;google_apis;armeabi-v7a" && echo "no" | avdmanager create avd -n test -k "system-images;android-25;google_apis;armeabi-v7a"

CHANGELOG.md

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,30 @@
11
# Optimizely Android X SDK Changelog
2+
### 1.5.0
3+
September 26, 2017
4+
5+
- Release 1.5.0
6+
7+
*New Features*
8+
9+
- Numeric metrics
10+
- Client-side programmatic forced variations.
11+
12+
13+
*Bug Fixes*
14+
15+
- Remove Espresso dependency
16+
- Narrow proguard rules
17+
- Last modified fixed so that multiple project files can be used.
18+
- Call start listener if there is an exception.
19+
- Example of overriding Gson and android-logger in test-app gradle file.
20+
- Fix crash on API 17 (missing annotation).
21+
- Support for Android O (please see developer docs for details). Basically, Android O and above will use JobScheduler and pre Android O will continue to use AlarmService. This is done through a class called the JobWorkService which allows you to keep your Service and IntentService intact. Developers can piggyback on this method and keep thier IntentServices and use the JobWorkService.
22+
23+
*Breaking Changes*
24+
25+
- Same as 1.4.0 see below.
26+
- Need to add permissions to both receivers in your manifest if you plan on using the EventRescheduler or the DatafileRescheduler (see test_app manifest for example) https://github.com/optimizely/android-sdk/blob/master/test-app/src/main/AndroidManifest.xml
27+
228
### 1.4.0
329
August 9, 2017
430

datafile-handler/src/main/java/com/optimizely/ab/android/datafile_handler/DatafileRescheduler.java

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -96,11 +96,15 @@ static class Dispatcher {
9696

9797
void dispatch(Intent intent) {
9898
List<String> projectIds = backgroundWatchersCache.getWatchingProjectIds();
99-
for (String projectId : projectIds) {
100-
intent.putExtra(DatafileService.EXTRA_PROJECT_ID, projectId);
101-
ServiceScheduler.startService(context, DatafileService.JOB_ID, intent);
10299

103-
logger.info("Rescheduled data file watching for project {}", projectId);
100+
for (String projectId : projectIds) {
101+
// for scheduled jobs Android O and above, we use the JobScheduler and persistent periodic jobs
102+
// so, we don't need to do anything.
103+
// if (android.os.Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
104+
intent.putExtra(DatafileService.EXTRA_PROJECT_ID, projectId);
105+
ServiceScheduler.startService(context, DatafileService.JOB_ID, intent);
106+
logger.info("Rescheduled data file watching for project {}", projectId);
107+
//}
104108
}
105109

106110
}

datafile-handler/src/main/java/com/optimizely/ab/android/datafile_handler/DatafileService.java

Lines changed: 22 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,6 @@
2929
import com.optimizely.ab.android.shared.Cache;
3030
import com.optimizely.ab.android.shared.Client;
3131
import com.optimizely.ab.android.shared.OptlyStorage;
32-
import com.optimizely.ab.android.shared.JobWorkScheduledService;
3332

3433
import org.slf4j.Logger;
3534
import org.slf4j.LoggerFactory;
@@ -41,7 +40,7 @@
4140
* These services will only be used if you are using our {@link DefaultDatafileHandler}.
4241
* You can chose to implement your own handler and use all or part of this package.
4342
*/
44-
public class DatafileService extends Service implements JobWorkScheduledService {
43+
public class DatafileService extends Service {
4544
/**
4645
* Extra containing the project id this instance of Optimizely was built with
4746
*/
@@ -61,7 +60,27 @@ public class DatafileService extends Service implements JobWorkScheduledService
6160
@RequiresApi(api = Build.VERSION_CODES.HONEYCOMB)
6261
@Override
6362
public int onStartCommand(Intent intent, int flags, int startId) {
64-
onWork(intent);
63+
if (intent != null) {
64+
if (intent.hasExtra(EXTRA_PROJECT_ID)) {
65+
String projectId = intent.getStringExtra(EXTRA_PROJECT_ID);
66+
DatafileClient datafileClient = new DatafileClient(
67+
new Client(new OptlyStorage(this.getApplicationContext()), LoggerFactory.getLogger(OptlyStorage.class)),
68+
LoggerFactory.getLogger(DatafileClient.class));
69+
DatafileCache datafileCache = new DatafileCache(
70+
projectId,
71+
new Cache(this.getApplicationContext(), LoggerFactory.getLogger(Cache.class)),
72+
LoggerFactory.getLogger(DatafileCache.class));
73+
74+
String datafileUrl = getDatafileUrl(projectId);
75+
DatafileLoader datafileLoader = new DatafileLoader(this, datafileClient, datafileCache, Executors.newSingleThreadExecutor(), LoggerFactory.getLogger(DatafileLoader.class));
76+
datafileLoader.getDatafile(datafileUrl, null);
77+
} else {
78+
logger.warn("Data file service received an intent with no project id extra");
79+
}
80+
} else {
81+
logger.warn("Data file service received a null intent");
82+
}
83+
6584
return super.onStartCommand(intent, flags, startId);
6685
}
6786

@@ -104,37 +123,6 @@ public void getDatafile(String projectId, DatafileLoader datafileLoader, Datafil
104123
datafileLoader.getDatafile(datafileUrl, loadedListener);
105124
}
106125

107-
@Override
108-
public void initialize() {
109-
110-
}
111-
112-
@RequiresApi(api = Build.VERSION_CODES.HONEYCOMB)
113-
@Override
114-
public void onWork(@Nullable Intent intent) {
115-
if (intent != null) {
116-
if (intent.hasExtra(EXTRA_PROJECT_ID)) {
117-
String projectId = intent.getStringExtra(EXTRA_PROJECT_ID);
118-
DatafileClient datafileClient = new DatafileClient(
119-
new Client(new OptlyStorage(this.getApplicationContext()), LoggerFactory.getLogger(OptlyStorage.class)),
120-
LoggerFactory.getLogger(DatafileClient.class));
121-
DatafileCache datafileCache = new DatafileCache(
122-
projectId,
123-
new Cache(this.getApplicationContext(), LoggerFactory.getLogger(Cache.class)),
124-
LoggerFactory.getLogger(DatafileCache.class));
125-
126-
String datafileUrl = getDatafileUrl(projectId);
127-
DatafileLoader datafileLoader = new DatafileLoader(this, datafileClient, datafileCache, Executors.newSingleThreadExecutor(), LoggerFactory.getLogger(DatafileLoader.class));
128-
datafileLoader.getDatafile(datafileUrl, null);
129-
} else {
130-
logger.warn("Data file service received an intent with no project id extra");
131-
}
132-
} else {
133-
logger.warn("Data file service received a null intent");
134-
}
135-
136-
}
137-
138126
public class LocalBinder extends Binder {
139127
public DatafileService getService() {
140128
// Return this instance of LocalService so clients can call public methods

datafile-handler/src/main/java/com/optimizely/ab/android/datafile_handler/DefaultDatafileHandler.java

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@
1616

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

19-
import android.app.AlarmManager;
2019
import android.content.Context;
2120
import android.content.Intent;
2221
import android.os.Build;
@@ -116,6 +115,18 @@ public void startBackgroundUpdates(Context context, String projectId, Long updat
116115
Intent intent = new Intent(context.getApplicationContext(), DatafileService.class);
117116
intent.putExtra(DatafileService.EXTRA_PROJECT_ID, projectId);
118117
serviceScheduler.schedule(intent, updateInterval * 1000);
118+
119+
storeInterval(context, updateInterval * 1000);
120+
}
121+
122+
private static void storeInterval(Context context, long interval) {
123+
OptlyStorage storage = new OptlyStorage(context);
124+
storage.saveLong("DATAFILE_INTERVAL", interval);
125+
}
126+
127+
public static long getUpdateInterval(Context context) {
128+
OptlyStorage storage = new OptlyStorage(context);
129+
return storage.getLong("DATAFILE_INTERVAL", -1);
119130
}
120131

121132
/**
@@ -127,12 +138,14 @@ public void startBackgroundUpdates(Context context, String projectId, Long updat
127138
public void stopBackgroundUpdates(Context context, String projectId) {
128139
ServiceScheduler.PendingIntentFactory pendingIntentFactory = new ServiceScheduler
129140
.PendingIntentFactory(context.getApplicationContext());
130-
ServiceScheduler serviceScheduler = new ServiceScheduler(context, pendingIntentFactory,
141+
ServiceScheduler serviceScheduler = new ServiceScheduler(context.getApplicationContext(), pendingIntentFactory,
131142
LoggerFactory.getLogger(ServiceScheduler.class));
132143
Intent intent = new Intent(context.getApplicationContext(), DatafileService.class);
133144
serviceScheduler.unschedule(intent);
134145

135146
clearBackgroundCache(context, projectId);
147+
148+
storeInterval(context, -1);
136149
}
137150

138151
private void enableBackgroundCache(Context context, String projectId) {

event-handler/src/androidTest/java/com/optimizely/ab/android/event_handler/ServiceSchedulerTest.java

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -94,9 +94,8 @@ public void testScheduleWithNoDurationExtra() {
9494

9595
if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
9696
ArgumentCaptor<JobInfo> jobInfoArgumentCaptor = ArgumentCaptor.forClass(JobInfo.class);
97-
ArgumentCaptor<JobWorkItem> workItemArgumentCaptor = ArgumentCaptor.forClass(JobWorkItem.class);
9897

99-
verify(jobScheduler).enqueue(jobInfoArgumentCaptor.capture(), workItemArgumentCaptor.capture());
98+
verify(jobScheduler).schedule(jobInfoArgumentCaptor.capture());
10099

101100
assertEquals(jobInfoArgumentCaptor.getValue().getIntervalMillis(), AlarmManager.INTERVAL_HOUR );
102101

@@ -128,9 +127,8 @@ public void testScheduleWithDurationExtra() {
128127

129128
if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
130129
ArgumentCaptor<JobInfo> jobInfoArgumentCaptor = ArgumentCaptor.forClass(JobInfo.class);
131-
ArgumentCaptor<JobWorkItem> workItemArgumentCaptor = ArgumentCaptor.forClass(JobWorkItem.class);
132130

133-
verify(jobScheduler).enqueue(jobInfoArgumentCaptor.capture(), workItemArgumentCaptor.capture());
131+
verify(jobScheduler).schedule(jobInfoArgumentCaptor.capture());
134132

135133
assertEquals(jobInfoArgumentCaptor.getValue().getIntervalMillis(), duration );
136134
}
@@ -196,7 +194,7 @@ public void testCancel() {
196194
when(pendingIntentFactory.getPendingIntent(intent)).thenReturn(pendingIntent);
197195
serviceScheduler.unschedule(intent);
198196
if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
199-
verify(jobScheduler).cancel(EventIntentService.JOB_ID);
197+
verify(jobScheduler).getAllPendingJobs();
200198
}
201199
else {
202200
verify(alarmManager).cancel(pendingIntent);

event-handler/src/main/java/com/optimizely/ab/android/event_handler/EventDAO.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,8 @@ List<Pair<Long, Event>> getEvents() {
152152
if (cursor != null && !cursor.isClosed()) {
153153
try {
154154
cursor.close();
155+
logger.info("Closed database");
156+
155157
}
156158
catch (Exception e) {
157159
logger.error("Error closing db cursor", e);

event-handler/src/main/java/com/optimizely/ab/android/event_handler/EventIntentService.java

Lines changed: 10 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,6 @@
2727
import com.optimizely.ab.android.shared.Client;
2828
import com.optimizely.ab.android.shared.OptlyStorage;
2929
import com.optimizely.ab.android.shared.ServiceScheduler;
30-
import com.optimizely.ab.android.shared.JobWorkScheduledService;
3130

3231
import org.slf4j.Logger;
3332
import org.slf4j.LoggerFactory;
@@ -41,7 +40,7 @@
4140
* worker queue.
4241
*
4342
*/
44-
public class EventIntentService extends IntentService implements JobWorkScheduledService {
43+
public class EventIntentService extends IntentService {
4544
static final String EXTRA_URL = "com.optimizely.ab.android.EXTRA_URL";
4645
static final String EXTRA_REQUEST_BODY = "com.optimizely.ab.android.EXTRA_REQUEST_BODY";
4746
static final String EXTRA_INTERVAL = "com.optimizely.ab.android.EXTRA_INTERVAL";
@@ -63,7 +62,15 @@ public EventIntentService() {
6362
public void onCreate() {
6463
super.onCreate();
6564

66-
initialize();
65+
OptlyStorage optlyStorage = new OptlyStorage(this);
66+
EventClient eventClient = new EventClient(new Client(optlyStorage,
67+
LoggerFactory.getLogger(Client.class)), LoggerFactory.getLogger(EventClient.class));
68+
EventDAO eventDAO = EventDAO.getInstance(this, "1", LoggerFactory.getLogger(EventDAO.class));
69+
ServiceScheduler serviceScheduler = new ServiceScheduler(
70+
this,
71+
new ServiceScheduler.PendingIntentFactory(this),
72+
LoggerFactory.getLogger(ServiceScheduler.class));
73+
eventDispatcher = new EventDispatcher(this, optlyStorage, eventDAO, eventClient, serviceScheduler, LoggerFactory.getLogger(EventDispatcher.class));
6774
}
6875

6976
/**
@@ -73,11 +80,7 @@ public void onCreate() {
7380

7481
@Override
7582
protected void onHandleIntent(Intent intent) {
76-
onWork(intent);
77-
}
7883

79-
@Override
80-
public void onWork(@Nullable Intent intent) {
8184
if (intent == null) {
8285
logger.warn("Handled a null intent");
8386
return;
@@ -89,20 +92,6 @@ public void onWork(@Nullable Intent intent) {
8992
} else {
9093
logger.warn("Unable to create dependencies needed by intent handler");
9194
}
92-
}
93-
94-
@RequiresApi(api = Build.VERSION_CODES.HONEYCOMB)
95-
@Override
96-
public void initialize() {
97-
OptlyStorage optlyStorage = new OptlyStorage(this);
98-
EventClient eventClient = new EventClient(new Client(optlyStorage,
99-
LoggerFactory.getLogger(Client.class)), LoggerFactory.getLogger(EventClient.class));
100-
EventDAO eventDAO = EventDAO.getInstance(this, "1", LoggerFactory.getLogger(EventDAO.class));
101-
ServiceScheduler serviceScheduler = new ServiceScheduler(
102-
this,
103-
new ServiceScheduler.PendingIntentFactory(this),
104-
LoggerFactory.getLogger(ServiceScheduler.class));
105-
eventDispatcher = new EventDispatcher(this, optlyStorage, eventDAO, eventClient, serviceScheduler, LoggerFactory.getLogger(EventDispatcher.class));
10695

10796
}
10897
}

shared/src/main/AndroidManifest.xml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,12 @@
2020
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
2121
package="com.optimizely.ab.android.shared">
2222

23+
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
24+
2325
<application>
2426
<service android:name=".JobWorkService"
2527
android:permission="android.permission.BIND_JOB_SERVICE" />
28+
<service android:name=".ScheduledJobService"
29+
android:permission="android.permission.BIND_JOB_SERVICE" />
2630
</application>
2731
</manifest>

shared/src/main/java/com/optimizely/ab/android/shared/JobWorkScheduledService.java

Lines changed: 0 additions & 32 deletions
This file was deleted.

0 commit comments

Comments
 (0)