Skip to content

Commit 3fe727f

Browse files
authored
Handles running on ancient Android devices (#32)
- Fixes typos - Adds api level annotations to SDK methods - Makes test app and android modules compile time min sdk 10 - Adds runtime check to to no-op and log if Android api is less than 14 - Tested on a API 10 emulator - Can't really be tested without excessive wrapping of Build.VERSION.SDK_INIT
1 parent 8c5be36 commit 3fe727f

File tree

19 files changed

+102
-15
lines changed

19 files changed

+102
-15
lines changed

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -73,11 +73,11 @@ Tests can be run by right clicking the file in the project pane or by clicking t
7373

7474
## Releasing
7575

76-
The default branch is devel. Feature branch PRs are automatically made against it. When PRs are reviewed and pass checks they should be squased and merged into devel. The version of the SDK in devel should always be the version of the next release plus `-SNAPSHOT`.
76+
The default branch is devel. Feature branch PRs are automatically made against it. When PRs are reviewed and pass checks they should be squashed and merged into devel. The version of the SDK in devel should always be the version of the next release plus `-SNAPSHOT`.
7777

7878
If a beta, or snapshot, build needs to be published simply checkout beta and merge devel. The beta branch should fast forward. Push devel and Travis will start. If the tests pass the build will be sent to our Maven repos on Bintray.
7979

80-
When a release version needs to be published checkout the master branch and merge devel. The master branch should be fast forwared. Remove the `-SNAPSHOT` from the version in `build.gradle` and commit directly onto master. Push master and if the tests pass Travis will publish the version to our Maven repos on Bintray. if the version already exists on Bintray the upload will be rejected. The commit that updates the version should also be tagged with the version number.
80+
When a release version needs to be published checkout the master branch and merge devel. The master branch should be fast forwarded. Remove the `-SNAPSHOT` from the version in `build.gradle` and commit directly onto master. Push master and if the tests pass Travis will publish the version to our Maven repos on Bintray. if the version already exists on Bintray the upload will be rejected. The commit that updates the version should also be tagged with the version number.
8181

8282
Once the next release has been published from the master branch the snapshot version in devel should be bumped to the next targeted version.
8383

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,9 @@
1717

1818
import android.content.Context;
1919
import android.content.Intent;
20+
import android.os.Build;
2021
import android.os.IBinder;
22+
import android.support.annotation.RequiresApi;
2123
import android.support.test.InstrumentationRegistry;
2224
import android.support.test.rule.ServiceTestRule;
2325

@@ -42,6 +44,7 @@ public class DatafileServiceTest {
4244
@Rule
4345
public final ServiceTestRule mServiceRule = new ServiceTestRule();
4446

47+
@RequiresApi(api = Build.VERSION_CODES.HONEYCOMB)
4548
@Test
4649
@Ignore
4750
public void testBinding() throws TimeoutException {
@@ -56,6 +59,7 @@ public void testBinding() throws TimeoutException {
5659
assertTrue(dataFileService.isBound());
5760
}
5861

62+
@RequiresApi(api = Build.VERSION_CODES.HONEYCOMB)
5963
@Test
6064
@Ignore
6165
public void testValidStart() throws TimeoutException {
@@ -70,6 +74,7 @@ public void testValidStart() throws TimeoutException {
7074
verify(logger).info("Started watching project {} in the background", "1");
7175
}
7276

77+
@RequiresApi(api = Build.VERSION_CODES.HONEYCOMB)
7378
@Test
7479
public void testNullIntentStart() throws TimeoutException {
7580
Context context = InstrumentationRegistry.getTargetContext();
@@ -82,6 +87,7 @@ public void testNullIntentStart() throws TimeoutException {
8287
verify(logger).warn("Data file service received a null intent");
8388
}
8489

90+
@RequiresApi(api = Build.VERSION_CODES.HONEYCOMB)
8591
@Test
8692
@Ignore
8793
public void testNoProjectIdIntentStart() throws TimeoutException {

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

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@
1717

1818
import android.content.Context;
1919
import android.content.Intent;
20+
import android.os.Build;
21+
import android.support.annotation.RequiresApi;
2022
import android.support.test.espresso.core.deps.guava.util.concurrent.ListeningExecutorService;
2123
import android.support.test.espresso.core.deps.guava.util.concurrent.MoreExecutors;
2224
import android.support.test.runner.AndroidJUnit4;
@@ -109,6 +111,7 @@ public void stop() {
109111
verify(appContext).unbindService(dataFileServiceConnection);
110112
}
111113

114+
@RequiresApi(api = Build.VERSION_CODES.HONEYCOMB)
112115
@Test
113116
public void injectOptimizely() {
114117
Context context = mock(Context.class);
@@ -130,6 +133,7 @@ public void injectOptimizely() {
130133
verify(startListener).onStart(any(AndroidOptimizely.class));
131134
}
132135

136+
@RequiresApi(api = Build.VERSION_CODES.HONEYCOMB)
133137
@Test
134138
public void injectOptimizelyNullListener() {
135139
Context context = mock(Context.class);
@@ -154,6 +158,7 @@ public void injectOptimizelyNullListener() {
154158
assertEquals(optimizelyManager.getProjectId(), intent.getStringExtra(DataFileService.EXTRA_PROJECT_ID));
155159
}
156160

161+
@RequiresApi(api = Build.VERSION_CODES.HONEYCOMB)
157162
@Test
158163
public void injectOptimizelyHandlesInvalidDataFile() {
159164
Context context = mock(Context.class);
@@ -178,6 +183,7 @@ public void injectOptimizelyHandlesInvalidDataFile() {
178183
assertEquals(optimizelyManager.getProjectId(), intent.getStringExtra(DataFileService.EXTRA_PROJECT_ID));
179184
}
180185

186+
@RequiresApi(api = Build.VERSION_CODES.HONEYCOMB)
181187
@Test
182188
public void injectOptimizelyDoesNotDuplicateCallback() {
183189
Context context = mock(Context.class);
@@ -209,9 +215,4 @@ public void injectOptimizelyDoesNotDuplicateCallback() {
209215

210216
verify(logger).info("No listener to send Optimizely to");
211217
}
212-
213-
// @Test
214-
// public void getOptimizelyFromCompiledDataFile() {
215-
//
216-
// }
217218
}

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,10 @@
1717
package com.optimizely.ab.android.sdk;
1818

1919
import android.os.AsyncTask;
20+
import android.os.Build;
2021
import android.support.annotation.NonNull;
2122
import android.support.annotation.Nullable;
23+
import android.support.annotation.RequiresApi;
2224

2325
import com.optimizely.ab.android.shared.Cache;
2426
import com.optimizely.ab.android.shared.Client;
@@ -44,6 +46,7 @@ class DataFileLoader {
4446
this.taskChain = taskChain;
4547
}
4648

49+
@RequiresApi(api = Build.VERSION_CODES.HONEYCOMB)
4750
boolean getDataFile(@NonNull String projectId, @Nullable DataFileLoadedListener dataFileLoadedListener) {
4851
taskChain.start(projectId, dataFileLoadedListener);
4952

@@ -62,6 +65,7 @@ static class TaskChain {
6265
this.executor = Executors.newSingleThreadExecutor();
6366
}
6467

68+
@RequiresApi(api = Build.VERSION_CODES.HONEYCOMB)
6569
void start(@NonNull String projectId, @Nullable DataFileLoadedListener dataFileLoadedListener) {
6670
DataFileClient dataFileClient = new DataFileClient(
6771
new Client(new OptlyStorage(dataFileService), LoggerFactory.getLogger(OptlyStorage.class)),

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,10 @@
1919
import android.app.Service;
2020
import android.content.Intent;
2121
import android.os.Binder;
22+
import android.os.Build;
2223
import android.os.IBinder;
2324
import android.support.annotation.NonNull;
25+
import android.support.annotation.RequiresApi;
2426

2527
import com.optimizely.ab.android.shared.Cache;
2628

@@ -42,6 +44,7 @@ public class DataFileService extends Service {
4244
* @hide
4345
* @see Service#onStartCommand(Intent, int, int)
4446
*/
47+
@RequiresApi(api = Build.VERSION_CODES.HONEYCOMB)
4548
@Override
4649
public int onStartCommand(Intent intent, int flags, int startId) {
4750
if (intent != null) {
@@ -93,6 +96,7 @@ void stop() {
9396
stopSelf();
9497
}
9598

99+
@RequiresApi(api = Build.VERSION_CODES.HONEYCOMB)
96100
void getDataFile(String projectId, DataFileLoader dataFileLoader, DataFileLoadedListener loadedListener) {
97101
dataFileLoader.getDataFile(projectId, loadedListener);
98102
}

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

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

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

19+
import android.annotation.TargetApi;
1920
import android.app.Activity;
2021
import android.app.AlarmManager;
2122
import android.app.Application;
@@ -25,11 +26,13 @@
2526
import android.content.ServiceConnection;
2627
import android.content.res.Resources;
2728
import android.os.AsyncTask;
29+
import android.os.Build;
2830
import android.os.Bundle;
2931
import android.os.IBinder;
3032
import android.support.annotation.NonNull;
3133
import android.support.annotation.Nullable;
3234
import android.support.annotation.RawRes;
35+
import android.support.annotation.RequiresApi;
3336

3437
import com.optimizely.ab.Optimizely;
3538
import com.optimizely.ab.android.event_handler.OptlyEventHandler;
@@ -121,7 +124,11 @@ void setOptimizelyStartListener(@Nullable OptimizelyStartListener optimizelyStar
121124
* @param activity an Activity, used to automatically unbind {@link DataFileService}
122125
* @param optimizelyStartListener callback that {@link AndroidOptimizely} instances are sent to.
123126
*/
127+
@TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
124128
public void start(@NonNull Activity activity, @NonNull OptimizelyStartListener optimizelyStartListener) {
129+
if (!isAndroidVersionSupported()) {
130+
return;
131+
}
125132
activity.getApplication().registerActivityLifecycleCallbacks(new OptlyActivityLifecycleCallbacks(this));
126133
start(activity.getApplication(), optimizelyStartListener);
127134
}
@@ -135,12 +142,16 @@ public void start(@NonNull Activity activity, @NonNull OptimizelyStartListener o
135142
* @param optimizelyStartListener callback that {@link AndroidOptimizely} instances are sent to.
136143
*/
137144
public void start(@NonNull Context context, @NonNull OptimizelyStartListener optimizelyStartListener) {
145+
if (!isAndroidVersionSupported()) {
146+
return;
147+
}
138148
this.optimizelyStartListener = optimizelyStartListener;
139149
this.dataFileServiceConnection = new DataFileServiceConnection(this);
140150
final Intent intent = new Intent(context.getApplicationContext(), DataFileService.class);
141151
context.getApplicationContext().bindService(intent, dataFileServiceConnection, Context.BIND_AUTO_CREATE);
142152
}
143153

154+
@TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
144155
void stop(@NonNull Activity activity, @NonNull OptlyActivityLifecycleCallbacks optlyActivityLifecycleCallbacks) {
145156
stop(activity);
146157
activity.getApplication().unregisterActivityLifecycleCallbacks(optlyActivityLifecycleCallbacks);
@@ -156,6 +167,9 @@ void stop(@NonNull Activity activity, @NonNull OptlyActivityLifecycleCallbacks o
156167
*/
157168
@SuppressWarnings("WeakerAccess")
158169
public void stop(@NonNull Context context) {
170+
if (!isAndroidVersionSupported()) {
171+
return;
172+
}
159173
if (dataFileServiceConnection != null && dataFileServiceConnection.isBound()) {
160174
context.getApplicationContext().unbindService(dataFileServiceConnection);
161175
}
@@ -178,6 +192,7 @@ public void stop(@NonNull Context context) {
178192
*/
179193
@NonNull
180194
public AndroidOptimizely getOptimizely() {
195+
isAndroidVersionSupported();
181196
return androidOptimizely;
182197
}
183198

@@ -194,6 +209,10 @@ public AndroidOptimizely getOptimizely() {
194209
*/
195210
@NonNull
196211
public AndroidOptimizely getOptimizely(@NonNull Context context, @RawRes int dataFileRes) {
212+
if (!isAndroidVersionSupported()) {
213+
return androidOptimizely;
214+
}
215+
197216
AndroidUserExperimentRecord userExperimentRecord =
198217
(AndroidUserExperimentRecord) AndroidUserExperimentRecord.newInstance(getProjectId(), context);
199218
// Blocking File I/O is necessary here in order to provide a synchronous API
@@ -229,6 +248,7 @@ String getProjectId() {
229248
return projectId;
230249
}
231250

251+
@RequiresApi(api = Build.VERSION_CODES.HONEYCOMB)
232252
void injectOptimizely(@NonNull final Context context, final @NonNull AndroidUserExperimentRecord userExperimentRecord, @NonNull final ServiceScheduler serviceScheduler, @NonNull final String dataFile) {
233253
AsyncTask<Void, Void, UserExperimentRecord> initUserExperimentRecordTask = new AsyncTask<Void, Void, UserExperimentRecord>() {
234254
@Override
@@ -275,6 +295,7 @@ private AndroidOptimizely buildOptimizely(@NonNull Context context, @NonNull Str
275295
return new AndroidOptimizely(optimizely, LoggerFactory.getLogger(AndroidOptimizely.class));
276296
}
277297

298+
@RequiresApi(api = Build.VERSION_CODES.ICE_CREAM_SANDWICH)
278299
static class OptlyActivityLifecycleCallbacks implements Application.ActivityLifecycleCallbacks {
279300

280301
@NonNull private OptimizelyManager optimizelyManager;
@@ -360,6 +381,7 @@ static class DataFileServiceConnection implements ServiceConnection {
360381
* @see ServiceConnection#onServiceConnected(ComponentName, IBinder)
361382
* @hide
362383
*/
384+
@RequiresApi(api = Build.VERSION_CODES.HONEYCOMB)
363385
@Override
364386
public void onServiceConnected(ComponentName className,
365387
IBinder service) {
@@ -464,4 +486,14 @@ public OptimizelyManager build() {
464486

465487
}
466488
}
489+
490+
private boolean isAndroidVersionSupported() {
491+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
492+
return true;
493+
} else {
494+
logger.warn("Optimizely will not work on this phone. It's Android version {} is less the minimum supported" +
495+
"version {}", Build.VERSION.SDK_INT, Build.VERSION_CODES.ICE_CREAM_SANDWICH);
496+
return false;
497+
}
498+
}
467499
}

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@
1515
*/
1616
package com.optimizely.ab.android.sdk;
1717

18+
import android.os.Build;
19+
import android.support.annotation.RequiresApi;
20+
1821
import org.junit.Test;
1922
import org.junit.runner.RunWith;
2023
import org.mockito.Mock;
@@ -129,6 +132,7 @@ public void loadFromCacheNullDataFile() {
129132
verify(dataFileLoadedListener, never()).onDataFileLoaded(any(String.class));
130133
}
131134

135+
@RequiresApi(api = Build.VERSION_CODES.HONEYCOMB)
132136
@Test
133137
public void getDataFile() {
134138
DataFileLoader.TaskChain taskChain = mock(DataFileLoader.TaskChain.class);

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616
package com.optimizely.ab.android.sdk;
1717

1818
import android.content.Context;
19+
import android.os.Build;
20+
import android.support.annotation.RequiresApi;
1921

2022
import com.optimizely.ab.android.shared.ServiceScheduler;
2123
import com.optimizely.user_experiment_record.AndroidUserExperimentRecord;
@@ -50,6 +52,7 @@ public void setup() {
5052
dataFileServiceConnection = new OptimizelyManager.DataFileServiceConnection(optimizelyManager);
5153
}
5254

55+
@RequiresApi(api = Build.VERSION_CODES.HONEYCOMB)
5356
@Test
5457
public void onServiceConnected() {
5558
DataFileService.LocalBinder binder = mock(DataFileService.LocalBinder.class);
@@ -66,6 +69,7 @@ public void onServiceConnected() {
6669
verify(optimizelyManager).injectOptimizely(any(Context.class), any(AndroidUserExperimentRecord.class), any(ServiceScheduler.class), eq(""));
6770
}
6871

72+
@RequiresApi(api = Build.VERSION_CODES.HONEYCOMB)
6973
@Test
7074
public void onServiceConnectedNotBoundWhenDataFileLoaded() {
7175
DataFileService.LocalBinder binder = mock(DataFileService.LocalBinder.class);
@@ -86,6 +90,7 @@ public void onServiceConnectedNotBoundWhenDataFileLoaded() {
8690
verify(optimizelyManager, never()).injectOptimizely(any(Context.class), any(AndroidUserExperimentRecord.class), any(ServiceScheduler.class), eq(""));
8791
}
8892

93+
@RequiresApi(api = Build.VERSION_CODES.HONEYCOMB)
8994
@Test
9095
public void onServiceConnectedNullServiceFromBinder() {
9196
DataFileService.LocalBinder binder = mock(DataFileService.LocalBinder.class);

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616
package com.optimizely.ab.android.sdk;
1717

1818
import android.app.Activity;
19+
import android.os.Build;
20+
import android.support.annotation.RequiresApi;
1921

2022
import org.junit.Before;
2123
import org.junit.Test;
@@ -35,11 +37,13 @@ public class OptimizelyManagerOptlyActivityLifecycleCallbacksTest {
3537
@Mock Activity activity;
3638
private OptimizelyManager.OptlyActivityLifecycleCallbacks optlyActivityLifecycleCallbacks;
3739

40+
@RequiresApi(api = Build.VERSION_CODES.ICE_CREAM_SANDWICH)
3841
@Before
3942
public void setup() {
4043
optlyActivityLifecycleCallbacks = new OptimizelyManager.OptlyActivityLifecycleCallbacks(optimizelyManager);
4144
}
4245

46+
@RequiresApi(api = Build.VERSION_CODES.ICE_CREAM_SANDWICH)
4347
@Test
4448
public void onActivityStopped() {
4549
optlyActivityLifecycleCallbacks.onActivityStopped(activity);

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@
1717
package com.optimizely.ab.android.event_handler;
1818

1919
import android.content.Context;
20+
import android.os.Build;
21+
import android.support.annotation.RequiresApi;
2022
import android.support.test.InstrumentationRegistry;
2123
import android.support.test.runner.AndroidJUnit4;
2224
import android.util.Pair;
@@ -47,6 +49,7 @@ public class EventDAOTest {
4749
private Logger logger;
4850
private Context context;
4951

52+
@RequiresApi(api = Build.VERSION_CODES.HONEYCOMB)
5053
@Before
5154
public void setupEventDAO() {
5255
logger = mock(Logger.class);

0 commit comments

Comments
 (0)