diff --git a/.travis.yml b/.travis.yml index 725a96a9e..d5b72d494 100644 --- a/.travis.yml +++ b/.travis.yml @@ -25,5 +25,5 @@ before_script: - adb shell input keyevent 82 & script: - ./gradlew cleanAllModules - - ./gradlew testAllModules + - ./gradlew testAllModulesTravis - if [[ -n $TRAVIS_TAG ]]; then ./gradlew ship; fi \ No newline at end of file diff --git a/android-sdk/src/main/java/com/optimizely/ab/android/sdk/OptimizelyManager.java b/android-sdk/src/main/java/com/optimizely/ab/android/sdk/OptimizelyManager.java index 02a4895a6..af05b6575 100644 --- a/android-sdk/src/main/java/com/optimizely/ab/android/sdk/OptimizelyManager.java +++ b/android-sdk/src/main/java/com/optimizely/ab/android/sdk/OptimizelyManager.java @@ -89,6 +89,16 @@ public class OptimizelyManager { this.logger = logger; } + @NonNull + public Long getDataFileDownloadInterval() { + return dataFileDownloadInterval; + } + + @NonNull + public TimeUnit getDataFileDownloadIntervalTimeUnit() { + return dataFileDownloadIntervalTimeUnit; + } + /** * Returns the {@link OptimizelyManager} builder * @@ -606,6 +616,14 @@ public Builder withDataFileDownloadInterval(long interval, @NonNull TimeUnit tim public OptimizelyManager build() { final Logger logger = LoggerFactory.getLogger(OptimizelyManager.class); + // AlarmManager doesn't allow intervals less than 60 seconds + if (dataFileDownloadIntervalTimeUnit.toMillis(dataFileDownloadInterval) < (60 * 1000)) { + dataFileDownloadIntervalTimeUnit = TimeUnit.SECONDS; + dataFileDownloadInterval = 60L; + logger.warn("Minimum datafile polling interval is 60 seconds. " + + "Defaulting to 60 seconds."); + } + return new OptimizelyManager(projectId, eventHandlerDispatchInterval, eventHandlerDispatchIntervalTimeUnit, diff --git a/android-sdk/src/test/java/com/optimizely/ab/android/sdk/OptimizelyManagerBuilderTest.java b/android-sdk/src/test/java/com/optimizely/ab/android/sdk/OptimizelyManagerBuilderTest.java new file mode 100644 index 000000000..e4e9e4cfe --- /dev/null +++ b/android-sdk/src/test/java/com/optimizely/ab/android/sdk/OptimizelyManagerBuilderTest.java @@ -0,0 +1,57 @@ +/**************************************************************************** + * Copyright 2017, Optimizely, Inc. and contributors * + * * + * Licensed under the Apache License, Version 2.0 (the "License"); * + * you may not use this file except in compliance with the License. * + * You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, software * + * distributed under the License is distributed on an "AS IS" BASIS, * + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * + * See the License for the specific language governing permissions and * + * limitations under the License. * + ***************************************************************************/ + +package com.optimizely.ab.android.sdk; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.runners.MockitoJUnitRunner; + +import java.util.concurrent.TimeUnit; + +import static junit.framework.Assert.assertEquals; + +@RunWith(MockitoJUnitRunner.class) +public class OptimizelyManagerBuilderTest { + + /** + * Verify that building the {@link OptimizelyManager} with a polling interval less than 60 + * seconds defaults to 60 seconds. + */ + @Test + public void testBuildWithInvalidPollingInterval() { + OptimizelyManager manager = OptimizelyManager.builder("1") + .withDataFileDownloadInterval(5, TimeUnit.SECONDS) + .build(); + + assertEquals(60L, manager.getDataFileDownloadInterval().longValue()); + assertEquals(TimeUnit.SECONDS, manager.getDataFileDownloadIntervalTimeUnit()); + } + + /** + * Verify that building the {@link OptimizelyManager} with a polling interval greater than 60 + * seconds is properly registered. + */ + @Test + public void testBuildWithValidPollingInterval() { + OptimizelyManager manager = OptimizelyManager.builder("1") + .withDataFileDownloadInterval(61, TimeUnit.SECONDS) + .build(); + + assertEquals(61L, manager.getDataFileDownloadInterval().longValue()); + assertEquals(TimeUnit.SECONDS, manager.getDataFileDownloadIntervalTimeUnit()); + } +} diff --git a/build.gradle b/build.gradle index 70197b94a..0eac3040f 100644 --- a/build.gradle +++ b/build.gradle @@ -84,6 +84,12 @@ task testAllModules << { logger.info("Running android tests for all modules") } -testAllModules.dependsOn(':android-sdk:connectedAndroidTest', ':android-sdk:test', +task testAllModulesTravis << { + logger.info("Running android tests for Travis") +} + +testAllModulesTravis.dependsOn(':android-sdk:connectedAndroidTest', ':android-sdk:test', ':event-handler:connectedAndroidTest', ':event-handler:test', ':user-profile:connectedAndroidTest', ':shared:connectedAndroidTest') + +testAllModules.dependsOn('testAllModulesTravis', ':test-app:connectedAndroidTest') \ No newline at end of file diff --git a/test-app/README.md b/test-app/README.md new file mode 100644 index 000000000..2b9637a89 --- /dev/null +++ b/test-app/README.md @@ -0,0 +1,32 @@ +# Optimizely X Android SDK Demo App + +This module houses the demo app used to demonstrate how to get started with the Android SDK. The app +is also used to run integration tests on using the Android Espresso framework. + +## Experiments Run + +The experiments run in this demo app are part of the mobile-test@optimizely.com account. + +We run the following experiment: + - Background change in second activity, which is loaded after the splash screen. + +## How it works + +The SDK is implemented in the following way: + - The splash screen initializes the Optimizely manager asynchronously. This starts the datafile + fetch. + - Once the datafile is fetched and the Optimizely manager is started, we use grab the `optimizelyClient` + from the manager and use it to activate the `background_experiment`. This buckets the user and sends + an impression event. + - We then use the bucketed variation to determine which activity to show. `VariationAActivity` for + `variation_a` and `VariationBActivity` for `variation_b`. + - Each of those activities include a `Test Conversion` button. + - Clicking on that button will call `optimizelyClient.track()` and send a conversion event for the + event named `sample_conversion`. + - Then the application will navigate to the conversion page to confirm that a conversion event has + been sent. + +## Running the test app + +Run `./gradlew test-app:connectedAndroidTest` to run the Espresso tests. + diff --git a/test-app/src/androidTest/java/com/optimizely/ab/android/test_app/MainActivityEspressoTest.java b/test-app/src/androidTest/java/com/optimizely/ab/android/test_app/MainActivityEspressoTest.java index 8e8c92014..dfd379d16 100644 --- a/test-app/src/androidTest/java/com/optimizely/ab/android/test_app/MainActivityEspressoTest.java +++ b/test-app/src/androidTest/java/com/optimizely/ab/android/test_app/MainActivityEspressoTest.java @@ -1,18 +1,18 @@ -/* - * Copyright 2016, Optimizely - *

- * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - *

- * http://www.apache.org/licenses/LICENSE-2.0 - *

- * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ +/**************************************************************************** + * Copyright 2017, Optimizely, Inc. and contributors * + * * + * Licensed under the Apache License, Version 2.0 (the "License"); * + * you may not use this file except in compliance with the License. * + * You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, software * + * distributed under the License is distributed on an "AS IS" BASIS, * + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * + * See the License for the specific language governing permissions and * + * limitations under the License. * + ***************************************************************************/ package com.optimizely.ab.android.test_app; import android.app.AlarmManager; @@ -47,6 +47,7 @@ import static android.support.test.espresso.Espresso.onView; import static android.support.test.espresso.action.ViewActions.click; import static android.support.test.espresso.assertion.ViewAssertions.matches; +import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed; import static android.support.test.espresso.matcher.ViewMatchers.withId; import static android.support.test.espresso.matcher.ViewMatchers.withText; import static junit.framework.Assert.assertFalse; @@ -62,7 +63,7 @@ public class MainActivityEspressoTest { private ServiceScheduler serviceScheduler; private Intent dataFileServiceIntent, eventIntentService; - private ActivityTestRule activityTestRule = new ActivityTestRule<>(MainActivity.class); + private ActivityTestRule activityTestRule = new ActivityTestRule<>(SplashScreenActivity.class); @Rule public TestRule chain = RuleChain .outerRule(new ExternalResource() { @Override @@ -148,38 +149,25 @@ protected void after() { public void experimentActivationForWhitelistUser() throws InterruptedException { // Check that the text was changed. // These tests are pointed at a real project. - // The user 'test_user` is in the whitelist for variation 1 for experiment 0 and experiment 1. - onView(withId(R.id.button_1)) - .check(matches(withText(context.getString(R.string.main_act_button_1_text_var_1)))); + // The user 'test_user` is in the whitelist for variation_a for experiment background_experiment + onView(withId(R.id.tv_variation_a_text_1)) + .check(matches(isDisplayed())); // Espresso will wait for Optimizely to start due to the registered idling resources - onView(withId(R.id.text_view_1)) - .check(matches(withText(context.getString(R.string.main_act_text_view_1_var_1)))); - assertTrue(serviceScheduler.isScheduled(dataFileServiceIntent)); - onView(withId(R.id.button_1)) // withId(R.id.my_view) is a ViewMatcher - .perform(click()); // click() is a ViewAction - - onView(withId(R.id.text_view_1)) - .check(matches(withText(context.getString(R.string.secondary_frag_text_view_1_var_1)))); - - onView(withId(R.id.button_1)) // withId(R.id.my_view) is a ViewMatcher - .perform(click()); // click() is a ViewAction + onView(withId(R.id.btn_variation_conversion)) // withId(R.id.my_view) is a ViewMatcher + .perform(click()); // click() is a ViewAction List> events = CountingIdlingResourceManager.getEvents(); - assertTrue(events.size() == 6); + assertTrue(events.size() == 2); Iterator> iterator = events.iterator(); while (iterator.hasNext()) { Pair event = iterator.next(); final String url = event.first; final String payload = event.second; - if (url.equals("https://logx.optimizely.com/log/decision") && payload.contains("7676481120") && payload.contains("7661891902") - || url.equals("https://logx.optimizely.com/log/decision") && payload.contains("7651112186") && payload.contains("7674261140") - || url.equals("https://logx.optimizely.com/log/event") && payload.contains("experiment_0") - || url.equals("https://logx.optimizely.com/log/event") && payload.contains("experiment_1") - || url.equals("https://logx.optimizely.com/log/decision") && payload.contains("7680080715") && payload.contains("7685562539") - || url.equals("https://logx.optimizely.com/log/event") && payload.contains("experiment_2")) { + if (url.equals("https://logx.optimizely.com/log/decision") && payload.contains("8126664113") && payload.contains("8146590584") + || url.equals("https://logx.optimizely.com/log/event") && payload.contains("sample_conversion")) { iterator.remove(); } } @@ -187,7 +175,6 @@ public void experimentActivationForWhitelistUser() throws InterruptedException { MyApplication myApplication = (MyApplication) activityTestRule.getActivity().getApplication(); UserProfile userProfile = myApplication.getOptimizelyManager().getUserProfile(); // Being in the white list should override user profile - assertNull(userProfile.lookup("test_user", "experiment_0")); - assertNull(userProfile.lookup("test_user", "experiment_1")); + assertNull(userProfile.lookup("test_user", "background_experiment")); } } diff --git a/test-app/src/main/AndroidManifest.xml b/test-app/src/main/AndroidManifest.xml index 58f4bb3b8..bd3bceb46 100644 --- a/test-app/src/main/AndroidManifest.xml +++ b/test-app/src/main/AndroidManifest.xml @@ -1,9 +1,26 @@ - + + - - + + - + + + - + - + - - - - - + + + + \ No newline at end of file diff --git a/test-app/src/main/java/com/optimizely/ab/android/test_app/ActivationErrorActivity.java b/test-app/src/main/java/com/optimizely/ab/android/test_app/ActivationErrorActivity.java new file mode 100644 index 000000000..8df43b1ef --- /dev/null +++ b/test-app/src/main/java/com/optimizely/ab/android/test_app/ActivationErrorActivity.java @@ -0,0 +1,38 @@ +/**************************************************************************** + * Copyright 2017, Optimizely, Inc. and contributors * + * * + * Licensed under the Apache License, Version 2.0 (the "License"); * + * you may not use this file except in compliance with the License. * + * You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, software * + * distributed under the License is distributed on an "AS IS" BASIS, * + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * + * See the License for the specific language governing permissions and * + * limitations under the License. * + ***************************************************************************/ +package com.optimizely.ab.android.test_app; + +import android.support.v7.app.AppCompatActivity; +import android.os.Bundle; +import android.view.View; +import android.widget.Button; + +public class ActivationErrorActivity extends AppCompatActivity { + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_activation_error); + Button btnConversion = (Button)findViewById(R.id.btn_conversion_error_back); + + btnConversion.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + onBackPressed(); + } + }); + } +} diff --git a/test-app/src/main/java/com/optimizely/ab/android/test_app/ConversionFragment.java b/test-app/src/main/java/com/optimizely/ab/android/test_app/ConversionFragment.java new file mode 100644 index 000000000..c46e203cd --- /dev/null +++ b/test-app/src/main/java/com/optimizely/ab/android/test_app/ConversionFragment.java @@ -0,0 +1,66 @@ +/**************************************************************************** + * Copyright 2017, Optimizely, Inc. and contributors * + * * + * Licensed under the Apache License, Version 2.0 (the "License"); * + * you may not use this file except in compliance with the License. * + * You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, software * + * distributed under the License is distributed on an "AS IS" BASIS, * + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * + * See the License for the specific language governing permissions and * + * limitations under the License. * + ***************************************************************************/ + +package com.optimizely.ab.android.test_app; + +import android.app.Fragment; +import android.content.Intent; +import android.os.Bundle; +import android.support.annotation.Nullable; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.Button; + +import com.optimizely.ab.android.sdk.OptimizelyClient; +import com.optimizely.ab.android.sdk.OptimizelyManager; +import com.optimizely.ab.android.shared.CountingIdlingResourceManager; + +public class ConversionFragment extends Fragment { + + Button conversionButton; + + @Nullable + @Override + public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { + return inflater.inflate(R.layout.fragment_conversion, container, false); + } + + @Override + public void onViewCreated(View view, Bundle savedInstanceState) { + conversionButton = (Button) view.findViewById(R.id.btn_variation_conversion); + + final MyApplication myApplication = (MyApplication) getActivity().getApplication(); + final OptimizelyManager optimizelyManager = myApplication.getOptimizelyManager(); + + conversionButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + String userId = myApplication.getAnonUserId(); + + // This tracks a conversion event for the event named `sample_conversion` + OptimizelyClient optimizely = optimizelyManager.getOptimizely(); + optimizely.track("sample_conversion", userId); + + // Utility method for verifying event dispatches in our automated tests + CountingIdlingResourceManager.increment(); // increment for conversion event + + Intent intent = new Intent(myApplication.getBaseContext(), EventConfirmationActivity.class); + startActivity(intent); + } + }); + } +} diff --git a/test-app/src/main/java/com/optimizely/ab/android/test_app/EventConfirmationActivity.java b/test-app/src/main/java/com/optimizely/ab/android/test_app/EventConfirmationActivity.java new file mode 100644 index 000000000..fcfa19f91 --- /dev/null +++ b/test-app/src/main/java/com/optimizely/ab/android/test_app/EventConfirmationActivity.java @@ -0,0 +1,28 @@ +/**************************************************************************** + * Copyright 2017, Optimizely, Inc. and contributors * + * * + * Licensed under the Apache License, Version 2.0 (the "License"); * + * you may not use this file except in compliance with the License. * + * You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, software * + * distributed under the License is distributed on an "AS IS" BASIS, * + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * + * See the License for the specific language governing permissions and * + * limitations under the License. * + ***************************************************************************/ +package com.optimizely.ab.android.test_app; + +import android.support.v7.app.AppCompatActivity; +import android.os.Bundle; + +public class EventConfirmationActivity extends AppCompatActivity { + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_event_confirmation); + } +} diff --git a/test-app/src/main/java/com/optimizely/ab/android/test_app/MainActivity.java b/test-app/src/main/java/com/optimizely/ab/android/test_app/MainActivity.java deleted file mode 100644 index 6db83ef29..000000000 --- a/test-app/src/main/java/com/optimizely/ab/android/test_app/MainActivity.java +++ /dev/null @@ -1,104 +0,0 @@ -/* - * Copyright 2016, Optimizely - *

- * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - *

- * http://www.apache.org/licenses/LICENSE-2.0 - *

- * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.optimizely.ab.android.test_app; - -import android.content.Intent; -import android.os.Bundle; -import android.support.annotation.Nullable; -import android.support.v7.app.AppCompatActivity; -import android.view.View; -import android.widget.Button; -import android.widget.TextView; - -import com.optimizely.ab.android.sdk.OptimizelyClient; -import com.optimizely.ab.android.sdk.OptimizelyManager; -import com.optimizely.ab.android.sdk.OptimizelyStartListener; -import com.optimizely.ab.android.shared.CountingIdlingResourceManager; -import com.optimizely.ab.config.Variation; - -public class MainActivity extends AppCompatActivity { - - // The Idling Resource which will be null in production. - @Nullable private static CountingIdlingResourceManager countingIdlingResourceManager; - private OptimizelyManager optimizelyManager; - private MyApplication myApplication; - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.activity_main); - Button button = (Button) findViewById(R.id.button_1); - - // This could also be done via DI framework such as Dagger - myApplication = (MyApplication) getApplication(); - optimizelyManager = myApplication.getOptimizelyManager(); - - // Load Optimizely from a compiled in data file - final OptimizelyClient optimizely = optimizelyManager.initialize(this, R.raw.data_file); - CountingIdlingResourceManager.increment(); // For impression event - Variation variation = optimizely.activate("experiment_0", myApplication.getAnonUserId(), myApplication.getAttributes()); - if (variation != null) { - if (variation.is("variation_1")) { - button.setText(R.string.main_act_button_1_text_var_1); - } else if (variation.is("variation_2")) { - button.setText(R.string.main_act_button_1_text_var_2); - } - } else { - button.setText(R.string.main_act_button_1_text_default); - } - - button.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - CountingIdlingResourceManager.increment(); - optimizely.track("experiment_0", myApplication.getAnonUserId(), myApplication.getAttributes()); - // For track event - - v.getContext().getResources().getString(R.string.app_name); - Intent intent = new Intent(v.getContext(), SecondaryActivity.class); - startActivity(intent); - } - }); - } - - @Override - protected void onStart() { - super.onStart(); - - CountingIdlingResourceManager.increment(); // For Optimizely starting - optimizelyManager.initialize(this, new OptimizelyStartListener() { - @Override - public void onStart(OptimizelyClient optimizely) { - CountingIdlingResourceManager.decrement(); // For Optimizely starting - TextView textView1 = (TextView) findViewById(R.id.text_view_1); - textView1.setVisibility(View.VISIBLE); - CountingIdlingResourceManager.increment(); // For impression event - Variation variation = optimizely.activate("experiment_1", myApplication.getAnonUserId()); - if (variation != null) { - if (variation.is("variation_1")) { - textView1.setText(R.string.main_act_text_view_1_var_1); - } else if (variation.is("variation_2")) { - textView1.setText(R.string.main_act_text_view_1_var_2); - } - } else { - textView1.setText(R.string.main_act_text_view_1_default); - } - } - }); - } - - -} diff --git a/test-app/src/main/java/com/optimizely/ab/android/test_app/MyApplication.java b/test-app/src/main/java/com/optimizely/ab/android/test_app/MyApplication.java index 4779aa599..dced661d3 100644 --- a/test-app/src/main/java/com/optimizely/ab/android/test_app/MyApplication.java +++ b/test-app/src/main/java/com/optimizely/ab/android/test_app/MyApplication.java @@ -1,18 +1,18 @@ -/** - * Copyright 2016, Optimizely - *

- * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - *

- * http://www.apache.org/licenses/LICENSE-2.0 - *

- * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ +/**************************************************************************** + * Copyright 2017, Optimizely, Inc. and contributors * + * * + * Licensed under the Apache License, Version 2.0 (the "License"); * + * you may not use this file except in compliance with the License. * + * You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, software * + * distributed under the License is distributed on an "AS IS" BASIS, * + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * + * See the License for the specific language governing permissions and * + * limitations under the License. * + ***************************************************************************/ package com.optimizely.ab.android.test_app; import android.app.Application; @@ -30,7 +30,10 @@ public class MyApplication extends Application { - public static final String PROJECT_ID = "7664231436"; + // Project ID owned by mobile-test@optimizely.com + // if you'd like to configure your own experiment please check out https://developers.optimizely.com/x/solutions/sdks/getting-started/index.html?language=android&platform=mobile + // to create your own project and experiment. Then just replace your project ID below. + public static final String PROJECT_ID = "8136462271"; private OptimizelyManager optimizelyManager; public OptimizelyManager getOptimizelyManager() { @@ -47,10 +50,15 @@ public Map getAttributes() { } @NonNull public String getAnonUserId() { + // this is a convenience method that creates and persists an anonymous user id, + // which we need to pass into the activate and track calls SharedPreferences sharedPreferences = getSharedPreferences("user", Context.MODE_PRIVATE); String id = sharedPreferences.getString("userId", null); if (id == null) { id = UUID.randomUUID().toString(); + + // comment this out to get a brand new user id every time this function is called. + // useful for incrementing results page count for QA purposes sharedPreferences.edit().putString("userId", id).apply(); } return id; diff --git a/test-app/src/main/java/com/optimizely/ab/android/test_app/NotificationService.java b/test-app/src/main/java/com/optimizely/ab/android/test_app/NotificationService.java index b0fcb2e45..ac689c148 100644 --- a/test-app/src/main/java/com/optimizely/ab/android/test_app/NotificationService.java +++ b/test-app/src/main/java/com/optimizely/ab/android/test_app/NotificationService.java @@ -1,18 +1,18 @@ -/** - * Copyright 2016, Optimizely - *

- * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - *

- * http://www.apache.org/licenses/LICENSE-2.0 - *

- * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ +/**************************************************************************** + * Copyright 2017, Optimizely, Inc. and contributors * + * * + * Licensed under the Apache License, Version 2.0 (the "License"); * + * you may not use this file except in compliance with the License. * + * You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, software * + * distributed under the License is distributed on an "AS IS" BASIS, * + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * + * See the License for the specific language governing permissions and * + * limitations under the License. * + ***************************************************************************/ package com.optimizely.ab.android.test_app; import android.app.IntentService; @@ -52,27 +52,28 @@ private void showNotification() { .setSmallIcon(android.R.drawable.ic_notification_clear_all) .setContentTitle(getString(R.string.notification_service_notification_title)) .setContentText(getString(R.string.notification_service_notification_text)); + // @TODO(mng): Add notification back when we add more complex scenario // Creates an explicit intent for an Activity in your app - Intent resultIntent = new Intent(this, MainActivity.class); - - // The stack builder object will contain an artificial back stack for the - // started Activity. - // This ensures that navigating backward from the Activity leads out of - // your application to the Home screen. - TaskStackBuilder stackBuilder = TaskStackBuilder.create(this); - // Adds the back stack for the Intent (but not the Intent itself) - stackBuilder.addParentStack(SecondaryActivity.class); - // Adds the Intent that starts the Activity to the top of the stack - stackBuilder.addNextIntent(resultIntent); - PendingIntent resultPendingIntent = - stackBuilder.getPendingIntent( - 0, - PendingIntent.FLAG_UPDATE_CURRENT - ); - mBuilder.setContentIntent(resultPendingIntent); - NotificationManager mNotificationManager = - (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); - // mId allows you to update the notification later on. - mNotificationManager.notify(0, mBuilder.build()); +// Intent resultIntent = new Intent(this, MainActivity.class); +// +// // The stack builder object will contain an artificial back stack for the +// // started Activity. +// // This ensures that navigating backward from the Activity leads out of +// // your application to the Home screen. +// TaskStackBuilder stackBuilder = TaskStackBuilder.create(this); +// // Adds the back stack for the Intent (but not the Intent itself) +// stackBuilder.addParentStack(SecondaryActivity.class); +// // Adds the Intent that starts the Activity to the top of the stack +// stackBuilder.addNextIntent(resultIntent); +// PendingIntent resultPendingIntent = +// stackBuilder.getPendingIntent( +// 0, +// PendingIntent.FLAG_UPDATE_CURRENT +// ); +// mBuilder.setContentIntent(resultPendingIntent); +// NotificationManager mNotificationManager = +// (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); +// // mId allows you to update the notification later on. +// mNotificationManager.notify(0, mBuilder.build()); } } diff --git a/test-app/src/main/java/com/optimizely/ab/android/test_app/SecondaryActivity.java b/test-app/src/main/java/com/optimizely/ab/android/test_app/SecondaryActivity.java deleted file mode 100644 index 716270205..000000000 --- a/test-app/src/main/java/com/optimizely/ab/android/test_app/SecondaryActivity.java +++ /dev/null @@ -1,40 +0,0 @@ -/** - * Copyright 2016, Optimizely - *

- * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - *

- * http://www.apache.org/licenses/LICENSE-2.0 - *

- * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.optimizely.ab.android.test_app; - -import android.os.Bundle; -import android.support.v7.app.AppCompatActivity; - -import com.optimizely.ab.android.sdk.OptimizelyClient; -import com.optimizely.ab.android.sdk.OptimizelyManager; -import com.optimizely.ab.android.shared.CountingIdlingResourceManager; - -public class SecondaryActivity extends AppCompatActivity { - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.activity_secondary); - - // Get Optimizely from the Intent that started this Activity - final MyApplication myApplication = (MyApplication) getApplication(); - final OptimizelyManager optimizelyManager = myApplication.getOptimizelyManager(); - OptimizelyClient optimizely = optimizelyManager.getOptimizely(); - CountingIdlingResourceManager.increment(); // For track event - optimizely.track("experiment_1", myApplication.getAnonUserId()); - } - -} diff --git a/test-app/src/main/java/com/optimizely/ab/android/test_app/SecondaryFragment.java b/test-app/src/main/java/com/optimizely/ab/android/test_app/SecondaryFragment.java deleted file mode 100644 index edc7c1cdd..000000000 --- a/test-app/src/main/java/com/optimizely/ab/android/test_app/SecondaryFragment.java +++ /dev/null @@ -1,76 +0,0 @@ -/* - * Copyright 2016, Optimizely - *

- * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - *

- * http://www.apache.org/licenses/LICENSE-2.0 - *

- * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.optimizely.ab.android.test_app; - -import android.content.Intent; -import android.os.Bundle; -import android.support.annotation.Nullable; -import android.support.v4.app.Fragment; -import android.support.v4.app.FragmentActivity; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.Button; -import android.widget.TextView; - -import com.optimizely.ab.android.sdk.OptimizelyClient; -import com.optimizely.ab.android.sdk.OptimizelyManager; -import com.optimizely.ab.android.shared.CountingIdlingResourceManager; -import com.optimizely.ab.config.Variation; - -public class SecondaryFragment extends Fragment { - - TextView textView1; - Button button1; - - @Nullable - @Override - public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { - return inflater.inflate(R.layout.fragment_secondary, container, false); - } - - @Override - public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { - textView1 = (TextView) view.findViewById(R.id.text_view_1); - button1 = (Button) view.findViewById(R.id.button_1); - - final MyApplication myApplication = (MyApplication) getActivity().getApplication(); - final OptimizelyManager optimizelyManager = myApplication.getOptimizelyManager(); - - OptimizelyClient optimizely = optimizelyManager.getOptimizely(); - CountingIdlingResourceManager.increment(); - Variation variation = optimizely.activate("experiment_2", myApplication.getAnonUserId()); - if (variation != null) { - if (variation.is("variation_1")) { - textView1.setText(R.string.secondary_frag_text_view_1_var_1); - } else if (variation.is("variation_2")) { - textView1.setText(R.string.secondary_frag_text_view_1_var_2); - } - } else { - textView1.setText(R.string.secondary_frag_text_view_1_default); - } - - button1.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - final FragmentActivity activity = getActivity(); - Intent intent = new Intent(activity, NotificationService.class); - activity.startService(intent); - } - }); - } -} diff --git a/test-app/src/main/java/com/optimizely/ab/android/test_app/SplashScreenActivity.java b/test-app/src/main/java/com/optimizely/ab/android/test_app/SplashScreenActivity.java new file mode 100644 index 000000000..4607c46d3 --- /dev/null +++ b/test-app/src/main/java/com/optimizely/ab/android/test_app/SplashScreenActivity.java @@ -0,0 +1,82 @@ +/**************************************************************************** + * Copyright 2017, Optimizely, Inc. and contributors * + * * + * Licensed under the Apache License, Version 2.0 (the "License"); * + * you may not use this file except in compliance with the License. * + * You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, software * + * distributed under the License is distributed on an "AS IS" BASIS, * + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * + * See the License for the specific language governing permissions and * + * limitations under the License. * + ***************************************************************************/ +package com.optimizely.ab.android.test_app; + +import android.content.Intent; +import android.support.annotation.Nullable; +import android.support.v7.app.AppCompatActivity; +import android.os.Bundle; + +import com.optimizely.ab.android.sdk.OptimizelyClient; +import com.optimizely.ab.android.sdk.OptimizelyManager; +import com.optimizely.ab.android.sdk.OptimizelyStartListener; +import com.optimizely.ab.android.shared.CountingIdlingResourceManager; +import com.optimizely.ab.config.Variation; + +public class SplashScreenActivity extends AppCompatActivity { + + // The Idling Resource which will be null in production. + @Nullable + private static CountingIdlingResourceManager countingIdlingResourceManager; + + private OptimizelyManager optimizelyManager; + private MyApplication myApplication; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_splash_screen); + + // This could also be done via DI framework such as Dagger + myApplication = (MyApplication) getApplication(); + optimizelyManager = myApplication.getOptimizelyManager(); + } + + @Override + protected void onStart() { + super.onStart(); + + // Initialize Optimizely asynchronously + optimizelyManager.initialize(this, new OptimizelyStartListener() { + + @Override + public void onStart(OptimizelyClient optimizely) { + // this is the control variation, it will show if we are not able to determine which variation to bucket the user into + Intent intent = new Intent(myApplication.getBaseContext(), ActivationErrorActivity.class); + + // Activate user and start activity based on the variation we get. + // You can pass in any string for the user ID. In this example we just use a convenience method to generate a random one. + String userId = myApplication.getAnonUserId(); + Variation backgroundVariation = optimizelyManager.getOptimizely().activate("background_experiment", userId); + + // Utility method for verifying event dispatches in our automated tests + CountingIdlingResourceManager.increment(); // increment for impression event + + // variation is nullable so we should check for null values + if (backgroundVariation != null) { + // Show activity based on the variation the user got bucketed into + if (backgroundVariation.getKey().equals("variation_a")) { + intent = new Intent(myApplication.getBaseContext(), VariationAActivity.class); + } else if (backgroundVariation.getKey().equals("variation_b")) { + intent = new Intent(myApplication.getBaseContext(), VariationBActivity.class); + } + } + + startActivity(intent); + } + }); + } +} diff --git a/test-app/src/main/java/com/optimizely/ab/android/test_app/VariationAActivity.java b/test-app/src/main/java/com/optimizely/ab/android/test_app/VariationAActivity.java new file mode 100644 index 000000000..90ac5c664 --- /dev/null +++ b/test-app/src/main/java/com/optimizely/ab/android/test_app/VariationAActivity.java @@ -0,0 +1,28 @@ +/**************************************************************************** + * Copyright 2017, Optimizely, Inc. and contributors * + * * + * Licensed under the Apache License, Version 2.0 (the "License"); * + * you may not use this file except in compliance with the License. * + * You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, software * + * distributed under the License is distributed on an "AS IS" BASIS, * + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * + * See the License for the specific language governing permissions and * + * limitations under the License. * + ***************************************************************************/ +package com.optimizely.ab.android.test_app; + +import android.os.Bundle; +import android.support.v7.app.AppCompatActivity; + +public class VariationAActivity extends AppCompatActivity { + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_variation_a); + } +} diff --git a/test-app/src/main/java/com/optimizely/ab/android/test_app/VariationBActivity.java b/test-app/src/main/java/com/optimizely/ab/android/test_app/VariationBActivity.java new file mode 100644 index 000000000..8e3c5f595 --- /dev/null +++ b/test-app/src/main/java/com/optimizely/ab/android/test_app/VariationBActivity.java @@ -0,0 +1,29 @@ +/**************************************************************************** + * Copyright 2017, Optimizely, Inc. and contributors * + * * + * Licensed under the Apache License, Version 2.0 (the "License"); * + * you may not use this file except in compliance with the License. * + * You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, software * + * distributed under the License is distributed on an "AS IS" BASIS, * + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * + * See the License for the specific language governing permissions and * + * limitations under the License. * + ***************************************************************************/ + +package com.optimizely.ab.android.test_app; + +import android.os.Bundle; +import android.support.v7.app.AppCompatActivity; + +public class VariationBActivity extends AppCompatActivity { + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_variation_b); + } +} diff --git a/test-app/src/main/res/drawable/ic_background_confirmation.xml b/test-app/src/main/res/drawable/ic_background_confirmation.xml new file mode 100644 index 000000000..ad20d1102 --- /dev/null +++ b/test-app/src/main/res/drawable/ic_background_confirmation.xml @@ -0,0 +1,204 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/test-app/src/main/res/drawable/ic_background_error.xml b/test-app/src/main/res/drawable/ic_background_error.xml new file mode 100644 index 000000000..d89352e65 --- /dev/null +++ b/test-app/src/main/res/drawable/ic_background_error.xml @@ -0,0 +1,90 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/test-app/src/main/res/drawable/ic_background_varia.xml b/test-app/src/main/res/drawable/ic_background_varia.xml new file mode 100644 index 000000000..59201cab9 --- /dev/null +++ b/test-app/src/main/res/drawable/ic_background_varia.xml @@ -0,0 +1,9 @@ + + + + diff --git a/test-app/src/main/res/drawable/ic_background_varib_marina.xml b/test-app/src/main/res/drawable/ic_background_varib_marina.xml new file mode 100644 index 000000000..2b6eb3457 --- /dev/null +++ b/test-app/src/main/res/drawable/ic_background_varib_marina.xml @@ -0,0 +1,5 @@ + + + + diff --git a/test-app/src/main/res/drawable/ic_optimizely_logo.xml b/test-app/src/main/res/drawable/ic_optimizely_logo.xml new file mode 100644 index 000000000..6a7f1962b --- /dev/null +++ b/test-app/src/main/res/drawable/ic_optimizely_logo.xml @@ -0,0 +1,12 @@ + + + + + diff --git a/test-app/src/main/res/layout/activity_activation_error.xml b/test-app/src/main/res/layout/activity_activation_error.xml new file mode 100644 index 000000000..36c23136e --- /dev/null +++ b/test-app/src/main/res/layout/activity_activation_error.xml @@ -0,0 +1,62 @@ + + + + + +