├── .editorconfig ├── .github ├── ISSUE_TEMPLATE │ ├── BUG-REPORT.yml │ ├── ENHANCEMENT.yml │ ├── FEATURE-REQUEST.md │ └── config.yml ├── pull_request_template.md └── workflows │ ├── android.yml │ ├── build.yml │ ├── source_clear_cron.yml │ └── ticket_reference_check.yml ├── .gitignore ├── .idea └── copyright │ ├── FullStack_Copyright.xml │ └── profiles_settings.xml ├── CHANGELOG.md ├── CODEOWNERS ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── android-sdk ├── .gitignore ├── build.gradle └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── optimizely │ │ └── ab │ │ └── android │ │ └── sdk │ │ ├── ODPIntegrationTest.java │ │ ├── ODPIntegrationUpdateConfigTest.java │ │ ├── OptimizelyClientEngineTest.java │ │ ├── OptimizelyClientTest.java │ │ ├── OptimizelyDefaultAttributesTest.java │ │ ├── OptimizelyManagerBuilderTest.java │ │ ├── OptimizelyManagerEventHandlerTest.java │ │ └── OptimizelyManagerTest.java │ ├── debug │ └── res │ │ └── raw │ │ ├── datafile │ │ ├── datafile_api │ │ ├── emptydatafile │ │ ├── validprojectconfigv3 │ │ └── validprojectconfigv4 │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── optimizely │ │ │ └── ab │ │ │ └── android │ │ │ └── sdk │ │ │ ├── OptimizelyClient.java │ │ │ ├── OptimizelyClientEngine.java │ │ │ ├── OptimizelyDefaultAttributes.java │ │ │ ├── OptimizelyLiteLogger.java │ │ │ ├── OptimizelyManager.java │ │ │ └── OptimizelyStartListener.java │ └── resources │ │ └── android-logger.properties │ └── test │ └── java │ └── com │ └── optimizely │ └── ab │ └── android │ └── sdk │ ├── OptimizelyClientTest.java │ ├── OptimizelyManagerBuilderTest.java │ ├── OptimizelyManagerIntervalTest.java │ └── OptimizelyManagerOptlyActivityLifecycleCallbacksTest.java ├── build.gradle ├── datafile-handler ├── .gitignore ├── build.gradle └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── optimizely │ │ └── ab │ │ └── android │ │ └── datafile_handler │ │ ├── BackgroundWatchersCacheTest.java │ │ ├── DatafileCacheTest.java │ │ ├── DatafileClientTest.java │ │ ├── DatafileLoaderTest.java │ │ ├── DatafileReschedulerTest.java │ │ ├── DatafileWorkerTest.java │ │ └── DefaultDatafileHandlerTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── optimizely │ │ │ └── ab │ │ │ └── android │ │ │ └── datafile_handler │ │ │ ├── BackgroundWatchersCache.java │ │ │ ├── DatafileCache.java │ │ │ ├── DatafileClient.java │ │ │ ├── DatafileHandler.java │ │ │ ├── DatafileLoadedListener.java │ │ │ ├── DatafileLoader.java │ │ │ ├── DatafileRescheduler.java │ │ │ ├── DatafileWorker.java │ │ │ └── DefaultDatafileHandler.java │ └── res │ │ └── values │ │ └── strings.xml │ └── test │ └── java │ └── com │ └── optimizely │ └── ab │ └── android │ └── datafile_handler │ └── DefaultDatafileHandlerUnitTest.java ├── docs └── readme.md ├── event-handler ├── .gitignore ├── build.gradle └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── optimizely │ │ └── ab │ │ └── android │ │ └── event_handler │ │ ├── DefaultEventHandlerTest.java │ │ ├── EventClientTest.java │ │ ├── EventDAOTest.java │ │ ├── EventReschedulerTest.java │ │ ├── EventSQLiteOpenHelperTest.java │ │ └── EventWorkerTest.java │ ├── main │ ├── AndroidManifest.xml │ └── java │ │ └── com │ │ └── optimizely │ │ └── ab │ │ └── android │ │ └── event_handler │ │ ├── DefaultEventHandler.java │ │ ├── Event.java │ │ ├── EventClient.java │ │ ├── EventDAO.java │ │ ├── EventDispatcher.java │ │ ├── EventRescheduler.java │ │ ├── EventSQLiteOpenHelper.java │ │ ├── EventTable.java │ │ └── EventWorker.java │ └── test │ ├── java │ └── com │ │ └── optimizely │ │ └── ab │ │ └── android │ │ └── event_handler │ │ ├── EventClientTest.java │ │ └── EventWorkerUnitTest.java │ └── resources │ └── org.mockito.plugins.MockMaker ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── odp ├── .gitignore ├── build.gradle └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── optimizely │ │ └── ab │ │ └── android │ │ └── odp │ │ ├── DefaultODPApiManagerTest.kt │ │ ├── ODPEventClientTest.kt │ │ ├── ODPEventWorkerTest.kt │ │ ├── ODPSegmentClientTest.kt │ │ └── VuidManagerTest.kt │ └── main │ ├── AndroidManifest.xml │ └── java │ └── com │ └── optimizely │ └── ab │ └── android │ └── odp │ ├── DefaultODPApiManager.kt │ ├── ODPEventClient.kt │ ├── ODPEventWorker.kt │ ├── ODPSegmentClient.kt │ └── VuidManager.kt ├── proguard-rules.txt ├── scripts └── android-wait-for-emulator.sh ├── settings.gradle ├── shared ├── .gitignore ├── build.gradle └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── optimizely │ │ └── ab │ │ └── android │ │ └── shared │ │ ├── CacheTest.java │ │ ├── ClientTest.java │ │ ├── OptlyStorageTest.java │ │ ├── TestWorker.java │ │ └── WorkerSchedulerTest.java │ ├── main │ ├── AndroidManifest.xml │ └── java │ │ └── com │ │ └── optimizely │ │ └── ab │ │ └── android │ │ └── shared │ │ ├── Cache.java │ │ ├── CachedCounter.java │ │ ├── Client.java │ │ ├── CountingIdlingResourceInterface.java │ │ ├── CountingIdlingResourceManager.java │ │ ├── DatafileConfig.java │ │ ├── EventHandlerUtils.java │ │ ├── OptlyStorage.java │ │ ├── TLSSocketFactory.java │ │ └── WorkerScheduler.java │ └── test │ └── java │ └── com │ └── optimizely │ └── ab │ └── android │ └── shared │ └── EventHandlerUtilsTest.java ├── test-app ├── .gitignore ├── README.md ├── build.gradle ├── images │ └── demo-app-flow.png ├── src │ └── main │ │ ├── AndroidManifest.xml │ │ ├── java │ │ └── com │ │ │ └── optimizely │ │ │ └── ab │ │ │ └── android │ │ │ └── test_app │ │ │ ├── ActivationErrorActivity.kt │ │ │ ├── BaseActivity.kt │ │ │ ├── ConversionFragment.kt │ │ │ ├── CouponActivity.kt │ │ │ ├── EventConfirmationActivity.kt │ │ │ ├── MyApplication.kt │ │ │ ├── NotificationService.kt │ │ │ ├── Samples │ │ │ ├── APISamplesInJava.java │ │ │ └── APISamplesInKotlin.kt │ │ │ ├── SplashScreenActivity.kt │ │ │ ├── VariationAActivity.kt │ │ │ └── VariationBActivity.kt │ │ ├── res │ │ ├── drawable │ │ │ ├── ic_background_confirmation.xml │ │ │ ├── ic_background_error.xml │ │ │ ├── ic_background_varia.xml │ │ │ ├── ic_background_varib_marina.xml │ │ │ └── ic_optimizely_logo.xml │ │ ├── layout │ │ │ ├── activity_activation_error.xml │ │ │ ├── activity_event_confirmation.xml │ │ │ ├── activity_splash_screen.xml │ │ │ ├── activity_variation_a.xml │ │ │ ├── activity_variation_b.xml │ │ │ ├── coupon.xml │ │ │ └── fragment_conversion.xml │ │ ├── mipmap-hdpi │ │ │ └── ic_launcher.png │ │ ├── mipmap-mdpi │ │ │ └── ic_launcher.png │ │ ├── mipmap-xhdpi │ │ │ └── ic_launcher.png │ │ ├── mipmap-xxhdpi │ │ │ └── ic_launcher.png │ │ ├── raw │ │ │ └── datafile.json │ │ ├── values-w820dp │ │ │ └── dimens.xml │ │ └── values │ │ │ ├── colors.xml │ │ │ ├── dimens.xml │ │ │ ├── strings.xml │ │ │ └── styles.xml │ │ └── resources │ │ └── android-logger.properties └── test-app-proguard-rules.pro └── user-profile ├── .gitignore ├── build.gradle └── src ├── androidTest └── java │ └── com │ └── optimizely │ └── ab │ └── android │ └── user_profile │ ├── DefaultUserProfileServiceTest.java │ ├── DiskCacheTest.java │ ├── UserProfileCacheTest.java │ └── UserProfileCacheUtilsTest.java └── main ├── AndroidManifest.xml └── java └── com └── optimizely └── ab └── android └── user_profile ├── DefaultUserProfileService.java ├── UserProfileCache.java └── UserProfileCacheUtils.java /.editorconfig: -------------------------------------------------------------------------------- 1 | # indicate this is the root of the project 2 | root = true 3 | 4 | [*.{kt,java,xml,gradle,md}] 5 | charset = utf-8 6 | indent_style = space 7 | indent_size = 4 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | end_of_line = lf 11 | 12 | [{*.gradle.kts,*.kt,*.kts,*.main.kts}] 13 | disabled_rules = import-ordering 14 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/BUG-REPORT.yml: -------------------------------------------------------------------------------- 1 | name: 🐞 Bug 2 | description: File a bug/issue 3 | title: "[BUG] " 4 | labels: ["bug", "needs-triage"] 5 | body: 6 | - type: checkboxes 7 | attributes: 8 | label: Is there an existing issue for this? 9 | description: Please search to see if an issue already exists for the bug you encountered. 10 | options: 11 | - label: I have searched the existing issues 12 | required: true 13 | - type: textarea 14 | attributes: 15 | label: SDK Version 16 | description: Version of the SDK in use? 17 | validations: 18 | required: true 19 | - type: textarea 20 | attributes: 21 | label: Current Behavior 22 | description: A concise description of what you're experiencing. 23 | validations: 24 | required: true 25 | - type: textarea 26 | attributes: 27 | label: Expected Behavior 28 | description: A concise description of what you expected to happen. 29 | validations: 30 | required: true 31 | - type: textarea 32 | attributes: 33 | label: Steps To Reproduce 34 | description: Steps to reproduce the behavior. 35 | placeholder: | 36 | 1. In this environment... 37 | 2. With this config... 38 | 3. Run '...' 39 | 4. See error... 40 | validations: 41 | required: true 42 | - type: textarea 43 | attributes: 44 | label: Link 45 | description: Link to code demonstrating the problem. 46 | validations: 47 | required: false 48 | - type: textarea 49 | attributes: 50 | label: Logs 51 | description: Logs/stack traces related to the problem (⚠️do not include sensitive information). 52 | validations: 53 | required: false 54 | - type: dropdown 55 | attributes: 56 | label: Severity 57 | description: What is the severity of the problem? 58 | multiple: true 59 | options: 60 | - Blocking development 61 | - Affecting users 62 | - Minor issue 63 | validations: 64 | required: false 65 | - type: textarea 66 | attributes: 67 | label: Workaround/Solution 68 | description: Do you have any workaround or solution in mind for the problem? 69 | validations: 70 | required: false 71 | - type: textarea 72 | attributes: 73 | label: "Recent Change" 74 | description: Has this issue started happening after an update or experiment change? 75 | validations: 76 | required: false 77 | - type: textarea 78 | attributes: 79 | label: Conflicts 80 | description: Are there other libraries/dependencies potentially in conflict? 81 | validations: 82 | required: false -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/ENHANCEMENT.yml: -------------------------------------------------------------------------------- 1 | name: ✨Enhancement 2 | description: Create a new ticket for a Enhancement/Tech-initiative for the benefit of the SDK which would be considered for a minor version update. 3 | title: "[ENHANCEMENT] <title>" 4 | labels: ["enhancement"] 5 | body: 6 | - type: textarea 7 | id: description 8 | attributes: 9 | label: "Description" 10 | description: Briefly describe the enhancement in a few sentences. 11 | placeholder: Short description... 12 | validations: 13 | required: true 14 | - type: textarea 15 | id: benefits 16 | attributes: 17 | label: "Benefits" 18 | description: How would the enhancement benefit to your product or usage? 19 | placeholder: Benefits... 20 | validations: 21 | required: true 22 | - type: textarea 23 | id: detail 24 | attributes: 25 | label: "Detail" 26 | description: How would you like the enhancement to work? Please provide as much detail as possible 27 | placeholder: Detailed description... 28 | validations: 29 | required: false 30 | - type: textarea 31 | id: examples 32 | attributes: 33 | label: "Examples" 34 | description: Are there any examples of this enhancement in other products/services? If so, please provide links or references. 35 | placeholder: Links/References... 36 | validations: 37 | required: false 38 | - type: textarea 39 | id: risks 40 | attributes: 41 | label: "Risks/Downsides" 42 | description: Do you think this enhancement could have any potential downsides or risks? 43 | placeholder: Risks/Downsides... 44 | validations: 45 | required: false -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/FEATURE-REQUEST.md: -------------------------------------------------------------------------------- 1 | <!-- 2 | Thanks for filing in issue! Are you requesting a new feature? If so, please share your feedback with us on the following link. 3 | --> 4 | ## Feedback requesting a new feature can be shared [here.](https://feedback.optimizely.com/) 5 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | contact_links: 3 | - name: 💡Feature Requests 4 | url: https://feedback.optimizely.com/ 5 | about: Feedback requesting a new feature can be shared here. -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | ## Summary 2 | - The "what"; a concise description of each logical change 3 | - Another change 4 | 5 | The "why", or other context. 6 | 7 | ## Test plan 8 | 9 | ## Issues 10 | - "THING-1234" or "Fixes #123" -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Reusable action of building snapshot and publish 2 | 3 | on: 4 | workflow_call: 5 | inputs: 6 | action: 7 | required: true 8 | type: string 9 | github_tag: 10 | required: false 11 | type: string 12 | secrets: 13 | MAVEN_SIGNING_KEY_BASE64: 14 | required: false 15 | MAVEN_SIGNING_PASSPHRASE: 16 | required: false 17 | MAVEN_CENTRAL_USERNAME: 18 | required: false 19 | MAVEN_CENTRAL_PASSWORD: 20 | required: false 21 | jobs: 22 | run_build: 23 | runs-on: ubuntu-latest 24 | steps: 25 | - uses: actions/checkout@v4 26 | - name: set up JDK 17 27 | uses: actions/setup-java@v4 28 | with: 29 | java-version: '17' 30 | distribution: 'temurin' 31 | - name: Setup Gradle cache 32 | uses: gradle/gradle-build-action@v2 33 | with: 34 | gradle-version: wrapper 35 | - name: Grant execute permission for gradlew 36 | run: chmod +x gradlew 37 | - name: Clean all modules 38 | run: ./gradlew cleanAllModules 39 | - name: ${{ inputs.action }} 40 | env: 41 | MAVEN_SIGNING_KEY_BASE64: ${{ secrets.MAVEN_SIGNING_KEY_BASE64 }} 42 | MAVEN_SIGNING_PASSPHRASE: ${{ secrets.MAVEN_SIGNING_PASSPHRASE }} 43 | MAVEN_CENTRAL_USERNAME: ${{ secrets.MAVEN_CENTRAL_USERNAME }} 44 | MAVEN_CENTRAL_PASSWORD: ${{ secrets.MAVEN_CENTRAL_PASSWORD }} 45 | run: GITHUB_TAG=${{ inputs.github_tag }} ./gradlew ${{ inputs.action }} 46 | -------------------------------------------------------------------------------- /.github/workflows/source_clear_cron.yml: -------------------------------------------------------------------------------- 1 | name: Source clear 2 | 3 | on: 4 | schedule: 5 | # Runs "weekly" 6 | - cron: '0 0 * * 0' 7 | 8 | jobs: 9 | source_clear: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v2 13 | - name: Source clear scan 14 | env: 15 | SRCCLR_API_TOKEN: ${{ secrets.SRCCLR_API_TOKEN }} 16 | run: curl -sSL https://download.sourceclear.com/ci.sh | bash -s – scan -------------------------------------------------------------------------------- /.github/workflows/ticket_reference_check.yml: -------------------------------------------------------------------------------- 1 | name: Jira ticket reference check 2 | 3 | on: 4 | pull_request: 5 | types: [opened, edited, reopened, synchronize] 6 | 7 | jobs: 8 | 9 | jira_ticket_reference_check: 10 | runs-on: ubuntu-latest 11 | 12 | steps: 13 | - name: Check for Jira ticket reference 14 | uses: optimizely/github-action-ticket-reference-checker-public@master 15 | with: 16 | bodyRegex: 'FSSDK-(?<ticketNumber>\d+)' 17 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | 5 | # selectively ignored (copyright must not be ignored) 6 | /.idea/* 7 | !/.idea/copyright 8 | 9 | .DS_Store 10 | /build 11 | /captures 12 | values.gradle 13 | jacoco.exec 14 | 15 | -------------------------------------------------------------------------------- /.idea/copyright/FullStack_Copyright.xml: -------------------------------------------------------------------------------- 1 | <component name="CopyrightManager"> 2 | <copyright> 3 | <option name="notice" value="Copyright 2022, 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 https://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." /> 4 | <option name="myName" value="FullStack Copyright" /> 5 | </copyright> 6 | </component> -------------------------------------------------------------------------------- /.idea/copyright/profiles_settings.xml: -------------------------------------------------------------------------------- 1 | <component name="CopyrightManager"> 2 | <settings default="FullStack Copyright"> 3 | <LanguageOptions name="__TEMPLATE__"> 4 | <option name="block" value="false" /> 5 | </LanguageOptions> 6 | </settings> 7 | </component> -------------------------------------------------------------------------------- /CODEOWNERS: -------------------------------------------------------------------------------- 1 | # This is a comment. 2 | # Each line is a file pattern followed by one or more owners. 3 | 4 | # These owners will be the default owners for everything in the repo. 5 | # Unless a later match takes precedence, @global-owner1 and @global-owner2 6 | # will be requested for review when someone opens a pull request. 7 | * @optimizely/fullstack-devs 8 | 9 | # Order is important; the last matching pattern takes the most precedence. 10 | # When someone opens a pull request that only modifies JS files, only @js-owner 11 | # and not the global owner(s) will be requested for a review. 12 | #*.js @js-owner 13 | 14 | # You can also use email addresses if you prefer. They'll be used to look up 15 | # users just like we do for commit author emails. 16 | #docs/* docs@example.com 17 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to the Optimizely Android SDK 2 | 3 | We welcome contributions and feedback! All contributors must sign our [Contributor License Agreement (CLA)](https://docs.google.com/a/optimizely.com/forms/d/e/1FAIpQLSf9cbouWptIpMgukAKZZOIAhafvjFCV8hS00XJLWQnWDFtwtA/viewform) to be eligible to contribute. Please read the [README](README.md) to set up your development environment, then read the guidelines below for information on submitting your code. 4 | 5 | ## Development process 6 | 7 | 1. Fork the repository and create your branch from master. 8 | 2. Please follow the [commit message guidelines](https://github.com/angular/angular/blob/master/CONTRIBUTING.md#-commit-message-guidelines) for each commit message. 9 | 3. Make sure to add tests! 10 | 4. `git push` your changes to GitHub. 11 | 5. Open a PR from your fork into the master branch of the original repo 12 | 6. Make sure that all unit tests are passing and that there are no merge conflicts between your branch and `master`. 13 | 7. Open a pull request from `YOUR_NAME/branch_name` to `master`. 14 | 8. A repository maintainer will review your pull request and, if all goes well, squash and merge it! 15 | 16 | All branches will be built and run against the entire test suite on Gtihub Actions with every commit. 17 | 18 | The `test-app` module is built against a real Optimizely project. Changing the project ID will cause tests to fail. The test app should be used as a reference. 19 | 20 | ## Pull request acceptance criteria 21 | 22 | * **All code must have test coverage.** We use JUnit. Changes in functionality should have accompanying unit tests. Bug fixes should have accompanying regression tests. 23 | * Unit tests are located in `MODULE_NAME/src/test` and instrumented tests are located in `MODULE_NAME/src/androidTest` for each module with one file per class. 24 | * Version will be bumped automatically through Gradle after your change is merged. 25 | * Make sure `./gradlew testAllModules` runs successfully before submitting. 26 | 27 | ## Style 28 | 29 | Refer to the [Google Java Style Guide](https://google.github.io/styleguide/javaguide.html) 30 | 31 | ## License 32 | 33 | All contributions are under the CLA mentioned above. For this project, Optimizely uses the Apache 2.0 license, and so asks that by contributing your code, you agree to license your contribution under the terms of the [Apache License v2.0](http://www.apache.org/licenses/LICENSE-2.0). Your contributions should also include the following header: 34 | 35 | ``` 36 | /**************************************************************************** 37 | * Copyright YEAR, Optimizely, Inc. and contributors * 38 | * * 39 | * Licensed under the Apache License, Version 2.0 (the "License"); * 40 | * you may not use this file except in compliance with the License. * 41 | * You may obtain a copy of the License at * 42 | * * 43 | * http://www.apache.org/licenses/LICENSE-2.0 * 44 | * * 45 | * Unless required by applicable law or agreed to in writing, software * 46 | * distributed under the License is distributed on an "AS IS" BASIS, * 47 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * 48 | * See the License for the specific language governing permissions and * 49 | * limitations under the License. * 50 | ***************************************************************************/ 51 | ``` 52 | 53 | The YEAR above should be the year of the contribution. If work on the file has been done over multiple years, list each year in the section above. Example: Optimizely writes the file and releases it in 2014. No changes are made in 2015. Change made in 2016. YEAR should be “2014, 2016”. 54 | 55 | ## Contact 56 | 57 | If you have questions, please contact developers@optimizely.com. 58 | -------------------------------------------------------------------------------- /android-sdk/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /android-sdk/src/androidTest/java/com/optimizely/ab/android/sdk/OptimizelyClientEngineTest.java: -------------------------------------------------------------------------------- 1 | /**************************************************************************** 2 | * Copyright 2017,2021, Optimizely, Inc. and contributors * 3 | * * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); * 5 | * you may not use this file except in compliance with the License. * 6 | * You may obtain a copy of the License at * 7 | * * 8 | * http://www.apache.org/licenses/LICENSE-2.0 * 9 | * * 10 | * Unless required by applicable law or agreed to in writing, software * 11 | * distributed under the License is distributed on an "AS IS" BASIS, * 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * 13 | * See the License for the specific language governing permissions and * 14 | * limitations under the License. * 15 | ***************************************************************************/ 16 | 17 | package com.optimizely.ab.android.sdk; 18 | 19 | import android.app.UiModeManager; 20 | import android.content.Context; 21 | import android.content.res.Configuration; 22 | import android.os.Build; 23 | import androidx.annotation.RequiresApi; 24 | import androidx.test.ext.junit.runners.AndroidJUnit4; 25 | 26 | import com.optimizely.ab.event.internal.payload.EventBatch; 27 | 28 | import org.junit.Test; 29 | import org.junit.runner.RunWith; 30 | 31 | import static junit.framework.Assert.assertEquals; 32 | import static org.mockito.Mockito.mock; 33 | import static org.mockito.Mockito.when; 34 | 35 | @RunWith(AndroidJUnit4.class) 36 | public class OptimizelyClientEngineTest { 37 | @Test 38 | public void testGetClientEngineNameFromContextAndroidTV() { 39 | Context context = mock(Context.class); 40 | UiModeManager uiModeManager = mock(UiModeManager.class); 41 | when(context.getSystemService(Context.UI_MODE_SERVICE)).thenReturn(uiModeManager); 42 | when(uiModeManager.getCurrentModeType()).thenReturn(Configuration.UI_MODE_TYPE_TELEVISION); 43 | assertEquals("android-tv-sdk", OptimizelyClientEngine.getClientEngineNameFromContext(context)); 44 | } 45 | 46 | @Test 47 | public void testGetClientEngineNameFromContextAndroid() { 48 | Context context = mock(Context.class); 49 | UiModeManager uiModeManager = mock(UiModeManager.class); 50 | when(context.getSystemService(Context.UI_MODE_SERVICE)).thenReturn(uiModeManager); 51 | when(uiModeManager.getCurrentModeType()).thenReturn(Configuration.UI_MODE_TYPE_NORMAL); 52 | assertEquals("android-sdk", OptimizelyClientEngine.getClientEngineNameFromContext(context)); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /android-sdk/src/androidTest/java/com/optimizely/ab/android/sdk/OptimizelyDefaultAttributesTest.java: -------------------------------------------------------------------------------- 1 | /**************************************************************************** 2 | * Copyright 2017, 2023 Optimizely, Inc. and contributors * 3 | * * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); * 5 | * you may not use this file except in compliance with the License. * 6 | * You may obtain a copy of the License at * 7 | * * 8 | * http://www.apache.org/licenses/LICENSE-2.0 * 9 | * * 10 | * Unless required by applicable law or agreed to in writing, software * 11 | * distributed under the License is distributed on an "AS IS" BASIS, * 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * 13 | * See the License for the specific language governing permissions and * 14 | * limitations under the License. * 15 | ***************************************************************************/ 16 | package com.optimizely.ab.android.sdk; 17 | 18 | import java.util.Map; 19 | import android.content.Context; 20 | 21 | import androidx.test.ext.junit.runners.AndroidJUnit4; 22 | 23 | import org.junit.After; 24 | import org.junit.Before; 25 | import org.junit.Test; 26 | import org.junit.runner.RunWith; 27 | import org.slf4j.Logger; 28 | 29 | import static org.mockito.ArgumentMatchers.any; 30 | import static org.mockito.ArgumentMatchers.eq; 31 | import static org.mockito.Mockito.mock; 32 | import static org.mockito.Mockito.verify; 33 | import static org.mockito.Mockito.when; 34 | 35 | import static org.junit.Assert.*; 36 | 37 | /** 38 | * Tests for {@link OptimizelyDefaultAttributes} 39 | */ 40 | @RunWith(AndroidJUnit4.class) 41 | public class OptimizelyDefaultAttributesTest { 42 | private Logger logger; 43 | 44 | @Before 45 | public void setUp() throws Exception { 46 | logger = mock(Logger.class); 47 | } 48 | 49 | @After 50 | public void tearDown() throws Exception { 51 | 52 | } 53 | 54 | @Test 55 | public void buildDefaultAttributesMap() throws Exception { 56 | Context context = mock(Context.class); 57 | Context appContext = mock(Context.class); 58 | when(context.getApplicationContext()).thenReturn(appContext); 59 | when(appContext.getPackageName()).thenReturn("com.optly"); 60 | 61 | Map<String, String> defaultAttributes = OptimizelyDefaultAttributes.buildDefaultAttributesMap(context, logger); 62 | 63 | assertEquals(defaultAttributes.size(), 4); 64 | } 65 | 66 | @Test 67 | public void buildODPCommonData() throws Exception { 68 | Context context = mock(Context.class); 69 | Map<String, Object> commonData = OptimizelyDefaultAttributes.buildODPCommonData(context, logger); 70 | 71 | assertEquals(commonData.size(), 4); 72 | 73 | assertEquals(commonData.get("os"), "Android"); 74 | assertEquals(commonData.get("device_type"), "Phone"); 75 | assertTrue(commonData.get("os_version").toString().length() >= 1); 76 | assertTrue(commonData.get("model").toString().length() > 2); 77 | } 78 | 79 | } 80 | 81 | -------------------------------------------------------------------------------- /android-sdk/src/debug/res/raw/datafile: -------------------------------------------------------------------------------- 1 | {"groups": [], "projectId": "8504447126", "variables": [{"defaultValue": "true", "type": "boolean", "id": "8516291943", "key": "test_variable"}], "version": "3", "experiments": [{"status": "Running", "key": "android_experiment_key", "layerId": "8499056327", "trafficAllocation": [{"entityId": "8509854340", "endOfRange": 5000}, {"entityId": "8505434669", "endOfRange": 10000}], "audienceIds": [], "variations": [{"variables": [], "id": "8509854340", "key": "var_1"}, {"variables": [], "id": "8505434669", "key": "var_2"}], "forcedVariations": {}, "id": "8509139139"}], "audiences": [], "anonymizeIP": true, "attributes": [], "revision": "7", "events": [{"experimentIds": ["8509139139"], "id": "8505434668", "key": "test_event"}], "accountId": "8362480420"} -------------------------------------------------------------------------------- /android-sdk/src/debug/res/raw/datafile_api: -------------------------------------------------------------------------------- 1 | {"version":"4","rollouts":[],"anonymizeIP":true,"botFiltering":true,"projectId":"10431130345","variables":[],"featureFlags":[{"experimentIds":["10390977673"],"id":"4482920077","key":"feature_1","rolloutId":"","variables":[{"defaultValue":"42","id":"2687470095","key":"i_42","type":"integer"},{"defaultValue":"4.2","id":"2689280165","key":"d_4_2","type":"double"},{"defaultValue":"true","id":"2689660112","key":"b_true","type":"boolean"},{"defaultValue":"foo","id":"2696150066","key":"s_foo","type":"string"}]},{"experimentIds":["10420810910"],"id":"4482920078","key":"feature_2","rolloutId":"","variables":[]}],"experiments":[{"status":"Running","key":"exp_with_audience","layerId":"10420273888","trafficAllocation":[{"entityId":"10389729780","endOfRange":10000}],"audienceIds":[],"variations":[{"variables":[],"featureEnabled":true,"id":"10389729780","key":"a"},{"variables":[],"id":"10416523121","key":"b"}],"forcedVariations":{},"id":"10390977673"},{"status":"Running","key":"exp_no_audience","layerId":"10417730432","trafficAllocation":[{"entityId":"10418551353","endOfRange":10000}],"audienceIds":[],"variations":[{"variables":[],"id":"10418551353","key":"variation_with_traffic"},{"variables":[],"id":"10418510624","key":"variation_no_traffic"}],"forcedVariations":{},"id":"10420810910"}],"audiences":[{"id":"10413101795","conditions":"[\"and\", [\"or\", [\"or\", {\"type\": \"custom_attribute\", \"name\": \"testvar\", \"value\": \"testvalue\"}]]]","name":"testvalue_audience"}],"groups":[],"attributes":[{"id":"10401066170","key":"testvar"}],"accountId":"10367498574","events":[{"experimentIds":["10420810910"],"id":"10404198134","key":"event1"},{"experimentIds":["10420810910","10390977673"],"id":"10404198135","key":"event_multiple_running_exp_attached"}],"revision":"241"} -------------------------------------------------------------------------------- /android-sdk/src/debug/res/raw/emptydatafile: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/optimizely/android-sdk/841a5d236d822e53803e4064665663b221057b64/android-sdk/src/debug/res/raw/emptydatafile -------------------------------------------------------------------------------- /android-sdk/src/debug/res/raw/validprojectconfigv3: -------------------------------------------------------------------------------- 1 | { "groups": [], "projectId": "8504447126", "variables": [{ "defaultValue": "true", "type": "boolean", "id": "8516291943", "key": "test_variable" }], "version": "3", "experiments": [{ "status": "Running", "key": "android_experiment_key", "layerId": "8499056327", "trafficAllocation": [{ "entityId": "8509854340", "endOfRange": 5000 }, { "entityId": "8505434669", "endOfRange": 10000 }], "audienceIds": [], "variations": [{ "variables": [], "id": "8509854340", "key": "var_1" }, { "variables": [], "id": "8505434669", "key": "var_2" }], "forcedVariations": {}, "id": "8509139139" }], "audiences": [], "anonymizeIP": true, "attributes": [], "revision": "7", "events": [{ "experimentIds": ["8509139139"], "id": "8505434668", "key": "test_event" }], "accountId": "8362480420" } -------------------------------------------------------------------------------- /android-sdk/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | <?xml version="1.0" encoding="utf-8"?> 2 | <!-- 3 | /**************************************************************************** 4 | * Copyright 2017, Optimizely, Inc. and contributors * 5 | * * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); * 7 | * you may not use this file except in compliance with the License. * 8 | * You may obtain a copy of the License at * 9 | * * 10 | * http://www.apache.org/licenses/LICENSE-2.0 * 11 | * * 12 | * Unless required by applicable law or agreed to in writing, software * 13 | * distributed under the License is distributed on an "AS IS" BASIS, * 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * 15 | * See the License for the specific language governing permissions and * 16 | * limitations under the License. * 17 | ***************************************************************************/ 18 | --> 19 | 20 | <manifest xmlns:android="http://schemas.android.com/apk/res/android" 21 | package="com.optimizely.ab.android.sdk"> 22 | 23 | <uses-permission android:name="android.permission.INTERNET"/> 24 | 25 | </manifest> 26 | -------------------------------------------------------------------------------- /android-sdk/src/main/java/com/optimizely/ab/android/sdk/OptimizelyClientEngine.java: -------------------------------------------------------------------------------- 1 | /**************************************************************************** 2 | * Copyright 2017, Optimizely, Inc. and contributors * 3 | * * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); * 5 | * you may not use this file except in compliance with the License. * 6 | * You may obtain a copy of the License at * 7 | * * 8 | * http://www.apache.org/licenses/LICENSE-2.0 * 9 | * * 10 | * Unless required by applicable law or agreed to in writing, software * 11 | * distributed under the License is distributed on an "AS IS" BASIS, * 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * 13 | * See the License for the specific language governing permissions and * 14 | * limitations under the License. * 15 | ***************************************************************************/ 16 | 17 | package com.optimizely.ab.android.sdk; 18 | 19 | import android.app.UiModeManager; 20 | import android.content.Context; 21 | import android.content.res.Configuration; 22 | import androidx.annotation.NonNull; 23 | 24 | import com.optimizely.ab.event.internal.payload.EventBatch; 25 | 26 | /** 27 | * This class manages client engine value of the Event depending on current mode of UI. 28 | */ 29 | public class OptimizelyClientEngine { 30 | 31 | /** 32 | * Get client engine name for current UI mode type 33 | * 34 | * @param context any valid Android {@link Context} 35 | * @return client engine name ("android-sdk" or "android-tv-sdk") 36 | */ 37 | public static String getClientEngineNameFromContext(@NonNull Context context) { 38 | UiModeManager uiModeManager = (UiModeManager) context.getSystemService(Context.UI_MODE_SERVICE); 39 | 40 | if (uiModeManager != null && uiModeManager.getCurrentModeType() == Configuration.UI_MODE_TYPE_TELEVISION) { 41 | return "android-tv-sdk"; 42 | } 43 | 44 | return "android-sdk"; 45 | } 46 | 47 | /** 48 | * Get client engine value for current UI mode type 49 | * 50 | * @param context any valid Android {@link Context} 51 | * @return String value of client engine 52 | * @deprecated Consider using {@link #getClientEngineNameFromContext(Context)} 53 | */ 54 | @Deprecated 55 | public static EventBatch.ClientEngine getClientEngineFromContext(@NonNull Context context) { 56 | UiModeManager uiModeManager = (UiModeManager) context.getSystemService(Context.UI_MODE_SERVICE); 57 | 58 | if (uiModeManager != null && uiModeManager.getCurrentModeType() == Configuration.UI_MODE_TYPE_TELEVISION) { 59 | return EventBatch.ClientEngine.ANDROID_TV_SDK; 60 | } 61 | 62 | return EventBatch.ClientEngine.ANDROID_SDK; 63 | } 64 | 65 | } 66 | -------------------------------------------------------------------------------- /android-sdk/src/main/java/com/optimizely/ab/android/sdk/OptimizelyStartListener.java: -------------------------------------------------------------------------------- 1 | /**************************************************************************** 2 | * Copyright 2016, Optimizely, Inc. and contributors * 3 | * * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); * 5 | * you may not use this file except in compliance with the License. * 6 | * You may obtain a copy of the License at * 7 | * * 8 | * http://www.apache.org/licenses/LICENSE-2.0 * 9 | * * 10 | * Unless required by applicable law or agreed to in writing, software * 11 | * distributed under the License is distributed on an "AS IS" BASIS, * 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * 13 | * See the License for the specific language governing permissions and * 14 | * limitations under the License. * 15 | ***************************************************************************/ 16 | 17 | package com.optimizely.ab.android.sdk; 18 | 19 | import android.app.Activity; 20 | import android.content.Context; 21 | 22 | /** 23 | * Listens for new instances of {@link OptimizelyClient} that are built after calling 24 | *{@link OptimizelyManager#initialize(Context, Integer,OptimizelyStartListener)} 25 | */ 26 | public interface OptimizelyStartListener { 27 | /** 28 | * Receives a started {@link OptimizelyClient} instances 29 | * 30 | * @param optimizely an {@link OptimizelyClient} that is started 31 | */ 32 | void onStart(OptimizelyClient optimizely); 33 | } 34 | -------------------------------------------------------------------------------- /android-sdk/src/main/resources/android-logger.properties: -------------------------------------------------------------------------------- 1 | #**************************************************************************** 2 | # Copyright 2016, Optimizely, Inc. and contributors * 3 | # * 4 | # Licensed under the Apache License, Version 2.0 (the "License"); * 5 | # you may not use this file except in compliance with the License. * 6 | # You may obtain a copy of the License at * 7 | # * 8 | # http://www.apache.org/licenses/LICENSE-2.0 * 9 | # * 10 | # Unless required by applicable law or agreed to in writing, software * 11 | # distributed under the License is distributed on an "AS IS" BASIS, * 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * 13 | # See the License for the specific language governing permissions and * 14 | # limitations under the License. * 15 | #**************************************************************************/ 16 | 17 | # Android Logger configuration example 18 | 19 | # https://github.com/noveogroup/android-logger 20 | # Valid logging levels are: VERBOSE, DEBUG, INFO, WARN, ERROR, ASSERT 21 | 22 | # Default configuration (disabled because it would override tags from the application) 23 | #root=DEBUG:Optly 24 | 25 | # Core 26 | logger.com.optimizely.ab.Optimizely=DEBUG:Optly.core 27 | logger.com.optimizely.ab.annotations=DEBUG:Optly.annotations 28 | logger.com.optimizely.ab.bucketing=DEBUG:Optly.bucketing 29 | logger.com.optimizely.ab.config=DEBUG:Optly.config 30 | logger.com.optimizely.ab.error=DEBUG:Optly.error 31 | logger.com.optimizely.ab.event=DEBUG:Optly.event 32 | logger.com.optimizely.ab.internal=DEBUG:Optly.internal 33 | 34 | # Android 35 | logger.com.optimizely.ab.android.sdk=DEBUG:Optly.androidSdk 36 | logger.com.optimizely.ab.android.datafile_handler=DEBUG:Optly.datafileHandler 37 | logger.com.optimizely.ab.android.event_handler=DEBUG:Optly.eventHandler 38 | logger.com.optimizely.ab.android.shared=DEBUG:Optly.shared 39 | logger.com.optimizely.ab.android.user_profile=DEBUG:Optly.userProfile 40 | 41 | # Disable most SDK logs by commenting all other lines and uncommenting below 42 | #logger.com.optimizely.ab=ASSERT:Optly 43 | -------------------------------------------------------------------------------- /android-sdk/src/test/java/com/optimizely/ab/android/sdk/OptimizelyManagerOptlyActivityLifecycleCallbacksTest.java: -------------------------------------------------------------------------------- 1 | /**************************************************************************** 2 | * Copyright 2016,2021, Optimizely, Inc. and contributors * 3 | * * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); * 5 | * you may not use this file except in compliance with the License. * 6 | * You may obtain a copy of the License at * 7 | * * 8 | * http://www.apache.org/licenses/LICENSE-2.0 * 9 | * * 10 | * Unless required by applicable law or agreed to in writing, software * 11 | * distributed under the License is distributed on an "AS IS" BASIS, * 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * 13 | * See the License for the specific language governing permissions and * 14 | * limitations under the License. * 15 | ***************************************************************************/ 16 | 17 | package com.optimizely.ab.android.sdk; 18 | 19 | import android.app.Activity; 20 | import android.os.Build; 21 | 22 | import org.junit.Before; 23 | import org.junit.Test; 24 | import org.junit.runner.RunWith; 25 | import org.mockito.Mock; 26 | import org.mockito.junit.MockitoJUnitRunner; 27 | 28 | import static org.mockito.Mockito.verify; 29 | 30 | /** 31 | * Tests for {@link OptimizelyManager.OptlyActivityLifecycleCallbacks} 32 | */ 33 | @RunWith(MockitoJUnitRunner.class) 34 | public class OptimizelyManagerOptlyActivityLifecycleCallbacksTest { 35 | 36 | @Mock OptimizelyManager optimizelyManager; 37 | @Mock Activity activity; 38 | private OptimizelyManager.OptlyActivityLifecycleCallbacks optlyActivityLifecycleCallbacks; 39 | 40 | @Before 41 | public void setup() { 42 | optlyActivityLifecycleCallbacks = new OptimizelyManager.OptlyActivityLifecycleCallbacks(optimizelyManager); 43 | } 44 | 45 | @Test 46 | public void onActivityStopped() { 47 | optlyActivityLifecycleCallbacks.onActivityStopped(activity); 48 | verify(optimizelyManager).stop(activity, optlyActivityLifecycleCallbacks); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /datafile-handler/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /datafile-handler/build.gradle: -------------------------------------------------------------------------------- 1 | /**************************************************************************** 2 | * Copyright 2016-2020, Optimizely, Inc. and contributors * 3 | * * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); * 5 | * you may not use this file except in compliance with the License. * 6 | * You may obtain a copy of the License at * 7 | * * 8 | * http://www.apache.org/licenses/LICENSE-2.0 * 9 | * * 10 | * Unless required by applicable law or agreed to in writing, software * 11 | * distributed under the License is distributed on an "AS IS" BASIS, * 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * 13 | * See the License for the specific language governing permissions and * 14 | * limitations under the License. * 15 | ***************************************************************************/ 16 | 17 | apply plugin: 'com.android.library' 18 | apply plugin: 'kotlin-android' 19 | 20 | android { 21 | compileSdkVersion compile_sdk_version 22 | buildToolsVersion build_tools_version 23 | 24 | defaultConfig { 25 | minSdkVersion min_sdk_version 26 | targetSdkVersion target_sdk_version 27 | multiDexEnabled true 28 | testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner' 29 | } 30 | testOptions { 31 | unitTests.returnDefaultValues = true 32 | } 33 | buildTypes { 34 | release { 35 | minifyEnabled false 36 | } 37 | debug { 38 | testCoverageEnabled true 39 | } 40 | } 41 | compileOptions { 42 | sourceCompatibility JavaVersion.VERSION_17 43 | targetCompatibility JavaVersion.VERSION_17 44 | } 45 | } 46 | 47 | dependencies { 48 | api project(':shared') 49 | implementation "androidx.annotation:annotation:$annotations_ver" 50 | implementation "androidx.work:work-runtime:$work_runtime" 51 | 52 | compileOnly "com.noveogroup.android:android-logger:$android_logger_ver" 53 | 54 | testImplementation "junit:junit:$junit_ver" 55 | testImplementation "org.mockito:mockito-core:$mockito_ver" 56 | testImplementation "com.noveogroup.android:android-logger:$android_logger_ver" 57 | 58 | androidTestImplementation "androidx.work:work-testing:$work_runtime" 59 | androidTestImplementation "androidx.test.ext:junit:$androidx_test_junit" 60 | androidTestImplementation "androidx.test.espresso:espresso-core:$espresso_ver" 61 | // Set this dependency to use JUnit 4 rules 62 | androidTestImplementation "androidx.test:rules:$androidx_test_rules" 63 | androidTestImplementation "androidx.test:core:$androidx_test_core" 64 | androidTestImplementation "androidx.test:core-ktx:$androidx_test_core" 65 | 66 | androidTestImplementation "org.mockito:mockito-core:$mockito_ver" 67 | androidTestImplementation "org.mockito:mockito-android:$mockito_ver" 68 | androidTestImplementation "com.noveogroup.android:android-logger:$android_logger_ver" 69 | androidTestImplementation "com.fasterxml.jackson.core:jackson-databind:$jacksonversion" 70 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" 71 | } 72 | -------------------------------------------------------------------------------- /datafile-handler/src/androidTest/java/com/optimizely/ab/android/datafile_handler/DatafileCacheTest.java: -------------------------------------------------------------------------------- 1 | /**************************************************************************** 2 | * Copyright 2016, Optimizely, Inc. and contributors * 3 | * * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); * 5 | * you may not use this file except in compliance with the License. * 6 | * You may obtain a copy of the License at * 7 | * * 8 | * http://www.apache.org/licenses/LICENSE-2.0 * 9 | * * 10 | * Unless required by applicable law or agreed to in writing, software * 11 | * distributed under the License is distributed on an "AS IS" BASIS, * 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * 13 | * See the License for the specific language governing permissions and * 14 | * limitations under the License. * 15 | ***************************************************************************/ 16 | 17 | package com.optimizely.ab.android.datafile_handler; 18 | 19 | import androidx.test.platform.app.InstrumentationRegistry; 20 | import androidx.test.ext.junit.runners.AndroidJUnit4; 21 | 22 | import com.optimizely.ab.android.shared.Cache; 23 | 24 | import org.json.JSONException; 25 | import org.json.JSONObject; 26 | import org.junit.Before; 27 | import org.junit.Test; 28 | import org.junit.runner.RunWith; 29 | import org.slf4j.Logger; 30 | 31 | import java.io.IOException; 32 | 33 | import static junit.framework.Assert.assertEquals; 34 | import static junit.framework.Assert.assertNotNull; 35 | import static junit.framework.Assert.assertNull; 36 | import static junit.framework.Assert.assertTrue; 37 | import static org.mockito.Mockito.mock; 38 | import static org.mockito.Mockito.verify; 39 | import static org.mockito.Mockito.when; 40 | import static org.mockito.ArgumentMatchers.any; 41 | import static org.mockito.ArgumentMatchers.contains; 42 | 43 | /** 44 | * Tests for {@link DatafileCache} 45 | */ 46 | @RunWith(AndroidJUnit4.class) 47 | public class DatafileCacheTest { 48 | 49 | private DatafileCache datafileCache; 50 | private Logger logger; 51 | 52 | @Before 53 | public void setup() { 54 | logger = mock(Logger.class); 55 | Cache cache = new Cache(InstrumentationRegistry.getInstrumentation().getTargetContext(), logger); 56 | datafileCache = new DatafileCache("1", cache, logger); 57 | } 58 | 59 | @Test 60 | public void loadBeforeSaving() { 61 | assertNull(datafileCache.load()); 62 | } 63 | 64 | @Test 65 | public void persistence() throws JSONException { 66 | assertTrue(datafileCache.save("{}")); 67 | final JSONObject jsonObject = datafileCache.load(); 68 | assertNotNull(jsonObject); 69 | final String actual = jsonObject.toString(); 70 | assertEquals(new JSONObject("{}").toString(), actual); 71 | assertTrue(datafileCache.delete()); 72 | assertNull(datafileCache.load()); 73 | } 74 | 75 | @Test 76 | public void loadJsonException() throws IOException { 77 | Cache cache = mock(Cache.class); 78 | DatafileCache datafileCache = new DatafileCache("1", cache, logger); 79 | when(cache.load(datafileCache.getFileName())).thenReturn("{"); 80 | assertNull(datafileCache.load()); 81 | verify(logger).error(contains("Unable to parse data file"), any(JSONException.class)); 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /datafile-handler/src/androidTest/java/com/optimizely/ab/android/datafile_handler/DatafileWorkerTest.java: -------------------------------------------------------------------------------- 1 | /**************************************************************************** 2 | * Copyright 2021, Optimizely, Inc. and contributors * 3 | * * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); * 5 | * you may not use this file except in compliance with the License. * 6 | * You may obtain a copy of the License at * 7 | * * 8 | * http://www.apache.org/licenses/LICENSE-2.0 * 9 | * * 10 | * Unless required by applicable law or agreed to in writing, software * 11 | * distributed under the License is distributed on an "AS IS" BASIS, * 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * 13 | * See the License for the specific language governing permissions and * 14 | * limitations under the License. * 15 | ***************************************************************************/ 16 | 17 | package com.optimizely.ab.android.datafile_handler; 18 | 19 | import static org.hamcrest.CoreMatchers.is; 20 | import static org.junit.Assert.assertEquals; 21 | import static org.junit.Assert.assertThat; 22 | import static org.mockito.ArgumentMatchers.any; 23 | import static org.mockito.ArgumentMatchers.anyString; 24 | import static org.mockito.ArgumentMatchers.eq; 25 | import static org.mockito.Mockito.mock; 26 | import static org.mockito.Mockito.verify; 27 | import static org.mockito.Mockito.when; 28 | 29 | import android.content.Context; 30 | 31 | import androidx.test.core.app.ApplicationProvider; 32 | import androidx.test.ext.junit.runners.AndroidJUnit4; 33 | import androidx.work.Data; 34 | import androidx.work.ListenableWorker; 35 | import androidx.work.testing.TestWorkerBuilder; 36 | 37 | import com.optimizely.ab.android.shared.Cache; 38 | import com.optimizely.ab.android.shared.DatafileConfig; 39 | import com.optimizely.ab.event.LogEvent; 40 | 41 | import org.junit.Before; 42 | import org.junit.Test; 43 | import org.junit.runner.RunWith; 44 | import org.slf4j.Logger; 45 | import org.slf4j.LoggerFactory; 46 | 47 | import java.util.concurrent.Executor; 48 | import java.util.concurrent.Executors; 49 | 50 | /** 51 | * Tests {@link DatafileWorker} 52 | */ 53 | @RunWith(AndroidJUnit4.class) 54 | public class DatafileWorkerTest { 55 | private Context context; 56 | private Executor executor; 57 | private String sdkKey = "sdkKey"; 58 | private Logger logger = LoggerFactory.getLogger("test"); 59 | 60 | @Before 61 | public void setUp() { 62 | context = ApplicationProvider.getApplicationContext(); 63 | executor = Executors.newSingleThreadExecutor(); 64 | } 65 | 66 | @Test 67 | public void testInputData() { 68 | DatafileConfig datafileConfig1 = new DatafileConfig(null, sdkKey); 69 | Data data = DatafileWorker.getData(datafileConfig1); 70 | 71 | DatafileConfig datafileConfig2 = DatafileWorker.getDataConfig(data); 72 | assertEquals(datafileConfig2.getKey(), sdkKey); 73 | } 74 | 75 | @Test 76 | public void testDatafileFetch() { 77 | DatafileWorker worker = mockDatafileWorker(sdkKey); 78 | worker.datafileLoader = mock(DatafileLoader.class); 79 | 80 | ListenableWorker.Result result = worker.doWork(); 81 | 82 | DatafileConfig datafileConfig = new DatafileConfig(null, sdkKey); 83 | String datafileUrl = datafileConfig.getUrl(); 84 | DatafileCache datafileCache = new DatafileCache(datafileConfig.getKey(), new Cache(context, logger), logger); 85 | 86 | verify(worker.datafileLoader).getDatafile(eq(datafileUrl), eq(datafileCache), eq(null)); 87 | assertThat(result, is(ListenableWorker.Result.success())); // success 88 | } 89 | 90 | // Helpers 91 | 92 | DatafileWorker mockDatafileWorker(String sdkKey) { 93 | DatafileConfig datafileConfig = new DatafileConfig(null, sdkKey); 94 | Data inputData = DatafileWorker.getData(datafileConfig); 95 | 96 | return (DatafileWorker) TestWorkerBuilder.from(context, DatafileWorker.class, executor) 97 | .setInputData(inputData) 98 | .build(); 99 | } 100 | 101 | } 102 | -------------------------------------------------------------------------------- /datafile-handler/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | <?xml version="1.0" encoding="utf-8"?> 2 | <!-- 3 | /**************************************************************************** 4 | * Copyright 2017, Optimizely, Inc. and contributors * 5 | * * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); * 7 | * you may not use this file except in compliance with the License. * 8 | * You may obtain a copy of the License at * 9 | * * 10 | * http://www.apache.org/licenses/LICENSE-2.0 * 11 | * * 12 | * Unless required by applicable law or agreed to in writing, software * 13 | * distributed under the License is distributed on an "AS IS" BASIS, * 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * 15 | * See the License for the specific language governing permissions and * 16 | * limitations under the License. * 17 | ***************************************************************************/ 18 | --> 19 | 20 | <manifest xmlns:android="http://schemas.android.com/apk/res/android" 21 | package="com.optimizely.ab.android.datafile_handler"> 22 | 23 | <uses-permission android:name="android.permission.INTERNET"/> 24 | <application> 25 | <receiver 26 | android:name="DatafileRescheduler" 27 | android:enabled="true" 28 | android:exported="false"> 29 | <intent-filter> 30 | <action android:name="android.intent.action.MY_PACKAGE_REPLACED" /> 31 | <action android:name="android.intent.action.BOOT_COMPLETED" /> 32 | </intent-filter> 33 | </receiver> 34 | </application> 35 | </manifest> 36 | -------------------------------------------------------------------------------- /datafile-handler/src/main/java/com/optimizely/ab/android/datafile_handler/DatafileCache.java: -------------------------------------------------------------------------------- 1 | /**************************************************************************** 2 | * Copyright 2016-2017, Optimizely, Inc. and contributors * 3 | * * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); * 5 | * you may not use this file except in compliance with the License. * 6 | * You may obtain a copy of the License at * 7 | * * 8 | * http://www.apache.org/licenses/LICENSE-2.0 * 9 | * * 10 | * Unless required by applicable law or agreed to in writing, software * 11 | * distributed under the License is distributed on an "AS IS" BASIS, * 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * 13 | * See the License for the specific language governing permissions and * 14 | * limitations under the License. * 15 | ***************************************************************************/ 16 | 17 | package com.optimizely.ab.android.datafile_handler; 18 | 19 | import androidx.annotation.NonNull; 20 | import androidx.annotation.Nullable; 21 | import androidx.annotation.VisibleForTesting; 22 | 23 | import com.optimizely.ab.android.shared.Cache; 24 | import com.optimizely.ab.android.shared.DatafileConfig; 25 | 26 | import org.json.JSONException; 27 | import org.json.JSONObject; 28 | import org.slf4j.Logger; 29 | 30 | /** 31 | * Abstracts the actual datafile to a cached file containing the JSONObject as a string. 32 | */ 33 | public class DatafileCache { 34 | 35 | private static final String FILENAME = "optly-data-file-%s.json"; 36 | 37 | @NonNull private final Cache cache; 38 | @NonNull private final String filename; 39 | @NonNull private final Logger logger; 40 | 41 | /** 42 | * Create a DatafileCache Object 43 | * @param cacheKey key used to cache 44 | * @param cache shared generic file based {link Cache} 45 | * @param logger logger to use 46 | */ 47 | public DatafileCache(@NonNull String cacheKey, @NonNull Cache cache, @NonNull Logger logger) { 48 | this.cache = cache; 49 | this.filename = String.format(FILENAME, cacheKey); 50 | this.logger = logger; 51 | } 52 | 53 | /** 54 | * Delete the datafile cache 55 | * @return true if successful 56 | */ 57 | public boolean delete() { 58 | return cache.delete(filename); 59 | } 60 | 61 | /** 62 | * Check to see if the datafile cache exists 63 | * @return true if it exists 64 | */ 65 | public boolean exists() { 66 | return cache.exists(filename); 67 | } 68 | 69 | /** 70 | * Return the filename to the datafile cache 71 | * @return filename for datafile cache 72 | */ 73 | @VisibleForTesting 74 | public String getFileName() { 75 | return filename; 76 | } 77 | 78 | /** 79 | * Loads the datafile from cache into a JSONObject 80 | * @return JSONObject if exists or null if it doesn't or there was a problem 81 | */ 82 | @Nullable 83 | public JSONObject load() { 84 | String datafile = cache.load(filename); 85 | 86 | if (datafile == null) { 87 | return null; 88 | } 89 | try { 90 | return new JSONObject(datafile); 91 | } catch (JSONException e) { 92 | logger.error("Unable to parse data file", e); 93 | return null; 94 | } 95 | } 96 | 97 | /** 98 | * Save a datafile to cache. 99 | * @param dataFile to write to cache 100 | * @return true if successful. 101 | */ 102 | public boolean save(String dataFile) { 103 | return cache.save(filename, dataFile); 104 | } 105 | 106 | public boolean equals(Object o) { 107 | if (o == this) { 108 | return true; 109 | } 110 | if (!(o instanceof DatafileCache)) { 111 | return false; 112 | } 113 | DatafileCache p = (DatafileCache) o; 114 | return this.filename.equals(p.filename); 115 | } 116 | 117 | } 118 | -------------------------------------------------------------------------------- /datafile-handler/src/main/java/com/optimizely/ab/android/datafile_handler/DatafileLoadedListener.java: -------------------------------------------------------------------------------- 1 | /**************************************************************************** 2 | * Copyright 2016-2017, Optimizely, Inc. and contributors * 3 | * * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); * 5 | * you may not use this file except in compliance with the License. * 6 | * You may obtain a copy of the License at * 7 | * * 8 | * http://www.apache.org/licenses/LICENSE-2.0 * 9 | * * 10 | * Unless required by applicable law or agreed to in writing, software * 11 | * distributed under the License is distributed on an "AS IS" BASIS, * 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * 13 | * See the License for the specific language governing permissions and * 14 | * limitations under the License. * 15 | ***************************************************************************/ 16 | 17 | package com.optimizely.ab.android.datafile_handler; 18 | 19 | import androidx.annotation.Nullable; 20 | 21 | /** 22 | * Listens for new Optimizely datafiles 23 | * 24 | * Datafiles can come from a local file or the CDN 25 | * 26 | * 27 | */ 28 | public interface DatafileLoadedListener { 29 | 30 | /** 31 | * Called with new datafile after a download. 32 | * 33 | * @param dataFile the datafile json, can be null if datafile loading failed. 34 | * 35 | */ 36 | void onDatafileLoaded(@Nullable String dataFile); 37 | } 38 | -------------------------------------------------------------------------------- /datafile-handler/src/main/java/com/optimizely/ab/android/datafile_handler/DatafileWorker.java: -------------------------------------------------------------------------------- 1 | /**************************************************************************** 2 | * Copyright 2021, Optimizely, Inc. and contributors * 3 | * * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); * 5 | * you may not use this file except in compliance with the License. * 6 | * You may obtain a copy of the License at * 7 | * * 8 | * http://www.apache.org/licenses/LICENSE-2.0 * 9 | * * 10 | * Unless required by applicable law or agreed to in writing, software * 11 | * distributed under the License is distributed on an "AS IS" BASIS, * 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * 13 | * See the License for the specific language governing permissions and * 14 | * limitations under the License. * 15 | ***************************************************************************/ 16 | package com.optimizely.ab.android.datafile_handler; 17 | 18 | import android.content.Context; 19 | 20 | import androidx.annotation.NonNull; 21 | import androidx.annotation.VisibleForTesting; 22 | import androidx.work.Data; 23 | import androidx.work.Worker; 24 | import androidx.work.WorkerParameters; 25 | 26 | import com.optimizely.ab.android.shared.Cache; 27 | import com.optimizely.ab.android.shared.Client; 28 | import com.optimizely.ab.android.shared.DatafileConfig; 29 | import com.optimizely.ab.android.shared.OptlyStorage; 30 | 31 | import org.slf4j.LoggerFactory; 32 | 33 | public class DatafileWorker extends Worker { 34 | public static final String workerId = "DatafileWorker"; 35 | 36 | @VisibleForTesting 37 | public DatafileLoader datafileLoader; 38 | 39 | public DatafileWorker(@NonNull Context context, @NonNull WorkerParameters workerParams) { 40 | super(context, workerParams); 41 | 42 | DatafileClient datafileClient = new DatafileClient( 43 | new Client(new OptlyStorage(context), LoggerFactory.getLogger(OptlyStorage.class)), 44 | LoggerFactory.getLogger(DatafileClient.class)); 45 | DatafileCache datafileCache = null; 46 | 47 | datafileLoader = new DatafileLoader(context, datafileClient, datafileCache, LoggerFactory.getLogger(DatafileLoader.class)); 48 | } 49 | 50 | public static Data getData(DatafileConfig datafileConfig) { 51 | return new Data.Builder().putString("DatafileConfig", datafileConfig.toJSONString()).build(); 52 | } 53 | 54 | public static DatafileConfig getDataConfig(Data data) { 55 | String extraDatafileConfig = data.getString("DatafileConfig"); 56 | return DatafileConfig.fromJSONString(extraDatafileConfig); 57 | } 58 | 59 | @NonNull 60 | @Override 61 | public Result doWork() { 62 | DatafileConfig datafileConfig = getDataConfig(getInputData()); 63 | String datafileUrl = datafileConfig.getUrl(); 64 | 65 | DatafileCache datafileCache = new DatafileCache( 66 | datafileConfig.getKey(), 67 | new Cache(this.getApplicationContext(), LoggerFactory.getLogger(Cache.class)), 68 | LoggerFactory.getLogger(DatafileCache.class)); 69 | 70 | datafileLoader.getDatafile(datafileUrl, datafileCache, null); 71 | 72 | return Result.success(); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /datafile-handler/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | <resources> 2 | <string name="app_name">datafile-handler</string> 3 | </resources> 4 | -------------------------------------------------------------------------------- /datafile-handler/src/test/java/com/optimizely/ab/android/datafile_handler/DefaultDatafileHandlerUnitTest.java: -------------------------------------------------------------------------------- 1 | package com.optimizely.ab.android.datafile_handler; 2 | 3 | import android.content.Context; 4 | 5 | import com.optimizely.ab.android.shared.DatafileConfig; 6 | 7 | import org.junit.Test; 8 | 9 | import static org.junit.Assert.assertFalse; 10 | import static org.mockito.Mockito.mock; 11 | import static org.mockito.Mockito.when; 12 | 13 | /** 14 | * Example local unit test, which will execute on the development machine (host). 15 | * 16 | * @see <a href="http://d.android.com/tools/testing">Testing documentation</a> 17 | */ 18 | public class DefaultDatafileHandlerUnitTest { 19 | 20 | DatafileHandler handler = mock(DefaultDatafileHandler.class); 21 | 22 | @Test 23 | public void testHandler() throws Exception { 24 | handler = new DefaultDatafileHandler(); 25 | Context context = mock(Context.class); 26 | when(context.getApplicationContext()).thenReturn(context); 27 | assertFalse(handler.isDatafileSaved(context, new DatafileConfig("1", null))); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /docs/readme.md: -------------------------------------------------------------------------------- 1 | # Generate Code Documentation 2 | ### Steps 3 | 4 | * Checkout **master** branch. 5 | * Open project in **Android Studio**. 6 | * Go to **Tools -> Generate JavaDoc** 7 | * Make sure **Generate JavaDoc scope -> Custom Scope** is checked. 8 | * Click on three dotted icon to make custom scope. 9 | * Give name **Custom_Docs**. 10 | * Copy and paste following regex: **`!test:*..*&&!lib:*..*&&!src[test-app]:*..*`** into **Pattern** and click **OK**. 11 | * Uncheck **Include test sources**. 12 | * Select **Output Directory**. 13 | * Click **OK**. 14 | * This will generate HTML documentation in given **Output Directory**. 15 | * Browse **Output Directory/index.html** in browser. 16 | -------------------------------------------------------------------------------- /event-handler/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /event-handler/build.gradle: -------------------------------------------------------------------------------- 1 | /**************************************************************************** 2 | * Copyright 2016, Optimizely, Inc. and contributors * 3 | * * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); * 5 | * you may not use this file except in compliance with the License. * 6 | * You may obtain a copy of the License at * 7 | * * 8 | * http://www.apache.org/licenses/LICENSE-2.0 * 9 | * * 10 | * Unless required by applicable law or agreed to in writing, software * 11 | * distributed under the License is distributed on an "AS IS" BASIS, * 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * 13 | * See the License for the specific language governing permissions and * 14 | * limitations under the License. * 15 | ***************************************************************************/ 16 | 17 | apply plugin: 'com.android.library' 18 | apply plugin: 'kotlin-android' 19 | 20 | android { 21 | compileSdkVersion compile_sdk_version 22 | buildToolsVersion build_tools_version 23 | 24 | defaultConfig { 25 | minSdkVersion min_sdk_version 26 | targetSdkVersion target_sdk_version 27 | multiDexEnabled true 28 | testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner' 29 | } 30 | testOptions { 31 | unitTests.returnDefaultValues = true 32 | unitTests.all { 33 | jvmArgs '--add-opens', 'java.base/java.lang=ALL-UNNAMED' 34 | jvmArgs '--add-opens=java.base/java.util.zip=ALL-UNNAMED' 35 | jvmArgs '--add-opens=java.base/java.io=ALL-UNNAMED' 36 | } 37 | } 38 | buildTypes { 39 | release { 40 | minifyEnabled false 41 | } 42 | debug { 43 | testCoverageEnabled true 44 | } 45 | } 46 | compileOptions { 47 | sourceCompatibility JavaVersion.VERSION_17 48 | targetCompatibility JavaVersion.VERSION_17 49 | } 50 | buildToolsVersion build_tools_version 51 | } 52 | 53 | dependencies { 54 | api project(':shared') 55 | implementation "androidx.annotation:annotation:$annotations_ver" 56 | implementation "androidx.work:work-runtime:$work_runtime" 57 | 58 | compileOnly "com.noveogroup.android:android-logger:$android_logger_ver" 59 | 60 | testImplementation "junit:junit:$junit_ver" 61 | testImplementation "org.mockito:mockito-core:$mockito_ver" 62 | testImplementation "org.mockito:mockito-inline:$mockito_ver" 63 | testImplementation "com.noveogroup.android:android-logger:$android_logger_ver" 64 | 65 | androidTestImplementation "androidx.work:work-testing:$work_runtime" 66 | androidTestImplementation "androidx.test.ext:junit:$androidx_test_junit" 67 | androidTestImplementation "androidx.test.espresso:espresso-core:$espresso_ver" 68 | // Set this dependency to use JUnit 4 rules 69 | androidTestImplementation "androidx.test:rules:$androidx_test_rules" 70 | androidTestImplementation "androidx.test:core:$androidx_test_core" 71 | androidTestImplementation "androidx.test:core-ktx:$androidx_test_core" 72 | 73 | androidTestImplementation "org.mockito:mockito-core:$mockito_ver" 74 | androidTestImplementation "org.mockito:mockito-android:$mockito_ver" 75 | androidTestImplementation "com.noveogroup.android:android-logger:$android_logger_ver" 76 | androidTestImplementation "com.fasterxml.jackson.core:jackson-databind:$jacksonversion" 77 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" 78 | } 79 | 80 | -------------------------------------------------------------------------------- /event-handler/src/androidTest/java/com/optimizely/ab/android/event_handler/DefaultEventHandlerTest.java: -------------------------------------------------------------------------------- 1 | /**************************************************************************** 2 | * Copyright 2017, Optimizely, Inc. and contributors * 3 | * * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); * 5 | * you may not use this file except in compliance with the License. * 6 | * You may obtain a copy of the License at * 7 | * * 8 | * http://www.apache.org/licenses/LICENSE-2.0 * 9 | * * 10 | * Unless required by applicable law or agreed to in writing, software * 11 | * distributed under the License is distributed on an "AS IS" BASIS, * 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * 13 | * See the License for the specific language governing permissions and * 14 | * limitations under the License. * 15 | ***************************************************************************/ 16 | 17 | package com.optimizely.ab.android.event_handler; 18 | 19 | import android.content.Context; 20 | 21 | import androidx.test.ext.junit.runners.AndroidJUnit4; 22 | import androidx.test.platform.app.InstrumentationRegistry; 23 | 24 | import com.optimizely.ab.event.LogEvent; 25 | import com.optimizely.ab.event.internal.payload.EventBatch; 26 | 27 | import org.junit.Before; 28 | import org.junit.Test; 29 | import org.junit.runner.RunWith; 30 | import org.slf4j.Logger; 31 | 32 | import java.net.MalformedURLException; 33 | import java.util.HashMap; 34 | 35 | import static org.mockito.Mockito.mock; 36 | import static org.mockito.Mockito.verify; 37 | 38 | /** 39 | * Tests {@link DefaultEventHandler} 40 | */ 41 | @RunWith(AndroidJUnit4.class) 42 | public class DefaultEventHandlerTest { 43 | private Context context; 44 | private Logger logger; 45 | 46 | private DefaultEventHandler eventHandler; 47 | private String url = "http://www.foo.com"; 48 | 49 | @Before 50 | public void setupEventHandler() { 51 | context = InstrumentationRegistry.getInstrumentation().getTargetContext(); 52 | logger = mock(Logger.class); 53 | eventHandler = DefaultEventHandler.getInstance(context); 54 | eventHandler.logger = logger; 55 | } 56 | 57 | @Test 58 | public void dispatchEventSuccess() throws MalformedURLException { 59 | eventHandler.dispatchEvent(new LogEvent(LogEvent.RequestMethod.POST, url, new HashMap<String, String>(), new EventBatch())); 60 | verify(logger).info("Sent url {} to the event handler service", "http://www.foo.com"); 61 | } 62 | 63 | @Test 64 | public void dispatchEmptyUrlString() { 65 | eventHandler.dispatchEvent(new LogEvent(LogEvent.RequestMethod.POST, "", new HashMap<String, String>(), new EventBatch())); 66 | verify(logger).error("Event dispatcher received an empty url"); 67 | } 68 | 69 | @Test 70 | public void dispatchEmptyParams() { 71 | eventHandler.setDispatchInterval(60L * 1000L); 72 | 73 | eventHandler.dispatchEvent(new LogEvent(LogEvent.RequestMethod.POST, url, new HashMap<String, String>(), new EventBatch())); 74 | //verify(context).startService(any(Intent.class)); 75 | verify(logger).info("Sent url {} to the event handler service (with retry interval of {} seconds)", "http://www.foo.com", 60L); 76 | } 77 | 78 | } 79 | -------------------------------------------------------------------------------- /event-handler/src/androidTest/java/com/optimizely/ab/android/event_handler/EventClientTest.java: -------------------------------------------------------------------------------- 1 | /**************************************************************************** 2 | * Copyright 2017, Optimizely, Inc. and contributors * 3 | * * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); * 5 | * you may not use this file except in compliance with the License. * 6 | * You may obtain a copy of the License at * 7 | * * 8 | * http://www.apache.org/licenses/LICENSE-2.0 * 9 | * * 10 | * Unless required by applicable law or agreed to in writing, software * 11 | * distributed under the License is distributed on an "AS IS" BASIS, * 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * 13 | * See the License for the specific language governing permissions and * 14 | * limitations under the License. * 15 | ***************************************************************************/ 16 | 17 | package com.optimizely.ab.android.event_handler; 18 | 19 | import androidx.annotation.RequiresApi; 20 | import androidx.test.ext.junit.runners.AndroidJUnit4; 21 | 22 | import com.optimizely.ab.android.shared.Client; 23 | 24 | import org.junit.Before; 25 | import org.junit.Test; 26 | import org.junit.runner.RunWith; 27 | import org.mockito.ArgumentCaptor; 28 | import org.mockito.Mock; 29 | import org.slf4j.Logger; 30 | 31 | import java.io.IOException; 32 | import java.io.InputStream; 33 | import java.io.OutputStream; 34 | import java.net.HttpURLConnection; 35 | import java.net.URL; 36 | 37 | import static org.mockito.ArgumentMatchers.any; 38 | import static org.mockito.ArgumentMatchers.anyInt; 39 | import static org.mockito.ArgumentMatchers.contains; 40 | import static org.mockito.Mockito.doThrow; 41 | import static org.mockito.Mockito.mock; 42 | import static org.mockito.Mockito.verify; 43 | import static org.mockito.Mockito.when; 44 | 45 | /** 46 | * Tests {@link DefaultEventHandler} 47 | */ 48 | @RunWith(AndroidJUnit4.class) 49 | public class EventClientTest { 50 | 51 | @Mock 52 | Logger logger = mock(Logger.class); 53 | @Mock 54 | Client client = mock(Client.class); 55 | private HttpURLConnection urlConnection; 56 | private EventClient eventClient; 57 | private Event event; 58 | 59 | @Before 60 | public void setupEventClient() throws IOException { 61 | urlConnection = mock(HttpURLConnection.class); 62 | when(urlConnection.getOutputStream()).thenReturn(mock(OutputStream.class)); 63 | when(urlConnection.getInputStream()).thenReturn(mock(InputStream.class)); 64 | this.eventClient = new EventClient(client, logger); 65 | URL url = new URL("http://www.foo.com"); 66 | event = new Event(url, ""); 67 | } 68 | 69 | @Test 70 | public void testEventClient() { 71 | eventClient.sendEvent(event); 72 | 73 | verify(logger).debug("SendEvent completed: {}", event); 74 | } 75 | 76 | @Test 77 | public void testConnectionAndReadTimeout() throws IOException { 78 | when(client.openConnection(event.getURL())).thenReturn(urlConnection); 79 | when(urlConnection.getResponseCode()).thenReturn(200); 80 | doThrow(new IOException()).when(urlConnection).getOutputStream(); 81 | 82 | eventClient.sendEvent(event); 83 | 84 | 85 | ArgumentCaptor<Client.Request> captor1 = ArgumentCaptor.forClass(Client.Request.class); 86 | verify(client).execute(captor1.capture(), anyInt(), anyInt()); 87 | Object response = captor1.getValue().execute(); 88 | 89 | verify(logger).error(contains("Unable to send event"), any(Event.class), any(IOException.class)); 90 | verify(urlConnection).setConnectTimeout(10*1000); 91 | verify(urlConnection).setReadTimeout(60*1000); 92 | verify(urlConnection).disconnect(); 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /event-handler/src/androidTest/java/com/optimizely/ab/android/event_handler/EventReschedulerTest.java: -------------------------------------------------------------------------------- 1 | // Copyright 2016,2021,2023, Optimizely, Inc. and contributors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package com.optimizely.ab.android.event_handler; 16 | 17 | import android.content.Context; 18 | import android.content.Intent; 19 | import android.net.NetworkInfo; 20 | import android.net.wifi.WifiManager; 21 | 22 | import org.junit.Before; 23 | import org.junit.Ignore; 24 | import org.junit.Test; 25 | import org.junit.runner.RunWith; 26 | import org.mockito.junit.MockitoJUnitRunner; 27 | import org.slf4j.Logger; 28 | 29 | import static org.mockito.ArgumentMatchers.any; 30 | import static org.mockito.ArgumentMatchers.matches; 31 | import static org.mockito.Mockito.doNothing; 32 | import static org.mockito.Mockito.doReturn; 33 | import static org.mockito.Mockito.doThrow; 34 | import static org.mockito.Mockito.mock; 35 | import static org.mockito.Mockito.spy; 36 | import static org.mockito.Mockito.verify; 37 | import static org.mockito.Mockito.when; 38 | 39 | import androidx.test.ext.junit.runners.AndroidJUnit4; 40 | 41 | /** 42 | * Unit tests for {@link EventRescheduler} 43 | */ 44 | @RunWith(AndroidJUnit4.class) 45 | public class EventReschedulerTest { 46 | 47 | private Context context; 48 | private Intent intent; 49 | private Logger logger; 50 | private EventRescheduler rescheduler; 51 | 52 | @Before 53 | public void setupEventRescheduler() { 54 | context = mock(Context.class); 55 | intent = mock(Intent.class); 56 | logger = mock(Logger.class); 57 | rescheduler = spy(new EventRescheduler()); 58 | rescheduler.logger = logger; 59 | } 60 | 61 | @Test 62 | public void onReceiveNullIntent() { 63 | rescheduler.onReceive(context, null); 64 | verify(logger).warn("Received invalid broadcast to event rescheduler"); 65 | } 66 | 67 | @Test 68 | public void onReceiveNullContext() { 69 | rescheduler.onReceive(null, intent); 70 | verify(logger).warn("Received invalid broadcast to event rescheduler"); 71 | } 72 | 73 | @Test 74 | public void onReceiveInvalidAction() { 75 | when(intent.getAction()).thenReturn("invalid"); 76 | rescheduler.onReceive(context, intent); 77 | verify(logger).warn("Received unsupported broadcast action to event rescheduler"); 78 | } 79 | 80 | @Test 81 | public void onReceiveWhenRescheduleWithException() { 82 | when(intent.getAction()).thenThrow(new IllegalStateException()); 83 | rescheduler.onReceive(context, intent); 84 | verify(logger).warn(matches("WorkScheduler failed to reschedule an event service.*")); 85 | } 86 | 87 | @Test 88 | public void onReceiveValidBootComplete() { 89 | when(intent.getAction()).thenReturn(Intent.ACTION_BOOT_COMPLETED); 90 | rescheduler.onReceive(context, intent); 91 | verify(logger).info("Rescheduling event flushing if necessary"); 92 | } 93 | 94 | @Test 95 | public void onReceiveValidPackageReplaced() { 96 | when(intent.getAction()).thenReturn(Intent.ACTION_MY_PACKAGE_REPLACED); 97 | rescheduler.onReceive(context, intent); 98 | verify(logger).info("Rescheduling event flushing if necessary"); 99 | } 100 | 101 | @Test 102 | public void flushOnWifiConnectionIfScheduled() { 103 | final Intent eventServiceIntent = mock(Intent.class); 104 | when(intent.getAction()).thenReturn(WifiManager.WIFI_STATE_CHANGED_ACTION); 105 | NetworkInfo info = mock(NetworkInfo.class); 106 | when(info.isConnected()).thenReturn(true); 107 | when(intent.getParcelableExtra(WifiManager.EXTRA_NETWORK_INFO)).thenReturn(info); 108 | 109 | rescheduler.reschedule(context, intent); 110 | verify(logger).info("Preemptively flushing events since wifi became available"); 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /event-handler/src/androidTest/java/com/optimizely/ab/android/event_handler/EventSQLiteOpenHelperTest.java: -------------------------------------------------------------------------------- 1 | /**************************************************************************** 2 | * Copyright 2016,2021, Optimizely, Inc. and contributors * 3 | * * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); * 5 | * you may not use this file except in compliance with the License. * 6 | * You may obtain a copy of the License at * 7 | * * 8 | * http://www.apache.org/licenses/LICENSE-2.0 * 9 | * * 10 | * Unless required by applicable law or agreed to in writing, software * 11 | * distributed under the License is distributed on an "AS IS" BASIS, * 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * 13 | * See the License for the specific language governing permissions and * 14 | * limitations under the License. * 15 | ***************************************************************************/ 16 | 17 | package com.optimizely.ab.android.event_handler; 18 | 19 | import android.content.Context; 20 | import android.database.Cursor; 21 | import android.database.sqlite.SQLiteDatabase; 22 | import android.os.Build; 23 | import androidx.annotation.RequiresApi; 24 | import androidx.test.platform.app.InstrumentationRegistry; 25 | import androidx.test.ext.junit.runners.AndroidJUnit4; 26 | 27 | import org.junit.After; 28 | import org.junit.Before; 29 | import org.junit.Test; 30 | import org.junit.runner.RunWith; 31 | import org.slf4j.Logger; 32 | 33 | import static junit.framework.Assert.assertEquals; 34 | import static org.mockito.Mockito.mock; 35 | import static org.mockito.Mockito.verify; 36 | 37 | /** 38 | * Tests for {@link EventSQLiteOpenHelper} 39 | */ 40 | @RunWith(AndroidJUnit4.class) 41 | public class EventSQLiteOpenHelperTest { 42 | 43 | private Context context; 44 | private Logger logger; 45 | 46 | @Before 47 | public void setup() { 48 | context = InstrumentationRegistry.getInstrumentation().getTargetContext(); 49 | logger = mock(Logger.class); 50 | } 51 | 52 | @After 53 | public void teardown() { 54 | context.deleteDatabase(String.format(EventSQLiteOpenHelper.DB_NAME, "1")); 55 | } 56 | 57 | @Test 58 | public void onCreateMakesTables() { 59 | EventSQLiteOpenHelper eventSQLiteOpenHelper = 60 | new EventSQLiteOpenHelper(context, "1", null, 1, logger); 61 | SQLiteDatabase db = eventSQLiteOpenHelper.getWritableDatabase(); 62 | String[] projection = { 63 | EventTable.Column._ID, 64 | EventTable.Column.URL, 65 | }; 66 | Cursor cursor = db.query(EventTable.NAME, projection, null, null, null, null, null); 67 | assertEquals(2, cursor.getColumnCount()); 68 | cursor.close(); 69 | 70 | verify(logger).info("Created event table with SQL: {}", EventSQLiteOpenHelper.SQL_CREATE_EVENT_TABLE); 71 | } 72 | 73 | } 74 | -------------------------------------------------------------------------------- /event-handler/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | <?xml version="1.0" encoding="utf-8"?> 2 | <!-- 3 | /**************************************************************************** 4 | * Copyright 2017, Optimizely, Inc. and contributors * 5 | * * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); * 7 | * you may not use this file except in compliance with the License. * 8 | * You may obtain a copy of the License at * 9 | * * 10 | * http://www.apache.org/licenses/LICENSE-2.0 * 11 | * * 12 | * Unless required by applicable law or agreed to in writing, software * 13 | * distributed under the License is distributed on an "AS IS" BASIS, * 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * 15 | * See the License for the specific language governing permissions and * 16 | * limitations under the License. * 17 | ***************************************************************************/ 18 | --> 19 | 20 | <manifest xmlns:android="http://schemas.android.com/apk/res/android" 21 | package="com.optimizely.ab.android.event_handler"> 22 | 23 | <uses-permission android:name="android.permission.INTERNET"/> 24 | 25 | <application> 26 | <receiver 27 | android:name=".EventRescheduler" 28 | android:enabled="true" 29 | android:exported="false"> 30 | <intent-filter> 31 | <action android:name="android.intent.action.MY_PACKAGE_REPLACED" /> 32 | <action android:name="android.intent.action.BOOT_COMPLETED" /> 33 | </intent-filter> 34 | </receiver> 35 | </application> 36 | 37 | </manifest> 38 | -------------------------------------------------------------------------------- /event-handler/src/main/java/com/optimizely/ab/android/event_handler/Event.java: -------------------------------------------------------------------------------- 1 | /**************************************************************************** 2 | * Copyright 2016, Optimizely, Inc. and contributors * 3 | * * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); * 5 | * you may not use this file except in compliance with the License. * 6 | * You may obtain a copy of the License at * 7 | * * 8 | * http://www.apache.org/licenses/LICENSE-2.0 * 9 | * * 10 | * Unless required by applicable law or agreed to in writing, software * 11 | * distributed under the License is distributed on an "AS IS" BASIS, * 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * 13 | * See the License for the specific language governing permissions and * 14 | * limitations under the License. * 15 | ***************************************************************************/ 16 | 17 | package com.optimizely.ab.android.event_handler; 18 | 19 | import java.net.URL; 20 | 21 | /** 22 | * Event model 23 | */ 24 | class Event { 25 | private final URL url; 26 | private final String requestBody; 27 | 28 | Event(URL url, String requestBody) { 29 | this.url = url; 30 | this.requestBody = requestBody; 31 | } 32 | 33 | String getRequestBody() { 34 | return this.requestBody; 35 | } 36 | 37 | URL getURL() { 38 | return this.url; 39 | } 40 | 41 | /** 42 | * Returns the String representation of an event 43 | * 44 | * @hide 45 | */ 46 | @Override 47 | public String toString() { 48 | return String.format("Sending %s to %s", requestBody, url); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /event-handler/src/main/java/com/optimizely/ab/android/event_handler/EventSQLiteOpenHelper.java: -------------------------------------------------------------------------------- 1 | /**************************************************************************** 2 | * Copyright 2016,2021, Optimizely, Inc. and contributors * 3 | * * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); * 5 | * you may not use this file except in compliance with the License. * 6 | * You may obtain a copy of the License at * 7 | * * 8 | * http://www.apache.org/licenses/LICENSE-2.0 * 9 | * * 10 | * Unless required by applicable law or agreed to in writing, software * 11 | * distributed under the License is distributed on an "AS IS" BASIS, * 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * 13 | * See the License for the specific language governing permissions and * 14 | * limitations under the License. * 15 | ***************************************************************************/ 16 | 17 | package com.optimizely.ab.android.event_handler; 18 | 19 | import android.content.Context; 20 | import android.database.sqlite.SQLiteDatabase; 21 | import android.database.sqlite.SQLiteOpenHelper; 22 | import android.os.Build; 23 | import androidx.annotation.NonNull; 24 | import androidx.annotation.Nullable; 25 | import androidx.annotation.RequiresApi; 26 | 27 | import org.slf4j.Logger; 28 | 29 | /** 30 | * Handles transactions for the events db 31 | */ 32 | class EventSQLiteOpenHelper extends SQLiteOpenHelper { 33 | 34 | static final int VERSION = 1; 35 | static final String DB_NAME = "optly-events-%s"; 36 | 37 | static final String SQL_CREATE_EVENT_TABLE = 38 | "CREATE TABLE " + EventTable.NAME + " (" + 39 | EventTable._ID + " INTEGER PRIMARY KEY, " + 40 | EventTable.Column.URL + " TEXT NOT NULL," + 41 | EventTable.Column.REQUEST_BODY + " TEXT NOT NULL" + 42 | ")"; 43 | 44 | private static final String SQL_DELETE_EVENT_TABLE = 45 | "DROP TABLE IF EXISTS " + EventTable.NAME; 46 | 47 | @NonNull private final Logger logger; 48 | @NonNull private final String projectId; 49 | @NonNull private final Context context; 50 | 51 | EventSQLiteOpenHelper(@NonNull Context context, @NonNull String projectId, @Nullable SQLiteDatabase.CursorFactory factory, int version, @NonNull Logger logger) { 52 | super(context, String.format(DB_NAME, projectId), factory, version); 53 | this.logger = logger; 54 | this.projectId = projectId; 55 | this.context = context; 56 | } 57 | 58 | /** 59 | * @hide 60 | * @see SQLiteOpenHelper#onCreate(SQLiteDatabase) 61 | */ 62 | @Override 63 | public void onCreate(SQLiteDatabase db) { 64 | try { 65 | // Deletes the old events db that stored events for all projects 66 | context.deleteDatabase("optly-events"); 67 | db.execSQL(SQL_CREATE_EVENT_TABLE); 68 | logger.info("Created event table with SQL: {}", SQL_CREATE_EVENT_TABLE); 69 | } catch (Exception e) { 70 | logger.error("Error creating optly-events table.", e); 71 | } 72 | } 73 | 74 | /** 75 | * @hide 76 | * @see SQLiteOpenHelper#onUpgrade(SQLiteDatabase, int, int) 77 | */ 78 | 79 | @Override 80 | public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { 81 | 82 | } 83 | 84 | public String getDbName() { 85 | return String.format(DB_NAME, projectId); 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /event-handler/src/main/java/com/optimizely/ab/android/event_handler/EventTable.java: -------------------------------------------------------------------------------- 1 | /**************************************************************************** 2 | * Copyright 2016, Optimizely, Inc. and contributors * 3 | * * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); * 5 | * you may not use this file except in compliance with the License. * 6 | * You may obtain a copy of the License at * 7 | * * 8 | * http://www.apache.org/licenses/LICENSE-2.0 * 9 | * * 10 | * Unless required by applicable law or agreed to in writing, software * 11 | * distributed under the License is distributed on an "AS IS" BASIS, * 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * 13 | * See the License for the specific language governing permissions and * 14 | * limitations under the License. * 15 | ***************************************************************************/ 16 | 17 | package com.optimizely.ab.android.event_handler; 18 | 19 | import android.provider.BaseColumns; 20 | 21 | /** 22 | * Constants for Event SQL table 23 | */ 24 | final class EventTable implements BaseColumns { 25 | static final String NAME = "event"; 26 | 27 | private EventTable() { 28 | } 29 | 30 | class Column { 31 | static final String _ID = BaseColumns._ID; 32 | static final String URL = "url"; 33 | static final String REQUEST_BODY = "requestBody"; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /event-handler/src/test/resources/org.mockito.plugins.MockMaker: -------------------------------------------------------------------------------- 1 | mock-maker-inline 2 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | android.enableJetifier=true 2 | android.useAndroidX=true 3 | org.gradle.jvmargs=-Xmx1g 4 | android.disableAutomaticComponentCreation=true -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/optimizely/android-sdk/841a5d236d822e53803e4064665663b221057b64/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Thu Jan 28 11:38:35 PST 2021 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | org.gradle.jvmargs=-Xmx1g 6 | zipStorePath=wrapper/dists 7 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-bin.zip 8 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 12 | set DEFAULT_JVM_OPTS= 13 | 14 | set DIRNAME=%~dp0 15 | if "%DIRNAME%" == "" set DIRNAME=. 16 | set APP_BASE_NAME=%~n0 17 | set APP_HOME=%DIRNAME% 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windowz variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | if "%@eval[2+2]" == "4" goto 4NT_args 53 | 54 | :win9xME_args 55 | @rem Slurp the command line arguments. 56 | set CMD_LINE_ARGS= 57 | set _SKIP=2 58 | 59 | :win9xME_args_slurp 60 | if "x%~1" == "x" goto execute 61 | 62 | set CMD_LINE_ARGS=%* 63 | goto execute 64 | 65 | :4NT_args 66 | @rem Get arguments from the 4NT Shell from JP Software 67 | set CMD_LINE_ARGS=%$ 68 | 69 | :execute 70 | @rem Setup the command line 71 | 72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if "%ERRORLEVEL%"=="0" goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 85 | exit /b 1 86 | 87 | :mainEnd 88 | if "%OS%"=="Windows_NT" endlocal 89 | 90 | :omega 91 | -------------------------------------------------------------------------------- /odp/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /odp/build.gradle: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2022, Optimizely, Inc. and contributors 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // https://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | 17 | apply plugin: 'com.android.library' 18 | apply plugin: 'kotlin-android' 19 | apply plugin: 'org.jlleitschuh.gradle.ktlint' 20 | 21 | android { 22 | compileSdkVersion compile_sdk_version 23 | buildToolsVersion build_tools_version 24 | 25 | defaultConfig { 26 | minSdkVersion min_sdk_version 27 | targetSdkVersion target_sdk_version 28 | versionCode 1 29 | versionName "1.0" 30 | multiDexEnabled true 31 | testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner' 32 | } 33 | testOptions { 34 | unitTests.returnDefaultValues = true 35 | } 36 | buildTypes { 37 | release { 38 | minifyEnabled false 39 | } 40 | debug { 41 | testCoverageEnabled true 42 | } 43 | } 44 | compileOptions { 45 | sourceCompatibility JavaVersion.VERSION_17 46 | targetCompatibility JavaVersion.VERSION_17 47 | } 48 | buildToolsVersion build_tools_version 49 | 50 | kotlinOptions { 51 | jvmTarget = '1.8' 52 | } 53 | } 54 | 55 | dependencies { 56 | api project(':shared') 57 | implementation "androidx.annotation:annotation:$annotations_ver" 58 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" 59 | implementation "androidx.work:work-runtime:$work_runtime" 60 | // Add SLF4J API for Logger interface 61 | implementation "org.slf4j:slf4j-api:$slf4j_ver" 62 | 63 | testImplementation "junit:junit:$junit_ver" 64 | testImplementation "org.mockito:mockito-core:$mockito_ver" 65 | 66 | compileOnly "com.noveogroup.android:android-logger:$android_logger_ver" 67 | 68 | androidTestImplementation "androidx.work:work-testing:$work_runtime" 69 | androidTestImplementation "androidx.test.ext:junit-ktx:$androidx_test_junit" 70 | androidTestImplementation "androidx.test.ext:junit:$androidx_test_junit" 71 | androidTestImplementation "androidx.test.espresso:espresso-core:$espresso_ver" 72 | // Set this dependency to use JUnit 4 rules 73 | androidTestImplementation "androidx.test:rules:$androidx_test_rules" 74 | androidTestImplementation "androidx.test:core:$androidx_test_core" 75 | androidTestImplementation "androidx.test:core-ktx:$androidx_test_core" 76 | 77 | androidTestImplementation "org.mockito:mockito-core:$mockito_ver" 78 | androidTestImplementation "org.mockito:mockito-android:$mockito_ver" 79 | androidTestImplementation "org.slf4j:slf4j-api:$slf4j_ver" 80 | } 81 | -------------------------------------------------------------------------------- /odp/src/androidTest/java/com/optimizely/ab/android/odp/DefaultODPApiManagerTest.kt: -------------------------------------------------------------------------------- 1 | // Copyright 2022-2023, Optimizely, Inc. and contributors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package com.optimizely.ab.android.odp 16 | 17 | import android.content.Context 18 | import androidx.test.core.app.ApplicationProvider 19 | import androidx.test.ext.junit.runners.AndroidJUnit4 20 | import org.junit.Assert.assertEquals 21 | import org.junit.Test 22 | import org.junit.runner.RunWith 23 | import org.mockito.Mockito.mock 24 | import org.mockito.Mockito.verify 25 | 26 | @RunWith(AndroidJUnit4::class) 27 | class DefaultODPApiManagerTest { 28 | private val apiKey = "valid-key" 29 | private val apiEndpoint = "http://valid-endpoint" 30 | 31 | private var context: Context = ApplicationProvider.getApplicationContext() 32 | private val defaultODPApiManager = DefaultODPApiManager(context, 10, 10) 33 | 34 | @Test 35 | fun fetchQualifiedSegments() { 36 | val userKey = "fs_user_id" 37 | val userValue = "user123" 38 | val segmentsToCheck = setOf("a") 39 | 40 | val expRequestPayload = "{\"query\": \"query(\$userId: String, \$audiences: [String]) {customer(fs_user_id: \$userId) {audiences(subset: \$audiences) {edges {node {name state}}}}}\", \"variables\": {\"userId\": \"user123\", \"audiences\": [\"a\"]}}" 41 | 42 | val segmentClient = mock(ODPSegmentClient::class.java) 43 | defaultODPApiManager.segmentClient = segmentClient 44 | 45 | defaultODPApiManager.fetchQualifiedSegments(apiKey, apiEndpoint, userKey, userValue, segmentsToCheck) 46 | verify(segmentClient).fetchQualifiedSegments(apiKey, apiEndpoint, expRequestPayload) 47 | } 48 | 49 | @Test 50 | fun sendEvents() { 51 | val payload = "event-payload" 52 | val status = defaultODPApiManager.sendEvents(apiKey, apiEndpoint, payload) 53 | assertEquals(status, 200) // always return success to java-sdk 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /odp/src/androidTest/java/com/optimizely/ab/android/odp/ODPSegmentClientTest.kt: -------------------------------------------------------------------------------- 1 | // Copyright 2022, Optimizely, Inc. and contributors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package com.optimizely.ab.android.odp 16 | 17 | import androidx.test.ext.junit.runners.AndroidJUnit4 18 | import com.optimizely.ab.android.shared.Client 19 | import java.io.OutputStream 20 | import java.net.HttpURLConnection 21 | import org.junit.Assert.assertNull 22 | import org.junit.Before 23 | import org.junit.Test 24 | import org.junit.runner.RunWith 25 | import org.mockito.ArgumentCaptor 26 | import org.mockito.ArgumentMatchers.any 27 | import org.mockito.ArgumentMatchers.contains 28 | import org.mockito.ArgumentMatchers.eq 29 | import org.mockito.Mockito.`when` 30 | import org.mockito.Mockito.mock 31 | import org.mockito.Mockito.verify 32 | import org.slf4j.Logger 33 | 34 | @RunWith(AndroidJUnit4::class) 35 | class ODPSegmentClientTest { 36 | private val logger = mock(Logger::class.java) 37 | private val client = mock(Client::class.java) 38 | private val urlConnection = mock(HttpURLConnection::class.java) 39 | private val captor = ArgumentCaptor.forClass(Client.Request::class.java) 40 | private lateinit var segmentClient: ODPSegmentClient 41 | 42 | private val apiKey = "valid-key" 43 | private var apiEndpoint = "http://valid-endpoint" 44 | private val payload = "valid-payload" 45 | private val response = "valid-response" 46 | 47 | @Before 48 | fun setUp() { 49 | segmentClient = ODPSegmentClient(client, logger) 50 | `when`(client.openConnection(any())).thenReturn(urlConnection) 51 | `when`(client.readStream(urlConnection)).thenReturn(response) 52 | `when`(urlConnection.outputStream).thenReturn(mock(OutputStream::class.java)) 53 | } 54 | 55 | @Test 56 | fun fetchQualifiedSegments_200() { 57 | `when`(urlConnection.responseCode).thenReturn(200) 58 | 59 | segmentClient.fetchQualifiedSegments(apiKey, apiEndpoint, payload) 60 | 61 | verify(client).execute(captor.capture(), eq(0), eq(0)) 62 | val received = captor.value.execute() 63 | 64 | assert(received == response) 65 | verify(urlConnection).connectTimeout = 10 * 1000 66 | verify(urlConnection).readTimeout = 60 * 1000 67 | verify(urlConnection).setRequestProperty("x-api-key", apiKey) 68 | verify(urlConnection).disconnect() 69 | } 70 | 71 | @Test 72 | fun fetchQualifiedSegments_400() { 73 | `when`(urlConnection.responseCode).thenReturn(400) 74 | 75 | segmentClient.fetchQualifiedSegments(apiKey, apiEndpoint, payload) 76 | 77 | verify(client).execute(captor.capture(), eq(0), eq(0)) 78 | val received = captor.value.execute() 79 | 80 | assertNull(received) 81 | verify(logger).error("Unexpected response from ODP segment endpoint, status: 400") 82 | verify(urlConnection).disconnect() 83 | } 84 | 85 | @Test 86 | fun fetchQualifiedSegments_500() { 87 | `when`(urlConnection.responseCode).thenReturn(500) 88 | 89 | segmentClient.fetchQualifiedSegments(apiKey, apiEndpoint, payload) 90 | 91 | verify(client).execute(captor.capture(), eq(0), eq(0)) 92 | val received = captor.value.execute() 93 | 94 | assertNull(received) 95 | verify(logger).error("Unexpected response from ODP segment endpoint, status: 500") 96 | verify(urlConnection).disconnect() 97 | } 98 | 99 | @Test 100 | fun fetchQualifiedSegments_connectionFailed() { 101 | `when`(urlConnection.responseCode).thenReturn(200) 102 | 103 | apiEndpoint = "invalid-url" 104 | segmentClient.fetchQualifiedSegments(apiKey, apiEndpoint, payload) 105 | 106 | verify(client).execute(captor.capture(), eq(0), eq(0)) 107 | val received = captor.value.execute() 108 | 109 | assertNull(received) 110 | verify(logger).error(contains("Error making ODP segment request"), any()) 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /odp/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | <?xml version="1.0" encoding="utf-8"?> 2 | <!-- 3 | // 4 | // Copyright 2022, Optimizely, Inc. and contributors 5 | // 6 | // Licensed under the Apache License, Version 2.0 (the "License"); 7 | // you may not use this file except in compliance with the License. 8 | // You may obtain a copy of the License at 9 | // 10 | // https://www.apache.org/licenses/LICENSE-2.0 11 | // 12 | // Unless required by applicable law or agreed to in writing, software 13 | // distributed under the License is distributed on an "AS IS" BASIS, 14 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | // See the License for the specific language governing permissions and 16 | // limitations under the License. 17 | // 18 | --> 19 | <manifest xmlns:android="http://schemas.android.com/apk/res/android" 20 | package="com.optimizely.ab.android.odp"> 21 | </manifest> 22 | -------------------------------------------------------------------------------- /odp/src/main/java/com/optimizely/ab/android/odp/DefaultODPApiManager.kt: -------------------------------------------------------------------------------- 1 | // Copyright 2022-2023, Optimizely, Inc. and contributors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package com.optimizely.ab.android.odp 16 | 17 | import android.content.Context 18 | import androidx.annotation.VisibleForTesting 19 | import com.optimizely.ab.android.shared.Client 20 | import com.optimizely.ab.android.shared.OptlyStorage 21 | import com.optimizely.ab.android.shared.WorkerScheduler 22 | import com.optimizely.ab.odp.ODPApiManager 23 | import org.slf4j.LoggerFactory 24 | import java.util.concurrent.TimeUnit 25 | 26 | @VisibleForTesting(otherwise = VisibleForTesting.PROTECTED) 27 | open class DefaultODPApiManager(private val context: Context, timeoutForSegmentFetch: Int, timeoutForEventDispatch: Int) : ODPApiManager { 28 | 29 | init { 30 | ODPSegmentClient.CONNECTION_TIMEOUT = TimeUnit.SECONDS.toMillis(timeoutForSegmentFetch.toLong()).toInt() 31 | ODPEventClient.CONNECTION_TIMEOUT = TimeUnit.SECONDS.toMillis(timeoutForEventDispatch.toLong()).toInt() 32 | } 33 | 34 | @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) 35 | var segmentClient = ODPSegmentClient( 36 | Client(OptlyStorage(context), LoggerFactory.getLogger(Client::class.java)), 37 | LoggerFactory.getLogger(ODPSegmentClient::class.java) 38 | ) 39 | private val logger = LoggerFactory.getLogger(DefaultODPApiManager::class.java) 40 | 41 | override fun fetchQualifiedSegments( 42 | apiKey: String, 43 | apiEndpoint: String, 44 | userKey: String, 45 | userValue: String, 46 | segmentsToCheck: Set<String>, 47 | ): List<String>? { 48 | return segmentClient.fetchQualifiedSegments( 49 | apiKey, 50 | apiEndpoint, 51 | getSegmentsQueryPayload(userKey, userValue, segmentsToCheck) 52 | ) 53 | } 54 | 55 | override fun sendEvents(apiKey: String, apiEndpoint: String, payload: String): Int { 56 | val inputData = ODPEventWorker.getData(apiKey, apiEndpoint, payload) 57 | WorkerScheduler.startService( 58 | context, 59 | ODPEventWorker.workerId, 60 | ODPEventWorker::class.java, 61 | inputData, 62 | 0 63 | ) 64 | 65 | logger.debug("Sent an ODP event ({}) to the event handler service: {}", payload, apiEndpoint) 66 | 67 | // return success to the caller - retries on failure will be taken care in WorkManager. 68 | return 200 69 | } 70 | 71 | // Helpers 72 | 73 | private fun getSegmentsQueryPayload(userKey: String, userValue: String, segmentsToCheck: Set<String>): String { 74 | val query = java.lang.String.format( 75 | "query(\$userId: String, \$audiences: [String]) {customer(%s: \$userId) {audiences(subset: \$audiences) {edges {node {name state}}}}}", 76 | userKey 77 | ) 78 | 79 | val variables = java.lang.String.format( 80 | "{\"userId\": \"%s\", \"audiences\": [%s]}", 81 | userValue, 82 | getSegmentsStringForRequest(segmentsToCheck) 83 | ) 84 | 85 | return String.format("{\"query\": \"%s\", \"variables\": %s}", query, variables) 86 | } 87 | 88 | private fun getSegmentsStringForRequest(segmentsList: Set<String?>): String { 89 | val segmentsString = StringBuilder() 90 | val segmentsListIterator = segmentsList.iterator() 91 | for (i in segmentsList.indices) { 92 | if (i > 0) { 93 | segmentsString.append(", ") 94 | } 95 | segmentsString.append("\"").append(segmentsListIterator.next()).append("\"") 96 | } 97 | return segmentsString.toString() 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /odp/src/main/java/com/optimizely/ab/android/odp/ODPEventWorker.kt: -------------------------------------------------------------------------------- 1 | // Copyright 2022, Optimizely, Inc. and contributors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | package com.optimizely.ab.android.odp 15 | 16 | import android.content.Context 17 | import androidx.annotation.VisibleForTesting 18 | import androidx.work.Data 19 | import androidx.work.Worker 20 | import androidx.work.WorkerParameters 21 | import com.optimizely.ab.android.shared.Client 22 | import com.optimizely.ab.android.shared.EventHandlerUtils 23 | import com.optimizely.ab.android.shared.OptlyStorage 24 | import org.slf4j.LoggerFactory 25 | 26 | class ODPEventWorker(context: Context, workerParams: WorkerParameters) : 27 | Worker(context, workerParams) { 28 | 29 | @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) 30 | public var eventClient = ODPEventClient( 31 | Client(OptlyStorage(context), LoggerFactory.getLogger(Client::class.java)), 32 | LoggerFactory.getLogger(ODPEventClient::class.java) 33 | ) 34 | 35 | override fun doWork(): Result { 36 | val data = inputData 37 | val apiEndpoint = getApiEndpointFromInputData(data) 38 | val apiKey = getApiKeyFromInputData(data) 39 | val body = getEventBodyFromInputData(data) 40 | 41 | if (apiEndpoint == null || apiKey == null || body == null) { 42 | // malformed request (not retried). 43 | return Result.failure() 44 | } 45 | 46 | val status = eventClient.dispatch(apiKey, apiEndpoint, body) ?: false 47 | 48 | // NOTE: retries on failure is taken care in ODPEventClient 49 | return if (status) Result.success() else Result.failure() 50 | } 51 | 52 | @VisibleForTesting 53 | fun getEventBodyFromInputData(inputData: Data): String? { 54 | // check non-compressed data first 55 | val body = inputData.getString(KEY_ODP_EVENT_BODY) 56 | if (body != null) return body 57 | 58 | // check if data compressed 59 | val compressed = inputData.getString(KEY_ODP_EVENT_BODY_COMPRESSED) ?: return null 60 | return try { 61 | EventHandlerUtils.decompress(compressed) 62 | } catch (e: Exception) { 63 | null 64 | } 65 | } 66 | 67 | @VisibleForTesting 68 | fun getApiEndpointFromInputData(data: Data): String? { 69 | return data.getString(KEY_ODP_EVENT_APIENDPOINT) 70 | } 71 | 72 | @VisibleForTesting 73 | fun getApiKeyFromInputData(data: Data): String? { 74 | return data.getString(KEY_ODP_EVENT_APIKEY) 75 | } 76 | 77 | companion object { 78 | const val workerId = "ODPEventWorker" 79 | 80 | const val KEY_ODP_EVENT_APIENDPOINT = "apiEndpoint" 81 | const val KEY_ODP_EVENT_APIKEY = "apiKey" 82 | const val KEY_ODP_EVENT_BODY = "body" 83 | const val KEY_ODP_EVENT_BODY_COMPRESSED = "bodyCompressed" 84 | 85 | fun getData(apiKey: String, apiEndpoint: String, payload: String): Data { 86 | // androidx.work.Data throws IllegalStateException if total data length is more than MAX_DATA_BYTES 87 | // compress larger body and decompress it before dispatching. The compress rate is very high because of repeated data (20KB -> 1KB, 45KB -> 1.5KB). 88 | val maxSizeBeforeCompress = Data.MAX_DATA_BYTES - 1000 // 1000 reserved for other meta data 89 | 90 | val builder = Data.Builder() 91 | .putString(KEY_ODP_EVENT_APIENDPOINT, apiEndpoint) 92 | .putString(KEY_ODP_EVENT_APIKEY, apiKey) 93 | 94 | if (payload.length < maxSizeBeforeCompress) { 95 | builder.putString(KEY_ODP_EVENT_BODY, payload) 96 | } else { 97 | val compressed: String = EventHandlerUtils.compress(payload) 98 | builder.putString(KEY_ODP_EVENT_BODY_COMPRESSED, compressed) 99 | } 100 | 101 | return builder.build() 102 | } 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /odp/src/main/java/com/optimizely/ab/android/odp/VuidManager.kt: -------------------------------------------------------------------------------- 1 | // Copyright 2022, Optimizely, Inc. and contributors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package com.optimizely.ab.android.odp 16 | 17 | import android.content.Context 18 | import androidx.annotation.VisibleForTesting 19 | import com.optimizely.ab.android.shared.OptlyStorage 20 | import java.util.UUID 21 | 22 | class VuidManager private constructor() { 23 | var vuid = "" 24 | private val keyForVuid = "vuid" // stored in the private "optly" storage 25 | 26 | companion object { 27 | @Volatile 28 | private var INSTANCE: VuidManager? = null 29 | 30 | @Synchronized 31 | fun getInstance(): VuidManager = INSTANCE ?: VuidManager().also { INSTANCE = it } 32 | 33 | fun isVuid(visitorId: String): Boolean { 34 | return visitorId.startsWith("vuid_", ignoreCase = true) 35 | } 36 | 37 | @VisibleForTesting 38 | fun removeSharedForTesting() { 39 | INSTANCE = null 40 | } 41 | } 42 | 43 | @Synchronized 44 | fun configure(enableVuid: Boolean, context: Context) { 45 | if (!enableVuid) { 46 | removeVuid(context) 47 | this.vuid = "" 48 | } else { 49 | this.vuid = load(context) 50 | } 51 | } 52 | 53 | @VisibleForTesting 54 | fun makeVuid(): String { 55 | val maxLength = 32 // required by ODP server 56 | 57 | // make sure UUIDv4 is used (not UUIDv1 or UUIDv6) since the trailing 5 chars will be truncated. See TDD for details. 58 | val vuidFull = "vuid_" + UUID.randomUUID().toString().replace("-", "").lowercase() 59 | return if (vuidFull.length <= maxLength) vuidFull else vuidFull.substring(0, maxLength) 60 | } 61 | 62 | @VisibleForTesting 63 | fun load(context: Context): String { 64 | val storage = OptlyStorage(context) 65 | val oldVuid = storage.getString(keyForVuid, null) 66 | if (oldVuid != null) { 67 | return oldVuid 68 | } 69 | 70 | val vuid = makeVuid() 71 | save(context, vuid) 72 | return vuid 73 | } 74 | 75 | @VisibleForTesting 76 | fun save(context: Context, vuid: String) { 77 | val storage = OptlyStorage(context) 78 | storage.saveString(keyForVuid, vuid) 79 | } 80 | 81 | fun removeVuid(context: Context) { 82 | val storage = OptlyStorage(context) 83 | storage.remove(keyForVuid) 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /proguard-rules.txt: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in /Users/jdeffibaugh/Library/Android/sdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | 19 | ## Below are the suggested rules from the developer documentation: 20 | ## https://developers.optimizely.com/x/solutions/sdks/reference/index.html?language=android&platform=mobile#installation 21 | 22 | # Optimizely 23 | -keep class com.optimizely.ab.config.** 24 | -keepclassmembers class com.optimizely.ab.config.** { 25 | *; 26 | } 27 | # Keep Payload classes that get sent to the ODP server 28 | -keep class com.optimizely.ab.odp.ODPEvent { *; } 29 | # ODP event uses this. R8 complains about it. 30 | -dontwarn java.beans.Transient 31 | 32 | # Keep Payload classes that get sent to Optimizely's backend 33 | -keep class com.optimizely.ab.event.internal.payload.** { *; } 34 | 35 | # Keep these for logging purposes 36 | -keep class com.optimizely.ab.bucketing.DecisionService { *; } 37 | -keep class com.optimizely.ab.Optimizely { *; } 38 | 39 | # Safely ignore warnings about other libraries since we are using Gson 40 | -dontwarn com.fasterxml.jackson.** 41 | -dontwarn org.json.** 42 | 43 | # Annotations 44 | -dontwarn javax.annotation.** 45 | 46 | # Findbugs 47 | -dontwarn edu.umd.cs.findbugs.annotations.SuppressFBWarnings 48 | 49 | # slf4j 50 | -dontwarn org.slf4j.** 51 | -keep class org.slf4j.** {*;} 52 | 53 | # Android Logger 54 | -keep class com.noveogroup.android.log.** { *; } 55 | 56 | -optimizations !class/unboxing/enum 57 | 58 | -dontwarn com.google.gson.** 59 | -dontwarn com.optimizely.ab.config.parser.** 60 | 61 | # Gson (https://github.com/google/gson/blob/master/examples/android-proguard-example/proguard.cfg) 62 | ##---------------Begin: proguard configuration for Gson ---------- 63 | # Gson uses generic type information stored in a class file when working with fields. Proguard 64 | # removes such information by default, so configure it to keep all of it. 65 | -keepattributes Signature 66 | 67 | # For using GSON @Expose annotation 68 | -keepattributes *Annotation* 69 | 70 | # Gson specific classes 71 | -dontwarn sun.misc.** 72 | 73 | # Prevent proguard from stripping interface information from TypeAdapter, TypeAdapterFactory, 74 | # JsonSerializer, JsonDeserializer instances (so they can be used in @JsonAdapter) 75 | -keep class * extends com.google.gson.TypeAdapter 76 | -keep class * implements com.google.gson.TypeAdapterFactory 77 | -keep class * implements com.google.gson.JsonSerializer 78 | -keep class * implements com.google.gson.JsonDeserializer 79 | 80 | # Prevent R8 from leaving Data object members always null 81 | -keepclassmembers,allowobfuscation class * { 82 | @com.google.gson.annotations.SerializedName <fields>; 83 | } 84 | 85 | # Retain generic signatures of TypeToken and its subclasses with R8 version 3.0 and higher. 86 | -keep,allowobfuscation,allowshrinking class com.google.gson.reflect.TypeToken 87 | -keep,allowobfuscation,allowshrinking class * extends com.google.gson.reflect.TypeToken 88 | 89 | # Retain the name used by the Java SDK to determine whether Gson is usable as a config parser. 90 | -keepnames class com.google.gson.Gson 91 | ##---------------End: proguard configuration for Gson ---------- 92 | 93 | -------------------------------------------------------------------------------- /scripts/android-wait-for-emulator.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Originally written by Ralf Kistner <ralf@embarkmobile.com>, but placed in the public domain 4 | 5 | set +e 6 | 7 | bootanim="" 8 | failcounter=0 9 | #timeout_in_sec=360 # 6 minutes 10 | timeout_in_sec=900 # 15 minutes 11 | echo "Waiting for emulator to start" 12 | until [[ "$bootanim" =~ "stopped" ]]; do 13 | bootanim=`adb -e shell getprop init.svc.bootanim 2>&1 &` 14 | #echo bootanim=\`$bootanim\` 15 | if [[ "$bootanim" =~ "device not found" || "$bootanim" =~ "device offline" 16 | || "$bootanim" =~ "running" || "$bootanim" =~ "error: no emulators found" ]]; then 17 | let "failcounter += 5" 18 | echo -n "." 19 | if [[ $failcounter -gt timeout_in_sec ]]; then 20 | echo "Timeout ($timeout_in_sec seconds) reached; failed to start emulator" 21 | exit 1 22 | fi 23 | else 24 | if [[ ! "$bootanim" =~ "stopped" ]]; then 25 | echo "unexpected behavior from (adb -e shell getprop init.svc.bootanim): $bootanim" 26 | exit 1 27 | fi 28 | fi 29 | sleep 5 30 | done 31 | 32 | echo "Emulator is ready (took $failcounter seconds)" 33 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':event-handler', ':android-sdk', ':shared', ':test-app', ':user-profile', ':datafile-handler', ':odp' 2 | -------------------------------------------------------------------------------- /shared/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /shared/build.gradle: -------------------------------------------------------------------------------- 1 | /**************************************************************************** 2 | * Copyright 2016, Optimizely, Inc. and contributors * 3 | * * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); * 5 | * you may not use this file except in compliance with the License. * 6 | * You may obtain a copy of the License at * 7 | * * 8 | * http://www.apache.org/licenses/LICENSE-2.0 * 9 | * * 10 | * Unless required by applicable law or agreed to in writing, software * 11 | * distributed under the License is distributed on an "AS IS" BASIS, * 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * 13 | * See the License for the specific language governing permissions and * 14 | * limitations under the License. * 15 | ***************************************************************************/ 16 | 17 | apply plugin: 'com.android.library' 18 | apply plugin: 'kotlin-android' 19 | 20 | android { 21 | compileSdkVersion compile_sdk_version 22 | buildToolsVersion build_tools_version 23 | 24 | defaultConfig { 25 | minSdkVersion min_sdk_version 26 | targetSdkVersion target_sdk_version 27 | versionCode 1 28 | versionName "1.0" 29 | multiDexEnabled true 30 | testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner' 31 | } 32 | testOptions { 33 | unitTests.returnDefaultValues = true 34 | } 35 | buildTypes { 36 | release { 37 | minifyEnabled false 38 | } 39 | debug { 40 | testCoverageEnabled true 41 | } 42 | } 43 | compileOptions { 44 | sourceCompatibility JavaVersion.VERSION_17 45 | targetCompatibility JavaVersion.VERSION_17 46 | } 47 | buildToolsVersion build_tools_version 48 | } 49 | 50 | dependencies { 51 | api ("com.optimizely.ab:core-api:$java_core_ver") { 52 | exclude group: 'com.google.code.findbugs' 53 | } 54 | 55 | implementation "androidx.annotation:annotation:$annotations_ver" 56 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" 57 | implementation "androidx.work:work-runtime:$work_runtime" 58 | // the default json parser for java-sdk core-api (app can exclude it to replace with other parsers) 59 | implementation "com.google.code.gson:gson:$gson_ver" 60 | // Base64 61 | implementation "commons-codec:commons-codec:1.15" 62 | 63 | compileOnly "com.noveogroup.android:android-logger:$android_logger_ver" 64 | 65 | testImplementation "junit:junit:$junit_ver" 66 | testImplementation "org.mockito:mockito-core:$mockito_ver" 67 | testImplementation "com.noveogroup.android:android-logger:$android_logger_ver" 68 | 69 | androidTestImplementation "androidx.work:work-testing:$work_runtime" 70 | androidTestImplementation "androidx.test.ext:junit:$androidx_test_junit" 71 | androidTestImplementation "androidx.test.espresso:espresso-core:$espresso_ver" 72 | // Set this dependency to use JUnit 4 rules 73 | androidTestImplementation "androidx.test:rules:$androidx_test_rules" 74 | androidTestImplementation "androidx.test:core:$androidx_test_core" 75 | androidTestImplementation "androidx.test:core-ktx:$androidx_test_core" 76 | 77 | androidTestImplementation "org.mockito:mockito-core:$mockito_ver" 78 | androidTestImplementation "org.mockito:mockito-android:$mockito_ver" 79 | androidTestImplementation "com.noveogroup.android:android-logger:$android_logger_ver" 80 | androidTestImplementation "com.fasterxml.jackson.core:jackson-databind:$jacksonversion" 81 | } 82 | -------------------------------------------------------------------------------- /shared/src/androidTest/java/com/optimizely/ab/android/shared/CacheTest.java: -------------------------------------------------------------------------------- 1 | /**************************************************************************** 2 | * Copyright 2016-2017, Optimizely, Inc. and contributors * 3 | * * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); * 5 | * you may not use this file except in compliance with the License. * 6 | * You may obtain a copy of the License at * 7 | * * 8 | * http://www.apache.org/licenses/LICENSE-2.0 * 9 | * * 10 | * Unless required by applicable law or agreed to in writing, software * 11 | * distributed under the License is distributed on an "AS IS" BASIS, * 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * 13 | * See the License for the specific language governing permissions and * 14 | * limitations under the License. * 15 | ***************************************************************************/ 16 | 17 | package com.optimizely.ab.android.shared; 18 | 19 | import android.content.Context; 20 | import androidx.test.platform.app.InstrumentationRegistry; 21 | import androidx.test.ext.junit.runners.AndroidJUnit4; 22 | 23 | import org.junit.Before; 24 | import org.junit.Test; 25 | import org.junit.runner.RunWith; 26 | import org.mockito.Mockito; 27 | import org.slf4j.Logger; 28 | 29 | import java.io.FileNotFoundException; 30 | import java.io.FileOutputStream; 31 | import java.io.IOException; 32 | 33 | import static junit.framework.Assert.assertEquals; 34 | import static junit.framework.Assert.assertFalse; 35 | import static junit.framework.Assert.assertNull; 36 | import static junit.framework.Assert.assertTrue; 37 | import static org.mockito.Mockito.mock; 38 | import static org.mockito.Mockito.verify; 39 | import static org.mockito.Mockito.when; 40 | 41 | /** 42 | * Tests for {@link CacheTest} 43 | */ 44 | @RunWith(AndroidJUnit4.class) 45 | public class CacheTest { 46 | 47 | private static final String FILENAME = "foo.txt"; 48 | 49 | private Cache cache; 50 | private Logger logger; 51 | 52 | @Before 53 | public void setup() { 54 | Context context = InstrumentationRegistry.getInstrumentation().getTargetContext(); 55 | logger = mock(Logger.class); 56 | cache = new Cache(context, logger); 57 | } 58 | 59 | @Test 60 | public void testSaveExistsLoadAndDelete() throws IOException { 61 | assertTrue(cache.save(FILENAME, "bar")); 62 | assertTrue(cache.exists(FILENAME)); 63 | String data = cache.load(FILENAME); 64 | assertEquals("bar", data); 65 | assertTrue(cache.delete(FILENAME)); 66 | } 67 | 68 | @Test 69 | public void testDeleteFail() { 70 | assertFalse(cache.delete(FILENAME)); 71 | } 72 | 73 | @Test 74 | public void testExistsFalse() { 75 | assertFalse(cache.exists(FILENAME)); 76 | } 77 | 78 | @Test 79 | public void testLoadFileNotFoundExceptionReturnsNull() throws FileNotFoundException { 80 | Context context = mock(Context.class); 81 | Cache cache = new Cache(context, logger); 82 | when(context.openFileInput(FILENAME)).thenThrow(new FileNotFoundException()); 83 | assertNull(cache.load(FILENAME)); 84 | verify(logger).warn("Unable to load file {}.", FILENAME); 85 | } 86 | 87 | @Test 88 | public void testSaveFail() throws IOException { 89 | Context context = mock(Context.class); 90 | Cache cache = new Cache(context, logger); 91 | FileOutputStream fileOutputStream = mock(FileOutputStream.class); 92 | 93 | String data = "{}"; 94 | Mockito.doThrow(new IOException()).when(fileOutputStream).write(data.getBytes()); 95 | when(context.openFileOutput(FILENAME, Context.MODE_PRIVATE)).thenReturn(fileOutputStream); 96 | assertFalse(cache.save(FILENAME, data)); 97 | verify(logger).error("Error saving file {}.", FILENAME); 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /shared/src/androidTest/java/com/optimizely/ab/android/shared/OptlyStorageTest.java: -------------------------------------------------------------------------------- 1 | /**************************************************************************** 2 | * Copyright 2016, Optimizely, Inc. and contributors * 3 | * * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); * 5 | * you may not use this file except in compliance with the License. * 6 | * You may obtain a copy of the License at * 7 | * * 8 | * http://www.apache.org/licenses/LICENSE-2.0 * 9 | * * 10 | * Unless required by applicable law or agreed to in writing, software * 11 | * distributed under the License is distributed on an "AS IS" BASIS, * 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * 13 | * See the License for the specific language governing permissions and * 14 | * limitations under the License. * 15 | ***************************************************************************/ 16 | 17 | package com.optimizely.ab.android.shared; 18 | 19 | import android.content.Context; 20 | import androidx.test.platform.app.InstrumentationRegistry; 21 | import androidx.test.ext.junit.runners.AndroidJUnit4; 22 | 23 | import org.junit.Before; 24 | import org.junit.Test; 25 | import org.junit.runner.RunWith; 26 | 27 | import static junit.framework.Assert.assertEquals; 28 | 29 | 30 | /** 31 | * Tests for {@link OptlyStorage} 32 | */ 33 | @RunWith(AndroidJUnit4.class) 34 | public class OptlyStorageTest { 35 | 36 | private OptlyStorage optlyStorage; 37 | 38 | @Before 39 | public void setup() { 40 | Context context = InstrumentationRegistry.getInstrumentation().getTargetContext(); 41 | optlyStorage = new OptlyStorage(context); 42 | } 43 | 44 | @Test 45 | public void saveAndGetLong() { 46 | optlyStorage.saveLong("foo", 1); 47 | assertEquals(1L, optlyStorage.getLong("foo", 0L)); 48 | } 49 | } 50 | 51 | -------------------------------------------------------------------------------- /shared/src/androidTest/java/com/optimizely/ab/android/shared/TestWorker.java: -------------------------------------------------------------------------------- 1 | package com.optimizely.ab.android.shared; 2 | 3 | import android.content.Context; 4 | 5 | import androidx.annotation.NonNull; 6 | import androidx.work.Data; 7 | import androidx.work.Worker; 8 | import androidx.work.WorkerParameters; 9 | 10 | public class TestWorker extends Worker { 11 | public static final String workerId = "TestWorker"; 12 | 13 | // static counter to trace periodic work execution 14 | public static int count = 0; 15 | 16 | public TestWorker(@NonNull Context context, @NonNull WorkerParameters workerParams) { 17 | super(context, workerParams); 18 | } 19 | 20 | @Override 21 | public Result doWork() { 22 | count++; 23 | 24 | Data input = getInputData(); 25 | if (input.size() == 0) { 26 | return Result.failure(); 27 | } else { 28 | return Result.success(input); 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /shared/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | <?xml version="1.0" encoding="utf-8"?> 2 | <!-- 3 | /**************************************************************************** 4 | * Copyright 2017, Optimizely, Inc. and contributors * 5 | * * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); * 7 | * you may not use this file except in compliance with the License. * 8 | * You may obtain a copy of the License at * 9 | * * 10 | * http://www.apache.org/licenses/LICENSE-2.0 * 11 | * * 12 | * Unless required by applicable law or agreed to in writing, software * 13 | * distributed under the License is distributed on an "AS IS" BASIS, * 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * 15 | * See the License for the specific language governing permissions and * 16 | * limitations under the License. * 17 | ***************************************************************************/ 18 | --> 19 | 20 | <manifest xmlns:android="http://schemas.android.com/apk/res/android" 21 | package="com.optimizely.ab.android.shared"> 22 | <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/> 23 | </manifest> 24 | -------------------------------------------------------------------------------- /shared/src/main/java/com/optimizely/ab/android/shared/CachedCounter.java: -------------------------------------------------------------------------------- 1 | /**************************************************************************** 2 | * Copyright 2017, Optimizely, Inc. and contributors * 3 | * * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); * 5 | * you may not use this file except in compliance with the License. * 6 | * You may obtain a copy of the License at * 7 | * * 8 | * http://www.apache.org/licenses/LICENSE-2.0 * 9 | * * 10 | * Unless required by applicable law or agreed to in writing, software * 11 | * distributed under the License is distributed on an "AS IS" BASIS, * 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * 13 | * See the License for the specific language governing permissions and * 14 | * limitations under the License. * 15 | ***************************************************************************/ 16 | 17 | package com.optimizely.ab.android.shared; 18 | 19 | import android.content.Context; 20 | 21 | import org.slf4j.Logger; 22 | import org.slf4j.LoggerFactory; 23 | 24 | public class CachedCounter implements CountingIdlingResourceInterface { 25 | private final Cache cache; 26 | private final Logger logger = LoggerFactory.getLogger("CachedCounter"); 27 | private final Context context; 28 | private final static String fileName = "OptlyCachedCounterKey"; 29 | public CachedCounter(Context context) { 30 | this.context = context; 31 | this.cache = new Cache(context, logger); 32 | if (!cache.exists(fileName)) { 33 | cache.save(fileName, "0"); 34 | } 35 | } 36 | 37 | synchronized public void increment() { 38 | String value = cache.load(fileName); 39 | Integer val = Integer.valueOf(value); 40 | val += 1; 41 | cache.save(fileName, val.toString()); 42 | } 43 | 44 | synchronized public void decrement() { 45 | String value = cache.load(fileName); 46 | Integer val = Integer.valueOf(value); 47 | if (val == 0) { 48 | return; 49 | } 50 | val -= 1; 51 | cache.save(fileName, val.toString()); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /shared/src/main/java/com/optimizely/ab/android/shared/CountingIdlingResourceInterface.java: -------------------------------------------------------------------------------- 1 | /**************************************************************************** 2 | * Copyright 2017, Optimizely, Inc. and contributors * 3 | * * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); * 5 | * you may not use this file except in compliance with the License. * 6 | * You may obtain a copy of the License at * 7 | * * 8 | * http://www.apache.org/licenses/LICENSE-2.0 * 9 | * * 10 | * Unless required by applicable law or agreed to in writing, software * 11 | * distributed under the License is distributed on an "AS IS" BASIS, * 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * 13 | * See the License for the specific language governing permissions and * 14 | * limitations under the License. * 15 | ***************************************************************************/ 16 | 17 | package com.optimizely.ab.android.shared; 18 | 19 | /** 20 | * Interface for dealing with Espresso idling counter 21 | */ 22 | public interface CountingIdlingResourceInterface { 23 | public void increment(); 24 | public void decrement(); 25 | } 26 | -------------------------------------------------------------------------------- /shared/src/main/java/com/optimizely/ab/android/shared/CountingIdlingResourceManager.java: -------------------------------------------------------------------------------- 1 | /**************************************************************************** 2 | * Copyright 2016, Optimizely, Inc. and contributors * 3 | * * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); * 5 | * you may not use this file except in compliance with the License. * 6 | * You may obtain a copy of the License at * 7 | * * 8 | * http://www.apache.org/licenses/LICENSE-2.0 * 9 | * * 10 | * Unless required by applicable law or agreed to in writing, software * 11 | * distributed under the License is distributed on an "AS IS" BASIS, * 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * 13 | * See the License for the specific language governing permissions and * 14 | * limitations under the License. * 15 | ***************************************************************************/ 16 | 17 | package com.optimizely.ab.android.shared; 18 | 19 | import android.content.Context; 20 | import androidx.annotation.NonNull; 21 | import androidx.annotation.Nullable; 22 | import android.util.Pair; 23 | 24 | import java.util.LinkedList; 25 | import java.util.List; 26 | 27 | /** 28 | * Manages an Espresso like CountingIdlingResource 29 | */ 30 | public class CountingIdlingResourceManager { 31 | 32 | @Nullable private static CountingIdlingResourceInterface countingIdlingResource; 33 | @NonNull private static List<Pair<String, String>> eventList = new LinkedList<>(); 34 | 35 | @Nullable public static CountingIdlingResourceInterface getIdlingResource(Context context) { 36 | if (countingIdlingResource == null) { 37 | countingIdlingResource = new CachedCounter(context); 38 | } 39 | return countingIdlingResource; 40 | } 41 | 42 | public static void setIdlingResource(@NonNull CountingIdlingResourceInterface countingIdlingResource) { 43 | CountingIdlingResourceManager.countingIdlingResource = countingIdlingResource; 44 | } 45 | 46 | public static void increment() { 47 | if (countingIdlingResource != null) { 48 | countingIdlingResource.increment(); 49 | } 50 | } 51 | 52 | public static void decrement() { 53 | if (countingIdlingResource != null) { 54 | countingIdlingResource.decrement(); 55 | } 56 | } 57 | 58 | public static void recordEvent(Pair<String, String> event) { 59 | if (countingIdlingResource != null) { 60 | eventList.add(event); 61 | } 62 | } 63 | 64 | public static void clearEvents() { 65 | eventList.clear(); 66 | } 67 | 68 | public static List<Pair<String, String>> getEvents() { 69 | return eventList; 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /shared/src/main/java/com/optimizely/ab/android/shared/EventHandlerUtils.java: -------------------------------------------------------------------------------- 1 | /**************************************************************************** 2 | * Copyright 2022, Optimizely, Inc. and contributors * 3 | * * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); * 5 | * you may not use this file except in compliance with the License. * 6 | * You may obtain a copy of the License at * 7 | * * 8 | * http://www.apache.org/licenses/LICENSE-2.0 * 9 | * * 10 | * Unless required by applicable law or agreed to in writing, software * 11 | * distributed under the License is distributed on an "AS IS" BASIS, * 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * 13 | * See the License for the specific language governing permissions and * 14 | * limitations under the License. * 15 | ***************************************************************************/ 16 | 17 | package com.optimizely.ab.android.shared; 18 | 19 | import androidx.annotation.NonNull; 20 | 21 | import org.apache.commons.codec.binary.Base64; 22 | 23 | import java.io.ByteArrayOutputStream; 24 | import java.io.IOException; 25 | import java.util.zip.Deflater; 26 | import java.util.zip.Inflater; 27 | 28 | public class EventHandlerUtils { 29 | 30 | private static final int BUFFER_SIZE = 32*1024; 31 | 32 | public static String compress(@NonNull String decompressed) throws IOException { 33 | byte[] data = decompressed.getBytes(); 34 | 35 | final Deflater deflater = new Deflater(); 36 | deflater.setInput(data); 37 | 38 | try (final ByteArrayOutputStream outputStream = new ByteArrayOutputStream(data.length)) { 39 | deflater.finish(); 40 | final byte[] buffer = new byte[BUFFER_SIZE]; 41 | while (!deflater.finished()) { 42 | final int count = deflater.deflate(buffer); 43 | outputStream.write(buffer, 0, count); 44 | } 45 | 46 | byte[] bytes = outputStream.toByteArray(); 47 | // encoded to Base64 (instead of byte[] since WorkManager.Data size is unexpectedly expanded with byte[]). 48 | return encodeToBase64(bytes); 49 | } 50 | finally { 51 | deflater.end(); 52 | } 53 | } 54 | 55 | public static String decompress(@NonNull String base64) throws Exception { 56 | byte[] data = decodeFromBase64(base64); 57 | 58 | final Inflater inflater = new Inflater(); 59 | inflater.setInput(data); 60 | 61 | try (final ByteArrayOutputStream outputStream = new ByteArrayOutputStream(data.length)) { 62 | byte[] buffer = new byte[BUFFER_SIZE]; 63 | while (!inflater.finished()) { 64 | final int count = inflater.inflate(buffer); 65 | outputStream.write(buffer, 0, count); 66 | } 67 | 68 | return outputStream.toString(); 69 | } 70 | finally { 71 | inflater.end(); 72 | } 73 | } 74 | 75 | static String encodeToBase64(byte[] bytes) { 76 | // - org.apache.commons.Base64 is used (instead of android.util.Base64) for unit testing 77 | // - encodeBase64() for backward compatibility (instead of encodeBase64String()). 78 | String base64 = ""; 79 | if (bytes != null) { 80 | byte[] encoded = Base64.encodeBase64(bytes); 81 | base64 = new String(encoded); 82 | } 83 | return base64; 84 | } 85 | 86 | static byte[] decodeFromBase64(String base64) { 87 | return Base64.decodeBase64(base64.getBytes()); 88 | } 89 | 90 | } 91 | -------------------------------------------------------------------------------- /shared/src/main/java/com/optimizely/ab/android/shared/OptlyStorage.java: -------------------------------------------------------------------------------- 1 | /**************************************************************************** 2 | * Copyright 2016, Optimizely, Inc. and contributors * 3 | * * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); * 5 | * you may not use this file except in compliance with the License. * 6 | * You may obtain a copy of the License at * 7 | * * 8 | * http://www.apache.org/licenses/LICENSE-2.0 * 9 | * * 10 | * Unless required by applicable law or agreed to in writing, software * 11 | * distributed under the License is distributed on an "AS IS" BASIS, * 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * 13 | * See the License for the specific language governing permissions and * 14 | * limitations under the License. * 15 | ***************************************************************************/ 16 | 17 | package com.optimizely.ab.android.shared; 18 | 19 | import android.content.Context; 20 | import android.content.SharedPreferences; 21 | import androidx.annotation.NonNull; 22 | 23 | /** 24 | * Wrapper for {@link SharedPreferences} 25 | * @hide 26 | */ 27 | public class OptlyStorage { 28 | 29 | private static final String PREFS_NAME = "optly"; 30 | 31 | @NonNull private Context context; 32 | 33 | /** 34 | * Creates a new instance of {@link OptlyStorage} 35 | * 36 | * @param context any instance of {@link Context} 37 | * @hide 38 | */ 39 | public OptlyStorage(@NonNull Context context) { 40 | this.context = context; 41 | } 42 | 43 | /** 44 | * Save a long value 45 | * 46 | * @param key a String key 47 | * @param value a long value 48 | * @hide 49 | */ 50 | public void saveLong(String key, long value) { 51 | getWritablePrefs().putLong(key, value ).apply(); 52 | } 53 | 54 | /** 55 | * Get a long value 56 | * @param key a String key 57 | * @param defaultValue the value to return if the key isn't stored 58 | * @return the long value 59 | * @hide 60 | */ 61 | public long getLong(String key, long defaultValue) { 62 | return getReadablePrefs().getLong(key, defaultValue); 63 | } 64 | 65 | /** 66 | * Save a string value 67 | * 68 | * @param key a String key 69 | * @param value a string value 70 | * @hide 71 | */ 72 | public void saveString(String key, String value) { 73 | getWritablePrefs().putString(key, value).apply(); 74 | } 75 | 76 | /** 77 | * Get a string value 78 | * @param key a String key 79 | * @param defaultValue the value to return if the key isn't stored 80 | * @return the string value 81 | * @hide 82 | */ 83 | public String getString(String key, String defaultValue) { 84 | return getReadablePrefs().getString(key, defaultValue); 85 | } 86 | 87 | private SharedPreferences.Editor getWritablePrefs() { 88 | return context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE).edit(); 89 | } 90 | 91 | private SharedPreferences getReadablePrefs() { 92 | return context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE); 93 | } 94 | 95 | public void remove(String key) { 96 | getWritablePrefs().remove(key).apply(); 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /shared/src/main/java/com/optimizely/ab/android/shared/TLSSocketFactory.java: -------------------------------------------------------------------------------- 1 | /**************************************************************************** 2 | * Copyright 2021, Optimizely, Inc. and contributors * 3 | * * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); * 5 | * you may not use this file except in compliance with the License. * 6 | * You may obtain a copy of the License at * 7 | * * 8 | * http://www.apache.org/licenses/LICENSE-2.0 * 9 | * * 10 | * Unless required by applicable law or agreed to in writing, software * 11 | * distributed under the License is distributed on an "AS IS" BASIS, * 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * 13 | * See the License for the specific language governing permissions and * 14 | * limitations under the License. * 15 | ***************************************************************************/ 16 | 17 | package com.optimizely.ab.android.shared; 18 | 19 | import java.io.IOException; 20 | import java.net.InetAddress; 21 | import java.net.Socket; 22 | import java.net.UnknownHostException; 23 | import java.security.KeyManagementException; 24 | import java.security.NoSuchAlgorithmException; 25 | 26 | import javax.net.ssl.SSLContext; 27 | import javax.net.ssl.SSLSocket; 28 | import javax.net.ssl.SSLSocketFactory; 29 | 30 | // Android uses TLS1.2 as a default for API 21+ 31 | // This factory is to set TLS1.2 as default for older devices (datafile download and event uploading) 32 | 33 | public class TLSSocketFactory extends SSLSocketFactory { 34 | private SSLSocketFactory sslSocketFactory; 35 | 36 | public TLSSocketFactory() throws KeyManagementException, NoSuchAlgorithmException { 37 | SSLContext context = SSLContext.getInstance("TLS"); 38 | context.init(null, null, null); 39 | sslSocketFactory = context.getSocketFactory(); 40 | } 41 | 42 | @Override 43 | public String[] getDefaultCipherSuites() { 44 | return sslSocketFactory.getDefaultCipherSuites(); 45 | } 46 | 47 | @Override 48 | public String[] getSupportedCipherSuites() { 49 | return sslSocketFactory.getSupportedCipherSuites(); 50 | } 51 | 52 | @Override 53 | public Socket createSocket() throws IOException { 54 | return enableTLSOnSocket(sslSocketFactory.createSocket()); 55 | } 56 | 57 | @Override 58 | public Socket createSocket(Socket s, String host, int port, boolean autoClose) throws IOException { 59 | return enableTLSOnSocket(sslSocketFactory.createSocket(s, host, port, autoClose)); 60 | } 61 | 62 | @Override 63 | public Socket createSocket(String host, int port) throws IOException, UnknownHostException { 64 | return enableTLSOnSocket(sslSocketFactory.createSocket(host, port)); 65 | } 66 | 67 | @Override 68 | public Socket createSocket(String host, int port, InetAddress localHost, int localPort) throws IOException, UnknownHostException { 69 | return enableTLSOnSocket(sslSocketFactory.createSocket(host, port, localHost, localPort)); 70 | } 71 | 72 | @Override 73 | public Socket createSocket(InetAddress host, int port) throws IOException { 74 | return enableTLSOnSocket(sslSocketFactory.createSocket(host, port)); 75 | } 76 | 77 | @Override 78 | public Socket createSocket(InetAddress address, int port, InetAddress localAddress, int localPort) throws IOException { 79 | return enableTLSOnSocket(sslSocketFactory.createSocket(address, port, localAddress, localPort)); 80 | } 81 | 82 | private Socket enableTLSOnSocket(Socket socket) { 83 | if(socket instanceof SSLSocket) { 84 | ((SSLSocket)socket).setEnabledProtocols(new String[] {"TLSv1.2"}); 85 | } 86 | return socket; 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /shared/src/test/java/com/optimizely/ab/android/shared/EventHandlerUtilsTest.java: -------------------------------------------------------------------------------- 1 | package com.optimizely.ab.android.shared; 2 | 3 | import static org.junit.Assert.assertEquals; 4 | 5 | import org.junit.Test; 6 | 7 | public class EventHandlerUtilsTest { 8 | 9 | @Test 10 | public void compressAndDecompress() throws Exception { 11 | String str = makeRandomString(1000); 12 | 13 | String compressed = EventHandlerUtils.compress(str); 14 | assert(compressed.length() < (str.length() * 0.5)); 15 | 16 | String decompressed = EventHandlerUtils.decompress(compressed); 17 | assertEquals(str, decompressed); 18 | } 19 | 20 | @Test(timeout=30000) 21 | public void measureCompressionDelay() throws Exception { 22 | int maxEventSize = 100000; // 100KB (~100 attributes) 23 | int count = 3000; 24 | 25 | String body = EventHandlerUtilsTest.makeRandomString(maxEventSize); 26 | 27 | long start = System.currentTimeMillis(); 28 | for (int i = 0; i < count; i++) { 29 | EventHandlerUtils.compress(body); 30 | } 31 | long end = System.currentTimeMillis(); 32 | float delayCompress = ((float)(end - start))/count; 33 | System.out.println("Compression Delay: " + String.valueOf(delayCompress) + " millisecs"); 34 | assert(delayCompress < 10); // less than 1ms for 100KB (set 10ms upperbound) 35 | 36 | start = System.currentTimeMillis(); 37 | for (int i = 0; i < count; i++) { 38 | String compressed = EventHandlerUtils.compress(body); 39 | EventHandlerUtils.decompress(compressed); 40 | } 41 | end = System.currentTimeMillis(); 42 | float delayDecompress = ((float)(end - start))/count - delayCompress; 43 | System.out.println("Decompression Delay: " + String.valueOf(delayDecompress) + " millisecs"); 44 | assert(delayDecompress < 10); // less than 1ms for 100KB (set 10ms upperbound) 45 | } 46 | 47 | public static String makeRandomString(int maxSize) { 48 | StringBuilder builder = new StringBuilder(); 49 | 50 | // for high compression rate, shift repeated string window. 51 | int window = 100; 52 | int shift = 3; // adjust (1...10) this for compression rate. smaller for higher rates. 53 | 54 | int start = 0; 55 | int end = start + window; 56 | int i = 0; 57 | 58 | int size = 0; 59 | while (true) { 60 | String str = String.valueOf(i); 61 | size += str.length(); 62 | if (size > maxSize) { 63 | break; 64 | } 65 | builder.append(str); 66 | 67 | i++; 68 | if (i > end) { 69 | start = start + shift; 70 | end = start + window; 71 | i = start; 72 | } 73 | } 74 | 75 | return builder.toString(); 76 | } 77 | 78 | } 79 | -------------------------------------------------------------------------------- /test-app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | src/main/res/values/git_ignored_strings.xml 3 | -------------------------------------------------------------------------------- /test-app/README.md: -------------------------------------------------------------------------------- 1 | # Optimizely X Android SDK Demo App 2 | 3 | This module houses the demo app used to demonstrate how to get started with the Android SDK. The app 4 | is also used to run integration tests with the Android Espresso framework. 5 | 6 | ![Demo App Flow](./images/demo-app-flow.png) 7 | 8 | ## Experiments Run 9 | 10 | We run the following experiment: 11 | - Background change in second activity, which is loaded after the splash screen. 12 | 13 | ## How it works 14 | 15 | The SDK is implemented in the following way: 16 | - The splash screen initializes the Optimizely manager asynchronously. This starts the datafile 17 | fetch. 18 | - Once the datafile is fetched and the Optimizely manager is started, we grab the `optimizelyClient` 19 | from the manager and use it to activate the experiment named `background_experiment`. This buckets the user and sends 20 | an impression event. 21 | - We then use the bucketed variation to determine which activity to show. `VariationAActivity` for 22 | `variation_a`, `VariationBActivity` for `variation_b`, or `ActivationErrorActivity` for the control. 23 | - Each of the variation activities includes a `Test Conversion` button. 24 | - Clicking on that button will call `optimizelyClient.track()` and send a conversion event for the 25 | event named `sample_conversion`. 26 | - Then the application will navigate to the conversion page to confirm that a conversion event has 27 | been sent. 28 | 29 | ## Running the test app 30 | 31 | Run `./gradlew test-app:connectedAndroidTest` to run the Espresso tests. 32 | 33 | -------------------------------------------------------------------------------- /test-app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | apply plugin: 'kotlin-android' 3 | 4 | android { 5 | compileSdkVersion compile_sdk_version 6 | buildToolsVersion build_tools_version 7 | 8 | 9 | defaultConfig { 10 | minSdkVersion 21 11 | targetSdkVersion target_sdk_version 12 | versionCode 1 13 | versionName "1.0.2" 14 | testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner' 15 | } 16 | testOptions { 17 | unitTests.returnDefaultValues = true 18 | } 19 | buildTypes { 20 | debug { 21 | // enable proguard for debug mode (keep both of these to detect issues while testing) 22 | minifyEnabled true 23 | debuggable false 24 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'test-app-proguard-rules.pro' 25 | } 26 | release { 27 | minifyEnabled true 28 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'test-app-proguard-rules.pro' 29 | signingConfig signingConfigs.debug 30 | } 31 | } 32 | compileOptions { 33 | sourceCompatibility JavaVersion.VERSION_17 34 | targetCompatibility JavaVersion.VERSION_17 35 | } 36 | packagingOptions { 37 | resources { 38 | excludes += ['META-INF/LICENSE.txt', 'META-INF/NOTICE.txt', 'META-INF/LICENSE', 'META-INF/NOTICE'] 39 | } 40 | } 41 | 42 | lint { 43 | abortOnError false 44 | } 45 | } 46 | 47 | dependencies { 48 | // Includes the Optimizely Feature Experimentation, Optimizely Full Stack (legacy), and Optimizely Rollouts Java SDK, event handler, and user profile 49 | //implementation "com.optimizely.ab:android-sdk:1.0.0" 50 | implementation (project(':android-sdk')) { 51 | exclude group: 'com.google.code.gson', module:'gson' 52 | } 53 | 54 | // EXAMPLE - replace gson with jackson-databind json parser 55 | implementation "com.fasterxml.jackson.core:jackson-databind:$jacksonversion" 56 | 57 | // SLF4J logger 58 | // https://github.com/noveogroup/android-logger (resources/android-logger.properties) 59 | implementation "com.noveogroup.android:android-logger:$android_logger_ver" 60 | // https://mvnrepository.com/artifact/org.slf4j/slf4j-android 61 | //implementation "org.slf4j:slf4j-android:1.7.25" 62 | 63 | implementation "androidx.appcompat:appcompat:1.2.0" 64 | implementation "com.google.android.material:material:1.2.1" 65 | 66 | // required by API-16 67 | implementation "com.google.code.gson:gson:$gson_ver" 68 | implementation 'com.google.code.findbugs:annotations:3.0.0' 69 | 70 | testImplementation "junit:junit:$junit_ver" 71 | testImplementation "org.mockito:mockito-core:$mockito_ver" 72 | testImplementation "com.noveogroup.android:android-logger:$android_logger_ver" 73 | // testImplementation 'com.optimizely.ab:android-sdk:1.0.0' 74 | testImplementation project(':android-sdk') 75 | 76 | androidTestImplementation "androidx.work:work-testing:$work_runtime" 77 | androidTestImplementation "androidx.test.ext:junit:$androidx_test_junit" 78 | androidTestImplementation "androidx.test.espresso:espresso-core:$espresso_ver" 79 | // Set this dependency to use JUnit 4 rules 80 | androidTestImplementation "androidx.test:rules:$androidx_test_rules" 81 | androidTestImplementation "androidx.test:core:$androidx_test_core" 82 | androidTestImplementation "androidx.test:core-ktx:$androidx_test_core" 83 | 84 | androidTestImplementation "org.mockito:mockito-core:$mockito_ver" 85 | androidTestImplementation "com.crittercism.dexmaker:dexmaker:$dexmaker_ver" 86 | androidTestImplementation "com.crittercism.dexmaker:dexmaker-dx:$dexmaker_ver" 87 | androidTestImplementation "com.crittercism.dexmaker:dexmaker-mockito:$dexmaker_ver" 88 | // androidTestImplementation 'com.optimizely.ab:android-sdk:1.0.0' 89 | androidTestImplementation project(':android-sdk') 90 | androidTestImplementation project(path: ':shared') 91 | androidTestImplementation "com.fasterxml.jackson.core:jackson-databind:$jacksonversion" 92 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" 93 | } 94 | 95 | -------------------------------------------------------------------------------- /test-app/images/demo-app-flow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/optimizely/android-sdk/841a5d236d822e53803e4064665663b221057b64/test-app/images/demo-app-flow.png -------------------------------------------------------------------------------- /test-app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | <?xml version="1.0" encoding="utf-8"?> 2 | <!-- 3 | /**************************************************************************** 4 | * Copyright 2016-2020, Optimizely, Inc. and contributors * 5 | * * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); * 7 | * you may not use this file except in compliance with the License. * 8 | * You may obtain a copy of the License at * 9 | * * 10 | * http://www.apache.org/licenses/LICENSE-2.0 * 11 | * * 12 | * Unless required by applicable law or agreed to in writing, software * 13 | * distributed under the License is distributed on an "AS IS" BASIS, * 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * 15 | * See the License for the specific language governing permissions and * 16 | * limitations under the License. * 17 | ***************************************************************************/ 18 | --> 19 | 20 | <manifest xmlns:android="http://schemas.android.com/apk/res/android" 21 | xmlns:tools="http://schemas.android.com/tools" 22 | package="com.optimizely.ab.android.test_app"> 23 | 24 | <uses-permission android:name="android.permission.DISABLE_KEYGUARD" /> 25 | <uses-permission android:name="android.permission.WAKE_LOCK" /> 26 | <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/> 27 | <uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/> 28 | 29 | <application 30 | android:name=".MyApplication" 31 | android:allowBackup="false" 32 | android:icon="@mipmap/ic_launcher" 33 | android:label="@string/app_name" 34 | android:supportsRtl="true" 35 | android:theme="@style/AppTheme" 36 | tools:ignore="GoogleAppIndexingWarning"> 37 | <service 38 | android:name=".NotificationService" 39 | android:exported="false" /> 40 | 41 | <activity 42 | android:name=".SplashScreenActivity" 43 | android:exported="true" 44 | android:theme="@style/SplashTheme"> 45 | <intent-filter> 46 | <action android:name="android.intent.action.MAIN" /> 47 | 48 | <category android:name="android.intent.category.LAUNCHER" /> 49 | </intent-filter> 50 | </activity> 51 | <activity android:name=".VariationAActivity" /> 52 | <activity android:name=".EventConfirmationActivity" /> 53 | <activity android:name=".ActivationErrorActivity" /> 54 | <activity android:name=".VariationBActivity" /> 55 | <activity android:name=".CouponActivity" /> 56 | </application> 57 | 58 | </manifest> 59 | -------------------------------------------------------------------------------- /test-app/src/main/java/com/optimizely/ab/android/test_app/ActivationErrorActivity.kt: -------------------------------------------------------------------------------- 1 | /**************************************************************************** 2 | * Copyright 2017-2020, Optimizely, Inc. and contributors * 3 | * * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); * 5 | * you may not use this file except in compliance with the License. * 6 | * You may obtain a copy of the License at * 7 | * * 8 | * http://www.apache.org/licenses/LICENSE-2.0 * 9 | * * 10 | * Unless required by applicable law or agreed to in writing, software * 11 | * distributed under the License is distributed on an "AS IS" BASIS, * 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * 13 | * See the License for the specific language governing permissions and * 14 | * limitations under the License. * 15 | */ 16 | package com.optimizely.ab.android.test_app 17 | 18 | import android.os.Bundle 19 | import androidx.appcompat.app.AppCompatActivity 20 | import android.view.View 21 | import android.widget.Button 22 | 23 | class ActivationErrorActivity : AppCompatActivity() { 24 | override fun onCreate(savedInstanceState: Bundle?) { 25 | super.onCreate(savedInstanceState) 26 | setContentView(R.layout.activity_activation_error) 27 | val btnConversion = findViewById<View>(R.id.btn_conversion_error_back) as Button 28 | btnConversion.setOnClickListener { onBackPressed() } 29 | } 30 | } -------------------------------------------------------------------------------- /test-app/src/main/java/com/optimizely/ab/android/test_app/BaseActivity.kt: -------------------------------------------------------------------------------- 1 | /**************************************************************************** 2 | * Copyright 2020, Optimizely, Inc. and contributors * 3 | * * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); * 5 | * you may not use this file except in compliance with the License. * 6 | * You may obtain a copy of the License at * 7 | * * 8 | * http://www.apache.org/licenses/LICENSE-2.0 * 9 | * * 10 | * Unless required by applicable law or agreed to in writing, software * 11 | * distributed under the License is distributed on an "AS IS" BASIS, * 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * 13 | * See the License for the specific language governing permissions and * 14 | * limitations under the License. * 15 | */ 16 | package com.optimizely.ab.android.test_app 17 | 18 | import android.content.Context 19 | import android.content.Intent 20 | import android.os.Bundle 21 | import android.os.PersistableBundle 22 | import androidx.appcompat.app.AppCompatActivity 23 | import android.util.AttributeSet 24 | import android.view.View 25 | 26 | open class BaseActivity : AppCompatActivity() { 27 | private var showCoupon = false 28 | open fun setShowCoupon(v: Boolean?) { 29 | v?.let { 30 | showCoupon = it 31 | } 32 | } 33 | open fun getShowCoupon(): Boolean { 34 | return showCoupon 35 | } 36 | 37 | override fun onCreate(savedInstanceState: Bundle?) { 38 | super.onCreate(savedInstanceState) 39 | showCoupon = intent.getBooleanExtra("show_coupon", false) 40 | 41 | val application = applicationContext as MyApplication 42 | application?.currentActivity = this 43 | if (showCoupon) { 44 | intent = Intent(this, CouponActivity::class.java) 45 | startActivity(intent) 46 | } 47 | } 48 | } -------------------------------------------------------------------------------- /test-app/src/main/java/com/optimizely/ab/android/test_app/ConversionFragment.kt: -------------------------------------------------------------------------------- 1 | /**************************************************************************** 2 | * Copyright 2017-2020, Optimizely, Inc. and contributors * 3 | * * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); * 5 | * you may not use this file except in compliance with the License. * 6 | * You may obtain a copy of the License at * 7 | * * 8 | * http://www.apache.org/licenses/LICENSE-2.0 * 9 | * * 10 | * Unless required by applicable law or agreed to in writing, software * 11 | * distributed under the License is distributed on an "AS IS" BASIS, * 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * 13 | * See the License for the specific language governing permissions and * 14 | * limitations under the License. * 15 | */ 16 | package com.optimizely.ab.android.test_app 17 | 18 | import android.app.Fragment 19 | import android.content.Intent 20 | import android.os.Bundle 21 | import android.view.LayoutInflater 22 | import android.view.View 23 | import android.view.ViewGroup 24 | import android.widget.Button 25 | import com.optimizely.ab.android.shared.CountingIdlingResourceManager 26 | 27 | class ConversionFragment : Fragment() { 28 | private var conversionButton: Button? = null 29 | override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { 30 | return inflater.inflate(R.layout.fragment_conversion, container, false) 31 | } 32 | 33 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { 34 | conversionButton = view.findViewById<View>(R.id.btn_variation_conversion) as Button 35 | val myApplication = activity.application as MyApplication 36 | val optimizelyManager = myApplication.optimizelyManager 37 | conversionButton?.setOnClickListener { 38 | val userId = myApplication.anonUserId 39 | 40 | // This tracks a conversion event for the event named `sample_conversion` 41 | val optimizely = optimizelyManager?.optimizely 42 | optimizely?.track("sample_conversion", userId) 43 | optimizely?.track("my_conversion", userId) 44 | 45 | // Utility method for verifying event dispatches in our automated tests 46 | CountingIdlingResourceManager.increment() // increment for conversion event 47 | val intent = Intent(myApplication.baseContext, EventConfirmationActivity::class.java) 48 | startActivity(intent) 49 | } 50 | } 51 | } -------------------------------------------------------------------------------- /test-app/src/main/java/com/optimizely/ab/android/test_app/CouponActivity.kt: -------------------------------------------------------------------------------- 1 | /**************************************************************************** 2 | * Copyright 2020, Optimizely, Inc. and contributors * 3 | * * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); * 5 | * you may not use this file except in compliance with the License. * 6 | * You may obtain a copy of the License at * 7 | * * 8 | * http://www.apache.org/licenses/LICENSE-2.0 * 9 | * * 10 | * Unless required by applicable law or agreed to in writing, software * 11 | * distributed under the License is distributed on an "AS IS" BASIS, * 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * 13 | * See the License for the specific language governing permissions and * 14 | * limitations under the License. * 15 | */ 16 | package com.optimizely.ab.android.test_app 17 | 18 | import android.content.Context 19 | import android.content.res.ColorStateList 20 | import android.graphics.Color 21 | import android.os.Bundle 22 | import androidx.appcompat.app.AppCompatActivity 23 | import android.util.AttributeSet 24 | import android.view.View 25 | import android.widget.TextView 26 | 27 | class CouponActivity : BaseActivity() { 28 | override fun setShowCoupon(v: Boolean?) { 29 | super.setShowCoupon(v) 30 | if (!getShowCoupon()) { 31 | finish() 32 | } 33 | } 34 | override fun onCreate(savedInstanceState: Bundle?) { 35 | super.onCreate(savedInstanceState) 36 | setContentView(R.layout.coupon) 37 | 38 | val button = findViewById<View>(R.id.redeem) 39 | 40 | val txt = findViewById<View>(R.id.message) as TextView 41 | 42 | val app = application as MyApplication 43 | 44 | val userId = app.anonUserId ?: "something" 45 | val attributes = app.attributes ?: HashMap<String, Any>() 46 | 47 | val message = app.optimizelyManager?.optimizely?.getFeatureVariableString("show_coupon", "message", userId, attributes) 48 | val discount = app.optimizelyManager?.optimizely?.getFeatureVariableInteger("show_coupon", "discount", userId, attributes) 49 | 50 | txt.text = "$message $discount%" 51 | 52 | when (app.optimizelyManager?.optimizely?.getFeatureVariableString("show_coupon", "text_color", userId, attributes)) { 53 | "yellow" -> txt.setTextColor(Color.YELLOW) 54 | } 55 | 56 | button.setOnClickListener() { 57 | finish() 58 | } 59 | } 60 | } -------------------------------------------------------------------------------- /test-app/src/main/java/com/optimizely/ab/android/test_app/EventConfirmationActivity.kt: -------------------------------------------------------------------------------- 1 | /**************************************************************************** 2 | * Copyright 2017-2020, Optimizely, Inc. and contributors * 3 | * * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); * 5 | * you may not use this file except in compliance with the License. * 6 | * You may obtain a copy of the License at * 7 | * * 8 | * http://www.apache.org/licenses/LICENSE-2.0 * 9 | * * 10 | * Unless required by applicable law or agreed to in writing, software * 11 | * distributed under the License is distributed on an "AS IS" BASIS, * 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * 13 | * See the License for the specific language governing permissions and * 14 | * limitations under the License. * 15 | */ 16 | package com.optimizely.ab.android.test_app 17 | 18 | import android.os.Bundle 19 | import androidx.appcompat.app.AppCompatActivity 20 | 21 | class EventConfirmationActivity : AppCompatActivity() { 22 | override fun onCreate(savedInstanceState: Bundle?) { 23 | super.onCreate(savedInstanceState) 24 | setContentView(R.layout.activity_event_confirmation) 25 | } 26 | } -------------------------------------------------------------------------------- /test-app/src/main/java/com/optimizely/ab/android/test_app/MyApplication.kt: -------------------------------------------------------------------------------- 1 | /**************************************************************************** 2 | * Copyright 2016-2021, 2023 Optimizely, Inc. and contributors * 3 | * * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); * 5 | * you may not use this file except in compliance with the License. * 6 | * You may obtain a copy of the License at * 7 | * * 8 | * http://www.apache.org/licenses/LICENSE-2.0 * 9 | * * 10 | * Unless required by applicable law or agreed to in writing, software * 11 | * distributed under the License is distributed on an "AS IS" BASIS, * 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * 13 | * See the License for the specific language governing permissions and * 14 | * limitations under the License. * 15 | */ 16 | package com.optimizely.ab.android.test_app 17 | 18 | import android.app.Application 19 | import android.content.Context 20 | import android.os.Build 21 | import com.optimizely.ab.android.sdk.OptimizelyManager 22 | import java.util.* 23 | import java.util.concurrent.TimeUnit 24 | 25 | class MyApplication : Application() { 26 | var optimizelyManager: OptimizelyManager? = null 27 | private set 28 | 29 | val attributes: Map<String, *> 30 | get() { 31 | val attributes: MutableMap<String, Any> = HashMap() 32 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { 33 | attributes["locale"] = resources.configuration.locales[0].toString() 34 | } else { 35 | attributes["locale"] = resources.configuration.locale.toString() 36 | } 37 | val abbr = getAbbriviation(getLocality(location)) 38 | 39 | attributes["location"] = abbr 40 | attributes["semanticVersioning"] = "2.1.0" 41 | 42 | return attributes 43 | } 44 | 45 | val anonUserId: String 46 | get() { 47 | // this is a convenience method that creates and persists an anonymous user id, 48 | // which we need to pass into the activate and track calls 49 | val sharedPreferences = getSharedPreferences("user", Context.MODE_PRIVATE) 50 | var id = sharedPreferences.getString("userId", null) 51 | if (id == null) { 52 | id = UUID.randomUUID().toString() 53 | 54 | // comment this out to get a brand new user id every time this function is called. 55 | // useful for incrementing results page count for QA purposes 56 | sharedPreferences.edit().putString("userId", id).apply() 57 | } 58 | return id 59 | } 60 | 61 | private val userAge: Int 62 | private get() = 65 63 | 64 | override fun onCreate() { 65 | super.onCreate() 66 | 67 | // This app is built against a real Optimizely project with real experiments set. Automated 68 | // espresso tests are run against this project id. Changing it will make the Optimizely 69 | // must match the project id of the compiled in Optimizely data file in rest/raw/data_file.json. 70 | optimizelyManager = OptimizelyManager.builder() 71 | .withEventDispatchInterval(60L, TimeUnit.SECONDS) 72 | .withDatafileDownloadInterval(15, TimeUnit.MINUTES) 73 | .withSDKKey("FCnSegiEkRry9rhVMroit4") 74 | .build(applicationContext) 75 | } 76 | 77 | var currentActivity: BaseActivity? = null 78 | 79 | private fun getAbbriviation(locality: Any): String { 80 | return "NY" 81 | } 82 | 83 | private fun getLocality(location: Any?): Any { 84 | return "NY" 85 | } 86 | 87 | private val location: Any? 88 | private get() = null 89 | 90 | } 91 | -------------------------------------------------------------------------------- /test-app/src/main/java/com/optimizely/ab/android/test_app/NotificationService.kt: -------------------------------------------------------------------------------- 1 | /**************************************************************************** 2 | * Copyright 2016-2017, Optimizely, Inc. and contributors * 3 | * * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); * 5 | * you may not use this file except in compliance with the License. * 6 | * You may obtain a copy of the License at * 7 | * * 8 | * http://www.apache.org/licenses/LICENSE-2.0 * 9 | * * 10 | * Unless required by applicable law or agreed to in writing, software * 11 | * distributed under the License is distributed on an "AS IS" BASIS, * 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * 13 | * See the License for the specific language governing permissions and * 14 | * limitations under the License. * 15 | */ 16 | package com.optimizely.ab.android.test_app 17 | 18 | import android.app.IntentService 19 | import android.content.Intent 20 | import androidx.core.app.NotificationCompat 21 | import com.optimizely.ab.android.shared.CountingIdlingResourceManager 22 | 23 | class NotificationService : IntentService("NotificationService") { 24 | override fun onHandleIntent(intent: Intent?) { 25 | if (intent != null) { 26 | showNotification() 27 | 28 | // Get Optimizely from the Intent that started this Service 29 | val myApplication = application as MyApplication 30 | val optimizelyManager = myApplication.optimizelyManager 31 | val optimizely = optimizelyManager!!.optimizely 32 | CountingIdlingResourceManager.increment() 33 | optimizely.track("experiment_2", myApplication.anonUserId) 34 | } 35 | } 36 | 37 | private fun showNotification() { 38 | val mBuilder = NotificationCompat.Builder(this) 39 | .setSmallIcon(android.R.drawable.ic_notification_clear_all) 40 | .setContentTitle(getString(R.string.notification_service_notification_title)) 41 | .setContentText(getString(R.string.notification_service_notification_text)) 42 | // @TODO(mng): Add notification back when we add more complex scenario 43 | // Creates an explicit intent for an Activity in your app 44 | // Intent resultIntent = new Intent(this, MainActivity.class); 45 | // 46 | // // The stack builder object will contain an artificial back stack for the 47 | // // started Activity. 48 | // // This ensures that navigating backward from the Activity leads out of 49 | // // your application to the Home screen. 50 | // TaskStackBuilder stackBuilder = TaskStackBuilder.create(this); 51 | // // Adds the back stack for the Intent (but not the Intent itself) 52 | // stackBuilder.addParentStack(SecondaryActivity.class); 53 | // // Adds the Intent that starts the Activity to the top of the stack 54 | // stackBuilder.addNextIntent(resultIntent); 55 | // PendingIntent resultPendingIntent = 56 | // stackBuilder.getPendingIntent( 57 | // 0, 58 | // PendingIntent.FLAG_UPDATE_CURRENT 59 | // ); 60 | // mBuilder.setContentIntent(resultPendingIntent); 61 | // NotificationManager mNotificationManager = 62 | // (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); 63 | // // mId allows you to update the notification later on. 64 | // mNotificationManager.notify(0, mBuilder.build()); 65 | } 66 | } -------------------------------------------------------------------------------- /test-app/src/main/java/com/optimizely/ab/android/test_app/VariationAActivity.kt: -------------------------------------------------------------------------------- 1 | /**************************************************************************** 2 | * Copyright 2017-2020, Optimizely, Inc. and contributors * 3 | * * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); * 5 | * you may not use this file except in compliance with the License. * 6 | * You may obtain a copy of the License at * 7 | * * 8 | * http://www.apache.org/licenses/LICENSE-2.0 * 9 | * * 10 | * Unless required by applicable law or agreed to in writing, software * 11 | * distributed under the License is distributed on an "AS IS" BASIS, * 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * 13 | * See the License for the specific language governing permissions and * 14 | * limitations under the License. * 15 | */ 16 | package com.optimizely.ab.android.test_app 17 | 18 | import android.content.Context 19 | import android.content.Intent 20 | import android.os.Bundle 21 | import android.util.AttributeSet 22 | import android.view.View 23 | 24 | class VariationAActivity : BaseActivity() { 25 | override fun onCreate(savedInstanceState: Bundle?) { 26 | super.onCreate(savedInstanceState) 27 | setContentView(R.layout.activity_variation_a) 28 | } 29 | 30 | override fun setShowCoupon(v: Boolean?) { 31 | super.setShowCoupon(v) 32 | if (getShowCoupon()) { 33 | val app = applicationContext as MyApplication 34 | val intent = Intent(this, CouponActivity::class.java) 35 | startActivity(intent) 36 | } 37 | } 38 | 39 | } -------------------------------------------------------------------------------- /test-app/src/main/java/com/optimizely/ab/android/test_app/VariationBActivity.kt: -------------------------------------------------------------------------------- 1 | /**************************************************************************** 2 | * Copyright 2017-2020, Optimizely, Inc. and contributors * 3 | * * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); * 5 | * you may not use this file except in compliance with the License. * 6 | * You may obtain a copy of the License at * 7 | * * 8 | * http://www.apache.org/licenses/LICENSE-2.0 * 9 | * * 10 | * Unless required by applicable law or agreed to in writing, software * 11 | * distributed under the License is distributed on an "AS IS" BASIS, * 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * 13 | * See the License for the specific language governing permissions and * 14 | * limitations under the License. * 15 | */ 16 | package com.optimizely.ab.android.test_app 17 | 18 | import android.content.Intent 19 | import android.os.Bundle 20 | 21 | class VariationBActivity : BaseActivity() { 22 | override fun onCreate(savedInstanceState: Bundle?) { 23 | super.onCreate(savedInstanceState) 24 | setContentView(R.layout.activity_variation_b) 25 | } 26 | 27 | override fun setShowCoupon(v: Boolean?) { 28 | super.setShowCoupon(v) 29 | if (getShowCoupon()) { 30 | val app = applicationContext as MyApplication 31 | val intent = Intent(this, CouponActivity::class.java) 32 | startActivity(intent) 33 | } 34 | } 35 | 36 | } -------------------------------------------------------------------------------- /test-app/src/main/res/drawable/ic_background_varia.xml: -------------------------------------------------------------------------------- 1 | <vector android:height="24dp" android:viewportHeight="667.0" 2 | android:viewportWidth="375.0" android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android"> 3 | <path android:fillColor="#003952" 4 | android:pathData="M905.7,347.1C883.4,570.9 679.4,752.3 450.1,752.3C220.9,752.3 53.1,570.9 75.4,347.1C97.7,123.2 301.7,-58.2 531,-58.2C661.4,-58.2 772,0.5 838.9,92.3L948.7,25.7L857.5,120.6L791.6,189.4L423.9,571.7C452.9,583 485.1,589.2 519.3,589.2C668.8,589.2 801.8,470.9 816.4,325C819.5,294 817,264.3 809.7,236.7L871.9,147.4C900.2,206.3 913,274.5 905.7,347.1L905.7,347.1ZM1028,-35.8L862.2,24.2C777.3,-72.4 652.5,-132.1 508,-132.1C219.6,-132.1 -38,106 -67.3,399.7C-96.6,693.4 113.5,931.5 401.9,931.5C690.3,931.5 947.8,693.4 977.1,399.7C987.1,299.7 969.3,206.1 930.1,126.1L1028,-35.8Z" 5 | android:strokeColor="#00000000" android:strokeWidth="1"/> 6 | <path android:fillColor="#003952" 7 | android:pathData="M572,60.7C422.5,60.7 289.5,179 275,325C271.4,361.5 275.5,396.3 286,427.9L758.5,141.2C714.2,91.6 648.4,60.7 572,60.7" 8 | android:strokeColor="#00000000" android:strokeWidth="1"/> 9 | </vector> 10 | -------------------------------------------------------------------------------- /test-app/src/main/res/drawable/ic_background_varib_marina.xml: -------------------------------------------------------------------------------- 1 | <vector android:height="24dp" android:viewportHeight="667.0" 2 | android:viewportWidth="375.0" android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android"> 3 | <path android:fillColor="#3FA29B" android:pathData="M375,373.9l-89,54c-10.6,-31.6 -14.7,-66.4 -11.1,-102.9c7.4,-73.9 45.1,-140.7 100,-188.7V0h-64.3C183.4,71 90.1,199.9 75.4,347.1c-13,130.1 38.2,245.8 128.4,319.9H375V373.9z"/> 4 | <path android:fillColor="#3FA29B" android:pathData="M150.3,0H0v182.1C37.9,112.1 89.4,50 150.3,0z"/> 5 | </vector> 6 | -------------------------------------------------------------------------------- /test-app/src/main/res/drawable/ic_optimizely_logo.xml: -------------------------------------------------------------------------------- 1 | <vector android:height="24dp" android:viewportHeight="178.0" 2 | android:viewportWidth="183.0" android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android"> 3 | <path android:fillColor="#003952" 4 | android:pathData="M-96,-244h375v667h-375z" 5 | android:strokeColor="#00000000" android:strokeWidth="1"/> 6 | <path android:fillColor="#FFFFFF" 7 | android:pathData="M162.62,80.18C158.9,117.48 124.91,147.72 86.69,147.72C48.48,147.72 20.51,117.48 24.24,80.18C27.96,42.87 61.95,12.63 100.17,12.63C121.91,12.63 140.33,22.42 151.49,37.72L169.78,26.62L154.58,42.43L143.6,53.9L82.32,117.61C87.16,119.5 92.51,120.54 98.22,120.54C123.14,120.54 145.3,100.82 147.73,76.5C148.24,71.34 147.83,66.38 146.62,61.78L156.98,46.9C161.7,56.72 163.83,68.08 162.62,80.18L162.62,80.18ZM182.99,16.36L155.36,26.37C141.21,10.27 120.41,0.31 96.33,0.31C48.26,0.31 5.34,40 0.45,88.95C-4.43,137.9 30.58,177.59 78.65,177.59C126.72,177.59 169.64,137.9 174.52,88.95C176.19,72.28 173.22,56.68 166.69,43.36L182.99,16.36Z" 8 | android:strokeColor="#00000000" android:strokeWidth="1"/> 9 | <path android:fillColor="#FFFFFF" 10 | android:pathData="M107.01,32.45C82.09,32.45 59.92,52.17 57.5,76.5C56.89,82.58 57.58,88.38 59.34,93.65L138.08,45.86C130.7,37.59 119.73,32.45 107.01,32.45" 11 | android:strokeColor="#00000000" android:strokeWidth="1"/> 12 | </vector> 13 | -------------------------------------------------------------------------------- /test-app/src/main/res/layout/activity_activation_error.xml: -------------------------------------------------------------------------------- 1 | <?xml version="1.0" encoding="utf-8"?> 2 | <!-- 3 | /**************************************************************************** 4 | * Copyright 2017, Optimizely, Inc. and contributors * 5 | * * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); * 7 | * you may not use this file except in compliance with the License. * 8 | * You may obtain a copy of the License at * 9 | * * 10 | * http://www.apache.org/licenses/LICENSE-2.0 * 11 | * * 12 | * Unless required by applicable law or agreed to in writing, software * 13 | * distributed under the License is distributed on an "AS IS" BASIS, * 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * 15 | * See the License for the specific language governing permissions and * 16 | * limitations under the License. * 17 | ***************************************************************************/ 18 | --> 19 | <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" 20 | xmlns:tools="http://schemas.android.com/tools" 21 | android:id="@+id/activity_activation_error" 22 | android:layout_width="match_parent" 23 | android:layout_height="match_parent" 24 | android:paddingBottom="@dimen/activity_vertical_margin" 25 | android:paddingLeft="@dimen/activity_horizontal_margin" 26 | android:paddingRight="@dimen/activity_horizontal_margin" 27 | android:paddingTop="@dimen/activity_vertical_margin" 28 | android:background="@drawable/ic_background_error" 29 | tools:context="com.optimizely.ab.android.test_app.ActivationErrorActivity"> 30 | <TextView 31 | android:id="@+id/tv_error_variation_text_1" 32 | android:layout_width="wrap_content" 33 | android:layout_height="wrap_content" 34 | android:layout_centerHorizontal="true" 35 | android:textColor="#FFFFFF" 36 | android:textSize="20dp" 37 | android:text="Oops! Let's try" 38 | android:layout_centerVertical="true" /> 39 | <TextView 40 | android:id="@+id/tv_error_variation_text_2" 41 | android:layout_below="@+id/tv_error_variation_text_1" 42 | android:layout_width="wrap_content" 43 | android:layout_height="wrap_content" 44 | android:layout_centerHorizontal="true" 45 | android:textColor="#FFFFFF" 46 | android:textSize="20dp" 47 | android:text="this again" 48 | android:layout_centerVertical="true"/> 49 | <Button 50 | android:layout_marginTop="40dp" 51 | android:id="@+id/btn_conversion_error_back" 52 | android:layout_width="wrap_content" 53 | android:layout_height="wrap_content" 54 | android:text="Back" 55 | android:textAllCaps="true" 56 | android:textSize="16dp" 57 | android:textColor="#FFFFFF" 58 | android:background="@color/colorAccent" 59 | android:padding="15dp" 60 | android:layout_below="@+id/tv_error_variation_text_2" 61 | android:layout_centerHorizontal="true"/> 62 | </RelativeLayout> 63 | -------------------------------------------------------------------------------- /test-app/src/main/res/layout/activity_event_confirmation.xml: -------------------------------------------------------------------------------- 1 | <?xml version="1.0" encoding="utf-8"?> 2 | <!-- 3 | /**************************************************************************** 4 | * Copyright 2017, Optimizely, Inc. and contributors * 5 | * * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); * 7 | * you may not use this file except in compliance with the License. * 8 | * You may obtain a copy of the License at * 9 | * * 10 | * http://www.apache.org/licenses/LICENSE-2.0 * 11 | * * 12 | * Unless required by applicable law or agreed to in writing, software * 13 | * distributed under the License is distributed on an "AS IS" BASIS, * 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * 15 | * See the License for the specific language governing permissions and * 16 | * limitations under the License. * 17 | ***************************************************************************/ 18 | --> 19 | <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" 20 | xmlns:tools="http://schemas.android.com/tools" 21 | android:id="@+id/activity_event_confirmation" 22 | android:layout_width="match_parent" 23 | android:layout_height="match_parent" 24 | android:paddingBottom="@dimen/activity_vertical_margin" 25 | android:paddingLeft="@dimen/activity_horizontal_margin" 26 | android:paddingRight="@dimen/activity_horizontal_margin" 27 | android:paddingTop="@dimen/activity_vertical_margin" 28 | android:background="@drawable/ic_background_confirmation" 29 | tools:context="com.optimizely.ab.android.test_app.EventConfirmationActivity"> 30 | <TextView 31 | android:id="@+id/tv_event_confirmation_text_1" 32 | android:layout_width="150dp" 33 | android:layout_height="wrap_content" 34 | android:layout_centerHorizontal="true" 35 | android:textAlignment="center" 36 | android:minLines="3" 37 | android:textColor="@color/colorPrimary" 38 | android:textSize="20dp" 39 | android:text="Conversion event sent to Optimizely." 40 | android:layout_centerVertical="true" /> 41 | </RelativeLayout> 42 | -------------------------------------------------------------------------------- /test-app/src/main/res/layout/activity_splash_screen.xml: -------------------------------------------------------------------------------- 1 | <?xml version="1.0" encoding="utf-8"?> 2 | <!-- 3 | /**************************************************************************** 4 | * Copyright 2017, Optimizely, Inc. and contributors * 5 | * * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); * 7 | * you may not use this file except in compliance with the License. * 8 | * You may obtain a copy of the License at * 9 | * * 10 | * http://www.apache.org/licenses/LICENSE-2.0 * 11 | * * 12 | * Unless required by applicable law or agreed to in writing, software * 13 | * distributed under the License is distributed on an "AS IS" BASIS, * 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * 15 | * See the License for the specific language governing permissions and * 16 | * limitations under the License. * 17 | ***************************************************************************/ 18 | --> 19 | <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" 20 | xmlns:tools="http://schemas.android.com/tools" 21 | android:id="@+id/activity_splash_screen" 22 | android:layout_width="match_parent" 23 | android:layout_height="match_parent" 24 | android:paddingBottom="@dimen/activity_vertical_margin" 25 | android:paddingLeft="@dimen/activity_horizontal_margin" 26 | android:paddingRight="@dimen/activity_horizontal_margin" 27 | android:paddingTop="@dimen/activity_vertical_margin" 28 | android:gravity="center_vertical" 29 | android:background="@color/colorPrimary" 30 | tools:context="com.optimizely.ab.android.test_app.SplashScreenActivity"> 31 | <ImageView 32 | android:layout_width="150dp" 33 | android:layout_height="150dp" 34 | android:adjustViewBounds="true" 35 | android:layout_centerVertical="true" 36 | android:layout_centerHorizontal="true" 37 | android:scaleType="fitCenter" 38 | android:src="@drawable/ic_optimizely_logo"/> 39 | </RelativeLayout> 40 | -------------------------------------------------------------------------------- /test-app/src/main/res/layout/activity_variation_a.xml: -------------------------------------------------------------------------------- 1 | <?xml version="1.0" encoding="utf-8"?> 2 | <!-- 3 | /**************************************************************************** 4 | * Copyright 2017, Optimizely, Inc. and contributors * 5 | * * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); * 7 | * you may not use this file except in compliance with the License. * 8 | * You may obtain a copy of the License at * 9 | * * 10 | * http://www.apache.org/licenses/LICENSE-2.0 * 11 | * * 12 | * Unless required by applicable law or agreed to in writing, software * 13 | * distributed under the License is distributed on an "AS IS" BASIS, * 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * 15 | * See the License for the specific language governing permissions and * 16 | * limitations under the License. * 17 | ***************************************************************************/ 18 | --> 19 | <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" 20 | xmlns:tools="http://schemas.android.com/tools" 21 | android:id="@+id/activity_variation_activity" 22 | android:background="@drawable/ic_background_varia" 23 | android:layout_width="match_parent" 24 | android:layout_height="match_parent" 25 | android:paddingBottom="@dimen/activity_vertical_margin" 26 | android:paddingLeft="@dimen/activity_horizontal_margin" 27 | android:paddingRight="@dimen/activity_horizontal_margin" 28 | android:paddingTop="@dimen/activity_vertical_margin" 29 | android:gravity="center_vertical" 30 | tools:context="com.optimizely.ab.android.test_app.VariationAActivity"> 31 | <TextView 32 | android:id="@+id/tv_variation_a_text_1" 33 | android:layout_width="wrap_content" 34 | android:layout_height="wrap_content" 35 | android:layout_centerHorizontal="true" 36 | android:textColor="@color/colorPrimary" 37 | android:textSize="150dp" 38 | android:text="A" 39 | android:layout_centerVertical="true" /> 40 | <TextView 41 | android:id="@+id/tv_variation_a_text_2" 42 | android:layout_below="@+id/tv_variation_a_text_1" 43 | android:layout_width="wrap_content" 44 | android:layout_height="wrap_content" 45 | android:layout_centerHorizontal="true" 46 | android:textColor="@color/colorPrimary" 47 | android:textSize="20dp" 48 | android:textAllCaps="true" 49 | android:text="variation" 50 | android:layout_marginTop="-20dp" 51 | android:layout_marginBottom="30dp" 52 | android:layout_centerVertical="true"/> 53 | 54 | <fragment 55 | android:id="@+id/frag" 56 | android:name="com.optimizely.ab.android.test_app.ConversionFragment" 57 | android:layout_width="wrap_content" 58 | android:layout_height="wrap_content" 59 | android:layout_below="@id/tv_variation_a_text_2" 60 | android:layout_centerHorizontal="true" 61 | tools:layout="@layout/fragment_conversion" /> 62 | </RelativeLayout> 63 | -------------------------------------------------------------------------------- /test-app/src/main/res/layout/activity_variation_b.xml: -------------------------------------------------------------------------------- 1 | <?xml version="1.0" encoding="utf-8"?> 2 | <!-- 3 | /**************************************************************************** 4 | * Copyright 2017, Optimizely, Inc. and contributors * 5 | * * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); * 7 | * you may not use this file except in compliance with the License. * 8 | * You may obtain a copy of the License at * 9 | * * 10 | * http://www.apache.org/licenses/LICENSE-2.0 * 11 | * * 12 | * Unless required by applicable law or agreed to in writing, software * 13 | * distributed under the License is distributed on an "AS IS" BASIS, * 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * 15 | * See the License for the specific language governing permissions and * 16 | * limitations under the License. * 17 | ***************************************************************************/ 18 | --> 19 | <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" 20 | xmlns:tools="http://schemas.android.com/tools" 21 | android:id="@+id/activity_variation_b" 22 | android:background="@drawable/ic_background_varib_marina" 23 | android:layout_width="match_parent" 24 | android:layout_height="match_parent" 25 | android:paddingBottom="@dimen/activity_vertical_margin" 26 | android:paddingLeft="@dimen/activity_horizontal_margin" 27 | android:paddingRight="@dimen/activity_horizontal_margin" 28 | android:paddingTop="@dimen/activity_vertical_margin" 29 | android:gravity="center_vertical" 30 | tools:context="com.optimizely.ab.android.test_app.VariationBActivity"> 31 | <TextView 32 | android:id="@+id/tv_variation_b_text_1" 33 | android:layout_width="wrap_content" 34 | android:layout_height="wrap_content" 35 | android:layout_centerHorizontal="true" 36 | android:textColor="#FFFFFF" 37 | android:textSize="150dp" 38 | android:text="B" 39 | android:layout_centerVertical="true" /> 40 | <TextView 41 | android:id="@+id/tv_variation_b_text_2" 42 | android:layout_below="@+id/tv_variation_b_text_1" 43 | android:layout_width="wrap_content" 44 | android:layout_height="wrap_content" 45 | android:layout_centerHorizontal="true" 46 | android:textColor="#FFFFFF" 47 | android:textSize="20dp" 48 | android:textAllCaps="true" 49 | android:text="variation" 50 | android:layout_marginTop="-20dp" 51 | android:layout_marginBottom="30dp" 52 | android:layout_centerVertical="true"/> 53 | 54 | <fragment 55 | android:id="@+id/frag" 56 | android:name="com.optimizely.ab.android.test_app.ConversionFragment" 57 | android:layout_width="wrap_content" 58 | android:layout_height="wrap_content" 59 | android:layout_below="@id/tv_variation_b_text_2" 60 | android:layout_centerHorizontal="true" 61 | tools:layout="@layout/fragment_conversion" /> 62 | </RelativeLayout> 63 | -------------------------------------------------------------------------------- /test-app/src/main/res/layout/coupon.xml: -------------------------------------------------------------------------------- 1 | <?xml version="1.0" encoding="utf-8"?> 2 | <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" 3 | android:id="@+id/coupon" 4 | android:layout_width="match_parent" 5 | android:layout_height="match_parent" 6 | android:background="#FFC107" 7 | android:orientation="vertical"> 8 | 9 | <TextView 10 | android:id="@+id/textView" 11 | android:layout_width="match_parent" 12 | android:layout_height="134dp" 13 | android:autoText="false" 14 | android:gravity="center" 15 | android:text="Coupon" 16 | android:textAppearance="@style/TextAppearance.AppCompat.Large" 17 | android:textSize="24sp" 18 | android:textStyle="bold" /> 19 | 20 | <TextView 21 | android:id="@+id/message" 22 | android:layout_width="match_parent" 23 | android:layout_height="133dp" 24 | android:gravity="center" 25 | android:text="You get a 10% discount" 26 | android:textSize="24sp" /> 27 | 28 | <Button 29 | android:id="@+id/redeem" 30 | android:layout_width="match_parent" 31 | android:layout_height="wrap_content" 32 | android:text="Redeem" /> 33 | </LinearLayout> -------------------------------------------------------------------------------- /test-app/src/main/res/layout/fragment_conversion.xml: -------------------------------------------------------------------------------- 1 | <?xml version="1.0" encoding="utf-8"?> 2 | <!-- 3 | /**************************************************************************** 4 | * Copyright 2017, Optimizely, Inc. and contributors * 5 | * * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); * 7 | * you may not use this file except in compliance with the License. * 8 | * You may obtain a copy of the License at * 9 | * * 10 | * http://www.apache.org/licenses/LICENSE-2.0 * 11 | * * 12 | * Unless required by applicable law or agreed to in writing, software * 13 | * distributed under the License is distributed on an "AS IS" BASIS, * 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * 15 | * See the License for the specific language governing permissions and * 16 | * limitations under the License. * 17 | ***************************************************************************/ 18 | --> 19 | <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" 20 | xmlns:tools="http://schemas.android.com/tools" 21 | android:id="@+id/frag" 22 | android:layout_width="match_parent" 23 | android:layout_height="match_parent" 24 | android:orientation="vertical"> 25 | 26 | <Button 27 | android:id="@+id/btn_variation_conversion" 28 | android:layout_width="wrap_content" 29 | android:layout_height="wrap_content" 30 | android:background="@color/colorAccent" 31 | android:padding="15dp" 32 | android:text="Test Conversion" 33 | android:textColor="#FFFFFF" 34 | android:textSize="16dp" /> 35 | 36 | </RelativeLayout> -------------------------------------------------------------------------------- /test-app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/optimizely/android-sdk/841a5d236d822e53803e4064665663b221057b64/test-app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /test-app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/optimizely/android-sdk/841a5d236d822e53803e4064665663b221057b64/test-app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /test-app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/optimizely/android-sdk/841a5d236d822e53803e4064665663b221057b64/test-app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /test-app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/optimizely/android-sdk/841a5d236d822e53803e4064665663b221057b64/test-app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /test-app/src/main/res/raw/datafile.json: -------------------------------------------------------------------------------- 1 | { 2 | "groups": [], 3 | "variables": [], 4 | "experiments": [ 5 | { 6 | "status": "Running", 7 | "key": "background_experiment", 8 | "layerId": "8143622708", 9 | "trafficAllocation": [ 10 | { 11 | "entityId": "8146590584", 12 | "endOfRange": 5000 13 | }, 14 | { 15 | "entityId": "8134412703", 16 | "endOfRange": 10000 17 | } 18 | ], 19 | "audienceIds": [], 20 | "variations": [ 21 | { 22 | "variables": [], 23 | "id": "8146590584", 24 | "key": "variation_a" 25 | }, 26 | { 27 | "variables": [], 28 | "id": "8134412703", 29 | "key": "variation_b" 30 | } 31 | ], 32 | "forcedVariations": { 33 | "test_user": "variation_a" 34 | }, 35 | "id": "8126664113" 36 | } 37 | ], 38 | "audiences": [], 39 | "anonymizeIP": false, 40 | "accountId": "6384711706", 41 | "projectId": "8136462271", 42 | "version": "3", 43 | "attributes": [], 44 | "events": [ 45 | { 46 | "experimentIds": [ 47 | "8126664113" 48 | ], 49 | "id": "8128595839", 50 | "key": "sample_conversion" 51 | } 52 | ], 53 | "revision": "5" 54 | } -------------------------------------------------------------------------------- /test-app/src/main/res/values-w820dp/dimens.xml: -------------------------------------------------------------------------------- 1 | <resources> 2 | <!-- Example customization of dimensions originally defined in res/values/dimens.xml 3 | (such as screen margins) for screens with more than 820dp of available width. This 4 | would include 7" and 10" devices in landscape (~960dp and ~1280dp respectively). --> 5 | <dimen name="activity_horizontal_margin">64dp</dimen> 6 | </resources> 7 | -------------------------------------------------------------------------------- /test-app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | <?xml version="1.0" encoding="utf-8"?> 2 | <resources> 3 | <color name="colorPrimary">#003952</color> 4 | <color name="colorPrimaryDark">#1976D2</color> 5 | <color name="colorAccent">#ff5500</color> 6 | <color name="colorInverse">#FFFFFF</color> 7 | </resources> 8 | -------------------------------------------------------------------------------- /test-app/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | <resources> 2 | <!-- Default screen margins, per the Android Design guidelines. --> 3 | <dimen name="activity_horizontal_margin">16dp</dimen> 4 | <dimen name="activity_vertical_margin">16dp</dimen> 5 | </resources> 6 | -------------------------------------------------------------------------------- /test-app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | <resources> 2 | <string name="app_name">Optimizely Demo App</string> 3 | <string name="main_act_button_1_text_default">Next Activity</string> 4 | <string name="main_act_button_1_text_var_1">Start Next Activity!</string> 5 | <string name="main_act_button_1_text_var_2">Start Second Activity!</string> 6 | <string name="main_act_text_view_1_default">Optimizely X Test App</string> 7 | <string name="main_act_text_view_1_var_1">Optimizely X</string> 8 | <string name="main_act_text_view_1_var_2">Optimizely Test App</string> 9 | <string name="main_act_text_view_1_cd">Text View 1</string> 10 | <string name="secondary_frag_text_view_1_default">Inside of a Fragment</string> 11 | <string name="secondary_frag_text_view_1_var_1">We\'re Inside of a Fragment</string> 12 | <string name="secondary_frag_text_view_1_var_2">We are Inside of a Fragment</string> 13 | <string name="secondary_frag_button_1_default">Show Toast</string> 14 | <string name="notification_service_notification_title">Optimizely X Android</string> 15 | <string name="notification_service_notification_text">Works in services!</string> 16 | </resources> 17 | -------------------------------------------------------------------------------- /test-app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | <resources> 2 | 3 | <!-- Base application theme. --> 4 | <style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar"> 5 | <!-- Customize your theme here. --> 6 | <item name="colorPrimary">@color/colorPrimary</item> 7 | <item name="colorPrimaryDark">@color/colorPrimaryDark</item> 8 | <item name="colorAccent">@color/colorAccent</item> 9 | </style> 10 | 11 | <style name="SplashTheme" parent="Theme.AppCompat.NoActionBar"> 12 | <item name="colorPrimaryDark">@color/colorPrimaryDark</item> 13 | </style> 14 | 15 | </resources> 16 | -------------------------------------------------------------------------------- /test-app/src/main/resources/android-logger.properties: -------------------------------------------------------------------------------- 1 | #**************************************************************************** 2 | # Copyright 2016, Optimizely, Inc. and contributors * 3 | # * 4 | # Licensed under the Apache License, Version 2.0 (the "License"); * 5 | # you may not use this file except in compliance with the License. * 6 | # You may obtain a copy of the License at * 7 | # * 8 | # http://www.apache.org/licenses/LICENSE-2.0 * 9 | # * 10 | # Unless required by applicable law or agreed to in writing, software * 11 | # distributed under the License is distributed on an "AS IS" BASIS, * 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * 13 | # See the License for the specific language governing permissions and * 14 | # limitations under the License. * 15 | #**************************************************************************/ 16 | 17 | # Android Logger configuration example 18 | 19 | # https://github.com/noveogroup/android-logger 20 | # Valid logging levels are: VERBOSE, DEBUG, INFO, WARN, ERROR, ASSERT 21 | 22 | # Default configuration 23 | root=DEBUG:Optly 24 | 25 | # Core 26 | logger.com.optimizely.ab.Optimizely=DEBUG:Optly.core 27 | logger.com.optimizely.ab.annotations=DEBUG:Optly.annotations 28 | logger.com.optimizely.ab.bucketing=DEBUG:Optly.bucketing 29 | logger.com.optimizely.ab.config=DEBUG:Optly.config 30 | logger.com.optimizely.ab.error=DEBUG:Optly.error 31 | logger.com.optimizely.ab.event=DEBUG:Optly.event 32 | logger.com.optimizely.ab.internal=DEBUG:Optly.internal 33 | 34 | # Android 35 | logger.com.optimizely.ab.android.sdk=DEBUG:Optly.androidSdk 36 | logger.com.optimizely.ab.android.datafile_handler=DEBUG:Optly.datafileHandler 37 | logger.com.optimizely.ab.android.event_handler=DEBUG:Optly.eventHandler 38 | logger.com.optimizely.ab.android.shared=DEBUG:Optly.shared 39 | logger.com.optimizely.ab.android.user-profile=DEBUG:Optly.userProfile 40 | 41 | # Disable most SDK logs by commenting all other lines and uncommenting below 42 | #logger.com.optimizely.ab=ASSERT:Optly 43 | -------------------------------------------------------------------------------- /test-app/test-app-proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in /Users/jdeffibaugh/Library/Android/sdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # when gson is replaced with jackson-databind json parser 11 | -keep class com.fasterxml.jackson.** {*;} 12 | 13 | #-printconfiguration proguard-merged-config.txt 14 | -------------------------------------------------------------------------------- /user-profile/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /user-profile/build.gradle: -------------------------------------------------------------------------------- 1 | /**************************************************************************** 2 | * Copyright 2016-2020, Optimizely, Inc. and contributors * 3 | * * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); * 5 | * you may not use this file except in compliance with the License. * 6 | * You may obtain a copy of the License at * 7 | * * 8 | * http://www.apache.org/licenses/LICENSE-2.0 * 9 | * * 10 | * Unless required by applicable law or agreed to in writing, software * 11 | * distributed under the License is distributed on an "AS IS" BASIS, * 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * 13 | * See the License for the specific language governing permissions and * 14 | * limitations under the License. * 15 | ***************************************************************************/ 16 | 17 | apply plugin: 'com.android.library' 18 | apply plugin: 'kotlin-android' 19 | 20 | android { 21 | compileSdkVersion compile_sdk_version 22 | buildToolsVersion build_tools_version 23 | 24 | defaultConfig { 25 | minSdkVersion min_sdk_version 26 | targetSdkVersion target_sdk_version 27 | multiDexEnabled true 28 | testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner' 29 | } 30 | testOptions { 31 | unitTests.returnDefaultValues = true 32 | } 33 | buildTypes { 34 | release { 35 | minifyEnabled false 36 | } 37 | debug { 38 | testCoverageEnabled true 39 | } 40 | } 41 | compileOptions { 42 | sourceCompatibility JavaVersion.VERSION_17 43 | targetCompatibility JavaVersion.VERSION_17 44 | } 45 | } 46 | 47 | dependencies { 48 | api project(':shared') 49 | 50 | implementation "androidx.annotation:annotation:$annotations_ver" 51 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" 52 | 53 | compileOnly "com.noveogroup.android:android-logger:$android_logger_ver" 54 | 55 | testImplementation "junit:junit:$junit_ver" 56 | testImplementation "org.mockito:mockito-core:$mockito_ver" 57 | testImplementation "com.noveogroup.android:android-logger:$android_logger_ver" 58 | 59 | androidTestImplementation "androidx.work:work-testing:$work_runtime" 60 | androidTestImplementation "androidx.test.ext:junit:$androidx_test_junit" 61 | androidTestImplementation "androidx.test.espresso:espresso-core:$espresso_ver" 62 | // Set this dependency to use JUnit 4 rules 63 | androidTestImplementation "androidx.test:rules:$androidx_test_rules" 64 | androidTestImplementation "androidx.test:core:$androidx_test_core" 65 | androidTestImplementation "androidx.test:core-ktx:$androidx_test_core" 66 | 67 | androidTestImplementation "org.mockito:mockito-core:$mockito_ver" 68 | androidTestImplementation "org.mockito:mockito-android:$mockito_ver" 69 | androidTestImplementation "com.noveogroup.android:android-logger:$android_logger_ver" 70 | androidTestImplementation "com.fasterxml.jackson.core:jackson-databind:$jacksonversion" 71 | } 72 | -------------------------------------------------------------------------------- /user-profile/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | <?xml version="1.0" encoding="utf-8"?> 2 | <!-- 3 | /**************************************************************************** 4 | * Copyright 2017, Optimizely, Inc. and contributors * 5 | * * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); * 7 | * you may not use this file except in compliance with the License. * 8 | * You may obtain a copy of the License at * 9 | * * 10 | * http://www.apache.org/licenses/LICENSE-2.0 * 11 | * * 12 | * Unless required by applicable law or agreed to in writing, software * 13 | * distributed under the License is distributed on an "AS IS" BASIS, * 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * 15 | * See the License for the specific language governing permissions and * 16 | * limitations under the License. * 17 | ***************************************************************************/ 18 | --> 19 | <manifest package="com.optimizely.ab.android.user_profile" 20 | xmlns:android="http://schemas.android.com/apk/res/android"> 21 | </manifest> 22 | --------------------------------------------------------------------------------