From 2aab02cea93d1e20fdb1288e6b9acd233961c054 Mon Sep 17 00:00:00 2001 From: Kevin Lind <40409666+kevinlind@users.noreply.github.com> Date: Fri, 29 Jan 2021 17:41:22 -0800 Subject: [PATCH 001/101] Setup project (#1) * Update README and add boilerplate leagal docs * Add top-level .gitignore * Add base IdentityEdge extension * Add Makefile * Add CircleCI config * Rename files from IdentityEdge to Identity * Update README installation instructions * Use extension name constant. --- .circleci/config.yml | 62 +++++ .gitignore | 11 + CODE_OF_CONDUCT.md | 74 +++++ COPYRIGHT | 5 + LICENSE | 201 ++++++++++++++ Makefile | 61 +++++ README.md | 57 +++- code/.gitignore | 14 + code/app/.gitignore | 1 + code/app/build.gradle | 33 +++ code/app/proguard-rules.pro | 21 ++ code/app/src/main/AndroidManifest.xml | 37 +++ .../IdentityEdgeTestApplication.java | 71 +++++ .../identityEdgeTestApp/MainActivity.java | 31 +++ .../drawable-v24/ic_launcher_foreground.xml | 30 ++ .../res/drawable/ic_launcher_background.xml | 170 ++++++++++++ .../app/src/main/res/layout/activity_main.xml | 18 ++ .../res/mipmap-anydpi-v26/ic_launcher.xml | 5 + .../mipmap-anydpi-v26/ic_launcher_round.xml | 5 + .../src/main/res/mipmap-hdpi/ic_launcher.png | Bin 0 -> 3593 bytes .../res/mipmap-hdpi/ic_launcher_round.png | Bin 0 -> 5339 bytes .../src/main/res/mipmap-mdpi/ic_launcher.png | Bin 0 -> 2636 bytes .../res/mipmap-mdpi/ic_launcher_round.png | Bin 0 -> 3388 bytes .../src/main/res/mipmap-xhdpi/ic_launcher.png | Bin 0 -> 4926 bytes .../res/mipmap-xhdpi/ic_launcher_round.png | Bin 0 -> 7472 bytes .../main/res/mipmap-xxhdpi/ic_launcher.png | Bin 0 -> 7909 bytes .../res/mipmap-xxhdpi/ic_launcher_round.png | Bin 0 -> 11873 bytes .../main/res/mipmap-xxxhdpi/ic_launcher.png | Bin 0 -> 10652 bytes .../res/mipmap-xxxhdpi/ic_launcher_round.png | Bin 0 -> 16570 bytes code/app/src/main/res/values/colors.xml | 6 + code/app/src/main/res/values/strings.xml | 3 + code/app/src/main/res/values/styles.xml | 10 + .../main/res/xml/network_security_config.xml | 9 + code/build.gradle | 80 ++++++ code/gradle.properties | 26 ++ code/gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 54329 bytes code/gradle/wrapper/gradle-wrapper.properties | 6 + code/gradlew | 172 ++++++++++++ code/gradlew.bat | 84 ++++++ code/identityedge/.gitignore | 1 + code/identityedge/build.gradle | 259 ++++++++++++++++++ code/identityedge/consumer-rules.pro | 0 code/identityedge/proguard-rules.pro | 21 ++ .../mobile/ExampleInstrumentedTest.java | 43 +++ .../identityedge/src/main/AndroidManifest.xml | 3 + .../com/adobe/marketing/mobile/Identity.java | 45 +++ .../marketing/mobile/IdentityConstants.java | 27 ++ .../marketing/mobile/IdentityExtension.java | 42 +++ .../mobile/IdentityExtensionVersionTest.java | 75 +++++ code/settings.gradle | 3 + 50 files changed, 1821 insertions(+), 1 deletion(-) create mode 100644 .circleci/config.yml create mode 100644 .gitignore create mode 100644 CODE_OF_CONDUCT.md create mode 100644 COPYRIGHT create mode 100644 LICENSE create mode 100644 Makefile create mode 100644 code/.gitignore create mode 100644 code/app/.gitignore create mode 100644 code/app/build.gradle create mode 100644 code/app/proguard-rules.pro create mode 100644 code/app/src/main/AndroidManifest.xml create mode 100644 code/app/src/main/java/com/adobe/marketing/mobile/identityEdgeTestApp/IdentityEdgeTestApplication.java create mode 100644 code/app/src/main/java/com/adobe/marketing/mobile/identityEdgeTestApp/MainActivity.java create mode 100644 code/app/src/main/res/drawable-v24/ic_launcher_foreground.xml create mode 100644 code/app/src/main/res/drawable/ic_launcher_background.xml create mode 100644 code/app/src/main/res/layout/activity_main.xml create mode 100644 code/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml create mode 100644 code/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml create mode 100644 code/app/src/main/res/mipmap-hdpi/ic_launcher.png create mode 100644 code/app/src/main/res/mipmap-hdpi/ic_launcher_round.png create mode 100644 code/app/src/main/res/mipmap-mdpi/ic_launcher.png create mode 100644 code/app/src/main/res/mipmap-mdpi/ic_launcher_round.png create mode 100644 code/app/src/main/res/mipmap-xhdpi/ic_launcher.png create mode 100644 code/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png create mode 100644 code/app/src/main/res/mipmap-xxhdpi/ic_launcher.png create mode 100644 code/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png create mode 100644 code/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png create mode 100644 code/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png create mode 100644 code/app/src/main/res/values/colors.xml create mode 100644 code/app/src/main/res/values/strings.xml create mode 100644 code/app/src/main/res/values/styles.xml create mode 100644 code/app/src/main/res/xml/network_security_config.xml create mode 100644 code/build.gradle create mode 100644 code/gradle.properties create mode 100644 code/gradle/wrapper/gradle-wrapper.jar create mode 100644 code/gradle/wrapper/gradle-wrapper.properties create mode 100755 code/gradlew create mode 100644 code/gradlew.bat create mode 100644 code/identityedge/.gitignore create mode 100644 code/identityedge/build.gradle create mode 100644 code/identityedge/consumer-rules.pro create mode 100644 code/identityedge/proguard-rules.pro create mode 100644 code/identityedge/src/androidTest/java/com/adobe/marketing/mobile/ExampleInstrumentedTest.java create mode 100644 code/identityedge/src/main/AndroidManifest.xml create mode 100644 code/identityedge/src/main/java/com/adobe/marketing/mobile/Identity.java create mode 100644 code/identityedge/src/main/java/com/adobe/marketing/mobile/IdentityConstants.java create mode 100644 code/identityedge/src/main/java/com/adobe/marketing/mobile/IdentityExtension.java create mode 100644 code/identityedge/src/test/java/com/adobe/marketing/mobile/IdentityExtensionVersionTest.java create mode 100644 code/settings.gradle diff --git a/.circleci/config.yml b/.circleci/config.yml new file mode 100644 index 00000000..0eceb194 --- /dev/null +++ b/.circleci/config.yml @@ -0,0 +1,62 @@ +version: 2.1 + +# Workflows orchestrate a set of jobs to be run; +workflows: + version: 2 + build-test-deploy: + jobs: + - build-and-test + - hold: + filters: + branches: + only: + - main + type: approval + requires: + - build-and-test + - publish: + requires: + - hold + +jobs: + build-and-test: + working_directory: ~/code + docker: + - image: circleci/android:api-29 + environment: + JVM_OPTS: -Xmx3200m + steps: + - checkout + - run: + name: Javadoc + command: make ci-javadoc + - store_artifacts: + path: ci/javadoc/build/reports + + - run: + name: Build + command: make ci-build + + - run: + name: Build App + command: make ci-build-app + + - run: + name: UnitTests + command: make ci-unit-test + - store_artifacts: + path: ci/unit-test/build/reports + - store_test_results: + path: ci/unit-test/build/test-results + + publish: + docker: + - image: circleci/android:api-29 + environment: + JVM_OPTS: -Xmx3200m + steps: + - checkout + - run: + name: Bintray Upload + command: code/gradlew -p code/identityedge bintrayUpload -PapiKey=$bintrayAPIKey -PgpgPassphrase=$gpgPassphrase + \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..e06fcde8 --- /dev/null +++ b/.gitignore @@ -0,0 +1,11 @@ +# DSStore +.DS_Store + +# Generated files +bin/ +build/ +ci/ + +# IntelliJ +*.iml +**/.idea/ diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 00000000..7ba0d6a4 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,74 @@ +# Adobe Code of Conduct + +## Our Pledge + +In the interest of fostering an open and welcoming environment, we as +contributors and maintainers pledge to making participation in our project and +our community a harassment-free experience for everyone, regardless of age, body +size, disability, ethnicity, gender identity and expression, level of experience, +nationality, personal appearance, race, religion, or sexual identity and +orientation. + +## Our Standards + +Examples of behavior that contributes to creating a positive environment +include: + +* Using welcoming and inclusive language +* Being respectful of differing viewpoints and experiences +* Gracefully accepting constructive criticism +* Focusing on what is best for the community +* Showing empathy towards other community members + +Examples of unacceptable behavior by participants include: + +* The use of sexualized language or imagery and unwelcome sexual attention or +advances +* Trolling, insulting/derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or electronic + address, without explicit permission +* Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Our Responsibilities + +Project maintainers are responsible for clarifying the standards of acceptable +behavior and are expected to take appropriate and fair corrective action in +response to any instances of unacceptable behavior. + +Project maintainers have the right and responsibility to remove, edit, or +reject comments, commits, code, wiki edits, issues, and other contributions +that are not aligned to this Code of Conduct, or to ban temporarily or +permanently any contributor for other behaviors that they deem inappropriate, +threatening, offensive, or harmful. + +## Scope + +This Code of Conduct applies both within project spaces and in public spaces +when an individual is representing the project or its community. Examples of +representing a project or community include using an official project e-mail +address, posting via an official social media account, or acting as an appointed +representative at an online or offline event. Representation of a project may be +further defined and clarified by project maintainers. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported by contacting the project team at Grp-opensourceoffice@adobe.com. All +complaints will be reviewed and investigated and will result in a response that +is deemed necessary and appropriate to the circumstances. The project team is +obligated to maintain confidentiality with regard to the reporter of an incident. +Further details of specific enforcement policies may be posted separately. + +Project maintainers who do not follow or enforce the Code of Conduct in good +faith may face temporary or permanent repercussions as determined by other +members of the project's leadership. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, +available at [https://contributor-covenant.org/version/1/4][version] + +[homepage]: https://contributor-covenant.org +[version]: https://contributor-covenant.org/version/1/4/ diff --git a/COPYRIGHT b/COPYRIGHT new file mode 100644 index 00000000..8aa33c0c --- /dev/null +++ b/COPYRIGHT @@ -0,0 +1,5 @@ +© Copyright 2015-2021 Adobe. All rights reserved. + +Adobe holds the copyright for all the files found in this repository. + +See the LICENSE file for licensing information. diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..161d7ff9 --- /dev/null +++ b/LICENSE @@ -0,0 +1,201 @@ +Apache License +Version 2.0, January 2004 +http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + +"License" shall mean the terms and conditions for use, reproduction, +and distribution as defined by Sections 1 through 9 of this document. + +"Licensor" shall mean the copyright owner or entity authorized by +the copyright owner that is granting the License. + +"Legal Entity" shall mean the union of the acting entity and all +other entities that control, are controlled by, or are under common +control with that entity. For the purposes of this definition, +"control" means (i) the power, direct or indirect, to cause the +direction or management of such entity, whether by contract or +otherwise, or (ii) ownership of fifty percent (50%) or more of the +outstanding shares, or (iii) beneficial ownership of such entity. + +"You" (or "Your") shall mean an individual or Legal Entity +exercising permissions granted by this License. + +"Source" form shall mean the preferred form for making modifications, +including but not limited to software source code, documentation +source, and configuration files. + +"Object" form shall mean any form resulting from mechanical +transformation or translation of a Source form, including but +not limited to compiled object code, generated documentation, +and conversions to other media types. + +"Work" shall mean the work of authorship, whether in Source or +Object form, made available under the License, as indicated by a +copyright notice that is included in or attached to the work +(an example is provided in the Appendix below). + +"Derivative Works" shall mean any work, whether in Source or Object +form, that is based on (or derived from) the Work and for which the +editorial revisions, annotations, elaborations, or other modifications +represent, as a whole, an original work of authorship. For the purposes +of this License, Derivative Works shall not include works that remain +separable from, or merely link (or bind by name) to the interfaces of, +the Work and Derivative Works thereof. + +"Contribution" shall mean any work of authorship, including +the original version of the Work and any modifications or additions +to that Work or Derivative Works thereof, that is intentionally +submitted to Licensor for inclusion in the Work by the copyright owner +or by an individual or Legal Entity authorized to submit on behalf of +the copyright owner. For the purposes of this definition, "submitted" +means any form of electronic, verbal, or written communication sent +to the Licensor or its representatives, including but not limited to +communication on electronic mailing lists, source code control systems, +and issue tracking systems that are managed by, or on behalf of, the +Licensor for the purpose of discussing and improving the Work, but +excluding communication that is conspicuously marked or otherwise +designated in writing by the copyright owner as "Not a Contribution." + +"Contributor" shall mean Licensor and any individual or Legal Entity +on behalf of whom a Contribution has been received by Licensor and +subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of +this License, each Contributor hereby grants to You a perpetual, +worldwide, non-exclusive, no-charge, royalty-free, irrevocable +copyright license to reproduce, prepare Derivative Works of, +publicly display, publicly perform, sublicense, and distribute the +Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of +this License, each Contributor hereby grants to You a perpetual, +worldwide, non-exclusive, no-charge, royalty-free, irrevocable +(except as stated in this section) patent license to make, have made, +use, offer to sell, sell, import, and otherwise transfer the Work, +where such license applies only to those patent claims licensable +by such Contributor that are necessarily infringed by their +Contribution(s) alone or by combination of their Contribution(s) +with the Work to which such Contribution(s) was submitted. If You +institute patent litigation against any entity (including a +cross-claim or counterclaim in a lawsuit) alleging that the Work +or a Contribution incorporated within the Work constitutes direct +or contributory patent infringement, then any patent licenses +granted to You under this License for that Work shall terminate +as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the +Work or Derivative Works thereof in any medium, with or without +modifications, and in Source or Object form, provided that You +meet the following conditions: + +(a) You must give any other recipients of the Work or +Derivative Works a copy of this License; and + +(b) You must cause any modified files to carry prominent notices +stating that You changed the files; and + +(c) You must retain, in the Source form of any Derivative Works +that You distribute, all copyright, patent, trademark, and +attribution notices from the Source form of the Work, +excluding those notices that do not pertain to any part of +the Derivative Works; and + +(d) If the Work includes a "NOTICE" text file as part of its +distribution, then any Derivative Works that You distribute must +include a readable copy of the attribution notices contained +within such NOTICE file, excluding those notices that do not +pertain to any part of the Derivative Works, in at least one +of the following places: within a NOTICE text file distributed +as part of the Derivative Works; within the Source form or +documentation, if provided along with the Derivative Works; or, +within a display generated by the Derivative Works, if and +wherever such third-party notices normally appear. The contents +of the NOTICE file are for informational purposes only and +do not modify the License. You may add Your own attribution +notices within Derivative Works that You distribute, alongside +or as an addendum to the NOTICE text from the Work, provided +that such additional attribution notices cannot be construed +as modifying the License. + +You may add Your own copyright statement to Your modifications and +may provide additional or different license terms and conditions +for use, reproduction, or distribution of Your modifications, or +for any such Derivative Works as a whole, provided Your use, +reproduction, and distribution of the Work otherwise complies with +the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, +any Contribution intentionally submitted for inclusion in the Work +by You to the Licensor shall be under the terms and conditions of +this License, without any additional terms or conditions. +Notwithstanding the above, nothing herein shall supersede or modify +the terms of any separate license agreement you may have executed +with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade +names, trademarks, service marks, or product names of the Licensor, +except as required for reasonable and customary use in describing the +origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or +agreed to in writing, Licensor provides the Work (and each +Contributor provides its Contributions) on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +implied, including, without limitation, any warranties or conditions +of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A +PARTICULAR PURPOSE. You are solely responsible for determining the +appropriateness of using or redistributing the Work and assume any +risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, +whether in tort (including negligence), contract, or otherwise, +unless required by applicable law (such as deliberate and grossly +negligent acts) or agreed to in writing, shall any Contributor be +liable to You for damages, including any direct, indirect, special, +incidental, or consequential damages of any character arising as a +result of this License or out of the use or inability to use the +Work (including but not limited to damages for loss of goodwill, +work stoppage, computer failure or malfunction, or any and all +other commercial damages or losses), even if such Contributor +has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing +the Work or Derivative Works thereof, You may choose to offer, +and charge a fee for, acceptance of support, warranty, indemnity, +or other liability obligations and/or rights consistent with this +License. However, in accepting such obligations, You may act only +on Your own behalf and on Your sole responsibility, not on behalf +of any other Contributor, and only if You agree to indemnify, +defend, and hold each Contributor harmless for any liability +incurred by, or claims asserted against, such Contributor by reason +of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + +To apply the Apache License to your work, attach the following +boilerplate notice, with the fields enclosed by brackets "[]" +replaced with your own identifying information. (Don't include +the brackets!) The text should be enclosed in the appropriate +comment syntax for the file format. We also recommend that a +file or class name and description of purpose be included on the +same "printed page" as the copyright notice for easier +identification within third-party archives. + +Copyright 2021 Adobe + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/Makefile b/Makefile new file mode 100644 index 00000000..bdf27e4c --- /dev/null +++ b/Makefile @@ -0,0 +1,61 @@ +EXTENSION-LIBRARY-FOLDER-NAME = identityedge + +BUILD-ASSEMBLE-LOCATION = ./ci/assemble +ROOT_DIR=$(shell git rev-parse --show-toplevel) + +PROJECT_NAME = $(shell cat $(ROOT_DIR)/code/gradle.properties | grep "moduleProjectName" | cut -d'=' -f2) +AAR_NAME = $(shell cat $(ROOT_DIR)/code/gradle.properties | grep "moduleAARName" | cut -d'=' -f2) +MODULE_NAME = $(shell cat $(ROOT_DIR)/code/gradle.properties | grep "moduleName" | cut -d'=' -f2) +LIB_VERSION = $(shell cat $(ROOT_DIR)/code/gradle.properties | grep "moduleVersion" | cut -d'=' -f2) +SOURCE_FILE_DIR = $(ROOT_DIR)/code/$(PROJECT_NAME) +AAR_FILE_DIR = $(ROOT_DIR)/code/$(PROJECT_NAME)/build/outputs/aar + +create-ci: clean + (mkdir -p ci) + +clean: + (rm -rf ci) + (rm -rf $(AAR_FILE_DIR)) + (./code/gradlew -p code clean) + +ci-build: create-ci + (mkdir -p ci/assemble) + + (./code/gradlew -p code/$(EXTENSION-LIBRARY-FOLDER-NAME) assemblePhone) + (mv $(AAR_FILE_DIR)/$(EXTENSION-LIBRARY-FOLDER-NAME)-phone-release.aar $(AAR_FILE_DIR)/$(MODULE_NAME)-release-$(LIB_VERSION).aar) + (cp -r ./code/$(EXTENSION-LIBRARY-FOLDER-NAME)/build $(BUILD-ASSEMBLE-LOCATION)) + +ci-build-app: + (./code/gradlew -p code/app assemble) + +ci-unit-test: create-ci + (mkdir -p ci/unit-test) + (./code/gradlew -p code/$(EXTENSION-LIBRARY-FOLDER-NAME) platformUnitTestJacocoReport) + (cp -r ./code/$(EXTENSION-LIBRARY-FOLDER-NAME)/build ./ci/unit-test/) + +ci-functional-test: create-ci + (mkdir -p ci/functional-test) + (./code/gradlew -p code/$(EXTENSION-LIBRARY-FOLDER-NAME) uninstallPhoneDebugAndroidTest) + (./code/gradlew -p code/$(EXTENSION-LIBRARY-FOLDER-NAME) connectedPhoneDebugAndroidTest platformFunctionalTestJacocoReport) + (cp -r ./code/$(EXTENSION-LIBRARY-FOLDER-NAME)/build ./ci/functional-test) + +ci-javadoc: create-ci + (mkdir -p ci/javadoc) + (./code/gradlew -p code/$(EXTENSION-LIBRARY-FOLDER-NAME) javadocPublic > ci/javadocPublic.log 2>&1) + (cp -r ./code/$(EXTENSION-LIBRARY-FOLDER-NAME)/build ./ci/javadoc) + +ci-generate-library-debug: + (./code/gradlew -p code/${EXTENSION-LIBRARY-FOLDER-NAME} assemblePhoneDebug) + +ci-generate-library-release: + (./code/gradlew -p code/${EXTENSION-LIBRARY-FOLDER-NAME} assemblePhoneRelease) + +ci-publish-staging-all: + (./code/gradlew -p code/${EXTENSION-LIBRARY-FOLDER-NAME} clean artifactoryPublish) + +ci-publish-main-all: + (./code/gradlew -p code/${EXTENSION-LIBRARY-FOLDER-NAME} clean artifactoryPublish -PisMain=true) + +ci-publish: + (code/gradlew -p code/${EXTENSION-LIBRARY-FOLDER-NAME} assemblePhone) + (code/gradlew -p code/${EXTENSION-LIBRARY-FOLDER-NAME} bintrayUpload -PapiKey=$(APIKEY)) \ No newline at end of file diff --git a/README.md b/README.md index 9ee74aaa..b46a13c5 100644 --- a/README.md +++ b/README.md @@ -1 +1,56 @@ -# aepsdk-identityedge-android +# Adobe Experience Platform Identity Edge Mobile Extension + + +## About this project + +The AEP Identity Edge Mobile Extension is an extension for the [Adobe Experience Platform SDK](https://github.com/Adobe-Marketing-Cloud/acp-sdks) and requires the `MobileCore` extension. + + +### Installation + +Integrate the Identity Edge extension into your app by including the following in your gradle file's `dependencies`: + +``` +implementation 'com.adobe.marketing.mobile:identityedge:1.+' +implementation 'com.adobe.marketing.mobile:edge:1.+' +implementation 'com.adobe.marketing.mobile:core:1.+' +``` + +### Development + +**Open the project** + +To open and run the project, open the `code/settings.gradle` file in Android Studio + +**Run demo application** + +Once you opened the project in Android Studio (see above), select the `app` runnable and your favorite simulator and run the program. + +**View the platform events with Assurance** + +Configure a new Assurance session by setting the Base URL to `testapp://main` and launch Assurance in the demo app by running the following command in your terminal: + +```bash +$ adb shell am start -W -a android.intent.action.VIEW -d "testapp://main?adb_validation_sessionid=ADD_YOUR_SESSION_ID_HERE" com.adobe.marketing.mobile.identitytestapp +``` + +Note: replace ADD_YOUR_SESSION_ID_HERE with your Assurance session identifier. + +Once the connection is established and the events list starts getting populated, you can filter the Identity extension events by typing `AEP` in the `Search Events` search box. See full list of available events [here](https://aep-sdks.gitbook.io/docs/beta/experience-platform-extension/experience-platform-debugging#event-types-handled-by-the-aep-mobile-extension). + + + +## Related Projects + +| Project | Description | +| ------------------------------------------------------------ | ------------------------------------------------------------ | +| [AEP SDK Sample App for Android](https://github.com/adobe/aepsdk-sample-app-android) | Contains Android sample app for the AEP SDK. | + +## Contributing + +Contributions are welcomed! Read the [Contributing Guide](./.github/CONTRIBUTING.md) for more information. + +## Licensing + +This project is licensed under the Apache V2 License. See [LICENSE](LICENSE) for more information. + diff --git a/code/.gitignore b/code/.gitignore new file mode 100644 index 00000000..603b1407 --- /dev/null +++ b/code/.gitignore @@ -0,0 +1,14 @@ +*.iml +.gradle +/local.properties +/.idea/caches +/.idea/libraries +/.idea/modules.xml +/.idea/workspace.xml +/.idea/navEditor.xml +/.idea/assetWizardSettings.xml +.DS_Store +/build +/captures +.externalNativeBuild +.cxx diff --git a/code/app/.gitignore b/code/app/.gitignore new file mode 100644 index 00000000..42afabfd --- /dev/null +++ b/code/app/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/code/app/build.gradle b/code/app/build.gradle new file mode 100644 index 00000000..60852f96 --- /dev/null +++ b/code/app/build.gradle @@ -0,0 +1,33 @@ +apply plugin: 'com.android.application' + +android { + compileSdkVersion 30 + buildToolsVersion "30.0.2" + + defaultConfig { + applicationId "com.adobe.marketing.mobile.identitytestapp" + minSdkVersion 19 + targetSdkVersion 30 + versionCode 1 + versionName "1.0" + } + + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + } +} + +dependencies { + implementation fileTree(dir: "libs", include: ["*.jar"]) + implementation 'androidx.appcompat:appcompat:1.2.0' + implementation 'androidx.constraintlayout:constraintlayout:2.0.4' + + implementation project(':identityedge') + implementation 'com.adobe.marketing.mobile:core:1+' + implementation 'com.adobe.marketing.mobile:signal:1+' + implementation 'com.adobe.marketing.mobile:edge:1+' + implementation 'com.adobe.marketing.mobile:assurance:1+' +} \ No newline at end of file diff --git a/code/app/proguard-rules.pro b/code/app/proguard-rules.pro new file mode 100644 index 00000000..481bb434 --- /dev/null +++ b/code/app/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/code/app/src/main/AndroidManifest.xml b/code/app/src/main/AndroidManifest.xml new file mode 100644 index 00000000..a5240406 --- /dev/null +++ b/code/app/src/main/AndroidManifest.xml @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/code/app/src/main/java/com/adobe/marketing/mobile/identityEdgeTestApp/IdentityEdgeTestApplication.java b/code/app/src/main/java/com/adobe/marketing/mobile/identityEdgeTestApp/IdentityEdgeTestApplication.java new file mode 100644 index 00000000..def9f016 --- /dev/null +++ b/code/app/src/main/java/com/adobe/marketing/mobile/identityEdgeTestApp/IdentityEdgeTestApplication.java @@ -0,0 +1,71 @@ +/* ****************************************************************************** + * ADOBE CONFIDENTIAL + * ___________________ + * + * Copyright 2021 Adobe + * All Rights Reserved. + * + * NOTICE: All information contained herein is, and remains + * the property of Adobe and its suppliers, if any. The intellectual + * and technical concepts contained herein are proprietary to Adobe + * and its suppliers and are protected by all applicable intellectual + * property laws, including trade secret and copyright laws. + * Dissemination of this information or reproduction of this material + * is strictly forbidden unless prior written permission is obtained + * from Adobe. + ******************************************************************************/ + +package com.adobe.marketing.mobile.identityEdgeTestApp; + +import android.app.Application; +import android.util.Log; + +import com.adobe.marketing.mobile.AdobeCallback; +import com.adobe.marketing.mobile.Assurance; +import com.adobe.marketing.mobile.Identity; +import com.adobe.marketing.mobile.Edge; +import com.adobe.marketing.mobile.InvalidInitException; +import com.adobe.marketing.mobile.LoggingMode; +import com.adobe.marketing.mobile.MobileCore; +import com.adobe.marketing.mobile.Signal; + +public class IdentityEdgeTestApplication extends Application { + private static final String LOG_TAG = "IdentityEdgeTestApplication"; + + // TODO: fill in your Launch environment ID here + private final String LAUNCH_ENVIRONMENT_ID = ""; + + @Override + public void onCreate() { + super.onCreate(); + MobileCore.setApplication(this); + + MobileCore.setLogLevel(LoggingMode.VERBOSE); + + /* Launch generates a unique environment ID that the SDK uses to retrieve your + configuration. This ID is generated when an app configuration is created and published to + a given environment. It is strongly recommended to configure the SDK with the Launch + environment ID. + */ + MobileCore.configureWithAppID(LAUNCH_ENVIRONMENT_ID); + + // register Adobe core extensions + try { + Signal.registerExtension(); + Identity.registerExtension(); + Edge.registerExtension(); + Assurance.registerExtension(); + } catch (InvalidInitException e) { + e.printStackTrace(); + } + + // once all the extensions are registered, call MobileCore.start(...) to start processing the events + MobileCore.start(new AdobeCallback() { + @Override + public void call(final Object o) { + Log.d(LOG_TAG, "Mobile SDK was initialized"); + } + }); + } + +} diff --git a/code/app/src/main/java/com/adobe/marketing/mobile/identityEdgeTestApp/MainActivity.java b/code/app/src/main/java/com/adobe/marketing/mobile/identityEdgeTestApp/MainActivity.java new file mode 100644 index 00000000..335973b3 --- /dev/null +++ b/code/app/src/main/java/com/adobe/marketing/mobile/identityEdgeTestApp/MainActivity.java @@ -0,0 +1,31 @@ +/* ****************************************************************************** + * ADOBE CONFIDENTIAL + * ___________________ + * + * Copyright 2021 Adobe + * All Rights Reserved. + * + * NOTICE: All information contained herein is, and remains + * the property of Adobe and its suppliers, if any. The intellectual + * and technical concepts contained herein are proprietary to Adobe + * and its suppliers and are protected by all applicable intellectual + * property laws, including trade secret and copyright laws. + * Dissemination of this information or reproduction of this material + * is strictly forbidden unless prior written permission is obtained + * from Adobe. + ******************************************************************************/ + +package com.adobe.marketing.mobile.identityEdgeTestApp; + +import androidx.appcompat.app.AppCompatActivity; + +import android.os.Bundle; + +public class MainActivity extends AppCompatActivity { + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_main); + } +} diff --git a/code/app/src/main/res/drawable-v24/ic_launcher_foreground.xml b/code/app/src/main/res/drawable-v24/ic_launcher_foreground.xml new file mode 100644 index 00000000..2b068d11 --- /dev/null +++ b/code/app/src/main/res/drawable-v24/ic_launcher_foreground.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/code/app/src/main/res/drawable/ic_launcher_background.xml b/code/app/src/main/res/drawable/ic_launcher_background.xml new file mode 100644 index 00000000..07d5da9c --- /dev/null +++ b/code/app/src/main/res/drawable/ic_launcher_background.xml @@ -0,0 +1,170 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/code/app/src/main/res/layout/activity_main.xml b/code/app/src/main/res/layout/activity_main.xml new file mode 100644 index 00000000..4fc24441 --- /dev/null +++ b/code/app/src/main/res/layout/activity_main.xml @@ -0,0 +1,18 @@ + + + + + + \ No newline at end of file diff --git a/code/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/code/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml new file mode 100644 index 00000000..eca70cfe --- /dev/null +++ b/code/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/code/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/code/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml new file mode 100644 index 00000000..eca70cfe --- /dev/null +++ b/code/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/code/app/src/main/res/mipmap-hdpi/ic_launcher.png b/code/app/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..a571e60098c92c2baca8a5df62f2929cbff01b52 GIT binary patch literal 3593 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 P00000NkvXXu0mjfP=x?Y literal 0 HcmV?d00001 diff --git a/code/app/src/main/res/mipmap-hdpi/ic_launcher_round.png b/code/app/src/main/res/mipmap-hdpi/ic_launcher_round.png new file mode 100644 index 0000000000000000000000000000000000000000..61da551c5594a1f9d26193983d2cd69189014603 GIT binary patch literal 5339 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 literal 0 HcmV?d00001 diff --git a/code/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/code/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png new file mode 100644 index 0000000000000000000000000000000000000000..b216f2d313cc673d8b8c4da591c174ebed52795c GIT binary patch literal 11873 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 literal 0 HcmV?d00001 diff --git a/code/app/src/main/res/values/colors.xml b/code/app/src/main/res/values/colors.xml new file mode 100644 index 00000000..4faecfa8 --- /dev/null +++ b/code/app/src/main/res/values/colors.xml @@ -0,0 +1,6 @@ + + + #6200EE + #3700B3 + #03DAC5 + \ No newline at end of file diff --git a/code/app/src/main/res/values/strings.xml b/code/app/src/main/res/values/strings.xml new file mode 100644 index 00000000..13b30d55 --- /dev/null +++ b/code/app/src/main/res/values/strings.xml @@ -0,0 +1,3 @@ + + Identity Edge Test Application + \ No newline at end of file diff --git a/code/app/src/main/res/values/styles.xml b/code/app/src/main/res/values/styles.xml new file mode 100644 index 00000000..fac92916 --- /dev/null +++ b/code/app/src/main/res/values/styles.xml @@ -0,0 +1,10 @@ + + + + + \ No newline at end of file diff --git a/code/app/src/main/res/xml/network_security_config.xml b/code/app/src/main/res/xml/network_security_config.xml new file mode 100644 index 00000000..e1eb89e5 --- /dev/null +++ b/code/app/src/main/res/xml/network_security_config.xml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/code/build.gradle b/code/build.gradle new file mode 100644 index 00000000..f4e2d426 --- /dev/null +++ b/code/build.gradle @@ -0,0 +1,80 @@ +// Top-level build file where you can add configuration options common to all sub-projects/modules. +buildscript { + repositories { + google() + jcenter() + maven { + url "https://plugins.gradle.org/m2/" + } + } + dependencies { + classpath "com.android.tools.build:gradle:4.0.1" + classpath "org.jfrog.buildinfo:build-info-extractor-gradle:4.11.0" + classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.+' + // NOTE: Do not place your application dependencies here; they belong + // in the individual module build.gradle files + } +} + +allprojects { + repositories { + google() + jcenter() + maven { + url artifactoryUrl + credentials { + username = artifactoryUserName + password = artifactoryPassword + } + } + maven { + url artifactorySnapshotUrl + credentials { + username = artifactoryUserName + password = artifactoryPassword + } + } + } + + apply plugin: 'maven-publish' + apply plugin: "com.jfrog.artifactory" +} + +artifactory { + contextUrl = artifactoryContextUrl + publish { + repository { + repoKey = artifactoryRepoKey + username = artifactoryUserName + password = artifactoryPassword + maven = true + } + defaults { + publishArtifacts = true + publishPom = true + } + } +} + +task clean(type: Delete) { + delete rootProject.buildDir +} + +ext { + // android config + minSdkVersion = 19 + targetSdkVersion = 29 + compileSdkVersion = 29 + versionCode = 1 + + // java config + sourceCompatibility = JavaVersion.VERSION_1_7 + targetCompatibility = JavaVersion.VERSION_1_7 + + // dependencies + junitVersion = "1.1.2" + mockitoCoreVersion = "2.28.2" + buildToolsVersion = "29.0.3" + testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + jacocoVersion = "0.8.5" +} \ No newline at end of file diff --git a/code/gradle.properties b/code/gradle.properties new file mode 100644 index 00000000..615c1664 --- /dev/null +++ b/code/gradle.properties @@ -0,0 +1,26 @@ +# Project-wide Gradle settings. + +org.gradle.jvmargs=-Xmx2048m + +android.useAndroidX=true +android.enableJetifier=true + +moduleProjectName=identityedge +moduleName=identityedge +moduleAARName=identityedge-phone-release.aar +moduleVersion=1.0.0-alpha-1 + +mavenRepoName=AdobeMobileIdentityEdgeSdk +mavenRepoDescription=Adobe Experience Platform Identity Edge extension for the Adobe Experience Platform Mobile SDK +mavenUploadDryRunFlag=false + +# production versions for production build +mavenCoreVersion=1.5.7 + +#artifactory variables +artifactoryUrl=https://dummyurl/ +artifactoryContextUrl=https://dummyurl/ +artifactorySnapshotUrl=https://dummyurl/ +artifactoryRepoKey=xxx +artifactoryUserName=xxx +artifactoryPassword=xxx diff --git a/code/gradle/wrapper/gradle-wrapper.jar b/code/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..f6b961fd5a86aa5fbfe90f707c3138408be7c718 GIT binary patch literal 54329 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

<5G2M1WZP5 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 F{|7XC?P&l2 literal 0 HcmV?d00001 diff --git a/code/gradle/wrapper/gradle-wrapper.properties b/code/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 00000000..6afd7877 --- /dev/null +++ b/code/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Mon Jan 25 11:55:49 PST 2021 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-6.1.1-all.zip diff --git a/code/gradlew b/code/gradlew new file mode 100755 index 00000000..cccdd3d5 --- /dev/null +++ b/code/gradlew @@ -0,0 +1,172 @@ +#!/usr/bin/env sh + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS="" + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn () { + echo "$*" +} + +die () { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; + NONSTOP* ) + nonstop=true + ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin, switch paths to Windows format before running java +if $cygwin ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=$((i+1)) + done + case $i in + (0) set -- ;; + (1) set -- "$args0" ;; + (2) set -- "$args0" "$args1" ;; + (3) set -- "$args0" "$args1" "$args2" ;; + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Escape application args +save () { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " +} +APP_ARGS=$(save "$@") + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong +if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then + cd "$(dirname "$0")" +fi + +exec "$JAVACMD" "$@" diff --git a/code/gradlew.bat b/code/gradlew.bat new file mode 100644 index 00000000..e95643d6 --- /dev/null +++ b/code/gradlew.bat @@ -0,0 +1,84 @@ +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS= + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windows variants + +if not "%OS%" == "Windows_NT" goto win9xME_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/code/identityedge/.gitignore b/code/identityedge/.gitignore new file mode 100644 index 00000000..42afabfd --- /dev/null +++ b/code/identityedge/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/code/identityedge/build.gradle b/code/identityedge/build.gradle new file mode 100644 index 00000000..c79265d8 --- /dev/null +++ b/code/identityedge/build.gradle @@ -0,0 +1,259 @@ +apply plugin: 'com.android.library' +apply plugin: 'jacoco' +apply plugin: 'com.jfrog.bintray' + +jacoco { + toolVersion = rootProject.ext.jacocoVersion +} + +android { + compileSdkVersion rootProject.ext.compileSdkVersion + buildToolsVersion rootProject.ext.buildToolsVersion + + defaultConfig { + minSdkVersion rootProject.ext.minSdkVersion + targetSdkVersion rootProject.ext.targetSdkVersion + versionCode 1 + versionName rootProject.moduleVersion + + testInstrumentationRunner rootProject.ext.testInstrumentationRunner + consumerProguardFiles "consumer-rules.pro" + } + + flavorDimensions "target" + productFlavors { + phone { + dimension "target" + } + } + + buildTypes { + debug { + testCoverageEnabled true + debuggable true + } + + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + } + + testOptions { + unitTests.returnDefaultValues = true + } + + compileOptions { + sourceCompatibility rootProject.ext.sourceCompatibility + targetCompatibility rootProject.ext.targetCompatibility + } +} + +android.libraryVariants.all { variant -> + tasks.withType(Javadoc) { + source = [android.sourceSets.main.java.sourceFiles, android.sourceSets.phone.java.sourceFiles] + ext.androidJar = "${android.sdkDirectory}/platforms/${android.compileSdkVersion}/android.jar" + + doFirst{classpath = files(variant.javaCompile.classpath.files) + files(ext.androidJar)} + + exclude "**/BuildConfig.java" + exclude "**/R.java" + options { + links "https://developer.android.com/reference" + } + } +} + +task javadocInternal(type: Javadoc) { + destinationDir = reporting.file("javadocInternal") + options.memberLevel = JavadocMemberLevel.PRIVATE +} + +task javadocPublic(type: Javadoc) { + destinationDir = reporting.file("javadocPublic") + options.memberLevel = JavadocMemberLevel.PUBLIC +} + +task javadocPublish(type: Jar) { + from javadocPublic + archiveClassifier.set('javadoc') +} + +artifactoryPublish.dependsOn('assemblePhone') +publishing { + publications { + aar(MavenPublication) { + groupId = 'com.adobe.marketing.mobile' + artifactId = rootProject.moduleName + version = rootProject.moduleVersion + artifact("$buildDir/outputs/aar/${rootProject.moduleAARName}") + artifact javadocPublish + pom.withXml { + asNode().appendNode('name', mavenRepoName) + asNode().appendNode('description', mavenRepoDescription) + asNode().appendNode('url', 'https://aep-sdks.gitbook.io/docs/') + def scmNode = asNode().appendNode('scm') + scmNode.appendNode('url', 'https://github.com/Adobe-Marketing-Cloud/acp-sdks/') + + + def developersNode = asNode().appendNode('developers') + def developerNode = developersNode.appendNode('developer') + developerNode.appendNode('id', 'adobe') + developerNode.appendNode('name', 'adobe') + + def licensesNode = asNode().appendNode('licenses') + def licenseNode = licensesNode.appendNode('license') + licenseNode.appendNode('name', 'Adobe Proprietary') + + def dependenciesNode = asNode().appendNode('dependencies') + + //Iterate over the compile dependencies (we don't want the test ones), adding a node for each + + def coreDependencyNode = dependenciesNode.appendNode('dependency') + coreDependencyNode.appendNode('groupId', 'com.adobe.marketing.mobile') + coreDependencyNode.appendNode('artifactId', 'core') + coreDependencyNode.appendNode('version', mavenCoreVersion) + } + } + aarSnapshot(MavenPublication) { + groupId = 'com.adobe.marketing.mobile' + artifactId = rootProject.moduleName + if (project.hasProperty("isMain")) { + version = rootProject.moduleVersion + '-internal' + } else { + version = rootProject.moduleVersion + '-SNAPSHOT' + } + artifact("$buildDir/outputs/aar/${rootProject.moduleAARName}") + artifact javadocPublish + pom.withXml { + asNode().appendNode('name', mavenRepoName) + asNode().appendNode('description', mavenRepoDescription) + asNode().appendNode('url', 'https://aep-sdks.gitbook.io/docs/') + def scmNode = asNode().appendNode('scm') + scmNode.appendNode('url', 'https://github.com/Adobe-Marketing-Cloud/acp-sdks/') + + + def developersNode = asNode().appendNode('developers') + def developerNode = developersNode.appendNode('developer') + developerNode.appendNode('id', 'adobe') + developerNode.appendNode('name', 'adobe') + + def licensesNode = asNode().appendNode('licenses') + def licenseNode = licensesNode.appendNode('license') + licenseNode.appendNode('name', 'Adobe Proprietary') + + def dependenciesNode = asNode().appendNode('dependencies') + + //Iterate over the compile dependencies (we don't want the test ones), adding a node for each + + def coreDependencyNode = dependenciesNode.appendNode('dependency') + coreDependencyNode.appendNode('groupId', 'com.adobe.marketing.mobile') + coreDependencyNode.appendNode('artifactId', 'core') + coreDependencyNode.appendNode('version', mavenCoreVersion + '-SNAPSHOT') + } + } + } +} + +artifactoryPublish { + publications(publishing.publications.aarSnapshot) +} + +bintray { + user = 'sdkci' + //This apiKey is passed as a gradle parameter to the task + //from the Jenkins Pipeline. + if (project.hasProperty("apiKey")) { + key = "$apiKey" + } else { + key = "xxxxxxxxxxxxxxx" + } + + String gpgpass = "xxxxxxxxxxxxxxx" + if (project.hasProperty("gpgPassphrase")) { + gpgpass = "$gpgPassphrase" + } + dryRun = false + pkg { + repo = "mobileservicesdk" + name = mavenRepoName + userOrg = "eaps" + version { + name = rootProject.moduleVersion + desc = mavenRepoDescription + gpg { + sign = true + passphrase = gpgpass + } + } + } + + publications = ['aar'] +} + +bintrayUpload.dependsOn('assemblePhone') + + +task platformUnitTestJacocoReport(type: JacocoReport, dependsOn: "testPhoneDebugUnitTest") { + def excludeRegex = ['**/ADB*.class', '**/BuildConfig.class'] + def debugTree = fileTree(dir: "${project.buildDir}/intermediates/javac/phoneDebug/classes/com/adobe/marketing/mobile", excludes: excludeRegex) + + additionalClassDirs.setFrom files([debugTree]) + additionalSourceDirs.setFrom files(android.sourceSets.main.java.sourceFiles) + sourceDirectories.setFrom files(android.sourceSets.phone.java.sourceFiles) + executionData "$buildDir/jacoco/testPhoneDebugUnitTest.exec" + + reports { + xml.enabled true + csv.enabled false + html.enabled true + } +} + +task platformFunctionalTestJacocoReport(type: JacocoReport, dependsOn: "createPhoneDebugCoverageReport") { + def excludeRegex = ['**/ADB*.class', '**/BuildConfig.class'] + def debugTree = fileTree(dir: "${project.buildDir}/intermediates/javac/phoneDebug/classes/com/adobe/marketing/mobile", excludes: excludeRegex) + + additionalClassDirs.setFrom files([debugTree]) + additionalSourceDirs.setFrom files(android.sourceSets.main.java.sourceFiles) + sourceDirectories.setFrom files(android.sourceSets.phone.java.sourceFiles) + executionData fileTree(dir: "$buildDir", includes: [ + "outputs/code_coverage/phoneDebugAndroidTest/connected/*coverage.ec" + ]) + + reports { + xml.enabled true + csv.enabled false + html.enabled true + } +} + + +dependencies { + implementation 'androidx.appcompat:appcompat:1.2.0' + + // Adobe Mobile SDK Core + implementation "com.adobe.marketing.mobile:core:${rootProject.mavenCoreVersion}" + + testImplementation "androidx.test.ext:junit:${rootProject.ext.junitVersion}" + testImplementation "org.mockito:mockito-core:${rootProject.ext.mockitoCoreVersion}" + testImplementation 'com.fasterxml.jackson.core:jackson-databind:2.9.9' + testImplementation 'org.powermock:powermock-api-mockito2:2.0.0' + testImplementation 'org.powermock:powermock-module-junit4:2.0.0' + testImplementation 'org.json:json:20180813' + + androidTestImplementation "androidx.test.ext:junit:${rootProject.ext.junitVersion}" + androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0' + // Using older version of PowerMock as it supports minimum Android API < 26 + // the root issue appears to be a dependency with Objenesis in Mockito-Core + // where Objenesis 2 requires minimum Android API 26 + androidTestImplementation 'org.powermock:powermock-module-junit4:1+' + androidTestImplementation 'com.fasterxml.jackson.core:jackson-databind:2.9.9' + +} + +tasks.withType(Test) { + testLogging { + showStandardStreams = true + } +} \ No newline at end of file diff --git a/code/identityedge/consumer-rules.pro b/code/identityedge/consumer-rules.pro new file mode 100644 index 00000000..e69de29b diff --git a/code/identityedge/proguard-rules.pro b/code/identityedge/proguard-rules.pro new file mode 100644 index 00000000..481bb434 --- /dev/null +++ b/code/identityedge/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/code/identityedge/src/androidTest/java/com/adobe/marketing/mobile/ExampleInstrumentedTest.java b/code/identityedge/src/androidTest/java/com/adobe/marketing/mobile/ExampleInstrumentedTest.java new file mode 100644 index 00000000..b27b692b --- /dev/null +++ b/code/identityedge/src/androidTest/java/com/adobe/marketing/mobile/ExampleInstrumentedTest.java @@ -0,0 +1,43 @@ +/* ****************************************************************************** + * ADOBE CONFIDENTIAL + * ___________________ + * + * Copyright 2021 Adobe + * All Rights Reserved. + * + * NOTICE: All information contained herein is, and remains + * the property of Adobe and its suppliers, if any. The intellectual + * and technical concepts contained herein are proprietary to Adobe + * and its suppliers and are protected by all applicable intellectual + * property laws, including trade secret and copyright laws. + * Dissemination of this information or reproduction of this material + * is strictly forbidden unless prior written permission is obtained + * from Adobe. + ******************************************************************************/ + +package com.adobe.marketing.mobile; + +import android.content.Context; + +import androidx.test.platform.app.InstrumentationRegistry; +import androidx.test.ext.junit.runners.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import static org.junit.Assert.*; + +/** + * Instrumented test, which will execute on an Android device. + * + * @see Testing documentation + */ +@RunWith(AndroidJUnit4.class) +public class ExampleInstrumentedTest { + @Test + public void useAppContext() { + // Context of the app under test. + Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); + assertEquals("com.adobe.marketing.mobile.identityedge.test", appContext.getPackageName()); + } +} \ No newline at end of file diff --git a/code/identityedge/src/main/AndroidManifest.xml b/code/identityedge/src/main/AndroidManifest.xml new file mode 100644 index 00000000..08ba300c --- /dev/null +++ b/code/identityedge/src/main/AndroidManifest.xml @@ -0,0 +1,3 @@ + + \ No newline at end of file diff --git a/code/identityedge/src/main/java/com/adobe/marketing/mobile/Identity.java b/code/identityedge/src/main/java/com/adobe/marketing/mobile/Identity.java new file mode 100644 index 00000000..d501e886 --- /dev/null +++ b/code/identityedge/src/main/java/com/adobe/marketing/mobile/Identity.java @@ -0,0 +1,45 @@ +/* ****************************************************************************** + * ADOBE CONFIDENTIAL + * ___________________ + * + * Copyright 2021 Adobe + * All Rights Reserved. + * + * NOTICE: All information contained herein is, and remains + * the property of Adobe and its suppliers, if any. The intellectual + * and technical concepts contained herein are proprietary to Adobe + * and its suppliers and are protected by all applicable intellectual + * property laws, including trade secret and copyright laws. + * Dissemination of this information or reproduction of this material + * is strictly forbidden unless prior written permission is obtained + * from Adobe. + ******************************************************************************/ + +package com.adobe.marketing.mobile; + +public class Identity { + private static final String LOG_TAG = "Identity"; + + private Identity() {} + + /** + * Returns the version of the {@code Identity} extension + * @return The version as {@code String} + */ + public static String extensionVersion() { + return IdentityConstants.EXTENSION_VERSION; + } + + /** + * Registers the extension with the Mobile SDK. This method should be called only once in your application class. + */ + public static void registerExtension() { + MobileCore.registerExtension(IdentityExtension.class, new ExtensionErrorCallback() { + @Override + public void error(ExtensionError extensionError) { + MobileCore.log(LoggingMode.ERROR, LOG_TAG, + "There was an error registering the Identity Edge extension: " + extensionError.getErrorName()); + } + }); + } +} diff --git a/code/identityedge/src/main/java/com/adobe/marketing/mobile/IdentityConstants.java b/code/identityedge/src/main/java/com/adobe/marketing/mobile/IdentityConstants.java new file mode 100644 index 00000000..36a1bdf0 --- /dev/null +++ b/code/identityedge/src/main/java/com/adobe/marketing/mobile/IdentityConstants.java @@ -0,0 +1,27 @@ +/* ****************************************************************************** + * ADOBE CONFIDENTIAL + * ___________________ + * + * Copyright 2021 Adobe + * All Rights Reserved. + * + * NOTICE: All information contained herein is, and remains + * the property of Adobe and its suppliers, if any. The intellectual + * and technical concepts contained herein are proprietary to Adobe + * and its suppliers and are protected by all applicable intellectual + * property laws, including trade secret and copyright laws. + * Dissemination of this information or reproduction of this material + * is strictly forbidden unless prior written permission is obtained + * from Adobe. + ******************************************************************************/ + +package com.adobe.marketing.mobile; + +class IdentityConstants { + + static final String EXTENSION_NAME = "com.adobe.module.identity"; + static final String EXTENSION_VERSION = "1.0.0-alpha-1"; + + + private IdentityConstants() {} +} diff --git a/code/identityedge/src/main/java/com/adobe/marketing/mobile/IdentityExtension.java b/code/identityedge/src/main/java/com/adobe/marketing/mobile/IdentityExtension.java new file mode 100644 index 00000000..65cc251f --- /dev/null +++ b/code/identityedge/src/main/java/com/adobe/marketing/mobile/IdentityExtension.java @@ -0,0 +1,42 @@ +/* ****************************************************************************** + * ADOBE CONFIDENTIAL + * ___________________ + * + * Copyright 2021 Adobe + * All Rights Reserved. + * + * NOTICE: All information contained herein is, and remains + * the property of Adobe and its suppliers, if any. The intellectual + * and technical concepts contained herein are proprietary to Adobe + * and its suppliers and are protected by all applicable intellectual + * property laws, including trade secret and copyright laws. + * Dissemination of this information or reproduction of this material + * is strictly forbidden unless prior written permission is obtained + * from Adobe. + ******************************************************************************/ + +package com.adobe.marketing.mobile; + +class IdentityExtension extends Extension { + protected IdentityExtension(ExtensionApi extensionApi) { + super(extensionApi); + } + + /** + * Required override. Each extension must have a unique name within the application. + * @return unique name of this extension + */ + @Override + protected String getName() { + return IdentityConstants.EXTENSION_NAME; + } + + /** + * Optional override. + * @return the version of this extension + */ + @Override + protected String getVersion() { + return IdentityConstants.EXTENSION_VERSION; + } +} diff --git a/code/identityedge/src/test/java/com/adobe/marketing/mobile/IdentityExtensionVersionTest.java b/code/identityedge/src/test/java/com/adobe/marketing/mobile/IdentityExtensionVersionTest.java new file mode 100644 index 00000000..be5d6947 --- /dev/null +++ b/code/identityedge/src/test/java/com/adobe/marketing/mobile/IdentityExtensionVersionTest.java @@ -0,0 +1,75 @@ +/* ****************************************************************************** + * ADOBE CONFIDENTIAL + * ___________________ + * + * Copyright 2021 Adobe + * All Rights Reserved. + * + * NOTICE: All information contained herein is, and remains + * the property of Adobe and its suppliers, if any. The intellectual + * and technical concepts contained herein are proprietary to Adobe + * and its suppliers and are protected by all applicable intellectual + * property laws, including trade secret and copyright laws. + * Dissemination of this information or reproduction of this material + * is strictly forbidden unless prior written permission is obtained + * from Adobe. + ******************************************************************************/ + +package com.adobe.marketing.mobile; + +import org.junit.Test; + +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.Properties; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; + +public class IdentityExtensionVersionTest { + private static String GRADLE_PROPERTIES_PATH = "../gradle.properties"; + private static String PROPERTY_MODULE_VERSION = "moduleVersion"; + + @Test + public void extensionVersion_verifyModuleVersionInPropertiesFile_asEqual() { + Properties properties = loadProperties(GRADLE_PROPERTIES_PATH); + + assertNotNull(Identity.extensionVersion()); + assertFalse(Identity.extensionVersion().isEmpty()); + + String moduleVersion = properties.getProperty(PROPERTY_MODULE_VERSION); + assertNotNull(moduleVersion); + assertFalse(moduleVersion.isEmpty()); + + assertEquals(String.format("Expected version to match in gradle.properties (%s) and extensionVersion API (%s)", + moduleVersion, Identity.extensionVersion()), + moduleVersion, Identity.extensionVersion()); + } + + + private Properties loadProperties(final String filepath) { + Properties properties = new Properties(); + InputStream input = null; + + try { + input = new FileInputStream(filepath); + + properties.load(input); + } catch (IOException e) { + e.printStackTrace(); + } finally { + if (input != null) { + try { + input.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + } + + return properties; + } + +} diff --git a/code/settings.gradle b/code/settings.gradle new file mode 100644 index 00000000..246d7dac --- /dev/null +++ b/code/settings.gradle @@ -0,0 +1,3 @@ +include ':identityedge' +include ':app' +rootProject.name = "identityedge-sdk" \ No newline at end of file From 554e235c4eb92b69be5de73afaad7388f14d6ef0 Mon Sep 17 00:00:00 2001 From: Pravin Prakash Kumar Date: Fri, 26 Feb 2021 09:19:28 -0800 Subject: [PATCH 002/101] [AMSDK-11119] - Rename to IdentityEdge, Move to identity package + added Listeners (#2) * [AMSDK-11119] - Rename to IdentityEdge, Move to identity package + listeners --- .../IdentityEdgeTestApplication.java | 4 +- code/build.gradle | 13 +-- code/gradle.properties | 2 +- .../mobile/ExampleInstrumentedTest.java | 43 --------- .../identityedge/ExampleInstrumentedTest.java | 37 ++++++++ .../com/adobe/marketing/mobile/Identity.java | 45 ---------- .../marketing/mobile/IdentityConstants.java | 27 ------ .../marketing/mobile/IdentityExtension.java | 42 --------- .../mobile/identityedge/IdentityEdge.java | 44 +++++++++ .../identityedge/IdentityEdgeConstants.java | 36 ++++++++ .../identityedge/IdentityEdgeExtension.java | 89 +++++++++++++++++++ .../ListenerConfigurationResponseContent.java | 65 ++++++++++++++ ...ListenerGenericIdentityRequestContent.java | 66 ++++++++++++++ .../ListenerIdentityRequestIdentity.java | 66 ++++++++++++++ .../IdentityExtensionVersionTest.java | 36 ++++---- ...tenerConfigurationResponseContentTest.java | 71 +++++++++++++++ ...enerGenericIdentityRequestContentTest.java | 82 +++++++++++++++++ .../ListenerIdentityRequestIdentityTest.java | 82 +++++++++++++++++ 18 files changed, 657 insertions(+), 193 deletions(-) delete mode 100644 code/identityedge/src/androidTest/java/com/adobe/marketing/mobile/ExampleInstrumentedTest.java create mode 100644 code/identityedge/src/androidTest/java/com/adobe/marketing/mobile/identityedge/ExampleInstrumentedTest.java delete mode 100644 code/identityedge/src/main/java/com/adobe/marketing/mobile/Identity.java delete mode 100644 code/identityedge/src/main/java/com/adobe/marketing/mobile/IdentityConstants.java delete mode 100644 code/identityedge/src/main/java/com/adobe/marketing/mobile/IdentityExtension.java create mode 100644 code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdge.java create mode 100644 code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeConstants.java create mode 100644 code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeExtension.java create mode 100644 code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/ListenerConfigurationResponseContent.java create mode 100644 code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/ListenerGenericIdentityRequestContent.java create mode 100644 code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/ListenerIdentityRequestIdentity.java rename code/identityedge/src/test/java/com/adobe/marketing/mobile/{ => identityedge}/IdentityExtensionVersionTest.java (59%) create mode 100644 code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/ListenerConfigurationResponseContentTest.java create mode 100644 code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/ListenerGenericIdentityRequestContentTest.java create mode 100644 code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/ListenerIdentityRequestIdentityTest.java diff --git a/code/app/src/main/java/com/adobe/marketing/mobile/identityEdgeTestApp/IdentityEdgeTestApplication.java b/code/app/src/main/java/com/adobe/marketing/mobile/identityEdgeTestApp/IdentityEdgeTestApplication.java index def9f016..84a1290f 100644 --- a/code/app/src/main/java/com/adobe/marketing/mobile/identityEdgeTestApp/IdentityEdgeTestApplication.java +++ b/code/app/src/main/java/com/adobe/marketing/mobile/identityEdgeTestApp/IdentityEdgeTestApplication.java @@ -22,12 +22,12 @@ import com.adobe.marketing.mobile.AdobeCallback; import com.adobe.marketing.mobile.Assurance; -import com.adobe.marketing.mobile.Identity; import com.adobe.marketing.mobile.Edge; import com.adobe.marketing.mobile.InvalidInitException; import com.adobe.marketing.mobile.LoggingMode; import com.adobe.marketing.mobile.MobileCore; import com.adobe.marketing.mobile.Signal; +import com.adobe.marketing.mobile.identityedge.IdentityEdge; public class IdentityEdgeTestApplication extends Application { private static final String LOG_TAG = "IdentityEdgeTestApplication"; @@ -52,7 +52,7 @@ public void onCreate() { // register Adobe core extensions try { Signal.registerExtension(); - Identity.registerExtension(); + IdentityEdge.registerExtension(); Edge.registerExtension(); Assurance.registerExtension(); } catch (InvalidInitException e) { diff --git a/code/build.gradle b/code/build.gradle index f4e2d426..eed97616 100644 --- a/code/build.gradle +++ b/code/build.gradle @@ -21,18 +21,7 @@ allprojects { google() jcenter() maven { - url artifactoryUrl - credentials { - username = artifactoryUserName - password = artifactoryPassword - } - } - maven { - url artifactorySnapshotUrl - credentials { - username = artifactoryUserName - password = artifactoryPassword - } + url "https://eaps.bintray.com/mobileservicesdk" } } diff --git a/code/gradle.properties b/code/gradle.properties index 615c1664..a7e35f41 100644 --- a/code/gradle.properties +++ b/code/gradle.properties @@ -15,7 +15,7 @@ mavenRepoDescription=Adobe Experience Platform Identity Edge extension for the A mavenUploadDryRunFlag=false # production versions for production build -mavenCoreVersion=1.5.7 +mavenCoreVersion=1.6.1-SNAPSHOT #artifactory variables artifactoryUrl=https://dummyurl/ diff --git a/code/identityedge/src/androidTest/java/com/adobe/marketing/mobile/ExampleInstrumentedTest.java b/code/identityedge/src/androidTest/java/com/adobe/marketing/mobile/ExampleInstrumentedTest.java deleted file mode 100644 index b27b692b..00000000 --- a/code/identityedge/src/androidTest/java/com/adobe/marketing/mobile/ExampleInstrumentedTest.java +++ /dev/null @@ -1,43 +0,0 @@ -/* ****************************************************************************** - * ADOBE CONFIDENTIAL - * ___________________ - * - * Copyright 2021 Adobe - * All Rights Reserved. - * - * NOTICE: All information contained herein is, and remains - * the property of Adobe and its suppliers, if any. The intellectual - * and technical concepts contained herein are proprietary to Adobe - * and its suppliers and are protected by all applicable intellectual - * property laws, including trade secret and copyright laws. - * Dissemination of this information or reproduction of this material - * is strictly forbidden unless prior written permission is obtained - * from Adobe. - ******************************************************************************/ - -package com.adobe.marketing.mobile; - -import android.content.Context; - -import androidx.test.platform.app.InstrumentationRegistry; -import androidx.test.ext.junit.runners.AndroidJUnit4; - -import org.junit.Test; -import org.junit.runner.RunWith; - -import static org.junit.Assert.*; - -/** - * Instrumented test, which will execute on an Android device. - * - * @see Testing documentation - */ -@RunWith(AndroidJUnit4.class) -public class ExampleInstrumentedTest { - @Test - public void useAppContext() { - // Context of the app under test. - Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); - assertEquals("com.adobe.marketing.mobile.identityedge.test", appContext.getPackageName()); - } -} \ No newline at end of file diff --git a/code/identityedge/src/androidTest/java/com/adobe/marketing/mobile/identityedge/ExampleInstrumentedTest.java b/code/identityedge/src/androidTest/java/com/adobe/marketing/mobile/identityedge/ExampleInstrumentedTest.java new file mode 100644 index 00000000..e56522ac --- /dev/null +++ b/code/identityedge/src/androidTest/java/com/adobe/marketing/mobile/identityedge/ExampleInstrumentedTest.java @@ -0,0 +1,37 @@ +/* + Copyright 2021 Adobe. All rights reserved. + This file is licensed to you under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. You may obtain a copy + of the License at http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software distributed under + the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + OF ANY KIND, either express or implied. See the License for the specific language + governing permissions and limitations under the License. +*/ + +package com.adobe.marketing.mobile.identityedge; + +import android.content.Context; + +import androidx.test.platform.app.InstrumentationRegistry; +import androidx.test.ext.junit.runners.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import static org.junit.Assert.*; + +/** + * Instrumented test, which will execute on an Android device. + * + * @see Testing documentation + */ +@RunWith(AndroidJUnit4.class) +public class ExampleInstrumentedTest { + @Test + public void useAppContext() { + // Context of the app under test. + Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); + assertEquals("com.adobe.marketing.mobile.identityedge.test", appContext.getPackageName()); + } +} \ No newline at end of file diff --git a/code/identityedge/src/main/java/com/adobe/marketing/mobile/Identity.java b/code/identityedge/src/main/java/com/adobe/marketing/mobile/Identity.java deleted file mode 100644 index d501e886..00000000 --- a/code/identityedge/src/main/java/com/adobe/marketing/mobile/Identity.java +++ /dev/null @@ -1,45 +0,0 @@ -/* ****************************************************************************** - * ADOBE CONFIDENTIAL - * ___________________ - * - * Copyright 2021 Adobe - * All Rights Reserved. - * - * NOTICE: All information contained herein is, and remains - * the property of Adobe and its suppliers, if any. The intellectual - * and technical concepts contained herein are proprietary to Adobe - * and its suppliers and are protected by all applicable intellectual - * property laws, including trade secret and copyright laws. - * Dissemination of this information or reproduction of this material - * is strictly forbidden unless prior written permission is obtained - * from Adobe. - ******************************************************************************/ - -package com.adobe.marketing.mobile; - -public class Identity { - private static final String LOG_TAG = "Identity"; - - private Identity() {} - - /** - * Returns the version of the {@code Identity} extension - * @return The version as {@code String} - */ - public static String extensionVersion() { - return IdentityConstants.EXTENSION_VERSION; - } - - /** - * Registers the extension with the Mobile SDK. This method should be called only once in your application class. - */ - public static void registerExtension() { - MobileCore.registerExtension(IdentityExtension.class, new ExtensionErrorCallback() { - @Override - public void error(ExtensionError extensionError) { - MobileCore.log(LoggingMode.ERROR, LOG_TAG, - "There was an error registering the Identity Edge extension: " + extensionError.getErrorName()); - } - }); - } -} diff --git a/code/identityedge/src/main/java/com/adobe/marketing/mobile/IdentityConstants.java b/code/identityedge/src/main/java/com/adobe/marketing/mobile/IdentityConstants.java deleted file mode 100644 index 36a1bdf0..00000000 --- a/code/identityedge/src/main/java/com/adobe/marketing/mobile/IdentityConstants.java +++ /dev/null @@ -1,27 +0,0 @@ -/* ****************************************************************************** - * ADOBE CONFIDENTIAL - * ___________________ - * - * Copyright 2021 Adobe - * All Rights Reserved. - * - * NOTICE: All information contained herein is, and remains - * the property of Adobe and its suppliers, if any. The intellectual - * and technical concepts contained herein are proprietary to Adobe - * and its suppliers and are protected by all applicable intellectual - * property laws, including trade secret and copyright laws. - * Dissemination of this information or reproduction of this material - * is strictly forbidden unless prior written permission is obtained - * from Adobe. - ******************************************************************************/ - -package com.adobe.marketing.mobile; - -class IdentityConstants { - - static final String EXTENSION_NAME = "com.adobe.module.identity"; - static final String EXTENSION_VERSION = "1.0.0-alpha-1"; - - - private IdentityConstants() {} -} diff --git a/code/identityedge/src/main/java/com/adobe/marketing/mobile/IdentityExtension.java b/code/identityedge/src/main/java/com/adobe/marketing/mobile/IdentityExtension.java deleted file mode 100644 index 65cc251f..00000000 --- a/code/identityedge/src/main/java/com/adobe/marketing/mobile/IdentityExtension.java +++ /dev/null @@ -1,42 +0,0 @@ -/* ****************************************************************************** - * ADOBE CONFIDENTIAL - * ___________________ - * - * Copyright 2021 Adobe - * All Rights Reserved. - * - * NOTICE: All information contained herein is, and remains - * the property of Adobe and its suppliers, if any. The intellectual - * and technical concepts contained herein are proprietary to Adobe - * and its suppliers and are protected by all applicable intellectual - * property laws, including trade secret and copyright laws. - * Dissemination of this information or reproduction of this material - * is strictly forbidden unless prior written permission is obtained - * from Adobe. - ******************************************************************************/ - -package com.adobe.marketing.mobile; - -class IdentityExtension extends Extension { - protected IdentityExtension(ExtensionApi extensionApi) { - super(extensionApi); - } - - /** - * Required override. Each extension must have a unique name within the application. - * @return unique name of this extension - */ - @Override - protected String getName() { - return IdentityConstants.EXTENSION_NAME; - } - - /** - * Optional override. - * @return the version of this extension - */ - @Override - protected String getVersion() { - return IdentityConstants.EXTENSION_VERSION; - } -} diff --git a/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdge.java b/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdge.java new file mode 100644 index 00000000..5804bea9 --- /dev/null +++ b/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdge.java @@ -0,0 +1,44 @@ +/* + Copyright 2021 Adobe. All rights reserved. + This file is licensed to you under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. You may obtain a copy + of the License at http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software distributed under + the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + OF ANY KIND, either express or implied. See the License for the specific language + governing permissions and limitations under the License. +*/ + +package com.adobe.marketing.mobile.identityedge; + +import com.adobe.marketing.mobile.ExtensionError; +import com.adobe.marketing.mobile.ExtensionErrorCallback; +import com.adobe.marketing.mobile.LoggingMode; +import com.adobe.marketing.mobile.MobileCore; + +public class IdentityEdge { + private static final String LOG_TAG = "IdentityEdge"; + + private IdentityEdge() {} + + /** + * Returns the version of the {@link IdentityEdge} extension + * @return The version as {@code String} + */ + public static String extensionVersion() { + return IdentityEdgeConstants.EXTENSION_VERSION; + } + + /** + * Registers the extension with the Mobile SDK. This method should be called only once in your application class. + */ + public static void registerExtension() { + MobileCore.registerExtension(IdentityEdgeExtension.class, new ExtensionErrorCallback() { + @Override + public void error(ExtensionError extensionError) { + MobileCore.log(LoggingMode.ERROR, LOG_TAG, + "There was an error registering the Identity Edge extension: " + extensionError.getErrorName()); + } + }); + } +} diff --git a/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeConstants.java b/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeConstants.java new file mode 100644 index 00000000..3587df61 --- /dev/null +++ b/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeConstants.java @@ -0,0 +1,36 @@ +/* + Copyright 2021 Adobe. All rights reserved. + This file is licensed to you under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. You may obtain a copy + of the License at http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software distributed under + the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + OF ANY KIND, either express or implied. See the License for the specific language + governing permissions and limitations under the License. +*/ + +package com.adobe.marketing.mobile.identityedge; + +class IdentityEdgeConstants { + + static final String LOG_TAG = "IdentityEdge"; + static final String EXTENSION_NAME = "com.adobe.identityedge"; + static final String EXTENSION_VERSION = "1.0.0-alpha-1"; + + + final class EventSource { + static final String REQUEST_IDENTITY = "com.adobe.eventSource.requestIdentity"; + static final String REQUEST_CONTENT = "com.adobe.eventSource.requestContent"; + static final String RESPONSE_CONTENT = "com.adobe.eventSource.responseContent"; + private EventSource() { } + } + + final class EventType { + static final String GENERIC_IDENTITY = "com.adobe.eventType.generic.identity"; + static final String CONFIGURATION = "com.adobe.eventType.configuration"; + static final String IDENTITY = "com.adobe.eventType.identity"; + private EventType() { } + } + + private IdentityEdgeConstants() {} +} diff --git a/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeExtension.java b/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeExtension.java new file mode 100644 index 00000000..fb929666 --- /dev/null +++ b/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeExtension.java @@ -0,0 +1,89 @@ +/* + Copyright 2021 Adobe. All rights reserved. + This file is licensed to you under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. You may obtain a copy + of the License at http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software distributed under + the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + OF ANY KIND, either express or implied. See the License for the specific language + governing permissions and limitations under the License. +*/ + +package com.adobe.marketing.mobile.identityedge; + +import com.adobe.marketing.mobile.Event; +import com.adobe.marketing.mobile.Extension; +import com.adobe.marketing.mobile.ExtensionApi; +import com.adobe.marketing.mobile.ExtensionError; +import com.adobe.marketing.mobile.ExtensionErrorCallback; +import com.adobe.marketing.mobile.LoggingMode; +import com.adobe.marketing.mobile.MobileCore; + + +class IdentityEdgeExtension extends Extension { + + /** + * Constructor. + * + *

+ * Called during the Identity extension's registration. + * The following listeners are registered during this extension's registration. + *

    + *
  • Listener {@link ListenerConfigurationResponseContent} to listen for event with eventType {@link IdentityEdgeConstants.EventType#CONFIGURATION} + * and EventSource {@link IdentityEdgeConstants.EventSource#RESPONSE_CONTENT}
  • + *
  • Listener {@link ListenerIdentityRequestIdentity} to listen for event with eventType {@link IdentityEdgeConstants.EventType#IDENTITY} + * and EventSource {@link IdentityEdgeConstants.EventSource#REQUEST_IDENTITY}
  • + *
  • Listener {@link ListenerGenericIdentityRequestContent} to listen for event with eventType {@link IdentityEdgeConstants.EventType#GENERIC_IDENTITY} + * * and EventSource {@link IdentityEdgeConstants.EventSource#REQUEST_CONTENT}
  • + *
+ *

+ * Thread : Background thread created by MobileCore + * + * @param extensionApi {@link ExtensionApi} instance + */ + protected IdentityEdgeExtension(ExtensionApi extensionApi) { + super(extensionApi); + ExtensionErrorCallback listenerErrorCallback = new ExtensionErrorCallback() { + @Override + public void error(final ExtensionError extensionError) { + MobileCore.log(LoggingMode.ERROR, IdentityEdgeConstants.LOG_TAG, String.format("Failed to register listener, error: %s", + extensionError.getErrorName())); + } + }; + + extensionApi.registerEventListener(IdentityEdgeConstants.EventType.CONFIGURATION, IdentityEdgeConstants.EventSource.RESPONSE_CONTENT, ListenerConfigurationResponseContent.class, listenerErrorCallback); + extensionApi.registerEventListener(IdentityEdgeConstants.EventType.IDENTITY, IdentityEdgeConstants.EventSource.REQUEST_IDENTITY, ListenerIdentityRequestIdentity.class, listenerErrorCallback); + extensionApi.registerEventListener(IdentityEdgeConstants.EventType.GENERIC_IDENTITY, IdentityEdgeConstants.EventSource.REQUEST_CONTENT, ListenerGenericIdentityRequestContent.class, listenerErrorCallback); + } + + /** + * Required override. Each extension must have a unique name within the application. + * @return unique name of this extension + */ + @Override + protected String getName() { + return IdentityEdgeConstants.EXTENSION_NAME; + } + + /** + * Optional override. + * @return the version of this extension + */ + @Override + protected String getVersion() { + return IdentityEdgeConstants.EXTENSION_VERSION; + } + + + void handleGenericIdentityRequest(final Event event) { + // TODO + } + + void handleIdentityRequest(final Event event) { + // TODO + } + + void handleConfigurationResponse(final Event event) { + // TODO + } +} diff --git a/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/ListenerConfigurationResponseContent.java b/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/ListenerConfigurationResponseContent.java new file mode 100644 index 00000000..bc696280 --- /dev/null +++ b/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/ListenerConfigurationResponseContent.java @@ -0,0 +1,65 @@ +/* + Copyright 2021 Adobe. All rights reserved. + This file is licensed to you under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. You may obtain a copy + of the License at http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software distributed under + the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + OF ANY KIND, either express or implied. See the License for the specific language + governing permissions and limitations under the License. +*/ + +package com.adobe.marketing.mobile.identityedge; + +import com.adobe.marketing.mobile.Event; +import com.adobe.marketing.mobile.ExtensionApi; +import com.adobe.marketing.mobile.ExtensionListener; +import com.adobe.marketing.mobile.LoggingMode; +import com.adobe.marketing.mobile.MobileCore; + +class ListenerConfigurationResponseContent extends ExtensionListener { + + /** + * Constructor. + * + * @param extensionApi an instance of {@link ExtensionApi} + * @param type the {@link String} eventType this listener is registered to handle + * @param source the {@link String} eventSource this listener is registered to handle + */ + ListenerConfigurationResponseContent(final ExtensionApi extensionApi, final String type, final String source) { + super(extensionApi, type, source); + } + + /** + * Method that gets called when event with event type {@link IdentityEdgeConstants.EventType#CONFIGURATION} + * and with event source {@link IdentityEdgeConstants.EventSource#RESPONSE_CONTENT} is dispatched through eventHub. + * + * @param event the configuration response content {@link Event} to be processed + */ + @Override + public void hear(final Event event) { + if (event == null || event.getEventData() == null) { + MobileCore.log(LoggingMode.DEBUG, IdentityEdgeConstants.LOG_TAG, "Event or Event data is null. Ignoring the event listened by ListenerConfigurationResponseContent"); + return; + } + + final IdentityEdgeExtension parentExtension = getIdentityEdgeExtension(); + + if (parentExtension == null) { + MobileCore.log(LoggingMode.DEBUG, IdentityEdgeConstants.LOG_TAG, + "The parent extension, associated with the ListenerConfigurationResponseContent is null, ignoring the configuration response content event."); + return; + } + + parentExtension.handleConfigurationResponse(event); + } + + /** + * Returns the parent extension associated with the listener. + * + * @return a {@link IdentityEdgeExtension} object registered with the eventHub + */ + IdentityEdgeExtension getIdentityEdgeExtension() { + return (IdentityEdgeExtension) getParentExtension(); + } +} diff --git a/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/ListenerGenericIdentityRequestContent.java b/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/ListenerGenericIdentityRequestContent.java new file mode 100644 index 00000000..f1bcbe1c --- /dev/null +++ b/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/ListenerGenericIdentityRequestContent.java @@ -0,0 +1,66 @@ +/* + Copyright 2021 Adobe. All rights reserved. + This file is licensed to you under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. You may obtain a copy + of the License at http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software distributed under + the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + OF ANY KIND, either express or implied. See the License for the specific language + governing permissions and limitations under the License. +*/ + +package com.adobe.marketing.mobile.identityedge; + +import com.adobe.marketing.mobile.Event; +import com.adobe.marketing.mobile.ExtensionApi; +import com.adobe.marketing.mobile.ExtensionListener; +import com.adobe.marketing.mobile.LoggingMode; +import com.adobe.marketing.mobile.MobileCore; + +class ListenerGenericIdentityRequestContent extends ExtensionListener { + + /** + * Constructor. + * + * @param extensionApi an instance of {@link ExtensionApi} + * @param type the {@link String} eventType this listener is registered to handle + * @param source the {@link String} eventSource this listener is registered to handle + */ + ListenerGenericIdentityRequestContent(final ExtensionApi extensionApi, final String type, final String source) { + super(extensionApi, type, source); + } + + /** + * Method that gets called when event with event type {@link IdentityEdgeConstants.EventType#GENERIC_IDENTITY} + * and with event source {@link IdentityEdgeConstants.EventSource#REQUEST_CONTENT} is dispatched through eventHub. + * + * @param event the generic identity request content {@link Event} to be processed + */ + @Override + public void hear(final Event event) { + if (event == null || event.getEventData() == null) { + MobileCore.log(LoggingMode.DEBUG, IdentityEdgeConstants.LOG_TAG, "Event or Event data is null. Ignoring the event listened by ListenerGenericIdentityRequestContent"); + return; + } + + final IdentityEdgeExtension parentExtension = getIdentityEdgeExtension(); + + if (parentExtension == null) { + MobileCore.log(LoggingMode.DEBUG, IdentityEdgeConstants.LOG_TAG, + "The parent extension, associated with the ListenerGenericIdentityRequestContent is null, ignoring the generic identity request content event."); + return; + } + + parentExtension.handleGenericIdentityRequest(event); + } + + + /** + * Returns the parent extension associated with the listener. + * + * @return a {@link IdentityEdgeExtension} object registered with the eventHub + */ + IdentityEdgeExtension getIdentityEdgeExtension() { + return (IdentityEdgeExtension) getParentExtension(); + } +} diff --git a/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/ListenerIdentityRequestIdentity.java b/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/ListenerIdentityRequestIdentity.java new file mode 100644 index 00000000..7addca0e --- /dev/null +++ b/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/ListenerIdentityRequestIdentity.java @@ -0,0 +1,66 @@ +/* + Copyright 2021 Adobe. All rights reserved. + This file is licensed to you under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. You may obtain a copy + of the License at http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software distributed under + the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + OF ANY KIND, either express or implied. See the License for the specific language + governing permissions and limitations under the License. +*/ + +package com.adobe.marketing.mobile.identityedge; + +import com.adobe.marketing.mobile.Event; +import com.adobe.marketing.mobile.ExtensionApi; +import com.adobe.marketing.mobile.ExtensionListener; +import com.adobe.marketing.mobile.LoggingMode; +import com.adobe.marketing.mobile.MobileCore; + +class ListenerIdentityRequestIdentity extends ExtensionListener { + + /** + * Constructor. + * + * @param extensionApi an instance of {@link ExtensionApi} + * @param type the {@link String} eventType this listener is registered to handle + * @param source the {@link String} eventSource this listener is registered to handle + */ + ListenerIdentityRequestIdentity(final ExtensionApi extensionApi, final String type, final String source) { + super(extensionApi, type, source); + } + + + /** + * Method that gets called when event with event type {@link IdentityEdgeConstants.EventType#GENERIC_IDENTITY} + * and with event source {@link IdentityEdgeConstants.EventSource#REQUEST_CONTENT} is dispatched through eventHub. + * + * @param event the request identity {@link Event} to be processed + */ + @Override + public void hear(final Event event) { + if (event == null || event.getEventData() == null) { + MobileCore.log(LoggingMode.DEBUG, IdentityEdgeConstants.LOG_TAG, "Event or Event data is null. Ignoring the event listened by ListenerIdentityRequestIdentity"); + return; + } + + final IdentityEdgeExtension parentExtension = getIdentityEdgeExtension(); + + if (parentExtension == null) { + MobileCore.log(LoggingMode.DEBUG, IdentityEdgeConstants.LOG_TAG, + "The parent extension, associated with the ListenerIdentityRequestIdentity is null, ignoring request identity event."); + return; + } + + parentExtension.handleIdentityRequest(event); + } + + /** + * Returns the parent extension associated with the listener. + * + * @return a {@link IdentityEdgeExtension} object registered with the eventHub + */ + IdentityEdgeExtension getIdentityEdgeExtension() { + return (IdentityEdgeExtension) getParentExtension(); + } +} diff --git a/code/identityedge/src/test/java/com/adobe/marketing/mobile/IdentityExtensionVersionTest.java b/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/IdentityExtensionVersionTest.java similarity index 59% rename from code/identityedge/src/test/java/com/adobe/marketing/mobile/IdentityExtensionVersionTest.java rename to code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/IdentityExtensionVersionTest.java index be5d6947..cc9ce344 100644 --- a/code/identityedge/src/test/java/com/adobe/marketing/mobile/IdentityExtensionVersionTest.java +++ b/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/IdentityExtensionVersionTest.java @@ -1,21 +1,15 @@ -/* ****************************************************************************** - * ADOBE CONFIDENTIAL - * ___________________ - * - * Copyright 2021 Adobe - * All Rights Reserved. - * - * NOTICE: All information contained herein is, and remains - * the property of Adobe and its suppliers, if any. The intellectual - * and technical concepts contained herein are proprietary to Adobe - * and its suppliers and are protected by all applicable intellectual - * property laws, including trade secret and copyright laws. - * Dissemination of this information or reproduction of this material - * is strictly forbidden unless prior written permission is obtained - * from Adobe. - ******************************************************************************/ +/* + Copyright 2021 Adobe. All rights reserved. + This file is licensed to you under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. You may obtain a copy + of the License at http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software distributed under + the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + OF ANY KIND, either express or implied. See the License for the specific language + governing permissions and limitations under the License. +*/ -package com.adobe.marketing.mobile; +package com.adobe.marketing.mobile.identityedge; import org.junit.Test; @@ -36,16 +30,16 @@ public class IdentityExtensionVersionTest { public void extensionVersion_verifyModuleVersionInPropertiesFile_asEqual() { Properties properties = loadProperties(GRADLE_PROPERTIES_PATH); - assertNotNull(Identity.extensionVersion()); - assertFalse(Identity.extensionVersion().isEmpty()); + assertNotNull(IdentityEdge.extensionVersion()); + assertFalse(IdentityEdge.extensionVersion().isEmpty()); String moduleVersion = properties.getProperty(PROPERTY_MODULE_VERSION); assertNotNull(moduleVersion); assertFalse(moduleVersion.isEmpty()); assertEquals(String.format("Expected version to match in gradle.properties (%s) and extensionVersion API (%s)", - moduleVersion, Identity.extensionVersion()), - moduleVersion, Identity.extensionVersion()); + moduleVersion, IdentityEdge.extensionVersion()), + moduleVersion, IdentityEdge.extensionVersion()); } diff --git a/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/ListenerConfigurationResponseContentTest.java b/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/ListenerConfigurationResponseContentTest.java new file mode 100644 index 00000000..8760ee26 --- /dev/null +++ b/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/ListenerConfigurationResponseContentTest.java @@ -0,0 +1,71 @@ +package com.adobe.marketing.mobile.identityedge; + +import com.adobe.marketing.mobile.Event; +import com.adobe.marketing.mobile.MobileCore; + +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.Mockito; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +public class ListenerConfigurationResponseContentTest { + + @Mock + private IdentityEdgeExtension mockIdentityEdgeExtension; + + private ListenerConfigurationResponseContent listener; + + @Before + public void setup() { + mockIdentityEdgeExtension = Mockito.mock(IdentityEdgeExtension.class); + MobileCore.start(null); + listener = spy(new ListenerConfigurationResponseContent(null, IdentityEdgeConstants.EventType.CONFIGURATION, IdentityEdgeConstants.EventSource.RESPONSE_CONTENT)); + } + + @Test + public void testHear() { + // setup + Event event = new Event.Builder("Configuration response event", IdentityEdgeConstants.EventType.CONFIGURATION, + IdentityEdgeConstants.EventSource.RESPONSE_CONTENT).build(); + doReturn(mockIdentityEdgeExtension).when(listener).getIdentityEdgeExtension(); + + // test + listener.hear(event); + + // verify + verify(mockIdentityEdgeExtension, times(1)).handleConfigurationResponse(event); + } + + @Test + public void testHear_WhenParentExtensionNull() { + // setup + Event event = new Event.Builder("Configuration response event", IdentityEdgeConstants.EventType.CONFIGURATION, + IdentityEdgeConstants.EventSource.RESPONSE_CONTENT).build(); + doReturn(null).when(listener).getIdentityEdgeExtension(); + + // test + listener.hear(event); + + // verify + verify(mockIdentityEdgeExtension, times(0)).handleConfigurationResponse(any(Event.class)); + } + + @Test + public void testHear_WhenEventNull() { + // setup + doReturn(null).when(listener).getIdentityEdgeExtension(); + doReturn(mockIdentityEdgeExtension).when(listener).getIdentityEdgeExtension(); + + // test + listener.hear(null); + + // verify + verify(mockIdentityEdgeExtension, times(0)).handleConfigurationResponse(any(Event.class)); + } +} diff --git a/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/ListenerGenericIdentityRequestContentTest.java b/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/ListenerGenericIdentityRequestContentTest.java new file mode 100644 index 00000000..a607a779 --- /dev/null +++ b/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/ListenerGenericIdentityRequestContentTest.java @@ -0,0 +1,82 @@ +/* + Copyright 2021 Adobe. All rights reserved. + This file is licensed to you under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. You may obtain a copy + of the License at http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software distributed under + the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + OF ANY KIND, either express or implied. See the License for the specific language + governing permissions and limitations under the License. +*/ + +package com.adobe.marketing.mobile.identityedge; + +import com.adobe.marketing.mobile.Event; +import com.adobe.marketing.mobile.MobileCore; + +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.Mockito; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +public class ListenerGenericIdentityRequestContentTest { + + @Mock + private IdentityEdgeExtension mockIdentityEdgeExtension; + + private ListenerGenericIdentityRequestContent listener; + + @Before + public void setup() { + mockIdentityEdgeExtension = Mockito.mock(IdentityEdgeExtension.class); + MobileCore.start(null); + listener = spy(new ListenerGenericIdentityRequestContent(null, IdentityEdgeConstants.EventType.GENERIC_IDENTITY, IdentityEdgeConstants.EventSource.REQUEST_CONTENT)); + } + + @Test + public void testHear() { + // setup + Event event = new Event.Builder("Generic Identity Request event", IdentityEdgeConstants.EventType.GENERIC_IDENTITY, + IdentityEdgeConstants.EventSource.REQUEST_CONTENT).build(); + doReturn(mockIdentityEdgeExtension).when(listener).getIdentityEdgeExtension(); + + // test + listener.hear(event); + + // verify + verify(mockIdentityEdgeExtension, times(1)).handleGenericIdentityRequest(event); + } + + @Test + public void testHear_WhenParentExtensionNull() { + // setup + Event event = new Event.Builder("Generic Identity Request event", IdentityEdgeConstants.EventType.GENERIC_IDENTITY, + IdentityEdgeConstants.EventSource.REQUEST_CONTENT).build(); + doReturn(null).when(listener).getIdentityEdgeExtension(); + + // test + listener.hear(event); + + // verify + verify(mockIdentityEdgeExtension, times(0)).handleGenericIdentityRequest(any(Event.class)); + } + + @Test + public void testHear_WhenEventNull() { + // setup + doReturn(null).when(listener).getIdentityEdgeExtension(); + doReturn(mockIdentityEdgeExtension).when(listener).getIdentityEdgeExtension(); + + // test + listener.hear(null); + + // verify + verify(mockIdentityEdgeExtension, times(0)).handleGenericIdentityRequest(any(Event.class)); + } +} diff --git a/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/ListenerIdentityRequestIdentityTest.java b/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/ListenerIdentityRequestIdentityTest.java new file mode 100644 index 00000000..5d89f73f --- /dev/null +++ b/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/ListenerIdentityRequestIdentityTest.java @@ -0,0 +1,82 @@ +/* + Copyright 2021 Adobe. All rights reserved. + This file is licensed to you under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. You may obtain a copy + of the License at http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software distributed under + the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + OF ANY KIND, either express or implied. See the License for the specific language + governing permissions and limitations under the License. +*/ + +package com.adobe.marketing.mobile.identityedge; + +import com.adobe.marketing.mobile.Event; +import com.adobe.marketing.mobile.MobileCore; + +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.Mockito; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +public class ListenerIdentityRequestIdentityTest { + + @Mock + private IdentityEdgeExtension mockIdentityEdgeExtension; + + private ListenerIdentityRequestIdentity listener; + + @Before + public void setup() { + mockIdentityEdgeExtension = Mockito.mock(IdentityEdgeExtension.class); + MobileCore.start(null); + listener = spy(new ListenerIdentityRequestIdentity(null, IdentityEdgeConstants.EventType.IDENTITY, IdentityEdgeConstants.EventSource.REQUEST_IDENTITY)); + } + + @Test + public void testHear() { + // setup + Event event = new Event.Builder("Request Identity", IdentityEdgeConstants.EventType.IDENTITY, + IdentityEdgeConstants.EventSource.REQUEST_IDENTITY).build(); + doReturn(mockIdentityEdgeExtension).when(listener).getIdentityEdgeExtension(); + + // test + listener.hear(event); + + // verify + verify(mockIdentityEdgeExtension, times(1)).handleIdentityRequest(event); + } + + @Test + public void testHear_WhenParentExtensionNull() { + // setup + Event event = new Event.Builder("Request Identity", IdentityEdgeConstants.EventType.IDENTITY, + IdentityEdgeConstants.EventSource.REQUEST_IDENTITY).build(); + doReturn(null).when(listener).getIdentityEdgeExtension(); + + // test + listener.hear(event); + + // verify + verify(mockIdentityEdgeExtension, times(0)).handleIdentityRequest(any(Event.class)); + } + + @Test + public void testHear_WhenEventNull() { + // setup + doReturn(null).when(listener).getIdentityEdgeExtension(); + doReturn(mockIdentityEdgeExtension).when(listener).getIdentityEdgeExtension(); + + // test + listener.hear(null); + + // verify + verify(mockIdentityEdgeExtension, times(0)).handleIdentityRequest(any(Event.class)); + } +} From d9a24990bdc34540040260d38db3a663bf88d7e7 Mon Sep 17 00:00:00 2001 From: Kevin Lind <40409666+kevinlind@users.noreply.github.com> Date: Tue, 2 Mar 2021 11:19:27 -0800 Subject: [PATCH 003/101] Add functional testing to CI (#3) * Add circleci job to run functional tests in an emulator * Fix yaml formatting in circleci config * rename circleci job to build-and-unit-test * Call Make targets when running functional tests --- .circleci/config.yml | 31 +++++++++++++++++++++++++++---- 1 file changed, 27 insertions(+), 4 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 0eceb194..85ceb96c 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -1,11 +1,14 @@ version: 2.1 - +orbs: + android: circleci/android@1.0 + # Workflows orchestrate a set of jobs to be run; workflows: version: 2 build-test-deploy: jobs: - - build-and-test + - build-and-unit-test + - functional-test - hold: filters: branches: @@ -13,13 +16,14 @@ workflows: - main type: approval requires: - - build-and-test + - build-and-unit-test + - functional-test - publish: requires: - hold jobs: - build-and-test: + build-and-unit-test: working_directory: ~/code docker: - image: circleci/android:api-29 @@ -49,6 +53,25 @@ jobs: - store_test_results: path: ci/unit-test/build/test-results + functional-test: + executor: + name: android/android-machine + resource-class: large + steps: + - checkout + - android/start-emulator-and-run-tests: + # It should match the name seen in the "sdkmanager --list" output + system-image: system-images;android-29;default;x86 + # The command to be run, while waiting for emulator startup + post-emulator-launch-assemble-command: make ci-build + # The test command + test-command: make ci-functional-test + + - store_artifacts: + path: ci/functional-test/build/reports + - store_test_results: + path: ci/functional-test/build/outputs/androidTest-results + publish: docker: - image: circleci/android:api-29 From 0caa25d7d588dd6a913555ba3b6151107a1d3b11 Mon Sep 17 00:00:00 2001 From: Nick Porter <43650450+nporter-adbe@users.noreply.github.com> Date: Fri, 5 Mar 2021 19:03:52 -0700 Subject: [PATCH 004/101] [AMSDK-11019] ECID handling in Identity Edge (#4) * Add ECID and tests * Add IdentityMap from Edge extension * Create IdentityEdgeProperties and tests * Add utils copied from Edge * Create storage service * Add required constants * Create IdentityEdgeState and tests * Rename extension version test class * Add doc comment * Doc comment for storage service * Remove configuration handling * Fix IdentityEdgeState log tag * update event names * Make ECID parameter final * Update class comment for IdentityEdgeProperties * Make getECID protected * getIdentityProperties -> getIdentityEdgeProperties * Remove config listener and remove unused imports * Make IdentityEdgeState methods protected * Add test for ECID(final String ecidString) * Remove config listener * Fix complier issue * Remove test to be added in a following PR * persist data in xdm format * Use extension name as datastore name * Update listener doc comment * Update event type in doc comment * Save after generating ECID and add assertion in test * Add null check in IdentityMap.fromData * Add null check in IdentityEdgeProperties.readECIDFromIdentityMap * Improve doc comments, logs, and handle empty/null ECID string * Add tests for storage service --- .../marketing/mobile/identityedge/ECID.java | 58 ++++ .../identityedge/IdentityEdgeConstants.java | 31 +- .../identityedge/IdentityEdgeExtension.java | 10 +- .../identityedge/IdentityEdgeProperties.java | 106 +++++++ .../identityedge/IdentityEdgeState.java | 75 +++++ .../IdentityEdgeStorageService.java | 114 +++++++ .../mobile/identityedge/IdentityMap.java | 180 +++++++++++ .../ListenerConfigurationResponseContent.java | 65 ---- .../marketing/mobile/identityedge/Utils.java | 279 ++++++++++++++++++ .../mobile/identityedge/ECIDTests.java | 82 +++++ ... => IdentityEdgeExtensionVersionTest.java} | 2 +- .../IdentityEdgePropertiesTests.java | 142 +++++++++ .../identityedge/IdentityEdgeStateTests.java | 98 ++++++ .../IdentityEdgeStorageServiceTests.java | 164 ++++++++++ ...tenerConfigurationResponseContentTest.java | 71 ----- .../mobile/identityedge/UtilsTests.java | 164 ++++++++++ 16 files changed, 1495 insertions(+), 146 deletions(-) create mode 100644 code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/ECID.java create mode 100644 code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeProperties.java create mode 100644 code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeState.java create mode 100644 code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeStorageService.java create mode 100644 code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityMap.java delete mode 100644 code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/ListenerConfigurationResponseContent.java create mode 100644 code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/Utils.java create mode 100644 code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/ECIDTests.java rename code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/{IdentityExtensionVersionTest.java => IdentityEdgeExtensionVersionTest.java} (98%) create mode 100644 code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/IdentityEdgePropertiesTests.java create mode 100644 code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeStateTests.java create mode 100644 code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeStorageServiceTests.java delete mode 100644 code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/ListenerConfigurationResponseContentTest.java create mode 100644 code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/UtilsTests.java diff --git a/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/ECID.java b/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/ECID.java new file mode 100644 index 00000000..15ab476b --- /dev/null +++ b/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/ECID.java @@ -0,0 +1,58 @@ +/* + Copyright 2021 Adobe. All rights reserved. + This file is licensed to you under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. You may obtain a copy + of the License at http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software distributed under + the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + OF ANY KIND, either express or implied. See the License for the specific language + governing permissions and limitations under the License. +*/ + +package com.adobe.marketing.mobile.identityedge; + +import com.adobe.marketing.mobile.LoggingMode; +import com.adobe.marketing.mobile.MobileCore; + +import java.util.Locale; +import java.util.UUID; + +/** + * This class represents an ECID + */ +class ECID { + private final String ecidString; + + /** + * Initializes and generates a new ECID + */ + ECID() { + final UUID uuid = UUID.randomUUID(); + final long most = uuid.getMostSignificantBits(); + final long least = uuid.getLeastSignificantBits(); + // return formatted string, flip negatives if they're set. + ecidString = String.format(Locale.US, "%019d%019d", most < 0 ? -most : most, least < 0 ? -least : least); + } + + /** + * Creates a new ECID with the passed in string + * @param ecidString a valid (38-digit UUID) ECID string representation, if null or empty a new ECID will be generated + */ + ECID(final String ecidString) { + if (Utils.isNullOrEmpty(ecidString)) { + MobileCore.log(LoggingMode.DEBUG, IdentityEdgeConstants.LOG_TAG, "Creating an ECID with null or empty ecidString is not allowed, generating a new ECID."); + this.ecidString = new ECID().getEcidString(); + return; + } + + this.ecidString = ecidString; + } + + /** + * Retrieves the string representation of the ECID + * @return string representation of the ECID + */ + public String getEcidString() { + return ecidString; + } +} diff --git a/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeConstants.java b/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeConstants.java index 3587df61..78c06085 100644 --- a/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeConstants.java +++ b/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeConstants.java @@ -22,15 +22,44 @@ final class EventSource { static final String REQUEST_IDENTITY = "com.adobe.eventSource.requestIdentity"; static final String REQUEST_CONTENT = "com.adobe.eventSource.requestContent"; static final String RESPONSE_CONTENT = "com.adobe.eventSource.responseContent"; + static final String RESPONSE_IDENTITY = "com.adobe.eventSource.responseIdentity"; private EventSource() { } } final class EventType { static final String GENERIC_IDENTITY = "com.adobe.eventType.generic.identity"; - static final String CONFIGURATION = "com.adobe.eventType.configuration"; + static final String IDENTITY_EDGE = "com.adobe.eventType.identityEdge"; static final String IDENTITY = "com.adobe.eventType.identity"; private EventType() { } } + final class EventNames { + static final String IDENTITY_REQUEST_IDENTITY_ECID = "Identity Edge Request ECID"; + static final String IDENTITY_RESPONSE_CONTENT_ONE_TIME = "Identity Edge Response Content One Time"; + private EventNames() { } + } + + final class EventDataKeys { + static final String VISITOR_ID_ECID = "mid"; + private EventDataKeys() { } + } + + final class Namespaces { + static final String ECID = "ECID"; + private Namespaces() { } + } + + final class XDMKeys { + static final String IDENTITY_MAP = "identityMap"; + static final String ID = "id"; + private XDMKeys() { } + } + + final class DataStoreKey { + static final String DATASTORE_NAME = EXTENSION_NAME; + static final String IDENTITY_PROPERTIES = "identity.properties"; + private DataStoreKey() { } + } + private IdentityEdgeConstants() {} } diff --git a/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeExtension.java b/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeExtension.java index fb929666..d2475dd8 100644 --- a/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeExtension.java +++ b/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeExtension.java @@ -29,9 +29,7 @@ class IdentityEdgeExtension extends Extension { * Called during the Identity extension's registration. * The following listeners are registered during this extension's registration. *

    - *
  • Listener {@link ListenerConfigurationResponseContent} to listen for event with eventType {@link IdentityEdgeConstants.EventType#CONFIGURATION} - * and EventSource {@link IdentityEdgeConstants.EventSource#RESPONSE_CONTENT}
  • - *
  • Listener {@link ListenerIdentityRequestIdentity} to listen for event with eventType {@link IdentityEdgeConstants.EventType#IDENTITY} + *
  • Listener {@link ListenerIdentityRequestIdentity} to listen for event with eventType {@link IdentityEdgeConstants.EventType#IDENTITY_EDGE} * and EventSource {@link IdentityEdgeConstants.EventSource#REQUEST_IDENTITY}
  • *
  • Listener {@link ListenerGenericIdentityRequestContent} to listen for event with eventType {@link IdentityEdgeConstants.EventType#GENERIC_IDENTITY} * * and EventSource {@link IdentityEdgeConstants.EventSource#REQUEST_CONTENT}
  • @@ -51,8 +49,7 @@ public void error(final ExtensionError extensionError) { } }; - extensionApi.registerEventListener(IdentityEdgeConstants.EventType.CONFIGURATION, IdentityEdgeConstants.EventSource.RESPONSE_CONTENT, ListenerConfigurationResponseContent.class, listenerErrorCallback); - extensionApi.registerEventListener(IdentityEdgeConstants.EventType.IDENTITY, IdentityEdgeConstants.EventSource.REQUEST_IDENTITY, ListenerIdentityRequestIdentity.class, listenerErrorCallback); + extensionApi.registerEventListener(IdentityEdgeConstants.EventType.IDENTITY_EDGE, IdentityEdgeConstants.EventSource.REQUEST_IDENTITY, ListenerIdentityRequestIdentity.class, listenerErrorCallback); extensionApi.registerEventListener(IdentityEdgeConstants.EventType.GENERIC_IDENTITY, IdentityEdgeConstants.EventSource.REQUEST_CONTENT, ListenerGenericIdentityRequestContent.class, listenerErrorCallback); } @@ -83,7 +80,4 @@ void handleIdentityRequest(final Event event) { // TODO } - void handleConfigurationResponse(final Event event) { - // TODO - } } diff --git a/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeProperties.java b/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeProperties.java new file mode 100644 index 00000000..cca57c29 --- /dev/null +++ b/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeProperties.java @@ -0,0 +1,106 @@ +/* + Copyright 2021 Adobe. All rights reserved. + This file is licensed to you under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. You may obtain a copy + of the License at http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software distributed under + the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + OF ANY KIND, either express or implied. See the License for the specific language + governing permissions and limitations under the License. +*/ + +package com.adobe.marketing.mobile.identityedge; + +import com.adobe.marketing.mobile.LoggingMode; +import com.adobe.marketing.mobile.MobileCore; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * Represents a type which contains instances variables for the Identity Edge extension + */ +class IdentityEdgeProperties { + + private static final String LOG_TAG = "IdentityEdgeProperties"; + + // The current Experience Cloud ID + private ECID ecid; + + IdentityEdgeProperties() { } + + /** + * Creates a identity edge properties instance based on the map + * @param xdmData a map representing an identity edge properties instance + */ + IdentityEdgeProperties(final Map xdmData) { + if (Utils.isNullOrEmpty(xdmData)) { + return; + } + + IdentityMap identityMap = IdentityMap.fromData(xdmData); + ecid = readECIDFromIdentityMap(identityMap); + } + + /** + * Sets the current {@link ECID} + * @param ecid the new {@link ECID} + */ + void setECID(final ECID ecid) { + this.ecid = ecid; + } + + /** + * Retrieves the current {@link ECID} + * @return current {@link ECID} + */ + ECID getECID() { + return ecid; + } + + /** + * Converts this into an event data representation in XDM format + * @param allowEmpty If this {@link IdentityEdgeProperties} contains no data, return a dictionary with a single {@link IdentityMap} key + * @return A dictionary representing this in XDM format + */ + Map toXDMData(final boolean allowEmpty) { + final Map map = new HashMap<>(); + final IdentityMap identityMap = new IdentityMap(); + + if (ecid != null) { + identityMap.addItem(IdentityEdgeConstants.Namespaces.ECID, ecid.getEcidString()); + } + + final Map>> dict = identityMap.toObjectMap(); + if (dict != null && (!dict.isEmpty() || allowEmpty)) { + map.put(IdentityEdgeConstants.XDMKeys.IDENTITY_MAP, dict); + } + + return map; + } + + /** + * Reads the ECID from an IdentityMap + * @param identityMap an IdentityMap + * @return ECID stored in the IdentityMap or null if not found + */ + static ECID readECIDFromIdentityMap(IdentityMap identityMap) { + if (identityMap == null) { return null; } + final List>ecidArr = identityMap.getIdentityItemForNamespace(IdentityEdgeConstants.Namespaces.ECID); + if (ecidArr == null) { return null; } + final Map ecidDict = ecidArr.get(0); + if (ecidDict == null) { return null; } + String ecidStr = null; + try { + ecidStr = (String) ecidDict.get(IdentityEdgeConstants.XDMKeys.ID); + } catch (ClassCastException e) { + MobileCore.log(LoggingMode.DEBUG, LOG_TAG, "Failed to create read ECID from IdentityMap"); + } + + if (ecidStr == null) { return null; } + return new ECID(ecidStr); + } + +} diff --git a/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeState.java b/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeState.java new file mode 100644 index 00000000..da062ce1 --- /dev/null +++ b/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeState.java @@ -0,0 +1,75 @@ +/* + Copyright 2021 Adobe. All rights reserved. + This file is licensed to you under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. You may obtain a copy + of the License at http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software distributed under + the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + OF ANY KIND, either express or implied. See the License for the specific language + governing permissions and limitations under the License. +*/ + +package com.adobe.marketing.mobile.identityedge; + +import com.adobe.marketing.mobile.Event; +import com.adobe.marketing.mobile.LoggingMode; +import com.adobe.marketing.mobile.MobileCore; +import com.adobe.marketing.mobile.MobilePrivacyStatus; + +import java.util.Map; + +/** + * Manages the business logic of the Identity Edge extension + */ +class IdentityEdgeState { + private String LOG_TAG = "IdentityEdgeState"; + private boolean hasBooted = false; + private IdentityEdgeProperties identityProperties; + + /** + * Creates a new {@link IdentityEdgeState} with the given {@link IdentityEdgeProperties} + * @param identityProperties identity edge properties + */ + IdentityEdgeState(final IdentityEdgeProperties identityProperties) { + this.identityProperties = identityProperties; + } + + /** + * @return The current {@link IdentityEdgeProperties} for this identity state + */ + IdentityEdgeProperties getIdentityEdgeProperties() { + return identityProperties; + } + + /** + * @return Returns true if IdentityEdge has booted, false otherwise + */ + boolean hasBooted() { + return hasBooted; + } + + /** + * Completes init for the Identity Edge extension. + * @return True if we should share state after bootup, false otherwise + */ + boolean bootupIfReady() { + if (hasBooted) { return true; } + // Load properties from local storage + identityProperties = IdentityEdgeStorageService.loadPropertiesFromPersistence(); + + if (identityProperties == null) { + identityProperties = new IdentityEdgeProperties(); + } + + // Generate new ECID on first launch + if (identityProperties.getECID() == null) { + identityProperties.setECID(new ECID()); + IdentityEdgeStorageService.savePropertiesToPersistence(identityProperties); + } + + hasBooted = true; + MobileCore.log(LoggingMode.DEBUG, LOG_TAG, "Identity Edge has successfully booted up"); + return true; + } + +} diff --git a/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeStorageService.java b/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeStorageService.java new file mode 100644 index 00000000..2d515236 --- /dev/null +++ b/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeStorageService.java @@ -0,0 +1,114 @@ +/* + Copyright 2021 Adobe. All rights reserved. + This file is licensed to you under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. You may obtain a copy + of the License at http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software distributed under + the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + OF ANY KIND, either express or implied. See the License for the specific language + governing permissions and limitations under the License. +*/ + +package com.adobe.marketing.mobile.identityedge; + +import android.app.Application; +import android.content.Context; +import android.content.SharedPreferences; + +import com.adobe.marketing.mobile.LoggingMode; +import com.adobe.marketing.mobile.MobileCore; + +import org.json.JSONException; +import org.json.JSONObject; + +import java.util.Map; + +/** + * Manages persistence for the Identity Edge extension + */ +class IdentityEdgeStorageService { + private static final String LOG_TAG = "IdentityEdgeStorageService"; + + /** + * Loads identity edge properties from local storage, returns null if not found. + * @return properties stored in local storage if present, otherwise null. + */ + static IdentityEdgeProperties loadPropertiesFromPersistence() { + final SharedPreferences sharedPreferences = getSharedPreference(); + if (sharedPreferences == null) { + MobileCore.log(LoggingMode.DEBUG, LOG_TAG, "Shared Preference value is null. Unable to load saved identity properties from persistence."); + return null; + } + + final String jsonString = sharedPreferences.getString(IdentityEdgeConstants.DataStoreKey.IDENTITY_PROPERTIES, null); + + if (jsonString == null) { + MobileCore.log(LoggingMode.VERBOSE, LOG_TAG, "No previous properties were stored in persistence. Current identity properties are null"); + return null; + } + + try { + final JSONObject jsonObject = new JSONObject(jsonString); + final Map propertyMap = Utils.toMap(jsonObject); + final IdentityEdgeProperties loadedProperties = new IdentityEdgeProperties(propertyMap); + return loadedProperties; + } catch (JSONException exception) { + MobileCore.log(LoggingMode.DEBUG, LOG_TAG, "Serialization error while reading properties jsonString from persistence. Unable to load saved identity properties from persistence."); + return null; + } + } + + /** + * Saves the properties to local storage + * @param properties properties to be stored + */ + static void savePropertiesToPersistence(final IdentityEdgeProperties properties) { + SharedPreferences sharedPreferences = getSharedPreference(); + if (sharedPreferences == null) { + MobileCore.log(LoggingMode.DEBUG, LOG_TAG, "Shared Preference value is null. Unable to write identity properties to persistence."); + return; + } + + SharedPreferences.Editor editor = sharedPreferences.edit(); + + if (editor == null) { + MobileCore.log(LoggingMode.DEBUG, LOG_TAG, "Shared Preference Editor is null. Unable to write identity properties to persistence."); + return; + } + + if (properties == null) { + MobileCore.log(LoggingMode.DEBUG, LOG_TAG, "Identity Properties are null, removing them from persistence."); + editor.remove(IdentityEdgeConstants.DataStoreKey.IDENTITY_PROPERTIES); + editor.apply(); + return; + } + + final JSONObject jsonObject = new JSONObject(properties.toXDMData(false)); + final String jsonString = jsonObject.toString(); + editor.putString(IdentityEdgeConstants.DataStoreKey.IDENTITY_PROPERTIES, jsonString); + editor.apply(); + } + + /** + * Getter for the applications {@link SharedPreferences} + *

    + * Returns null if the app or app context is not available + * + * @return a {@code SharedPreferences} instance + */ + private static SharedPreferences getSharedPreference() { + final Application application = MobileCore.getApplication(); + if (application == null) { + MobileCore.log(LoggingMode.DEBUG, LOG_TAG, "Application value is null. Unable to read/write data from persistence."); + return null; + } + + final Context context = application.getApplicationContext(); + if (context == null) { + MobileCore.log(LoggingMode.DEBUG, LOG_TAG, "Context value is null. Unable to read/write data from persistence."); + return null; + } + + return context.getSharedPreferences(IdentityEdgeConstants.DataStoreKey.DATASTORE_NAME, Context.MODE_PRIVATE); + } +} diff --git a/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityMap.java b/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityMap.java new file mode 100644 index 00000000..b1dfda00 --- /dev/null +++ b/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityMap.java @@ -0,0 +1,180 @@ +/* + Copyright 2021 Adobe. All rights reserved. + This file is licensed to you under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. You may obtain a copy + of the License at http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software distributed under + the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + OF ANY KIND, either express or implied. See the License for the specific language + governing permissions and limitations under the License. +*/ + +package com.adobe.marketing.mobile.identityedge; + +import com.adobe.marketing.mobile.LoggingMode; +import com.adobe.marketing.mobile.MobileCore; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * Defines a map containing a set of end user identities, keyed on either namespace integration + * code or the namespace ID of the identity. Within each namespace, the identity is unique. + * The values of the map are an array, meaning that more than one identity of each namespace + * may be carried. + * + * @see IdentityMap Schema + */ +@SuppressWarnings("unused") +class IdentityMap { + private static final String LOG_TAG = "IdentityMap"; + + private static final String JSON_KEY_ID = "id"; + private static final String JSON_KEY_AUTHENTICATION_STATE = "authenticationState"; + private static final String JSON_KEY_PRIMARY = "primary"; + + enum AuthenticationState { + AMBIGUOUS("ambiguous"), + AUTHENTICATED("authenticated"), + LOGGED_OUT("loggedOut"); + + private String name; + + AuthenticationState(final String name) { + this.name = name; + } + + String getName() { + return name; + } + + AuthenticationState fromString(final String state) { + if ("authenticated".equalsIgnoreCase(state)) { + return AUTHENTICATED; + } else if ("loggedOut".equalsIgnoreCase(state)) { + return LOGGED_OUT; + } else { + return AMBIGUOUS; + } + } + } + + private Map>> identityItems; + + IdentityMap() { + identityItems = new HashMap<>(); + } + + /** + * Gets the IdentityItem for the namespace + * @param namespace namespace for the id + * @return IdentityItem for the namespace, null if not found + */ + List> getIdentityItemForNamespace(final String namespace) { + return identityItems.get(namespace); + } + + /** + * Add an identity item which is used to clearly distinguish entities that are interacting + * with digital experiences. + * + * @param namespace the namespace integration code or namespace ID of the identity + * @param id identity of the consumer in the related namespace + * @param state the state this identity is authenticated as for this observed ExperienceEvent. + * Default is {@link AuthenticationState.AMBIGUOUS}. + * @param primary Indicates this identity is the preferred identity. Is used as a hint to help + * systems better organize how identities are queried. Default is false. + * + * @see IdentityItem Schema + */ + void addItem(final String namespace, final String id, final AuthenticationState state, final boolean primary) { + if (Utils.isNullOrEmpty(id)) { + MobileCore.log(LoggingMode.DEBUG, LOG_TAG, "IdentityMap add item ignored as must contain a non-null/non-empty id."); + return; + } + + if (Utils.isNullOrEmpty(namespace)) { + MobileCore.log(LoggingMode.DEBUG, LOG_TAG, + "IdentityMap add item ignored as must contain a non-null/non-empty namespace."); + return; + } + + Map item = new HashMap<>(); + item.put(JSON_KEY_ID, id); + item.put(JSON_KEY_PRIMARY, primary); + + if (state != null) { + item.put(JSON_KEY_AUTHENTICATION_STATE, state.getName()); + } + + addItemToMap(namespace, item); + } + + /** + * Add an identity item which is used to clearly distinguish entities that are interacting + * with digital experiences. Uses default authentication state and primary as defined by the Experience Platform. + * + * @param namespace the namespace integration code or namespace ID of the identity + * @param id identity of the consumer in the related namespace + * + * @see IdentityItem Schema + */ + void addItem(final String namespace, final String id) { + if (Utils.isNullOrEmpty(id)) { + MobileCore.log(LoggingMode.DEBUG, LOG_TAG, "IdentityMap add item ignored as must contain a non-null/non-empty id."); + return; + } + + if (Utils.isNullOrEmpty(namespace)) { + MobileCore.log(LoggingMode.DEBUG, LOG_TAG, + "IdentityMap add item ignored as must contain a non-null/non-empty namespace."); + return; + } + + Map item = new HashMap<>(); + item.put(JSON_KEY_ID, id); + + addItemToMap(namespace, item); + } + + private void addItemToMap(final String namespace, final Map item) { + // check if namespace exists + List> itemList; + + if (identityItems.containsKey(namespace)) { + itemList = identityItems.get(namespace); + } else { + itemList = new ArrayList<>(); + } + + itemList.add(item); + this.identityItems.put(namespace, itemList); + } + + Map>> toObjectMap() { + return identityItems; + } + + static IdentityMap fromData(Map data) { + if (data == null) { return null; } + final Map identityMapDict = (HashMap) data.get(IdentityEdgeConstants.XDMKeys.IDENTITY_MAP); + if (identityMapDict == null) { return null; } + + final IdentityMap identityMap = new IdentityMap(); + + for (String namespace : identityMapDict.keySet()) { + try { + final ArrayList> idArr = (ArrayList>) identityMapDict.get(namespace); + for (Object idMap: idArr) { + identityMap.addItemToMap(namespace, (Map) idMap); + } + } catch (ClassCastException e) { + MobileCore.log(LoggingMode.DEBUG, LOG_TAG, "Failed to create IdentityMap from data."); + } + } + + return identityMap; + } +} \ No newline at end of file diff --git a/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/ListenerConfigurationResponseContent.java b/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/ListenerConfigurationResponseContent.java deleted file mode 100644 index bc696280..00000000 --- a/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/ListenerConfigurationResponseContent.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - Copyright 2021 Adobe. All rights reserved. - This file is licensed to you under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. You may obtain a copy - of the License at http://www.apache.org/licenses/LICENSE-2.0 - Unless required by applicable law or agreed to in writing, software distributed under - the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS - OF ANY KIND, either express or implied. See the License for the specific language - governing permissions and limitations under the License. -*/ - -package com.adobe.marketing.mobile.identityedge; - -import com.adobe.marketing.mobile.Event; -import com.adobe.marketing.mobile.ExtensionApi; -import com.adobe.marketing.mobile.ExtensionListener; -import com.adobe.marketing.mobile.LoggingMode; -import com.adobe.marketing.mobile.MobileCore; - -class ListenerConfigurationResponseContent extends ExtensionListener { - - /** - * Constructor. - * - * @param extensionApi an instance of {@link ExtensionApi} - * @param type the {@link String} eventType this listener is registered to handle - * @param source the {@link String} eventSource this listener is registered to handle - */ - ListenerConfigurationResponseContent(final ExtensionApi extensionApi, final String type, final String source) { - super(extensionApi, type, source); - } - - /** - * Method that gets called when event with event type {@link IdentityEdgeConstants.EventType#CONFIGURATION} - * and with event source {@link IdentityEdgeConstants.EventSource#RESPONSE_CONTENT} is dispatched through eventHub. - * - * @param event the configuration response content {@link Event} to be processed - */ - @Override - public void hear(final Event event) { - if (event == null || event.getEventData() == null) { - MobileCore.log(LoggingMode.DEBUG, IdentityEdgeConstants.LOG_TAG, "Event or Event data is null. Ignoring the event listened by ListenerConfigurationResponseContent"); - return; - } - - final IdentityEdgeExtension parentExtension = getIdentityEdgeExtension(); - - if (parentExtension == null) { - MobileCore.log(LoggingMode.DEBUG, IdentityEdgeConstants.LOG_TAG, - "The parent extension, associated with the ListenerConfigurationResponseContent is null, ignoring the configuration response content event."); - return; - } - - parentExtension.handleConfigurationResponse(event); - } - - /** - * Returns the parent extension associated with the listener. - * - * @return a {@link IdentityEdgeExtension} object registered with the eventHub - */ - IdentityEdgeExtension getIdentityEdgeExtension() { - return (IdentityEdgeExtension) getParentExtension(); - } -} diff --git a/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/Utils.java b/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/Utils.java new file mode 100644 index 00000000..f60b9403 --- /dev/null +++ b/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/Utils.java @@ -0,0 +1,279 @@ +/* + Copyright 2021 Adobe. All rights reserved. + This file is licensed to you under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. You may obtain a copy + of the License at http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software distributed under + the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + OF ANY KIND, either express or implied. See the License for the specific language + governing permissions and limitations under the License. +*/ + +package com.adobe.marketing.mobile.identityedge; + +import com.adobe.marketing.mobile.LoggingMode; +import com.adobe.marketing.mobile.MobileCore; +import com.adobe.marketing.mobile.MobilePrivacyStatus; + +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +class Utils { + private static final String LOG_TAG = "Utils"; + + private Utils() {} + + static boolean isNullOrEmpty(final String str) { + return str == null || str.isEmpty(); + } + + static boolean isNullOrEmpty(final Map map) { + return map == null || map.isEmpty(); + } + + static boolean isNullOrEmpty(final JSONArray jsonArray) { + return jsonArray == null || jsonArray.length() == 0; + } + + static boolean isNullOrEmpty(final JSONObject jsonObject) { + return jsonObject == null || jsonObject.length() == 0; + } + + /* Helpers for manipulating Maps */ + /** + * Adds {@code key}/{@code values} to {@code map} if {@code values} is not null or an + * empty collection. + * + * @param map collection to put {@code values} mapped to {@code key} if {@code values} is + * non-null and contains at least one entry + * @param key key used to map {@code values} in {@code map} + * @param values a map to add to {@code map} if not null or empty + */ + static void putIfNotEmpty(final Map map, final String key, final Map values) { + boolean addValues = + map != null && + key != null && + values != null && + !values.isEmpty(); + + if (addValues) { + map.put(key, values); + } + } + + /** + * Adds {@code key}/{@code value} to {@code map} if {@code value} is not null or an + * empty collection. + * + * @param map collection to put {@code value} mapped to {@code key} if {@code value} is + * non-null and contains at least one entry + * @param key key used to map {@code value} in {@code map} + * @param value a String to add to {@code map} if not null or empty + */ + public static void putIfNotEmpty(final Map map, final String key, final String value) { + boolean addValues = + map != null && + key != null && + value != null && + !value.isEmpty(); + + if (addValues) { + map.put(key, value); + } + } + + /** + * Adds {@code key}/{@code value} to {@code map} if {@code value} is not null or an + * empty collection. + * + * @param map collection to put {@code value} mapped to {@code key} if {@code value} is + * non-null and contains at least one entry + * @param key key used to map {@code value} in {@code map} + * @param value a Object to add to {@code map} if not null + */ + static void putIfNotNull(final Map map, final String key, final Object value) { + boolean addValues = + map != null && + key != null && + value != null; + + if (addValues) { + map.put(key, value); + } + } + + /* JSON - Map conversion helpers */ + // TODO: add tests / replace with third party library for json conversion; test more around jsonObject/jsonArray with null nodes + // TODO: check what should be the expected behavior with the konductor team (e.g. don't add the null nodes or add them with null values) + /** + * Converts provided {@link JSONObject} into {@link Map} for any number of levels, which can be used as event data + * This method is recursive. + * The elements for which the conversion fails will be skipped. + * + * @param jsonObject to be converted + * @return {@link Map} containing the elements from the provided json, null if {@code jsonObject} is null + */ + static Map toMap(final JSONObject jsonObject) { + if (jsonObject == null) { + return null; + } + + Map jsonAsMap = new HashMap(); + Iterator keysIterator = jsonObject.keys(); + + while (keysIterator.hasNext()) { + String nextKey = keysIterator.next(); + Object value = null; + Object returnValue; + + try { + value = jsonObject.get(nextKey); + } catch (JSONException e) { + MobileCore.log(LoggingMode.DEBUG, LOG_TAG, + "toMap - Unable to convert jsonObject to Map for key " + nextKey + ", skipping."); + } + + if (value == null) { + continue; + } + + if (value instanceof JSONObject) { + returnValue = toMap((JSONObject)value); + } else if (value instanceof JSONArray) { + returnValue = toList((JSONArray) value); + } else { + returnValue = value; + } + + jsonAsMap.put(nextKey, returnValue); + } + + return jsonAsMap; + } + + /** + * Converts provided {@link JSONArray} into {@link List} for any number of levels which can be used as event data + * This method is recursive. + * The elements for which the conversion fails will be skipped. + * + * @param jsonArray to be converted + * @return {@link List} containing the elements from the provided json, null if {@code jsonArray} is null + */ + static List toList(final JSONArray jsonArray) { + if (jsonArray == null) { + return null; + } + + List jsonArrayAsList = new ArrayList(); + int size = jsonArray.length(); + + for (int i = 0; i < size; i++) { + Object value = null; + Object returnValue = null; + + try { + value = jsonArray.get(i); + } catch (JSONException e) { + MobileCore.log(LoggingMode.DEBUG, LOG_TAG, + "toList - Unable to convert jsonObject to List for index " + i + ", skipping."); + } + + if (value == null) { + continue; + } + + if (value instanceof JSONObject) { + returnValue = toMap((JSONObject)value); + } else if (value instanceof JSONArray) { + returnValue = toList((JSONArray) value); + } else { + returnValue = value; + } + + jsonArrayAsList.add(returnValue); + } + + return jsonArrayAsList; + } + + static List> toListOfMaps(final JSONArray jsonArray) { + if (jsonArray == null) { + return null; + } + + List> jsonArrayAsList = new ArrayList>(); + int size = jsonArray.length(); + + for (int i = 0; i < size; i++) { + Object value = null; + Map returnValue = null; + + try { + value = jsonArray.get(i); + } catch (JSONException e) { + MobileCore.log(LoggingMode.DEBUG, LOG_TAG, + "toList - Unable to convert jsonObject to List for index " + i + ", skipping."); + } + + if (value == null) { + continue; + } + + if (value instanceof JSONObject) { + returnValue = toMap((JSONObject)value); + jsonArrayAsList.add(returnValue); + } + } + + return jsonArrayAsList; + } + + /** + * Creates a deep copy of the provided {@link Map}. + * + * @param map to be copied + * @return {@link Map} containing a deep copy of all the elements in {@code map} + */ + static Map deepCopy(final Map map) { + if (map == null) { + return null; + } + + try { + return Utils.toMap(new JSONObject(map)); + } catch (NullPointerException e) { + MobileCore.log(LoggingMode.DEBUG, LOG_TAG, "deepCopy - Unable to deep copy map, json string invalid."); + } + + return null; + } + + /** + * Creates a deep copy of the provided {@code listOfMaps}. + * + * @param listOfMaps to be copied + * @return {@link List} containing a deep copy of all the elements in {@code listOfMaps} + * @see {@link #deepCopy(Map)} + */ + static List> deepCopy(final List> listOfMaps) { + if (listOfMaps == null) { + return null; + } + + List> deepCopy = new ArrayList>(); + + for (Map map : listOfMaps) { + deepCopy.add(deepCopy(map)); + } + + return deepCopy; + } + +} diff --git a/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/ECIDTests.java b/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/ECIDTests.java new file mode 100644 index 00000000..56cae9f5 --- /dev/null +++ b/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/ECIDTests.java @@ -0,0 +1,82 @@ +/* + Copyright 2021 Adobe. All rights reserved. + This file is licensed to you under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. You may obtain a copy + of the License at http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software distributed under + the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + OF ANY KIND, either express or implied. See the License for the specific language + governing permissions and limitations under the License. +*/ + +package com.adobe.marketing.mobile.identityedge; + +import org.junit.Test; + +import java.util.HashSet; +import java.util.Set; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import static junit.framework.Assert.assertTrue; +import static junit.framework.TestCase.assertEquals; +import static junit.framework.TestCase.assertNotNull; + +@SuppressWarnings("unchecked") +public class ECIDTests { + + @Test + public void testECID_correctLength() { + assertEquals(38, new ECID().getEcidString().length()); + } + + @Test + public void testECID_correctWithConstructor() { + // setup + ECID ecid = new ECID(); + + // test + ECID constructedEcid = new ECID(ecid.getEcidString()); + assertEquals(ecid.getEcidString(), constructedEcid.getEcidString()); + } + + @Test + public void testECID_correctWithConstructor_null() { + // test + ECID constructedEcid = new ECID(null); + assertNotNull(constructedEcid.getEcidString()); + } + + @Test + public void testECID_correctWithConstructor_emptyString() { + // test + ECID constructedEcid = new ECID(null); + assertNotNull(constructedEcid.getEcidString()); + } + + @Test + public void testECID_onlyContainsNumbers() { + // contains only digits + String regex = "[0-9]+"; + Pattern p = Pattern.compile(regex); + Matcher m = p.matcher(new ECID().getEcidString()); + + assertTrue(m.matches()); + } + + @Test + public void testECID_ReasonablyRandom() { + // setup + int count = 1000; + Set ecids = new HashSet(); + + // test + for (int i = 0; i < count; i++) { + ecids.add(new ECID().getEcidString()); + } + + // verify + assertEquals(count, ecids.size()); + } + +} diff --git a/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/IdentityExtensionVersionTest.java b/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeExtensionVersionTest.java similarity index 98% rename from code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/IdentityExtensionVersionTest.java rename to code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeExtensionVersionTest.java index cc9ce344..a59e504e 100644 --- a/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/IdentityExtensionVersionTest.java +++ b/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeExtensionVersionTest.java @@ -22,7 +22,7 @@ import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; -public class IdentityExtensionVersionTest { +public class IdentityEdgeExtensionVersionTest { private static String GRADLE_PROPERTIES_PATH = "../gradle.properties"; private static String PROPERTY_MODULE_VERSION = "moduleVersion"; diff --git a/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/IdentityEdgePropertiesTests.java b/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/IdentityEdgePropertiesTests.java new file mode 100644 index 00000000..49567f0e --- /dev/null +++ b/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/IdentityEdgePropertiesTests.java @@ -0,0 +1,142 @@ +/* + Copyright 2021 Adobe. All rights reserved. + This file is licensed to you under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. You may obtain a copy + of the License at http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software distributed under + the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + OF ANY KIND, either express or implied. See the License for the specific language + governing permissions and limitations under the License. +*/ + +package com.adobe.marketing.mobile.identityedge; + +import org.junit.Test; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; + +public class IdentityEdgePropertiesTests { + + @Test + public void testIdentityEdgeProperties_toXDMDataEmpty() { + // setup + IdentityEdgeProperties props = new IdentityEdgeProperties(); + + // test + Map xdmMap = props.toXDMData(false); + + // verify + assertNull(xdmMap.get(IdentityEdgeConstants.XDMKeys.IDENTITY_MAP)); + } + + @Test + public void testIdentityEdgeProperties_toXDMDataFull() { + // setup + IdentityEdgeProperties props = new IdentityEdgeProperties(); + props.setECID(new ECID()); + + // test + Map xdmData = props.toXDMData(false); + + // verify + assertEquals(props.getECID().getEcidString(), ecidFromIdentityMap(xdmData)); + } + + @Test + public void testIdentityEdgeProperties_toXDMDataMissingECID() { + // setup + IdentityEdgeProperties props = new IdentityEdgeProperties(); + + // test + Map xdmData = props.toXDMData(false); + + // verify + assertNull(ecidFromIdentityMap(xdmData)); + } + + @Test + public void testIdentityEdgeProperties_toXDMDataMissingPrivacy() { + // setup + IdentityEdgeProperties props = new IdentityEdgeProperties(); + props.setECID(new ECID()); + + // test + Map xdmData = props.toXDMData(false); + + // verify + assertEquals(props.getECID().getEcidString(), ecidFromIdentityMap(xdmData)); + } + + @Test + public void testIdentityEdgeProperties_fromXDMDataFull() { + // setup + IdentityEdgeProperties props = new IdentityEdgeProperties(); + props.setECID(new ECID()); + + // test + Map xdmData = props.toXDMData(false); + IdentityEdgeProperties loadedProps = new IdentityEdgeProperties(xdmData); + + // verify + assertEquals(ecidFromIdentityMap(xdmData), loadedProps.getECID().getEcidString()); + } + + @Test + public void testIdentityEdgeProperties_fromXDMDataMissingECID() { + // setup + IdentityEdgeProperties props = new IdentityEdgeProperties(); + + // test + Map map = props.toXDMData(false); + IdentityEdgeProperties loadedProps = new IdentityEdgeProperties(map); + + // verify + assertNull(loadedProps.getECID()); + } + + @Test + public void testIdentityEdgeProperties_fromXDMDataMissingPrivacy() { + // setup + IdentityEdgeProperties props = new IdentityEdgeProperties(); + props.setECID(new ECID()); + + // test + Map xdmMap = props.toXDMData(false); + IdentityEdgeProperties loadedProps = new IdentityEdgeProperties(xdmMap); + + // verify + assertEquals(ecidFromIdentityMap(xdmMap), loadedProps.getECID().getEcidString()); + } + + @Test + public void testIdentityEdgeProperties_toXDMDataWithECID() { + // setup + IdentityEdgeProperties props = new IdentityEdgeProperties(); + props.setECID(new ECID()); + + // test + Map xdmMap = props.toXDMData(false); + + // verify + assertEquals(props.getECID().getEcidString(), ecidFromIdentityMap(xdmMap)); + } + + private String ecidFromIdentityMap(Map xdmMap) { + if (xdmMap == null) { return null; } + Map identityMap = (HashMap) xdmMap.get("identityMap"); + if (identityMap == null) { return null; } + List ecidArr = (ArrayList) identityMap.get("ECID"); + if (ecidArr == null) { return null; } + Map ecidDict = (HashMap) ecidArr.get(0); + if (ecidDict == null) { return null; } + String ecid = (String) ecidDict.get("id"); + return ecid; + } + +} diff --git a/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeStateTests.java b/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeStateTests.java new file mode 100644 index 00000000..8b391e95 --- /dev/null +++ b/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeStateTests.java @@ -0,0 +1,98 @@ +/* + Copyright 2021 Adobe. All rights reserved. + This file is licensed to you under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. You may obtain a copy + of the License at http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software distributed under + the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + OF ANY KIND, either express or implied. See the License for the specific language + governing permissions and limitations under the License. +*/ + +package com.adobe.marketing.mobile.identityedge; + +import android.app.Application; +import android.content.Context; +import android.content.SharedPreferences; + +import com.adobe.marketing.mobile.MobileCore; + +import org.json.JSONObject; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.powermock.api.mockito.PowerMockito; +import org.powermock.core.classloader.annotations.PrepareForTest; +import org.powermock.modules.junit4.PowerMockRunner; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; + +@RunWith(PowerMockRunner.class) +@PrepareForTest({MobileCore.class}) +public class IdentityEdgeStateTests { + + @Mock + Application mockApplication; + + @Mock + Context mockContext; + + @Mock + SharedPreferences mockSharedPreference; + + @Mock + SharedPreferences.Editor mockSharedPreferenceEditor; + + @Before + public void before() throws Exception { + PowerMockito.mockStatic(MobileCore.class); + + Mockito.when(MobileCore.getApplication()).thenReturn(mockApplication); + Mockito.when(mockApplication.getApplicationContext()).thenReturn(mockContext); + Mockito.when(mockContext.getSharedPreferences(IdentityEdgeConstants.DataStoreKey.DATASTORE_NAME, 0)).thenReturn(mockSharedPreference); + Mockito.when(mockSharedPreference.edit()).thenReturn(mockSharedPreferenceEditor); + } + + + @Test + public void testIdentityEdgeState_BootupIfReadyGeneratesECID() { + // setup + IdentityEdgeState state = new IdentityEdgeState(new IdentityEdgeProperties()); + assertNull(state.getIdentityEdgeProperties().getECID()); + + // test + boolean result = state.bootupIfReady(); + verify(mockSharedPreferenceEditor, Mockito.times(1)).apply(); // saves to data store + + // verify + assertTrue(result); + assertNotNull(state.getIdentityEdgeProperties().getECID()); + } + + @Test + public void testIdentityEdgeState_BootupIfReadyLoadsFromPersistence() { + // setup + IdentityEdgeState state = new IdentityEdgeState(new IdentityEdgeProperties()); + + IdentityEdgeProperties persistedProps = new IdentityEdgeProperties(); + persistedProps.setECID(new ECID()); + final JSONObject jsonObject = new JSONObject(persistedProps.toXDMData(false)); + final String propsJSON = jsonObject.toString(); + Mockito.when(mockSharedPreference.getString(IdentityEdgeConstants.DataStoreKey.IDENTITY_PROPERTIES, null)).thenReturn(propsJSON); + + // test + boolean result = state.bootupIfReady(); + verify(mockSharedPreferenceEditor, never()).apply(); + + // verify + assertTrue(result); + assertEquals(persistedProps.getECID().getEcidString(), state.getIdentityEdgeProperties().getECID().getEcidString()); + } +} diff --git a/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeStorageServiceTests.java b/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeStorageServiceTests.java new file mode 100644 index 00000000..d21121a2 --- /dev/null +++ b/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeStorageServiceTests.java @@ -0,0 +1,164 @@ +/* + Copyright 2021 Adobe. All rights reserved. + This file is licensed to you under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. You may obtain a copy + of the License at http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software distributed under + the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + OF ANY KIND, either express or implied. See the License for the specific language + governing permissions and limitations under the License. +*/ + +package com.adobe.marketing.mobile.identityedge; + + +import android.app.Application; +import android.content.Context; +import android.content.SharedPreferences; + +import com.adobe.marketing.mobile.MobileCore; + +import org.json.JSONObject; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.powermock.api.mockito.PowerMockito; +import org.powermock.core.classloader.annotations.PrepareForTest; +import org.powermock.modules.junit4.PowerMockRunner; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; + +@RunWith(PowerMockRunner.class) +@PrepareForTest({MobileCore.class}) +public class IdentityEdgeStorageServiceTests { + + @Mock + Application mockApplication; + + @Mock + Context mockContext; + + @Mock + SharedPreferences mockSharedPreference; + + @Mock + SharedPreferences.Editor mockSharedPreferenceEditor; + + @Before + public void before() throws Exception { + PowerMockito.mockStatic(MobileCore.class); + + Mockito.when(MobileCore.getApplication()).thenReturn(mockApplication); + Mockito.when(mockApplication.getApplicationContext()).thenReturn(mockContext); + Mockito.when(mockContext.getSharedPreferences(IdentityEdgeConstants.DataStoreKey.DATASTORE_NAME, 0)).thenReturn(mockSharedPreference); + Mockito.when(mockSharedPreference.edit()).thenReturn(mockSharedPreferenceEditor); + } + + @Test + public void testStorageService_load_nullSharedPrefs() { + // setup + Mockito.when(mockApplication.getApplicationContext()).thenReturn(null); + + // test + IdentityEdgeProperties props = IdentityEdgeStorageService.loadPropertiesFromPersistence(); + + // verify + assertNull(props); + } + + @Test + public void testStorageService_load_emptyPrefs() { + // setup + Mockito.when(mockSharedPreference.getString(IdentityEdgeConstants.DataStoreKey.IDENTITY_PROPERTIES, null)).thenReturn(null); + + // test + IdentityEdgeProperties props = IdentityEdgeStorageService.loadPropertiesFromPersistence(); + + // verify + assertNull(props); + } + + @Test + public void testStorageService_load_invalidJSON() { + // setup + Mockito.when(mockSharedPreference.getString(IdentityEdgeConstants.DataStoreKey.IDENTITY_PROPERTIES, null)).thenReturn("{"); + + // test + IdentityEdgeProperties props = IdentityEdgeStorageService.loadPropertiesFromPersistence(); + + // verify + assertNull(props); + } + + @Test + public void testStorageService_load_validJSON() { + // setup + IdentityEdgeProperties persistedProps = new IdentityEdgeProperties(); + persistedProps.setECID(new ECID()); + final JSONObject jsonObject = new JSONObject(persistedProps.toXDMData(false)); + final String propsJSON = jsonObject.toString(); + Mockito.when(mockSharedPreference.getString(IdentityEdgeConstants.DataStoreKey.IDENTITY_PROPERTIES, null)).thenReturn(propsJSON); + + // test + IdentityEdgeProperties props = IdentityEdgeStorageService.loadPropertiesFromPersistence(); + + // verify + assertEquals(persistedProps.toXDMData(false), props.toXDMData(false)); + } + + @Test + public void testStorageService_save_nullSharedPrefs() { + // setup + Mockito.when(mockApplication.getApplicationContext()).thenReturn(null); + + // test + IdentityEdgeProperties props = new IdentityEdgeProperties(); + IdentityEdgeStorageService.savePropertiesToPersistence(props); + + // verify + verify(mockSharedPreferenceEditor, never()).apply(); + } + + @Test + public void testStorageService_save_nullEditor() { + // setup + Mockito.when(mockSharedPreference.edit()).thenReturn(null); + + // test + IdentityEdgeProperties props = new IdentityEdgeProperties(); + IdentityEdgeStorageService.savePropertiesToPersistence(props); + + // verify + verify(mockSharedPreferenceEditor, never()).apply(); + } + + @Test + public void testStorageService_save_nullProps() { + // test + IdentityEdgeStorageService.savePropertiesToPersistence(null); + + // verify + verify(mockSharedPreferenceEditor, Mockito.times(1)).remove(IdentityEdgeConstants.DataStoreKey.IDENTITY_PROPERTIES); + verify(mockSharedPreferenceEditor, Mockito.times(1)).apply(); + } + + @Test + public void testStorageService_save_validProps() { + // test + IdentityEdgeProperties props = new IdentityEdgeProperties(); + props.setECID(new ECID()); + IdentityEdgeStorageService.savePropertiesToPersistence(props); + + // verify + final JSONObject jsonObject = new JSONObject(props.toXDMData(false)); + final String expectedJSON = jsonObject.toString(); + verify(mockSharedPreferenceEditor, Mockito.times(1)).putString(IdentityEdgeConstants.DataStoreKey.IDENTITY_PROPERTIES, expectedJSON); + verify(mockSharedPreferenceEditor, Mockito.times(1)).apply(); + } + +} diff --git a/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/ListenerConfigurationResponseContentTest.java b/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/ListenerConfigurationResponseContentTest.java deleted file mode 100644 index 8760ee26..00000000 --- a/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/ListenerConfigurationResponseContentTest.java +++ /dev/null @@ -1,71 +0,0 @@ -package com.adobe.marketing.mobile.identityedge; - -import com.adobe.marketing.mobile.Event; -import com.adobe.marketing.mobile.MobileCore; - -import org.junit.Before; -import org.junit.Test; -import org.mockito.Mock; -import org.mockito.Mockito; - -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.doReturn; -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; - -public class ListenerConfigurationResponseContentTest { - - @Mock - private IdentityEdgeExtension mockIdentityEdgeExtension; - - private ListenerConfigurationResponseContent listener; - - @Before - public void setup() { - mockIdentityEdgeExtension = Mockito.mock(IdentityEdgeExtension.class); - MobileCore.start(null); - listener = spy(new ListenerConfigurationResponseContent(null, IdentityEdgeConstants.EventType.CONFIGURATION, IdentityEdgeConstants.EventSource.RESPONSE_CONTENT)); - } - - @Test - public void testHear() { - // setup - Event event = new Event.Builder("Configuration response event", IdentityEdgeConstants.EventType.CONFIGURATION, - IdentityEdgeConstants.EventSource.RESPONSE_CONTENT).build(); - doReturn(mockIdentityEdgeExtension).when(listener).getIdentityEdgeExtension(); - - // test - listener.hear(event); - - // verify - verify(mockIdentityEdgeExtension, times(1)).handleConfigurationResponse(event); - } - - @Test - public void testHear_WhenParentExtensionNull() { - // setup - Event event = new Event.Builder("Configuration response event", IdentityEdgeConstants.EventType.CONFIGURATION, - IdentityEdgeConstants.EventSource.RESPONSE_CONTENT).build(); - doReturn(null).when(listener).getIdentityEdgeExtension(); - - // test - listener.hear(event); - - // verify - verify(mockIdentityEdgeExtension, times(0)).handleConfigurationResponse(any(Event.class)); - } - - @Test - public void testHear_WhenEventNull() { - // setup - doReturn(null).when(listener).getIdentityEdgeExtension(); - doReturn(mockIdentityEdgeExtension).when(listener).getIdentityEdgeExtension(); - - // test - listener.hear(null); - - // verify - verify(mockIdentityEdgeExtension, times(0)).handleConfigurationResponse(any(Event.class)); - } -} diff --git a/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/UtilsTests.java b/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/UtilsTests.java new file mode 100644 index 00000000..7621abeb --- /dev/null +++ b/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/UtilsTests.java @@ -0,0 +1,164 @@ +/* + Copyright 2021 Adobe. All rights reserved. + This file is licensed to you under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. You may obtain a copy + of the License at http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software distributed under + the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + OF ANY KIND, either express or implied. See the License for the specific language + governing permissions and limitations under the License. +*/ + +package com.adobe.marketing.mobile.identityedge; + +import com.adobe.marketing.mobile.MobilePrivacyStatus; + +import org.junit.Test; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import static junit.framework.TestCase.assertEquals; +import static junit.framework.TestCase.assertFalse; +import static junit.framework.TestCase.assertNotNull; +import static junit.framework.TestCase.assertNull; +import static junit.framework.TestCase.assertTrue; + +@SuppressWarnings("unchecked") +public class UtilsTests { + + @Test + public void testUtils_deepCopyNull() { + assertNull(Utils.deepCopy((Map)null)); + } + + @Test + public void testUtils_deepCopyEmpty() { + Map emptyMap = new HashMap<>(); + assertEquals(0, Utils.deepCopy(emptyMap).size()); + } + + @Test + public void testUtils_deepCopyValidSimpleNull() { + Map map = new HashMap<>(); + map.put("key1", "value1"); + Map deepCopy = Utils.deepCopy(map); + + map = null; + assertNotNull(deepCopy); + } + + @Test + public void testUtils_deepCopyValidSimple() { + Map map = new HashMap<>(); + map.put("key1", "value1"); + + Map deepCopy = Utils.deepCopy(map); + deepCopy.remove("key1"); + + assertTrue(map.containsKey("key1")); + } + + @Test + public void testUtils_deepCopyValidNested() { + Map map = new HashMap<>(); + map.put("key1", "value1"); + + Map nested = new HashMap<>(); + nested.put("nestedKey", "nestedValue"); + map.put("nestedMap", nested); + + Map deepCopy = Utils.deepCopy(map); + Map nestedDeepCopy = (Map) deepCopy.get("nestedMap"); + nestedDeepCopy.put("newKey", "newValue"); + + assertFalse(nested.size() == nestedDeepCopy.size()); + } + + @Test + public void testUtils_deepCopyNullKey_returnsNull() { + Map map = new HashMap<>(); + map.put("key1", "value1"); + map.put(null, "null"); + + Map nested = new HashMap<>(); + nested.put("nestedKey", "nestedValue"); + map.put(null, "nestednull"); + + assertNull(Utils.deepCopy(map)); + } + + @Test + public void testPutIfNotNull_NonNull_InsertsCorrectly() { + Map map = new HashMap<>(); + Utils.putIfNotNull(map, "testKey", new Integer(2)); + + assertEquals(1, map.size()); + assertEquals(2, map.get("testKey")); + } + + @Test + public void testPutIfNotNull_Null_NoInsert() { + Map map = new HashMap<>(); + Utils.putIfNotNull(map, "testKey", null); + + assertEquals(0, map.size()); + } + + @Test + public void testUtils_deepCopyListOfMaps_Null() { + assertNull(Utils.deepCopy((List)null)); + } + + @Test + public void testUtils_deepCopyListOfMaps_Empty() { + assertEquals(0, Utils.deepCopy(new ArrayList>()).size()); + } + + @Test + public void testUtils_deepCopyListOfMaps_ValidSimpleNull() { + List> list = new ArrayList<>(); + list.add(new HashMap()); + assertNotNull(Utils.deepCopy(list)); + } + + @Test + public void testUtils_deepCopyListOfMaps_ValidSimple() { + Map map = new HashMap<>(); + map.put("key1", "value1"); + List> list = new ArrayList<>(); + list.add(map); + + List> deepCopy = Utils.deepCopy(list); + deepCopy.remove(0); + + assertEquals(1, list.size()); + } + + @Test + public void testUtils_deepCopyListOfMaps_ValidNested() { + Map map = new HashMap<>(); + map.put("key1", "value1"); + + Map nested = new HashMap<>(); + nested.put("nestedKey", "nestedValue"); + map.put("nestedMap", nested); + + List> list = new ArrayList>(); + list.add(map); + + Map deepCopy = Utils.deepCopy(map); + List> nestedDeepCopy = Utils.deepCopy(list); + nestedDeepCopy.get(0).put("newKey", "newValue"); + ((Map)nestedDeepCopy.get(0).get("nestedMap")).put("nestedKey2", 2222); + + assertEquals(3, nestedDeepCopy.get(0).size()); + assertEquals(2, list.get(0).size()); + assertTrue(list.get(0).containsKey("key1")); + assertEquals(1, ((Map)list.get(0).get("nestedMap")).size()); + assertEquals(2, ((Map)nestedDeepCopy.get(0).get("nestedMap")).size()); + } + +} From 61fae43e9e50aba6e45f496936173497e1b94424 Mon Sep 17 00:00:00 2001 From: Nick Porter <43650450+nporter-adbe@users.noreply.github.com> Date: Wed, 10 Mar 2021 15:58:07 -0700 Subject: [PATCH 005/101] [AMSDK-11079] Add getEcid API (#5) * Add ECID and tests * Add IdentityMap from Edge extension * Create IdentityEdgeProperties and tests * Add utils copied from Edge * Create storage service * Add required constants * Create IdentityEdgeState and tests * Rename extension version test class * Add doc comment * Doc comment for storage service * Remove configuration handling * Fix IdentityEdgeState log tag * update event names * Make ECID parameter final * Update class comment for IdentityEdgeProperties * Make getECID protected * getIdentityProperties -> getIdentityEdgeProperties * Remove config listener and remove unused imports * Make IdentityEdgeState methods protected * Add test for ECID(final String ecidString) * Remove config listener * Start on public API * Fix complier issue * Remove test to be added in a following PR * persist data in xdm format * Use extension name as datastore name * Update listener doc comment * Update event type in doc comment * Save after generating ECID and add assertion in test * Add null check in IdentityMap.fromData * Add null check in IdentityEdgeProperties.readECIDFromIdentityMap * Add tests for IdentityEdgeExtension ECID getter * Add copyright to IdentityEdgeExtensionTests * Add tests for public get ECID API * Improve doc comments, logs, and handle empty/null ECID string * Add tests for storage service * remove unused import * Fix comments and logs * Fix listener event source * fix listener source in tests * Revert un-needed listener change * Handle case where there are empty IDs * Add null check for identity map * Invoke with empty identity map when no ECID found and replace ecidString with toString * Move ECID read to IdentityMap * use raw data for API tests * Ensure when IdentityEdgeProps is empty we dispatch an empty map * Add test with invalid event data * add log when failing to get extension api --- .../marketing/mobile/identityedge/ECID.java | 5 +- .../mobile/identityedge/IdentityEdge.java | 73 +++++ .../identityedge/IdentityEdgeExtension.java | 48 +++- .../identityedge/IdentityEdgeProperties.java | 29 +- .../mobile/identityedge/IdentityMap.java | 20 ++ .../mobile/identityedge/ECIDTests.java | 14 +- .../IdentityEdgeExtensionTests.java | 220 +++++++++++++++ .../IdentityEdgePropertiesTests.java | 10 +- .../identityedge/IdentityEdgeStateTests.java | 2 +- .../identityedge/IdentityEdgeTests.java | 254 ++++++++++++++++++ 10 files changed, 634 insertions(+), 41 deletions(-) create mode 100644 code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeExtensionTests.java create mode 100644 code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeTests.java diff --git a/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/ECID.java b/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/ECID.java index 15ab476b..8e4e476a 100644 --- a/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/ECID.java +++ b/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/ECID.java @@ -41,7 +41,7 @@ class ECID { ECID(final String ecidString) { if (Utils.isNullOrEmpty(ecidString)) { MobileCore.log(LoggingMode.DEBUG, IdentityEdgeConstants.LOG_TAG, "Creating an ECID with null or empty ecidString is not allowed, generating a new ECID."); - this.ecidString = new ECID().getEcidString(); + this.ecidString = new ECID().toString(); return; } @@ -52,7 +52,8 @@ class ECID { * Retrieves the string representation of the ECID * @return string representation of the ECID */ - public String getEcidString() { + @Override + public String toString() { return ecidString; } } diff --git a/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdge.java b/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdge.java index 5804bea9..e4d19166 100644 --- a/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdge.java +++ b/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdge.java @@ -11,6 +11,10 @@ package com.adobe.marketing.mobile.identityedge; +import com.adobe.marketing.mobile.AdobeCallback; +import com.adobe.marketing.mobile.AdobeCallbackWithError; +import com.adobe.marketing.mobile.AdobeError; +import com.adobe.marketing.mobile.Event; import com.adobe.marketing.mobile.ExtensionError; import com.adobe.marketing.mobile.ExtensionErrorCallback; import com.adobe.marketing.mobile.LoggingMode; @@ -41,4 +45,73 @@ public void error(ExtensionError extensionError) { } }); } + + /** + * Returns the Experience Cloud ID. An empty string is returned if the Experience Cloud ID was previously cleared. + * @param callback {@link AdobeCallback} of {@link String} invoked with the Experience Cloud ID + * If an {@link AdobeCallbackWithError} is provided, an {@link AdobeError} can be returned in the + * eventuality of any error that occurred while getting the Experience Cloud ID + */ + public static void getExperienceCloudId(final AdobeCallback callback) { + if (callback == null) { + MobileCore.log(LoggingMode.DEBUG, LOG_TAG, "Unexpected null callback, provide a callback to retrieve current ECID."); + return; + } + + final Event event = new Event.Builder(IdentityEdgeConstants.EventNames.IDENTITY_REQUEST_IDENTITY_ECID, + IdentityEdgeConstants.EventType.IDENTITY_EDGE, + IdentityEdgeConstants.EventSource.REQUEST_IDENTITY).build(); + + final ExtensionErrorCallback errorCallback = new ExtensionErrorCallback() { + @Override + public void error(final ExtensionError extensionError) { + returnError(callback, extensionError); + MobileCore.log(LoggingMode.DEBUG, LOG_TAG, String.format("Failed to dispatch %s event: Error : %s.", IdentityEdgeConstants.EventNames.IDENTITY_REQUEST_IDENTITY_ECID, + extensionError.getErrorName())); + } + }; + + MobileCore.dispatchEventWithResponseCallback(event, new AdobeCallback() { + @Override + public void call(Event responseEvent) { + if (responseEvent == null || responseEvent.getEventData() == null) { + returnError(callback, AdobeError.UNEXPECTED_ERROR); + return; + } + + final IdentityMap identityMap = IdentityMap.fromData(responseEvent.getEventData()); + if (identityMap == null) { + MobileCore.log(LoggingMode.DEBUG, LOG_TAG, "Failed to read IdentityMap from response event, invoking error callback with AdobeError.UNEXPECTED_ERROR"); + returnError(callback, AdobeError.UNEXPECTED_ERROR); + return; + } + + final ECID ecid = identityMap.getFirstECID(); + if (ecid != null) { + callback.call(ecid.toString()); + } else { + callback.call(""); + } + + } + }, errorCallback); + } + + /** + * When an {@link AdobeCallbackWithError} is provided, the fail method will be called with provided {@link AdobeError}. + * @param callback should not be null, should be instance of {@code AdobeCallbackWithError} + * @param error the {@code AdobeError} returned back in the callback + */ + private static void returnError (final AdobeCallback callback, final AdobeError error) { + if (callback == null) { + return; + } + + final AdobeCallbackWithError adobeCallbackWithError = callback instanceof AdobeCallbackWithError ? + (AdobeCallbackWithError) callback : null; + + if (adobeCallbackWithError != null) { + adobeCallbackWithError.fail(error); + } + } } diff --git a/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeExtension.java b/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeExtension.java index d2475dd8..ee349e6d 100644 --- a/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeExtension.java +++ b/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeExtension.java @@ -19,8 +19,12 @@ import com.adobe.marketing.mobile.LoggingMode; import com.adobe.marketing.mobile.MobileCore; +import java.util.Map; + class IdentityEdgeExtension extends Extension { + private final String LOG_TAG = "IdentityEdgeExtension"; + private IdentityEdgeState state = new IdentityEdgeState(new IdentityEdgeProperties()); /** * Constructor. @@ -76,8 +80,50 @@ void handleGenericIdentityRequest(final Event event) { // TODO } + /** + * Handles events requesting for identifiers. Dispatches response event containing the identifiers. Called by listener registered with event hub. + * @param event the identity request event + */ void handleIdentityRequest(final Event event) { - // TODO + if (!canProcessEvents(event)) { return; } + + Map xdmData = state.getIdentityEdgeProperties().toXDMData(true); + Event responseEvent = new Event.Builder(IdentityEdgeConstants.EventNames.IDENTITY_RESPONSE_CONTENT_ONE_TIME, + IdentityEdgeConstants.EventType.IDENTITY_EDGE, + IdentityEdgeConstants.EventSource.RESPONSE_IDENTITY) + .setEventData(xdmData) + .build(); + + MobileCore.dispatchResponseEvent(responseEvent, event, new ExtensionErrorCallback() { + @Override + public void error(ExtensionError extensionError) { + MobileCore.log(LoggingMode.DEBUG, LOG_TAG, "Failed to dispatch Identity Edge response event for event " + + event.getUniqueIdentifier() + + " with error " + + extensionError.getErrorName()); + } + }); } + /** + * Determines if Identity Edge is ready to handle events, this is determined by if the Identity Edge extension has booted up + * @param event An {@link Event} + * @return True if we can process events, false otherwise + */ + private boolean canProcessEvents(final Event event) { + if (state.hasBooted()) { return true; } // we have booted, return true + + final ExtensionApi extensionApi = super.getApi(); + if (extensionApi == null ) { + MobileCore.log(LoggingMode.DEBUG, LOG_TAG, "ExtensionApi is null, unable to process events"); + return false; + } + + if (state.bootupIfReady()) { + extensionApi.setXDMSharedEventState(state.getIdentityEdgeProperties().toXDMData(false), null, null); + return true; + } + + return false; // cannot handle any events until we have booted + } } diff --git a/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeProperties.java b/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeProperties.java index cca57c29..58ecd2e6 100644 --- a/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeProperties.java +++ b/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeProperties.java @@ -14,7 +14,6 @@ import com.adobe.marketing.mobile.LoggingMode; import com.adobe.marketing.mobile.MobileCore; -import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -41,7 +40,9 @@ class IdentityEdgeProperties { } IdentityMap identityMap = IdentityMap.fromData(xdmData); - ecid = readECIDFromIdentityMap(identityMap); + if (identityMap != null) { + ecid = identityMap.getFirstECID(); + } } /** @@ -70,7 +71,7 @@ Map toXDMData(final boolean allowEmpty) { final IdentityMap identityMap = new IdentityMap(); if (ecid != null) { - identityMap.addItem(IdentityEdgeConstants.Namespaces.ECID, ecid.getEcidString()); + identityMap.addItem(IdentityEdgeConstants.Namespaces.ECID, ecid.toString()); } final Map>> dict = identityMap.toObjectMap(); @@ -81,26 +82,4 @@ Map toXDMData(final boolean allowEmpty) { return map; } - /** - * Reads the ECID from an IdentityMap - * @param identityMap an IdentityMap - * @return ECID stored in the IdentityMap or null if not found - */ - static ECID readECIDFromIdentityMap(IdentityMap identityMap) { - if (identityMap == null) { return null; } - final List>ecidArr = identityMap.getIdentityItemForNamespace(IdentityEdgeConstants.Namespaces.ECID); - if (ecidArr == null) { return null; } - final Map ecidDict = ecidArr.get(0); - if (ecidDict == null) { return null; } - String ecidStr = null; - try { - ecidStr = (String) ecidDict.get(IdentityEdgeConstants.XDMKeys.ID); - } catch (ClassCastException e) { - MobileCore.log(LoggingMode.DEBUG, LOG_TAG, "Failed to create read ECID from IdentityMap"); - } - - if (ecidStr == null) { return null; } - return new ECID(ecidStr); - } - } diff --git a/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityMap.java b/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityMap.java index b1dfda00..73758593 100644 --- a/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityMap.java +++ b/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityMap.java @@ -177,4 +177,24 @@ static IdentityMap fromData(Map data) { return identityMap; } + + /** + * Reads the ECID from an IdentityMap + * @return ECID stored in the IdentityMap or null if not found + */ + ECID getFirstECID() { + final List> ecidArr = getIdentityItemForNamespace(IdentityEdgeConstants.Namespaces.ECID); + if (ecidArr == null) { return null; } + final Map ecidDict = ecidArr.get(0); + if (ecidDict == null) { return null; } + String ecidStr = null; + try { + ecidStr = (String) ecidDict.get(IdentityEdgeConstants.XDMKeys.ID); + } catch (ClassCastException e) { + MobileCore.log(LoggingMode.DEBUG, LOG_TAG, "Failed to create read ECID from IdentityMap"); + } + + if (ecidStr == null) { return null; } + return new ECID(ecidStr); + } } \ No newline at end of file diff --git a/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/ECIDTests.java b/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/ECIDTests.java index 56cae9f5..ad996dc7 100644 --- a/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/ECIDTests.java +++ b/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/ECIDTests.java @@ -27,7 +27,7 @@ public class ECIDTests { @Test public void testECID_correctLength() { - assertEquals(38, new ECID().getEcidString().length()); + assertEquals(38, new ECID().toString().length()); } @Test @@ -36,22 +36,22 @@ public void testECID_correctWithConstructor() { ECID ecid = new ECID(); // test - ECID constructedEcid = new ECID(ecid.getEcidString()); - assertEquals(ecid.getEcidString(), constructedEcid.getEcidString()); + ECID constructedEcid = new ECID(ecid.toString()); + assertEquals(ecid.toString(), constructedEcid.toString()); } @Test public void testECID_correctWithConstructor_null() { // test ECID constructedEcid = new ECID(null); - assertNotNull(constructedEcid.getEcidString()); + assertNotNull(constructedEcid.toString()); } @Test public void testECID_correctWithConstructor_emptyString() { // test ECID constructedEcid = new ECID(null); - assertNotNull(constructedEcid.getEcidString()); + assertNotNull(constructedEcid.toString()); } @Test @@ -59,7 +59,7 @@ public void testECID_onlyContainsNumbers() { // contains only digits String regex = "[0-9]+"; Pattern p = Pattern.compile(regex); - Matcher m = p.matcher(new ECID().getEcidString()); + Matcher m = p.matcher(new ECID().toString()); assertTrue(m.matches()); } @@ -72,7 +72,7 @@ public void testECID_ReasonablyRandom() { // test for (int i = 0; i < count; i++) { - ecids.add(new ECID().getEcidString()); + ecids.add(new ECID().toString()); } // verify diff --git a/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeExtensionTests.java b/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeExtensionTests.java new file mode 100644 index 00000000..ad376397 --- /dev/null +++ b/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeExtensionTests.java @@ -0,0 +1,220 @@ +/* + Copyright 2021 Adobe. All rights reserved. + This file is licensed to you under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. You may obtain a copy + of the License at http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software distributed under + the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + OF ANY KIND, either express or implied. See the License for the specific language + governing permissions and limitations under the License. +*/ + +package com.adobe.marketing.mobile.identityedge; + +import android.app.Application; +import android.content.Context; +import android.content.SharedPreferences; + +import com.adobe.marketing.mobile.Event; +import com.adobe.marketing.mobile.ExtensionApi; +import com.adobe.marketing.mobile.ExtensionErrorCallback; +import com.adobe.marketing.mobile.MobileCore; + +import org.json.JSONObject; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.powermock.api.mockito.PowerMockito; +import org.powermock.core.classloader.annotations.PrepareForTest; +import org.powermock.modules.junit4.PowerMockRunner; + +import java.util.Map; + +import static junit.framework.TestCase.assertEquals; +import static junit.framework.TestCase.assertNotNull; +import static junit.framework.TestCase.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +@RunWith(PowerMockRunner.class) +@PrepareForTest({Event.class, MobileCore.class, ExtensionApi.class, IdentityEdgeState.class}) +public class IdentityEdgeExtensionTests { + private IdentityEdgeExtension extension; + + @Mock + ExtensionApi mockExtensionApi; + + @Mock + Application mockApplication; + + @Mock + Context mockContext; + + @Mock + SharedPreferences mockSharedPreference; + + @Mock + SharedPreferences.Editor mockSharedPreferenceEditor; + + @Before + public void setup() { + PowerMockito.mockStatic(MobileCore.class); + + Mockito.when(MobileCore.getApplication()).thenReturn(mockApplication); + Mockito.when(mockApplication.getApplicationContext()).thenReturn(mockContext); + Mockito.when(mockContext.getSharedPreferences(IdentityEdgeConstants.DataStoreKey.DATASTORE_NAME, 0)).thenReturn(mockSharedPreference); + Mockito.when(mockSharedPreference.edit()).thenReturn(mockSharedPreferenceEditor); + + extension = new IdentityEdgeExtension(mockExtensionApi); + } + + // ======================================================================================== + // constructor + // ======================================================================================== + @Test + public void test_ListenersRegistration() { + // setup + final ArgumentCaptor callbackCaptor = ArgumentCaptor.forClass(ExtensionErrorCallback.class); + + // test + // constructor is called in the setup step() + + // verify 2 listeners are registered + verify(mockExtensionApi, times(2)).registerEventListener(anyString(), + anyString(), any(Class.class), any(ExtensionErrorCallback.class)); + + // verify listeners are registered with correct event source and type + verify(mockExtensionApi, times(1)).registerEventListener(eq(IdentityEdgeConstants.EventType.IDENTITY_EDGE), + eq(IdentityEdgeConstants.EventSource.REQUEST_IDENTITY), eq(ListenerIdentityRequestIdentity.class), callbackCaptor.capture()); + verify(mockExtensionApi, times(1)).registerEventListener(eq(IdentityEdgeConstants.EventType.GENERIC_IDENTITY), + eq(IdentityEdgeConstants.EventSource.REQUEST_CONTENT), eq(ListenerGenericIdentityRequestContent.class), callbackCaptor.capture()); + + // verify the callback + ExtensionErrorCallback extensionErrorCallback = callbackCaptor.getValue(); + Assert.assertNotNull("The extension callback should not be null", extensionErrorCallback); + + // TODO - enable when ExtensionError creation is available + //extensionErrorCallback.error(ExtensionError.UNEXPECTED_ERROR); + } + + // ======================================================================================== + // getName + // ======================================================================================== + @Test + public void test_getName() { + // test + String moduleName = extension.getName(); + assertEquals("getName should return the correct module name", IdentityEdgeConstants.EXTENSION_NAME, moduleName); + } + + // ======================================================================================== + // getVersion + // ======================================================================================== + @Test + public void test_getVersion() { + // test + String moduleVersion = extension.getVersion(); + assertEquals("getVersion should return the correct module version", IdentityEdgeConstants.EXTENSION_VERSION, + moduleVersion); + } + + // ======================================================================================== + // handleIdentityRequest + // ======================================================================================== + + @Test + public void test_handleIdentityRequest_nullEvent_shouldNotThrow() { + // test + extension.handleIdentityRequest(null); + } + + @Test + public void test_handleIdentityRequest_generatesNewECID() { + // setup + Event event = new Event.Builder("Test event", IdentityEdgeConstants.EventType.IDENTITY_EDGE, IdentityEdgeConstants.EventSource.REQUEST_IDENTITY).build(); + final ArgumentCaptor responseEventCaptor = ArgumentCaptor.forClass(Event.class); + final ArgumentCaptor requestEventCaptor = ArgumentCaptor.forClass(Event.class); + + // test + extension.handleIdentityRequest(event); + + // verify + PowerMockito.verifyStatic(MobileCore.class, Mockito.times(1)); + MobileCore.dispatchResponseEvent(responseEventCaptor.capture(), requestEventCaptor.capture(), any(ExtensionErrorCallback.class)); + + // verify response event containing ECID is dispatched + Event ecidResponseEvent = responseEventCaptor.getAllValues().get(0); + final IdentityMap identityMap = IdentityMap.fromData(ecidResponseEvent.getEventData()); + final ECID ecid = identityMap.getFirstECID(); + + assertNotNull(ecid); + assertTrue(ecid.toString().length() > 0); + } + + @Test + public void test_handleIdentityRequest_loadsPersistedECID() { + // setup + final ECID existingECID = new ECID(); + setupExistingIdentityEdgeProps(existingECID); + + Event event = new Event.Builder("Test event", IdentityEdgeConstants.EventType.IDENTITY_EDGE, IdentityEdgeConstants.EventSource.REQUEST_IDENTITY).build(); + final ArgumentCaptor responseEventCaptor = ArgumentCaptor.forClass(Event.class); + final ArgumentCaptor requestEventCaptor = ArgumentCaptor.forClass(Event.class); + + // test + extension.handleIdentityRequest(event); + + // verify + PowerMockito.verifyStatic(MobileCore.class, Mockito.times(1)); + MobileCore.dispatchResponseEvent(responseEventCaptor.capture(), requestEventCaptor.capture(), any(ExtensionErrorCallback.class)); + + // verify response event containing ECID is dispatched + Event ecidResponseEvent = responseEventCaptor.getAllValues().get(0); + final IdentityMap identityMap = IdentityMap.fromData(ecidResponseEvent.getEventData()); + final ECID ecid = identityMap.getFirstECID(); + + assertEquals(existingECID.toString(), ecid.toString()); + } + + @Test + public void test_handleIdentityRequest_noIdentifiers_emptyIdentityMap() { + // setup + IdentityEdgeProperties emptyProps = new IdentityEdgeProperties(); + PowerMockito.stub(PowerMockito.method(IdentityEdgeState.class, "getIdentityEdgeProperties")).toReturn(emptyProps); + + Event event = new Event.Builder("Test event", IdentityEdgeConstants.EventType.IDENTITY_EDGE, IdentityEdgeConstants.EventSource.REQUEST_IDENTITY).build(); + final ArgumentCaptor responseEventCaptor = ArgumentCaptor.forClass(Event.class); + final ArgumentCaptor requestEventCaptor = ArgumentCaptor.forClass(Event.class); + + // test + extension.handleIdentityRequest(event); + + // verify + PowerMockito.verifyStatic(MobileCore.class, Mockito.times(1)); + MobileCore.dispatchResponseEvent(responseEventCaptor.capture(), requestEventCaptor.capture(), any(ExtensionErrorCallback.class)); + + // verify response event containing ECID is dispatched + Event ecidResponseEvent = responseEventCaptor.getAllValues().get(0); + final Map xdmData = ecidResponseEvent.getEventData(); + final Map identityMap = (Map) xdmData.get("identityMap"); + + assertTrue(identityMap.isEmpty()); + } + + private void setupExistingIdentityEdgeProps(final ECID ecid) { + IdentityEdgeProperties persistedProps = new IdentityEdgeProperties(); + persistedProps.setECID(ecid); + final JSONObject jsonObject = new JSONObject(persistedProps.toXDMData(false)); + final String propsJSON = jsonObject.toString(); + Mockito.when(mockSharedPreference.getString(IdentityEdgeConstants.DataStoreKey.IDENTITY_PROPERTIES, null)).thenReturn(propsJSON); + } + + +} diff --git a/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/IdentityEdgePropertiesTests.java b/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/IdentityEdgePropertiesTests.java index 49567f0e..5ddcf3f8 100644 --- a/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/IdentityEdgePropertiesTests.java +++ b/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/IdentityEdgePropertiesTests.java @@ -45,7 +45,7 @@ public void testIdentityEdgeProperties_toXDMDataFull() { Map xdmData = props.toXDMData(false); // verify - assertEquals(props.getECID().getEcidString(), ecidFromIdentityMap(xdmData)); + assertEquals(props.getECID().toString(), ecidFromIdentityMap(xdmData)); } @Test @@ -70,7 +70,7 @@ public void testIdentityEdgeProperties_toXDMDataMissingPrivacy() { Map xdmData = props.toXDMData(false); // verify - assertEquals(props.getECID().getEcidString(), ecidFromIdentityMap(xdmData)); + assertEquals(props.getECID().toString(), ecidFromIdentityMap(xdmData)); } @Test @@ -84,7 +84,7 @@ public void testIdentityEdgeProperties_fromXDMDataFull() { IdentityEdgeProperties loadedProps = new IdentityEdgeProperties(xdmData); // verify - assertEquals(ecidFromIdentityMap(xdmData), loadedProps.getECID().getEcidString()); + assertEquals(ecidFromIdentityMap(xdmData), loadedProps.getECID().toString()); } @Test @@ -111,7 +111,7 @@ public void testIdentityEdgeProperties_fromXDMDataMissingPrivacy() { IdentityEdgeProperties loadedProps = new IdentityEdgeProperties(xdmMap); // verify - assertEquals(ecidFromIdentityMap(xdmMap), loadedProps.getECID().getEcidString()); + assertEquals(ecidFromIdentityMap(xdmMap), loadedProps.getECID().toString()); } @Test @@ -124,7 +124,7 @@ public void testIdentityEdgeProperties_toXDMDataWithECID() { Map xdmMap = props.toXDMData(false); // verify - assertEquals(props.getECID().getEcidString(), ecidFromIdentityMap(xdmMap)); + assertEquals(props.getECID().toString(), ecidFromIdentityMap(xdmMap)); } private String ecidFromIdentityMap(Map xdmMap) { diff --git a/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeStateTests.java b/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeStateTests.java index 8b391e95..7a86ab8c 100644 --- a/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeStateTests.java +++ b/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeStateTests.java @@ -93,6 +93,6 @@ public void testIdentityEdgeState_BootupIfReadyLoadsFromPersistence() { // verify assertTrue(result); - assertEquals(persistedProps.getECID().getEcidString(), state.getIdentityEdgeProperties().getECID().getEcidString()); + assertEquals(persistedProps.getECID().toString(), state.getIdentityEdgeProperties().getECID().toString()); } } diff --git a/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeTests.java b/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeTests.java new file mode 100644 index 00000000..af3352b2 --- /dev/null +++ b/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeTests.java @@ -0,0 +1,254 @@ +/* + Copyright 2021 Adobe. All rights reserved. + This file is licensed to you under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. You may obtain a copy + of the License at http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software distributed under + the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + OF ANY KIND, either express or implied. See the License for the specific language + governing permissions and limitations under the License. +*/ + +package com.adobe.marketing.mobile.identityedge; + +import com.adobe.marketing.mobile.AdobeCallback; +import com.adobe.marketing.mobile.AdobeCallbackWithError; +import com.adobe.marketing.mobile.AdobeError; +import com.adobe.marketing.mobile.Event; +import com.adobe.marketing.mobile.ExtensionErrorCallback; +import com.adobe.marketing.mobile.MobileCore; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.ArgumentMatchers; +import org.mockito.Mockito; +import org.powermock.api.mockito.PowerMockito; +import org.powermock.core.classloader.annotations.PrepareForTest; +import org.powermock.modules.junit4.PowerMockRunner; + +import java.lang.reflect.Array; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.any; + +@RunWith(PowerMockRunner.class) +@PrepareForTest({MobileCore.class}) +public class IdentityEdgeTests { + + @Before + public void setup() { + PowerMockito.mockStatic(MobileCore.class); + } + + // ======================================================================================== + // extensionVersion + // ======================================================================================== + + @Test + public void test_extensionVersionAPI() { + // test + String extensionVersion = IdentityEdge.extensionVersion(); + assertEquals("The Extension version API returns the correct value", IdentityEdgeConstants.EXTENSION_VERSION, + extensionVersion); + } + + // ======================================================================================== + // registerExtension + // ======================================================================================== + @Test + public void testRegistration() { + // test + IdentityEdge.registerExtension(); + final ArgumentCaptor callbackCaptor = ArgumentCaptor.forClass(ExtensionErrorCallback.class); + + // The identity edge extension should register with core + PowerMockito.verifyStatic(MobileCore.class, Mockito.times(1)); + MobileCore.registerExtension(ArgumentMatchers.eq(IdentityEdgeExtension.class), callbackCaptor.capture()); + + // verify the callback + ExtensionErrorCallback extensionErrorCallback = callbackCaptor.getValue(); + assertNotNull("The extension callback should not be null", extensionErrorCallback); + + // TODO - enable when ExtensionError creation is available + // should not crash on calling the callback + //extensionErrorCallback.error(ExtensionError.UNEXPECTED_ERROR); + } + + // ======================================================================================== + // getExperienceCloudId API + // ======================================================================================== + @Test + public void testGetExperienceCloudId() { + // setup + final ArgumentCaptor eventCaptor = ArgumentCaptor.forClass(Event.class); + final ArgumentCaptor adobeCallbackCaptor = ArgumentCaptor.forClass(AdobeCallback.class); + final ArgumentCaptor extensionErrorCallbackCaptor = ArgumentCaptor.forClass(ExtensionErrorCallback.class); + final List callbackReturnValues = new ArrayList<>(); + + // test + IdentityEdge.getExperienceCloudId(new AdobeCallback() { + @Override + public void call(String s) { + callbackReturnValues.add(s); + } + }); + + // verify + PowerMockito.verifyStatic(MobileCore.class, Mockito.times(1)); + MobileCore.dispatchEventWithResponseCallback(eventCaptor.capture(), adobeCallbackCaptor.capture(), extensionErrorCallbackCaptor.capture()); + + // verify the dispatched event details + Event dispatchedEvent = eventCaptor.getValue(); + assertEquals(IdentityEdgeConstants.EventNames.IDENTITY_REQUEST_IDENTITY_ECID, dispatchedEvent.getName()); + assertEquals(IdentityEdgeConstants.EventType.IDENTITY_EDGE.toLowerCase(), dispatchedEvent.getType()); + assertEquals(IdentityEdgeConstants.EventSource.REQUEST_IDENTITY.toLowerCase(), dispatchedEvent.getSource()); + assertTrue(dispatchedEvent.getEventData().isEmpty()); + + // verify callback responses + ECID ecid = new ECID(); + + Map ecidDict = new HashMap<>(); + ecidDict.put("id", ecid.toString()); + ArrayList ecidArr = new ArrayList<>(); + ecidArr.add(ecidDict); + Map identityMap = new HashMap<>(); + identityMap.put("ECID", ecidArr); + Map xdmData = new HashMap<>(); + xdmData.put("identityMap", identityMap); + + adobeCallbackCaptor.getValue().call(buildECIDResponseEvent(xdmData)); + assertEquals(ecid.toString(), callbackReturnValues.get(0)); + + // TODO - enable when ExtensionError creation is available + // should not crash on calling the callback + //extensionErrorCallback.error(ExtensionError.UNEXPECTED_ERROR); + } + + @Test + public void testGetExperienceCloudId_NullCallback() { + // test + IdentityEdge.getExperienceCloudId(null); + + // verify + PowerMockito.verifyStatic(MobileCore.class, Mockito.times(0)); + MobileCore.dispatchEventWithResponseCallback(any(Event.class), any(AdobeCallback.class), any(ExtensionErrorCallback.class)); + } + + @Test + public void testGetExperienceCloudId_NullResponseEvent() { + // setup + final String KEY_IS_ERRORCALLBACK_CALLED = "errorCallBackCalled"; + final String KEY_CAPTUREDERRORCALLBACK = "capturedErrorCallback"; + final Map errorCapture = new HashMap<>(); + final ArgumentCaptor adobeCallbackCaptor = ArgumentCaptor.forClass(AdobeCallback.class); + final AdobeCallbackWithError callbackWithError = new AdobeCallbackWithError() { + @Override + public void fail(AdobeError adobeError) { + errorCapture.put(KEY_IS_ERRORCALLBACK_CALLED, true); + errorCapture.put(KEY_CAPTUREDERRORCALLBACK, adobeError); + } + + @Override + public void call(Object o) { } + }; + + // test + IdentityEdge.getExperienceCloudId(callbackWithError); + + // verify if the event is dispatched + PowerMockito.verifyStatic(MobileCore.class, Mockito.times(1)); + MobileCore.dispatchEventWithResponseCallback(any(Event.class), adobeCallbackCaptor.capture(), any(ExtensionErrorCallback.class)); + + // set response event to null + adobeCallbackCaptor.getValue().call(null); + + // verify + assertTrue((boolean)errorCapture.get(KEY_IS_ERRORCALLBACK_CALLED)); + assertEquals(AdobeError.UNEXPECTED_ERROR, errorCapture.get(KEY_CAPTUREDERRORCALLBACK)); + } + + @Test + public void testGetExperienceCloudId_InvalidEventData() { + // setup + final String KEY_IS_ERRORCALLBACK_CALLED = "errorCallBackCalled"; + final String KEY_CAPTUREDERRORCALLBACK = "capturedErrorCallback"; + final Map errorCapture = new HashMap<>(); + final ArgumentCaptor adobeCallbackCaptor = ArgumentCaptor.forClass(AdobeCallback.class); + final AdobeCallbackWithError callbackWithError = new AdobeCallbackWithError() { + @Override + public void fail(AdobeError adobeError) { + errorCapture.put(KEY_IS_ERRORCALLBACK_CALLED, true); + errorCapture.put(KEY_CAPTUREDERRORCALLBACK, adobeError); + } + + @Override + public void call(Object o) { } + }; + + // test + IdentityEdge.getExperienceCloudId(callbackWithError); + + // verify if the event is dispatched + PowerMockito.verifyStatic(MobileCore.class, Mockito.times(1)); + MobileCore.dispatchEventWithResponseCallback(any(Event.class), adobeCallbackCaptor.capture(), any(ExtensionErrorCallback.class)); + + // set response event to null + Map eventData = new HashMap<>(); + eventData.put("someKey", "someValue"); + adobeCallbackCaptor.getValue().call(buildECIDResponseEvent(eventData)); + + // verify + assertTrue((boolean)errorCapture.get(KEY_IS_ERRORCALLBACK_CALLED)); + assertEquals(AdobeError.UNEXPECTED_ERROR, errorCapture.get(KEY_CAPTUREDERRORCALLBACK)); + } + + @Test + public void testGetExperienceCloudId_MissingECID() { + // setup + final String KEY_IS_ERRORCALLBACK_CALLED = "errorCallBackCalled"; + final String KEY_CAPTUREDERRORCALLBACK = "capturedErrorCallback"; + final Map errorCapture = new HashMap<>(); + final ArgumentCaptor adobeCallbackCaptor = ArgumentCaptor.forClass(AdobeCallback.class); + final AdobeCallbackWithError callbackWithError = new AdobeCallbackWithError() { + @Override + public void fail(AdobeError adobeError) { + errorCapture.put(KEY_IS_ERRORCALLBACK_CALLED, true); + errorCapture.put(KEY_CAPTUREDERRORCALLBACK, adobeError); + } + + @Override + public void call(Object o) { } + }; + + // test + IdentityEdge.getExperienceCloudId(callbackWithError); + + // verify if the event is dispatched + PowerMockito.verifyStatic(MobileCore.class, Mockito.times(1)); + MobileCore.dispatchEventWithResponseCallback(any(Event.class), adobeCallbackCaptor.capture(), any(ExtensionErrorCallback.class)); + + // set response event to map missing ECID + Map emptyXDMData = new HashMap<>(); + adobeCallbackCaptor.getValue().call(buildECIDResponseEvent(emptyXDMData)); + + // verify + assertTrue((boolean)errorCapture.get(KEY_IS_ERRORCALLBACK_CALLED)); + assertEquals(AdobeError.UNEXPECTED_ERROR, errorCapture.get(KEY_CAPTUREDERRORCALLBACK)); + } + + // ======================================================================================== + // Private method + // ======================================================================================== + private Event buildECIDResponseEvent (final Map eventData) { + return new Event.Builder(IdentityEdgeConstants.EventNames.IDENTITY_REQUEST_IDENTITY_ECID, IdentityEdgeConstants.EventType.IDENTITY_EDGE, IdentityEdgeConstants.EventSource.RESPONSE_IDENTITY).setEventData(eventData).build(); + } + +} From 904c0568cb2b9e3eb5bb9f906a18db7b45cbcaf9 Mon Sep 17 00:00:00 2001 From: Nick Porter <43650450+nporter-adbe@users.noreply.github.com> Date: Thu, 11 Mar 2021 11:03:53 -0700 Subject: [PATCH 006/101] [AMSDK-11127] Reset Identities API (#6) * Add resetIdentties API * Add required constants * Add reset listener * Handle reset event * Add test for handleRequestEvent * Add ticket number in TODO * Update test_ListenersRegistration for new listener * Improve log and null check * Don't allow empty when setting shared state and assert ECID length on regeneration * improve assertion --- .../mobile/identityedge/IdentityEdge.java | 19 +++++ .../identityedge/IdentityEdgeConstants.java | 3 +- .../identityedge/IdentityEdgeExtension.java | 33 +++++++- .../identityedge/IdentityEdgeState.java | 13 +++ .../ListenerIdentityRequestReset.java | 65 +++++++++++++++ .../IdentityEdgeExtensionTests.java | 35 +++++++- .../identityedge/IdentityEdgeStateTests.java | 18 +++++ .../identityedge/IdentityEdgeTests.java | 27 ++++++- .../ListenerIdentityRequestResetTests.java | 81 +++++++++++++++++++ 9 files changed, 290 insertions(+), 4 deletions(-) create mode 100644 code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/ListenerIdentityRequestReset.java create mode 100644 code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/ListenerIdentityRequestResetTests.java diff --git a/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdge.java b/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdge.java index e4d19166..3c7be545 100644 --- a/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdge.java +++ b/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdge.java @@ -97,6 +97,25 @@ public void call(Event responseEvent) { }, errorCallback); } + /** + * Clears all Identity Edge identifiers and generates a new Experience Cloud ID (ECID). + */ + public static void resetIdentities() { + final Event event = new Event.Builder(IdentityEdgeConstants.EventNames.REQUEST_RESET, + IdentityEdgeConstants.EventType.IDENTITY_EDGE, + IdentityEdgeConstants.EventSource.REQUEST_RESET).build(); + + final ExtensionErrorCallback errorCallback = new ExtensionErrorCallback() { + @Override + public void error(final ExtensionError extensionError) { + MobileCore.log(LoggingMode.DEBUG, LOG_TAG, String.format("Failed to dispatch %s event: Error : %s.", IdentityEdgeConstants.EventNames.REQUEST_RESET, + extensionError.getErrorName())); + } + }; + + MobileCore.dispatchEvent(event, errorCallback); + } + /** * When an {@link AdobeCallbackWithError} is provided, the fail method will be called with provided {@link AdobeError}. * @param callback should not be null, should be instance of {@code AdobeCallbackWithError} diff --git a/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeConstants.java b/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeConstants.java index 78c06085..bbd6fe00 100644 --- a/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeConstants.java +++ b/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeConstants.java @@ -21,8 +21,8 @@ class IdentityEdgeConstants { final class EventSource { static final String REQUEST_IDENTITY = "com.adobe.eventSource.requestIdentity"; static final String REQUEST_CONTENT = "com.adobe.eventSource.requestContent"; - static final String RESPONSE_CONTENT = "com.adobe.eventSource.responseContent"; static final String RESPONSE_IDENTITY = "com.adobe.eventSource.responseIdentity"; + static final String REQUEST_RESET = "com.adobe.eventSource.requestReset"; private EventSource() { } } @@ -36,6 +36,7 @@ private EventType() { } final class EventNames { static final String IDENTITY_REQUEST_IDENTITY_ECID = "Identity Edge Request ECID"; static final String IDENTITY_RESPONSE_CONTENT_ONE_TIME = "Identity Edge Response Content One Time"; + static final String REQUEST_RESET = "Identity Edge Request Reset"; private EventNames() { } } diff --git a/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeExtension.java b/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeExtension.java index ee349e6d..b2f5be6b 100644 --- a/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeExtension.java +++ b/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeExtension.java @@ -36,7 +36,9 @@ class IdentityEdgeExtension extends Extension { *
  • Listener {@link ListenerIdentityRequestIdentity} to listen for event with eventType {@link IdentityEdgeConstants.EventType#IDENTITY_EDGE} * and EventSource {@link IdentityEdgeConstants.EventSource#REQUEST_IDENTITY}
  • *
  • Listener {@link ListenerGenericIdentityRequestContent} to listen for event with eventType {@link IdentityEdgeConstants.EventType#GENERIC_IDENTITY} - * * and EventSource {@link IdentityEdgeConstants.EventSource#REQUEST_CONTENT}
  • + * and EventSource {@link IdentityEdgeConstants.EventSource#REQUEST_CONTENT} + *
  • Listener {@link ListenerIdentityRequestReset} to listen for event with eventType {@link IdentityEdgeConstants.EventType#IDENTITY_EDGE} + * and EventSource {@link IdentityEdgeConstants.EventSource#REQUEST_RESET}
  • * *

    * Thread : Background thread created by MobileCore @@ -55,6 +57,7 @@ public void error(final ExtensionError extensionError) { extensionApi.registerEventListener(IdentityEdgeConstants.EventType.IDENTITY_EDGE, IdentityEdgeConstants.EventSource.REQUEST_IDENTITY, ListenerIdentityRequestIdentity.class, listenerErrorCallback); extensionApi.registerEventListener(IdentityEdgeConstants.EventType.GENERIC_IDENTITY, IdentityEdgeConstants.EventSource.REQUEST_CONTENT, ListenerGenericIdentityRequestContent.class, listenerErrorCallback); + extensionApi.registerEventListener(IdentityEdgeConstants.EventType.IDENTITY_EDGE, IdentityEdgeConstants.EventSource.REQUEST_RESET, ListenerIdentityRequestReset.class, listenerErrorCallback); } /** @@ -105,6 +108,34 @@ public void error(ExtensionError extensionError) { }); } + /** + * Handles IdentityEdge request reset events. + * @param event the identity request reset event + */ + void handleRequestReset(final Event event) { + if (!canProcessEvents(event)) { + MobileCore.log(LoggingMode.DEBUG, LOG_TAG, "Unable to process request reset event. canProcessEvents returned false."); + return; + } + state.resetIdentifiers(); + + final ExtensionApi extensionApi = super.getApi(); + if (extensionApi == null ) { + MobileCore.log(LoggingMode.DEBUG, LOG_TAG, "ExtensionApi is null, unable to share XDM shared state for reset identities"); + return; + } + + // set the shared state + ExtensionErrorCallback errorCallback = new ExtensionErrorCallback() { + @Override + public void error(final ExtensionError extensionError) { + MobileCore.log(LoggingMode.DEBUG, LOG_TAG, String.format("Failed create XDM shared state. Error : %s.", extensionError.getErrorName())); + } + }; + + extensionApi.setXDMSharedEventState(state.getIdentityEdgeProperties().toXDMData(false), event, errorCallback); + } + /** * Determines if Identity Edge is ready to handle events, this is determined by if the Identity Edge extension has booted up * @param event An {@link Event} diff --git a/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeState.java b/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeState.java index da062ce1..58cc8b6a 100644 --- a/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeState.java +++ b/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeState.java @@ -72,4 +72,17 @@ boolean bootupIfReady() { return true; } + /** + * Clears all identities and regenerates a new ECID value, then saves the new identities to persistence. + */ + void resetIdentifiers() { + // TODO: AMSDK-11208 Determine if we should dispatch consent event + + identityProperties = new IdentityEdgeProperties(); + identityProperties.setECID(new ECID()); + IdentityEdgeStorageService.savePropertiesToPersistence(identityProperties); + + // TODO: AMSDK-11208 Use return value to tell IdentityEdge to dispatch consent ad id update + } + } diff --git a/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/ListenerIdentityRequestReset.java b/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/ListenerIdentityRequestReset.java new file mode 100644 index 00000000..80f58aa5 --- /dev/null +++ b/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/ListenerIdentityRequestReset.java @@ -0,0 +1,65 @@ +/* + Copyright 2021 Adobe. All rights reserved. + This file is licensed to you under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. You may obtain a copy + of the License at http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software distributed under + the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + OF ANY KIND, either express or implied. See the License for the specific language + governing permissions and limitations under the License. +*/ + +package com.adobe.marketing.mobile.identityedge; + +import com.adobe.marketing.mobile.Event; +import com.adobe.marketing.mobile.ExtensionApi; +import com.adobe.marketing.mobile.ExtensionListener; +import com.adobe.marketing.mobile.LoggingMode; +import com.adobe.marketing.mobile.MobileCore; + +public class ListenerIdentityRequestReset extends ExtensionListener { + /** + * Constructor. + * + * @param extensionApi an instance of {@link ExtensionApi} + * @param type the {@link String} eventType this listener is registered to handle + * @param source the {@link String} eventSource this listener is registered to handle + */ + ListenerIdentityRequestReset(final ExtensionApi extensionApi, final String type, final String source) { + super(extensionApi, type, source); + } + + + /** + * Method that gets called when event with event type {@link IdentityEdgeConstants.EventType#IDENTITY_EDGE} + * and with event source {@link IdentityEdgeConstants.EventSource#REQUEST_RESET} is dispatched through eventHub. + * + * @param event the identity reset request {@link Event} to be processed + */ + @Override + public void hear(final Event event) { + if (event == null) { + MobileCore.log(LoggingMode.DEBUG, IdentityEdgeConstants.LOG_TAG, "Event is null. Ignoring the event listened by ListenerIdentityRequestReset"); + return; + } + + final IdentityEdgeExtension parentExtension = getIdentityEdgeExtension(); + + if (parentExtension == null) { + MobileCore.log(LoggingMode.DEBUG, IdentityEdgeConstants.LOG_TAG, + "The parent extension, associated with the ListenerIdentityRequestReset is null, ignoring request identity reset event."); + return; + } + + parentExtension.handleRequestReset(event); + } + + /** + * Returns the parent extension associated with the listener. + * + * @return a {@link IdentityEdgeExtension} object registered with the eventHub + */ + IdentityEdgeExtension getIdentityEdgeExtension() { + return (IdentityEdgeExtension) getParentExtension(); + } +} diff --git a/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeExtensionTests.java b/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeExtensionTests.java index ad376397..d20abc66 100644 --- a/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeExtensionTests.java +++ b/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeExtensionTests.java @@ -32,6 +32,9 @@ import org.powermock.core.classloader.annotations.PrepareForTest; import org.powermock.modules.junit4.PowerMockRunner; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; import java.util.Map; import static junit.framework.TestCase.assertEquals; @@ -87,7 +90,7 @@ public void test_ListenersRegistration() { // constructor is called in the setup step() // verify 2 listeners are registered - verify(mockExtensionApi, times(2)).registerEventListener(anyString(), + verify(mockExtensionApi, times(3)).registerEventListener(anyString(), anyString(), any(Class.class), any(ExtensionErrorCallback.class)); // verify listeners are registered with correct event source and type @@ -95,6 +98,8 @@ public void test_ListenersRegistration() { eq(IdentityEdgeConstants.EventSource.REQUEST_IDENTITY), eq(ListenerIdentityRequestIdentity.class), callbackCaptor.capture()); verify(mockExtensionApi, times(1)).registerEventListener(eq(IdentityEdgeConstants.EventType.GENERIC_IDENTITY), eq(IdentityEdgeConstants.EventSource.REQUEST_CONTENT), eq(ListenerGenericIdentityRequestContent.class), callbackCaptor.capture()); + verify(mockExtensionApi, times(1)).registerEventListener(eq(IdentityEdgeConstants.EventType.IDENTITY_EDGE), + eq(IdentityEdgeConstants.EventSource.REQUEST_RESET), eq(ListenerIdentityRequestReset.class), callbackCaptor.capture()); // verify the callback ExtensionErrorCallback extensionErrorCallback = callbackCaptor.getValue(); @@ -208,6 +213,22 @@ public void test_handleIdentityRequest_noIdentifiers_emptyIdentityMap() { assertTrue(identityMap.isEmpty()); } + @Test + public void test_handleIdentityResetRequest() { + // setup + Event event = new Event.Builder("Test event", IdentityEdgeConstants.EventType.IDENTITY_EDGE, IdentityEdgeConstants.EventSource.REQUEST_RESET).build(); + final ArgumentCaptor sharedStateCaptor = ArgumentCaptor.forClass(Map.class); + + // test + extension.handleRequestReset(event); + + // verify + verify(mockExtensionApi, times(1)).setXDMSharedEventState(sharedStateCaptor.capture(), eq(event), any(ExtensionErrorCallback.class)); + Map sharedState = sharedStateCaptor.getValue(); + String sharedEcid = ecidFromIdentityMap(sharedState); + assertTrue(sharedEcid.length() > 0); + } + private void setupExistingIdentityEdgeProps(final ECID ecid) { IdentityEdgeProperties persistedProps = new IdentityEdgeProperties(); persistedProps.setECID(ecid); @@ -216,5 +237,17 @@ private void setupExistingIdentityEdgeProps(final ECID ecid) { Mockito.when(mockSharedPreference.getString(IdentityEdgeConstants.DataStoreKey.IDENTITY_PROPERTIES, null)).thenReturn(propsJSON); } + private String ecidFromIdentityMap(Map xdmMap) { + if (xdmMap == null) { return null; } + Map identityMap = (HashMap) xdmMap.get("identityMap"); + if (identityMap == null) { return null; } + List ecidArr = (ArrayList) identityMap.get("ECID"); + if (ecidArr == null) { return null; } + Map ecidDict = (HashMap) ecidArr.get(0); + if (ecidDict == null) { return null; } + String ecid = (String) ecidDict.get("id"); + return ecid; + } + } diff --git a/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeStateTests.java b/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeStateTests.java index 7a86ab8c..c926e9cf 100644 --- a/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeStateTests.java +++ b/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeStateTests.java @@ -28,6 +28,8 @@ import org.powermock.modules.junit4.PowerMockRunner; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; @@ -95,4 +97,20 @@ public void testIdentityEdgeState_BootupIfReadyLoadsFromPersistence() { assertTrue(result); assertEquals(persistedProps.getECID().toString(), state.getIdentityEdgeProperties().getECID().toString()); } + + @Test + public void testIdentityEdgeState_resetIdentifiers() { + // setup + IdentityEdgeState state = new IdentityEdgeState(new IdentityEdgeProperties()); + state.getIdentityEdgeProperties().setECID(new ECID()); + ECID existingEcid = state.getIdentityEdgeProperties().getECID(); + + // test + state.resetIdentifiers(); + + // verify + assertNotEquals(existingEcid.toString(), state.getIdentityEdgeProperties().getECID().toString()); // ECID should be regenerated + assertFalse(state.getIdentityEdgeProperties().getECID().toString().isEmpty()); // ECID should not be empty + verify(mockSharedPreferenceEditor, Mockito.times(1)).apply(); // should save to data store + } } diff --git a/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeTests.java b/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeTests.java index af3352b2..52a3fdd2 100644 --- a/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeTests.java +++ b/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeTests.java @@ -28,7 +28,6 @@ import org.powermock.core.classloader.annotations.PrepareForTest; import org.powermock.modules.junit4.PowerMockRunner; -import java.lang.reflect.Array; import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -244,6 +243,32 @@ public void call(Object o) { } assertEquals(AdobeError.UNEXPECTED_ERROR, errorCapture.get(KEY_CAPTUREDERRORCALLBACK)); } + // ======================================================================================== + // resetIdentities API + // ======================================================================================== + @Test + public void testResetIdentities() { + // setup + final ArgumentCaptor eventCaptor = ArgumentCaptor.forClass(Event.class); + final ArgumentCaptor adobeCallbackCaptor = ArgumentCaptor.forClass(AdobeCallback.class); + final ArgumentCaptor extensionErrorCallbackCaptor = ArgumentCaptor.forClass(ExtensionErrorCallback.class); + + + // test + IdentityEdge.resetIdentities(); + + // verify + PowerMockito.verifyStatic(MobileCore.class, Mockito.times(1)); + MobileCore.dispatchEvent(eventCaptor.capture(), extensionErrorCallbackCaptor.capture()); + + // verify the dispatched event details + Event dispatchedEvent = eventCaptor.getValue(); + assertEquals(IdentityEdgeConstants.EventNames.REQUEST_RESET, dispatchedEvent.getName()); + assertEquals(IdentityEdgeConstants.EventType.IDENTITY_EDGE.toLowerCase(), dispatchedEvent.getType()); + assertEquals(IdentityEdgeConstants.EventSource.REQUEST_RESET.toLowerCase(), dispatchedEvent.getSource()); + assertTrue(dispatchedEvent.getEventData().isEmpty()); + } + // ======================================================================================== // Private method // ======================================================================================== diff --git a/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/ListenerIdentityRequestResetTests.java b/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/ListenerIdentityRequestResetTests.java new file mode 100644 index 00000000..2076d736 --- /dev/null +++ b/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/ListenerIdentityRequestResetTests.java @@ -0,0 +1,81 @@ +/* + Copyright 2021 Adobe. All rights reserved. + This file is licensed to you under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. You may obtain a copy + of the License at http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software distributed under + the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + OF ANY KIND, either express or implied. See the License for the specific language + governing permissions and limitations under the License. +*/ + +package com.adobe.marketing.mobile.identityedge; + +import com.adobe.marketing.mobile.Event; +import com.adobe.marketing.mobile.MobileCore; + +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.Mockito; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +public class ListenerIdentityRequestResetTests { + @Mock + private IdentityEdgeExtension mockIdentityEdgeExtension; + + private ListenerIdentityRequestReset listener; + + @Before + public void setup() { + mockIdentityEdgeExtension = Mockito.mock(IdentityEdgeExtension.class); + MobileCore.start(null); + listener = spy(new ListenerIdentityRequestReset(null, IdentityEdgeConstants.EventType.IDENTITY_EDGE, IdentityEdgeConstants.EventSource.REQUEST_RESET)); + } + + @Test + public void testHear() { + // setup + Event event = new Event.Builder("Request Identity", IdentityEdgeConstants.EventType.IDENTITY, + IdentityEdgeConstants.EventSource.REQUEST_IDENTITY).build(); + doReturn(mockIdentityEdgeExtension).when(listener).getIdentityEdgeExtension(); + + // test + listener.hear(event); + + // verify + verify(mockIdentityEdgeExtension, times(1)).handleRequestReset(event); + } + + @Test + public void testHear_WhenParentExtensionNull() { + // setup + Event event = new Event.Builder("Request Identity", IdentityEdgeConstants.EventType.IDENTITY, + IdentityEdgeConstants.EventSource.REQUEST_IDENTITY).build(); + doReturn(null).when(listener).getIdentityEdgeExtension(); + + // test + listener.hear(event); + + // verify + verify(mockIdentityEdgeExtension, times(0)).handleRequestReset(any(Event.class)); + } + + @Test + public void testHear_WhenEventNull() { + // setup + doReturn(null).when(listener).getIdentityEdgeExtension(); + doReturn(mockIdentityEdgeExtension).when(listener).getIdentityEdgeExtension(); + + // test + listener.hear(null); + + // verify + verify(mockIdentityEdgeExtension, times(0)).handleRequestReset(any(Event.class)); + } +} From 8ffe5735ffb9c2e46594286133f04673b22d6de1 Mon Sep 17 00:00:00 2001 From: Pravin Prakash Kumar Date: Thu, 11 Mar 2021 16:33:03 -0800 Subject: [PATCH 007/101] [AMSDK-11081] - Update Identities public API (#7) * [AMSDK-11081] - Rename listener tests * [AMSDK-11081] - Listeners for remove and update Identity requests + tests * [AMSDK-11081] - UpdateIdentity Public API --- .../mobile/identityedge/IdentityEdge.java | 42 ++++++++-- .../identityedge/IdentityEdgeConstants.java | 4 + .../identityedge/IdentityEdgeExtension.java | 16 ++++ .../identityedge/IdentityEdgeState.java | 3 - .../mobile/identityedge/IdentityMap.java | 53 ++++++++---- .../ListenerIdentityEdgeRemoveIdentity.java | 66 +++++++++++++++ .../ListenerIdentityEdgeUpdateIdentity.java | 66 +++++++++++++++ .../IdentityEdgeExtensionTests.java | 10 ++- .../identityedge/IdentityEdgeTests.java | 43 ++++++++++ ...erGenericIdentityRequestContentTests.java} | 2 +- ...stenerIdentityEdgeRemoveIdentityTests.java | 82 +++++++++++++++++++ ...stenerIdentityEdgeUpdateIdentityTests.java | 82 +++++++++++++++++++ ...ListenerIdentityRequestIdentityTests.java} | 2 +- 13 files changed, 443 insertions(+), 28 deletions(-) create mode 100644 code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/ListenerIdentityEdgeRemoveIdentity.java create mode 100644 code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/ListenerIdentityEdgeUpdateIdentity.java rename code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/{ListenerGenericIdentityRequestContentTest.java => ListenerGenericIdentityRequestContentTests.java} (98%) create mode 100644 code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/ListenerIdentityEdgeRemoveIdentityTests.java create mode 100644 code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/ListenerIdentityEdgeUpdateIdentityTests.java rename code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/{ListenerIdentityRequestIdentityTest.java => ListenerIdentityRequestIdentityTests.java} (98%) diff --git a/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdge.java b/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdge.java index 3c7be545..22c71259 100644 --- a/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdge.java +++ b/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdge.java @@ -20,6 +20,8 @@ import com.adobe.marketing.mobile.LoggingMode; import com.adobe.marketing.mobile.MobileCore; +import java.util.Map; + public class IdentityEdge { private static final String LOG_TAG = "IdentityEdge"; @@ -27,6 +29,7 @@ private IdentityEdge() {} /** * Returns the version of the {@link IdentityEdge} extension + * * @return The version as {@code String} */ public static String extensionVersion() { @@ -48,9 +51,10 @@ public void error(ExtensionError extensionError) { /** * Returns the Experience Cloud ID. An empty string is returned if the Experience Cloud ID was previously cleared. - * @param callback {@link AdobeCallback} of {@link String} invoked with the Experience Cloud ID - * If an {@link AdobeCallbackWithError} is provided, an {@link AdobeError} can be returned in the - * eventuality of any error that occurred while getting the Experience Cloud ID + * + * @param callback {@link AdobeCallback} of {@link String} invoked with the Experience Cloud ID + * If an {@link AdobeCallbackWithError} is provided, an {@link AdobeError} can be returned in the + * eventuality of any error that occurred while getting the Experience Cloud ID */ public static void getExperienceCloudId(final AdobeCallback callback) { if (callback == null) { @@ -97,6 +101,33 @@ public void call(Event responseEvent) { }, errorCallback); } + /** + * Updates the currently known {@link IdentityMap} within the SDK and XDM shared state. + * The IdentityEdge extension will merge the received identifiers with the previously saved one in an additive manner, no identifiers will be removed using this API. + * + * @param identityMap The identifiers to add or update. + */ + public static void updateIdentities(final IdentityMap identityMap) { + if (identityMap == null || identityMap.toObjectMap().isEmpty()) { + MobileCore.log(LoggingMode.DEBUG, LOG_TAG, "Unable to updateIdentities, IdentityMap is null or empty"); + return; + } + + final ExtensionErrorCallback errorCallback = new ExtensionErrorCallback() { + @Override + public void error(final ExtensionError extensionError) { + MobileCore.log(LoggingMode.DEBUG, LOG_TAG, String.format("Consents.update() API. Failed to dispatch %s event: Error : %s.", IdentityEdgeConstants.EventNames.UPDATE_IDENTITIES, + extensionError.getErrorName())); + } + }; + + + final Event updateIdentitiesEvent = new Event.Builder(IdentityEdgeConstants.EventNames.UPDATE_IDENTITIES, + IdentityEdgeConstants.EventType.IDENTITY_EDGE, + IdentityEdgeConstants.EventSource.UPDATE_IDENTITY).setEventData(identityMap.asEventData()).build(); + MobileCore.dispatchEvent(updateIdentitiesEvent, errorCallback); + } + /** * Clears all Identity Edge identifiers and generates a new Experience Cloud ID (ECID). */ @@ -118,10 +149,11 @@ public void error(final ExtensionError extensionError) { /** * When an {@link AdobeCallbackWithError} is provided, the fail method will be called with provided {@link AdobeError}. + * * @param callback should not be null, should be instance of {@code AdobeCallbackWithError} - * @param error the {@code AdobeError} returned back in the callback + * @param error the {@code AdobeError} returned back in the callback */ - private static void returnError (final AdobeCallback callback, final AdobeError error) { + private static void returnError(final AdobeCallback callback, final AdobeError error) { if (callback == null) { return; } diff --git a/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeConstants.java b/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeConstants.java index bbd6fe00..4f8507e2 100644 --- a/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeConstants.java +++ b/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeConstants.java @@ -22,6 +22,8 @@ final class EventSource { static final String REQUEST_IDENTITY = "com.adobe.eventSource.requestIdentity"; static final String REQUEST_CONTENT = "com.adobe.eventSource.requestContent"; static final String RESPONSE_IDENTITY = "com.adobe.eventSource.responseIdentity"; + static final String UPDATE_IDENTITY = "com.adobe.eventSource.updateIdentity"; + static final String REMOVE_IDENTITY = "com.adobe.eventSource.removeIdentity"; static final String REQUEST_RESET = "com.adobe.eventSource.requestReset"; private EventSource() { } } @@ -36,6 +38,8 @@ private EventType() { } final class EventNames { static final String IDENTITY_REQUEST_IDENTITY_ECID = "Identity Edge Request ECID"; static final String IDENTITY_RESPONSE_CONTENT_ONE_TIME = "Identity Edge Response Content One Time"; + static final String UPDATE_IDENTITIES = "Identity Edge Update Identities"; + static final String REMOVE_IDENTITIES = "Idetity Edge Remove Identities"; static final String REQUEST_RESET = "Identity Edge Request Reset"; private EventNames() { } } diff --git a/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeExtension.java b/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeExtension.java index b2f5be6b..9b4f8ae9 100644 --- a/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeExtension.java +++ b/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeExtension.java @@ -37,6 +37,11 @@ class IdentityEdgeExtension extends Extension { * and EventSource {@link IdentityEdgeConstants.EventSource#REQUEST_IDENTITY} *
  • Listener {@link ListenerGenericIdentityRequestContent} to listen for event with eventType {@link IdentityEdgeConstants.EventType#GENERIC_IDENTITY} * and EventSource {@link IdentityEdgeConstants.EventSource#REQUEST_CONTENT}
  • + *
  • Listener {@link ListenerIdentityEdgeUpdateIdentity} to listen for event with eventType {@link IdentityEdgeConstants.EventType#IDENTITY_EDGE} + * and EventSource {@link IdentityEdgeConstants.EventSource#UPDATE_IDENTITY}
  • + *
  • Listener {@link ListenerIdentityEdgeRemoveIdentity} to listen for event with eventType {@link IdentityEdgeConstants.EventType#IDENTITY_EDGE} + * and EventSource {@link IdentityEdgeConstants.EventSource#REMOVE_IDENTITY}
  • + * and EventSource {@link IdentityEdgeConstants.EventSource#REQUEST_CONTENT} *
  • Listener {@link ListenerIdentityRequestReset} to listen for event with eventType {@link IdentityEdgeConstants.EventType#IDENTITY_EDGE} * and EventSource {@link IdentityEdgeConstants.EventSource#REQUEST_RESET}
  • * @@ -57,6 +62,8 @@ public void error(final ExtensionError extensionError) { extensionApi.registerEventListener(IdentityEdgeConstants.EventType.IDENTITY_EDGE, IdentityEdgeConstants.EventSource.REQUEST_IDENTITY, ListenerIdentityRequestIdentity.class, listenerErrorCallback); extensionApi.registerEventListener(IdentityEdgeConstants.EventType.GENERIC_IDENTITY, IdentityEdgeConstants.EventSource.REQUEST_CONTENT, ListenerGenericIdentityRequestContent.class, listenerErrorCallback); + extensionApi.registerEventListener(IdentityEdgeConstants.EventType.IDENTITY_EDGE, IdentityEdgeConstants.EventSource.UPDATE_IDENTITY, ListenerIdentityEdgeUpdateIdentity.class, listenerErrorCallback); + extensionApi.registerEventListener(IdentityEdgeConstants.EventType.IDENTITY_EDGE, IdentityEdgeConstants.EventSource.REMOVE_IDENTITY, ListenerIdentityEdgeRemoveIdentity.class, listenerErrorCallback); extensionApi.registerEventListener(IdentityEdgeConstants.EventType.IDENTITY_EDGE, IdentityEdgeConstants.EventSource.REQUEST_RESET, ListenerIdentityRequestReset.class, listenerErrorCallback); } @@ -78,6 +85,15 @@ protected String getVersion() { return IdentityEdgeConstants.EXTENSION_VERSION; } + // TODO: Docme + void handleUpdateIdentities(final Event event) { + // TODO + } + + // TODO: Docme + void handleRemoveIdentity(final Event event) { + // TODO + } void handleGenericIdentityRequest(final Event event) { // TODO diff --git a/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeState.java b/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeState.java index 58cc8b6a..4e0b78df 100644 --- a/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeState.java +++ b/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeState.java @@ -11,12 +11,9 @@ package com.adobe.marketing.mobile.identityedge; -import com.adobe.marketing.mobile.Event; import com.adobe.marketing.mobile.LoggingMode; import com.adobe.marketing.mobile.MobileCore; -import com.adobe.marketing.mobile.MobilePrivacyStatus; -import java.util.Map; /** * Manages the business logic of the Identity Edge extension diff --git a/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityMap.java b/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityMap.java index 73758593..4c802b68 100644 --- a/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityMap.java +++ b/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityMap.java @@ -28,7 +28,7 @@ * @see IdentityMap Schema */ @SuppressWarnings("unused") -class IdentityMap { +public class IdentityMap { private static final String LOG_TAG = "IdentityMap"; private static final String JSON_KEY_ID = "id"; @@ -69,10 +69,11 @@ AuthenticationState fromString(final String state) { /** * Gets the IdentityItem for the namespace + * * @param namespace namespace for the id * @return IdentityItem for the namespace, null if not found */ - List> getIdentityItemForNamespace(final String namespace) { + List> getIdentityItemsForNamespace(final String namespace) { return identityItems.get(namespace); } @@ -81,12 +82,11 @@ List> getIdentityItemForNamespace(final String namespace) { * with digital experiences. * * @param namespace the namespace integration code or namespace ID of the identity - * @param id identity of the consumer in the related namespace - * @param state the state this identity is authenticated as for this observed ExperienceEvent. - * Default is {@link AuthenticationState.AMBIGUOUS}. - * @param primary Indicates this identity is the preferred identity. Is used as a hint to help - * systems better organize how identities are queried. Default is false. - * + * @param id identity of the consumer in the related namespace + * @param state the state this identity is authenticated as for this observed ExperienceEvent. + * Default is {@link AuthenticationState#AMBIGUOUS}. + * @param primary Indicates this identity is the preferred identity. Is used as a hint to help + * systems better organize how identities are queried. Default is false. * @see IdentityItem Schema */ void addItem(final String namespace, final String id, final AuthenticationState state, final boolean primary) { @@ -117,8 +117,7 @@ void addItem(final String namespace, final String id, final AuthenticationState * with digital experiences. Uses default authentication state and primary as defined by the Experience Platform. * * @param namespace the namespace integration code or namespace ID of the identity - * @param id identity of the consumer in the related namespace - * + * @param id identity of the consumer in the related namespace * @see IdentityItem Schema */ void addItem(final String namespace, final String id) { @@ -157,17 +156,30 @@ Map>> toObjectMap() { return identityItems; } + /** + * Use this method to cast the {@code IdentityMap} as eventData for an SDK Event. + * + * @return {@link Map} representation of IdentityMap + */ + Map asEventData() { + return new HashMap(identityItems); + } + static IdentityMap fromData(Map data) { - if (data == null) { return null; } + if (data == null) { + return null; + } final Map identityMapDict = (HashMap) data.get(IdentityEdgeConstants.XDMKeys.IDENTITY_MAP); - if (identityMapDict == null) { return null; } + if (identityMapDict == null) { + return null; + } final IdentityMap identityMap = new IdentityMap(); for (String namespace : identityMapDict.keySet()) { try { final ArrayList> idArr = (ArrayList>) identityMapDict.get(namespace); - for (Object idMap: idArr) { + for (Object idMap : idArr) { identityMap.addItemToMap(namespace, (Map) idMap); } } catch (ClassCastException e) { @@ -180,13 +192,18 @@ static IdentityMap fromData(Map data) { /** * Reads the ECID from an IdentityMap + * * @return ECID stored in the IdentityMap or null if not found */ ECID getFirstECID() { - final List> ecidArr = getIdentityItemForNamespace(IdentityEdgeConstants.Namespaces.ECID); - if (ecidArr == null) { return null; } + final List> ecidArr = getIdentityItemsForNamespace(IdentityEdgeConstants.Namespaces.ECID); + if (ecidArr == null) { + return null; + } final Map ecidDict = ecidArr.get(0); - if (ecidDict == null) { return null; } + if (ecidDict == null) { + return null; + } String ecidStr = null; try { ecidStr = (String) ecidDict.get(IdentityEdgeConstants.XDMKeys.ID); @@ -194,7 +211,9 @@ ECID getFirstECID() { MobileCore.log(LoggingMode.DEBUG, LOG_TAG, "Failed to create read ECID from IdentityMap"); } - if (ecidStr == null) { return null; } + if (ecidStr == null) { + return null; + } return new ECID(ecidStr); } } \ No newline at end of file diff --git a/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/ListenerIdentityEdgeRemoveIdentity.java b/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/ListenerIdentityEdgeRemoveIdentity.java new file mode 100644 index 00000000..592f126c --- /dev/null +++ b/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/ListenerIdentityEdgeRemoveIdentity.java @@ -0,0 +1,66 @@ +/* + Copyright 2021 Adobe. All rights reserved. + This file is licensed to you under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. You may obtain a copy + of the License at http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software distributed under + the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + OF ANY KIND, either express or implied. See the License for the specific language + governing permissions and limitations under the License. +*/ + +package com.adobe.marketing.mobile.identityedge; + +import com.adobe.marketing.mobile.Event; +import com.adobe.marketing.mobile.ExtensionApi; +import com.adobe.marketing.mobile.ExtensionListener; +import com.adobe.marketing.mobile.LoggingMode; +import com.adobe.marketing.mobile.MobileCore; + +public class ListenerIdentityEdgeRemoveIdentity extends ExtensionListener { + + /** + * Constructor. + * + * @param extensionApi an instance of {@link ExtensionApi} + * @param type the {@link String} eventType this listener is registered to handle + * @param source the {@link String} eventSource this listener is registered to handle + */ + ListenerIdentityEdgeRemoveIdentity(final ExtensionApi extensionApi, final String type, final String source) { + super(extensionApi, type, source); + } + + + /** + * Method that gets called when event with event type {@link IdentityEdgeConstants.EventType#IDENTITY_EDGE} + * and with event source {@link IdentityEdgeConstants.EventSource#REMOVE_IDENTITY} is dispatched through eventHub. + * + * @param event the remove identity {@link Event} to be processed + */ + @Override + public void hear(final Event event) { + if (event == null || event.getEventData() == null) { + MobileCore.log(LoggingMode.DEBUG, IdentityEdgeConstants.LOG_TAG, "Event or Event data is null. Ignoring the event listened by ListenerIdentityEdgeRemoveIdentity"); + return; + } + + final IdentityEdgeExtension parentExtension = getIdentityEdgeExtension(); + + if (parentExtension == null) { + MobileCore.log(LoggingMode.DEBUG, IdentityEdgeConstants.LOG_TAG, + "The parent extension, associated with the ListenerIdentityEdgeRemoveIdentity is null, ignoring event."); + return; + } + + parentExtension.handleRemoveIdentity(event); + } + + /** + * Returns the parent extension associated with the listener. + * + * @return a {@link IdentityEdgeExtension} object registered with the eventHub + */ + IdentityEdgeExtension getIdentityEdgeExtension() { + return (IdentityEdgeExtension) getParentExtension(); + } +} diff --git a/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/ListenerIdentityEdgeUpdateIdentity.java b/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/ListenerIdentityEdgeUpdateIdentity.java new file mode 100644 index 00000000..094a2645 --- /dev/null +++ b/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/ListenerIdentityEdgeUpdateIdentity.java @@ -0,0 +1,66 @@ +/* + Copyright 2021 Adobe. All rights reserved. + This file is licensed to you under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. You may obtain a copy + of the License at http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software distributed under + the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + OF ANY KIND, either express or implied. See the License for the specific language + governing permissions and limitations under the License. +*/ + +package com.adobe.marketing.mobile.identityedge; + +import com.adobe.marketing.mobile.Event; +import com.adobe.marketing.mobile.ExtensionApi; +import com.adobe.marketing.mobile.ExtensionListener; +import com.adobe.marketing.mobile.LoggingMode; +import com.adobe.marketing.mobile.MobileCore; + +public class ListenerIdentityEdgeUpdateIdentity extends ExtensionListener { + + /** + * Constructor. + * + * @param extensionApi an instance of {@link ExtensionApi} + * @param type the {@link String} eventType this listener is registered to handle + * @param source the {@link String} eventSource this listener is registered to handle + */ + ListenerIdentityEdgeUpdateIdentity(final ExtensionApi extensionApi, final String type, final String source) { + super(extensionApi, type, source); + } + + + /** + * Method that gets called when event with event type {@link IdentityEdgeConstants.EventType#IDENTITY_EDGE} + * and with event source {@link IdentityEdgeConstants.EventSource#UPDATE_IDENTITY} is dispatched through eventHub. + * + * @param event the udpate identity {@link Event} to be processed + */ + @Override + public void hear(final Event event) { + if (event == null || event.getEventData() == null) { + MobileCore.log(LoggingMode.DEBUG, IdentityEdgeConstants.LOG_TAG, "Event or Event data is null. Ignoring the event listened by ListenerIdentityEdgeUpdateIdentity"); + return; + } + + final IdentityEdgeExtension parentExtension = getIdentityEdgeExtension(); + + if (parentExtension == null) { + MobileCore.log(LoggingMode.DEBUG, IdentityEdgeConstants.LOG_TAG, + "The parent extension, associated with the ListenerIdentityEdgeUpdateIdentity is null, ignoring event."); + return; + } + + parentExtension.handleUpdateIdentities(event); + } + + /** + * Returns the parent extension associated with the listener. + * + * @return a {@link IdentityEdgeExtension} object registered with the eventHub + */ + IdentityEdgeExtension getIdentityEdgeExtension() { + return (IdentityEdgeExtension) getParentExtension(); + } +} diff --git a/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeExtensionTests.java b/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeExtensionTests.java index d20abc66..a90616bb 100644 --- a/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeExtensionTests.java +++ b/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeExtensionTests.java @@ -90,7 +90,7 @@ public void test_ListenersRegistration() { // constructor is called in the setup step() // verify 2 listeners are registered - verify(mockExtensionApi, times(3)).registerEventListener(anyString(), + verify(mockExtensionApi, times(5)).registerEventListener(anyString(), anyString(), any(Class.class), any(ExtensionErrorCallback.class)); // verify listeners are registered with correct event source and type @@ -98,6 +98,10 @@ public void test_ListenersRegistration() { eq(IdentityEdgeConstants.EventSource.REQUEST_IDENTITY), eq(ListenerIdentityRequestIdentity.class), callbackCaptor.capture()); verify(mockExtensionApi, times(1)).registerEventListener(eq(IdentityEdgeConstants.EventType.GENERIC_IDENTITY), eq(IdentityEdgeConstants.EventSource.REQUEST_CONTENT), eq(ListenerGenericIdentityRequestContent.class), callbackCaptor.capture()); + verify(mockExtensionApi, times(1)).registerEventListener(eq(IdentityEdgeConstants.EventType.IDENTITY_EDGE), + eq(IdentityEdgeConstants.EventSource.UPDATE_IDENTITY), eq(ListenerIdentityEdgeUpdateIdentity.class), callbackCaptor.capture()); + verify(mockExtensionApi, times(1)).registerEventListener(eq(IdentityEdgeConstants.EventType.IDENTITY_EDGE), + eq(IdentityEdgeConstants.EventSource.REMOVE_IDENTITY), eq(ListenerIdentityEdgeRemoveIdentity.class), callbackCaptor.capture()); verify(mockExtensionApi, times(1)).registerEventListener(eq(IdentityEdgeConstants.EventType.IDENTITY_EDGE), eq(IdentityEdgeConstants.EventSource.REQUEST_RESET), eq(ListenerIdentityRequestReset.class), callbackCaptor.capture()); @@ -229,6 +233,10 @@ public void test_handleIdentityResetRequest() { assertTrue(sharedEcid.length() > 0); } + // ======================================================================================== + // private helper methods + // ======================================================================================== + private void setupExistingIdentityEdgeProps(final ECID ecid) { IdentityEdgeProperties persistedProps = new IdentityEdgeProperties(); persistedProps.setECID(ecid); diff --git a/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeTests.java b/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeTests.java index 52a3fdd2..3253ef0c 100644 --- a/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeTests.java +++ b/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeTests.java @@ -269,6 +269,49 @@ public void testResetIdentities() { assertTrue(dispatchedEvent.getEventData().isEmpty()); } + // ======================================================================================== + // updateIdentities API + // ======================================================================================== + @Test + public void testUpdateIdentities() { + // setup + final ArgumentCaptor eventCaptor = ArgumentCaptor.forClass(Event.class); + final ArgumentCaptor extensionErrorCallbackCaptor = ArgumentCaptor.forClass(ExtensionErrorCallback.class); + + // test + IdentityMap map = new IdentityMap(); + map.addItem("mainspace", "id", IdentityMap.AuthenticationState.AUTHENTICATED, true); + map.addItem("secondspace", "idtwo", IdentityMap.AuthenticationState.LOGGED_OUT, false); + IdentityEdge.updateIdentities(map); + + // verify + PowerMockito.verifyStatic(MobileCore.class, Mockito.times(1)); + MobileCore.dispatchEvent(eventCaptor.capture(), extensionErrorCallbackCaptor.capture()); + + // TODO - enable when ExtensionError creation is available + // should not crash on calling the callback + //extensionErrorCallback.error(ExtensionError.UNEXPECTED_ERROR); + + // verify the dispatched event details + Event dispatchedEvent = eventCaptor.getValue(); + assertEquals(IdentityEdgeConstants.EventNames.UPDATE_IDENTITIES,dispatchedEvent.getName()); + assertEquals(IdentityEdgeConstants.EventType.IDENTITY_EDGE.toLowerCase(),dispatchedEvent.getType()); + assertEquals(IdentityEdgeConstants.EventSource.UPDATE_IDENTITY.toLowerCase(),dispatchedEvent.getSource()); + assertEquals(map.asEventData(),dispatchedEvent.getEventData()); + } + + @Test + public void testUpdateIdentitiesNullAndEmptyMap() { + // test + IdentityMap map = new IdentityMap(); + IdentityEdge.updateIdentities(map); + IdentityEdge.updateIdentities(null); + + // verify no of these API calls dispatch an event + PowerMockito.verifyStatic(MobileCore.class, Mockito.times(0)); + MobileCore.dispatchEvent(any(Event.class), any(ExtensionErrorCallback.class)); + } + // ======================================================================================== // Private method // ======================================================================================== diff --git a/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/ListenerGenericIdentityRequestContentTest.java b/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/ListenerGenericIdentityRequestContentTests.java similarity index 98% rename from code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/ListenerGenericIdentityRequestContentTest.java rename to code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/ListenerGenericIdentityRequestContentTests.java index a607a779..fd93f91b 100644 --- a/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/ListenerGenericIdentityRequestContentTest.java +++ b/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/ListenerGenericIdentityRequestContentTests.java @@ -25,7 +25,7 @@ import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; -public class ListenerGenericIdentityRequestContentTest { +public class ListenerGenericIdentityRequestContentTests { @Mock private IdentityEdgeExtension mockIdentityEdgeExtension; diff --git a/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/ListenerIdentityEdgeRemoveIdentityTests.java b/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/ListenerIdentityEdgeRemoveIdentityTests.java new file mode 100644 index 00000000..5b105b1d --- /dev/null +++ b/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/ListenerIdentityEdgeRemoveIdentityTests.java @@ -0,0 +1,82 @@ +/* + Copyright 2021 Adobe. All rights reserved. + This file is licensed to you under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. You may obtain a copy + of the License at http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software distributed under + the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + OF ANY KIND, either express or implied. See the License for the specific language + governing permissions and limitations under the License. +*/ + +package com.adobe.marketing.mobile.identityedge; + +import com.adobe.marketing.mobile.Event; +import com.adobe.marketing.mobile.MobileCore; + +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.Mockito; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +public class ListenerIdentityEdgeRemoveIdentityTests { + + @Mock + private IdentityEdgeExtension mockIdentityEdgeExtension; + + private ListenerIdentityEdgeRemoveIdentity listener; + + @Before + public void setup() { + mockIdentityEdgeExtension = Mockito.mock(IdentityEdgeExtension.class); + MobileCore.start(null); + listener = spy(new ListenerIdentityEdgeRemoveIdentity(null, IdentityEdgeConstants.EventType.IDENTITY_EDGE, IdentityEdgeConstants.EventSource.REMOVE_IDENTITY)); + } + + @Test + public void testHear() { + // setup + Event event = new Event.Builder("Remove Identity", IdentityEdgeConstants.EventType.IDENTITY_EDGE, + IdentityEdgeConstants.EventSource.REMOVE_IDENTITY).build(); + doReturn(mockIdentityEdgeExtension).when(listener).getIdentityEdgeExtension(); + + // test + listener.hear(event); + + // verify + verify(mockIdentityEdgeExtension, times(1)).handleRemoveIdentity(event); + } + + @Test + public void testHear_WhenParentExtensionNull() { + // setup + Event event = new Event.Builder("Remove Identity", IdentityEdgeConstants.EventType.IDENTITY_EDGE, + IdentityEdgeConstants.EventSource.REMOVE_IDENTITY).build(); + doReturn(null).when(listener).getIdentityEdgeExtension(); + + // test + listener.hear(event); + + // verify + verify(mockIdentityEdgeExtension, times(0)).handleRemoveIdentity(any(Event.class)); + } + + @Test + public void testHear_WhenEventNull() { + // setup + doReturn(null).when(listener).getIdentityEdgeExtension(); + doReturn(mockIdentityEdgeExtension).when(listener).getIdentityEdgeExtension(); + + // test + listener.hear(null); + + // verify + verify(mockIdentityEdgeExtension, times(0)).handleRemoveIdentity(any(Event.class)); + } +} diff --git a/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/ListenerIdentityEdgeUpdateIdentityTests.java b/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/ListenerIdentityEdgeUpdateIdentityTests.java new file mode 100644 index 00000000..762a88bf --- /dev/null +++ b/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/ListenerIdentityEdgeUpdateIdentityTests.java @@ -0,0 +1,82 @@ +/* + Copyright 2021 Adobe. All rights reserved. + This file is licensed to you under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. You may obtain a copy + of the License at http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software distributed under + the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + OF ANY KIND, either express or implied. See the License for the specific language + governing permissions and limitations under the License. +*/ + +package com.adobe.marketing.mobile.identityedge; + +import com.adobe.marketing.mobile.Event; +import com.adobe.marketing.mobile.MobileCore; + +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.Mockito; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +public class ListenerIdentityEdgeUpdateIdentityTests { + + @Mock + private IdentityEdgeExtension mockIdentityEdgeExtension; + + private ListenerIdentityEdgeUpdateIdentity listener; + + @Before + public void setup() { + mockIdentityEdgeExtension = Mockito.mock(IdentityEdgeExtension.class); + MobileCore.start(null); + listener = spy(new ListenerIdentityEdgeUpdateIdentity(null, IdentityEdgeConstants.EventType.IDENTITY_EDGE, IdentityEdgeConstants.EventSource.UPDATE_IDENTITY)); + } + + @Test + public void testHear() { + // setup + Event event = new Event.Builder("Update Identities", IdentityEdgeConstants.EventType.IDENTITY_EDGE, + IdentityEdgeConstants.EventSource.UPDATE_IDENTITY).build(); + doReturn(mockIdentityEdgeExtension).when(listener).getIdentityEdgeExtension(); + + // test + listener.hear(event); + + // verify + verify(mockIdentityEdgeExtension, times(1)).handleUpdateIdentities(event); + } + + @Test + public void testHear_WhenParentExtensionNull() { + // setup + Event event = new Event.Builder("Update Identities", IdentityEdgeConstants.EventType.IDENTITY_EDGE, + IdentityEdgeConstants.EventSource.UPDATE_IDENTITY).build(); + doReturn(null).when(listener).getIdentityEdgeExtension(); + + // test + listener.hear(event); + + // verify + verify(mockIdentityEdgeExtension, times(0)).handleUpdateIdentities(any(Event.class)); + } + + @Test + public void testHear_WhenEventNull() { + // setup + doReturn(null).when(listener).getIdentityEdgeExtension(); + doReturn(mockIdentityEdgeExtension).when(listener).getIdentityEdgeExtension(); + + // test + listener.hear(null); + + // verify + verify(mockIdentityEdgeExtension, times(0)).handleUpdateIdentities(any(Event.class)); + } +} diff --git a/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/ListenerIdentityRequestIdentityTest.java b/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/ListenerIdentityRequestIdentityTests.java similarity index 98% rename from code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/ListenerIdentityRequestIdentityTest.java rename to code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/ListenerIdentityRequestIdentityTests.java index 5d89f73f..3034a9e0 100644 --- a/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/ListenerIdentityRequestIdentityTest.java +++ b/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/ListenerIdentityRequestIdentityTests.java @@ -25,7 +25,7 @@ import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; -public class ListenerIdentityRequestIdentityTest { +public class ListenerIdentityRequestIdentityTests { @Mock private IdentityEdgeExtension mockIdentityEdgeExtension; From 7eefc910c0f4ac6cf69ade366dc0f7d95d9af7b6 Mon Sep 17 00:00:00 2001 From: Nick Porter <43650450+nporter-adbe@users.noreply.github.com> Date: Fri, 12 Mar 2021 12:55:50 -0700 Subject: [PATCH 008/101] Add IdentityItem to IdentityMap (#8) * Add IdentityItem * Add tests for identity item * add convince overloaded constructor * Throw IllegalArgumentException if id null and add test * Add override for hashCode * Update access levels and update API signatures in IdentityItem * Clean up merge * Fix java doc * fix java doc * invert expression * Deep copy on getIdentityItemsForNamespace * Invert params * Add throws to javadoc * move throws doc to bottom of comment * use @link for javadoc * Rename IdentityEdge event type to EdgeIdentity * Add final * Add test for equals * Fix assertion * Add import --- .../identityedge/AuthenticationState.java | 41 +++++ .../mobile/identityedge/IdentityEdge.java | 16 +- .../identityedge/IdentityEdgeConstants.java | 4 +- .../identityedge/IdentityEdgeExtension.java | 18 +-- .../identityedge/IdentityEdgeProperties.java | 9 +- .../mobile/identityedge/IdentityItem.java | 151 ++++++++++++++++++ .../mobile/identityedge/IdentityMap.java | 140 ++++------------ .../ListenerIdentityEdgeRemoveIdentity.java | 2 +- .../ListenerIdentityEdgeUpdateIdentity.java | 2 +- .../ListenerIdentityRequestReset.java | 2 +- .../IdentityEdgeExtensionTests.java | 24 +-- .../identityedge/IdentityEdgeTests.java | 12 +- .../identityedge/IdentityItemTests.java | 127 +++++++++++++++ ...stenerIdentityEdgeRemoveIdentityTests.java | 6 +- ...stenerIdentityEdgeUpdateIdentityTests.java | 6 +- .../ListenerIdentityRequestResetTests.java | 2 +- 16 files changed, 406 insertions(+), 156 deletions(-) create mode 100644 code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/AuthenticationState.java create mode 100644 code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityItem.java create mode 100644 code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/IdentityItemTests.java diff --git a/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/AuthenticationState.java b/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/AuthenticationState.java new file mode 100644 index 00000000..df6b0f46 --- /dev/null +++ b/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/AuthenticationState.java @@ -0,0 +1,41 @@ +/* + Copyright 2021 Adobe. All rights reserved. + This file is licensed to you under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. You may obtain a copy + of the License at http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software distributed under + the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + OF ANY KIND, either express or implied. See the License for the specific language + governing permissions and limitations under the License. +*/ + +package com.adobe.marketing.mobile.identityedge; + +/** + * Represents the authentication state for an {@link IdentityItem} + */ +public enum AuthenticationState { + AMBIGUOUS("ambiguous"), + AUTHENTICATED("authenticated"), + LOGGED_OUT("loggedOut"); + + private String name; + + private AuthenticationState(final String name) { + this.name = name; + } + + public String getName() { + return name; + } + + public static AuthenticationState fromString(final String state) { + if ("authenticated".equalsIgnoreCase(state)) { + return AUTHENTICATED; + } else if ("loggedOut".equalsIgnoreCase(state)) { + return LOGGED_OUT; + } else { + return AMBIGUOUS; + } + } +} \ No newline at end of file diff --git a/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdge.java b/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdge.java index 22c71259..0cbfe0f4 100644 --- a/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdge.java +++ b/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdge.java @@ -20,7 +20,7 @@ import com.adobe.marketing.mobile.LoggingMode; import com.adobe.marketing.mobile.MobileCore; -import java.util.Map; +import java.util.List; public class IdentityEdge { private static final String LOG_TAG = "IdentityEdge"; @@ -63,7 +63,7 @@ public static void getExperienceCloudId(final AdobeCallback callback) { } final Event event = new Event.Builder(IdentityEdgeConstants.EventNames.IDENTITY_REQUEST_IDENTITY_ECID, - IdentityEdgeConstants.EventType.IDENTITY_EDGE, + IdentityEdgeConstants.EventType.EDGE_IDENTITY, IdentityEdgeConstants.EventSource.REQUEST_IDENTITY).build(); final ExtensionErrorCallback errorCallback = new ExtensionErrorCallback() { @@ -90,11 +90,11 @@ public void call(Event responseEvent) { return; } - final ECID ecid = identityMap.getFirstECID(); - if (ecid != null) { - callback.call(ecid.toString()); - } else { + final List ecidItems = identityMap.getIdentityItemsForNamespace(IdentityEdgeConstants.Namespaces.ECID); + if (ecidItems == null || ecidItems.isEmpty() || ecidItems.get(0).getId() == null) { callback.call(""); + } else { + callback.call(ecidItems.get(0).getId()); } } @@ -123,7 +123,7 @@ public void error(final ExtensionError extensionError) { final Event updateIdentitiesEvent = new Event.Builder(IdentityEdgeConstants.EventNames.UPDATE_IDENTITIES, - IdentityEdgeConstants.EventType.IDENTITY_EDGE, + IdentityEdgeConstants.EventType.EDGE_IDENTITY, IdentityEdgeConstants.EventSource.UPDATE_IDENTITY).setEventData(identityMap.asEventData()).build(); MobileCore.dispatchEvent(updateIdentitiesEvent, errorCallback); } @@ -133,7 +133,7 @@ public void error(final ExtensionError extensionError) { */ public static void resetIdentities() { final Event event = new Event.Builder(IdentityEdgeConstants.EventNames.REQUEST_RESET, - IdentityEdgeConstants.EventType.IDENTITY_EDGE, + IdentityEdgeConstants.EventType.EDGE_IDENTITY, IdentityEdgeConstants.EventSource.REQUEST_RESET).build(); final ExtensionErrorCallback errorCallback = new ExtensionErrorCallback() { diff --git a/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeConstants.java b/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeConstants.java index 4f8507e2..05ac0a8d 100644 --- a/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeConstants.java +++ b/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeConstants.java @@ -30,7 +30,7 @@ private EventSource() { } final class EventType { static final String GENERIC_IDENTITY = "com.adobe.eventType.generic.identity"; - static final String IDENTITY_EDGE = "com.adobe.eventType.identityEdge"; + static final String EDGE_IDENTITY = "com.adobe.eventType.edgeIdentity"; static final String IDENTITY = "com.adobe.eventType.identity"; private EventType() { } } @@ -39,7 +39,7 @@ final class EventNames { static final String IDENTITY_REQUEST_IDENTITY_ECID = "Identity Edge Request ECID"; static final String IDENTITY_RESPONSE_CONTENT_ONE_TIME = "Identity Edge Response Content One Time"; static final String UPDATE_IDENTITIES = "Identity Edge Update Identities"; - static final String REMOVE_IDENTITIES = "Idetity Edge Remove Identities"; + static final String REMOVE_IDENTITIES = "Identity Edge Remove Identities"; static final String REQUEST_RESET = "Identity Edge Request Reset"; private EventNames() { } } diff --git a/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeExtension.java b/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeExtension.java index 9b4f8ae9..70e87b01 100644 --- a/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeExtension.java +++ b/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeExtension.java @@ -33,16 +33,16 @@ class IdentityEdgeExtension extends Extension { * Called during the Identity extension's registration. * The following listeners are registered during this extension's registration. *
      - *
    • Listener {@link ListenerIdentityRequestIdentity} to listen for event with eventType {@link IdentityEdgeConstants.EventType#IDENTITY_EDGE} + *
    • Listener {@link ListenerIdentityRequestIdentity} to listen for event with eventType {@link IdentityEdgeConstants.EventType#EDGE_IDENTITY} * and EventSource {@link IdentityEdgeConstants.EventSource#REQUEST_IDENTITY}
    • *
    • Listener {@link ListenerGenericIdentityRequestContent} to listen for event with eventType {@link IdentityEdgeConstants.EventType#GENERIC_IDENTITY} * and EventSource {@link IdentityEdgeConstants.EventSource#REQUEST_CONTENT}
    • - *
    • Listener {@link ListenerIdentityEdgeUpdateIdentity} to listen for event with eventType {@link IdentityEdgeConstants.EventType#IDENTITY_EDGE} + *
    • Listener {@link ListenerIdentityEdgeUpdateIdentity} to listen for event with eventType {@link IdentityEdgeConstants.EventType#EDGE_IDENTITY} * and EventSource {@link IdentityEdgeConstants.EventSource#UPDATE_IDENTITY}
    • - *
    • Listener {@link ListenerIdentityEdgeRemoveIdentity} to listen for event with eventType {@link IdentityEdgeConstants.EventType#IDENTITY_EDGE} + *
    • Listener {@link ListenerIdentityEdgeRemoveIdentity} to listen for event with eventType {@link IdentityEdgeConstants.EventType#EDGE_IDENTITY} * and EventSource {@link IdentityEdgeConstants.EventSource#REMOVE_IDENTITY}
    • * and EventSource {@link IdentityEdgeConstants.EventSource#REQUEST_CONTENT} - *
    • Listener {@link ListenerIdentityRequestReset} to listen for event with eventType {@link IdentityEdgeConstants.EventType#IDENTITY_EDGE} + *
    • Listener {@link ListenerIdentityRequestReset} to listen for event with eventType {@link IdentityEdgeConstants.EventType#EDGE_IDENTITY} * and EventSource {@link IdentityEdgeConstants.EventSource#REQUEST_RESET}
    • *
    *

    @@ -60,11 +60,11 @@ public void error(final ExtensionError extensionError) { } }; - extensionApi.registerEventListener(IdentityEdgeConstants.EventType.IDENTITY_EDGE, IdentityEdgeConstants.EventSource.REQUEST_IDENTITY, ListenerIdentityRequestIdentity.class, listenerErrorCallback); + extensionApi.registerEventListener(IdentityEdgeConstants.EventType.EDGE_IDENTITY, IdentityEdgeConstants.EventSource.REQUEST_IDENTITY, ListenerIdentityRequestIdentity.class, listenerErrorCallback); extensionApi.registerEventListener(IdentityEdgeConstants.EventType.GENERIC_IDENTITY, IdentityEdgeConstants.EventSource.REQUEST_CONTENT, ListenerGenericIdentityRequestContent.class, listenerErrorCallback); - extensionApi.registerEventListener(IdentityEdgeConstants.EventType.IDENTITY_EDGE, IdentityEdgeConstants.EventSource.UPDATE_IDENTITY, ListenerIdentityEdgeUpdateIdentity.class, listenerErrorCallback); - extensionApi.registerEventListener(IdentityEdgeConstants.EventType.IDENTITY_EDGE, IdentityEdgeConstants.EventSource.REMOVE_IDENTITY, ListenerIdentityEdgeRemoveIdentity.class, listenerErrorCallback); - extensionApi.registerEventListener(IdentityEdgeConstants.EventType.IDENTITY_EDGE, IdentityEdgeConstants.EventSource.REQUEST_RESET, ListenerIdentityRequestReset.class, listenerErrorCallback); + extensionApi.registerEventListener(IdentityEdgeConstants.EventType.EDGE_IDENTITY, IdentityEdgeConstants.EventSource.UPDATE_IDENTITY, ListenerIdentityEdgeUpdateIdentity.class, listenerErrorCallback); + extensionApi.registerEventListener(IdentityEdgeConstants.EventType.EDGE_IDENTITY, IdentityEdgeConstants.EventSource.REMOVE_IDENTITY, ListenerIdentityEdgeRemoveIdentity.class, listenerErrorCallback); + extensionApi.registerEventListener(IdentityEdgeConstants.EventType.EDGE_IDENTITY, IdentityEdgeConstants.EventSource.REQUEST_RESET, ListenerIdentityRequestReset.class, listenerErrorCallback); } /** @@ -108,7 +108,7 @@ void handleIdentityRequest(final Event event) { Map xdmData = state.getIdentityEdgeProperties().toXDMData(true); Event responseEvent = new Event.Builder(IdentityEdgeConstants.EventNames.IDENTITY_RESPONSE_CONTENT_ONE_TIME, - IdentityEdgeConstants.EventType.IDENTITY_EDGE, + IdentityEdgeConstants.EventType.EDGE_IDENTITY, IdentityEdgeConstants.EventSource.RESPONSE_IDENTITY) .setEventData(xdmData) .build(); diff --git a/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeProperties.java b/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeProperties.java index 58ecd2e6..99bad7e3 100644 --- a/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeProperties.java +++ b/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeProperties.java @@ -41,7 +41,11 @@ class IdentityEdgeProperties { IdentityMap identityMap = IdentityMap.fromData(xdmData); if (identityMap != null) { - ecid = identityMap.getFirstECID(); + final List ecidItems = identityMap.getIdentityItemsForNamespace(IdentityEdgeConstants.Namespaces.ECID); + boolean containsEcid = ecidItems != null && !ecidItems.isEmpty() && ecidItems.get(0).getId() != null; + if (containsEcid) { + ecid = new ECID(ecidItems.get(0).getId()); + } } } @@ -71,7 +75,8 @@ Map toXDMData(final boolean allowEmpty) { final IdentityMap identityMap = new IdentityMap(); if (ecid != null) { - identityMap.addItem(IdentityEdgeConstants.Namespaces.ECID, ecid.toString()); + IdentityItem ecidItem = new IdentityItem(ecid.toString()); + identityMap.addItem(IdentityEdgeConstants.Namespaces.ECID, ecidItem); } final Map>> dict = identityMap.toObjectMap(); diff --git a/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityItem.java b/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityItem.java new file mode 100644 index 00000000..4bb8e0d0 --- /dev/null +++ b/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityItem.java @@ -0,0 +1,151 @@ +/* + Copyright 2021 Adobe. All rights reserved. + This file is licensed to you under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. You may obtain a copy + of the License at http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software distributed under + the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + OF ANY KIND, either express or implied. See the License for the specific language + governing permissions and limitations under the License. +*/ + +package com.adobe.marketing.mobile.identityedge; + +import com.adobe.marketing.mobile.LoggingMode; +import com.adobe.marketing.mobile.MobileCore; + +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; + +/** + * Represents an identity item + */ +public final class IdentityItem { + private String id; + private AuthenticationState authenticationState; + private boolean primary; + + private static final String LOG_TAG = "IdentityItem"; + private static final String JSON_KEY_ID = "id"; + private static final String JSON_KEY_AUTHENTICATION_STATE = "authenticationState"; + private static final String JSON_KEY_PRIMARY = "primary"; + + /** + * Creates a new {@link IdentityItem} + * @param id id for the item + * @param authenticationState {@link AuthenticationState} for the item + * @param primary primary flag for the item + * @throws IllegalArgumentException if id is null + */ + public IdentityItem(final String id, final AuthenticationState authenticationState, final boolean primary) { + if (id == null) { + throw new IllegalArgumentException("id must be non-null"); + } + this.id = id; + this.authenticationState = authenticationState; + if (authenticationState == null) { + this.authenticationState = AuthenticationState.AMBIGUOUS; + } + this.primary = primary; + } + + /** + * Creates a new {@link IdentityItem} with default values + * authenticationState is set to AMBIGUOUS + * primary is set to false + * @param id the id for this {@link IdentityItem} + */ + public IdentityItem(final String id) { + this(id, AuthenticationState.AMBIGUOUS, false); + } + + /** + * Creates a copy of item + * @param item A {@link IdentityItem} to be copied + */ + public IdentityItem(final IdentityItem item) { + this(item.id, item.authenticationState, item.primary); + } + + /** + * Converts this object into a map representation + * @return this object in a map representation + */ + Map toObjectMap() { + Map map = new HashMap<>(); + if (id != null) { + map.put(JSON_KEY_ID, id); + } + + if (authenticationState != null) { + map.put(JSON_KEY_AUTHENTICATION_STATE, authenticationState.toString()); + } else { + map.put(JSON_KEY_AUTHENTICATION_STATE, AuthenticationState.AMBIGUOUS.toString()); + } + + map.put(JSON_KEY_PRIMARY, primary); + return map; + } + + /** + * @return The id for this identity item + */ + public String getId() { + return id; + } + + /** + * @return Current {@link AuthenticationState} for this item + */ + public AuthenticationState getAuthenticationState() { + return authenticationState; + } + + /** + * @return true if this item is primary, false otherwise + */ + public boolean isPrimary() { + return primary; + } + + /** + * Creates an {@link IdentityItem} from the data + * @param data the data representing an {@link IdentityItem} + * @return an initialized {@link IdentityItem} based on the data, null if data is invalid + */ + static IdentityItem fromData(final Map data) { + if (data == null) { return null; } + + try { + final String id = (String) data.get(JSON_KEY_ID); + AuthenticationState authenticationState = AuthenticationState.fromString((String) data.get(JSON_KEY_AUTHENTICATION_STATE)); + if (authenticationState == null) { + authenticationState = AuthenticationState.AMBIGUOUS; + } + + boolean primary = false; + if (data.get(JSON_KEY_PRIMARY) != null) { + primary = (boolean) data.get(JSON_KEY_PRIMARY); + } + + return new IdentityItem(id, authenticationState, primary); + } catch (ClassCastException e) { + MobileCore.log(LoggingMode.DEBUG, LOG_TAG, "Failed to create IdentityItem from data."); + return null; + } + } + + @Override + public boolean equals(final Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + IdentityItem that = (IdentityItem) o; + return id.equalsIgnoreCase(that.id); + } + + @Override + public int hashCode() { + return Objects.hash(id); + } +} diff --git a/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityMap.java b/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityMap.java index 4c802b68..8f621312 100644 --- a/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityMap.java +++ b/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityMap.java @@ -31,49 +31,24 @@ public class IdentityMap { private static final String LOG_TAG = "IdentityMap"; - private static final String JSON_KEY_ID = "id"; - private static final String JSON_KEY_AUTHENTICATION_STATE = "authenticationState"; - private static final String JSON_KEY_PRIMARY = "primary"; - - enum AuthenticationState { - AMBIGUOUS("ambiguous"), - AUTHENTICATED("authenticated"), - LOGGED_OUT("loggedOut"); - - private String name; - - AuthenticationState(final String name) { - this.name = name; - } - - String getName() { - return name; - } - - AuthenticationState fromString(final String state) { - if ("authenticated".equalsIgnoreCase(state)) { - return AUTHENTICATED; - } else if ("loggedOut".equalsIgnoreCase(state)) { - return LOGGED_OUT; - } else { - return AMBIGUOUS; - } - } - } - - private Map>> identityItems; + private Map> identityItems; IdentityMap() { identityItems = new HashMap<>(); } /** - * Gets the IdentityItem for the namespace + * Gets the {@link IdentityItem}s for the namespace * * @param namespace namespace for the id * @return IdentityItem for the namespace, null if not found */ - List> getIdentityItemsForNamespace(final String namespace) { + public List getIdentityItemsForNamespace(final String namespace) { + final List items = new ArrayList<>(); + for (IdentityItem item : identityItems.get(namespace)) { + items.add(new IdentityItem((item))); + } + return identityItems.get(namespace); } @@ -82,47 +57,13 @@ List> getIdentityItemsForNamespace(final String namespace) { * with digital experiences. * * @param namespace the namespace integration code or namespace ID of the identity - * @param id identity of the consumer in the related namespace - * @param state the state this identity is authenticated as for this observed ExperienceEvent. - * Default is {@link AuthenticationState#AMBIGUOUS}. - * @param primary Indicates this identity is the preferred identity. Is used as a hint to help - * systems better organize how identities are queried. Default is false. - * @see IdentityItem Schema - */ - void addItem(final String namespace, final String id, final AuthenticationState state, final boolean primary) { - if (Utils.isNullOrEmpty(id)) { - MobileCore.log(LoggingMode.DEBUG, LOG_TAG, "IdentityMap add item ignored as must contain a non-null/non-empty id."); - return; - } - - if (Utils.isNullOrEmpty(namespace)) { - MobileCore.log(LoggingMode.DEBUG, LOG_TAG, - "IdentityMap add item ignored as must contain a non-null/non-empty namespace."); - return; - } - - Map item = new HashMap<>(); - item.put(JSON_KEY_ID, id); - item.put(JSON_KEY_PRIMARY, primary); - - if (state != null) { - item.put(JSON_KEY_AUTHENTICATION_STATE, state.getName()); - } - - addItemToMap(namespace, item); - } - - /** - * Add an identity item which is used to clearly distinguish entities that are interacting - * with digital experiences. Uses default authentication state and primary as defined by the Experience Platform. + * @param item {@link IdentityItem} to be added to the namespace * - * @param namespace the namespace integration code or namespace ID of the identity - * @param id identity of the consumer in the related namespace * @see IdentityItem Schema */ - void addItem(final String namespace, final String id) { - if (Utils.isNullOrEmpty(id)) { - MobileCore.log(LoggingMode.DEBUG, LOG_TAG, "IdentityMap add item ignored as must contain a non-null/non-empty id."); + public void addItem(final String namespace, final IdentityItem item) { + if (item == null) { + MobileCore.log(LoggingMode.DEBUG, LOG_TAG, "IdentityMap add item ignored as must contain a non-null IdentityItem."); return; } @@ -132,15 +73,12 @@ void addItem(final String namespace, final String id) { return; } - Map item = new HashMap<>(); - item.put(JSON_KEY_ID, id); - addItemToMap(namespace, item); } - private void addItemToMap(final String namespace, final Map item) { + private void addItemToMap(final String namespace, final IdentityItem item) { // check if namespace exists - List> itemList; + final List itemList; if (identityItems.containsKey(namespace)) { itemList = identityItems.get(namespace); @@ -153,7 +91,19 @@ private void addItemToMap(final String namespace, final Map item } Map>> toObjectMap() { - return identityItems; + final Map>> map = new HashMap>>(); + + for (String namespace : identityItems.keySet()) { + final List> namespaceIds = new ArrayList<>(); + + for(IdentityItem identityItem: identityItems.get(namespace)) { + namespaceIds.add(identityItem.toObjectMap()); + } + + map.put(namespace, namespaceIds); + } + + return map; } /** @@ -179,8 +129,11 @@ static IdentityMap fromData(Map data) { for (String namespace : identityMapDict.keySet()) { try { final ArrayList> idArr = (ArrayList>) identityMapDict.get(namespace); - for (Object idMap : idArr) { - identityMap.addItemToMap(namespace, (Map) idMap); + for (Object idMap: idArr) { + final IdentityItem item = IdentityItem.fromData((Map) idMap); + if (item != null) { + identityMap.addItemToMap(namespace, item); + } } } catch (ClassCastException e) { MobileCore.log(LoggingMode.DEBUG, LOG_TAG, "Failed to create IdentityMap from data."); @@ -189,31 +142,4 @@ static IdentityMap fromData(Map data) { return identityMap; } - - /** - * Reads the ECID from an IdentityMap - * - * @return ECID stored in the IdentityMap or null if not found - */ - ECID getFirstECID() { - final List> ecidArr = getIdentityItemsForNamespace(IdentityEdgeConstants.Namespaces.ECID); - if (ecidArr == null) { - return null; - } - final Map ecidDict = ecidArr.get(0); - if (ecidDict == null) { - return null; - } - String ecidStr = null; - try { - ecidStr = (String) ecidDict.get(IdentityEdgeConstants.XDMKeys.ID); - } catch (ClassCastException e) { - MobileCore.log(LoggingMode.DEBUG, LOG_TAG, "Failed to create read ECID from IdentityMap"); - } - - if (ecidStr == null) { - return null; - } - return new ECID(ecidStr); - } -} \ No newline at end of file +} diff --git a/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/ListenerIdentityEdgeRemoveIdentity.java b/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/ListenerIdentityEdgeRemoveIdentity.java index 592f126c..c64de9e8 100644 --- a/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/ListenerIdentityEdgeRemoveIdentity.java +++ b/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/ListenerIdentityEdgeRemoveIdentity.java @@ -32,7 +32,7 @@ public class ListenerIdentityEdgeRemoveIdentity extends ExtensionListener { /** - * Method that gets called when event with event type {@link IdentityEdgeConstants.EventType#IDENTITY_EDGE} + * Method that gets called when event with event type {@link IdentityEdgeConstants.EventType#EDGE_IDENTITY} * and with event source {@link IdentityEdgeConstants.EventSource#REMOVE_IDENTITY} is dispatched through eventHub. * * @param event the remove identity {@link Event} to be processed diff --git a/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/ListenerIdentityEdgeUpdateIdentity.java b/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/ListenerIdentityEdgeUpdateIdentity.java index 094a2645..9caabfce 100644 --- a/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/ListenerIdentityEdgeUpdateIdentity.java +++ b/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/ListenerIdentityEdgeUpdateIdentity.java @@ -32,7 +32,7 @@ public class ListenerIdentityEdgeUpdateIdentity extends ExtensionListener { /** - * Method that gets called when event with event type {@link IdentityEdgeConstants.EventType#IDENTITY_EDGE} + * Method that gets called when event with event type {@link IdentityEdgeConstants.EventType#EDGE_IDENTITY} * and with event source {@link IdentityEdgeConstants.EventSource#UPDATE_IDENTITY} is dispatched through eventHub. * * @param event the udpate identity {@link Event} to be processed diff --git a/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/ListenerIdentityRequestReset.java b/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/ListenerIdentityRequestReset.java index 80f58aa5..13c3f4f7 100644 --- a/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/ListenerIdentityRequestReset.java +++ b/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/ListenerIdentityRequestReset.java @@ -31,7 +31,7 @@ public class ListenerIdentityRequestReset extends ExtensionListener { /** - * Method that gets called when event with event type {@link IdentityEdgeConstants.EventType#IDENTITY_EDGE} + * Method that gets called when event with event type {@link IdentityEdgeConstants.EventType#EDGE_IDENTITY} * and with event source {@link IdentityEdgeConstants.EventSource#REQUEST_RESET} is dispatched through eventHub. * * @param event the identity reset request {@link Event} to be processed diff --git a/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeExtensionTests.java b/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeExtensionTests.java index a90616bb..4e38284a 100644 --- a/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeExtensionTests.java +++ b/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeExtensionTests.java @@ -94,15 +94,15 @@ public void test_ListenersRegistration() { anyString(), any(Class.class), any(ExtensionErrorCallback.class)); // verify listeners are registered with correct event source and type - verify(mockExtensionApi, times(1)).registerEventListener(eq(IdentityEdgeConstants.EventType.IDENTITY_EDGE), + verify(mockExtensionApi, times(1)).registerEventListener(eq(IdentityEdgeConstants.EventType.EDGE_IDENTITY), eq(IdentityEdgeConstants.EventSource.REQUEST_IDENTITY), eq(ListenerIdentityRequestIdentity.class), callbackCaptor.capture()); verify(mockExtensionApi, times(1)).registerEventListener(eq(IdentityEdgeConstants.EventType.GENERIC_IDENTITY), eq(IdentityEdgeConstants.EventSource.REQUEST_CONTENT), eq(ListenerGenericIdentityRequestContent.class), callbackCaptor.capture()); - verify(mockExtensionApi, times(1)).registerEventListener(eq(IdentityEdgeConstants.EventType.IDENTITY_EDGE), + verify(mockExtensionApi, times(1)).registerEventListener(eq(IdentityEdgeConstants.EventType.EDGE_IDENTITY), eq(IdentityEdgeConstants.EventSource.UPDATE_IDENTITY), eq(ListenerIdentityEdgeUpdateIdentity.class), callbackCaptor.capture()); - verify(mockExtensionApi, times(1)).registerEventListener(eq(IdentityEdgeConstants.EventType.IDENTITY_EDGE), + verify(mockExtensionApi, times(1)).registerEventListener(eq(IdentityEdgeConstants.EventType.EDGE_IDENTITY), eq(IdentityEdgeConstants.EventSource.REMOVE_IDENTITY), eq(ListenerIdentityEdgeRemoveIdentity.class), callbackCaptor.capture()); - verify(mockExtensionApi, times(1)).registerEventListener(eq(IdentityEdgeConstants.EventType.IDENTITY_EDGE), + verify(mockExtensionApi, times(1)).registerEventListener(eq(IdentityEdgeConstants.EventType.EDGE_IDENTITY), eq(IdentityEdgeConstants.EventSource.REQUEST_RESET), eq(ListenerIdentityRequestReset.class), callbackCaptor.capture()); // verify the callback @@ -147,7 +147,7 @@ public void test_handleIdentityRequest_nullEvent_shouldNotThrow() { @Test public void test_handleIdentityRequest_generatesNewECID() { // setup - Event event = new Event.Builder("Test event", IdentityEdgeConstants.EventType.IDENTITY_EDGE, IdentityEdgeConstants.EventSource.REQUEST_IDENTITY).build(); + Event event = new Event.Builder("Test event", IdentityEdgeConstants.EventType.EDGE_IDENTITY, IdentityEdgeConstants.EventSource.REQUEST_IDENTITY).build(); final ArgumentCaptor responseEventCaptor = ArgumentCaptor.forClass(Event.class); final ArgumentCaptor requestEventCaptor = ArgumentCaptor.forClass(Event.class); @@ -161,10 +161,10 @@ public void test_handleIdentityRequest_generatesNewECID() { // verify response event containing ECID is dispatched Event ecidResponseEvent = responseEventCaptor.getAllValues().get(0); final IdentityMap identityMap = IdentityMap.fromData(ecidResponseEvent.getEventData()); - final ECID ecid = identityMap.getFirstECID(); + final String ecid = identityMap.getIdentityItemsForNamespace("ECID").get(0).getId(); assertNotNull(ecid); - assertTrue(ecid.toString().length() > 0); + assertTrue(ecid.length() > 0); } @Test @@ -173,7 +173,7 @@ public void test_handleIdentityRequest_loadsPersistedECID() { final ECID existingECID = new ECID(); setupExistingIdentityEdgeProps(existingECID); - Event event = new Event.Builder("Test event", IdentityEdgeConstants.EventType.IDENTITY_EDGE, IdentityEdgeConstants.EventSource.REQUEST_IDENTITY).build(); + Event event = new Event.Builder("Test event", IdentityEdgeConstants.EventType.EDGE_IDENTITY, IdentityEdgeConstants.EventSource.REQUEST_IDENTITY).build(); final ArgumentCaptor responseEventCaptor = ArgumentCaptor.forClass(Event.class); final ArgumentCaptor requestEventCaptor = ArgumentCaptor.forClass(Event.class); @@ -187,9 +187,9 @@ public void test_handleIdentityRequest_loadsPersistedECID() { // verify response event containing ECID is dispatched Event ecidResponseEvent = responseEventCaptor.getAllValues().get(0); final IdentityMap identityMap = IdentityMap.fromData(ecidResponseEvent.getEventData()); - final ECID ecid = identityMap.getFirstECID(); + final String ecid = identityMap.getIdentityItemsForNamespace("ECID").get(0).getId(); - assertEquals(existingECID.toString(), ecid.toString()); + assertEquals(existingECID.toString(), ecid); } @Test @@ -198,7 +198,7 @@ public void test_handleIdentityRequest_noIdentifiers_emptyIdentityMap() { IdentityEdgeProperties emptyProps = new IdentityEdgeProperties(); PowerMockito.stub(PowerMockito.method(IdentityEdgeState.class, "getIdentityEdgeProperties")).toReturn(emptyProps); - Event event = new Event.Builder("Test event", IdentityEdgeConstants.EventType.IDENTITY_EDGE, IdentityEdgeConstants.EventSource.REQUEST_IDENTITY).build(); + Event event = new Event.Builder("Test event", IdentityEdgeConstants.EventType.EDGE_IDENTITY, IdentityEdgeConstants.EventSource.REQUEST_IDENTITY).build(); final ArgumentCaptor responseEventCaptor = ArgumentCaptor.forClass(Event.class); final ArgumentCaptor requestEventCaptor = ArgumentCaptor.forClass(Event.class); @@ -220,7 +220,7 @@ public void test_handleIdentityRequest_noIdentifiers_emptyIdentityMap() { @Test public void test_handleIdentityResetRequest() { // setup - Event event = new Event.Builder("Test event", IdentityEdgeConstants.EventType.IDENTITY_EDGE, IdentityEdgeConstants.EventSource.REQUEST_RESET).build(); + Event event = new Event.Builder("Test event", IdentityEdgeConstants.EventType.EDGE_IDENTITY, IdentityEdgeConstants.EventSource.REQUEST_RESET).build(); final ArgumentCaptor sharedStateCaptor = ArgumentCaptor.forClass(Map.class); // test diff --git a/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeTests.java b/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeTests.java index 3253ef0c..d4f65675 100644 --- a/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeTests.java +++ b/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeTests.java @@ -107,7 +107,7 @@ public void call(String s) { // verify the dispatched event details Event dispatchedEvent = eventCaptor.getValue(); assertEquals(IdentityEdgeConstants.EventNames.IDENTITY_REQUEST_IDENTITY_ECID, dispatchedEvent.getName()); - assertEquals(IdentityEdgeConstants.EventType.IDENTITY_EDGE.toLowerCase(), dispatchedEvent.getType()); + assertEquals(IdentityEdgeConstants.EventType.EDGE_IDENTITY.toLowerCase(), dispatchedEvent.getType()); assertEquals(IdentityEdgeConstants.EventSource.REQUEST_IDENTITY.toLowerCase(), dispatchedEvent.getSource()); assertTrue(dispatchedEvent.getEventData().isEmpty()); @@ -264,7 +264,7 @@ public void testResetIdentities() { // verify the dispatched event details Event dispatchedEvent = eventCaptor.getValue(); assertEquals(IdentityEdgeConstants.EventNames.REQUEST_RESET, dispatchedEvent.getName()); - assertEquals(IdentityEdgeConstants.EventType.IDENTITY_EDGE.toLowerCase(), dispatchedEvent.getType()); + assertEquals(IdentityEdgeConstants.EventType.EDGE_IDENTITY.toLowerCase(), dispatchedEvent.getType()); assertEquals(IdentityEdgeConstants.EventSource.REQUEST_RESET.toLowerCase(), dispatchedEvent.getSource()); assertTrue(dispatchedEvent.getEventData().isEmpty()); } @@ -280,8 +280,8 @@ public void testUpdateIdentities() { // test IdentityMap map = new IdentityMap(); - map.addItem("mainspace", "id", IdentityMap.AuthenticationState.AUTHENTICATED, true); - map.addItem("secondspace", "idtwo", IdentityMap.AuthenticationState.LOGGED_OUT, false); + map.addItem("mainspace", new IdentityItem("id", AuthenticationState.AUTHENTICATED, true)); + map.addItem("secondspace", new IdentityItem("idtwo", AuthenticationState.LOGGED_OUT, false)); IdentityEdge.updateIdentities(map); // verify @@ -295,7 +295,7 @@ public void testUpdateIdentities() { // verify the dispatched event details Event dispatchedEvent = eventCaptor.getValue(); assertEquals(IdentityEdgeConstants.EventNames.UPDATE_IDENTITIES,dispatchedEvent.getName()); - assertEquals(IdentityEdgeConstants.EventType.IDENTITY_EDGE.toLowerCase(),dispatchedEvent.getType()); + assertEquals(IdentityEdgeConstants.EventType.EDGE_IDENTITY.toLowerCase(),dispatchedEvent.getType()); assertEquals(IdentityEdgeConstants.EventSource.UPDATE_IDENTITY.toLowerCase(),dispatchedEvent.getSource()); assertEquals(map.asEventData(),dispatchedEvent.getEventData()); } @@ -316,7 +316,7 @@ public void testUpdateIdentitiesNullAndEmptyMap() { // Private method // ======================================================================================== private Event buildECIDResponseEvent (final Map eventData) { - return new Event.Builder(IdentityEdgeConstants.EventNames.IDENTITY_REQUEST_IDENTITY_ECID, IdentityEdgeConstants.EventType.IDENTITY_EDGE, IdentityEdgeConstants.EventSource.RESPONSE_IDENTITY).setEventData(eventData).build(); + return new Event.Builder(IdentityEdgeConstants.EventNames.IDENTITY_REQUEST_IDENTITY_ECID, IdentityEdgeConstants.EventType.EDGE_IDENTITY, IdentityEdgeConstants.EventSource.RESPONSE_IDENTITY).setEventData(eventData).build(); } } diff --git a/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/IdentityItemTests.java b/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/IdentityItemTests.java new file mode 100644 index 00000000..3ad7e5c6 --- /dev/null +++ b/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/IdentityItemTests.java @@ -0,0 +1,127 @@ +/* + Copyright 2021 Adobe. All rights reserved. + This file is licensed to you under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. You may obtain a copy + of the License at http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software distributed under + the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + OF ANY KIND, either express or implied. See the License for the specific language + governing permissions and limitations under the License. +*/ + +package com.adobe.marketing.mobile.identityedge; + +import org.junit.Test; + +import java.util.HashMap; +import java.util.Map; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +public class IdentityItemTests { + + @Test + public void testIdentityItem_toObjectMap_full() { + // setup + IdentityItem item = new IdentityItem("id", AuthenticationState.AUTHENTICATED, true); + + // test + Map data = item.toObjectMap(); + + // verify + assertEquals("id", (String) data.get("id")); + assertEquals("AUTHENTICATED", (String) data.get("authenticationState")); + assertEquals(true, (boolean) data.get("primary")); + } + + @Test(expected = IllegalArgumentException.class) + public void testIdentityItem_toObjectMap_missingId() { + // setup + IdentityItem item = new IdentityItem(null, AuthenticationState.AUTHENTICATED, true); + + // test + Map data = item.toObjectMap(); + } + + @Test + public void testIdentityItem_toObjectMap_missingAuthState() { + // setup + IdentityItem item = new IdentityItem("id", null, true); + + // test + Map data = item.toObjectMap(); + + // verify + assertEquals("id", (String) data.get("id")); + assertEquals("AMBIGUOUS", (String) data.get("authenticationState")); + assertEquals(true, (boolean) data.get("primary")); + } + + @Test + public void testIdentityItem_fromData_full() { + // setup + Map map = new HashMap<>(); + map.put("id", "test-id"); + map.put("authenticationState", "loggedOut"); + map.put("primary", true); + + // test + IdentityItem item = IdentityItem.fromData(map); + + // verify + assertEquals("test-id", item.getId()); + assertEquals("LOGGED_OUT", item.getAuthenticationState().toString()); + assertEquals(true, item.isPrimary()); + } + + @Test + public void testIdentityItem_fromData_missingAuthState() { + // setup + Map map = new HashMap<>(); + map.put("id", "test-id"); + map.put("primary", true); + + // test + IdentityItem item = IdentityItem.fromData(map); + + // verify + assertEquals("test-id", item.getId()); + assertEquals("AMBIGUOUS", item.getAuthenticationState().toString()); + assertEquals(true, item.isPrimary()); + } + + @Test + public void testIdentityItem_fromData_missingPrimary() { + // setup + Map map = new HashMap<>(); + map.put("id", "test-id"); + map.put("authenticationState", "loggedOut"); + + // test + IdentityItem item = IdentityItem.fromData(map); + + // verify + assertEquals("test-id", item.getId()); + assertEquals("LOGGED_OUT", item.getAuthenticationState().toString()); + assertEquals(false, item.isPrimary()); + } + + @Test + public void testIdentityItem_isEqualShouldReturnTrue() { + IdentityItem item1 = new IdentityItem("id", AuthenticationState.AMBIGUOUS , false); + IdentityItem item2 = new IdentityItem("id", AuthenticationState.AUTHENTICATED , true); + + assertTrue(item1.equals(item2)); + } + + @Test + public void testIdentityItem_isEqualShouldReturnFalse() { + IdentityItem item1 = new IdentityItem("id", AuthenticationState.AMBIGUOUS , false); + IdentityItem item2 = new IdentityItem("id2", AuthenticationState.AUTHENTICATED , true); + + assertFalse(item1.equals(item2)); + } +} diff --git a/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/ListenerIdentityEdgeRemoveIdentityTests.java b/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/ListenerIdentityEdgeRemoveIdentityTests.java index 5b105b1d..72b0c75f 100644 --- a/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/ListenerIdentityEdgeRemoveIdentityTests.java +++ b/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/ListenerIdentityEdgeRemoveIdentityTests.java @@ -36,13 +36,13 @@ public class ListenerIdentityEdgeRemoveIdentityTests { public void setup() { mockIdentityEdgeExtension = Mockito.mock(IdentityEdgeExtension.class); MobileCore.start(null); - listener = spy(new ListenerIdentityEdgeRemoveIdentity(null, IdentityEdgeConstants.EventType.IDENTITY_EDGE, IdentityEdgeConstants.EventSource.REMOVE_IDENTITY)); + listener = spy(new ListenerIdentityEdgeRemoveIdentity(null, IdentityEdgeConstants.EventType.EDGE_IDENTITY, IdentityEdgeConstants.EventSource.REMOVE_IDENTITY)); } @Test public void testHear() { // setup - Event event = new Event.Builder("Remove Identity", IdentityEdgeConstants.EventType.IDENTITY_EDGE, + Event event = new Event.Builder("Remove Identity", IdentityEdgeConstants.EventType.EDGE_IDENTITY, IdentityEdgeConstants.EventSource.REMOVE_IDENTITY).build(); doReturn(mockIdentityEdgeExtension).when(listener).getIdentityEdgeExtension(); @@ -56,7 +56,7 @@ public void testHear() { @Test public void testHear_WhenParentExtensionNull() { // setup - Event event = new Event.Builder("Remove Identity", IdentityEdgeConstants.EventType.IDENTITY_EDGE, + Event event = new Event.Builder("Remove Identity", IdentityEdgeConstants.EventType.EDGE_IDENTITY, IdentityEdgeConstants.EventSource.REMOVE_IDENTITY).build(); doReturn(null).when(listener).getIdentityEdgeExtension(); diff --git a/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/ListenerIdentityEdgeUpdateIdentityTests.java b/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/ListenerIdentityEdgeUpdateIdentityTests.java index 762a88bf..2e0ce62a 100644 --- a/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/ListenerIdentityEdgeUpdateIdentityTests.java +++ b/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/ListenerIdentityEdgeUpdateIdentityTests.java @@ -36,13 +36,13 @@ public class ListenerIdentityEdgeUpdateIdentityTests { public void setup() { mockIdentityEdgeExtension = Mockito.mock(IdentityEdgeExtension.class); MobileCore.start(null); - listener = spy(new ListenerIdentityEdgeUpdateIdentity(null, IdentityEdgeConstants.EventType.IDENTITY_EDGE, IdentityEdgeConstants.EventSource.UPDATE_IDENTITY)); + listener = spy(new ListenerIdentityEdgeUpdateIdentity(null, IdentityEdgeConstants.EventType.EDGE_IDENTITY, IdentityEdgeConstants.EventSource.UPDATE_IDENTITY)); } @Test public void testHear() { // setup - Event event = new Event.Builder("Update Identities", IdentityEdgeConstants.EventType.IDENTITY_EDGE, + Event event = new Event.Builder("Update Identities", IdentityEdgeConstants.EventType.EDGE_IDENTITY, IdentityEdgeConstants.EventSource.UPDATE_IDENTITY).build(); doReturn(mockIdentityEdgeExtension).when(listener).getIdentityEdgeExtension(); @@ -56,7 +56,7 @@ public void testHear() { @Test public void testHear_WhenParentExtensionNull() { // setup - Event event = new Event.Builder("Update Identities", IdentityEdgeConstants.EventType.IDENTITY_EDGE, + Event event = new Event.Builder("Update Identities", IdentityEdgeConstants.EventType.EDGE_IDENTITY, IdentityEdgeConstants.EventSource.UPDATE_IDENTITY).build(); doReturn(null).when(listener).getIdentityEdgeExtension(); diff --git a/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/ListenerIdentityRequestResetTests.java b/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/ListenerIdentityRequestResetTests.java index 2076d736..3fef0700 100644 --- a/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/ListenerIdentityRequestResetTests.java +++ b/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/ListenerIdentityRequestResetTests.java @@ -35,7 +35,7 @@ public class ListenerIdentityRequestResetTests { public void setup() { mockIdentityEdgeExtension = Mockito.mock(IdentityEdgeExtension.class); MobileCore.start(null); - listener = spy(new ListenerIdentityRequestReset(null, IdentityEdgeConstants.EventType.IDENTITY_EDGE, IdentityEdgeConstants.EventSource.REQUEST_RESET)); + listener = spy(new ListenerIdentityRequestReset(null, IdentityEdgeConstants.EventType.EDGE_IDENTITY, IdentityEdgeConstants.EventSource.REQUEST_RESET)); } @Test From 8f094783bf5192928d25a006658a4a8c85bbbe90 Mon Sep 17 00:00:00 2001 From: Nick Porter <43650450+nporter-adbe@users.noreply.github.com> Date: Mon, 15 Mar 2021 11:41:13 -0600 Subject: [PATCH 009/101] [AMSDK-11082] Get identities API (#11) * Add getIdentities API * Add java doc and fix event name in log * Rename test * Use JSON string for test and update auth state json key * Fix auth key in tests * fix sentence in java doc --- .../mobile/identityedge/IdentityEdge.java | 51 ++++- .../identityedge/IdentityEdgeConstants.java | 1 + .../mobile/identityedge/IdentityItem.java | 2 +- .../identityedge/IdentityEdgeTests.java | 201 +++++++++++++++++- .../identityedge/IdentityItemTests.java | 8 +- 5 files changed, 247 insertions(+), 16 deletions(-) diff --git a/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdge.java b/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdge.java index 0cbfe0f4..5d121200 100644 --- a/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdge.java +++ b/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdge.java @@ -128,6 +128,51 @@ public void error(final ExtensionError extensionError) { MobileCore.dispatchEvent(updateIdentitiesEvent, errorCallback); } + /** + * Returns all identifiers, including customer identifiers which were previously added. + * @param callback {@link AdobeCallback} invoked with the current {@link IdentityMap} + * If an {@link AdobeCallbackWithError} is provided, an {@link AdobeError} can be returned in the + * eventuality of any error that occurred while getting the stored identities. + */ + public static void getIdentities(final AdobeCallback callback) { + if (callback == null) { + MobileCore.log(LoggingMode.DEBUG, LOG_TAG, "Unexpected null callback, provide a callback to retrieve current IdentityMap."); + return; + } + + final Event event = new Event.Builder(IdentityEdgeConstants.EventNames.REQUEST_IDENTITIES, + IdentityEdgeConstants.EventType.EDGE_IDENTITY, + IdentityEdgeConstants.EventSource.REQUEST_IDENTITY).build(); + + final ExtensionErrorCallback errorCallback = new ExtensionErrorCallback() { + @Override + public void error(final ExtensionError extensionError) { + returnError(callback, extensionError); + MobileCore.log(LoggingMode.DEBUG, LOG_TAG, String.format("Failed to dispatch %s event: Error : %s.", IdentityEdgeConstants.EventNames.REQUEST_IDENTITIES, + extensionError.getErrorName())); + } + }; + + MobileCore.dispatchEventWithResponseCallback(event, new AdobeCallback() { + @Override + public void call(Event responseEvent) { + if (responseEvent == null || responseEvent.getEventData() == null) { + returnError(callback, AdobeError.UNEXPECTED_ERROR); + return; + } + + final IdentityMap identityMap = IdentityMap.fromData(responseEvent.getEventData()); + if (identityMap == null) { + MobileCore.log(LoggingMode.DEBUG, LOG_TAG, "Failed to read IdentityMap from response event, invoking error callback with AdobeError.UNEXPECTED_ERROR"); + returnError(callback, AdobeError.UNEXPECTED_ERROR); + return; + } + + callback.call(identityMap); + } + }, errorCallback); + } + /** * Clears all Identity Edge identifiers and generates a new Experience Cloud ID (ECID). */ @@ -153,13 +198,13 @@ public void error(final ExtensionError extensionError) { * @param callback should not be null, should be instance of {@code AdobeCallbackWithError} * @param error the {@code AdobeError} returned back in the callback */ - private static void returnError(final AdobeCallback callback, final AdobeError error) { + private static void returnError(final AdobeCallback callback, final AdobeError error) { if (callback == null) { return; } - final AdobeCallbackWithError adobeCallbackWithError = callback instanceof AdobeCallbackWithError ? - (AdobeCallbackWithError) callback : null; + final AdobeCallbackWithError adobeCallbackWithError = callback instanceof AdobeCallbackWithError ? + (AdobeCallbackWithError) callback : null; if (adobeCallbackWithError != null) { adobeCallbackWithError.fail(error); diff --git a/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeConstants.java b/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeConstants.java index 05ac0a8d..f586518e 100644 --- a/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeConstants.java +++ b/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeConstants.java @@ -41,6 +41,7 @@ final class EventNames { static final String UPDATE_IDENTITIES = "Identity Edge Update Identities"; static final String REMOVE_IDENTITIES = "Identity Edge Remove Identities"; static final String REQUEST_RESET = "Identity Edge Request Reset"; + static final String REQUEST_IDENTITIES = "Identity Edge Request Identities"; private EventNames() { } } diff --git a/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityItem.java b/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityItem.java index 4bb8e0d0..20c46bc7 100644 --- a/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityItem.java +++ b/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityItem.java @@ -28,7 +28,7 @@ public final class IdentityItem { private static final String LOG_TAG = "IdentityItem"; private static final String JSON_KEY_ID = "id"; - private static final String JSON_KEY_AUTHENTICATION_STATE = "authenticationState"; + private static final String JSON_KEY_AUTHENTICATION_STATE = "authenticatedState"; private static final String JSON_KEY_PRIMARY = "primary"; /** diff --git a/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeTests.java b/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeTests.java index d4f65675..d76f30e8 100644 --- a/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeTests.java +++ b/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeTests.java @@ -18,6 +18,7 @@ import com.adobe.marketing.mobile.ExtensionErrorCallback; import com.adobe.marketing.mobile.MobileCore; +import org.json.JSONObject; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -123,7 +124,7 @@ public void call(String s) { Map xdmData = new HashMap<>(); xdmData.put("identityMap", identityMap); - adobeCallbackCaptor.getValue().call(buildECIDResponseEvent(xdmData)); + adobeCallbackCaptor.getValue().call(buildIdentityResponseEvent(xdmData)); assertEquals(ecid.toString(), callbackReturnValues.get(0)); // TODO - enable when ExtensionError creation is available @@ -132,7 +133,7 @@ public void call(String s) { } @Test - public void testGetExperienceCloudId_NullCallback() { + public void testGetExperienceCloudId_nullCallback() { // test IdentityEdge.getExperienceCloudId(null); @@ -142,7 +143,7 @@ public void testGetExperienceCloudId_NullCallback() { } @Test - public void testGetExperienceCloudId_NullResponseEvent() { + public void testGetExperienceCloudId_nullResponseEvent() { // setup final String KEY_IS_ERRORCALLBACK_CALLED = "errorCallBackCalled"; final String KEY_CAPTUREDERRORCALLBACK = "capturedErrorCallback"; @@ -175,7 +176,7 @@ public void call(Object o) { } } @Test - public void testGetExperienceCloudId_InvalidEventData() { + public void testGetExperienceCloudId_invalidEventData() { // setup final String KEY_IS_ERRORCALLBACK_CALLED = "errorCallBackCalled"; final String KEY_CAPTUREDERRORCALLBACK = "capturedErrorCallback"; @@ -202,7 +203,7 @@ public void call(Object o) { } // set response event to null Map eventData = new HashMap<>(); eventData.put("someKey", "someValue"); - adobeCallbackCaptor.getValue().call(buildECIDResponseEvent(eventData)); + adobeCallbackCaptor.getValue().call(buildIdentityResponseEvent(eventData)); // verify assertTrue((boolean)errorCapture.get(KEY_IS_ERRORCALLBACK_CALLED)); @@ -210,7 +211,7 @@ public void call(Object o) { } } @Test - public void testGetExperienceCloudId_MissingECID() { + public void testGetExperienceCloudId_missingECID() { // setup final String KEY_IS_ERRORCALLBACK_CALLED = "errorCallBackCalled"; final String KEY_CAPTUREDERRORCALLBACK = "capturedErrorCallback"; @@ -236,7 +237,7 @@ public void call(Object o) { } // set response event to map missing ECID Map emptyXDMData = new HashMap<>(); - adobeCallbackCaptor.getValue().call(buildECIDResponseEvent(emptyXDMData)); + adobeCallbackCaptor.getValue().call(buildIdentityResponseEvent(emptyXDMData)); // verify assertTrue((boolean)errorCapture.get(KEY_IS_ERRORCALLBACK_CALLED)); @@ -312,10 +313,194 @@ public void testUpdateIdentitiesNullAndEmptyMap() { MobileCore.dispatchEvent(any(Event.class), any(ExtensionErrorCallback.class)); } + // ======================================================================================== + // getIdentities API + // ======================================================================================== + @Test + public void testGetIdentities() throws Exception { + // setup + final ArgumentCaptor eventCaptor = ArgumentCaptor.forClass(Event.class); + final ArgumentCaptor adobeCallbackCaptor = ArgumentCaptor.forClass(AdobeCallback.class); + final ArgumentCaptor extensionErrorCallbackCaptor = ArgumentCaptor.forClass(ExtensionErrorCallback.class); + final List callbackReturnValues = new ArrayList<>(); + + // test + IdentityEdge.getIdentities(new AdobeCallback() { + @Override + public void call(IdentityMap map) { + callbackReturnValues.add(map); + } + }); + + // verify + PowerMockito.verifyStatic(MobileCore.class, Mockito.times(1)); + MobileCore.dispatchEventWithResponseCallback(eventCaptor.capture(), adobeCallbackCaptor.capture(), extensionErrorCallbackCaptor.capture()); + + // verify the dispatched event details + Event dispatchedEvent = eventCaptor.getValue(); + assertEquals(IdentityEdgeConstants.EventNames.REQUEST_IDENTITIES, dispatchedEvent.getName()); + assertEquals(IdentityEdgeConstants.EventType.EDGE_IDENTITY.toLowerCase(), dispatchedEvent.getType()); + assertEquals(IdentityEdgeConstants.EventSource.REQUEST_IDENTITY.toLowerCase(), dispatchedEvent.getSource()); + assertTrue(dispatchedEvent.getEventData().isEmpty()); + + // verify callback responses + final ECID ecid = new ECID(); + final String coreId = "core-test-id"; + final String jsonStr = "{\n" + + " \"identityMap\": {\n" + + " \"ECID\": [\n" + + " {\n" + + " \"id\":" + ecid.toString() + ",\n" + + " \"authenticatedState\": \"ambiguous\",\n" + + " \"primary\": true\n" + + " }\n" + + " ],\n" + + " \"CORE\": [\n" + + " {\n" + + " \"id\":" + coreId + ",\n" + + " \"authenticatedState\": \"authenticated\",\n" + + " \"primary\": false\n" + + " }\n" + + " ]\n" + + " }\n" + + "}"; + + final JSONObject jsonObject = new JSONObject(jsonStr); + final Map xdmData = Utils.toMap(jsonObject); + + adobeCallbackCaptor.getValue().call(buildIdentityResponseEvent(xdmData)); + IdentityItem ecidItem = callbackReturnValues.get(0).getIdentityItemsForNamespace("ECID").get(0); + IdentityItem coreItem = callbackReturnValues.get(0).getIdentityItemsForNamespace("CORE").get(0); + + assertEquals(ecid.toString(), ecidItem.getId()); + assertEquals(AuthenticationState.AMBIGUOUS, ecidItem.getAuthenticationState()); + assertEquals(true, ecidItem.isPrimary()); + + assertEquals(coreId, coreItem.getId()); + assertEquals(AuthenticationState.AUTHENTICATED, coreItem.getAuthenticationState()); + assertEquals(false, coreItem.isPrimary()); + + // TODO - enable when ExtensionError creation is available + // should not crash on calling the callback + //extensionErrorCallback.error(ExtensionError.UNEXPECTED_ERROR); + } + + @Test + public void testGetIdentities_nullCallback() { + // test + IdentityEdge.getIdentities(null); + + // verify + PowerMockito.verifyStatic(MobileCore.class, Mockito.times(0)); + MobileCore.dispatchEventWithResponseCallback(any(Event.class), any(AdobeCallback.class), any(ExtensionErrorCallback.class)); + } + + @Test + public void testGetIdentities_nullResponseEvent() { + // setup + final String KEY_IS_ERRORCALLBACK_CALLED = "errorCallBackCalled"; + final String KEY_CAPTUREDERRORCALLBACK = "capturedErrorCallback"; + final Map errorCapture = new HashMap<>(); + final ArgumentCaptor adobeCallbackCaptor = ArgumentCaptor.forClass(AdobeCallback.class); + final AdobeCallbackWithError callbackWithError = new AdobeCallbackWithError() { + @Override + public void fail(AdobeError adobeError) { + errorCapture.put(KEY_IS_ERRORCALLBACK_CALLED, true); + errorCapture.put(KEY_CAPTUREDERRORCALLBACK, adobeError); + } + + @Override + public void call(Object o) { } + }; + + // test + IdentityEdge.getIdentities(callbackWithError); + + // verify if the event is dispatched + PowerMockito.verifyStatic(MobileCore.class, Mockito.times(1)); + MobileCore.dispatchEventWithResponseCallback(any(Event.class), adobeCallbackCaptor.capture(), any(ExtensionErrorCallback.class)); + + // set response event to null + adobeCallbackCaptor.getValue().call(null); + + // verify + assertTrue((boolean)errorCapture.get(KEY_IS_ERRORCALLBACK_CALLED)); + assertEquals(AdobeError.UNEXPECTED_ERROR, errorCapture.get(KEY_CAPTUREDERRORCALLBACK)); + } + + @Test + public void testGetIdentities_invalidEventData() { + // setup + final String KEY_IS_ERRORCALLBACK_CALLED = "errorCallBackCalled"; + final String KEY_CAPTUREDERRORCALLBACK = "capturedErrorCallback"; + final Map errorCapture = new HashMap<>(); + final ArgumentCaptor adobeCallbackCaptor = ArgumentCaptor.forClass(AdobeCallback.class); + final AdobeCallbackWithError callbackWithError = new AdobeCallbackWithError() { + @Override + public void fail(AdobeError adobeError) { + errorCapture.put(KEY_IS_ERRORCALLBACK_CALLED, true); + errorCapture.put(KEY_CAPTUREDERRORCALLBACK, adobeError); + } + + @Override + public void call(Object o) { } + }; + + // test + IdentityEdge.getIdentities(callbackWithError); + + // verify if the event is dispatched + PowerMockito.verifyStatic(MobileCore.class, Mockito.times(1)); + MobileCore.dispatchEventWithResponseCallback(any(Event.class), adobeCallbackCaptor.capture(), any(ExtensionErrorCallback.class)); + + // set response event to null + Map eventData = new HashMap<>(); + eventData.put("someKey", "someValue"); + adobeCallbackCaptor.getValue().call(buildIdentityResponseEvent(eventData)); + + // verify + assertTrue((boolean)errorCapture.get(KEY_IS_ERRORCALLBACK_CALLED)); + assertEquals(AdobeError.UNEXPECTED_ERROR, errorCapture.get(KEY_CAPTUREDERRORCALLBACK)); + } + + @Test + public void testGetIdentities_missingIdentityMap() { + // setup + final String KEY_IS_ERRORCALLBACK_CALLED = "errorCallBackCalled"; + final String KEY_CAPTUREDERRORCALLBACK = "capturedErrorCallback"; + final Map errorCapture = new HashMap<>(); + final ArgumentCaptor adobeCallbackCaptor = ArgumentCaptor.forClass(AdobeCallback.class); + final AdobeCallbackWithError callbackWithError = new AdobeCallbackWithError() { + @Override + public void fail(AdobeError adobeError) { + errorCapture.put(KEY_IS_ERRORCALLBACK_CALLED, true); + errorCapture.put(KEY_CAPTUREDERRORCALLBACK, adobeError); + } + + @Override + public void call(Object o) { } + }; + + // test + IdentityEdge.getIdentities(callbackWithError); + + // verify if the event is dispatched + PowerMockito.verifyStatic(MobileCore.class, Mockito.times(1)); + MobileCore.dispatchEventWithResponseCallback(any(Event.class), adobeCallbackCaptor.capture(), any(ExtensionErrorCallback.class)); + + // set response event to map missing IdentityMap + Map emptyXDMData = new HashMap<>(); + adobeCallbackCaptor.getValue().call(buildIdentityResponseEvent(emptyXDMData)); + + // verify + assertTrue((boolean)errorCapture.get(KEY_IS_ERRORCALLBACK_CALLED)); + assertEquals(AdobeError.UNEXPECTED_ERROR, errorCapture.get(KEY_CAPTUREDERRORCALLBACK)); + } + // ======================================================================================== // Private method // ======================================================================================== - private Event buildECIDResponseEvent (final Map eventData) { + private Event buildIdentityResponseEvent(final Map eventData) { return new Event.Builder(IdentityEdgeConstants.EventNames.IDENTITY_REQUEST_IDENTITY_ECID, IdentityEdgeConstants.EventType.EDGE_IDENTITY, IdentityEdgeConstants.EventSource.RESPONSE_IDENTITY).setEventData(eventData).build(); } diff --git a/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/IdentityItemTests.java b/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/IdentityItemTests.java index 3ad7e5c6..d32ea27f 100644 --- a/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/IdentityItemTests.java +++ b/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/IdentityItemTests.java @@ -33,7 +33,7 @@ public void testIdentityItem_toObjectMap_full() { // verify assertEquals("id", (String) data.get("id")); - assertEquals("AUTHENTICATED", (String) data.get("authenticationState")); + assertEquals("AUTHENTICATED", (String) data.get("authenticatedState")); assertEquals(true, (boolean) data.get("primary")); } @@ -56,7 +56,7 @@ public void testIdentityItem_toObjectMap_missingAuthState() { // verify assertEquals("id", (String) data.get("id")); - assertEquals("AMBIGUOUS", (String) data.get("authenticationState")); + assertEquals("AMBIGUOUS", (String) data.get("authenticatedState")); assertEquals(true, (boolean) data.get("primary")); } @@ -65,7 +65,7 @@ public void testIdentityItem_fromData_full() { // setup Map map = new HashMap<>(); map.put("id", "test-id"); - map.put("authenticationState", "loggedOut"); + map.put("authenticatedState", "loggedOut"); map.put("primary", true); // test @@ -98,7 +98,7 @@ public void testIdentityItem_fromData_missingPrimary() { // setup Map map = new HashMap<>(); map.put("id", "test-id"); - map.put("authenticationState", "loggedOut"); + map.put("authenticatedState", "loggedOut"); // test IdentityItem item = IdentityItem.fromData(map); From 15be7abe79af60c882abfa161495b3c7113f7d82 Mon Sep 17 00:00:00 2001 From: Pravin Prakash Kumar Date: Wed, 17 Mar 2021 07:53:01 -0700 Subject: [PATCH 010/101] [Dev] - Introducing the Goodness of Functional test helpers + First functional test (#9) * [Dev] - Add the functional test helpers + first valid functional test * [Dev] - First functional test * [Dev] - Assertion fail on misread of persistence in TestPersistence helper method --- code/identityedge/build.gradle | 12 + .../marketing/mobile/ADBCountDownLatch.java | 59 ++ .../marketing/mobile/MonitorExtension.java | 277 ++++++++++ .../adobe/marketing/mobile/TestConstants.java | 39 ++ .../adobe/marketing/mobile/TestHelper.java | 506 ++++++++++++++++++ .../mobile/TestPersistenceHelper.java | 117 ++++ .../identityedge/ExampleInstrumentedTest.java | 37 -- .../IdentityEdgePublicAPITest.java | 83 +++ .../mobile/identityedge/IdentityEdge.java | 3 +- .../IdentityEdgeTestConstants.java | 35 ++ .../identityedge/IdentityEdgeTestUtil.java | 92 ++++ 11 files changed, 1222 insertions(+), 38 deletions(-) create mode 100644 code/identityedge/src/androidTest/java/com/adobe/marketing/mobile/ADBCountDownLatch.java create mode 100644 code/identityedge/src/androidTest/java/com/adobe/marketing/mobile/MonitorExtension.java create mode 100644 code/identityedge/src/androidTest/java/com/adobe/marketing/mobile/TestConstants.java create mode 100644 code/identityedge/src/androidTest/java/com/adobe/marketing/mobile/TestHelper.java create mode 100644 code/identityedge/src/androidTest/java/com/adobe/marketing/mobile/TestPersistenceHelper.java delete mode 100644 code/identityedge/src/androidTest/java/com/adobe/marketing/mobile/identityedge/ExampleInstrumentedTest.java create mode 100644 code/identityedge/src/androidTest/java/com/adobe/marketing/mobile/identityedge/IdentityEdgePublicAPITest.java create mode 100644 code/identityedge/src/sharedTestUtils/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeTestConstants.java create mode 100644 code/identityedge/src/sharedTestUtils/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeTestUtil.java diff --git a/code/identityedge/build.gradle b/code/identityedge/build.gradle index c79265d8..c4d5cda9 100644 --- a/code/identityedge/build.gradle +++ b/code/identityedge/build.gradle @@ -47,6 +47,18 @@ android { sourceCompatibility rootProject.ext.sourceCompatibility targetCompatibility rootProject.ext.targetCompatibility } + + sourceSets { + String sharedTestUtilJavaDir = 'src/sharedTestUtils/java' + + test { + java.srcDirs += [sharedTestUtilJavaDir] + } + + androidTest { + java.srcDirs += [sharedTestUtilJavaDir] + } + } } android.libraryVariants.all { variant -> diff --git a/code/identityedge/src/androidTest/java/com/adobe/marketing/mobile/ADBCountDownLatch.java b/code/identityedge/src/androidTest/java/com/adobe/marketing/mobile/ADBCountDownLatch.java new file mode 100644 index 00000000..90af879d --- /dev/null +++ b/code/identityedge/src/androidTest/java/com/adobe/marketing/mobile/ADBCountDownLatch.java @@ -0,0 +1,59 @@ +/* + Copyright 2021 Adobe. All rights reserved. + This file is licensed to you under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. You may obtain a copy + of the License at http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software distributed under + the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + OF ANY KIND, either express or implied. See the License for the specific language + governing permissions and limitations under the License. +*/ + +package com.adobe.marketing.mobile; + +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; + +public class ADBCountDownLatch { + private final CountDownLatch latch; + private final int initialCount; + private final AtomicInteger currentCount; + + public ADBCountDownLatch(final int expectedCount) { + this.initialCount = expectedCount; + this.latch = new CountDownLatch(expectedCount); + this.currentCount = new AtomicInteger(); + } + + public void await() throws InterruptedException { + latch.await(); + } + + public boolean await(long timeout, TimeUnit unit) throws InterruptedException { + return latch.await(timeout, unit); + } + + public void countDown() { + currentCount.incrementAndGet(); + latch.countDown(); + } + + public long getCount() { + return latch.getCount(); + } + + public int getInitialCount() { + return initialCount; + } + + public int getCurrentCount() { + return currentCount.get(); + } + + @Override + public String toString() { + return String.format("%s, initial: %d, current: %d", latch.toString(), initialCount, currentCount.get()); + } + +} diff --git a/code/identityedge/src/androidTest/java/com/adobe/marketing/mobile/MonitorExtension.java b/code/identityedge/src/androidTest/java/com/adobe/marketing/mobile/MonitorExtension.java new file mode 100644 index 00000000..11b9eab5 --- /dev/null +++ b/code/identityedge/src/androidTest/java/com/adobe/marketing/mobile/MonitorExtension.java @@ -0,0 +1,277 @@ +/* + Copyright 2021 Adobe. All rights reserved. + This file is licensed to you under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. You may obtain a copy + of the License at http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software distributed under + the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + OF ANY KIND, either express or implied. See the License for the specific language + governing permissions and limitations under the License. +*/ + +package com.adobe.marketing.mobile; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; + +/** + * A third party extension class aiding for assertion against dispatched events, shared state + * and XDM shared state. + */ +class MonitorExtension extends Extension { + private static final String LOG_TAG = "MonitorExtension"; + + private static final Map> receivedEvents = new HashMap<>(); + private static final Map expectedEvents = new HashMap<>(); + + protected MonitorExtension(ExtensionApi extensionApi) { + super(extensionApi); + + extensionApi.registerWildcardListener( + MonitorListener.class, new ExtensionErrorCallback() { + @Override + public void error(ExtensionError extensionError) { + MobileCore.log(LoggingMode.ERROR, LOG_TAG, + "There was an error registering Extension Listener: " + + extensionError.getErrorName()); + } + }); + } + + @Override + protected String getName() { + return "MonitorExtension"; + } + + public static void registerExtension() { + MobileCore.registerExtension(MonitorExtension.class, new ExtensionErrorCallback() { + @Override + public void error(ExtensionError extensionError) { + MobileCore.log(LoggingMode.ERROR, LOG_TAG, + "There was an error registering the Monitor extension: " + extensionError.getErrorName()); + } + }); + } + + /** + * Unregister the Monitor Extension from the EventHub. + */ + public static void unregisterExtension() { + Event event = new Event.Builder("Unregister Monitor Extension Request", TestConstants.EventType.MONITOR, + TestConstants.EventSource.UNREGISTER) + .build(); + MobileCore.dispatchEvent(event, new ExtensionErrorCallback() { + @Override + public void error(ExtensionError extensionError) { + MobileCore.log(LoggingMode.ERROR, LOG_TAG, "Failed to unregister Monitor extension."); + } + }); + } + + /** + * Add an event to the list of expected events. + * @param type the type of the event. + * @param source the source of the event. + * @param count the number of events expected to be received. + */ + public static void setExpectedEvent(final String type, final String source, final int count) { + EventSpec eventSpec = new EventSpec(source, type); + expectedEvents.put(eventSpec, new ADBCountDownLatch(count)); + } + + public static Map getExpectedEvents() { + return expectedEvents; + } + + public static Map> getReceivedEvents() { + return receivedEvents; + } + + /** + * Resets the map of received and expected events. + */ + public static void reset() { + MobileCore.log(LoggingMode.VERBOSE, LOG_TAG, "Reset expected and received events."); + receivedEvents.clear(); + expectedEvents.clear(); + } + + /** + * Processor for all heard events. + * If the event type is of this Monitor Extension, then + * the action is performed per the event source. + * All other events are added to the map of received events. If the event is in the map + * of expected events, its latch is counted down. + * + * @param event + */ + public void wildcardProcessor(final Event event) { + if (TestConstants.EventType.MONITOR.equalsIgnoreCase(event.getType())) { + if (TestConstants.EventSource.SHARED_STATE_REQUEST.equalsIgnoreCase(event.getSource())) { + processSharedStateRequest(event); + } else if (TestConstants.EventSource.XDM_SHARED_STATE_REQUEST.equalsIgnoreCase(event.getSource())) { + processXDMSharedStateRequest(event); + } else if (TestConstants.EventSource.UNREGISTER.equalsIgnoreCase(event.getSource())) { + processUnregisterRequest(event); + } + + return; + } + + EventSpec eventSpec = new EventSpec(event.getSource(), event.getType()); + + MobileCore.log(LoggingMode.DEBUG, LOG_TAG, "Received and processing event " + eventSpec); + + if (!receivedEvents.containsKey(eventSpec)) { + receivedEvents.put(eventSpec, new ArrayList()); + } + + receivedEvents.get(eventSpec).add(event); + + + if (expectedEvents.containsKey(eventSpec)) { + expectedEvents.get(eventSpec).countDown(); + } + } + + /** + * Processor which unregisters this extension. + * @param event + */ + private void processUnregisterRequest(final Event event) { + MobileCore.log(LoggingMode.DEBUG, LOG_TAG, "Unregistering the Monitor Extension."); + getApi().unregisterExtension(); + } + + /** + * Processor which retrieves and dispatches the XDM shared state for the state owner specified + * in the request. + * @param event + */ + private void processXDMSharedStateRequest(final Event event) { + EventData eventData = event.getData(); + + if (eventData == null) { + return; + } + + String stateOwner = eventData.optString(TestConstants.EventDataKey.STATE_OWNER, null); + + if (stateOwner == null) { + return; + } + + EventData sharedState = getApi().getXDMSharedEventState(stateOwner, event); + + Event responseEvent = new Event.Builder("Get Shared State Response", TestConstants.EventType.MONITOR, + TestConstants.EventSource.XDM_SHARED_STATE_RESPONSE) + .setEventData(sharedState == null ? null : sharedState.toObjectMap()) + .setPairID(event.getResponsePairID()) + .build(); + + MobileCore.dispatchResponseEvent(responseEvent, event, null); + } + + /** + * Processor which retrieves and dispatches the shared state for the state owner specified + * in the request. + * @param event + */ + private void processSharedStateRequest(final Event event) { + EventData eventData = event.getData(); + + if (eventData == null) { + return; + } + + String stateOwner = eventData.optString(TestConstants.EventDataKey.STATE_OWNER, null); + + if (stateOwner == null) { + return; + } + + EventData sharedState = getApi().getSharedEventState(stateOwner, event); + + Event responseEvent = new Event.Builder("Get Shared State Response", TestConstants.EventType.MONITOR, + TestConstants.EventSource.SHARED_STATE_RESPONSE) + .setEventData(sharedState == null ? null : sharedState.toObjectMap()) + .setPairID(event.getResponsePairID()) + .build(); + + MobileCore.dispatchResponseEvent(responseEvent, event, null); + } + + /** + * Listener class + */ + public static class MonitorListener extends ExtensionListener { + + protected MonitorListener(ExtensionApi extension, String type, String source) { + super(extension, type, source); + } + + @Override + public void hear(Event event) { + MonitorExtension extension = getParentExtension(); + + if (extension != null) { + extension.wildcardProcessor(event); + } + } + + @Override + protected MonitorExtension getParentExtension() { + return (MonitorExtension) super.getParentExtension(); + } + } + + /** + * Class defining {@link Event} specifications, contains Event's source and type. + */ + public static class EventSpec { + final String source; + final String type; + + public EventSpec(final String source, final String type) { + if (source == null || source.isEmpty()) { + throw new IllegalArgumentException("Event Source cannot be null or empty."); + } + + if (type == null || type.isEmpty()) { + throw new IllegalArgumentException("Event Type cannot be null or empty."); + } + + // Normalize strings + this.source = source.toLowerCase(); + this.type = type.toLowerCase(); + } + + @Override + public String toString() { + return "type '" + type + "' and source '" + source + "'"; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + + if (o == null || getClass() != o.getClass()) { + return false; + } + + EventSpec eventSpec = (EventSpec) o; + return Objects.equals(source, eventSpec.source) && + Objects.equals(type, eventSpec.type); + } + + @Override + public int hashCode() { + return Objects.hash(source, type); + } + } +} \ No newline at end of file diff --git a/code/identityedge/src/androidTest/java/com/adobe/marketing/mobile/TestConstants.java b/code/identityedge/src/androidTest/java/com/adobe/marketing/mobile/TestConstants.java new file mode 100644 index 00000000..5980934e --- /dev/null +++ b/code/identityedge/src/androidTest/java/com/adobe/marketing/mobile/TestConstants.java @@ -0,0 +1,39 @@ +/* + Copyright 2021 Adobe. All rights reserved. + This file is licensed to you under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. You may obtain a copy + of the License at http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software distributed under + the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + OF ANY KIND, either express or implied. See the License for the specific language + governing permissions and limitations under the License. +*/ + +package com.adobe.marketing.mobile; + +/** + * Class to maintain test constants. + */ +public class TestConstants { + + public class EventType { + static final String MONITOR = "com.adobe.functional.eventType.monitor"; + private EventType() {} + } + + public class EventSource { + // Used by Monitor Extension + static final String XDM_SHARED_STATE_REQUEST = "com.adobe.eventSource.xdmsharedStateRequest"; + static final String XDM_SHARED_STATE_RESPONSE = "com.adobe.eventSource.xdmsharedStateResponse"; + static final String SHARED_STATE_REQUEST = "com.adobe.eventSource.sharedStateRequest"; + static final String SHARED_STATE_RESPONSE = "com.adobe.eventSource.sharedStateResponse"; + static final String UNREGISTER = "com.adobe.eventSource.unregister"; + private EventSource() {} + } + + public class EventDataKey { + static final String STATE_OWNER = "stateowner"; + private EventDataKey() {}; + } + +} diff --git a/code/identityedge/src/androidTest/java/com/adobe/marketing/mobile/TestHelper.java b/code/identityedge/src/androidTest/java/com/adobe/marketing/mobile/TestHelper.java new file mode 100644 index 00000000..b893b213 --- /dev/null +++ b/code/identityedge/src/androidTest/java/com/adobe/marketing/mobile/TestHelper.java @@ -0,0 +1,506 @@ +/* + Copyright 2021 Adobe. All rights reserved. + This file is licensed to you under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. You may obtain a copy + of the License at http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software distributed under + the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + OF ANY KIND, either express or implied. See the License for the specific language + governing permissions and limitations under the License. +*/ + +package com.adobe.marketing.mobile; + +import android.app.Application; +import android.app.Instrumentation; +import android.content.Context; + +import org.junit.rules.TestRule; +import org.junit.runner.Description; +import org.junit.runners.model.Statement; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.TimeUnit; + +import com.adobe.marketing.mobile.MonitorExtension.EventSpec; + +import androidx.test.platform.app.InstrumentationRegistry; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +/** + * Test helper for functional testing to read, write, reset and assert against eventhub events, shared states and persistence data. + */ +public class TestHelper { + private static final String TAG = "TestHelper"; + static final int WAIT_TIMEOUT_MS = 1000; + static final int WAIT_EVENT_TIMEOUT_MS = 2000; + static Application defaultApplication; + + // List of threads to wait for after test execution + private static List knownThreads = new ArrayList(); + + { + knownThreads.add("pool"); // used for threads that execute the listeners code + knownThreads.add("ADB"); // module internal threads + } + + /** + * {@code TestRule} which sets up the MobileCore for testing before each test execution, and + * tearsdown the MobileCore after test execution. + *

    + * To use, add the following to your test class: + *

    +     *    @Rule
    +     *    public TestHelper.SetupCoreRule coreRule = new TestHelper.SetupCoreRule();
    +     * 
    + */ + public static class SetupCoreRule implements TestRule { + + @Override + public Statement apply(final Statement base, final Description description) { + return new Statement() { + @Override + public void evaluate() throws Throwable { + if (defaultApplication == null) { + Context context = InstrumentationRegistry.getInstrumentation().getTargetContext(); + defaultApplication = Instrumentation.newApplication(CustomApplication.class, context); + } + + MobileCore.setLogLevel(LoggingMode.VERBOSE); + MobileCore.setApplication(defaultApplication); + + try { + base.evaluate(); + } catch (Throwable e) { + MobileCore.log(LoggingMode.DEBUG, "SetupCoreRule", "Wait after test failure."); + throw e; // rethrow test failure + } finally { + // After test execution + MobileCore.log(LoggingMode.DEBUG, "SetupCoreRule", "Finished '" + description.getMethodName() + "'"); + waitForThreads(5000); // wait to allow thread to run after test execution + Core core = MobileCore.getCore(); + + if (core != null && core.eventHub != null) { + core.eventHub.shutdown(); + core.eventHub = null; + } + MobileCore.setCore(null); + TestPersistenceHelper.resetKnownPersistence(); + resetTestExpectations(); + } + } + }; + } + } + + /** + * {@code TestRule} which registers the {@code MonitorExtension}, allowing test cases to assert + * events passing through the {@code EventHub}. This {@code TestRule} must be applied after + * the {@link SetupCoreRule} to ensure the {@code MobileCore} is setup for testing first. + *

    + * To use, add the following to your test class: + *

    +     *  @Rule
    +     *    public RuleChain rule = RuleChain.outerRule(new SetupCoreRule())
    +     * 							.around(new RegisterMonitorExtensionRule());
    +     * 
    + */ + public static class RegisterMonitorExtensionRule implements TestRule { + + @Override + public Statement apply(final Statement base, final Description description) { + return new Statement() { + @Override + public void evaluate() throws Throwable { + MonitorExtension.registerExtension(); + + try { + base.evaluate(); + } finally { + MonitorExtension.reset(); + } + } + }; + } + } + + /** + * Waits for all the {@code #knownThreads} to finish or fails the test after timeoutMillis if some of them are still running + * when the timer expires. If timeoutMillis is 0, a default timeout will be set = 1000ms + * + * @param timeoutMillis max waiting time + */ + public static void waitForThreads(final int timeoutMillis) { + int TEST_DEFAULT_TIMEOUT_MS = 1000; + int TEST_DEFAULT_SLEEP_MS = 50; + int TEST_INITIAL_SLEEP_MS = 100; + + long startTime = System.currentTimeMillis(); + int timeoutTestMillis = timeoutMillis > 0 ? timeoutMillis : TEST_DEFAULT_TIMEOUT_MS; + int sleepTime = Math.min(timeoutTestMillis, TEST_DEFAULT_SLEEP_MS); + + sleep(TEST_INITIAL_SLEEP_MS); + Set threadSet = getEligibleThreads(); + + while (threadSet.size() > 0 && ((System.currentTimeMillis() - startTime) < timeoutTestMillis)) { + MobileCore.log(LoggingMode.DEBUG, TAG, "waitForThreads - Still waiting for " + threadSet.size() + " thread(s)"); + + for (Thread t : threadSet) { + + MobileCore.log(LoggingMode.DEBUG, TAG, "waitForThreads - Waiting for thread " + t.getName() + " (" + t.getId() + ")"); + boolean done = false; + boolean timedOut = false; + + while (!done && !timedOut) { + if (t.getState().equals(Thread.State.TERMINATED) + || t.getState().equals(Thread.State.TIMED_WAITING) + || t.getState().equals(Thread.State.WAITING)) { + //Cannot use the join() API since we use a cached thread pool, which + //means that we keep idle threads around for 60secs (default timeout). + done = true; + } else { + //blocking + sleep(sleepTime); + timedOut = (System.currentTimeMillis() - startTime) > timeoutTestMillis; + } + } + + if (timedOut) { + MobileCore.log(LoggingMode.DEBUG, TAG, + "waitForThreads - Timeout out waiting for thread " + t.getName() + " (" + t.getId() + ")"); + } else { + MobileCore.log(LoggingMode.DEBUG, TAG, + "waitForThreads - Done waiting for thread " + t.getName() + " (" + t.getId() + ")"); + } + } + + threadSet = getEligibleThreads(); + } + + MobileCore.log(LoggingMode.DEBUG, TAG, "waitForThreads - All known threads are terminated."); + } + + /** + * Retrieves all the known threads that are still running + * + * @return set of running tests + */ + private static Set getEligibleThreads() { + Set threadSet = Thread.getAllStackTraces().keySet(); + Set eligibleThreads = new HashSet(); + + for (Thread t : threadSet) { + if (isAppThread(t) && !t.getState().equals(Thread.State.WAITING) && !t.getState().equals(Thread.State.TERMINATED) + && !t.getState().equals(Thread.State.TIMED_WAITING)) { + eligibleThreads.add(t); + } + } + + return eligibleThreads; + } + + /** + * Checks if current thread is not a daemon and its name starts with one of the known thread names specified here + * {@link #knownThreads} + * + * @param t current thread to verify + * @return true if it is a known thread, false otherwise + */ + private static boolean isAppThread(final Thread t) { + if (t.isDaemon()) { + return false; + } + + for (String prefix : knownThreads) { + if (t.getName().startsWith(prefix)) { + return true; + } + } + + return false; + } + + + /** + * Resets the network and event test expectations. + */ + public static void resetTestExpectations() { + MobileCore.log(LoggingMode.DEBUG, TAG, "Resetting functional test expectations for events"); + MonitorExtension.reset(); + } + + // --------------------------------------------------------------------------------------------- + // Event Test Helpers + // --------------------------------------------------------------------------------------------- + + /** + * Sets an expectation for a specific event type and source and how many times the event should be dispatched. + * + * @param type the event type + * @param source the event source + * @param count the expected number of times the event is dispatched + * @throws IllegalArgumentException if {@code count} is less than 1 + */ + public static void setExpectationEvent(final String type, final String source, final int count) { + if (count < 1) { + throw new IllegalArgumentException("Cannot set expectation event count less than 1!"); + } + + MonitorExtension.setExpectedEvent(type, source, count); + } + + /** + * Asserts if all the expected events were received and fails if an unexpected event was seen. + * + * @param ignoreUnexpectedEvents if set on false, an assertion is made on unexpected events, otherwise the unexpected events are ignored + * @throws InterruptedException + * @see #setExpectationEvent(String, String, int) + * @see #assertUnexpectedEvents() + */ + public static void assertExpectedEvents(final boolean ignoreUnexpectedEvents) throws InterruptedException { + Map expectedEvents = MonitorExtension.getExpectedEvents(); + + if (expectedEvents.isEmpty()) { + fail("There are no event expectations set, use this API after calling setExpectationEvent"); + return; + } + + for (Map.Entry expected : expectedEvents.entrySet()) { + boolean awaitResult = expected.getValue().await(WAIT_EVENT_TIMEOUT_MS, + TimeUnit.MILLISECONDS); + assertTrue("Timed out waiting for event type " + expected.getKey().type + " and source " + expected.getKey().source, + awaitResult); + int expectedCount = expected.getValue().getInitialCount(); + int receivedCount = expected.getValue().getCurrentCount(); + String failMessage = String.format("Expected %d events for '%s', but received %d", expectedCount, expected.getKey(), + receivedCount); + assertEquals(failMessage, expectedCount, receivedCount); + } + + if (!ignoreUnexpectedEvents) { + assertUnexpectedEvents(false); + } + } + + /** + * Asserts if any unexpected event was received. Use this method to verify the received events + * are correct when setting event expectations. Waits a short time before evaluating received + * events to allow all events to come in. + * + * @see #setExpectationEvent + */ + public static void assertUnexpectedEvents() throws InterruptedException { + assertUnexpectedEvents(true); + } + + /** + * Asserts if any unexpected event was received. Use this method to verify the received events + * are correct when setting event expectations. + * + * @param shouldWait waits a short time to allow events to be received when true + * @see #setExpectationEvent + */ + public static void assertUnexpectedEvents(final boolean shouldWait) throws InterruptedException { + // Short wait to allow events to come in + if (shouldWait) { + sleep(WAIT_TIMEOUT_MS); + } + + int unexpectedEventsReceivedCount = 0; + StringBuilder unexpectedEventsErrorString = new StringBuilder(); + + Map> receivedEvents = MonitorExtension.getReceivedEvents(); + Map expectedEvents = MonitorExtension.getExpectedEvents(); + + for (Map.Entry> receivedEvent : receivedEvents.entrySet()) { + ADBCountDownLatch expectedEventLatch = expectedEvents.get(receivedEvent.getKey()); + + if (expectedEventLatch != null) { + expectedEventLatch.await(WAIT_EVENT_TIMEOUT_MS, TimeUnit.MILLISECONDS); + int expectedCount = expectedEventLatch.getInitialCount(); + int receivedCount = receivedEvent.getValue().size(); + String failMessage = String.format("Expected %d events for '%s', but received %d", expectedCount, + receivedEvent.getKey(), receivedCount); + assertEquals(failMessage, expectedCount, receivedCount); + } else { + unexpectedEventsReceivedCount += receivedEvent.getValue().size(); + unexpectedEventsErrorString.append(String.format("(%s,%s,%d)", + receivedEvent.getKey().type, + receivedEvent.getKey().source, + receivedEvent.getValue().size()) + ); + MobileCore.log(LoggingMode.DEBUG, TAG, + "Received unexpected event with type: " + receivedEvent.getKey().type + " source: " + + receivedEvent.getKey().source); + } + } + + assertEquals(String.format("Received %d unexpected event(s): %s", unexpectedEventsReceivedCount, + unexpectedEventsErrorString.toString()), + 0, unexpectedEventsReceivedCount); + } + + /** + * Returns the {@code Event}(s) dispatched through the Event Hub, or empty if none was found. + * Use this API after calling {@link #setExpectationEvent(String, String, int)} to wait for + * the expected events. The wait time for each event is {@link #WAIT_EVENT_TIMEOUT_MS}ms. + * + * @param type the event type as in the expectation + * @param source the event source as in the expectation + * @return list of events with the provided {@code type} and {@code source}, or empty if none was dispatched + * @throws InterruptedException + * @throws IllegalArgumentException if {@code type} or {@code source} are null or empty strings + */ + public static List getDispatchedEventsWith(final String type, final String source) throws InterruptedException { + return getDispatchedEventsWith(type, source, WAIT_EVENT_TIMEOUT_MS); + } + + /** + * Returns the {@code Event}(s) dispatched through the Event Hub, or empty if none was found. + * Use this API after calling {@link #setExpectationEvent(String, String, int)} to wait for the right amount of time + * + * @param type the event type as in the expectation + * @param source the event source as in the expectation + * @param timeout how long should this method wait for the expected event, in milliseconds. + * @return list of events with the provided {@code type} and {@code source}, or empty if none was dispatched + * @throws InterruptedException + * @throws IllegalArgumentException if {@code type} or {@code source} are null or empty strings + */ + public static List getDispatchedEventsWith(final String type, final String source, + int timeout) throws InterruptedException { + EventSpec eventSpec = new EventSpec(source, type); + + Map> receivedEvents = MonitorExtension.getReceivedEvents(); + Map expectedEvents = MonitorExtension.getExpectedEvents(); + + ADBCountDownLatch expectedEventLatch = expectedEvents.get(eventSpec); + + if (expectedEventLatch != null) { + boolean awaitResult = expectedEventLatch.await(timeout, TimeUnit.MILLISECONDS); + assertTrue("Timed out waiting for event type " + eventSpec.type + " and source " + eventSpec.source, awaitResult); + } else { + sleep(WAIT_TIMEOUT_MS); + } + + return receivedEvents.containsKey(eventSpec) ? receivedEvents.get(eventSpec) : Collections.emptyList(); + } + + + /** + * Synchronous call to get the shared state for the specified {@code stateOwner}. + * This API throws an assertion failure in case of timeout. + * + * @param stateOwner the owner extension of the shared state (typically the name of the extension) + * @param timeout how long should this method wait for the requested shared state, in milliseconds + * @return latest shared state of the given {@code stateOwner} or null if no shared state was found + * @throws InterruptedException + */ + public static Map getSharedStateFor(final String stateOwner, int timeout) throws InterruptedException { + Event event = new Event.Builder("Get Shared State Request", TestConstants.EventType.MONITOR, + TestConstants.EventSource.SHARED_STATE_REQUEST) + .setEventData(new HashMap() { + { + put(TestConstants.EventDataKey.STATE_OWNER, stateOwner); + } + }) + .build(); + + final ADBCountDownLatch latch = new ADBCountDownLatch(1); + final Map sharedState = new HashMap<>(); + MobileCore.dispatchEventWithResponseCallback(event, + new AdobeCallback() { + @Override + public void call(Event event) { + if (event.getEventData() != null) { + sharedState.putAll(event.getEventData()); + } + + latch.countDown(); + } + }, + new ExtensionErrorCallback() { + @Override + public void error(ExtensionError extensionError) { + MobileCore.log(LoggingMode.ERROR, TAG, "Failed to get shared state for " + stateOwner + ": " + extensionError); + } + }); + + assertTrue("Timeout waiting for shared state " + stateOwner, latch.await(timeout, TimeUnit.MILLISECONDS)); + return sharedState.isEmpty() ? null : sharedState; + } + + /** + * Synchronous call to get the XDM shared state for the specified {@code stateOwner}. + * This API throws an assertion failure in case of timeout. + * + * @param stateOwner the owner extension of the shared state (typically the name of the extension) + * @param timeout how long should this method wait for the requested shared state, in milliseconds + * @return latest shared state of the given {@code stateOwner} or null if no shared state was found + * @throws InterruptedException + */ + public static Map getXDMSharedStateFor(final String stateOwner, int timeout) throws InterruptedException { + Event event = new Event.Builder("Get Shared State Request", TestConstants.EventType.MONITOR, + TestConstants.EventSource.XDM_SHARED_STATE_REQUEST) + .setEventData(new HashMap() { + { + put(TestConstants.EventDataKey.STATE_OWNER, stateOwner); + } + }) + .build(); + + final ADBCountDownLatch latch = new ADBCountDownLatch(1); + final Map sharedState = new HashMap<>(); + MobileCore.dispatchEventWithResponseCallback(event, + new AdobeCallback() { + @Override + public void call(Event event) { + if (event.getEventData() != null) { + sharedState.putAll(event.getEventData()); + } + + latch.countDown(); + } + }, + new ExtensionErrorCallback() { + @Override + public void error(ExtensionError extensionError) { + MobileCore.log(LoggingMode.ERROR, TAG, "Failed to get shared state for " + stateOwner + ": " + extensionError); + } + }); + + assertTrue("Timeout waiting for shared state " + stateOwner, latch.await(timeout, TimeUnit.MILLISECONDS)); + return sharedState.isEmpty() ? null : sharedState; + } + + + /** + * Pause test execution for the given {@code milliseconds} + * + * @param milliseconds the time to sleep the current thread. + */ + public static void sleep(int milliseconds) { + try { + Thread.sleep(milliseconds); + } catch (Exception e) { + e.printStackTrace(); + } + } + + /** + * Dummy Application for the test instrumentation + */ + public static class CustomApplication extends Application { + public CustomApplication() { + } + } + +} \ No newline at end of file diff --git a/code/identityedge/src/androidTest/java/com/adobe/marketing/mobile/TestPersistenceHelper.java b/code/identityedge/src/androidTest/java/com/adobe/marketing/mobile/TestPersistenceHelper.java new file mode 100644 index 00000000..bf1040d9 --- /dev/null +++ b/code/identityedge/src/androidTest/java/com/adobe/marketing/mobile/TestPersistenceHelper.java @@ -0,0 +1,117 @@ +/* + Copyright 2021 Adobe. All rights reserved. + This file is licensed to you under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. You may obtain a copy + of the License at http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software distributed under + the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + OF ANY KIND, either express or implied. See the License for the specific language + governing permissions and limitations under the License. +*/ + +package com.adobe.marketing.mobile; + +import android.app.Application; +import android.content.Context; +import android.content.SharedPreferences; + + +import com.adobe.marketing.mobile.identityedge.IdentityEdgeTestConstants; + +import java.util.ArrayList; + +import static org.junit.Assert.fail; + +/** + * Helper class to update and remove persisted data to extension concerned with testing IdentityEdge. + */ +public class TestPersistenceHelper { + + private static ArrayList knownDatastoreName = new ArrayList() {{ + add(IdentityEdgeTestConstants.DataStoreKey.IDENTITYEDGE_DATASTORE); + add(IdentityEdgeTestConstants.DataStoreKey.CONFIG_DATASTORE); + }}; + + /** + * Helper method to update the {@link SharedPreferences} data. + * + * @param datastore the name of the datastore to be updated + * @param key the persisted data key that has to be updated + * @param value the new value + */ + public static void updatePersistence(final String datastore, final String key, final String value) { + final Application application = TestHelper.defaultApplication; + if (application == null) { + fail("Unable to updatePersistence by TestPersistenceHelper. Application is null, fast failing the test case."); + } + + final Context context = application.getApplicationContext(); + if (context == null) { + fail("Unable to updatePersistence by TestPersistenceHelper. Context is null, fast failing the test case."); + } + + SharedPreferences sharedPreferences = context.getSharedPreferences(datastore, Context.MODE_PRIVATE); + + if (sharedPreferences == null) { + fail("Unable to updatePersistence by TestPersistenceHelper. sharedPreferences is null, fast failing the test case."); + } + + SharedPreferences.Editor editor = sharedPreferences.edit(); + editor.putString(key, value); + editor.apply(); + } + + /** + * Reads the requested persisted data from datastore. + * + * @param datastore the name of the datastore to be read + * @param key the key that needs to be read + * @return {@link String} value of persisted data. Null if data is not found in {@link SharedPreferences} + */ + public static String readPersistedData(final String datastore, final String key) { + final Application application = TestHelper.defaultApplication; + if (application == null) { + fail("Unable to readPersistedData by TestPersistenceHelper. Application is null, fast failing the test case."); + } + + final Context context = application.getApplicationContext(); + if (context == null) { + fail("Unable to readPersistedData by TestPersistenceHelper. Context is null, fast failing the test case."); + } + + SharedPreferences sharedPreferences = context.getSharedPreferences(datastore, Context.MODE_PRIVATE); + if (sharedPreferences == null) { + fail("Unable to readPersistedData by TestPersistenceHelper. sharedPreferences is null, fast failing the test case."); + } + + return sharedPreferences.getString(key, null); + } + + /** + * Clears the Configuration and Consent extension's persisted data + */ + public static void resetKnownPersistence() { + + final Application application = TestHelper.defaultApplication; + if (application == null) { + fail("Unable to resetPersistence by TestPersistenceHelper. Application is null, fast failing the test case."); + } + + final Context context = application.getApplicationContext(); + if (context == null) { + fail("Unable to resetPersistence by TestPersistenceHelper. Context is null, fast failing the test case."); + } + + for (String eachDatastore : knownDatastoreName) { + SharedPreferences sharedPreferences = context.getSharedPreferences(eachDatastore, Context.MODE_PRIVATE); + if (sharedPreferences == null) { + fail("Unable to resetPersistence by TestPersistenceHelper. sharedPreferences is null, fast failing the test case."); + } + + SharedPreferences.Editor editor = sharedPreferences.edit(); + editor.clear(); + editor.apply(); + } + } + +} diff --git a/code/identityedge/src/androidTest/java/com/adobe/marketing/mobile/identityedge/ExampleInstrumentedTest.java b/code/identityedge/src/androidTest/java/com/adobe/marketing/mobile/identityedge/ExampleInstrumentedTest.java deleted file mode 100644 index e56522ac..00000000 --- a/code/identityedge/src/androidTest/java/com/adobe/marketing/mobile/identityedge/ExampleInstrumentedTest.java +++ /dev/null @@ -1,37 +0,0 @@ -/* - Copyright 2021 Adobe. All rights reserved. - This file is licensed to you under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. You may obtain a copy - of the License at http://www.apache.org/licenses/LICENSE-2.0 - Unless required by applicable law or agreed to in writing, software distributed under - the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS - OF ANY KIND, either express or implied. See the License for the specific language - governing permissions and limitations under the License. -*/ - -package com.adobe.marketing.mobile.identityedge; - -import android.content.Context; - -import androidx.test.platform.app.InstrumentationRegistry; -import androidx.test.ext.junit.runners.AndroidJUnit4; - -import org.junit.Test; -import org.junit.runner.RunWith; - -import static org.junit.Assert.*; - -/** - * Instrumented test, which will execute on an Android device. - * - * @see Testing documentation - */ -@RunWith(AndroidJUnit4.class) -public class ExampleInstrumentedTest { - @Test - public void useAppContext() { - // Context of the app under test. - Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); - assertEquals("com.adobe.marketing.mobile.identityedge.test", appContext.getPackageName()); - } -} \ No newline at end of file diff --git a/code/identityedge/src/androidTest/java/com/adobe/marketing/mobile/identityedge/IdentityEdgePublicAPITest.java b/code/identityedge/src/androidTest/java/com/adobe/marketing/mobile/identityedge/IdentityEdgePublicAPITest.java new file mode 100644 index 00000000..0aa9eebb --- /dev/null +++ b/code/identityedge/src/androidTest/java/com/adobe/marketing/mobile/identityedge/IdentityEdgePublicAPITest.java @@ -0,0 +1,83 @@ +/* + Copyright 2021 Adobe. All rights reserved. + This file is licensed to you under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. You may obtain a copy + of the License at http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software distributed under + the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + OF ANY KIND, either express or implied. See the License for the specific language + governing permissions and limitations under the License. +*/ + +package com.adobe.marketing.mobile.identityedge; + +import androidx.test.ext.junit.runners.AndroidJUnit4; + +import com.adobe.marketing.mobile.AdobeCallback; +import com.adobe.marketing.mobile.MobileCore; +import com.adobe.marketing.mobile.TestHelper; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.RuleChain; +import org.junit.runner.RunWith; + +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.CountDownLatch; + +import static com.adobe.marketing.mobile.TestHelper.getSharedStateFor; +import static com.adobe.marketing.mobile.TestHelper.resetTestExpectations; +import static com.adobe.marketing.mobile.identityedge.IdentityEdgeTestUtil.flattenMap; +import static org.junit.Assert.assertEquals; + +@RunWith(AndroidJUnit4.class) +public class IdentityEdgePublicAPITest { + + @Rule + public RuleChain rule = RuleChain.outerRule(new TestHelper.SetupCoreRule()) + .around(new TestHelper.RegisterMonitorExtensionRule()); + + + // -------------------------------------------------------------------------------------------- + // Setup + // -------------------------------------------------------------------------------------------- + + @Before + public void setup() throws Exception { + IdentityEdge.registerExtension(); + + final CountDownLatch latch = new CountDownLatch(1); + MobileCore.start(new AdobeCallback() { + @Override + public void call(Object o) { + latch.countDown(); + } + }); + + latch.await(); + resetTestExpectations(); + } + + // -------------------------------------------------------------------------------------------- + // Tests for GetExtensionVersion API + // -------------------------------------------------------------------------------------------- + @Test + public void testGetExtensionVersionAPI() { + assertEquals(IdentityEdgeConstants.EXTENSION_VERSION, IdentityEdge.extensionVersion()); + } + + // -------------------------------------------------------------------------------------------- + // Tests for Register extension API + // -------------------------------------------------------------------------------------------- + @Test + public void testRegisterExtensionAPI() throws InterruptedException { + // test + // Consent.registerExtension() is called in the setup method + + // verify that the extension is registered with the correct version details + Map sharedStateMap = flattenMap(getSharedStateFor(IdentityEdgeTestConstants.SharedStateName.EVENT_HUB, 1000)); + assertEquals(IdentityEdgeConstants.EXTENSION_VERSION, sharedStateMap.get("extensions.com.adobe.identityedge.version")); + } +} diff --git a/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdge.java b/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdge.java index 5d121200..4204395e 100644 --- a/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdge.java +++ b/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdge.java @@ -22,6 +22,7 @@ import java.util.List; + public class IdentityEdge { private static final String LOG_TAG = "IdentityEdge"; @@ -116,7 +117,7 @@ public static void updateIdentities(final IdentityMap identityMap) { final ExtensionErrorCallback errorCallback = new ExtensionErrorCallback() { @Override public void error(final ExtensionError extensionError) { - MobileCore.log(LoggingMode.DEBUG, LOG_TAG, String.format("Consents.update() API. Failed to dispatch %s event: Error : %s.", IdentityEdgeConstants.EventNames.UPDATE_IDENTITIES, + MobileCore.log(LoggingMode.DEBUG, LOG_TAG, String.format("Update Identities API. Failed to dispatch %s event: Error : %s.", IdentityEdgeConstants.EventNames.UPDATE_IDENTITIES, extensionError.getErrorName())); } }; diff --git a/code/identityedge/src/sharedTestUtils/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeTestConstants.java b/code/identityedge/src/sharedTestUtils/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeTestConstants.java new file mode 100644 index 00000000..1d813450 --- /dev/null +++ b/code/identityedge/src/sharedTestUtils/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeTestConstants.java @@ -0,0 +1,35 @@ +/* + Copyright 2021 Adobe. All rights reserved. + This file is licensed to you under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. You may obtain a copy + of the License at http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software distributed under + the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + OF ANY KIND, either express or implied. See the License for the specific language + governing permissions and limitations under the License. +*/ + +package com.adobe.marketing.mobile.identityedge; + +public class IdentityEdgeTestConstants { + + public final class DataStoreKey { + public static final String CONFIG_DATASTORE = "AdobeMobile_ConfigState"; + public static final String IDENTITYEDGE_DATASTORE = "com.adobe.identityEdge"; + private DataStoreKey() { } + } + + public final class SharedStateName { + public static final String CONFIG = "com.adobe.module.configuration"; + public static final String EVENT_HUB = "com.adobe.module.eventhub"; + private SharedStateName() { } + } + + public final class GetConsentHelper { + public static final String VALUE = "getConsentValue"; + public static final String ERROR = "getConsentError"; + private GetConsentHelper() { } + } + + +} diff --git a/code/identityedge/src/sharedTestUtils/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeTestUtil.java b/code/identityedge/src/sharedTestUtils/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeTestUtil.java new file mode 100644 index 00000000..1a0db1f4 --- /dev/null +++ b/code/identityedge/src/sharedTestUtils/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeTestUtil.java @@ -0,0 +1,92 @@ +/* + Copyright 2021 Adobe. All rights reserved. + This file is licensed to you under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. You may obtain a copy + of the License at http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software distributed under + the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + OF ANY KIND, either express or implied. See the License for the specific language + governing permissions and limitations under the License. +*/ + +package com.adobe.marketing.mobile.identityedge; + +import com.adobe.marketing.mobile.LoggingMode; +import com.adobe.marketing.mobile.MobileCore; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ArrayNode; +import com.fasterxml.jackson.databind.node.ObjectNode; +import com.fasterxml.jackson.databind.node.ValueNode; + +import org.json.JSONObject; + +import java.io.IOException; +import java.util.Collections; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; + +class IdentityEdgeTestUtil { + /** + * Serialize the given {@code map} to a JSON Object, then flattens to {@code Map}. + * For example, a JSON such as "{xdm: {stitchId: myID, eventType: myType}}" is flattened + * to two map elements "xdm.stitchId" = "myID" and "xdm.eventType" = "myType". + * @param map map with JSON structure to flatten + * @return new map with flattened structure + */ + static Map flattenMap(final Map map) { + if (map == null || map.isEmpty()) { + return Collections.emptyMap(); + } + + try { + JSONObject jsonObject = new JSONObject(map); + Map payloadMap = new HashMap<>(); + addKeys("", new ObjectMapper().readTree(jsonObject.toString()), payloadMap); + return payloadMap; + } catch (IOException e) { + MobileCore.log(LoggingMode.ERROR, "FunctionalTestUtils", "Failed to parse JSON object to tree structure."); + } + + return Collections.emptyMap(); + } + + + /** + * Deserialize {@code JsonNode} and flatten to provided {@code map}. + * For example, a JSON such as "{xdm: {stitchId: myID, eventType: myType}}" is flattened + * to two map elements "xdm.stitchId" = "myID" and "xdm.eventType" = "myType". + * + * Method is called recursively. To use, call with an empty path such as + * {@code addKeys("", new ObjectMapper().readTree(JsonNodeAsString), map);} + * + * @param currentPath the path in {@code JsonNode} to process + * @param jsonNode {@link JsonNode} to deserialize + * @param map {@code Map} instance to store flattened JSON result + * + * @see Stack Overflow post + */ + private static void addKeys(String currentPath, JsonNode jsonNode, Map map) { + if (jsonNode.isObject()) { + ObjectNode objectNode = (ObjectNode) jsonNode; + Iterator> iter = objectNode.fields(); + String pathPrefix = currentPath.isEmpty() ? "" : currentPath + "."; + + while (iter.hasNext()) { + Map.Entry entry = iter.next(); + addKeys(pathPrefix + entry.getKey(), entry.getValue(), map); + } + } else if (jsonNode.isArray()) { + ArrayNode arrayNode = (ArrayNode) jsonNode; + + for (int i = 0; i < arrayNode.size(); i++) { + addKeys(currentPath + "[" + i + "]", arrayNode.get(i), map); + } + } else if (jsonNode.isValueNode()) { + ValueNode valueNode = (ValueNode) jsonNode; + map.put(currentPath, valueNode.asText()); + } + } + +} From 13bf5a009b943e9bfe5cce498da31561d4a59444 Mon Sep 17 00:00:00 2001 From: Kevin Lind <40409666+kevinlind@users.noreply.github.com> Date: Wed, 17 Mar 2021 22:04:15 -0700 Subject: [PATCH 011/101] Migrate ECID from direct Identity extension (#13) * Add method to load ECID from direct Identity datastore. * Load ECID from direct identity during IdentityState bootup * Add secondary ecid to IdentityProperties * Add API to update legacy ECID in IdentityState * Add listener for Hub Shared State changes from direct Identity to update legacy ECID value. * Correct copywrite on new files * Make ListenerHubSharedStateTests class public * handle class cast exceptions and mark local variables final * Make ECID class final and add unit tests for equals and hashCode * Correct documentation in ListenerHubSharedState * final local variables --- .../marketing/mobile/identityedge/ECID.java | 25 ++- .../identityedge/IdentityEdgeConstants.java | 10 ++ .../identityedge/IdentityEdgeExtension.java | 69 +++++++- .../identityedge/IdentityEdgeProperties.java | 39 ++++- .../identityedge/IdentityEdgeState.java | 29 +++- .../IdentityEdgeStorageService.java | 32 +++- .../identityedge/ListenerHubSharedState.java | 65 ++++++++ .../mobile/identityedge/ECIDTests.java | 53 +++++++ .../IdentityEdgeExtensionTests.java | 148 ++++++++++++++++-- .../IdentityEdgePropertiesTests.java | 47 ++++-- .../identityedge/IdentityEdgeStateTests.java | 123 ++++++++++++++- .../IdentityEdgeStorageServiceTests.java | 25 +++ .../ListenerHubSharedStateTests.java | 81 ++++++++++ 13 files changed, 704 insertions(+), 42 deletions(-) create mode 100644 code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/ListenerHubSharedState.java create mode 100644 code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/ListenerHubSharedStateTests.java diff --git a/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/ECID.java b/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/ECID.java index 8e4e476a..ae48bace 100644 --- a/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/ECID.java +++ b/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/ECID.java @@ -15,12 +15,13 @@ import com.adobe.marketing.mobile.MobileCore; import java.util.Locale; +import java.util.Objects; import java.util.UUID; /** * This class represents an ECID */ -class ECID { +final class ECID { private final String ecidString; /** @@ -56,4 +57,26 @@ class ECID { public String toString() { return ecidString; } + + /** + * Determine if ECID {@code o} is equal to this ECID. + * @param o the ECID instance to check for equality with this ECID. + * @return true if {@code o} is equal to this ECID instance. + */ + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + ECID ecid = (ECID) o; + return Objects.equals(ecidString, ecid.ecidString); + } + + /** + * Get the hash code for this ECID. + * @return hash code for this ECID. + */ + @Override + public int hashCode() { + return Objects.hash(ecidString); + } } diff --git a/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeConstants.java b/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeConstants.java index f586518e..5fd25283 100644 --- a/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeConstants.java +++ b/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeConstants.java @@ -25,6 +25,7 @@ final class EventSource { static final String UPDATE_IDENTITY = "com.adobe.eventSource.updateIdentity"; static final String REMOVE_IDENTITY = "com.adobe.eventSource.removeIdentity"; static final String REQUEST_RESET = "com.adobe.eventSource.requestReset"; + static final String SHARED_STATE = "com.adobe.eventSource.sharedState"; private EventSource() { } } @@ -32,6 +33,7 @@ final class EventType { static final String GENERIC_IDENTITY = "com.adobe.eventType.generic.identity"; static final String EDGE_IDENTITY = "com.adobe.eventType.edgeIdentity"; static final String IDENTITY = "com.adobe.eventType.identity"; + static final String HUB = "com.adobe.eventType.hub"; private EventType() { } } @@ -47,9 +49,15 @@ private EventNames() { } final class EventDataKeys { static final String VISITOR_ID_ECID = "mid"; + static final String STATE_OWNER = "stateowner"; private EventDataKeys() { } } + final class SharedStateKeys { + static final String IDENTITY_DIRECT = "com.adobe.module.identity"; + private SharedStateKeys() { } + } + final class Namespaces { static final String ECID = "ECID"; private Namespaces() { } @@ -64,6 +72,8 @@ private XDMKeys() { } final class DataStoreKey { static final String DATASTORE_NAME = EXTENSION_NAME; static final String IDENTITY_PROPERTIES = "identity.properties"; + static final String IDENTITY_DIRECT_DATASTORE_NAME = SharedStateKeys.IDENTITY_DIRECT; + static final String IDENTITY_DIRECT_ECID_KEY = "ADOBEMOBILE_PERSISTED_MID"; private DataStoreKey() { } } diff --git a/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeExtension.java b/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeExtension.java index 70e87b01..391d4898 100644 --- a/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeExtension.java +++ b/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeExtension.java @@ -24,7 +24,7 @@ class IdentityEdgeExtension extends Extension { private final String LOG_TAG = "IdentityEdgeExtension"; - private IdentityEdgeState state = new IdentityEdgeState(new IdentityEdgeProperties()); + private final IdentityEdgeState state = new IdentityEdgeState(new IdentityEdgeProperties()); /** * Constructor. @@ -41,9 +41,10 @@ class IdentityEdgeExtension extends Extension { * and EventSource {@link IdentityEdgeConstants.EventSource#UPDATE_IDENTITY} *
  • Listener {@link ListenerIdentityEdgeRemoveIdentity} to listen for event with eventType {@link IdentityEdgeConstants.EventType#EDGE_IDENTITY} * and EventSource {@link IdentityEdgeConstants.EventSource#REMOVE_IDENTITY}
  • - * and EventSource {@link IdentityEdgeConstants.EventSource#REQUEST_CONTENT} *
  • Listener {@link ListenerIdentityRequestReset} to listen for event with eventType {@link IdentityEdgeConstants.EventType#EDGE_IDENTITY} * and EventSource {@link IdentityEdgeConstants.EventSource#REQUEST_RESET}
  • + *
  • Listener {@link ListenerHubSharedState} to listen for event with eventType {@link IdentityEdgeConstants.EventType#HUB} + * and EventSource {@link IdentityEdgeConstants.EventSource#SHARED_STATE}
  • * *

    * Thread : Background thread created by MobileCore @@ -65,6 +66,7 @@ public void error(final ExtensionError extensionError) { extensionApi.registerEventListener(IdentityEdgeConstants.EventType.EDGE_IDENTITY, IdentityEdgeConstants.EventSource.UPDATE_IDENTITY, ListenerIdentityEdgeUpdateIdentity.class, listenerErrorCallback); extensionApi.registerEventListener(IdentityEdgeConstants.EventType.EDGE_IDENTITY, IdentityEdgeConstants.EventSource.REMOVE_IDENTITY, ListenerIdentityEdgeRemoveIdentity.class, listenerErrorCallback); extensionApi.registerEventListener(IdentityEdgeConstants.EventType.EDGE_IDENTITY, IdentityEdgeConstants.EventSource.REQUEST_RESET, ListenerIdentityRequestReset.class, listenerErrorCallback); + extensionApi.registerEventListener(IdentityEdgeConstants.EventType.HUB, IdentityEdgeConstants.EventSource.SHARED_STATE, ListenerHubSharedState.class, listenerErrorCallback); } /** @@ -99,6 +101,69 @@ void handleGenericIdentityRequest(final Event event) { // TODO } + /** + * Handles events of type {@code com.adobe.eventType.hub} and source {@code com.adobe.eventSource.sharedState}. + * If the state change event is for the direct Identity extension, get the direct Identity shared state and attempt + * to update the legacy ECID with the direct Identity extension ECID. + * @param event an event of type {@code com.adobe.eventType.hub} and source {@code com.adobe.eventSource.sharedState} + */ + void handleHubSharedState(final Event event) { + if (!canProcessEvents(event)) { + MobileCore.log(LoggingMode.DEBUG, LOG_TAG, "Unable to process direct Identity shared state change event. canProcessEvents returned false."); + return; + } + + final ExtensionApi extensionApi = getApi(); + if (extensionApi == null ) { + MobileCore.log(LoggingMode.DEBUG, LOG_TAG, "ExtensionApi is null, unable to process direct Identity shared state change event."); + return; + } + + if (event == null || event.getEventData() == null) { + return; + } + + try { + final String stateOwner = (String) event.getEventData().get(IdentityEdgeConstants.EventDataKeys.STATE_OWNER); + if (!IdentityEdgeConstants.SharedStateKeys.IDENTITY_DIRECT.equals(stateOwner)) { + return; + } + } catch (ClassCastException e) { + MobileCore.log(LoggingMode.DEBUG, LOG_TAG, "Could not process direct Identity shared state change event, failed to parse event state owner as String: " + e.getLocalizedMessage()); + return; + } + + final ExtensionErrorCallback getSharedStateCallback = new ExtensionErrorCallback() { + @Override + public void error(final ExtensionError extensionError) { + MobileCore.log(LoggingMode.DEBUG, LOG_TAG, String.format("Failed getting direct Identity shared state. Error : %s.", extensionError.getErrorName())); + } + }; + + final Map identityState = extensionApi.getSharedEventState(IdentityEdgeConstants.SharedStateKeys.IDENTITY_DIRECT, event, getSharedStateCallback); + + if (identityState == null) { + return; + } + + try { + final String legacyEcidString = (String) identityState.get(IdentityEdgeConstants.EventDataKeys.VISITOR_ID_ECID); + final ECID legacyEcid = legacyEcidString == null ? null : new ECID(legacyEcidString); + + if (state.updateLegacyExperienceCloudId(legacyEcid)) { + final ExtensionErrorCallback errorCallback = new ExtensionErrorCallback() { + @Override + public void error(final ExtensionError extensionError) { + MobileCore.log(LoggingMode.DEBUG, LOG_TAG, String.format("Failed to create XDM shared state. Error : %s.", extensionError.getErrorName())); + } + }; + extensionApi.setXDMSharedEventState(state.getIdentityEdgeProperties().toXDMData(false), event, errorCallback); + } + } catch (ClassCastException e) { + MobileCore.log(LoggingMode.DEBUG, LOG_TAG, "Could not process direct Identity shared state change event, failed to parse stored ECID as String: " + e.getLocalizedMessage()); + } + } + /** * Handles events requesting for identifiers. Dispatches response event containing the identifiers. Called by listener registered with event hub. * @param event the identity request event diff --git a/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeProperties.java b/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeProperties.java index 99bad7e3..30bfddf0 100644 --- a/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeProperties.java +++ b/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeProperties.java @@ -28,6 +28,9 @@ class IdentityEdgeProperties { // The current Experience Cloud ID private ECID ecid; + // A secondary (non-primary) Experience Cloud ID + private ECID ecidSecondary; + IdentityEdgeProperties() { } /** @@ -39,12 +42,16 @@ class IdentityEdgeProperties { return; } - IdentityMap identityMap = IdentityMap.fromData(xdmData); + final IdentityMap identityMap = IdentityMap.fromData(xdmData); if (identityMap != null) { final List ecidItems = identityMap.getIdentityItemsForNamespace(IdentityEdgeConstants.Namespaces.ECID); - boolean containsEcid = ecidItems != null && !ecidItems.isEmpty() && ecidItems.get(0).getId() != null; - if (containsEcid) { - ecid = new ECID(ecidItems.get(0).getId()); + if (ecidItems != null) { + if (ecidItems.size() > 0 && ecidItems.get(0) != null && ecidItems.get(0).getId() != null) { + ecid = new ECID(ecidItems.get(0).getId()); + } + if (ecidItems.size() > 1 && ecidItems.get(1) != null && ecidItems.get(1).getId() != null) { + ecidSecondary = new ECID(ecidItems.get(1).getId()); + } } } } @@ -65,6 +72,22 @@ ECID getECID() { return ecid; } + /** + * Sets a secondary {@link ECID} + * @param ecid a new secondary {@code ECID} + */ + void setECIDSecondary(final ECID ecid) { + this.ecidSecondary = ecid; + } + + /** + * Retrieves the secondary {@link ECID}. + * @return secondary {@code ECID} + */ + ECID getECIDSecondary() { + return ecidSecondary; + } + /** * Converts this into an event data representation in XDM format * @param allowEmpty If this {@link IdentityEdgeProperties} contains no data, return a dictionary with a single {@link IdentityMap} key @@ -75,8 +98,14 @@ Map toXDMData(final boolean allowEmpty) { final IdentityMap identityMap = new IdentityMap(); if (ecid != null) { - IdentityItem ecidItem = new IdentityItem(ecid.toString()); + final IdentityItem ecidItem = new IdentityItem(ecid.toString()); identityMap.addItem(IdentityEdgeConstants.Namespaces.ECID, ecidItem); + + // set second ECID only if primary exists + if (ecidSecondary != null) { + final IdentityItem ecidSecondaryItem = new IdentityItem(ecidSecondary.toString()); + identityMap.addItem(IdentityEdgeConstants.Namespaces.ECID, ecidSecondaryItem); + } } final Map>> dict = identityMap.toObjectMap(); diff --git a/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeState.java b/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeState.java index 4e0b78df..06a117a0 100644 --- a/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeState.java +++ b/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeState.java @@ -19,7 +19,7 @@ * Manages the business logic of the Identity Edge extension */ class IdentityEdgeState { - private String LOG_TAG = "IdentityEdgeState"; + private static String LOG_TAG = "IdentityEdgeState"; private boolean hasBooted = false; private IdentityEdgeProperties identityProperties; @@ -60,7 +60,14 @@ boolean bootupIfReady() { // Generate new ECID on first launch if (identityProperties.getECID() == null) { - identityProperties.setECID(new ECID()); + final ECID directIdentityEcid = IdentityEdgeStorageService.loadEcidFromDirectIdentityPersistence(); + if (directIdentityEcid == null) { + identityProperties.setECID(new ECID()); + MobileCore.log(LoggingMode.DEBUG, LOG_TAG, "Bootup - Generating new ECID '" + identityProperties.getECID().toString() + "'"); + } else { + identityProperties.setECID(directIdentityEcid); + MobileCore.log(LoggingMode.DEBUG, LOG_TAG, "Bootup - Loading ECID from direct Identity extension '" + directIdentityEcid + "'"); + } IdentityEdgeStorageService.savePropertiesToPersistence(identityProperties); } @@ -77,9 +84,27 @@ void resetIdentifiers() { identityProperties = new IdentityEdgeProperties(); identityProperties.setECID(new ECID()); + identityProperties.setECIDSecondary(null); IdentityEdgeStorageService.savePropertiesToPersistence(identityProperties); // TODO: AMSDK-11208 Use return value to tell IdentityEdge to dispatch consent ad id update } + /** + * Update the legacy ECID property with {@code legacyEcid} provided it does not equal the primary or secondary ECIDs + * currently in {@code IdentityEdgePoperties}. + * @param legacyEcid the current ECID from the direct Identity extension + * @return true if the legacy ECID was updated in {@code IdentityEdgeProperties} + */ + boolean updateLegacyExperienceCloudId(final ECID legacyEcid) { + if (legacyEcid == identityProperties.getECID() || legacyEcid == identityProperties.getECIDSecondary()) { + return false; + } + + identityProperties.setECIDSecondary(legacyEcid); + IdentityEdgeStorageService.savePropertiesToPersistence(identityProperties); + MobileCore.log(LoggingMode.DEBUG, LOG_TAG,"Identity direct ECID updated to '" + legacyEcid + "', updating the IdentityMap"); + return true; + } + } diff --git a/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeStorageService.java b/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeStorageService.java index 2d515236..0f34ee94 100644 --- a/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeStorageService.java +++ b/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeStorageService.java @@ -34,7 +34,7 @@ class IdentityEdgeStorageService { * @return properties stored in local storage if present, otherwise null. */ static IdentityEdgeProperties loadPropertiesFromPersistence() { - final SharedPreferences sharedPreferences = getSharedPreference(); + final SharedPreferences sharedPreferences = getSharedPreference(IdentityEdgeConstants.DataStoreKey.DATASTORE_NAME); if (sharedPreferences == null) { MobileCore.log(LoggingMode.DEBUG, LOG_TAG, "Shared Preference value is null. Unable to load saved identity properties from persistence."); return null; @@ -63,13 +63,13 @@ static IdentityEdgeProperties loadPropertiesFromPersistence() { * @param properties properties to be stored */ static void savePropertiesToPersistence(final IdentityEdgeProperties properties) { - SharedPreferences sharedPreferences = getSharedPreference(); + final SharedPreferences sharedPreferences = getSharedPreference(IdentityEdgeConstants.DataStoreKey.DATASTORE_NAME); if (sharedPreferences == null) { MobileCore.log(LoggingMode.DEBUG, LOG_TAG, "Shared Preference value is null. Unable to write identity properties to persistence."); return; } - SharedPreferences.Editor editor = sharedPreferences.edit(); + final SharedPreferences.Editor editor = sharedPreferences.edit(); if (editor == null) { MobileCore.log(LoggingMode.DEBUG, LOG_TAG, "Shared Preference Editor is null. Unable to write identity properties to persistence."); @@ -89,14 +89,36 @@ static void savePropertiesToPersistence(final IdentityEdgeProperties properties) editor.apply(); } + /** + * Retrieves the direct Identity extension ECID value stored in persistence. + * @return {@link ECID} stored in direct Identity extension's persistence, or null if no ECID value is stored. + */ + static ECID loadEcidFromDirectIdentityPersistence() { + final SharedPreferences sharedPreferences = getSharedPreference(IdentityEdgeConstants.DataStoreKey.IDENTITY_DIRECT_DATASTORE_NAME); + if (sharedPreferences == null) { + MobileCore.log(LoggingMode.DEBUG, LOG_TAG, "Shared Preference value is null. Unable to load saved direct identity ECID from persistence."); + return null; + } + + final String ecidString = sharedPreferences.getString(IdentityEdgeConstants.DataStoreKey.IDENTITY_DIRECT_ECID_KEY, null); + + if (ecidString == null || ecidString.isEmpty()) { + return null; + } + + return new ECID(ecidString); + } + /** * Getter for the applications {@link SharedPreferences} *

    * Returns null if the app or app context is not available * + * @param datastoreName the name of the data store to get + * * @return a {@code SharedPreferences} instance */ - private static SharedPreferences getSharedPreference() { + private static SharedPreferences getSharedPreference(final String datastoreName) { final Application application = MobileCore.getApplication(); if (application == null) { MobileCore.log(LoggingMode.DEBUG, LOG_TAG, "Application value is null. Unable to read/write data from persistence."); @@ -109,6 +131,6 @@ private static SharedPreferences getSharedPreference() { return null; } - return context.getSharedPreferences(IdentityEdgeConstants.DataStoreKey.DATASTORE_NAME, Context.MODE_PRIVATE); + return context.getSharedPreferences(datastoreName, Context.MODE_PRIVATE); } } diff --git a/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/ListenerHubSharedState.java b/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/ListenerHubSharedState.java new file mode 100644 index 00000000..bb5e8b59 --- /dev/null +++ b/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/ListenerHubSharedState.java @@ -0,0 +1,65 @@ +/* + Copyright 2021 Adobe. All rights reserved. + This file is licensed to you under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. You may obtain a copy + of the License at http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software distributed under + the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + OF ANY KIND, either express or implied. See the License for the specific language + governing permissions and limitations under the License. +*/ + +package com.adobe.marketing.mobile.identityedge; + +import com.adobe.marketing.mobile.Event; +import com.adobe.marketing.mobile.ExtensionApi; +import com.adobe.marketing.mobile.ExtensionListener; +import com.adobe.marketing.mobile.LoggingMode; +import com.adobe.marketing.mobile.MobileCore; + +class ListenerHubSharedState extends ExtensionListener { + /** + * Constructor. + * + * @param extensionApi an instance of {@link ExtensionApi} + * @param type the {@link String} eventType this listener is registered to handle + * @param source the {@link String} eventSource this listener is registered to handle + */ + ListenerHubSharedState(final ExtensionApi extensionApi, final String type, final String source) { + super(extensionApi, type, source); + } + + + /** + * Method that gets called when event with event type {@link IdentityEdgeConstants.EventType#HUB} + * and with event source {@link IdentityEdgeConstants.EventSource#SHARED_STATE} is dispatched through eventHub. + * + * @param event the hub shared state change {@link Event} to be processed + */ + @Override + public void hear(final Event event) { + if (event == null) { + MobileCore.log(LoggingMode.DEBUG, IdentityEdgeConstants.LOG_TAG, "Event is null. Ignoring the event listened by ListenerHubSharedState"); + return; + } + + final IdentityEdgeExtension parentExtension = getIdentityEdgeExtension(); + + if (parentExtension == null) { + MobileCore.log(LoggingMode.DEBUG, IdentityEdgeConstants.LOG_TAG, + "The parent extension, associated with the ListenerHubSharedState is null, ignoring hub shared state event."); + return; + } + + parentExtension.handleHubSharedState(event); + } + + /** + * Returns the parent extension associated with the listener. + * + * @return a {@link IdentityEdgeExtension} object registered with the eventHub + */ + IdentityEdgeExtension getIdentityEdgeExtension() { + return (IdentityEdgeExtension) getParentExtension(); + } +} diff --git a/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/ECIDTests.java b/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/ECIDTests.java index ad996dc7..978c4a25 100644 --- a/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/ECIDTests.java +++ b/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/ECIDTests.java @@ -20,8 +20,11 @@ import static junit.framework.Assert.assertTrue; import static junit.framework.TestCase.assertEquals; +import static junit.framework.TestCase.assertFalse; import static junit.framework.TestCase.assertNotNull; +import static org.junit.Assert.assertNotEquals; + @SuppressWarnings("unchecked") public class ECIDTests { @@ -79,4 +82,54 @@ public void testECID_ReasonablyRandom() { assertEquals(count, ecids.size()); } + @Test + public void testECID_hashCode_reasonablyRandom() { + // setup + int count = 1000; + Set ecids = new HashSet(count); + + // test + for (int i = 0; i < count; i++) { + ecids.add(new ECID()); + } + + // verify + assertEquals(count, ecids.size()); + } + + @Test + public void testECID_hashCode() { + ECID a = new ECID(); + ECID b = new ECID(a.toString()); + + assertEquals(a.hashCode(), b.hashCode()); + assertEquals(a.hashCode(), a.hashCode()); + + assertNotEquals(a.hashCode(), new ECID().hashCode()); + assertNotEquals(a.hashCode(), new NotECID(a.toString()).hashCode()); + } + + @Test + public void testECID_equals() { + ECID a = new ECID(); + ECID b = new ECID(a.toString()); + + assertTrue(a.equals(b)); + assertTrue(b.equals(a)); + assertTrue(a.equals(a)); + assertTrue(b.equals(b)); + + assertFalse(a.equals(null)); + assertFalse(a.equals(new ECID())); + + assertFalse(a.equals(new NotECID(a.toString()))); + } + + private class NotECID { + private final String ecidString; + NotECID(final String s) { + this.ecidString = s; + } + } + } diff --git a/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeExtensionTests.java b/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeExtensionTests.java index 4e38284a..ed874d9a 100644 --- a/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeExtensionTests.java +++ b/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeExtensionTests.java @@ -45,6 +45,7 @@ import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; +import static org.powermock.api.mockito.PowerMockito.when; @RunWith(PowerMockRunner.class) @PrepareForTest({Event.class, MobileCore.class, ExtensionApi.class, IdentityEdgeState.class}) @@ -90,7 +91,7 @@ public void test_ListenersRegistration() { // constructor is called in the setup step() // verify 2 listeners are registered - verify(mockExtensionApi, times(5)).registerEventListener(anyString(), + verify(mockExtensionApi, times(6)).registerEventListener(anyString(), anyString(), any(Class.class), any(ExtensionErrorCallback.class)); // verify listeners are registered with correct event source and type @@ -104,6 +105,8 @@ public void test_ListenersRegistration() { eq(IdentityEdgeConstants.EventSource.REMOVE_IDENTITY), eq(ListenerIdentityEdgeRemoveIdentity.class), callbackCaptor.capture()); verify(mockExtensionApi, times(1)).registerEventListener(eq(IdentityEdgeConstants.EventType.EDGE_IDENTITY), eq(IdentityEdgeConstants.EventSource.REQUEST_RESET), eq(ListenerIdentityRequestReset.class), callbackCaptor.capture()); + verify(mockExtensionApi, times(1)).registerEventListener(eq(IdentityEdgeConstants.EventType.HUB), + eq(IdentityEdgeConstants.EventSource.SHARED_STATE), eq(ListenerHubSharedState.class), callbackCaptor.capture()); // verify the callback ExtensionErrorCallback extensionErrorCallback = callbackCaptor.getValue(); @@ -229,8 +232,133 @@ public void test_handleIdentityResetRequest() { // verify verify(mockExtensionApi, times(1)).setXDMSharedEventState(sharedStateCaptor.capture(), eq(event), any(ExtensionErrorCallback.class)); Map sharedState = sharedStateCaptor.getValue(); - String sharedEcid = ecidFromIdentityMap(sharedState); - assertTrue(sharedEcid.length() > 0); + IdentityItem sharedEcid = getItemFromIdentityMap(sharedState, "ECID", 0); + assertNotNull(sharedEcid); + assertTrue(sharedEcid.getId().length() > 0); + } + + @Test + public void test_handleHubSharedState_updateLegacyEcidOnDirectIdentityStateChange() { + when(mockExtensionApi.getSharedEventState(eq(IdentityEdgeConstants.SharedStateKeys.IDENTITY_DIRECT), + any(Event.class), + any(ExtensionErrorCallback.class))) + .thenReturn(new HashMap() {{ + put(IdentityEdgeConstants.EventDataKeys.VISITOR_ID_ECID, "1234"); + }}); + + Event event = new Event.Builder("Test event", + IdentityEdgeConstants.EventType.HUB, + IdentityEdgeConstants.EventSource.SHARED_STATE) + .setEventData(new HashMap(){{ + put(IdentityEdgeConstants.EventDataKeys.STATE_OWNER, IdentityEdgeConstants.SharedStateKeys.IDENTITY_DIRECT); + }}).build(); + + extension.handleHubSharedState(event); + + final ArgumentCaptor> sharedStateCaptor = ArgumentCaptor.forClass(Map.class); + verify(mockExtensionApi, times(1)).setXDMSharedEventState(sharedStateCaptor.capture(), eq(event), any(ExtensionErrorCallback.class)); + Map sharedState = sharedStateCaptor.getValue(); + IdentityItem legacyEcidItem = getItemFromIdentityMap(sharedState, "ECID", 1); + assertNotNull(legacyEcidItem); + assertEquals("1234", legacyEcidItem.getId()); + } + + @Test + public void test_handleHubSharedState_noOpNullEvent() { + when(mockExtensionApi.getSharedEventState(eq(IdentityEdgeConstants.SharedStateKeys.IDENTITY_DIRECT), + any(Event.class), + any(ExtensionErrorCallback.class))) + .thenReturn(new HashMap() {{ + put(IdentityEdgeConstants.EventDataKeys.VISITOR_ID_ECID, "1234"); + }}); + + extension.handleHubSharedState(null); + + verify(mockExtensionApi, times(0)).setXDMSharedEventState(any(Map.class), any(Event.class), any(ExtensionErrorCallback.class)); + } + + @Test + public void test_handleHubSharedState_noOpNullEventData() { + when(mockExtensionApi.getSharedEventState(eq(IdentityEdgeConstants.SharedStateKeys.IDENTITY_DIRECT), + any(Event.class), + any(ExtensionErrorCallback.class))) + .thenReturn(new HashMap() {{ + put(IdentityEdgeConstants.EventDataKeys.VISITOR_ID_ECID, "1234"); + }}); + + Event event = new Event.Builder("Test event", + IdentityEdgeConstants.EventType.HUB, + IdentityEdgeConstants.EventSource.SHARED_STATE) + .build(); + + extension.handleHubSharedState(event); + + verify(mockExtensionApi, times(0)).setXDMSharedEventState(any(Map.class), any(Event.class), any(ExtensionErrorCallback.class)); + } + + @Test + public void test_handleHubSharedState_noOpNotDirectIdentityStateChange() { + when(mockExtensionApi.getSharedEventState(eq(IdentityEdgeConstants.SharedStateKeys.IDENTITY_DIRECT), + any(Event.class), + any(ExtensionErrorCallback.class))) + .thenReturn(new HashMap() {{ + put(IdentityEdgeConstants.EventDataKeys.VISITOR_ID_ECID, "1234"); + }}); + + Event event = new Event.Builder("Test event", + IdentityEdgeConstants.EventType.HUB, + IdentityEdgeConstants.EventSource.SHARED_STATE) + .setEventData(new HashMap(){{ + put(IdentityEdgeConstants.EventDataKeys.STATE_OWNER, "com.adobe.module.configuration"); + }}).build(); + + extension.handleHubSharedState(event); + + verify(mockExtensionApi, times(0)).setXDMSharedEventState(any(Map.class), any(Event.class), any(ExtensionErrorCallback.class)); + } + + @Test + public void test_handleHubSharedState_noOpNoDirectIdentitySharedState() { + when(mockExtensionApi.getSharedEventState(eq(IdentityEdgeConstants.SharedStateKeys.IDENTITY_DIRECT), + any(Event.class), + any(ExtensionErrorCallback.class))) + .thenReturn(null); + + Event event = new Event.Builder("Test event", + IdentityEdgeConstants.EventType.HUB, + IdentityEdgeConstants.EventSource.SHARED_STATE) + .setEventData(new HashMap(){{ + put(IdentityEdgeConstants.EventDataKeys.STATE_OWNER, IdentityEdgeConstants.SharedStateKeys.IDENTITY_DIRECT); + }}).build(); + + extension.handleHubSharedState(event); + + verify(mockExtensionApi, times(0)).setXDMSharedEventState(any(Map.class), any(Event.class), any(ExtensionErrorCallback.class)); + } + + @Test + public void test_handleHubSharedState_doesNotShareStateIfLegacyECIDDoesNotChange() { + when(mockExtensionApi.getSharedEventState(eq(IdentityEdgeConstants.SharedStateKeys.IDENTITY_DIRECT), + any(Event.class), + any(ExtensionErrorCallback.class))) + .thenReturn(new HashMap() {{ + put(IdentityEdgeConstants.EventDataKeys.VISITOR_ID_ECID, "1234"); + }}); + + Event event = new Event.Builder("Test event", + IdentityEdgeConstants.EventType.HUB, + IdentityEdgeConstants.EventSource.SHARED_STATE) + .setEventData(new HashMap(){{ + put(IdentityEdgeConstants.EventDataKeys.STATE_OWNER, IdentityEdgeConstants.SharedStateKeys.IDENTITY_DIRECT); + }}).build(); + + // IdentityState.updateLegacyExperienceCloudId returns false if Legacy ECID was not updated + PowerMockito.stub(PowerMockito.method(IdentityEdgeState.class, "updateLegacyExperienceCloudId")).toReturn(false); + + extension.handleHubSharedState(event); + + final ArgumentCaptor> sharedStateCaptor = ArgumentCaptor.forClass(Map.class); + verify(mockExtensionApi, times(0)).setXDMSharedEventState(sharedStateCaptor.capture(), eq(event), any(ExtensionErrorCallback.class)); } // ======================================================================================== @@ -245,17 +373,13 @@ private void setupExistingIdentityEdgeProps(final ECID ecid) { Mockito.when(mockSharedPreference.getString(IdentityEdgeConstants.DataStoreKey.IDENTITY_PROPERTIES, null)).thenReturn(propsJSON); } - private String ecidFromIdentityMap(Map xdmMap) { + private static IdentityItem getItemFromIdentityMap(final Map xdmMap, final String namespace, final int itemIndex) { if (xdmMap == null) { return null; } - Map identityMap = (HashMap) xdmMap.get("identityMap"); + Map identityMap = (Map) xdmMap.get("identityMap"); if (identityMap == null) { return null; } - List ecidArr = (ArrayList) identityMap.get("ECID"); - if (ecidArr == null) { return null; } - Map ecidDict = (HashMap) ecidArr.get(0); - if (ecidDict == null) { return null; } - String ecid = (String) ecidDict.get("id"); - return ecid; + List itemList = (List) identityMap.get(namespace); + if (itemList == null || itemList.size() <= itemIndex) { return null; } + return IdentityItem.fromData((Map)itemList.get(itemIndex)); } - } diff --git a/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/IdentityEdgePropertiesTests.java b/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/IdentityEdgePropertiesTests.java index 5ddcf3f8..efb31d4b 100644 --- a/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/IdentityEdgePropertiesTests.java +++ b/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/IdentityEdgePropertiesTests.java @@ -19,6 +19,7 @@ import java.util.Map; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; public class IdentityEdgePropertiesTests { @@ -40,12 +41,32 @@ public void testIdentityEdgeProperties_toXDMDataFull() { // setup IdentityEdgeProperties props = new IdentityEdgeProperties(); props.setECID(new ECID()); + props.setECIDSecondary(new ECID()); // test Map xdmData = props.toXDMData(false); // verify - assertEquals(props.getECID().toString(), ecidFromIdentityMap(xdmData)); + Map ecidItem = getItemFromIdentityMap(xdmData, "ECID", 0); + Map ecidSecondaryItem = getItemFromIdentityMap(xdmData, "ECID", 1); + + assertNotNull(ecidItem); + assertNotNull(ecidSecondaryItem); + assertEquals(props.getECID().toString(), (String)ecidItem.get("id")); + assertEquals(props.getECIDSecondary().toString(), (String)ecidSecondaryItem.get("id")); + } + + @Test + public void testIdentityEdgeProperties_toXDMDataOnlyPrimaryECID() { + // setup + IdentityEdgeProperties props = new IdentityEdgeProperties(); + props.setECID(new ECID()); + + // test + Map xdmMap = props.toXDMData(false); + + // verify + assertEquals(props.getECID().toString(), ecidFromIdentityMap(xdmMap)); } @Test @@ -78,6 +99,7 @@ public void testIdentityEdgeProperties_fromXDMDataFull() { // setup IdentityEdgeProperties props = new IdentityEdgeProperties(); props.setECID(new ECID()); + props.setECIDSecondary(new ECID()); // test Map xdmData = props.toXDMData(false); @@ -85,6 +107,7 @@ public void testIdentityEdgeProperties_fromXDMDataFull() { // verify assertEquals(ecidFromIdentityMap(xdmData), loadedProps.getECID().toString()); + assertEquals(props.getECIDSecondary(), loadedProps.getECIDSecondary()); } @Test @@ -114,19 +137,6 @@ public void testIdentityEdgeProperties_fromXDMDataMissingPrivacy() { assertEquals(ecidFromIdentityMap(xdmMap), loadedProps.getECID().toString()); } - @Test - public void testIdentityEdgeProperties_toXDMDataWithECID() { - // setup - IdentityEdgeProperties props = new IdentityEdgeProperties(); - props.setECID(new ECID()); - - // test - Map xdmMap = props.toXDMData(false); - - // verify - assertEquals(props.getECID().toString(), ecidFromIdentityMap(xdmMap)); - } - private String ecidFromIdentityMap(Map xdmMap) { if (xdmMap == null) { return null; } Map identityMap = (HashMap) xdmMap.get("identityMap"); @@ -139,4 +149,13 @@ private String ecidFromIdentityMap(Map xdmMap) { return ecid; } + private static Map getItemFromIdentityMap(final Map xdmMap, final String namespace, final int itemIndex) { + if (xdmMap == null) { return null; } + Map identityMap = (Map) xdmMap.get("identityMap"); + if (identityMap == null) { return null; } + List itemList = (List) identityMap.get(namespace); + if (itemList == null) { return null; } + return (Map) itemList.get(itemIndex); + } + } diff --git a/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeStateTests.java b/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeStateTests.java index c926e9cf..5569ee3d 100644 --- a/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeStateTests.java +++ b/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeStateTests.java @@ -78,6 +78,43 @@ public void testIdentityEdgeState_BootupIfReadyGeneratesECID() { assertNotNull(state.getIdentityEdgeProperties().getECID()); } + @Test + public void testIdentityEdgeState_BootupIfReadyLoadsDirectIdentityECID() { + // setup + ECID ecid = new ECID(); + Mockito.when(mockContext.getSharedPreferences(IdentityEdgeConstants.DataStoreKey.IDENTITY_DIRECT_DATASTORE_NAME, 0)).thenReturn(mockSharedPreference); + Mockito.when(mockSharedPreference.getString(IdentityEdgeConstants.DataStoreKey.IDENTITY_DIRECT_ECID_KEY, null)).thenReturn(ecid.toString()); + + IdentityEdgeState state = new IdentityEdgeState(new IdentityEdgeProperties()); + assertNull(state.getIdentityEdgeProperties().getECID()); + + // test + boolean result = state.bootupIfReady(); + verify(mockSharedPreferenceEditor, Mockito.times(1)).apply(); // saves to data store + + // verify + assertTrue(result); + assertEquals(ecid, state.getIdentityEdgeProperties().getECID()); + } + + @Test + public void testIdentityEdgeState_BootupIfReadyGenratesECIDWhenDirectECIDIsNull() { + // setup + Mockito.when(mockContext.getSharedPreferences(IdentityEdgeConstants.DataStoreKey.IDENTITY_DIRECT_DATASTORE_NAME, 0)).thenReturn(mockSharedPreference); + Mockito.when(mockSharedPreference.getString(IdentityEdgeConstants.DataStoreKey.IDENTITY_DIRECT_ECID_KEY, null)).thenReturn(null); + + IdentityEdgeState state = new IdentityEdgeState(new IdentityEdgeProperties()); + assertNull(state.getIdentityEdgeProperties().getECID()); + + // test + boolean result = state.bootupIfReady(); + verify(mockSharedPreferenceEditor, Mockito.times(1)).apply(); // saves to data store + + // verify + assertTrue(result); + assertNotNull(state.getIdentityEdgeProperties().getECID()); + } + @Test public void testIdentityEdgeState_BootupIfReadyLoadsFromPersistence() { // setup @@ -98,19 +135,103 @@ public void testIdentityEdgeState_BootupIfReadyLoadsFromPersistence() { assertEquals(persistedProps.getECID().toString(), state.getIdentityEdgeProperties().getECID().toString()); } + @Test + public void testIdentityEdgeState_BootupIfReadyLoadsFromPersistenceWhenDirectECIDIsValid() { + // setup + ECID ecid = new ECID(); + Mockito.when(mockContext.getSharedPreferences(IdentityEdgeConstants.DataStoreKey.IDENTITY_DIRECT_DATASTORE_NAME, 0)).thenReturn(mockSharedPreference); + Mockito.when(mockSharedPreference.getString(IdentityEdgeConstants.DataStoreKey.IDENTITY_DIRECT_ECID_KEY, null)).thenReturn(ecid.toString()); + + IdentityEdgeState state = new IdentityEdgeState(new IdentityEdgeProperties()); + + IdentityEdgeProperties persistedProps = new IdentityEdgeProperties(); + persistedProps.setECID(new ECID()); + final JSONObject jsonObject = new JSONObject(persistedProps.toXDMData(false)); + final String propsJSON = jsonObject.toString(); + Mockito.when(mockSharedPreference.getString(IdentityEdgeConstants.DataStoreKey.IDENTITY_PROPERTIES, null)).thenReturn(propsJSON); + + // test + boolean result = state.bootupIfReady(); + verify(mockSharedPreferenceEditor, never()).apply(); + + // verify + assertTrue(result); + assertEquals(persistedProps.getECID(), state.getIdentityEdgeProperties().getECID()); + } + @Test public void testIdentityEdgeState_resetIdentifiers() { // setup IdentityEdgeState state = new IdentityEdgeState(new IdentityEdgeProperties()); state.getIdentityEdgeProperties().setECID(new ECID()); + state.getIdentityEdgeProperties().setECIDSecondary(new ECID()); ECID existingEcid = state.getIdentityEdgeProperties().getECID(); // test state.resetIdentifiers(); // verify - assertNotEquals(existingEcid.toString(), state.getIdentityEdgeProperties().getECID().toString()); // ECID should be regenerated + assertNotEquals(existingEcid, state.getIdentityEdgeProperties().getECID()); // ECID should be regenerated assertFalse(state.getIdentityEdgeProperties().getECID().toString().isEmpty()); // ECID should not be empty + assertNull(state.getIdentityEdgeProperties().getECIDSecondary()); // should be cleared verify(mockSharedPreferenceEditor, Mockito.times(1)).apply(); // should save to data store } + + @Test + public void testIdentityEdgeState_updateLegacyExperienceCloudId() { + IdentityEdgeState state = new IdentityEdgeState(new IdentityEdgeProperties()); + ECID legacyEcid = new ECID(); + + state.updateLegacyExperienceCloudId(legacyEcid); + + assertEquals(legacyEcid, state.getIdentityEdgeProperties().getECIDSecondary()); + verify(mockSharedPreferenceEditor, Mockito.times(1)).apply(); + } + + @Test + public void testIdentityEdgeState_updateLegacyExperienceCloudId_notSetWhenECIDSame() { + IdentityEdgeState state = new IdentityEdgeState(new IdentityEdgeProperties()); + ECID legacyEcid = new ECID(); + state.getIdentityEdgeProperties().setECID(legacyEcid); + + state.updateLegacyExperienceCloudId(legacyEcid); + + assertNull(state.getIdentityEdgeProperties().getECIDSecondary()); + verify(mockSharedPreferenceEditor, Mockito.times(0)).apply(); + } + + @Test + public void testIdentityEdgeState_updateLegacyExperienceCloudId_notSetWhenSecondaryECIDSame() { + IdentityEdgeState state = new IdentityEdgeState(new IdentityEdgeProperties()); + ECID legacyEcid = new ECID(); + state.getIdentityEdgeProperties().setECIDSecondary(legacyEcid); + + state.updateLegacyExperienceCloudId(legacyEcid); + + assertEquals(legacyEcid, state.getIdentityEdgeProperties().getECIDSecondary()); + verify(mockSharedPreferenceEditor, Mockito.times(0)).apply(); + } + + @Test + public void testIdentityEdgeState_updateLegacyExperienceCloudId_clearsOnNull() { + IdentityEdgeState state = new IdentityEdgeState(new IdentityEdgeProperties()); + state.getIdentityEdgeProperties().setECID(new ECID()); + state.getIdentityEdgeProperties().setECIDSecondary(new ECID()); + + state.updateLegacyExperienceCloudId(null); + + assertNull(state.getIdentityEdgeProperties().getECIDSecondary()); + verify(mockSharedPreferenceEditor, Mockito.times(1)).apply(); + } + + @Test + public void testIdentityEdgeState_updateLegacyExperienceCloudId_notSetWhenExistingIsNull() { + IdentityEdgeState state = new IdentityEdgeState(new IdentityEdgeProperties()); + state.getIdentityEdgeProperties().setECID(new ECID()); + + state.updateLegacyExperienceCloudId(null); + + assertNull(state.getIdentityEdgeProperties().getECIDSecondary()); + verify(mockSharedPreferenceEditor, Mockito.times(0)).apply(); + } } diff --git a/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeStorageServiceTests.java b/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeStorageServiceTests.java index d21121a2..af219550 100644 --- a/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeStorageServiceTests.java +++ b/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeStorageServiceTests.java @@ -161,4 +161,29 @@ public void testStorageService_save_validProps() { verify(mockSharedPreferenceEditor, Mockito.times(1)).apply(); } + @Test + public void testStorageService_loadECID() { + ECID ecid = new ECID(); + Mockito.when(mockContext.getSharedPreferences(IdentityEdgeConstants.DataStoreKey.IDENTITY_DIRECT_DATASTORE_NAME, 0)).thenReturn(mockSharedPreference); + Mockito.when(mockSharedPreference.getString(IdentityEdgeConstants.DataStoreKey.IDENTITY_DIRECT_ECID_KEY, null)).thenReturn(ecid.toString()); + + assertEquals(ecid, IdentityEdgeStorageService.loadEcidFromDirectIdentityPersistence()); + } + + @Test + public void testStorageService_loadECID_nullECID() { + Mockito.when(mockContext.getSharedPreferences(IdentityEdgeConstants.DataStoreKey.IDENTITY_DIRECT_DATASTORE_NAME, 0)).thenReturn(mockSharedPreference); + Mockito.when(mockSharedPreference.getString(IdentityEdgeConstants.DataStoreKey.IDENTITY_DIRECT_ECID_KEY, null)).thenReturn(null); + + assertNull(IdentityEdgeStorageService.loadEcidFromDirectIdentityPersistence()); + } + + @Test + public void testStorageService_loadECID_emptyECID() { + Mockito.when(mockContext.getSharedPreferences(IdentityEdgeConstants.DataStoreKey.IDENTITY_DIRECT_DATASTORE_NAME, 0)).thenReturn(mockSharedPreference); + Mockito.when(mockSharedPreference.getString(IdentityEdgeConstants.DataStoreKey.IDENTITY_DIRECT_ECID_KEY, null)).thenReturn(""); + + assertNull(IdentityEdgeStorageService.loadEcidFromDirectIdentityPersistence()); + } + } diff --git a/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/ListenerHubSharedStateTests.java b/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/ListenerHubSharedStateTests.java new file mode 100644 index 00000000..ae0c7911 --- /dev/null +++ b/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/ListenerHubSharedStateTests.java @@ -0,0 +1,81 @@ +/* + Copyright 2021 Adobe. All rights reserved. + This file is licensed to you under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. You may obtain a copy + of the License at http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software distributed under + the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + OF ANY KIND, either express or implied. See the License for the specific language + governing permissions and limitations under the License. +*/ + +package com.adobe.marketing.mobile.identityedge; + +import com.adobe.marketing.mobile.Event; +import com.adobe.marketing.mobile.MobileCore; + +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.Mockito; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +public class ListenerHubSharedStateTests { + @Mock + private IdentityEdgeExtension mockIdentityEdgeExtension; + + private ListenerHubSharedState listener; + + @Before + public void setup() { + mockIdentityEdgeExtension = Mockito.mock(IdentityEdgeExtension.class); + MobileCore.start(null); + listener = spy(new ListenerHubSharedState(null, IdentityEdgeConstants.EventType.HUB, IdentityEdgeConstants.EventSource.SHARED_STATE)); + } + + @Test + public void testHear() { + // setup + Event event = new Event.Builder("Shared State Change", IdentityEdgeConstants.EventType.HUB, + IdentityEdgeConstants.EventSource.SHARED_STATE).build(); + doReturn(mockIdentityEdgeExtension).when(listener).getIdentityEdgeExtension(); + + // test + listener.hear(event); + + // verify + verify(mockIdentityEdgeExtension, times(1)).handleHubSharedState(event); + } + + @Test + public void testHear_WhenParentExtensionNull() { + // setup + Event event = new Event.Builder("Shared State Change", IdentityEdgeConstants.EventType.HUB, + IdentityEdgeConstants.EventSource.SHARED_STATE).build(); + doReturn(null).when(listener).getIdentityEdgeExtension(); + + // test + listener.hear(event); + + // verify + verify(mockIdentityEdgeExtension, times(0)).handleHubSharedState(any(Event.class)); + } + + @Test + public void testHear_WhenEventNull() { + // setup + doReturn(null).when(listener).getIdentityEdgeExtension(); + doReturn(mockIdentityEdgeExtension).when(listener).getIdentityEdgeExtension(); + + // test + listener.hear(null); + + // verify + verify(mockIdentityEdgeExtension, times(0)).handleHubSharedState(any(Event.class)); + } +} From d18203987502d0794a9990cf5ae1145c49e85ca2 Mon Sep 17 00:00:00 2001 From: Nick Porter <43650450+nporter-adbe@users.noreply.github.com> Date: Thu, 18 Mar 2021 09:24:56 -0600 Subject: [PATCH 012/101] [AMSDK-11210] Remove reset identities API (#12) * Remove rest identities API * Update doc comment in listener * Add reset complete event source * Update event source for reset response event --- .../mobile/identityedge/IdentityEdge.java | 19 -------------- .../identityedge/IdentityEdgeConstants.java | 3 ++- .../identityedge/IdentityEdgeExtension.java | 21 +++++++++++++-- .../ListenerIdentityRequestReset.java | 2 +- .../IdentityEdgeExtensionTests.java | 11 +++++--- .../identityedge/IdentityEdgeTests.java | 26 ------------------- 6 files changed, 30 insertions(+), 52 deletions(-) diff --git a/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdge.java b/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdge.java index 4204395e..359eea6b 100644 --- a/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdge.java +++ b/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdge.java @@ -174,25 +174,6 @@ public void call(Event responseEvent) { }, errorCallback); } - /** - * Clears all Identity Edge identifiers and generates a new Experience Cloud ID (ECID). - */ - public static void resetIdentities() { - final Event event = new Event.Builder(IdentityEdgeConstants.EventNames.REQUEST_RESET, - IdentityEdgeConstants.EventType.EDGE_IDENTITY, - IdentityEdgeConstants.EventSource.REQUEST_RESET).build(); - - final ExtensionErrorCallback errorCallback = new ExtensionErrorCallback() { - @Override - public void error(final ExtensionError extensionError) { - MobileCore.log(LoggingMode.DEBUG, LOG_TAG, String.format("Failed to dispatch %s event: Error : %s.", IdentityEdgeConstants.EventNames.REQUEST_RESET, - extensionError.getErrorName())); - } - }; - - MobileCore.dispatchEvent(event, errorCallback); - } - /** * When an {@link AdobeCallbackWithError} is provided, the fail method will be called with provided {@link AdobeError}. * diff --git a/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeConstants.java b/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeConstants.java index 5fd25283..1fb43762 100644 --- a/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeConstants.java +++ b/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeConstants.java @@ -26,6 +26,7 @@ final class EventSource { static final String REMOVE_IDENTITY = "com.adobe.eventSource.removeIdentity"; static final String REQUEST_RESET = "com.adobe.eventSource.requestReset"; static final String SHARED_STATE = "com.adobe.eventSource.sharedState"; + static final String RESET_COMPLETE = "com.adobe.eventSource.resetComplete"; private EventSource() { } } @@ -42,8 +43,8 @@ final class EventNames { static final String IDENTITY_RESPONSE_CONTENT_ONE_TIME = "Identity Edge Response Content One Time"; static final String UPDATE_IDENTITIES = "Identity Edge Update Identities"; static final String REMOVE_IDENTITIES = "Identity Edge Remove Identities"; - static final String REQUEST_RESET = "Identity Edge Request Reset"; static final String REQUEST_IDENTITIES = "Identity Edge Request Identities"; + static final String RESET_IDENTITIES_COMPLETE = "Edge Identity Reset Identities Complete"; private EventNames() { } } diff --git a/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeExtension.java b/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeExtension.java index 391d4898..7ecf1f59 100644 --- a/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeExtension.java +++ b/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeExtension.java @@ -42,6 +42,8 @@ class IdentityEdgeExtension extends Extension { *
  • Listener {@link ListenerIdentityEdgeRemoveIdentity} to listen for event with eventType {@link IdentityEdgeConstants.EventType#EDGE_IDENTITY} * and EventSource {@link IdentityEdgeConstants.EventSource#REMOVE_IDENTITY}
  • *
  • Listener {@link ListenerIdentityRequestReset} to listen for event with eventType {@link IdentityEdgeConstants.EventType#EDGE_IDENTITY} + * and EventSource {@link IdentityEdgeConstants.EventSource#REQUEST_CONTENT}
  • + *
  • Listener {@link ListenerIdentityRequestReset} to listen for event with eventType {@link IdentityEdgeConstants.EventType#GENERIC_IDENTITY} * and EventSource {@link IdentityEdgeConstants.EventSource#REQUEST_RESET}
  • *
  • Listener {@link ListenerHubSharedState} to listen for event with eventType {@link IdentityEdgeConstants.EventType#HUB} * and EventSource {@link IdentityEdgeConstants.EventSource#SHARED_STATE}
  • @@ -65,8 +67,8 @@ public void error(final ExtensionError extensionError) { extensionApi.registerEventListener(IdentityEdgeConstants.EventType.GENERIC_IDENTITY, IdentityEdgeConstants.EventSource.REQUEST_CONTENT, ListenerGenericIdentityRequestContent.class, listenerErrorCallback); extensionApi.registerEventListener(IdentityEdgeConstants.EventType.EDGE_IDENTITY, IdentityEdgeConstants.EventSource.UPDATE_IDENTITY, ListenerIdentityEdgeUpdateIdentity.class, listenerErrorCallback); extensionApi.registerEventListener(IdentityEdgeConstants.EventType.EDGE_IDENTITY, IdentityEdgeConstants.EventSource.REMOVE_IDENTITY, ListenerIdentityEdgeRemoveIdentity.class, listenerErrorCallback); - extensionApi.registerEventListener(IdentityEdgeConstants.EventType.EDGE_IDENTITY, IdentityEdgeConstants.EventSource.REQUEST_RESET, ListenerIdentityRequestReset.class, listenerErrorCallback); extensionApi.registerEventListener(IdentityEdgeConstants.EventType.HUB, IdentityEdgeConstants.EventSource.SHARED_STATE, ListenerHubSharedState.class, listenerErrorCallback); + extensionApi.registerEventListener(IdentityEdgeConstants.EventType.GENERIC_IDENTITY, IdentityEdgeConstants.EventSource.REQUEST_RESET, ListenerIdentityRequestReset.class, listenerErrorCallback); } /** @@ -201,7 +203,7 @@ void handleRequestReset(final Event event) { state.resetIdentifiers(); final ExtensionApi extensionApi = super.getApi(); - if (extensionApi == null ) { + if (extensionApi == null) { MobileCore.log(LoggingMode.DEBUG, LOG_TAG, "ExtensionApi is null, unable to share XDM shared state for reset identities"); return; } @@ -215,6 +217,21 @@ public void error(final ExtensionError extensionError) { }; extensionApi.setXDMSharedEventState(state.getIdentityEdgeProperties().toXDMData(false), event, errorCallback); + + // dispatch response event + final Event responseEvent = new Event.Builder(IdentityEdgeConstants.EventNames.RESET_IDENTITIES_COMPLETE, + IdentityEdgeConstants.EventType.EDGE_IDENTITY, + IdentityEdgeConstants.EventSource.RESET_COMPLETE).build(); + + MobileCore.dispatchEvent(responseEvent, new ExtensionErrorCallback() { + @Override + public void error(ExtensionError extensionError) { + MobileCore.log(LoggingMode.DEBUG, LOG_TAG, "Failed to dispatch Identity Edge reset response event for event " + + event.getUniqueIdentifier() + + " with error " + + extensionError.getErrorName()); + } + }); } /** diff --git a/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/ListenerIdentityRequestReset.java b/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/ListenerIdentityRequestReset.java index 13c3f4f7..2f839f4e 100644 --- a/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/ListenerIdentityRequestReset.java +++ b/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/ListenerIdentityRequestReset.java @@ -31,7 +31,7 @@ public class ListenerIdentityRequestReset extends ExtensionListener { /** - * Method that gets called when event with event type {@link IdentityEdgeConstants.EventType#EDGE_IDENTITY} + * Method that gets called when event with event type {@link IdentityEdgeConstants.EventType#GENERIC_IDENTITY} * and with event source {@link IdentityEdgeConstants.EventSource#REQUEST_RESET} is dispatched through eventHub. * * @param event the identity reset request {@link Event} to be processed diff --git a/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeExtensionTests.java b/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeExtensionTests.java index ed874d9a..2183c3d1 100644 --- a/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeExtensionTests.java +++ b/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeExtensionTests.java @@ -39,6 +39,7 @@ import static junit.framework.TestCase.assertEquals; import static junit.framework.TestCase.assertNotNull; +import static junit.framework.TestCase.assertNull; import static junit.framework.TestCase.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyString; @@ -103,7 +104,7 @@ public void test_ListenersRegistration() { eq(IdentityEdgeConstants.EventSource.UPDATE_IDENTITY), eq(ListenerIdentityEdgeUpdateIdentity.class), callbackCaptor.capture()); verify(mockExtensionApi, times(1)).registerEventListener(eq(IdentityEdgeConstants.EventType.EDGE_IDENTITY), eq(IdentityEdgeConstants.EventSource.REMOVE_IDENTITY), eq(ListenerIdentityEdgeRemoveIdentity.class), callbackCaptor.capture()); - verify(mockExtensionApi, times(1)).registerEventListener(eq(IdentityEdgeConstants.EventType.EDGE_IDENTITY), + verify(mockExtensionApi, times(1)).registerEventListener(eq(IdentityEdgeConstants.EventType.GENERIC_IDENTITY), eq(IdentityEdgeConstants.EventSource.REQUEST_RESET), eq(ListenerIdentityRequestReset.class), callbackCaptor.capture()); verify(mockExtensionApi, times(1)).registerEventListener(eq(IdentityEdgeConstants.EventType.HUB), eq(IdentityEdgeConstants.EventSource.SHARED_STATE), eq(ListenerHubSharedState.class), callbackCaptor.capture()); @@ -200,7 +201,7 @@ public void test_handleIdentityRequest_noIdentifiers_emptyIdentityMap() { // setup IdentityEdgeProperties emptyProps = new IdentityEdgeProperties(); PowerMockito.stub(PowerMockito.method(IdentityEdgeState.class, "getIdentityEdgeProperties")).toReturn(emptyProps); - + Event event = new Event.Builder("Test event", IdentityEdgeConstants.EventType.EDGE_IDENTITY, IdentityEdgeConstants.EventSource.REQUEST_IDENTITY).build(); final ArgumentCaptor responseEventCaptor = ArgumentCaptor.forClass(Event.class); final ArgumentCaptor requestEventCaptor = ArgumentCaptor.forClass(Event.class); @@ -223,14 +224,18 @@ public void test_handleIdentityRequest_noIdentifiers_emptyIdentityMap() { @Test public void test_handleIdentityResetRequest() { // setup - Event event = new Event.Builder("Test event", IdentityEdgeConstants.EventType.EDGE_IDENTITY, IdentityEdgeConstants.EventSource.REQUEST_RESET).build(); + Event event = new Event.Builder("Test event", IdentityEdgeConstants.EventType.GENERIC_IDENTITY, IdentityEdgeConstants.EventSource.REQUEST_RESET).build(); final ArgumentCaptor sharedStateCaptor = ArgumentCaptor.forClass(Map.class); + final ArgumentCaptor eventCaptor = ArgumentCaptor.forClass(Event.class); // test extension.handleRequestReset(event); // verify verify(mockExtensionApi, times(1)).setXDMSharedEventState(sharedStateCaptor.capture(), eq(event), any(ExtensionErrorCallback.class)); + PowerMockito.verifyStatic(MobileCore.class, Mockito.times(1)); + MobileCore.dispatchEvent(eventCaptor.capture(), any(ExtensionErrorCallback.class)); + Map sharedState = sharedStateCaptor.getValue(); IdentityItem sharedEcid = getItemFromIdentityMap(sharedState, "ECID", 0); assertNotNull(sharedEcid); diff --git a/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeTests.java b/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeTests.java index d76f30e8..979b1f6c 100644 --- a/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeTests.java +++ b/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeTests.java @@ -244,32 +244,6 @@ public void call(Object o) { } assertEquals(AdobeError.UNEXPECTED_ERROR, errorCapture.get(KEY_CAPTUREDERRORCALLBACK)); } - // ======================================================================================== - // resetIdentities API - // ======================================================================================== - @Test - public void testResetIdentities() { - // setup - final ArgumentCaptor eventCaptor = ArgumentCaptor.forClass(Event.class); - final ArgumentCaptor adobeCallbackCaptor = ArgumentCaptor.forClass(AdobeCallback.class); - final ArgumentCaptor extensionErrorCallbackCaptor = ArgumentCaptor.forClass(ExtensionErrorCallback.class); - - - // test - IdentityEdge.resetIdentities(); - - // verify - PowerMockito.verifyStatic(MobileCore.class, Mockito.times(1)); - MobileCore.dispatchEvent(eventCaptor.capture(), extensionErrorCallbackCaptor.capture()); - - // verify the dispatched event details - Event dispatchedEvent = eventCaptor.getValue(); - assertEquals(IdentityEdgeConstants.EventNames.REQUEST_RESET, dispatchedEvent.getName()); - assertEquals(IdentityEdgeConstants.EventType.EDGE_IDENTITY.toLowerCase(), dispatchedEvent.getType()); - assertEquals(IdentityEdgeConstants.EventSource.REQUEST_RESET.toLowerCase(), dispatchedEvent.getSource()); - assertTrue(dispatchedEvent.getEventData().isEmpty()); - } - // ======================================================================================== // updateIdentities API // ======================================================================================== From 37fb2862a8f50a48ee28bf7f8298370d9122f1e1 Mon Sep 17 00:00:00 2001 From: Kevin Lind <40409666+kevinlind@users.noreply.github.com> Date: Thu, 18 Mar 2021 23:48:17 -0700 Subject: [PATCH 013/101] Add unit test to verify secondary Ecid is not set if primary is not set (#15) * Add unit test to verify secondary ECID is not set if primary is not set * Make class variables final --- .../mobile/identityedge/IdentityItem.java | 11 ++++------ .../mobile/identityedge/IdentityMap.java | 6 +----- .../IdentityEdgePropertiesTests.java | 21 ++++++++++++++++++- 3 files changed, 25 insertions(+), 13 deletions(-) diff --git a/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityItem.java b/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityItem.java index 20c46bc7..c9c7372f 100644 --- a/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityItem.java +++ b/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityItem.java @@ -22,9 +22,9 @@ * Represents an identity item */ public final class IdentityItem { - private String id; - private AuthenticationState authenticationState; - private boolean primary; + private final String id; + private final AuthenticationState authenticationState; + private final boolean primary; private static final String LOG_TAG = "IdentityItem"; private static final String JSON_KEY_ID = "id"; @@ -43,10 +43,7 @@ public IdentityItem(final String id, final AuthenticationState authenticationSta throw new IllegalArgumentException("id must be non-null"); } this.id = id; - this.authenticationState = authenticationState; - if (authenticationState == null) { - this.authenticationState = AuthenticationState.AMBIGUOUS; - } + this.authenticationState = authenticationState != null ? authenticationState : AuthenticationState.AMBIGUOUS; this.primary = primary; } diff --git a/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityMap.java b/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityMap.java index 8f621312..e14606db 100644 --- a/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityMap.java +++ b/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityMap.java @@ -31,11 +31,7 @@ public class IdentityMap { private static final String LOG_TAG = "IdentityMap"; - private Map> identityItems; - - IdentityMap() { - identityItems = new HashMap<>(); - } + private final Map> identityItems = new HashMap<>(); /** * Gets the {@link IdentityItem}s for the namespace diff --git a/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/IdentityEdgePropertiesTests.java b/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/IdentityEdgePropertiesTests.java index efb31d4b..ffcc7e7d 100644 --- a/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/IdentityEdgePropertiesTests.java +++ b/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/IdentityEdgePropertiesTests.java @@ -19,6 +19,7 @@ import java.util.Map; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; @@ -51,9 +52,14 @@ public void testIdentityEdgeProperties_toXDMDataFull() { Map ecidSecondaryItem = getItemFromIdentityMap(xdmData, "ECID", 1); assertNotNull(ecidItem); - assertNotNull(ecidSecondaryItem); assertEquals(props.getECID().toString(), (String)ecidItem.get("id")); + assertFalse((boolean)ecidItem.get("primary")); + assertEquals(AuthenticationState.AMBIGUOUS.name(), (String)ecidItem.get("authenticatedState")); + + assertNotNull(ecidSecondaryItem); assertEquals(props.getECIDSecondary().toString(), (String)ecidSecondaryItem.get("id")); + assertFalse((boolean)ecidSecondaryItem.get("primary")); + assertEquals(AuthenticationState.AMBIGUOUS.name(), (String)ecidSecondaryItem.get("authenticatedState")); } @Test @@ -69,6 +75,19 @@ public void testIdentityEdgeProperties_toXDMDataOnlyPrimaryECID() { assertEquals(props.getECID().toString(), ecidFromIdentityMap(xdmMap)); } + @Test + public void testIdentityEdgeProperties_toXDMDataOnlySecondaryECID() { + // setup + IdentityEdgeProperties props = new IdentityEdgeProperties(); + props.setECIDSecondary(new ECID()); + + // test + Map xdmMap = props.toXDMData(false); + + // verify, can't have secondary ECID without primary ECID + assertNull(ecidFromIdentityMap(xdmMap)); + } + @Test public void testIdentityEdgeProperties_toXDMDataMissingECID() { // setup From 486e26a19c5bbc54a718f0442eb86bd8600df11e Mon Sep 17 00:00:00 2001 From: Pravin Prakash Kumar Date: Fri, 19 Mar 2021 12:19:12 -0700 Subject: [PATCH 014/101] [AMSDK-11081] - Part 2 Implementation of Update/Remove Identity API (#14) * [AMSDK-11081] - Update/Remove Identity API implementation * [AMSDK-11081] - Unit test for IdentityMap and RemoveIdentity Public API * [AMSDK-11081] - More Unit test for update/Remove * [AMSDK-11081] - Few more edits to unittests * [AMSDK-11081] - better naming and typo fixes * [AMSDK-11081] - rearrange parameters, setECID handling, case-insensitive search and more * [AMSDK-11081] - Caseinsensitive removal of reserved namespace items + cleanup * [AMSDK-11081] - cleanup and renaming --- .../mobile/identityedge/IdentityEdge.java | 40 +- .../identityedge/IdentityEdgeConstants.java | 4 + .../identityedge/IdentityEdgeExtension.java | 124 +++++-- .../identityedge/IdentityEdgeProperties.java | 126 +++++-- .../identityedge/IdentityEdgeState.java | 21 ++ .../IdentityEdgeStorageService.java | 5 +- .../mobile/identityedge/IdentityItem.java | 19 +- .../mobile/identityedge/IdentityMap.java | 204 +++++++++-- .../identityedge/IdentityEdgeTestUtil.java | 109 +++++- .../IdentityEdgeExtensionTests.java | 234 ++++++++++-- .../IdentityEdgePropertiesTests.java | 244 +++++++++---- .../identityedge/IdentityEdgeStateTests.java | 15 + .../IdentityEdgeStorageServiceTests.java | 1 - .../identityedge/IdentityEdgeTests.java | 90 ++++- .../mobile/identityedge/IdentityMapTests.java | 343 ++++++++++++++++++ 15 files changed, 1365 insertions(+), 214 deletions(-) create mode 100644 code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/IdentityMapTests.java diff --git a/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdge.java b/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdge.java index 359eea6b..ce83ef48 100644 --- a/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdge.java +++ b/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdge.java @@ -109,7 +109,7 @@ public void call(Event responseEvent) { * @param identityMap The identifiers to add or update. */ public static void updateIdentities(final IdentityMap identityMap) { - if (identityMap == null || identityMap.toObjectMap().isEmpty()) { + if (identityMap == null || identityMap.isEmpty()) { MobileCore.log(LoggingMode.DEBUG, LOG_TAG, "Unable to updateIdentities, IdentityMap is null or empty"); return; } @@ -129,8 +129,46 @@ public void error(final ExtensionError extensionError) { MobileCore.dispatchEvent(updateIdentitiesEvent, errorCallback); } + /** + * Removes the identity from the stored client-side {@link IdentityMap} and XDM shared state. The IdentityEdge extension will stop sending this identifier. + * This does not clear the identifier from the User Profile Graph. + * Identifiers which have an empty `id` or empty `namespace` are not allowed and are ignored. + * + * @param item {@link IdentityItem} representing the identity to remove. + * @param namespace The namespace the identity to remove is under. + */ + public static void removeIdentity(final IdentityItem item, final String namespace) { + if (Utils.isNullOrEmpty(namespace)) { + MobileCore.log(LoggingMode.DEBUG, LOG_TAG, "Unable to removeIdentity, namespace is null or empty"); + return; + } + + if (item == null) { + MobileCore.log(LoggingMode.DEBUG, LOG_TAG, "Unable to removeIdentity, IdentityItem is null"); + return; + } + + IdentityMap identityMap = new IdentityMap(); + identityMap.addItem(item, namespace); + + final ExtensionErrorCallback errorCallback = new ExtensionErrorCallback() { + @Override + public void error(final ExtensionError extensionError) { + MobileCore.log(LoggingMode.DEBUG, LOG_TAG, String.format("removeIdentity API. Failed to dispatch %s event: Error : %s.", IdentityEdgeConstants.EventNames.REMOVE_IDENTITIES, + extensionError.getErrorName())); + } + }; + + + final Event removeIdentitiesEvent = new Event.Builder(IdentityEdgeConstants.EventNames.REMOVE_IDENTITIES, + IdentityEdgeConstants.EventType.EDGE_IDENTITY, + IdentityEdgeConstants.EventSource.REMOVE_IDENTITY).setEventData(identityMap.asEventData()).build(); + MobileCore.dispatchEvent(removeIdentitiesEvent, errorCallback); + } + /** * Returns all identifiers, including customer identifiers which were previously added. + * * @param callback {@link AdobeCallback} invoked with the current {@link IdentityMap} * If an {@link AdobeCallbackWithError} is provided, an {@link AdobeError} can be returned in the * eventuality of any error that occurred while getting the stored identities. diff --git a/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeConstants.java b/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeConstants.java index 1fb43762..47010aa1 100644 --- a/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeConstants.java +++ b/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeConstants.java @@ -61,12 +61,16 @@ private SharedStateKeys() { } final class Namespaces { static final String ECID = "ECID"; + static final String IDFA = "IDFA"; + static final String GAID = "GAID"; private Namespaces() { } } final class XDMKeys { static final String IDENTITY_MAP = "identityMap"; static final String ID = "id"; + static final String AUTHENTICATED_STATE = "authenticatedState"; + static final String PRIMARY = "primary"; private XDMKeys() { } } diff --git a/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeExtension.java b/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeExtension.java index 7ecf1f59..9949e660 100644 --- a/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeExtension.java +++ b/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeExtension.java @@ -23,7 +23,7 @@ class IdentityEdgeExtension extends Extension { - private final String LOG_TAG = "IdentityEdgeExtension"; + private static final String LOG_TAG = "IdentityEdgeExtension"; private final IdentityEdgeState state = new IdentityEdgeState(new IdentityEdgeProperties()); /** @@ -73,6 +73,7 @@ public void error(final ExtensionError extensionError) { /** * Required override. Each extension must have a unique name within the application. + * * @return unique name of this extension */ @Override @@ -82,6 +83,7 @@ protected String getName() { /** * Optional override. + * * @return the version of this extension */ @Override @@ -89,14 +91,46 @@ protected String getVersion() { return IdentityEdgeConstants.EXTENSION_VERSION; } - // TODO: Docme + /** + * Handles update identity requests to add/update customer identifiers. + * + * @param event the edge update identity {@link Event} + */ void handleUpdateIdentities(final Event event) { - // TODO + if (!canProcessEvents()) { + MobileCore.log(LoggingMode.DEBUG, LOG_TAG, "Unable to process update identity event. canProcessEvents returned false."); + return; + } + final Map eventData = event.getEventData(); // do not need to null check on eventData, as they are done on listeners + final IdentityMap map = IdentityMap.fromData(eventData); + if (map == null) { + MobileCore.log(LoggingMode.DEBUG, LOG_TAG, "Failed to update identifiers as no identifiers were found in the event data."); + return; + } + + state.updateCustomerIdentifiers(map); + shareIdentityXDMSharedState(event); } - // TODO: Docme + /** + * Handles remove identity requests to remove customer identifiers. + * + * @param event the edge remove identity request {@link Event} + */ void handleRemoveIdentity(final Event event) { - // TODO + if (!canProcessEvents()) { + MobileCore.log(LoggingMode.DEBUG, LOG_TAG, "Unable to process remove identity event. canProcessEvents returned false."); + return; + } + final Map eventData = event.getEventData(); // do not need to null check on eventData, as they are done on listeners + final IdentityMap map = IdentityMap.fromData(eventData); + if (map == null) { + MobileCore.log(LoggingMode.DEBUG, LOG_TAG, "Failed to remove identifiers as no identifiers were found in the event data."); + return; + } + + state.removeCustomerIdentifiers(map); + shareIdentityXDMSharedState(event); } void handleGenericIdentityRequest(final Event event) { @@ -107,20 +141,15 @@ void handleGenericIdentityRequest(final Event event) { * Handles events of type {@code com.adobe.eventType.hub} and source {@code com.adobe.eventSource.sharedState}. * If the state change event is for the direct Identity extension, get the direct Identity shared state and attempt * to update the legacy ECID with the direct Identity extension ECID. + * * @param event an event of type {@code com.adobe.eventType.hub} and source {@code com.adobe.eventSource.sharedState} */ void handleHubSharedState(final Event event) { - if (!canProcessEvents(event)) { + if (!canProcessEvents()) { MobileCore.log(LoggingMode.DEBUG, LOG_TAG, "Unable to process direct Identity shared state change event. canProcessEvents returned false."); return; } - final ExtensionApi extensionApi = getApi(); - if (extensionApi == null ) { - MobileCore.log(LoggingMode.DEBUG, LOG_TAG, "ExtensionApi is null, unable to process direct Identity shared state change event."); - return; - } - if (event == null || event.getEventData() == null) { return; } @@ -135,16 +164,11 @@ void handleHubSharedState(final Event event) { return; } - final ExtensionErrorCallback getSharedStateCallback = new ExtensionErrorCallback() { - @Override - public void error(final ExtensionError extensionError) { - MobileCore.log(LoggingMode.DEBUG, LOG_TAG, String.format("Failed getting direct Identity shared state. Error : %s.", extensionError.getErrorName())); - } - }; - final Map identityState = extensionApi.getSharedEventState(IdentityEdgeConstants.SharedStateKeys.IDENTITY_DIRECT, event, getSharedStateCallback); + final Map identityState = getSharedState(IdentityEdgeConstants.SharedStateKeys.IDENTITY_DIRECT, event); if (identityState == null) { + MobileCore.log(LoggingMode.DEBUG, LOG_TAG, "Could not process direct Identity shared state change event, Identity shared state is null"); return; } @@ -153,13 +177,7 @@ public void error(final ExtensionError extensionError) { final ECID legacyEcid = legacyEcidString == null ? null : new ECID(legacyEcidString); if (state.updateLegacyExperienceCloudId(legacyEcid)) { - final ExtensionErrorCallback errorCallback = new ExtensionErrorCallback() { - @Override - public void error(final ExtensionError extensionError) { - MobileCore.log(LoggingMode.DEBUG, LOG_TAG, String.format("Failed to create XDM shared state. Error : %s.", extensionError.getErrorName())); - } - }; - extensionApi.setXDMSharedEventState(state.getIdentityEdgeProperties().toXDMData(false), event, errorCallback); + shareIdentityXDMSharedState(event); } } catch (ClassCastException e) { MobileCore.log(LoggingMode.DEBUG, LOG_TAG, "Could not process direct Identity shared state change event, failed to parse stored ECID as String: " + e.getLocalizedMessage()); @@ -168,10 +186,14 @@ public void error(final ExtensionError extensionError) { /** * Handles events requesting for identifiers. Dispatches response event containing the identifiers. Called by listener registered with event hub. - * @param event the identity request event + * + * @param event the identity request {@link Event} */ void handleIdentityRequest(final Event event) { - if (!canProcessEvents(event)) { return; } + if (!canProcessEvents()) { + MobileCore.log(LoggingMode.DEBUG, LOG_TAG, "Unable to process request reset event. canProcessEvents returned false."); + return; + } Map xdmData = state.getIdentityEdgeProperties().toXDMData(true); Event responseEvent = new Event.Builder(IdentityEdgeConstants.EventNames.IDENTITY_RESPONSE_CONTENT_ONE_TIME, @@ -193,15 +215,45 @@ public void error(ExtensionError extensionError) { /** * Handles IdentityEdge request reset events. - * @param event the identity request reset event + * + * @param event the identity request reset {@link Event} */ void handleRequestReset(final Event event) { - if (!canProcessEvents(event)) { + if (!canProcessEvents()) { MobileCore.log(LoggingMode.DEBUG, LOG_TAG, "Unable to process request reset event. canProcessEvents returned false."); return; } state.resetIdentifiers(); + shareIdentityXDMSharedState(event); + } + /** + * Retrieves the shared state for the given state owner + * + * @param stateOwner the state owner for the requested shared state + * @param event the {@link Event} for which is shared state is to be retrieved + */ + private Map getSharedState(final String stateOwner, final Event event) { + final ExtensionApi extensionApi = getApi(); + if (extensionApi == null) { + return null; + } + final ExtensionErrorCallback getSharedStateCallback = new ExtensionErrorCallback() { + @Override + public void error(final ExtensionError extensionError) { + MobileCore.log(LoggingMode.DEBUG, LOG_TAG, String.format("Failed getting direct Identity shared state. Error : %s.", extensionError.getErrorName())); + } + }; + + return extensionApi.getSharedEventState(stateOwner, event, getSharedStateCallback); + } + + /** + * Fetches the latest Identity Edge properties and shares the EdgeIdentity's XDMSharedState. + * + * @param event the {@link Event} that triggered the XDM shared state change + */ + private void shareIdentityXDMSharedState(final Event event) { final ExtensionApi extensionApi = super.getApi(); if (extensionApi == null) { MobileCore.log(LoggingMode.DEBUG, LOG_TAG, "ExtensionApi is null, unable to share XDM shared state for reset identities"); @@ -209,7 +261,7 @@ void handleRequestReset(final Event event) { } // set the shared state - ExtensionErrorCallback errorCallback = new ExtensionErrorCallback() { + final ExtensionErrorCallback errorCallback = new ExtensionErrorCallback() { @Override public void error(final ExtensionError extensionError) { MobileCore.log(LoggingMode.DEBUG, LOG_TAG, String.format("Failed create XDM shared state. Error : %s.", extensionError.getErrorName())); @@ -236,14 +288,16 @@ public void error(ExtensionError extensionError) { /** * Determines if Identity Edge is ready to handle events, this is determined by if the Identity Edge extension has booted up - * @param event An {@link Event} + * * @return True if we can process events, false otherwise */ - private boolean canProcessEvents(final Event event) { - if (state.hasBooted()) { return true; } // we have booted, return true + private boolean canProcessEvents() { + if (state.hasBooted()) { + return true; + } // we have booted, return true final ExtensionApi extensionApi = super.getApi(); - if (extensionApi == null ) { + if (extensionApi == null) { MobileCore.log(LoggingMode.DEBUG, LOG_TAG, "ExtensionApi is null, unable to process events"); return false; } diff --git a/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeProperties.java b/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeProperties.java index 30bfddf0..49163d75 100644 --- a/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeProperties.java +++ b/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeProperties.java @@ -14,6 +14,7 @@ import com.adobe.marketing.mobile.LoggingMode; import com.adobe.marketing.mobile.MobileCore; +import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -24,6 +25,11 @@ class IdentityEdgeProperties { private static final String LOG_TAG = "IdentityEdgeProperties"; + private static final List reservedNamespaces = new ArrayList() {{ + add(IdentityEdgeConstants.Namespaces.ECID); + add(IdentityEdgeConstants.Namespaces.GAID); + add(IdentityEdgeConstants.Namespaces.IDFA); + }}; // The current Experience Cloud ID private ECID ecid; @@ -31,10 +37,13 @@ class IdentityEdgeProperties { // A secondary (non-primary) Experience Cloud ID private ECID ecidSecondary; + private IdentityMap identityMap = new IdentityMap(); + IdentityEdgeProperties() { } /** * Creates a identity edge properties instance based on the map + * * @param xdmData a map representing an identity edge properties instance */ IdentityEdgeProperties(final Map xdmData) { @@ -42,7 +51,7 @@ class IdentityEdgeProperties { return; } - final IdentityMap identityMap = IdentityMap.fromData(xdmData); + identityMap = IdentityMap.fromData(xdmData); if (identityMap != null) { final List ecidItems = identityMap.getIdentityItemsForNamespace(IdentityEdgeConstants.Namespaces.ECID); if (ecidItems != null) { @@ -58,15 +67,33 @@ class IdentityEdgeProperties { /** * Sets the current {@link ECID} - * @param ecid the new {@link ECID} + * + * @param newEcid the new {@code ECID} */ - void setECID(final ECID ecid) { - this.ecid = ecid; + void setECID(final ECID newEcid) { + // delete the previous ECID from the identity map if exist + if (ecid != null) { + final IdentityItem previousECIDItem = new IdentityItem(ecid.toString()); + identityMap.removeItem(previousECIDItem, IdentityEdgeConstants.Namespaces.ECID); + } + + // if primary ecid is null, clear off all the existing ECID's + if (newEcid == null) { + setECIDSecondary(null); + identityMap.clearItemsForNamespace(IdentityEdgeConstants.Namespaces.ECID); + } else { + // And add the new primary Ecid as a first element of Identity map + final IdentityItem newECIDItem = new IdentityItem(newEcid.toString(), AuthenticationState.AMBIGUOUS, false); + identityMap.addItem(newECIDItem, IdentityEdgeConstants.Namespaces.ECID, true); + } + + this.ecid = newEcid; // keep the local variable up to date } /** * Retrieves the current {@link ECID} - * @return current {@link ECID} + * + * @return current {@code ECID} */ ECID getECID() { return ecid; @@ -74,39 +101,82 @@ ECID getECID() { /** * Sets a secondary {@link ECID} - * @param ecid a new secondary {@code ECID} + * + * @param newSecondaryEcid a new secondary {@code ECID} */ - void setECIDSecondary(final ECID ecid) { - this.ecidSecondary = ecid; + void setECIDSecondary(final ECID newSecondaryEcid) { + // delete the previous secondary ECID from the identity map if exist + if (ecidSecondary != null) { + final IdentityItem previousECIDItem = new IdentityItem(ecidSecondary.toString()); + identityMap.removeItem(previousECIDItem, IdentityEdgeConstants.Namespaces.ECID); + } + + // do not set secondary ECID if primary ECID is not set + if (ecid == null) { + MobileCore.log(LoggingMode.DEBUG, LOG_TAG, "Cannot set secondary ECID value as no primary ECID exists."); + this.ecidSecondary = null; + return; + } + + // add the new secondary ECID to Identity map + if (newSecondaryEcid != null) { + final IdentityItem newSecondaryECIDItem = new IdentityItem(newSecondaryEcid.toString(), AuthenticationState.AMBIGUOUS, false); + identityMap.addItem(newSecondaryECIDItem, IdentityEdgeConstants.Namespaces.ECID); + } + + this.ecidSecondary = newSecondaryEcid; // keep the local variable up to date } /** * Retrieves the secondary {@link ECID}. + * * @return secondary {@code ECID} */ ECID getECIDSecondary() { return ecidSecondary; } + /** + * Update the customer identifiers by merging the passed in {@link IdentityMap} with the current identifiers. + *

    + * Any identifier in the passed in {@code IdentityMap} which has the same id in the same namespace will update the current identifier. + * Any new identifier in the passed in {@code IdentityMap} will be added to the current identifiers + * Certain namespaces are not allowed to be modified and if exist in the given customer identifiers will be removed before the update operation is executed. + * The namespaces which cannot be modified through this function call include: + * - ECID + * - IDFA + * - GAID + * + * @param map the {@code IdentityMap} containing customer identifiers to add or update with the current customer identifiers + */ + void updateCustomerIdentifiers(final IdentityMap map) { + removeIdentitiesWithReservedNamespaces(map); + identityMap.merge(map); + } + + /** + * Remove customer identifiers specified in passed in {@link IdentityMap} from the current identifiers. + *

    + * Identifiers with following namespaces are prohibited from removing using the API + * - ECID + * - IDFA + * - GAID + * + * @param map the {@code IdentityMap} with items to remove from current identifiers + */ + void removeCustomerIdentifiers(final IdentityMap map) { + removeIdentitiesWithReservedNamespaces(map); + identityMap.remove(map); + } + /** * Converts this into an event data representation in XDM format - * @param allowEmpty If this {@link IdentityEdgeProperties} contains no data, return a dictionary with a single {@link IdentityMap} key + * + * @param allowEmpty If this {@link IdentityEdgeProperties} contains no data, return a dictionary with a single {@link IdentityMap} key * @return A dictionary representing this in XDM format */ Map toXDMData(final boolean allowEmpty) { final Map map = new HashMap<>(); - final IdentityMap identityMap = new IdentityMap(); - - if (ecid != null) { - final IdentityItem ecidItem = new IdentityItem(ecid.toString()); - identityMap.addItem(IdentityEdgeConstants.Namespaces.ECID, ecidItem); - - // set second ECID only if primary exists - if (ecidSecondary != null) { - final IdentityItem ecidSecondaryItem = new IdentityItem(ecidSecondary.toString()); - identityMap.addItem(IdentityEdgeConstants.Namespaces.ECID, ecidSecondaryItem); - } - } final Map>> dict = identityMap.toObjectMap(); if (dict != null && (!dict.isEmpty() || allowEmpty)) { @@ -116,4 +186,18 @@ Map toXDMData(final boolean allowEmpty) { return map; } + /** + * Filter out any items contained in reserved namespaces from the given {@link IdentityMap}. + * The list of reserved namespaces can be found at {@link #reservedNamespaces}. + * + * @param identityMap the {@code IdentityMap} to filter out items contained in reserved namespaces. + */ + private void removeIdentitiesWithReservedNamespaces(final IdentityMap identityMap) { + for (final String reservedNamespace : reservedNamespaces) { + if (identityMap.clearItemsForNamespace(reservedNamespace)) { + MobileCore.log(LoggingMode.DEBUG, LOG_TAG, String.format("Updating/Removing identifiers in namespace %s is not allowed.", reservedNamespace)); + } + } + } + } diff --git a/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeState.java b/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeState.java index 06a117a0..3e5678ec 100644 --- a/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeState.java +++ b/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeState.java @@ -90,6 +90,27 @@ void resetIdentifiers() { // TODO: AMSDK-11208 Use return value to tell IdentityEdge to dispatch consent ad id update } + + /** + * Update the customer identifiers by merging the passed in {@link IdentityMap} with the current identifiers present in {@link #identityProperties}. + * + * @param map the {@code IdentityMap} containing customer identifiers to add or update with the current customer identifiers + */ + void updateCustomerIdentifiers(final IdentityMap map) { + identityProperties.updateCustomerIdentifiers(map); + IdentityEdgeStorageService.savePropertiesToPersistence(identityProperties); + } + + /** + * Remove customer identifiers specified in passed in {@link IdentityMap} from the current identifiers present in {@link #identityProperties}. + * + * @param map the {@code IdentityMap} with items to remove from current identifiers + */ + void removeCustomerIdentifiers(final IdentityMap map) { + identityProperties.removeCustomerIdentifiers(map); + IdentityEdgeStorageService.savePropertiesToPersistence(identityProperties); + } + /** * Update the legacy ECID property with {@code legacyEcid} provided it does not equal the primary or secondary ECIDs * currently in {@code IdentityEdgePoperties}. diff --git a/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeStorageService.java b/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeStorageService.java index 0f34ee94..1e1145cd 100644 --- a/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeStorageService.java +++ b/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeStorageService.java @@ -31,6 +31,7 @@ class IdentityEdgeStorageService { /** * Loads identity edge properties from local storage, returns null if not found. + * * @return properties stored in local storage if present, otherwise null. */ static IdentityEdgeProperties loadPropertiesFromPersistence() { @@ -50,8 +51,7 @@ static IdentityEdgeProperties loadPropertiesFromPersistence() { try { final JSONObject jsonObject = new JSONObject(jsonString); final Map propertyMap = Utils.toMap(jsonObject); - final IdentityEdgeProperties loadedProperties = new IdentityEdgeProperties(propertyMap); - return loadedProperties; + return new IdentityEdgeProperties(propertyMap); } catch (JSONException exception) { MobileCore.log(LoggingMode.DEBUG, LOG_TAG, "Serialization error while reading properties jsonString from persistence. Unable to load saved identity properties from persistence."); return null; @@ -60,6 +60,7 @@ static IdentityEdgeProperties loadPropertiesFromPersistence() { /** * Saves the properties to local storage + * * @param properties properties to be stored */ static void savePropertiesToPersistence(final IdentityEdgeProperties properties) { diff --git a/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityItem.java b/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityItem.java index c9c7372f..df83070d 100644 --- a/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityItem.java +++ b/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityItem.java @@ -27,9 +27,6 @@ public final class IdentityItem { private final boolean primary; private static final String LOG_TAG = "IdentityItem"; - private static final String JSON_KEY_ID = "id"; - private static final String JSON_KEY_AUTHENTICATION_STATE = "authenticatedState"; - private static final String JSON_KEY_PRIMARY = "primary"; /** * Creates a new {@link IdentityItem} @@ -72,16 +69,16 @@ public IdentityItem(final IdentityItem item) { Map toObjectMap() { Map map = new HashMap<>(); if (id != null) { - map.put(JSON_KEY_ID, id); + map.put(IdentityEdgeConstants.XDMKeys.ID, id); } if (authenticationState != null) { - map.put(JSON_KEY_AUTHENTICATION_STATE, authenticationState.toString()); + map.put(IdentityEdgeConstants.XDMKeys.AUTHENTICATED_STATE, authenticationState.toString()); } else { - map.put(JSON_KEY_AUTHENTICATION_STATE, AuthenticationState.AMBIGUOUS.toString()); + map.put(IdentityEdgeConstants.XDMKeys.AUTHENTICATED_STATE, AuthenticationState.AMBIGUOUS.toString()); } - map.put(JSON_KEY_PRIMARY, primary); + map.put(IdentityEdgeConstants.XDMKeys.PRIMARY, primary); return map; } @@ -115,15 +112,15 @@ static IdentityItem fromData(final Map data) { if (data == null) { return null; } try { - final String id = (String) data.get(JSON_KEY_ID); - AuthenticationState authenticationState = AuthenticationState.fromString((String) data.get(JSON_KEY_AUTHENTICATION_STATE)); + final String id = (String) data.get(IdentityEdgeConstants.XDMKeys.ID); + AuthenticationState authenticationState = AuthenticationState.fromString((String) data.get(IdentityEdgeConstants.XDMKeys.AUTHENTICATED_STATE)); if (authenticationState == null) { authenticationState = AuthenticationState.AMBIGUOUS; } boolean primary = false; - if (data.get(JSON_KEY_PRIMARY) != null) { - primary = (boolean) data.get(JSON_KEY_PRIMARY); + if (data.get(IdentityEdgeConstants.XDMKeys.PRIMARY) != null) { + primary = (boolean) data.get(IdentityEdgeConstants.XDMKeys.PRIMARY); } return new IdentityItem(id, authenticationState, primary); diff --git a/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityMap.java b/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityMap.java index e14606db..597b9569 100644 --- a/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityMap.java +++ b/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityMap.java @@ -30,69 +30,115 @@ @SuppressWarnings("unused") public class IdentityMap { private static final String LOG_TAG = "IdentityMap"; - private final Map> identityItems = new HashMap<>(); /** * Gets the {@link IdentityItem}s for the namespace + * returns an empty list if no {@link IdentityItem}s were found for the namespace * * @param namespace namespace for the id - * @return IdentityItem for the namespace, null if not found + * @return IdentityItem for the namespace, */ - public List getIdentityItemsForNamespace(final String namespace) { - final List items = new ArrayList<>(); - for (IdentityItem item : identityItems.get(namespace)) { - items.add(new IdentityItem((item))); + public List getIdentityItemsForNamespace(final String namespace) { + final List copyItems = new ArrayList<>(); + if(Utils.isNullOrEmpty(namespace)) { + return copyItems; + } + + final List items = identityItems.get(namespace); + if (items == null) { + return copyItems; } - return identityItems.get(namespace); + for (IdentityItem item : items) { + copyItems.add(new IdentityItem(item)); + } + + return copyItems; } + /** * Add an identity item which is used to clearly distinguish entities that are interacting * with digital experiences. * + * @param item {@link IdentityItem} to be added to the namespace * @param namespace the namespace integration code or namespace ID of the identity - * @param item {@link IdentityItem} to be added to the namespace - * * @see IdentityItem Schema */ - public void addItem(final String namespace, final IdentityItem item) { + public void addItem(final IdentityItem item, final String namespace) { + addItem(item, namespace, false); + } + + /** + * Remove a single {@link IdentityItem} from this map. + * + * @param item {@link IdentityItem} to be added to the namespace + * @param namespace The {@code IdentityItem} to remove from the given namespace + */ + public void removeItem(final IdentityItem item, final String namespace) { if (item == null) { - MobileCore.log(LoggingMode.DEBUG, LOG_TAG, "IdentityMap add item ignored as must contain a non-null IdentityItem."); + MobileCore.log(LoggingMode.DEBUG, LOG_TAG, "IdentityMap remove item ignored as must contain a non-null IdentityItem."); return; } if (Utils.isNullOrEmpty(namespace)) { MobileCore.log(LoggingMode.DEBUG, LOG_TAG, - "IdentityMap add item ignored as must contain a non-null/non-empty namespace."); + "IdentityMap remove item ignored as must contain a non-null/non-empty namespace."); return; } - addItemToMap(namespace, item); + removeItemFromMap(item, namespace); } - private void addItemToMap(final String namespace, final IdentityItem item) { - // check if namespace exists - final List itemList; + /** + * Determines if this {@link IdentityMap} has no identities. + * + * @return {@code true} if this {@code IdentityMap} contains no identifiers + */ + public boolean isEmpty() { + return identityItems.isEmpty(); + } - if (identityItems.containsKey(namespace)) { - itemList = identityItems.get(namespace); - } else { - itemList = new ArrayList<>(); + // ======================================================================================== + // protected methods + // ======================================================================================== + + /** + * Add an identity item which is used to clearly distinguish entities that are interacting + * with digital experiences. + * + * @param item {@link IdentityItem} to be added to the namespace + * @param namespace the namespace integration code or namespace ID of the identity + * @param isFirstItem on {@code true} keeps the provided {@code IdentityItem} as the first element of the identity list for this namespace + * @see IdentityItem Schema + */ + void addItem(final IdentityItem item, final String namespace, final boolean isFirstItem) { + if (item == null) { + MobileCore.log(LoggingMode.DEBUG, LOG_TAG, "IdentityMap add item ignored as must contain a non-null IdentityItem."); + return; + } + + if (Utils.isNullOrEmpty(namespace)) { + MobileCore.log(LoggingMode.DEBUG, LOG_TAG, + "IdentityMap add item ignored as must contain a non-null/non-empty namespace."); + return; } - itemList.add(item); - this.identityItems.put(namespace, itemList); + addItemToMap(item, namespace, isFirstItem); } + + /** + * @return a {@link Map} representing this {@link IdentityMap} object + */ Map>> toObjectMap() { - final Map>> map = new HashMap>>(); + final Map>> map = new HashMap<>(); for (String namespace : identityItems.keySet()) { final List> namespaceIds = new ArrayList<>(); - for(IdentityItem identityItem: identityItems.get(namespace)) { + for (IdentityItem identityItem : identityItems.get(namespace)) { namespaceIds.add(identityItem.toObjectMap()); } @@ -102,6 +148,68 @@ Map>> toObjectMap() { return map; } + /** + * Merge the given map on to this {@link IdentityMap}. Any {@link IdentityItem} in map which shares the same + * namespace and id as an item in this {@code IdentityMap} will replace that {@code IdentityItem}. + * + * @param map {@link IdentityMap} to be merged into this object + */ + void merge(final IdentityMap map) { + if (map == null) { + return; + } + + for (final String namespace : map.identityItems.keySet()) { + for (IdentityItem identityItem : map.identityItems.get(namespace)) { + addItem(identityItem, namespace); + } + } + } + + /** + * Remove identities present in passed in map from this {@link IdentityMap}. + * Identities are removed which match the same namespace and id. + * + * @param map Identities to remove from this {@code IdentityMap} + */ + void remove(final IdentityMap map) { + if (map == null) { + return; + } + + for (final String namespace : map.identityItems.keySet()) { + for (IdentityItem identityItem : map.identityItems.get(namespace)) { + removeItem(identityItem, namespace); + } + } + } + + /** + * Removes all the {@link IdentityItem} on this {@link IdentityMap} linked to the specified namespace (case insensitive) + * + * @return a {@code boolean} representing a successful removal of all {@code IdentityItem} in a provided namespace + */ + boolean clearItemsForNamespace(final String namespace) { + if(namespace == null){ + return false; + } + + boolean isRemoved = false; + final List filteredNamespaces = new ArrayList<>(); + for(final String eachNamespace : identityItems.keySet()) { + if (namespace.equalsIgnoreCase(eachNamespace)) { + isRemoved = true; + filteredNamespaces.add(eachNamespace); + } + } + + for (final String eachNamespace : filteredNamespaces) { + identityItems.remove(eachNamespace); + } + + return isRemoved; + } + /** * Use this method to cast the {@code IdentityMap} as eventData for an SDK Event. * @@ -111,6 +219,12 @@ Map asEventData() { return new HashMap(identityItems); } + + /** + * Creates an {@link IdentityMap} from the + * + * @return {@link Map} representation of IdentityMap + */ static IdentityMap fromData(Map data) { if (data == null) { return null; @@ -122,13 +236,13 @@ static IdentityMap fromData(Map data) { final IdentityMap identityMap = new IdentityMap(); - for (String namespace : identityMapDict.keySet()) { + for (final String namespace : identityMapDict.keySet()) { try { final ArrayList> idArr = (ArrayList>) identityMapDict.get(namespace); - for (Object idMap: idArr) { + for (Object idMap : idArr) { final IdentityItem item = IdentityItem.fromData((Map) idMap); if (item != null) { - identityMap.addItemToMap(namespace, item); + identityMap.addItemToMap(item, namespace, false); } } } catch (ClassCastException e) { @@ -138,4 +252,40 @@ static IdentityMap fromData(Map data) { return identityMap; } + + // ======================================================================================== + // private methods + // ======================================================================================== + private void addItemToMap(final IdentityItem item, final String namespace, final boolean isFirstItem) { + // check if namespace exists + final List itemList; + + if (identityItems.containsKey(namespace)) { + itemList = identityItems.get(namespace); + } else { + itemList = new ArrayList<>(); + } + + if (isFirstItem) { + itemList.add(0, item); + } else { + itemList.add(item); + } + + identityItems.put(namespace, itemList); + } + + private void removeItemFromMap(final IdentityItem item, final String namespace) { + // check if namespace exists + if (!identityItems.containsKey(namespace)) { + return; + } + + final List itemList = identityItems.get(namespace); + itemList.remove(item); + + if(itemList.isEmpty()) { + identityItems.remove(namespace); + } + } } diff --git a/code/identityedge/src/sharedTestUtils/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeTestUtil.java b/code/identityedge/src/sharedTestUtils/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeTestUtil.java index 1a0db1f4..60fa3a8c 100644 --- a/code/identityedge/src/sharedTestUtils/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeTestUtil.java +++ b/code/identityedge/src/sharedTestUtils/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeTestUtil.java @@ -11,6 +11,7 @@ package com.adobe.marketing.mobile.identityedge; +import com.adobe.marketing.mobile.Event; import com.adobe.marketing.mobile.LoggingMode; import com.adobe.marketing.mobile.MobileCore; import com.fasterxml.jackson.databind.JsonNode; @@ -19,19 +20,97 @@ import com.fasterxml.jackson.databind.node.ObjectNode; import com.fasterxml.jackson.databind.node.ValueNode; +import org.json.JSONException; import org.json.JSONObject; import java.io.IOException; +import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.Iterator; +import java.util.List; import java.util.Map; + +/** + * Util class used by both Functional and Unit tests + */ class IdentityEdgeTestUtil { + + /** + * Helper method to create IdentityXDM Map using {@link TestItem}s + */ + static Map createXDMIdentityMap(TestItem... items) { + final Map>> allItems = new HashMap<>(); + for (TestItem item : items) { + final Map itemMap = new HashMap<>(); + itemMap.put(IdentityEdgeConstants.XDMKeys.ID, item.id); + itemMap.put(IdentityEdgeConstants.XDMKeys.AUTHENTICATED_STATE, "ambiguous"); + itemMap.put(IdentityEdgeConstants.XDMKeys.PRIMARY, item.isPrimary); + List> nameSpaceItems = allItems.get(item.namespace); + if (nameSpaceItems == null) { + nameSpaceItems = new ArrayList<>(); + } + nameSpaceItems.add(itemMap); + allItems.put(item.namespace, nameSpaceItems); + } + + final Map identityMapDict = new HashMap<>(); + identityMapDict.put(IdentityEdgeConstants.XDMKeys.IDENTITY_MAP, allItems); + return identityMapDict; + } + + /** + * Helper method to build remove identity request event with XDM formatted Identity jsonString + */ + static Event buildRemoveIdentityRequestWithJSONString(final String jsonStr) throws Exception { + final JSONObject jsonObject = new JSONObject(jsonStr); + final Map xdmData = Utils.toMap(jsonObject); + return buildRemoveIdentityRequest(xdmData); + } + + /** + * Helper method to build remove identity request event with XDM formatted Identity map + */ + static Event buildRemoveIdentityRequest(final Map map) { + return new Event.Builder("Remove Identity Event", IdentityEdgeConstants.EventType.EDGE_IDENTITY, IdentityEdgeConstants.EventSource.REMOVE_IDENTITY).setEventData(map).build(); + } + + /** + * Helper method to build update identity request event with XDM formatted Identity jsonString + */ + static Event buildUpdateIdentityRequestJSONString(final String jsonStr) throws Exception { + final JSONObject jsonObject = new JSONObject(jsonStr); + final Map xdmData = Utils.toMap(jsonObject); + return buildUpdateIdentityRequest(xdmData); + } + + /** + * Helper method to build update identity request event with XDM formatted Identity map + */ + static Event buildUpdateIdentityRequest(final Map map) { + return new Event.Builder("Update Identity Event", IdentityEdgeConstants.EventType.EDGE_IDENTITY, IdentityEdgeConstants.EventSource.UPDATE_IDENTITY).setEventData(map).build(); + } + + + /** + * Serialize the given {@code jsonString} to a JSON Object, then flattens to {@code Map}. + * If the provided string is not in JSON structure an {@link JSONException} is thrown. + * + * @param jsonString the string in JSON structure to flatten + * @return new map with flattened structure + */ + static Map flattenJSONString(final String jsonString) throws JSONException { + JSONObject jsonObject = new JSONObject(jsonString); + Map persistenceValueMap = Utils.toMap(jsonObject); + return flattenMap(persistenceValueMap); + } + /** * Serialize the given {@code map} to a JSON Object, then flattens to {@code Map}. * For example, a JSON such as "{xdm: {stitchId: myID, eventType: myType}}" is flattened * to two map elements "xdm.stitchId" = "myID" and "xdm.eventType" = "myType". + * * @param map map with JSON structure to flatten * @return new map with flattened structure */ @@ -57,14 +136,13 @@ static Map flattenMap(final Map map) { * Deserialize {@code JsonNode} and flatten to provided {@code map}. * For example, a JSON such as "{xdm: {stitchId: myID, eventType: myType}}" is flattened * to two map elements "xdm.stitchId" = "myID" and "xdm.eventType" = "myType". - * + *

    * Method is called recursively. To use, call with an empty path such as * {@code addKeys("", new ObjectMapper().readTree(JsonNodeAsString), map);} * * @param currentPath the path in {@code JsonNode} to process - * @param jsonNode {@link JsonNode} to deserialize - * @param map {@code Map} instance to store flattened JSON result - * + * @param jsonNode {@link JsonNode} to deserialize + * @param map {@code Map} instance to store flattened JSON result * @see Stack Overflow post */ private static void addKeys(String currentPath, JsonNode jsonNode, Map map) { @@ -89,4 +167,27 @@ private static void addKeys(String currentPath, JsonNode jsonNode, Map sharedState = sharedStateCaptor.getValue(); - IdentityItem sharedEcid = getItemFromIdentityMap(sharedState, "ECID", 0); - assertNotNull(sharedEcid); - assertTrue(sharedEcid.getId().length() > 0); + Map sharedState = flattenMap(sharedStateCaptor.getValue()); + assertTrue(sharedState.get("identityMap.ECID[0].id").length() > 0); } @Test @@ -263,9 +262,7 @@ public void test_handleHubSharedState_updateLegacyEcidOnDirectIdentityStateChang final ArgumentCaptor> sharedStateCaptor = ArgumentCaptor.forClass(Map.class); verify(mockExtensionApi, times(1)).setXDMSharedEventState(sharedStateCaptor.capture(), eq(event), any(ExtensionErrorCallback.class)); Map sharedState = sharedStateCaptor.getValue(); - IdentityItem legacyEcidItem = getItemFromIdentityMap(sharedState, "ECID", 1); - assertNotNull(legacyEcidItem); - assertEquals("1234", legacyEcidItem.getId()); + assertEquals("1234", flattenMap(sharedState).get("identityMap.ECID[1].id")); // Legacy ECID is set as a secondary ECID } @Test @@ -366,6 +363,208 @@ public void test_handleHubSharedState_doesNotShareStateIfLegacyECIDDoesNotChange verify(mockExtensionApi, times(0)).setXDMSharedEventState(sharedStateCaptor.capture(), eq(event), any(ExtensionErrorCallback.class)); } + // ======================================================================================== + // handleIdentityRequest + // ======================================================================================== + @Test + public void test_handleUpdateIdentities() throws Exception { + // setup + Map identityXDM = createXDMIdentityMap( + new TestItem("UserId", "secretID") + ); + final ArgumentCaptor sharedStateCaptor = ArgumentCaptor.forClass(Map.class); + final ArgumentCaptor persistenceValueCaptor = ArgumentCaptor.forClass(String.class); + + + // test + Event updateIdentityEvent = buildUpdateIdentityRequest(identityXDM); + extension.handleUpdateIdentities(updateIdentityEvent); + + // verify shared state + verify(mockExtensionApi, times(1)).setXDMSharedEventState(sharedStateCaptor.capture(), eq(updateIdentityEvent), any(ExtensionErrorCallback.class)); + Map sharedState = flattenMap(sharedStateCaptor.getValue()); + assertEquals("secretID", sharedState.get("identityMap.UserId[0].id")); + assertEquals("AMBIGUOUS", sharedState.get("identityMap.UserId[0].authenticatedState")); + assertEquals("false", sharedState.get("identityMap.UserId[0].primary")); + + // verify persistence + verify(mockSharedPreferenceEditor, times(2)).putString(eq(IdentityEdgeConstants.DataStoreKey.IDENTITY_PROPERTIES), persistenceValueCaptor.capture()); + Map persistedData = flattenJSONString(persistenceValueCaptor.getAllValues().get(1)); + assertEquals("secretID", persistedData.get("identityMap.UserId[0].id")); + assertEquals("AMBIGUOUS", persistedData.get("identityMap.UserId[0].authenticatedState")); + assertEquals("false", persistedData.get("identityMap.UserId[0].primary")); + } + + + @Test + public void test_handleUpdateIdentities_DoNotUpdateReservedNamespace() throws Exception { + // setup + Map identityXDM = createXDMIdentityMap( + new TestItem("ECID", "somevalue"), + new TestItem("GAID", "somevalue"), + new TestItem("IDFA", "somevalue"), + new TestItem("IdFA", "somevalue"), + new TestItem("gaid", "somevalue"), + new TestItem("UserId", "somevalue") + ); + + final ArgumentCaptor sharedStateCaptor = ArgumentCaptor.forClass(Map.class); + final ArgumentCaptor persistenceValueCaptor = ArgumentCaptor.forClass(String.class); + + // test + Event updateIdentityEvent = buildUpdateIdentityRequest(identityXDM); + extension.handleUpdateIdentities(updateIdentityEvent); + + // verify shared state + verify(mockExtensionApi, times(1)).setXDMSharedEventState(sharedStateCaptor.capture(), eq(updateIdentityEvent), any(ExtensionErrorCallback.class)); + Map sharedState = flattenMap(sharedStateCaptor.getValue()); + assertEquals(6, sharedState.size()); // 6 represents id, primary and authState of USERID identifier and generated ECID + assertEquals("somevalue", sharedState.get("identityMap.UserId[0].id")); + assertNotEquals("somevalue", sharedState.get("identityMap.ECID[0].id")); // verify that the ECID is not disturbed + + // verify persistence + verify(mockSharedPreferenceEditor, times(2)).putString(eq(IdentityEdgeConstants.DataStoreKey.IDENTITY_PROPERTIES), persistenceValueCaptor.capture()); + Map persistedData = flattenJSONString(persistenceValueCaptor.getAllValues().get(1)); + assertEquals(6, persistedData.size()); // 3 represents id, primary and authState of USERID identifier and generated ECID + assertEquals("somevalue", persistedData.get("identityMap.UserId[0].id")); + assertNotEquals("somevalue", persistedData.get("identityMap.ECID[0].id")); // verify that the ECID is not disturbed + } + + + @Test + public void test_handleUpdateIdentities_CaseSensitiveNamespace_OnCustomIdentifiers() throws Exception { + // setup + Map identityXDM = createXDMIdentityMap( + new TestItem("pushToken", "somevalue"), + new TestItem("PUSHTOKEN", "SOMEVALUE") + ); + Event updateIdentityEvent = buildUpdateIdentityRequest(identityXDM); + extension.handleUpdateIdentities(updateIdentityEvent); + + final ArgumentCaptor sharedStateCaptor = ArgumentCaptor.forClass(Map.class); + + // verify shared state + verify(mockExtensionApi, times(1)).setXDMSharedEventState(sharedStateCaptor.capture(), eq(updateIdentityEvent), any(ExtensionErrorCallback.class)); + Map sharedState = flattenMap(sharedStateCaptor.getValue()); + assertEquals("somevalue", sharedState.get("identityMap.pushToken[0].id")); + assertEquals("SOMEVALUE", sharedState.get("identityMap.PUSHTOKEN[0].id")); + } + + @Test + public void test_handleUpdateIdentities_nullEventData() { + // test + Event updateIdentityEvent = buildUpdateIdentityRequest(null); + extension.handleUpdateIdentities(updateIdentityEvent); + + // verify shared state + verify(mockExtensionApi, times(0)).setXDMSharedEventState(any(Map.class), any(Event.class), any(ExtensionErrorCallback.class)); + + // verify persistence + verify(mockSharedPreferenceEditor, times(1)).putString(anyString(), anyString()); // called only once while generating ECID + } + + + // ======================================================================================== + // handleRemoveRequest + // ======================================================================================== + @Test + public void test_handleRemoveIdentity() throws Exception { + // setup + Map identityXDM = createXDMIdentityMap( + new TestItem("UserId", "secretID"), + new TestItem("PushId", "token") + ); + JSONObject identityXDMJSON = new JSONObject(identityXDM); + Mockito.when(mockSharedPreference.getString(IdentityEdgeConstants.DataStoreKey.IDENTITY_PROPERTIES, null)).thenReturn(identityXDMJSON.toString()); + + final ArgumentCaptor sharedStateCaptor = ArgumentCaptor.forClass(Map.class); + final ArgumentCaptor persistenceValueCaptor = ArgumentCaptor.forClass(String.class); + extension = new IdentityEdgeExtension(mockExtensionApi); + + // test + Map removedIdentityXDM = createXDMIdentityMap( + new TestItem("UserId", "secretID") + ); + Event removeIdentityEvent = buildRemoveIdentityRequest(removedIdentityXDM); + extension.handleRemoveIdentity(removeIdentityEvent); + + // verify shared state + verify(mockExtensionApi, times(1)).setXDMSharedEventState(sharedStateCaptor.capture(), eq(removeIdentityEvent), any(ExtensionErrorCallback.class)); + Map sharedState = flattenMap(sharedStateCaptor.getValue()); + assertNull(sharedState.get("identityMap.UserId[0].id")); + assertEquals("token", sharedState.get("identityMap.PushId[0].id")); + + + // verify persistence + verify(mockSharedPreferenceEditor, times(2)).putString(eq(IdentityEdgeConstants.DataStoreKey.IDENTITY_PROPERTIES), persistenceValueCaptor.capture()); + Map persistedData = flattenJSONString(persistenceValueCaptor.getAllValues().get(1)); + assertNull(persistedData.get("identityMap.UserId[0].id")); + assertEquals("token", persistedData.get("identityMap.PushId[0].id")); + } + + @Test + public void test_handleRemoveIdentity_DoNotRemoveReservedNamespace() throws Exception { + // setup + Map identityXDM = createXDMIdentityMap( + new TestItem("GAID", "someGAID"), + new TestItem("ECID", "someECID"), + new TestItem("IDFA", "someIDFA") + ); + JSONObject identityXDMJSON = new JSONObject(identityXDM); + Mockito.when(mockSharedPreference.getString(IdentityEdgeConstants.DataStoreKey.IDENTITY_PROPERTIES, null)).thenReturn(identityXDMJSON.toString()); + + final ArgumentCaptor sharedStateCaptor = ArgumentCaptor.forClass(Map.class); + final ArgumentCaptor persistenceValueCaptor = ArgumentCaptor.forClass(String.class); + extension = new IdentityEdgeExtension(mockExtensionApi); + + // test + Map removedIdentityXDM = createXDMIdentityMap( + new TestItem("GAID", "someGAID"), + new TestItem("ecid", "someECID"), + new TestItem("Idfa", "someIDFA") + ); + Event removeIdentityEvent = buildRemoveIdentityRequest(removedIdentityXDM); + extension.handleRemoveIdentity(removeIdentityEvent); + + // verify shared state + verify(mockExtensionApi, times(1)).setXDMSharedEventState(sharedStateCaptor.capture(), eq(removeIdentityEvent), any(ExtensionErrorCallback.class)); + Map sharedState = flattenMap(sharedStateCaptor.getValue()); + assertEquals("someGAID", sharedState.get("identityMap.GAID[0].id")); + assertEquals("someECID", sharedState.get("identityMap.ECID[0].id")); + assertEquals("someIDFA", sharedState.get("identityMap.IDFA[0].id")); + + + // verify persistence + verify(mockSharedPreferenceEditor, times(1)).putString(eq(IdentityEdgeConstants.DataStoreKey.IDENTITY_PROPERTIES), persistenceValueCaptor.capture()); + Map persistedData = flattenJSONString(persistenceValueCaptor.getValue()); + assertEquals("someGAID", persistedData.get("identityMap.GAID[0].id")); + assertEquals("someECID", persistedData.get("identityMap.ECID[0].id")); + assertEquals("someIDFA", persistedData.get("identityMap.IDFA[0].id")); + } + + + @Test + public void test_handleRemoveIdentity_NullData() throws Exception { + // setup + Map identityXDM = createXDMIdentityMap( + new TestItem("PushId", "token") + ); + JSONObject identityXDMJSON = new JSONObject(identityXDM); + Mockito.when(mockSharedPreference.getString(IdentityEdgeConstants.DataStoreKey.IDENTITY_PROPERTIES, null)).thenReturn(identityXDMJSON.toString()); + extension = new IdentityEdgeExtension(mockExtensionApi); + + // test + Event removeIdentityEvent = buildRemoveIdentityRequest(null); + extension.handleRemoveIdentity(removeIdentityEvent); + + // verify shared state + verify(mockExtensionApi, times(0)).setXDMSharedEventState(any(Map.class), eq(removeIdentityEvent), any(ExtensionErrorCallback.class)); + + // verify persistence + verify(mockSharedPreferenceEditor, times(1)).putString(eq(IdentityEdgeConstants.DataStoreKey.IDENTITY_PROPERTIES), anyString()); + } + + // ======================================================================================== // private helper methods // ======================================================================================== @@ -378,13 +577,4 @@ private void setupExistingIdentityEdgeProps(final ECID ecid) { Mockito.when(mockSharedPreference.getString(IdentityEdgeConstants.DataStoreKey.IDENTITY_PROPERTIES, null)).thenReturn(propsJSON); } - private static IdentityItem getItemFromIdentityMap(final Map xdmMap, final String namespace, final int itemIndex) { - if (xdmMap == null) { return null; } - Map identityMap = (Map) xdmMap.get("identityMap"); - if (identityMap == null) { return null; } - List itemList = (List) identityMap.get(namespace); - if (itemList == null || itemList.size() <= itemIndex) { return null; } - return IdentityItem.fromData((Map)itemList.get(itemIndex)); - } - } diff --git a/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/IdentityEdgePropertiesTests.java b/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/IdentityEdgePropertiesTests.java index ffcc7e7d..f8e53cf8 100644 --- a/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/IdentityEdgePropertiesTests.java +++ b/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/IdentityEdgePropertiesTests.java @@ -13,20 +13,20 @@ import org.junit.Test; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; import java.util.Map; +import static com.adobe.marketing.mobile.identityedge.IdentityEdgeTestUtil.*; import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; public class IdentityEdgePropertiesTests { + // ====================================================================================================================== + // Tests for method : toXDMData(final boolean allowEmpty) + // ====================================================================================================================== + @Test - public void testIdentityEdgeProperties_toXDMDataEmpty() { + public void test_toXDMData_Empty() { // setup IdentityEdgeProperties props = new IdentityEdgeProperties(); @@ -37,8 +37,9 @@ public void testIdentityEdgeProperties_toXDMDataEmpty() { assertNull(xdmMap.get(IdentityEdgeConstants.XDMKeys.IDENTITY_MAP)); } + @Test - public void testIdentityEdgeProperties_toXDMDataFull() { + public void test_toXDMData_Full() { // setup IdentityEdgeProperties props = new IdentityEdgeProperties(); props.setECID(new ECID()); @@ -46,24 +47,21 @@ public void testIdentityEdgeProperties_toXDMDataFull() { // test Map xdmData = props.toXDMData(false); + Map flatMap = flattenMap(xdmData); - // verify - Map ecidItem = getItemFromIdentityMap(xdmData, "ECID", 0); - Map ecidSecondaryItem = getItemFromIdentityMap(xdmData, "ECID", 1); - - assertNotNull(ecidItem); - assertEquals(props.getECID().toString(), (String)ecidItem.get("id")); - assertFalse((boolean)ecidItem.get("primary")); - assertEquals(AuthenticationState.AMBIGUOUS.name(), (String)ecidItem.get("authenticatedState")); - - assertNotNull(ecidSecondaryItem); - assertEquals(props.getECIDSecondary().toString(), (String)ecidSecondaryItem.get("id")); - assertFalse((boolean)ecidSecondaryItem.get("primary")); - assertEquals(AuthenticationState.AMBIGUOUS.name(), (String)ecidSecondaryItem.get("authenticatedState")); + // verify primary ECID + assertEquals(props.getECID().toString(), flatMap.get("identityMap.ECID[0].id")); + assertEquals("AMBIGUOUS", flatMap.get("identityMap.ECID[0].authenticatedState")); + assertEquals("false", flatMap.get("identityMap.ECID[0].primary")); + + // verify secondary ECID + assertEquals(props.getECIDSecondary().toString(), flatMap.get("identityMap.ECID[1].id")); + assertEquals("AMBIGUOUS", flatMap.get("identityMap.ECID[1].authenticatedState")); + assertEquals("false", flatMap.get("identityMap.ECID[1].primary")); } @Test - public void testIdentityEdgeProperties_toXDMDataOnlyPrimaryECID() { + public void test_toXDMData_OnlyPrimaryECID() { // setup IdentityEdgeProperties props = new IdentityEdgeProperties(); props.setECID(new ECID()); @@ -72,109 +70,213 @@ public void testIdentityEdgeProperties_toXDMDataOnlyPrimaryECID() { Map xdmMap = props.toXDMData(false); // verify - assertEquals(props.getECID().toString(), ecidFromIdentityMap(xdmMap)); + assertEquals(props.getECID().toString(), flattenMap(xdmMap).get("identityMap.ECID[0].id")); } @Test - public void testIdentityEdgeProperties_toXDMDataOnlySecondaryECID() { + public void test_toXDMData_OnlySecondaryECID() { + // should not set secondary ECID if primary not set // setup IdentityEdgeProperties props = new IdentityEdgeProperties(); props.setECIDSecondary(new ECID()); + // test and verify, can't have secondary ECID without primary ECID + assertEquals(0,flattenMap(props.toXDMData(false)).size()); + } + + + // ====================================================================================================================== + // Tests for constructor : IdentityEdgeProperties(final Map xdmData) + // ====================================================================================================================== + + @Test + public void testConstruct_FromXDMData_LoadingDataFromPersistence() { + // setup + Map persistedIdentifiers = createXDMIdentityMap( + new TestItem("UserId", "secretID"), + new TestItem("PushId", "token"), + new TestECIDItem("primaryECID"), + new TestECIDItem("secondaryECID") + ); + // test - Map xdmMap = props.toXDMData(false); + IdentityEdgeProperties props = new IdentityEdgeProperties(persistedIdentifiers); - // verify, can't have secondary ECID without primary ECID - assertNull(ecidFromIdentityMap(xdmMap)); + // verify + Map flatMap = flattenMap(props.toXDMData(false)); + assertEquals(12,flatMap.size()); // 4x3 + assertEquals("primaryECID", props.getECID().toString()); + assertEquals("secondaryECID", props.getECIDSecondary().toString()); + assertEquals("secretID", flatMap.get("identityMap.UserId[0].id")); + assertEquals("token", flatMap.get("identityMap.PushId[0].id")); } @Test - public void testIdentityEdgeProperties_toXDMDataMissingECID() { + public void testConstruct_FromXDMData_NothingFromPersistence() { + // test + IdentityEdgeProperties props = new IdentityEdgeProperties(null); + + // verify + assertEquals(0,flattenMap(props.toXDMData(false)).size()); + } + + + // ====================================================================================================================== + // Tests for method : setECID(final ECID newEcid) + // ====================================================================================================================== + + @Test + public void test_setECID_WillReplaceTheOldECID() { // setup IdentityEdgeProperties props = new IdentityEdgeProperties(); - // test - Map xdmData = props.toXDMData(false); + // test 1 + props.setECID(new ECID("primary")); // verify - assertNull(ecidFromIdentityMap(xdmData)); + Map flatMap = flattenMap(props.toXDMData(false)); + assertEquals(3,flatMap.size()); + assertEquals("primary", flatMap.get("identityMap.ECID[0].id")); + assertEquals("false", flatMap.get("identityMap.ECID[0].primary")); + assertEquals("primary", props.getECID().toString()); + + // test 2 - call setECID again to replace the old one + props.setECID(new ECID("primaryAgain")); + + // verify + flatMap = flattenMap(props.toXDMData(false)); + assertEquals(3,flatMap.size()); + assertEquals("primaryAgain", flatMap.get("identityMap.ECID[0].id")); + assertEquals("false", flatMap.get("identityMap.ECID[0].primary")); + assertEquals("primaryAgain", props.getECID().toString()); } @Test - public void testIdentityEdgeProperties_toXDMDataMissingPrivacy() { + public void test_setECID_NullRemovesFromIdentityMap() { // setup IdentityEdgeProperties props = new IdentityEdgeProperties(); - props.setECID(new ECID()); - // test - Map xdmData = props.toXDMData(false); + // test 1 - set a valid ECID and then to null + props.setECID(new ECID("primary")); + props.setECID(null); // verify - assertEquals(props.getECID().toString(), ecidFromIdentityMap(xdmData)); + assertEquals(0,flattenMap(props.toXDMData(false)).size()); + assertNull(props.getECID()); } + + // ====================================================================================================================== + // Tests for method : setECIDSecondary(final ECID newEcid) + // ====================================================================================================================== + @Test - public void testIdentityEdgeProperties_fromXDMDataFull() { + public void test_setECIDSecondary_WillReplaceTheOldECID() { // setup IdentityEdgeProperties props = new IdentityEdgeProperties(); - props.setECID(new ECID()); - props.setECIDSecondary(new ECID()); - // test - Map xdmData = props.toXDMData(false); - IdentityEdgeProperties loadedProps = new IdentityEdgeProperties(xdmData); + // test 1 + props.setECID(new ECID("primary")); + props.setECIDSecondary(new ECID("secondary")); // verify - assertEquals(ecidFromIdentityMap(xdmData), loadedProps.getECID().toString()); - assertEquals(props.getECIDSecondary(), loadedProps.getECIDSecondary()); + Map flatMap = flattenMap(props.toXDMData(false)); + assertEquals(6,flatMap.size()); + assertEquals("secondary", flatMap.get("identityMap.ECID[1].id")); + assertEquals("false", flatMap.get("identityMap.ECID[1].primary")); + assertEquals("secondary", props.getECIDSecondary().toString()); + + // test 2 - call setECIDSecondary again to replace the old one + props.setECIDSecondary(new ECID("secondaryAgain")); + + // verify + flatMap = flattenMap(props.toXDMData(false)); + assertEquals(6,flatMap.size()); + assertEquals("secondaryAgain", flatMap.get("identityMap.ECID[1].id")); + assertEquals("false", flatMap.get("identityMap.ECID[1].primary")); + assertEquals("secondaryAgain", props.getECIDSecondary().toString()); } @Test - public void testIdentityEdgeProperties_fromXDMDataMissingECID() { + public void test_setECIDSecondary_NullRemovesFromIdentityMap() { // setup - IdentityEdgeProperties props = new IdentityEdgeProperties(); + IdentityEdgeProperties props = new IdentityEdgeProperties(createXDMIdentityMap( + new TestECIDItem("primary"), + new TestECIDItem("secondary") + )); + assertEquals(6, flattenMap(props.toXDMData(false)).size()); // test - Map map = props.toXDMData(false); - IdentityEdgeProperties loadedProps = new IdentityEdgeProperties(map); + props.setECIDSecondary(null); // verify - assertNull(loadedProps.getECID()); + assertEquals(3, flattenMap(props.toXDMData(false)).size()); + assertNull(props.getECIDSecondary()); } + @Test - public void testIdentityEdgeProperties_fromXDMDataMissingPrivacy() { + public void test_clearPrimaryECID_alsoClearsSecondaryECID() { // setup - IdentityEdgeProperties props = new IdentityEdgeProperties(); - props.setECID(new ECID()); + IdentityEdgeProperties props = new IdentityEdgeProperties(createXDMIdentityMap( + new TestECIDItem("primary"), + new TestECIDItem("secondary") + )); // test - Map xdmMap = props.toXDMData(false); - IdentityEdgeProperties loadedProps = new IdentityEdgeProperties(xdmMap); + props.setECID(null); // verify - assertEquals(ecidFromIdentityMap(xdmMap), loadedProps.getECID().toString()); + assertEquals(0, flattenMap(props.toXDMData(false)).size()); + assertNull(props.getECIDSecondary()); + assertNull(props.getECID()); } - private String ecidFromIdentityMap(Map xdmMap) { - if (xdmMap == null) { return null; } - Map identityMap = (HashMap) xdmMap.get("identityMap"); - if (identityMap == null) { return null; } - List ecidArr = (ArrayList) identityMap.get("ECID"); - if (ecidArr == null) { return null; } - Map ecidDict = (HashMap) ecidArr.get(0); - if (ecidDict == null) { return null; } - String ecid = (String) ecidDict.get("id"); - return ecid; + + @Test + public void test_setPrimaryECIDPreservesSecondaryECID() { + // setup + IdentityEdgeProperties props = new IdentityEdgeProperties(createXDMIdentityMap( + new TestECIDItem("primary"), + new TestECIDItem("secondary") + )); + + // test + props.setECID(new ECID("primaryAgain")); + + // verify + assertEquals(6, flattenMap(props.toXDMData(false)).size()); + assertEquals("secondary",props.getECIDSecondary().toString()); + assertEquals("primaryAgain",props.getECID().toString()); } - private static Map getItemFromIdentityMap(final Map xdmMap, final String namespace, final int itemIndex) { - if (xdmMap == null) { return null; } - Map identityMap = (Map) xdmMap.get("identityMap"); - if (identityMap == null) { return null; } - List itemList = (List) identityMap.get(namespace); - if (itemList == null) { return null; } - return (Map) itemList.get(itemIndex); + @Test + public void test_primaryECIDIsAlwaysTheFirstElement() { + // setup + IdentityEdgeProperties props = new IdentityEdgeProperties(); + + // test + props.setECID(new ECID("primary")); + props.setECIDSecondary(new ECID("secondary")); + props.setECID(new ECID("primaryAgain")); + + // verify + Map flatMap = flattenMap(props.toXDMData(false)); + assertEquals(6, flatMap.size()); + assertEquals("primaryAgain", flatMap.get("identityMap.ECID[0].id")); + assertEquals("secondary", flatMap.get("identityMap.ECID[1].id")); + } + + + // ====================================================================================================================== + // Tests for "updateCustomerIdentifiers" is already covered in "handleUpdateRequest" tests in IdentityEdgeExtensionTests + // ====================================================================================================================== + + + // ====================================================================================================================== + // Tests for "removeCustomerIdentifiers" is already covered in handleRemoveRequest tests in IdentityEdgeExtensionTests + // ====================================================================================================================== + } diff --git a/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeStateTests.java b/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeStateTests.java index 5569ee3d..a052abd3 100644 --- a/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeStateTests.java +++ b/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeStateTests.java @@ -177,13 +177,26 @@ public void testIdentityEdgeState_resetIdentifiers() { verify(mockSharedPreferenceEditor, Mockito.times(1)).apply(); // should save to data store } + + // ====================================================================================================================== + // Tests for "updateCustomerIdentifiers" is already covered in "handleUpdateRequest" tests in IdentityEdgeExtensionTests + // ====================================================================================================================== + + + // ====================================================================================================================== + // Tests for "removeCustomerIdentifiers" is already covered in handleRemoveRequest tests in IdentityEdgeExtensionTests + // ====================================================================================================================== + @Test public void testIdentityEdgeState_updateLegacyExperienceCloudId() { IdentityEdgeState state = new IdentityEdgeState(new IdentityEdgeProperties()); + state.getIdentityEdgeProperties().setECID(new ECID()); ECID legacyEcid = new ECID(); + // test state.updateLegacyExperienceCloudId(legacyEcid); + // verify assertEquals(legacyEcid, state.getIdentityEdgeProperties().getECIDSecondary()); verify(mockSharedPreferenceEditor, Mockito.times(1)).apply(); } @@ -196,6 +209,7 @@ public void testIdentityEdgeState_updateLegacyExperienceCloudId_notSetWhenECIDSa state.updateLegacyExperienceCloudId(legacyEcid); + // verify assertNull(state.getIdentityEdgeProperties().getECIDSecondary()); verify(mockSharedPreferenceEditor, Mockito.times(0)).apply(); } @@ -203,6 +217,7 @@ public void testIdentityEdgeState_updateLegacyExperienceCloudId_notSetWhenECIDSa @Test public void testIdentityEdgeState_updateLegacyExperienceCloudId_notSetWhenSecondaryECIDSame() { IdentityEdgeState state = new IdentityEdgeState(new IdentityEdgeProperties()); + state.getIdentityEdgeProperties().setECID(new ECID()); ECID legacyEcid = new ECID(); state.getIdentityEdgeProperties().setECIDSecondary(legacyEcid); diff --git a/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeStorageServiceTests.java b/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeStorageServiceTests.java index af219550..e9fe6a64 100644 --- a/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeStorageServiceTests.java +++ b/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeStorageServiceTests.java @@ -11,7 +11,6 @@ package com.adobe.marketing.mobile.identityedge; - import android.app.Application; import android.content.Context; import android.content.SharedPreferences; diff --git a/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeTests.java b/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeTests.java index 979b1f6c..e0858a79 100644 --- a/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeTests.java +++ b/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeTests.java @@ -11,6 +11,8 @@ package com.adobe.marketing.mobile.identityedge; +import android.provider.ContactsContract; + import com.adobe.marketing.mobile.AdobeCallback; import com.adobe.marketing.mobile.AdobeCallbackWithError; import com.adobe.marketing.mobile.AdobeError; @@ -157,7 +159,8 @@ public void fail(AdobeError adobeError) { } @Override - public void call(Object o) { } + public void call(Object o) { + } }; // test @@ -171,7 +174,7 @@ public void call(Object o) { } adobeCallbackCaptor.getValue().call(null); // verify - assertTrue((boolean)errorCapture.get(KEY_IS_ERRORCALLBACK_CALLED)); + assertTrue((boolean) errorCapture.get(KEY_IS_ERRORCALLBACK_CALLED)); assertEquals(AdobeError.UNEXPECTED_ERROR, errorCapture.get(KEY_CAPTUREDERRORCALLBACK)); } @@ -190,7 +193,8 @@ public void fail(AdobeError adobeError) { } @Override - public void call(Object o) { } + public void call(Object o) { + } }; // test @@ -206,7 +210,7 @@ public void call(Object o) { } adobeCallbackCaptor.getValue().call(buildIdentityResponseEvent(eventData)); // verify - assertTrue((boolean)errorCapture.get(KEY_IS_ERRORCALLBACK_CALLED)); + assertTrue((boolean) errorCapture.get(KEY_IS_ERRORCALLBACK_CALLED)); assertEquals(AdobeError.UNEXPECTED_ERROR, errorCapture.get(KEY_CAPTUREDERRORCALLBACK)); } @@ -225,7 +229,8 @@ public void fail(AdobeError adobeError) { } @Override - public void call(Object o) { } + public void call(Object o) { + } }; // test @@ -240,7 +245,7 @@ public void call(Object o) { } adobeCallbackCaptor.getValue().call(buildIdentityResponseEvent(emptyXDMData)); // verify - assertTrue((boolean)errorCapture.get(KEY_IS_ERRORCALLBACK_CALLED)); + assertTrue((boolean) errorCapture.get(KEY_IS_ERRORCALLBACK_CALLED)); assertEquals(AdobeError.UNEXPECTED_ERROR, errorCapture.get(KEY_CAPTUREDERRORCALLBACK)); } @@ -255,8 +260,8 @@ public void testUpdateIdentities() { // test IdentityMap map = new IdentityMap(); - map.addItem("mainspace", new IdentityItem("id", AuthenticationState.AUTHENTICATED, true)); - map.addItem("secondspace", new IdentityItem("idtwo", AuthenticationState.LOGGED_OUT, false)); + map.addItem(new IdentityItem("id", AuthenticationState.AUTHENTICATED, true),"mainspace"); + map.addItem(new IdentityItem("idtwo", AuthenticationState.LOGGED_OUT, false),"secondspace"); IdentityEdge.updateIdentities(map); // verify @@ -269,10 +274,10 @@ public void testUpdateIdentities() { // verify the dispatched event details Event dispatchedEvent = eventCaptor.getValue(); - assertEquals(IdentityEdgeConstants.EventNames.UPDATE_IDENTITIES,dispatchedEvent.getName()); - assertEquals(IdentityEdgeConstants.EventType.EDGE_IDENTITY.toLowerCase(),dispatchedEvent.getType()); - assertEquals(IdentityEdgeConstants.EventSource.UPDATE_IDENTITY.toLowerCase(),dispatchedEvent.getSource()); - assertEquals(map.asEventData(),dispatchedEvent.getEventData()); + assertEquals(IdentityEdgeConstants.EventNames.UPDATE_IDENTITIES, dispatchedEvent.getName()); + assertEquals(IdentityEdgeConstants.EventType.EDGE_IDENTITY.toLowerCase(), dispatchedEvent.getType()); + assertEquals(IdentityEdgeConstants.EventSource.UPDATE_IDENTITY.toLowerCase(), dispatchedEvent.getSource()); + assertEquals(map.asEventData(), dispatchedEvent.getEventData()); } @Test @@ -282,11 +287,55 @@ public void testUpdateIdentitiesNullAndEmptyMap() { IdentityEdge.updateIdentities(map); IdentityEdge.updateIdentities(null); - // verify no of these API calls dispatch an event + // verify none of these API calls dispatch an event PowerMockito.verifyStatic(MobileCore.class, Mockito.times(0)); MobileCore.dispatchEvent(any(Event.class), any(ExtensionErrorCallback.class)); } + + @Test + public void testRemoveIdentity() { + // setup + final ArgumentCaptor eventCaptor = ArgumentCaptor.forClass(Event.class); + final ArgumentCaptor extensionErrorCallbackCaptor = ArgumentCaptor.forClass(ExtensionErrorCallback.class); + IdentityItem sampleItem = new IdentityItem("sample", AuthenticationState.AMBIGUOUS, false); + + // test + IdentityEdge.removeIdentity(sampleItem, "namespace"); + + // verify dispatch event + PowerMockito.verifyStatic(MobileCore.class, Mockito.times(1)); + MobileCore.dispatchEvent(eventCaptor.capture(), extensionErrorCallbackCaptor.capture()); + + Event dispatchedEvent = eventCaptor.getValue(); + assertEquals(IdentityEdgeConstants.EventNames.REMOVE_IDENTITIES, dispatchedEvent.getName()); + assertEquals(IdentityEdgeConstants.EventType.EDGE_IDENTITY.toLowerCase(), dispatchedEvent.getType()); + assertEquals(IdentityEdgeConstants.EventSource.REMOVE_IDENTITY.toLowerCase(), dispatchedEvent.getSource()); + IdentityMap sampleInputIdentitymap = new IdentityMap(); + sampleInputIdentitymap.addItem(sampleItem,"namespace"); + assertEquals(sampleInputIdentitymap.asEventData(), dispatchedEvent.getEventData()); + + // TODO - enable when ExtensionError creation is available + // should not crash on calling the callback + //extensionErrorCallback.error(ExtensionError.UNEXPECTED_ERROR); + } + + @Test + public void testRemoveIdentity_WithInvalidInputs() { + // setup + IdentityItem sampleItem = new IdentityItem("sample", AuthenticationState.AMBIGUOUS, false); + + // test + IdentityEdge.removeIdentity(null, "namespace"); + IdentityEdge.removeIdentity(sampleItem, ""); + IdentityEdge.removeIdentity(sampleItem, null); + + // verify none of these API calls dispatch an event + PowerMockito.verifyStatic(MobileCore.class, Mockito.times(0)); + MobileCore.dispatchEvent(any(Event.class), any(ExtensionErrorCallback.class)); + } + + // ======================================================================================== // getIdentities API // ======================================================================================== @@ -384,7 +433,8 @@ public void fail(AdobeError adobeError) { } @Override - public void call(Object o) { } + public void call(Object o) { + } }; // test @@ -398,7 +448,7 @@ public void call(Object o) { } adobeCallbackCaptor.getValue().call(null); // verify - assertTrue((boolean)errorCapture.get(KEY_IS_ERRORCALLBACK_CALLED)); + assertTrue((boolean) errorCapture.get(KEY_IS_ERRORCALLBACK_CALLED)); assertEquals(AdobeError.UNEXPECTED_ERROR, errorCapture.get(KEY_CAPTUREDERRORCALLBACK)); } @@ -417,7 +467,8 @@ public void fail(AdobeError adobeError) { } @Override - public void call(Object o) { } + public void call(Object o) { + } }; // test @@ -433,7 +484,7 @@ public void call(Object o) { } adobeCallbackCaptor.getValue().call(buildIdentityResponseEvent(eventData)); // verify - assertTrue((boolean)errorCapture.get(KEY_IS_ERRORCALLBACK_CALLED)); + assertTrue((boolean) errorCapture.get(KEY_IS_ERRORCALLBACK_CALLED)); assertEquals(AdobeError.UNEXPECTED_ERROR, errorCapture.get(KEY_CAPTUREDERRORCALLBACK)); } @@ -452,7 +503,8 @@ public void fail(AdobeError adobeError) { } @Override - public void call(Object o) { } + public void call(Object o) { + } }; // test @@ -467,7 +519,7 @@ public void call(Object o) { } adobeCallbackCaptor.getValue().call(buildIdentityResponseEvent(emptyXDMData)); // verify - assertTrue((boolean)errorCapture.get(KEY_IS_ERRORCALLBACK_CALLED)); + assertTrue((boolean) errorCapture.get(KEY_IS_ERRORCALLBACK_CALLED)); assertEquals(AdobeError.UNEXPECTED_ERROR, errorCapture.get(KEY_CAPTUREDERRORCALLBACK)); } diff --git a/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/IdentityMapTests.java b/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/IdentityMapTests.java new file mode 100644 index 00000000..c707cb80 --- /dev/null +++ b/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/IdentityMapTests.java @@ -0,0 +1,343 @@ +/* + Copyright 2021 Adobe. All rights reserved. + This file is licensed to you under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. You may obtain a copy + of the License at http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software distributed under + the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + OF ANY KIND, either express or implied. See the License for the specific language + governing permissions and limitations under the License. +*/ + +package com.adobe.marketing.mobile.identityedge; + +import org.json.JSONObject; +import org.junit.Test; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + + +public class IdentityMapTests { + + @Test + public void test_AddItem() { + // test + IdentityMap map = new IdentityMap(); + map.addItem(new IdentityItem("California"),"location"); + + // verify + assertEquals("California", map.toObjectMap().get("location").get(0).get("id")); + } + + @Test + public void test_AddItem_InvalidInputs() { + // test + IdentityMap map = new IdentityMap(); + map.addItem( new IdentityItem("California"),""); + map.addItem(new IdentityItem("California"),null); + map.addItem( null,"namespace"); + + // verify + assertTrue(map.toObjectMap().isEmpty()); + } + + @Test + public void test_getIdentityItemsForNamespace() { + // setup + IdentityMap sampleUserMap = buildSampleIdentityMap(); // 2 items with namespace "Location", 3 items with namespace "Login" + + // test + List locationItems = sampleUserMap.getIdentityItemsForNamespace("location"); + + // verify + assertEquals(2, locationItems.size()); + assertEquals("280 Highway Lane", locationItems.get(0).getId()); + assertEquals("California", locationItems.get(1).getId()); + + // verify the login namespace returns 3 items + assertEquals(3, sampleUserMap.getIdentityItemsForNamespace("login").size()); + } + + + @Test + public void test_getIdentityItemsForNamespace_InvalidInputs() { + // setup + IdentityMap sampleUserMap = buildSampleIdentityMap(); // 2 items with namespace "Location", 3 items with namespace "Login" + + // test 1 + List items = sampleUserMap.getIdentityItemsForNamespace(""); + assertEquals(0, items.size()); + + // test 2 + items = sampleUserMap.getIdentityItemsForNamespace(null); + assertEquals(0, items.size()); + + // test 3 + items = sampleUserMap.getIdentityItemsForNamespace("unavailable"); + assertEquals(0, items.size()); + } + + + @Test + public void test_RemoveItem() { + // setup + IdentityMap sampleUserMap = buildSampleIdentityMap(); // 2 items with namespace "Location", 3 items with namespace "Login" + + // test + sampleUserMap.removeItem( new IdentityItem("California"),"location"); + + // verify + assertEquals(1, sampleUserMap.toObjectMap().get("location").size()); + assertEquals(3, sampleUserMap.toObjectMap().get("login").size()); + + // test 2 + sampleUserMap.removeItem(new IdentityItem("280 Highway Lane"),"location"); + sampleUserMap.removeItem(new IdentityItem("Student"), "login"); + + // verify + assertNull(sampleUserMap.toObjectMap().get("location")); + assertEquals(2, sampleUserMap.toObjectMap().get("login").size()); + } + + @Test + public void test_RemoveItem_InvalidInputs() { + // setup + IdentityMap sampleUserMap = buildSampleIdentityMap(); // 2 items with namespace "Location", 3 items with namespace "Login" + + // test + sampleUserMap.removeItem(new IdentityItem("California"),""); + sampleUserMap.removeItem(new IdentityItem("California"),null); + sampleUserMap.removeItem(null,"location"); + + // verify the existing identityMap is unchanged + assertEquals(2, sampleUserMap.toObjectMap().get("location").size()); + assertEquals(3, sampleUserMap.toObjectMap().get("login").size()); + } + + + @Test + public void test_merge() { + // setup + IdentityMap sampleUserMap = buildSampleIdentityMap(); // 2 items with namespace "Location", 3 items with namespace "Login" + + // test + IdentityMap newMap = new IdentityMap(); + newMap.addItem(new IdentityItem("doorNumber:544"),"location"); + sampleUserMap.merge(newMap); + + // verify the existing identityMap is unchanged + assertEquals(3, sampleUserMap.toObjectMap().get("location").size()); + assertEquals(3, sampleUserMap.toObjectMap().get("login").size()); + } + + + @Test + public void test_merge_EmptyIdentityMap() { + // setup + IdentityMap sampleUserMap = buildSampleIdentityMap(); // 2 items with namespace "Location", 3 items with namespace "Login" + + // test + sampleUserMap.merge(new IdentityMap()); + + // verify the existing identityMap is unchanged + assertEquals(2, sampleUserMap.toObjectMap().get("location").size()); + assertEquals(3, sampleUserMap.toObjectMap().get("login").size()); + } + + @Test + public void test_merge_nullMap() { + // setup + IdentityMap sampleUserMap = buildSampleIdentityMap(); // 2 items with namespace "location", 3 items with namespace "login" + + // test + sampleUserMap.merge(null); + + // verify the existing identityMap is unchanged + assertEquals(2, sampleUserMap.toObjectMap().get("location").size()); + assertEquals(3, sampleUserMap.toObjectMap().get("login").size()); + } + + + @Test + public void test_removeAllIdentityItemsForNamespace() { + // setup + IdentityMap sampleUserMap = buildSampleIdentityMap(); // 2 items with namespace "location", 3 items with namespace "login" + + // test + sampleUserMap.clearItemsForNamespace("location"); + + // verify the existing identityMap is unchanged + assertNull(sampleUserMap.toObjectMap().get("location")); + assertEquals(3, sampleUserMap.toObjectMap().get("login").size()); + } + + + @Test + public void test_removeAllIdentityItemsForNamespace_InvalidNamespace() { + // setup + IdentityMap sampleUserMap = buildSampleIdentityMap(); // 2 items with namespace "location", 3 items with namespace "login" + + // test + sampleUserMap.clearItemsForNamespace(null); + sampleUserMap.clearItemsForNamespace(""); + + // verify + assertEquals(2, sampleUserMap.toObjectMap().get("location").size()); + assertEquals(3, sampleUserMap.toObjectMap().get("login").size()); + } + + @Test + public void test_removeAllIdentityItemsForNamespace_onEmptyMap() { + // setup + IdentityMap emptyMap = new IdentityMap(); + + // test + emptyMap.clearItemsForNamespace("location"); + assertTrue(emptyMap.toObjectMap().isEmpty()); + } + + @Test + public void test_remove() { + // setup + IdentityMap sampleUserMap = buildSampleIdentityMap(); // 2 items with namespace "location", 3 items with namespace "login" + IdentityMap tobeRemovedMap = new IdentityMap(); + tobeRemovedMap.addItem(new IdentityItem("Student"),"login"); + tobeRemovedMap.addItem(new IdentityItem("California"),"location"); + + // test + sampleUserMap.remove(tobeRemovedMap); + + // verify + assertEquals(1, sampleUserMap.toObjectMap().get("location").size()); + assertEquals(2, sampleUserMap.toObjectMap().get("login").size()); + } + + @Test + public void test_remove_NullAndEmptyMap() { + // setup + IdentityMap sampleUserMap = buildSampleIdentityMap(); // 2 items with namespace "location", 3 items with namespace "login" + + // test + sampleUserMap.remove(null); + sampleUserMap.remove(new IdentityMap()); + + // verify that the existing map is unchanged + assertEquals(2, sampleUserMap.toObjectMap().get("location").size()); + assertEquals(3, sampleUserMap.toObjectMap().get("login").size()); + } + + @Test + public void test_remove_nonexistentNamespaceAndItems() { + // setup + IdentityMap sampleUserMap = buildSampleIdentityMap(); // 2 items with namespace "location", 3 items with namespace "login" + IdentityMap tobeRemovedMap = new IdentityMap(); + tobeRemovedMap.addItem(new IdentityItem("California"),"nonexistentNamespace"); + tobeRemovedMap.addItem(new IdentityItem("nonexistentID"),"login"); + + // test + sampleUserMap.remove(tobeRemovedMap); + + // verify that the existing map is unchanged + assertEquals(2, sampleUserMap.toObjectMap().get("location").size()); + assertEquals(3, sampleUserMap.toObjectMap().get("login").size()); + } + + + @Test + public void test_FromData() throws Exception { + // setup + final String jsonStr = "{\n" + + " \"identityMap\": {\n" + + " \"ECID\": [\n" + + " {\n" + + " \"id\":" + "randomECID" + ",\n" + + " \"authenticatedState\": \"ambiguous\",\n" + + " \"primary\": true\n" + + " }\n" + + " ],\n" + + " \"USERID\": [\n" + + " {\n" + + " \"id\":" + "someUserID" + ",\n" + + " \"authenticatedState\": \"authenticated\",\n" + + " \"primary\": false\n" + + " }\n" + + " ]\n" + + " }\n" + + "}"; + + final JSONObject jsonObject = new JSONObject(jsonStr); + final Map xdmData = Utils.toMap(jsonObject); + + // test + IdentityMap map = IdentityMap.fromData(xdmData); + + // verify + Map flattenedMap = IdentityEdgeTestUtil.flattenMap(map.asEventData()); + assertEquals("randomECID", flattenedMap.get("ECID[0].id")); + assertEquals("AMBIGUOUS", flattenedMap.get("ECID[0].authenticationState")); + assertEquals("true", flattenedMap.get("ECID[0].primary")); + assertEquals("someUserID", flattenedMap.get("USERID[0].id")); + assertEquals("AUTHENTICATED", flattenedMap.get("USERID[0].authenticationState")); + assertEquals("false", flattenedMap.get("USERID[0].primary")); + } + + + @Test + public void test_FromData_NullAndEmptyData() { + assertNull(IdentityMap.fromData(null)); + assertNull(IdentityMap.fromData(new HashMap())); + } + + + @Test + public void test_FromData_InvalidXDMData() throws Exception { + // setup + // ECID is map instead of list + final String invalidJsonStr = "{\n" + + " \"identityMap\": {\n" + + " \"ECID\": {\n" + + " \"id\": \"randomECID\",\n" + + " \"authenticatedState\": \"ambiguous\",\n" + + " \"primary\": true\n" + + " }\n" + + " }\n" + + "}"; + + final JSONObject jsonObject = new JSONObject(invalidJsonStr); + final Map xdmData = Utils.toMap(jsonObject); + + // test + IdentityMap map = IdentityMap.fromData(xdmData); + + // verify + assertTrue(map.isEmpty()); + } + + + private IdentityMap buildSampleIdentityMap() { + // User Login Identity Items + IdentityItem email = new IdentityItem("john@doe", AuthenticationState.AUTHENTICATED, true); + IdentityItem userName = new IdentityItem("John Doe", AuthenticationState.AUTHENTICATED, false); + IdentityItem accountType = new IdentityItem("Student", AuthenticationState.AUTHENTICATED, false); + + // User Location Address Identity Items + IdentityItem street = new IdentityItem("280 Highway Lane", AuthenticationState.AMBIGUOUS, false); + IdentityItem state = new IdentityItem("California", AuthenticationState.AMBIGUOUS, false); + + IdentityMap adobeIdentityMap = new IdentityMap(); + adobeIdentityMap.addItem(email,"login"); + adobeIdentityMap.addItem(userName, "login"); + adobeIdentityMap.addItem(accountType, "login"); + + adobeIdentityMap.addItem(street, "location"); + adobeIdentityMap.addItem(state,"location"); + return adobeIdentityMap; + } + +} From 41c9eab32d305e8ac3b51cb09e99f29096e11a0c Mon Sep 17 00:00:00 2001 From: Pravin Prakash Kumar Date: Mon, 22 Mar 2021 11:42:09 -0700 Subject: [PATCH 015/101] [Dev] - AuthenticationState Renaming and Remove ECID variable (#16) * [AMSDK-11081] - Rename listener tests * [AMSDK-11081] - Listeners for remove and update Identity requests + tests * [AMSDK-11081] - UpdateIdentity Public API * [AMSDK-11081] - Fix spacings in IdentityMap class * [Dev] - Add the functional test helpers + first valid functional test * [Dev] - First functional test * [Dev] - Assertion fail on misread of persistence in TestPersistence helper method * [AMSDK-11081] - Update/Remove Identity API implementation * [AMSDK-11081] - Unit test for IdentityMap and RemoveIdentity Public API * [AMSDK-11081] - More Unit test for update/Remove * [AMSDK-11081] - Few more edits to unittests * [AMSDK-11081] - better naming and typo fixes * [AMSDK-11081] - rearrange parameters, setECID handling, case-insensitive search and more * [AMSDK-11081] - Caseinsensitive removal of reserved namespace items + cleanup * [AMSDK-11081] - cleanup and renaming * [Dev] - Rename enum to AuthenticatedState and fix its toString * [Dev] - Enum AuthenticatedState * [Dev] - removed local ecid and secondaryECID local instances variables * [AMSDK-11081] - final on IdentityMap, enum string comparison change --- ...tionState.java => AuthenticatedState.java} | 10 ++-- .../identityedge/IdentityEdgeProperties.java | 59 ++++++++----------- .../identityedge/IdentityEdgeState.java | 21 +++++-- .../mobile/identityedge/IdentityItem.java | 34 +++++------ .../IdentityEdgeExtensionTests.java | 4 +- .../IdentityEdgePropertiesTests.java | 4 +- .../identityedge/IdentityEdgeTests.java | 14 ++--- .../identityedge/IdentityItemTests.java | 22 +++---- .../mobile/identityedge/IdentityMapTests.java | 14 ++--- 9 files changed, 90 insertions(+), 92 deletions(-) rename code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/{AuthenticationState.java => AuthenticatedState.java} (79%) diff --git a/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/AuthenticationState.java b/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/AuthenticatedState.java similarity index 79% rename from code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/AuthenticationState.java rename to code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/AuthenticatedState.java index df6b0f46..f6773eb4 100644 --- a/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/AuthenticationState.java +++ b/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/AuthenticatedState.java @@ -14,14 +14,14 @@ /** * Represents the authentication state for an {@link IdentityItem} */ -public enum AuthenticationState { +public enum AuthenticatedState { AMBIGUOUS("ambiguous"), AUTHENTICATED("authenticated"), LOGGED_OUT("loggedOut"); private String name; - private AuthenticationState(final String name) { + private AuthenticatedState(final String name) { this.name = name; } @@ -29,10 +29,10 @@ public String getName() { return name; } - public static AuthenticationState fromString(final String state) { - if ("authenticated".equalsIgnoreCase(state)) { + public static AuthenticatedState fromString(final String state) { + if (AUTHENTICATED.getName().equalsIgnoreCase(state)) { return AUTHENTICATED; - } else if ("loggedOut".equalsIgnoreCase(state)) { + } else if (LOGGED_OUT.getName().equalsIgnoreCase(state)) { return LOGGED_OUT; } else { return AMBIGUOUS; diff --git a/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeProperties.java b/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeProperties.java index 49163d75..6346b13b 100644 --- a/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeProperties.java +++ b/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeProperties.java @@ -31,15 +31,11 @@ class IdentityEdgeProperties { add(IdentityEdgeConstants.Namespaces.IDFA); }}; - // The current Experience Cloud ID - private ECID ecid; + private final IdentityMap identityMap; - // A secondary (non-primary) Experience Cloud ID - private ECID ecidSecondary; - - private IdentityMap identityMap = new IdentityMap(); - - IdentityEdgeProperties() { } + IdentityEdgeProperties() { + this.identityMap = new IdentityMap(); + } /** * Creates a identity edge properties instance based on the map @@ -47,22 +43,8 @@ class IdentityEdgeProperties { * @param xdmData a map representing an identity edge properties instance */ IdentityEdgeProperties(final Map xdmData) { - if (Utils.isNullOrEmpty(xdmData)) { - return; - } - - identityMap = IdentityMap.fromData(xdmData); - if (identityMap != null) { - final List ecidItems = identityMap.getIdentityItemsForNamespace(IdentityEdgeConstants.Namespaces.ECID); - if (ecidItems != null) { - if (ecidItems.size() > 0 && ecidItems.get(0) != null && ecidItems.get(0).getId() != null) { - ecid = new ECID(ecidItems.get(0).getId()); - } - if (ecidItems.size() > 1 && ecidItems.get(1) != null && ecidItems.get(1).getId() != null) { - ecidSecondary = new ECID(ecidItems.get(1).getId()); - } - } - } + IdentityMap map = IdentityMap.fromData(xdmData); + this.identityMap = map == null ? new IdentityMap() : map; // always keep an empty identity map so there is no need for null check } /** @@ -72,8 +54,9 @@ class IdentityEdgeProperties { */ void setECID(final ECID newEcid) { // delete the previous ECID from the identity map if exist - if (ecid != null) { - final IdentityItem previousECIDItem = new IdentityItem(ecid.toString()); + final ECID currentECID = getECID(); + if (currentECID != null) { + final IdentityItem previousECIDItem = new IdentityItem(currentECID.toString()); identityMap.removeItem(previousECIDItem, IdentityEdgeConstants.Namespaces.ECID); } @@ -83,11 +66,9 @@ void setECID(final ECID newEcid) { identityMap.clearItemsForNamespace(IdentityEdgeConstants.Namespaces.ECID); } else { // And add the new primary Ecid as a first element of Identity map - final IdentityItem newECIDItem = new IdentityItem(newEcid.toString(), AuthenticationState.AMBIGUOUS, false); + final IdentityItem newECIDItem = new IdentityItem(newEcid.toString(), AuthenticatedState.AMBIGUOUS, false); identityMap.addItem(newECIDItem, IdentityEdgeConstants.Namespaces.ECID, true); } - - this.ecid = newEcid; // keep the local variable up to date } /** @@ -96,7 +77,11 @@ void setECID(final ECID newEcid) { * @return current {@code ECID} */ ECID getECID() { - return ecid; + final List ecidItems = identityMap.getIdentityItemsForNamespace(IdentityEdgeConstants.Namespaces.ECID); + if (ecidItems != null && !ecidItems.isEmpty() && ecidItems.get(0) != null && !Utils.isNullOrEmpty(ecidItems.get(0).getId())) { + return new ECID(ecidItems.get(0).getId()); + } + return null; } /** @@ -106,25 +91,23 @@ ECID getECID() { */ void setECIDSecondary(final ECID newSecondaryEcid) { // delete the previous secondary ECID from the identity map if exist + final ECID ecidSecondary = getECIDSecondary(); if (ecidSecondary != null) { final IdentityItem previousECIDItem = new IdentityItem(ecidSecondary.toString()); identityMap.removeItem(previousECIDItem, IdentityEdgeConstants.Namespaces.ECID); } // do not set secondary ECID if primary ECID is not set - if (ecid == null) { + if (getECID() == null) { MobileCore.log(LoggingMode.DEBUG, LOG_TAG, "Cannot set secondary ECID value as no primary ECID exists."); - this.ecidSecondary = null; return; } // add the new secondary ECID to Identity map if (newSecondaryEcid != null) { - final IdentityItem newSecondaryECIDItem = new IdentityItem(newSecondaryEcid.toString(), AuthenticationState.AMBIGUOUS, false); + final IdentityItem newSecondaryECIDItem = new IdentityItem(newSecondaryEcid.toString(), AuthenticatedState.AMBIGUOUS, false); identityMap.addItem(newSecondaryECIDItem, IdentityEdgeConstants.Namespaces.ECID); } - - this.ecidSecondary = newSecondaryEcid; // keep the local variable up to date } /** @@ -133,7 +116,11 @@ void setECIDSecondary(final ECID newSecondaryEcid) { * @return secondary {@code ECID} */ ECID getECIDSecondary() { - return ecidSecondary; + final List ecidItems = identityMap.getIdentityItemsForNamespace(IdentityEdgeConstants.Namespaces.ECID); + if (ecidItems != null && ecidItems.size() > 1 && ecidItems.get(1) != null && !Utils.isNullOrEmpty(ecidItems.get(1).getId())) { + return new ECID(ecidItems.get(1).getId()); + } + return null; } /** diff --git a/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeState.java b/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeState.java index 3e5678ec..a03b574d 100644 --- a/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeState.java +++ b/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeState.java @@ -24,7 +24,8 @@ class IdentityEdgeState { private IdentityEdgeProperties identityProperties; /** - * Creates a new {@link IdentityEdgeState} with the given {@link IdentityEdgeProperties} + * Creates a new {@link IdentityEdgeState} with the given {@link IdentityEdgeProperties} + * * @param identityProperties identity edge properties */ IdentityEdgeState(final IdentityEdgeProperties identityProperties) { @@ -47,10 +48,13 @@ boolean hasBooted() { /** * Completes init for the Identity Edge extension. + * * @return True if we should share state after bootup, false otherwise */ boolean bootupIfReady() { - if (hasBooted) { return true; } + if (hasBooted) { + return true; + } // Load properties from local storage identityProperties = IdentityEdgeStorageService.loadPropertiesFromPersistence(); @@ -114,17 +118,26 @@ void removeCustomerIdentifiers(final IdentityMap map) { /** * Update the legacy ECID property with {@code legacyEcid} provided it does not equal the primary or secondary ECIDs * currently in {@code IdentityEdgePoperties}. + * * @param legacyEcid the current ECID from the direct Identity extension * @return true if the legacy ECID was updated in {@code IdentityEdgeProperties} */ boolean updateLegacyExperienceCloudId(final ECID legacyEcid) { - if (legacyEcid == identityProperties.getECID() || legacyEcid == identityProperties.getECIDSecondary()) { + final ECID ecid = identityProperties.getECID(); + final ECID ecidSecondary = identityProperties.getECIDSecondary(); + + if ((legacyEcid != null) && (legacyEcid.equals(ecid) || legacyEcid.equals(ecidSecondary))) { + return false; + } + + // no need to clear secondaryECID if its already null + if (legacyEcid == null && ecidSecondary == null){ return false; } identityProperties.setECIDSecondary(legacyEcid); IdentityEdgeStorageService.savePropertiesToPersistence(identityProperties); - MobileCore.log(LoggingMode.DEBUG, LOG_TAG,"Identity direct ECID updated to '" + legacyEcid + "', updating the IdentityMap"); + MobileCore.log(LoggingMode.DEBUG, LOG_TAG, "Identity direct ECID updated to '" + legacyEcid + "', updating the IdentityMap"); return true; } diff --git a/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityItem.java b/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityItem.java index df83070d..7dc845ff 100644 --- a/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityItem.java +++ b/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityItem.java @@ -23,7 +23,7 @@ */ public final class IdentityItem { private final String id; - private final AuthenticationState authenticationState; + private final AuthenticatedState authenticatedState; private final boolean primary; private static final String LOG_TAG = "IdentityItem"; @@ -31,27 +31,27 @@ public final class IdentityItem { /** * Creates a new {@link IdentityItem} * @param id id for the item - * @param authenticationState {@link AuthenticationState} for the item + * @param authenticatedState {@link AuthenticatedState} for the item * @param primary primary flag for the item * @throws IllegalArgumentException if id is null */ - public IdentityItem(final String id, final AuthenticationState authenticationState, final boolean primary) { + public IdentityItem(final String id, final AuthenticatedState authenticatedState, final boolean primary) { if (id == null) { throw new IllegalArgumentException("id must be non-null"); } this.id = id; - this.authenticationState = authenticationState != null ? authenticationState : AuthenticationState.AMBIGUOUS; + this.authenticatedState = authenticatedState != null ? authenticatedState : AuthenticatedState.AMBIGUOUS; this.primary = primary; } /** * Creates a new {@link IdentityItem} with default values - * authenticationState is set to AMBIGUOUS + * authenticatedState is set to AMBIGUOUS * primary is set to false * @param id the id for this {@link IdentityItem} */ public IdentityItem(final String id) { - this(id, AuthenticationState.AMBIGUOUS, false); + this(id, AuthenticatedState.AMBIGUOUS, false); } /** @@ -59,7 +59,7 @@ public IdentityItem(final String id) { * @param item A {@link IdentityItem} to be copied */ public IdentityItem(final IdentityItem item) { - this(item.id, item.authenticationState, item.primary); + this(item.id, item.authenticatedState, item.primary); } /** @@ -72,10 +72,10 @@ Map toObjectMap() { map.put(IdentityEdgeConstants.XDMKeys.ID, id); } - if (authenticationState != null) { - map.put(IdentityEdgeConstants.XDMKeys.AUTHENTICATED_STATE, authenticationState.toString()); + if (authenticatedState != null) { + map.put(IdentityEdgeConstants.XDMKeys.AUTHENTICATED_STATE, authenticatedState.getName()); } else { - map.put(IdentityEdgeConstants.XDMKeys.AUTHENTICATED_STATE, AuthenticationState.AMBIGUOUS.toString()); + map.put(IdentityEdgeConstants.XDMKeys.AUTHENTICATED_STATE, AuthenticatedState.AMBIGUOUS.getName()); } map.put(IdentityEdgeConstants.XDMKeys.PRIMARY, primary); @@ -90,10 +90,10 @@ public String getId() { } /** - * @return Current {@link AuthenticationState} for this item + * @return Current {@link AuthenticatedState} for this item */ - public AuthenticationState getAuthenticationState() { - return authenticationState; + public AuthenticatedState getAuthenticatedState() { + return authenticatedState; } /** @@ -113,9 +113,9 @@ static IdentityItem fromData(final Map data) { try { final String id = (String) data.get(IdentityEdgeConstants.XDMKeys.ID); - AuthenticationState authenticationState = AuthenticationState.fromString((String) data.get(IdentityEdgeConstants.XDMKeys.AUTHENTICATED_STATE)); - if (authenticationState == null) { - authenticationState = AuthenticationState.AMBIGUOUS; + AuthenticatedState authenticatedState = AuthenticatedState.fromString((String) data.get(IdentityEdgeConstants.XDMKeys.AUTHENTICATED_STATE)); + if (authenticatedState == null) { + authenticatedState = AuthenticatedState.AMBIGUOUS; } boolean primary = false; @@ -123,7 +123,7 @@ static IdentityItem fromData(final Map data) { primary = (boolean) data.get(IdentityEdgeConstants.XDMKeys.PRIMARY); } - return new IdentityItem(id, authenticationState, primary); + return new IdentityItem(id, authenticatedState, primary); } catch (ClassCastException e) { MobileCore.log(LoggingMode.DEBUG, LOG_TAG, "Failed to create IdentityItem from data."); return null; diff --git a/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeExtensionTests.java b/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeExtensionTests.java index 6a3b402e..472d401e 100644 --- a/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeExtensionTests.java +++ b/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeExtensionTests.java @@ -384,14 +384,14 @@ public void test_handleUpdateIdentities() throws Exception { verify(mockExtensionApi, times(1)).setXDMSharedEventState(sharedStateCaptor.capture(), eq(updateIdentityEvent), any(ExtensionErrorCallback.class)); Map sharedState = flattenMap(sharedStateCaptor.getValue()); assertEquals("secretID", sharedState.get("identityMap.UserId[0].id")); - assertEquals("AMBIGUOUS", sharedState.get("identityMap.UserId[0].authenticatedState")); + assertEquals("ambiguous", sharedState.get("identityMap.UserId[0].authenticatedState")); assertEquals("false", sharedState.get("identityMap.UserId[0].primary")); // verify persistence verify(mockSharedPreferenceEditor, times(2)).putString(eq(IdentityEdgeConstants.DataStoreKey.IDENTITY_PROPERTIES), persistenceValueCaptor.capture()); Map persistedData = flattenJSONString(persistenceValueCaptor.getAllValues().get(1)); assertEquals("secretID", persistedData.get("identityMap.UserId[0].id")); - assertEquals("AMBIGUOUS", persistedData.get("identityMap.UserId[0].authenticatedState")); + assertEquals("ambiguous", persistedData.get("identityMap.UserId[0].authenticatedState")); assertEquals("false", persistedData.get("identityMap.UserId[0].primary")); } diff --git a/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/IdentityEdgePropertiesTests.java b/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/IdentityEdgePropertiesTests.java index f8e53cf8..04f685a4 100644 --- a/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/IdentityEdgePropertiesTests.java +++ b/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/IdentityEdgePropertiesTests.java @@ -51,12 +51,12 @@ public void test_toXDMData_Full() { // verify primary ECID assertEquals(props.getECID().toString(), flatMap.get("identityMap.ECID[0].id")); - assertEquals("AMBIGUOUS", flatMap.get("identityMap.ECID[0].authenticatedState")); + assertEquals("ambiguous", flatMap.get("identityMap.ECID[0].authenticatedState")); assertEquals("false", flatMap.get("identityMap.ECID[0].primary")); // verify secondary ECID assertEquals(props.getECIDSecondary().toString(), flatMap.get("identityMap.ECID[1].id")); - assertEquals("AMBIGUOUS", flatMap.get("identityMap.ECID[1].authenticatedState")); + assertEquals("ambiguous", flatMap.get("identityMap.ECID[1].authenticatedState")); assertEquals("false", flatMap.get("identityMap.ECID[1].primary")); } diff --git a/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeTests.java b/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeTests.java index e0858a79..e20453fe 100644 --- a/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeTests.java +++ b/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeTests.java @@ -11,8 +11,6 @@ package com.adobe.marketing.mobile.identityedge; -import android.provider.ContactsContract; - import com.adobe.marketing.mobile.AdobeCallback; import com.adobe.marketing.mobile.AdobeCallbackWithError; import com.adobe.marketing.mobile.AdobeError; @@ -260,8 +258,8 @@ public void testUpdateIdentities() { // test IdentityMap map = new IdentityMap(); - map.addItem(new IdentityItem("id", AuthenticationState.AUTHENTICATED, true),"mainspace"); - map.addItem(new IdentityItem("idtwo", AuthenticationState.LOGGED_OUT, false),"secondspace"); + map.addItem(new IdentityItem("id", AuthenticatedState.AUTHENTICATED, true),"mainspace"); + map.addItem(new IdentityItem("idtwo", AuthenticatedState.LOGGED_OUT, false),"secondspace"); IdentityEdge.updateIdentities(map); // verify @@ -298,7 +296,7 @@ public void testRemoveIdentity() { // setup final ArgumentCaptor eventCaptor = ArgumentCaptor.forClass(Event.class); final ArgumentCaptor extensionErrorCallbackCaptor = ArgumentCaptor.forClass(ExtensionErrorCallback.class); - IdentityItem sampleItem = new IdentityItem("sample", AuthenticationState.AMBIGUOUS, false); + IdentityItem sampleItem = new IdentityItem("sample", AuthenticatedState.AMBIGUOUS, false); // test IdentityEdge.removeIdentity(sampleItem, "namespace"); @@ -323,7 +321,7 @@ public void testRemoveIdentity() { @Test public void testRemoveIdentity_WithInvalidInputs() { // setup - IdentityItem sampleItem = new IdentityItem("sample", AuthenticationState.AMBIGUOUS, false); + IdentityItem sampleItem = new IdentityItem("sample", AuthenticatedState.AMBIGUOUS, false); // test IdentityEdge.removeIdentity(null, "namespace"); @@ -396,11 +394,11 @@ public void call(IdentityMap map) { IdentityItem coreItem = callbackReturnValues.get(0).getIdentityItemsForNamespace("CORE").get(0); assertEquals(ecid.toString(), ecidItem.getId()); - assertEquals(AuthenticationState.AMBIGUOUS, ecidItem.getAuthenticationState()); + assertEquals(AuthenticatedState.AMBIGUOUS, ecidItem.getAuthenticatedState()); assertEquals(true, ecidItem.isPrimary()); assertEquals(coreId, coreItem.getId()); - assertEquals(AuthenticationState.AUTHENTICATED, coreItem.getAuthenticationState()); + assertEquals(AuthenticatedState.AUTHENTICATED, coreItem.getAuthenticatedState()); assertEquals(false, coreItem.isPrimary()); // TODO - enable when ExtensionError creation is available diff --git a/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/IdentityItemTests.java b/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/IdentityItemTests.java index d32ea27f..0d08d833 100644 --- a/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/IdentityItemTests.java +++ b/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/IdentityItemTests.java @@ -26,21 +26,21 @@ public class IdentityItemTests { @Test public void testIdentityItem_toObjectMap_full() { // setup - IdentityItem item = new IdentityItem("id", AuthenticationState.AUTHENTICATED, true); + IdentityItem item = new IdentityItem("id", AuthenticatedState.AUTHENTICATED, true); // test Map data = item.toObjectMap(); // verify assertEquals("id", (String) data.get("id")); - assertEquals("AUTHENTICATED", (String) data.get("authenticatedState")); + assertEquals("authenticated", (String) data.get("authenticatedState")); assertEquals(true, (boolean) data.get("primary")); } @Test(expected = IllegalArgumentException.class) public void testIdentityItem_toObjectMap_missingId() { // setup - IdentityItem item = new IdentityItem(null, AuthenticationState.AUTHENTICATED, true); + IdentityItem item = new IdentityItem(null, AuthenticatedState.AUTHENTICATED, true); // test Map data = item.toObjectMap(); @@ -56,7 +56,7 @@ public void testIdentityItem_toObjectMap_missingAuthState() { // verify assertEquals("id", (String) data.get("id")); - assertEquals("AMBIGUOUS", (String) data.get("authenticatedState")); + assertEquals("ambiguous", (String) data.get("authenticatedState")); assertEquals(true, (boolean) data.get("primary")); } @@ -73,7 +73,7 @@ public void testIdentityItem_fromData_full() { // verify assertEquals("test-id", item.getId()); - assertEquals("LOGGED_OUT", item.getAuthenticationState().toString()); + assertEquals("loggedOut", item.getAuthenticatedState().getName()); assertEquals(true, item.isPrimary()); } @@ -89,7 +89,7 @@ public void testIdentityItem_fromData_missingAuthState() { // verify assertEquals("test-id", item.getId()); - assertEquals("AMBIGUOUS", item.getAuthenticationState().toString()); + assertEquals("ambiguous", item.getAuthenticatedState().getName()); assertEquals(true, item.isPrimary()); } @@ -105,22 +105,22 @@ public void testIdentityItem_fromData_missingPrimary() { // verify assertEquals("test-id", item.getId()); - assertEquals("LOGGED_OUT", item.getAuthenticationState().toString()); + assertEquals("loggedOut", item.getAuthenticatedState().getName()); assertEquals(false, item.isPrimary()); } @Test public void testIdentityItem_isEqualShouldReturnTrue() { - IdentityItem item1 = new IdentityItem("id", AuthenticationState.AMBIGUOUS , false); - IdentityItem item2 = new IdentityItem("id", AuthenticationState.AUTHENTICATED , true); + IdentityItem item1 = new IdentityItem("id", AuthenticatedState.AMBIGUOUS , false); + IdentityItem item2 = new IdentityItem("id", AuthenticatedState.AUTHENTICATED , true); assertTrue(item1.equals(item2)); } @Test public void testIdentityItem_isEqualShouldReturnFalse() { - IdentityItem item1 = new IdentityItem("id", AuthenticationState.AMBIGUOUS , false); - IdentityItem item2 = new IdentityItem("id2", AuthenticationState.AUTHENTICATED , true); + IdentityItem item1 = new IdentityItem("id", AuthenticatedState.AMBIGUOUS , false); + IdentityItem item2 = new IdentityItem("id2", AuthenticatedState.AUTHENTICATED , true); assertFalse(item1.equals(item2)); } diff --git a/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/IdentityMapTests.java b/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/IdentityMapTests.java index c707cb80..62f30408 100644 --- a/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/IdentityMapTests.java +++ b/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/IdentityMapTests.java @@ -280,10 +280,10 @@ public void test_FromData() throws Exception { // verify Map flattenedMap = IdentityEdgeTestUtil.flattenMap(map.asEventData()); assertEquals("randomECID", flattenedMap.get("ECID[0].id")); - assertEquals("AMBIGUOUS", flattenedMap.get("ECID[0].authenticationState")); + assertEquals("AMBIGUOUS", flattenedMap.get("ECID[0].authenticatedState")); assertEquals("true", flattenedMap.get("ECID[0].primary")); assertEquals("someUserID", flattenedMap.get("USERID[0].id")); - assertEquals("AUTHENTICATED", flattenedMap.get("USERID[0].authenticationState")); + assertEquals("AUTHENTICATED", flattenedMap.get("USERID[0].authenticatedState")); assertEquals("false", flattenedMap.get("USERID[0].primary")); } @@ -322,13 +322,13 @@ public void test_FromData_InvalidXDMData() throws Exception { private IdentityMap buildSampleIdentityMap() { // User Login Identity Items - IdentityItem email = new IdentityItem("john@doe", AuthenticationState.AUTHENTICATED, true); - IdentityItem userName = new IdentityItem("John Doe", AuthenticationState.AUTHENTICATED, false); - IdentityItem accountType = new IdentityItem("Student", AuthenticationState.AUTHENTICATED, false); + IdentityItem email = new IdentityItem("john@doe", AuthenticatedState.AUTHENTICATED, true); + IdentityItem userName = new IdentityItem("John Doe", AuthenticatedState.AUTHENTICATED, false); + IdentityItem accountType = new IdentityItem("Student", AuthenticatedState.AUTHENTICATED, false); // User Location Address Identity Items - IdentityItem street = new IdentityItem("280 Highway Lane", AuthenticationState.AMBIGUOUS, false); - IdentityItem state = new IdentityItem("California", AuthenticationState.AMBIGUOUS, false); + IdentityItem street = new IdentityItem("280 Highway Lane", AuthenticatedState.AMBIGUOUS, false); + IdentityItem state = new IdentityItem("California", AuthenticatedState.AMBIGUOUS, false); IdentityMap adobeIdentityMap = new IdentityMap(); adobeIdentityMap.addItem(email,"login"); From 3f4089a2f5b5a62a305c5e327bf12a0b2cc8bd1b Mon Sep 17 00:00:00 2001 From: Emilia Dobrin <33132425+emdobrin@users.noreply.github.com> Date: Mon, 22 Mar 2021 13:57:58 -0700 Subject: [PATCH 016/101] [AMSDK-11140] Renaming to edgeidentity (#17) * [AMSDK-11140] Renaming to edgeidentity rename package to edge.identity rename module to edgeidentity rename to edgeidentity, extension name, class, listeners, constants Rename internal classes to Identity* rename to testApp Renaming in Makefile, readme * Updates after rebase * Review impl - circleci update after renaming --- .circleci/config.yml | 4 +- Makefile | 4 +- README.md | 12 +- code/app/build.gradle | 2 +- code/app/src/main/AndroidManifest.xml | 4 +- .../MainActivity.java | 2 +- .../TestApplication.java} | 10 +- code/app/src/main/res/values/strings.xml | 2 +- .../{identityedge => edgeidentity}/.gitignore | 0 .../build.gradle | 0 .../consumer-rules.pro | 0 .../proguard-rules.pro | 0 .../marketing/mobile/ADBCountDownLatch.java | 0 .../marketing/mobile/MonitorExtension.java | 0 .../adobe/marketing/mobile/TestConstants.java | 0 .../adobe/marketing/mobile/TestHelper.java | 0 .../mobile/TestPersistenceHelper.java | 8 +- .../edge/identity/IdentityPublicAPITest.java} | 15 +- .../src/main/AndroidManifest.xml | 2 +- .../edge/identity}/AuthenticatedState.java | 2 +- .../marketing/mobile/edge/identity}/ECID.java | 4 +- .../mobile/edge/identity/Identity.java} | 54 +++---- .../edge/identity/IdentityConstants.java} | 20 +-- .../edge/identity/IdentityExtension.java} | 92 ++++++------ .../mobile/edge/identity}/IdentityItem.java | 18 +-- .../mobile/edge/identity}/IdentityMap.java | 4 +- .../edge/identity/IdentityProperties.java} | 40 +++--- .../mobile/edge/identity/IdentityState.java} | 50 +++---- .../identity/IdentityStorageService.java} | 30 ++-- .../ListenerEdgeIdentityRemoveIdentity.java} | 24 ++-- .../ListenerEdgeIdentityRequestIdentity.java} | 24 ++-- .../ListenerEdgeIdentityUpdateIdentity.java} | 24 ++-- ...ListenerGenericIdentityRequestContent.java | 18 +-- .../identity}/ListenerHubSharedState.java | 18 +-- .../ListenerIdentityRequestReset.java | 18 +-- .../mobile/edge/identity}/Utils.java | 3 +- .../edge/identity/IdentityTestConstants.java} | 6 +- .../edge/identity/IdentityTestUtil.java} | 18 +-- .../mobile/edge/identity}/ECIDTests.java | 2 +- .../identity/IdentityExtensionTests.java} | 136 +++++++++--------- .../IdentityExtensionVersionTest.java} | 12 +- .../edge/identity}/IdentityItemTests.java | 2 +- .../edge/identity}/IdentityMapTests.java | 4 +- .../identity/IdentityPropertiesTests.java} | 40 +++--- .../edge/identity/IdentityStateTests.java} | 126 ++++++++-------- .../IdentityStorageServiceTests.java} | 58 ++++---- .../mobile/edge/identity/IdentityTests.java} | 74 +++++----- ...tenerEdgeIdentityRemoveIdentityTests.java} | 34 ++--- ...enerEdgeIdentityRequestIdentityTests.java} | 34 ++--- ...tenerEdgeIdentityUpdateIdentityTests.java} | 34 ++--- ...nerGenericIdentityRequestContentTests.java | 30 ++-- .../ListenerHubSharedStateTests.java | 30 ++-- .../ListenerIdentityRequestResetTests.java | 30 ++-- .../mobile/edge/identity}/UtilsTests.java | 4 +- code/gradle.properties | 12 +- code/settings.gradle | 4 +- 56 files changed, 597 insertions(+), 601 deletions(-) rename code/app/src/main/java/com/adobe/marketing/mobile/{identityEdgeTestApp => testApp}/MainActivity.java (94%) rename code/app/src/main/java/com/adobe/marketing/mobile/{identityEdgeTestApp/IdentityEdgeTestApplication.java => testApp/TestApplication.java} (88%) rename code/{identityedge => edgeidentity}/.gitignore (100%) rename code/{identityedge => edgeidentity}/build.gradle (100%) rename code/{identityedge => edgeidentity}/consumer-rules.pro (100%) rename code/{identityedge => edgeidentity}/proguard-rules.pro (100%) rename code/{identityedge => edgeidentity}/src/androidTest/java/com/adobe/marketing/mobile/ADBCountDownLatch.java (100%) rename code/{identityedge => edgeidentity}/src/androidTest/java/com/adobe/marketing/mobile/MonitorExtension.java (100%) rename code/{identityedge => edgeidentity}/src/androidTest/java/com/adobe/marketing/mobile/TestConstants.java (100%) rename code/{identityedge => edgeidentity}/src/androidTest/java/com/adobe/marketing/mobile/TestHelper.java (100%) rename code/{identityedge => edgeidentity}/src/androidTest/java/com/adobe/marketing/mobile/TestPersistenceHelper.java (94%) rename code/{identityedge/src/androidTest/java/com/adobe/marketing/mobile/identityedge/IdentityEdgePublicAPITest.java => edgeidentity/src/androidTest/java/com/adobe/marketing/mobile/edge/identity/IdentityPublicAPITest.java} (83%) rename code/{identityedge => edgeidentity}/src/main/AndroidManifest.xml (58%) rename code/{identityedge/src/main/java/com/adobe/marketing/mobile/identityedge => edgeidentity/src/main/java/com/adobe/marketing/mobile/edge/identity}/AuthenticatedState.java (96%) rename code/{identityedge/src/main/java/com/adobe/marketing/mobile/identityedge => edgeidentity/src/main/java/com/adobe/marketing/mobile/edge/identity}/ECID.java (92%) rename code/{identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdge.java => edgeidentity/src/main/java/com/adobe/marketing/mobile/edge/identity/Identity.java} (80%) rename code/{identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeConstants.java => edgeidentity/src/main/java/com/adobe/marketing/mobile/edge/identity/IdentityConstants.java} (85%) rename code/{identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeExtension.java => edgeidentity/src/main/java/com/adobe/marketing/mobile/edge/identity/IdentityExtension.java} (69%) rename code/{identityedge/src/main/java/com/adobe/marketing/mobile/identityedge => edgeidentity/src/main/java/com/adobe/marketing/mobile/edge/identity}/IdentityItem.java (85%) rename code/{identityedge/src/main/java/com/adobe/marketing/mobile/identityedge => edgeidentity/src/main/java/com/adobe/marketing/mobile/edge/identity}/IdentityMap.java (98%) rename code/{identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeProperties.java => edgeidentity/src/main/java/com/adobe/marketing/mobile/edge/identity/IdentityProperties.java} (81%) rename code/{identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeState.java => edgeidentity/src/main/java/com/adobe/marketing/mobile/edge/identity/IdentityState.java} (70%) rename code/{identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeStorageService.java => edgeidentity/src/main/java/com/adobe/marketing/mobile/edge/identity/IdentityStorageService.java} (82%) rename code/{identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/ListenerIdentityEdgeRemoveIdentity.java => edgeidentity/src/main/java/com/adobe/marketing/mobile/edge/identity/ListenerEdgeIdentityRemoveIdentity.java} (66%) rename code/{identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/ListenerIdentityRequestIdentity.java => edgeidentity/src/main/java/com/adobe/marketing/mobile/edge/identity/ListenerEdgeIdentityRequestIdentity.java} (64%) rename code/{identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/ListenerIdentityEdgeUpdateIdentity.java => edgeidentity/src/main/java/com/adobe/marketing/mobile/edge/identity/ListenerEdgeIdentityUpdateIdentity.java} (66%) rename code/{identityedge/src/main/java/com/adobe/marketing/mobile/identityedge => edgeidentity/src/main/java/com/adobe/marketing/mobile/edge/identity}/ListenerGenericIdentityRequestContent.java (73%) rename code/{identityedge/src/main/java/com/adobe/marketing/mobile/identityedge => edgeidentity/src/main/java/com/adobe/marketing/mobile/edge/identity}/ListenerHubSharedState.java (73%) rename code/{identityedge/src/main/java/com/adobe/marketing/mobile/identityedge => edgeidentity/src/main/java/com/adobe/marketing/mobile/edge/identity}/ListenerIdentityRequestReset.java (73%) rename code/{identityedge/src/main/java/com/adobe/marketing/mobile/identityedge => edgeidentity/src/main/java/com/adobe/marketing/mobile/edge/identity}/Utils.java (98%) rename code/{identityedge/src/sharedTestUtils/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeTestConstants.java => edgeidentity/src/sharedTestUtils/java/com/adobe/marketing/mobile/edge/identity/IdentityTestConstants.java} (87%) rename code/{identityedge/src/sharedTestUtils/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeTestUtil.java => edgeidentity/src/sharedTestUtils/java/com/adobe/marketing/mobile/edge/identity/IdentityTestUtil.java} (91%) rename code/{identityedge/src/test/java/com/adobe/marketing/mobile/identityedge => edgeidentity/src/test/java/com/adobe/marketing/mobile/edge/identity}/ECIDTests.java (98%) rename code/{identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeExtensionTests.java => edgeidentity/src/test/java/com/adobe/marketing/mobile/edge/identity/IdentityExtensionTests.java} (78%) rename code/{identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeExtensionVersionTest.java => edgeidentity/src/test/java/com/adobe/marketing/mobile/edge/identity/IdentityExtensionVersionTest.java} (85%) rename code/{identityedge/src/test/java/com/adobe/marketing/mobile/identityedge => edgeidentity/src/test/java/com/adobe/marketing/mobile/edge/identity}/IdentityItemTests.java (98%) rename code/{identityedge/src/test/java/com/adobe/marketing/mobile/identityedge => edgeidentity/src/test/java/com/adobe/marketing/mobile/edge/identity}/IdentityMapTests.java (98%) rename code/{identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/IdentityEdgePropertiesTests.java => edgeidentity/src/test/java/com/adobe/marketing/mobile/edge/identity/IdentityPropertiesTests.java} (86%) rename code/{identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeStateTests.java => edgeidentity/src/test/java/com/adobe/marketing/mobile/edge/identity/IdentityStateTests.java} (50%) rename code/{identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeStorageServiceTests.java => edgeidentity/src/test/java/com/adobe/marketing/mobile/edge/identity/IdentityStorageServiceTests.java} (59%) rename code/{identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeTests.java => edgeidentity/src/test/java/com/adobe/marketing/mobile/edge/identity/IdentityTests.java} (88%) rename code/{identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/ListenerIdentityEdgeRemoveIdentityTests.java => edgeidentity/src/test/java/com/adobe/marketing/mobile/edge/identity/ListenerEdgeIdentityRemoveIdentityTests.java} (56%) rename code/{identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/ListenerIdentityRequestIdentityTests.java => edgeidentity/src/test/java/com/adobe/marketing/mobile/edge/identity/ListenerEdgeIdentityRequestIdentityTests.java} (56%) rename code/{identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/ListenerIdentityEdgeUpdateIdentityTests.java => edgeidentity/src/test/java/com/adobe/marketing/mobile/edge/identity/ListenerEdgeIdentityUpdateIdentityTests.java} (56%) rename code/{identityedge/src/test/java/com/adobe/marketing/mobile/identityedge => edgeidentity/src/test/java/com/adobe/marketing/mobile/edge/identity}/ListenerGenericIdentityRequestContentTests.java (61%) rename code/{identityedge/src/test/java/com/adobe/marketing/mobile/identityedge => edgeidentity/src/test/java/com/adobe/marketing/mobile/edge/identity}/ListenerHubSharedStateTests.java (62%) rename code/{identityedge/src/test/java/com/adobe/marketing/mobile/identityedge => edgeidentity/src/test/java/com/adobe/marketing/mobile/edge/identity}/ListenerIdentityRequestResetTests.java (62%) rename code/{identityedge/src/test/java/com/adobe/marketing/mobile/identityedge => edgeidentity/src/test/java/com/adobe/marketing/mobile/edge/identity}/UtilsTests.java (98%) diff --git a/.circleci/config.yml b/.circleci/config.yml index 85ceb96c..6f50981f 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -81,5 +81,5 @@ jobs: - checkout - run: name: Bintray Upload - command: code/gradlew -p code/identityedge bintrayUpload -PapiKey=$bintrayAPIKey -PgpgPassphrase=$gpgPassphrase - \ No newline at end of file + command: code/gradlew -p code/edgeidentity bintrayUpload -PapiKey=$bintrayAPIKey -PgpgPassphrase=$gpgPassphrase + diff --git a/Makefile b/Makefile index bdf27e4c..14521c26 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -EXTENSION-LIBRARY-FOLDER-NAME = identityedge +EXTENSION-LIBRARY-FOLDER-NAME = edgeidentity BUILD-ASSEMBLE-LOCATION = ./ci/assemble ROOT_DIR=$(shell git rev-parse --show-toplevel) @@ -58,4 +58,4 @@ ci-publish-main-all: ci-publish: (code/gradlew -p code/${EXTENSION-LIBRARY-FOLDER-NAME} assemblePhone) - (code/gradlew -p code/${EXTENSION-LIBRARY-FOLDER-NAME} bintrayUpload -PapiKey=$(APIKEY)) \ No newline at end of file + (code/gradlew -p code/${EXTENSION-LIBRARY-FOLDER-NAME} bintrayUpload -PapiKey=$(APIKEY)) diff --git a/README.md b/README.md index b46a13c5..dc174ab7 100644 --- a/README.md +++ b/README.md @@ -1,17 +1,17 @@ -# Adobe Experience Platform Identity Edge Mobile Extension +# Adobe Experience Platform Edge Identity Mobile Extension ## About this project -The AEP Identity Edge Mobile Extension is an extension for the [Adobe Experience Platform SDK](https://github.com/Adobe-Marketing-Cloud/acp-sdks) and requires the `MobileCore` extension. +The Adobe Experience Platform Edge Identity is a mobile extension for the [Adobe Experience Platform SDK](https://github.com/Adobe-Marketing-Cloud/acp-sdks) and requires the `MobileCore` extension. This extension enables handling of user identity data from a mobile app when using the AEP Mobile SDK and the Edge Network extension. ### Installation -Integrate the Identity Edge extension into your app by including the following in your gradle file's `dependencies`: +Integrate the Edge Identity extension into your app by including the following in your gradle file's `dependencies`: ``` -implementation 'com.adobe.marketing.mobile:identityedge:1.+' +implementation 'com.adobe.marketing.mobile:edgeidentity:1.+' implementation 'com.adobe.marketing.mobile:edge:1.+' implementation 'com.adobe.marketing.mobile:core:1.+' ``` @@ -31,12 +31,12 @@ Once you opened the project in Android Studio (see above), select the `app` runn Configure a new Assurance session by setting the Base URL to `testapp://main` and launch Assurance in the demo app by running the following command in your terminal: ```bash -$ adb shell am start -W -a android.intent.action.VIEW -d "testapp://main?adb_validation_sessionid=ADD_YOUR_SESSION_ID_HERE" com.adobe.marketing.mobile.identitytestapp +$ adb shell am start -W -a android.intent.action.VIEW -d "testapp://main?adb_validation_sessionid=ADD_YOUR_SESSION_ID_HERE" com.adobe.marketing.mobile.testapp ``` Note: replace ADD_YOUR_SESSION_ID_HERE with your Assurance session identifier. -Once the connection is established and the events list starts getting populated, you can filter the Identity extension events by typing `AEP` in the `Search Events` search box. See full list of available events [here](https://aep-sdks.gitbook.io/docs/beta/experience-platform-extension/experience-platform-debugging#event-types-handled-by-the-aep-mobile-extension). +Once the connection is established and the events list starts getting populated, you can filter the events for this extension by typing `Edge Identity` in the `Search Events` search box. See full list of available events [here](https://aep-sdks.gitbook.io/docs/beta/experience-platform-extension/experience-platform-debugging#event-types-handled-by-the-aep-mobile-extension). diff --git a/code/app/build.gradle b/code/app/build.gradle index 60852f96..cdb6e30e 100644 --- a/code/app/build.gradle +++ b/code/app/build.gradle @@ -25,7 +25,7 @@ dependencies { implementation 'androidx.appcompat:appcompat:1.2.0' implementation 'androidx.constraintlayout:constraintlayout:2.0.4' - implementation project(':identityedge') + implementation project(':edgeidentity') implementation 'com.adobe.marketing.mobile:core:1+' implementation 'com.adobe.marketing.mobile:signal:1+' implementation 'com.adobe.marketing.mobile:edge:1+' diff --git a/code/app/src/main/AndroidManifest.xml b/code/app/src/main/AndroidManifest.xml index a5240406..5bb87b87 100644 --- a/code/app/src/main/AndroidManifest.xml +++ b/code/app/src/main/AndroidManifest.xml @@ -1,12 +1,12 @@ + package="com.adobe.marketing.mobile.testApp"> - Identity Edge Test Application + Edge Identity Test Application \ No newline at end of file diff --git a/code/identityedge/.gitignore b/code/edgeidentity/.gitignore similarity index 100% rename from code/identityedge/.gitignore rename to code/edgeidentity/.gitignore diff --git a/code/identityedge/build.gradle b/code/edgeidentity/build.gradle similarity index 100% rename from code/identityedge/build.gradle rename to code/edgeidentity/build.gradle diff --git a/code/identityedge/consumer-rules.pro b/code/edgeidentity/consumer-rules.pro similarity index 100% rename from code/identityedge/consumer-rules.pro rename to code/edgeidentity/consumer-rules.pro diff --git a/code/identityedge/proguard-rules.pro b/code/edgeidentity/proguard-rules.pro similarity index 100% rename from code/identityedge/proguard-rules.pro rename to code/edgeidentity/proguard-rules.pro diff --git a/code/identityedge/src/androidTest/java/com/adobe/marketing/mobile/ADBCountDownLatch.java b/code/edgeidentity/src/androidTest/java/com/adobe/marketing/mobile/ADBCountDownLatch.java similarity index 100% rename from code/identityedge/src/androidTest/java/com/adobe/marketing/mobile/ADBCountDownLatch.java rename to code/edgeidentity/src/androidTest/java/com/adobe/marketing/mobile/ADBCountDownLatch.java diff --git a/code/identityedge/src/androidTest/java/com/adobe/marketing/mobile/MonitorExtension.java b/code/edgeidentity/src/androidTest/java/com/adobe/marketing/mobile/MonitorExtension.java similarity index 100% rename from code/identityedge/src/androidTest/java/com/adobe/marketing/mobile/MonitorExtension.java rename to code/edgeidentity/src/androidTest/java/com/adobe/marketing/mobile/MonitorExtension.java diff --git a/code/identityedge/src/androidTest/java/com/adobe/marketing/mobile/TestConstants.java b/code/edgeidentity/src/androidTest/java/com/adobe/marketing/mobile/TestConstants.java similarity index 100% rename from code/identityedge/src/androidTest/java/com/adobe/marketing/mobile/TestConstants.java rename to code/edgeidentity/src/androidTest/java/com/adobe/marketing/mobile/TestConstants.java diff --git a/code/identityedge/src/androidTest/java/com/adobe/marketing/mobile/TestHelper.java b/code/edgeidentity/src/androidTest/java/com/adobe/marketing/mobile/TestHelper.java similarity index 100% rename from code/identityedge/src/androidTest/java/com/adobe/marketing/mobile/TestHelper.java rename to code/edgeidentity/src/androidTest/java/com/adobe/marketing/mobile/TestHelper.java diff --git a/code/identityedge/src/androidTest/java/com/adobe/marketing/mobile/TestPersistenceHelper.java b/code/edgeidentity/src/androidTest/java/com/adobe/marketing/mobile/TestPersistenceHelper.java similarity index 94% rename from code/identityedge/src/androidTest/java/com/adobe/marketing/mobile/TestPersistenceHelper.java rename to code/edgeidentity/src/androidTest/java/com/adobe/marketing/mobile/TestPersistenceHelper.java index bf1040d9..c7cd8ba6 100644 --- a/code/identityedge/src/androidTest/java/com/adobe/marketing/mobile/TestPersistenceHelper.java +++ b/code/edgeidentity/src/androidTest/java/com/adobe/marketing/mobile/TestPersistenceHelper.java @@ -16,20 +16,20 @@ import android.content.SharedPreferences; -import com.adobe.marketing.mobile.identityedge.IdentityEdgeTestConstants; +import com.adobe.marketing.mobile.edge.identity.IdentityTestConstants; import java.util.ArrayList; import static org.junit.Assert.fail; /** - * Helper class to update and remove persisted data to extension concerned with testing IdentityEdge. + * Helper class to update and remove persisted data to extension concerned with testing Identity. */ public class TestPersistenceHelper { private static ArrayList knownDatastoreName = new ArrayList() {{ - add(IdentityEdgeTestConstants.DataStoreKey.IDENTITYEDGE_DATASTORE); - add(IdentityEdgeTestConstants.DataStoreKey.CONFIG_DATASTORE); + add(IdentityTestConstants.DataStoreKey.IDENTITY_DATASTORE); + add(IdentityTestConstants.DataStoreKey.CONFIG_DATASTORE); }}; /** diff --git a/code/identityedge/src/androidTest/java/com/adobe/marketing/mobile/identityedge/IdentityEdgePublicAPITest.java b/code/edgeidentity/src/androidTest/java/com/adobe/marketing/mobile/edge/identity/IdentityPublicAPITest.java similarity index 83% rename from code/identityedge/src/androidTest/java/com/adobe/marketing/mobile/identityedge/IdentityEdgePublicAPITest.java rename to code/edgeidentity/src/androidTest/java/com/adobe/marketing/mobile/edge/identity/IdentityPublicAPITest.java index 0aa9eebb..7e7ce55c 100644 --- a/code/identityedge/src/androidTest/java/com/adobe/marketing/mobile/identityedge/IdentityEdgePublicAPITest.java +++ b/code/edgeidentity/src/androidTest/java/com/adobe/marketing/mobile/edge/identity/IdentityPublicAPITest.java @@ -9,7 +9,7 @@ governing permissions and limitations under the License. */ -package com.adobe.marketing.mobile.identityedge; +package com.adobe.marketing.mobile.edge.identity; import androidx.test.ext.junit.runners.AndroidJUnit4; @@ -23,17 +23,16 @@ import org.junit.rules.RuleChain; import org.junit.runner.RunWith; -import java.util.HashMap; import java.util.Map; import java.util.concurrent.CountDownLatch; import static com.adobe.marketing.mobile.TestHelper.getSharedStateFor; import static com.adobe.marketing.mobile.TestHelper.resetTestExpectations; -import static com.adobe.marketing.mobile.identityedge.IdentityEdgeTestUtil.flattenMap; +import static com.adobe.marketing.mobile.edge.identity.IdentityTestUtil.*; import static org.junit.Assert.assertEquals; @RunWith(AndroidJUnit4.class) -public class IdentityEdgePublicAPITest { +public class IdentityPublicAPITest { @Rule public RuleChain rule = RuleChain.outerRule(new TestHelper.SetupCoreRule()) @@ -46,7 +45,7 @@ public class IdentityEdgePublicAPITest { @Before public void setup() throws Exception { - IdentityEdge.registerExtension(); + Identity.registerExtension(); final CountDownLatch latch = new CountDownLatch(1); MobileCore.start(new AdobeCallback() { @@ -65,7 +64,7 @@ public void call(Object o) { // -------------------------------------------------------------------------------------------- @Test public void testGetExtensionVersionAPI() { - assertEquals(IdentityEdgeConstants.EXTENSION_VERSION, IdentityEdge.extensionVersion()); + assertEquals(IdentityConstants.EXTENSION_VERSION, Identity.extensionVersion()); } // -------------------------------------------------------------------------------------------- @@ -77,7 +76,7 @@ public void testRegisterExtensionAPI() throws InterruptedException { // Consent.registerExtension() is called in the setup method // verify that the extension is registered with the correct version details - Map sharedStateMap = flattenMap(getSharedStateFor(IdentityEdgeTestConstants.SharedStateName.EVENT_HUB, 1000)); - assertEquals(IdentityEdgeConstants.EXTENSION_VERSION, sharedStateMap.get("extensions.com.adobe.identityedge.version")); + Map sharedStateMap = flattenMap(getSharedStateFor(IdentityTestConstants.SharedStateName.EVENT_HUB, 1000)); + assertEquals(IdentityConstants.EXTENSION_VERSION, sharedStateMap.get("extensions.com.adobe.edge.identity.version")); } } diff --git a/code/identityedge/src/main/AndroidManifest.xml b/code/edgeidentity/src/main/AndroidManifest.xml similarity index 58% rename from code/identityedge/src/main/AndroidManifest.xml rename to code/edgeidentity/src/main/AndroidManifest.xml index 08ba300c..653c8ae3 100644 --- a/code/identityedge/src/main/AndroidManifest.xml +++ b/code/edgeidentity/src/main/AndroidManifest.xml @@ -1,3 +1,3 @@ + package="com.adobe.marketing.mobile.edge.identity"> \ No newline at end of file diff --git a/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/AuthenticatedState.java b/code/edgeidentity/src/main/java/com/adobe/marketing/mobile/edge/identity/AuthenticatedState.java similarity index 96% rename from code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/AuthenticatedState.java rename to code/edgeidentity/src/main/java/com/adobe/marketing/mobile/edge/identity/AuthenticatedState.java index f6773eb4..76a2b38f 100644 --- a/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/AuthenticatedState.java +++ b/code/edgeidentity/src/main/java/com/adobe/marketing/mobile/edge/identity/AuthenticatedState.java @@ -9,7 +9,7 @@ governing permissions and limitations under the License. */ -package com.adobe.marketing.mobile.identityedge; +package com.adobe.marketing.mobile.edge.identity; /** * Represents the authentication state for an {@link IdentityItem} diff --git a/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/ECID.java b/code/edgeidentity/src/main/java/com/adobe/marketing/mobile/edge/identity/ECID.java similarity index 92% rename from code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/ECID.java rename to code/edgeidentity/src/main/java/com/adobe/marketing/mobile/edge/identity/ECID.java index ae48bace..66d7e693 100644 --- a/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/ECID.java +++ b/code/edgeidentity/src/main/java/com/adobe/marketing/mobile/edge/identity/ECID.java @@ -9,7 +9,7 @@ governing permissions and limitations under the License. */ -package com.adobe.marketing.mobile.identityedge; +package com.adobe.marketing.mobile.edge.identity; import com.adobe.marketing.mobile.LoggingMode; import com.adobe.marketing.mobile.MobileCore; @@ -41,7 +41,7 @@ final class ECID { */ ECID(final String ecidString) { if (Utils.isNullOrEmpty(ecidString)) { - MobileCore.log(LoggingMode.DEBUG, IdentityEdgeConstants.LOG_TAG, "Creating an ECID with null or empty ecidString is not allowed, generating a new ECID."); + MobileCore.log(LoggingMode.DEBUG, IdentityConstants.LOG_TAG, "Creating an ECID with null or empty ecidString is not allowed, generating a new ECID."); this.ecidString = new ECID().toString(); return; } diff --git a/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdge.java b/code/edgeidentity/src/main/java/com/adobe/marketing/mobile/edge/identity/Identity.java similarity index 80% rename from code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdge.java rename to code/edgeidentity/src/main/java/com/adobe/marketing/mobile/edge/identity/Identity.java index ce83ef48..3ee00d3e 100644 --- a/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdge.java +++ b/code/edgeidentity/src/main/java/com/adobe/marketing/mobile/edge/identity/Identity.java @@ -9,7 +9,7 @@ governing permissions and limitations under the License. */ -package com.adobe.marketing.mobile.identityedge; +package com.adobe.marketing.mobile.edge.identity; import com.adobe.marketing.mobile.AdobeCallback; import com.adobe.marketing.mobile.AdobeCallbackWithError; @@ -23,29 +23,29 @@ import java.util.List; -public class IdentityEdge { - private static final String LOG_TAG = "IdentityEdge"; +public class Identity { + private static final String LOG_TAG = "Identity"; - private IdentityEdge() {} + private Identity() {} /** - * Returns the version of the {@link IdentityEdge} extension + * Returns the version of the {@link Identity} extension * * @return The version as {@code String} */ public static String extensionVersion() { - return IdentityEdgeConstants.EXTENSION_VERSION; + return IdentityConstants.EXTENSION_VERSION; } /** * Registers the extension with the Mobile SDK. This method should be called only once in your application class. */ public static void registerExtension() { - MobileCore.registerExtension(IdentityEdgeExtension.class, new ExtensionErrorCallback() { + MobileCore.registerExtension(IdentityExtension.class, new ExtensionErrorCallback() { @Override public void error(ExtensionError extensionError) { MobileCore.log(LoggingMode.ERROR, LOG_TAG, - "There was an error registering the Identity Edge extension: " + extensionError.getErrorName()); + "There was an error registering the Edge Identity extension: " + extensionError.getErrorName()); } }); } @@ -63,15 +63,15 @@ public static void getExperienceCloudId(final AdobeCallback callback) { return; } - final Event event = new Event.Builder(IdentityEdgeConstants.EventNames.IDENTITY_REQUEST_IDENTITY_ECID, - IdentityEdgeConstants.EventType.EDGE_IDENTITY, - IdentityEdgeConstants.EventSource.REQUEST_IDENTITY).build(); + final Event event = new Event.Builder(IdentityConstants.EventNames.IDENTITY_REQUEST_IDENTITY_ECID, + IdentityConstants.EventType.EDGE_IDENTITY, + IdentityConstants.EventSource.REQUEST_IDENTITY).build(); final ExtensionErrorCallback errorCallback = new ExtensionErrorCallback() { @Override public void error(final ExtensionError extensionError) { returnError(callback, extensionError); - MobileCore.log(LoggingMode.DEBUG, LOG_TAG, String.format("Failed to dispatch %s event: Error : %s.", IdentityEdgeConstants.EventNames.IDENTITY_REQUEST_IDENTITY_ECID, + MobileCore.log(LoggingMode.DEBUG, LOG_TAG, String.format("Failed to dispatch %s event: Error : %s.", IdentityConstants.EventNames.IDENTITY_REQUEST_IDENTITY_ECID, extensionError.getErrorName())); } }; @@ -91,7 +91,7 @@ public void call(Event responseEvent) { return; } - final List ecidItems = identityMap.getIdentityItemsForNamespace(IdentityEdgeConstants.Namespaces.ECID); + final List ecidItems = identityMap.getIdentityItemsForNamespace(IdentityConstants.Namespaces.ECID); if (ecidItems == null || ecidItems.isEmpty() || ecidItems.get(0).getId() == null) { callback.call(""); } else { @@ -104,7 +104,7 @@ public void call(Event responseEvent) { /** * Updates the currently known {@link IdentityMap} within the SDK and XDM shared state. - * The IdentityEdge extension will merge the received identifiers with the previously saved one in an additive manner, no identifiers will be removed using this API. + * The Identity extension will merge the received identifiers with the previously saved one in an additive manner, no identifiers will be removed using this API. * * @param identityMap The identifiers to add or update. */ @@ -117,20 +117,20 @@ public static void updateIdentities(final IdentityMap identityMap) { final ExtensionErrorCallback errorCallback = new ExtensionErrorCallback() { @Override public void error(final ExtensionError extensionError) { - MobileCore.log(LoggingMode.DEBUG, LOG_TAG, String.format("Update Identities API. Failed to dispatch %s event: Error : %s.", IdentityEdgeConstants.EventNames.UPDATE_IDENTITIES, + MobileCore.log(LoggingMode.DEBUG, LOG_TAG, String.format("Update Identities API. Failed to dispatch %s event: Error : %s.", IdentityConstants.EventNames.UPDATE_IDENTITIES, extensionError.getErrorName())); } }; - final Event updateIdentitiesEvent = new Event.Builder(IdentityEdgeConstants.EventNames.UPDATE_IDENTITIES, - IdentityEdgeConstants.EventType.EDGE_IDENTITY, - IdentityEdgeConstants.EventSource.UPDATE_IDENTITY).setEventData(identityMap.asEventData()).build(); + final Event updateIdentitiesEvent = new Event.Builder(IdentityConstants.EventNames.UPDATE_IDENTITIES, + IdentityConstants.EventType.EDGE_IDENTITY, + IdentityConstants.EventSource.UPDATE_IDENTITY).setEventData(identityMap.asEventData()).build(); MobileCore.dispatchEvent(updateIdentitiesEvent, errorCallback); } /** - * Removes the identity from the stored client-side {@link IdentityMap} and XDM shared state. The IdentityEdge extension will stop sending this identifier. + * Removes the identity from the stored client-side {@link IdentityMap} and XDM shared state. The Identity extension will stop sending this identifier. * This does not clear the identifier from the User Profile Graph. * Identifiers which have an empty `id` or empty `namespace` are not allowed and are ignored. * @@ -154,15 +154,15 @@ public static void removeIdentity(final IdentityItem item, final String namespac final ExtensionErrorCallback errorCallback = new ExtensionErrorCallback() { @Override public void error(final ExtensionError extensionError) { - MobileCore.log(LoggingMode.DEBUG, LOG_TAG, String.format("removeIdentity API. Failed to dispatch %s event: Error : %s.", IdentityEdgeConstants.EventNames.REMOVE_IDENTITIES, + MobileCore.log(LoggingMode.DEBUG, LOG_TAG, String.format("removeIdentity API. Failed to dispatch %s event: Error : %s.", IdentityConstants.EventNames.REMOVE_IDENTITIES, extensionError.getErrorName())); } }; - final Event removeIdentitiesEvent = new Event.Builder(IdentityEdgeConstants.EventNames.REMOVE_IDENTITIES, - IdentityEdgeConstants.EventType.EDGE_IDENTITY, - IdentityEdgeConstants.EventSource.REMOVE_IDENTITY).setEventData(identityMap.asEventData()).build(); + final Event removeIdentitiesEvent = new Event.Builder(IdentityConstants.EventNames.REMOVE_IDENTITIES, + IdentityConstants.EventType.EDGE_IDENTITY, + IdentityConstants.EventSource.REMOVE_IDENTITY).setEventData(identityMap.asEventData()).build(); MobileCore.dispatchEvent(removeIdentitiesEvent, errorCallback); } @@ -179,15 +179,15 @@ public static void getIdentities(final AdobeCallback callback) { return; } - final Event event = new Event.Builder(IdentityEdgeConstants.EventNames.REQUEST_IDENTITIES, - IdentityEdgeConstants.EventType.EDGE_IDENTITY, - IdentityEdgeConstants.EventSource.REQUEST_IDENTITY).build(); + final Event event = new Event.Builder(IdentityConstants.EventNames.REQUEST_IDENTITIES, + IdentityConstants.EventType.EDGE_IDENTITY, + IdentityConstants.EventSource.REQUEST_IDENTITY).build(); final ExtensionErrorCallback errorCallback = new ExtensionErrorCallback() { @Override public void error(final ExtensionError extensionError) { returnError(callback, extensionError); - MobileCore.log(LoggingMode.DEBUG, LOG_TAG, String.format("Failed to dispatch %s event: Error : %s.", IdentityEdgeConstants.EventNames.REQUEST_IDENTITIES, + MobileCore.log(LoggingMode.DEBUG, LOG_TAG, String.format("Failed to dispatch %s event: Error : %s.", IdentityConstants.EventNames.REQUEST_IDENTITIES, extensionError.getErrorName())); } }; diff --git a/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeConstants.java b/code/edgeidentity/src/main/java/com/adobe/marketing/mobile/edge/identity/IdentityConstants.java similarity index 85% rename from code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeConstants.java rename to code/edgeidentity/src/main/java/com/adobe/marketing/mobile/edge/identity/IdentityConstants.java index 47010aa1..0331b5d2 100644 --- a/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeConstants.java +++ b/code/edgeidentity/src/main/java/com/adobe/marketing/mobile/edge/identity/IdentityConstants.java @@ -9,12 +9,12 @@ governing permissions and limitations under the License. */ -package com.adobe.marketing.mobile.identityedge; +package com.adobe.marketing.mobile.edge.identity; -class IdentityEdgeConstants { +class IdentityConstants { - static final String LOG_TAG = "IdentityEdge"; - static final String EXTENSION_NAME = "com.adobe.identityedge"; + static final String LOG_TAG = "EdgeIdentity"; + static final String EXTENSION_NAME = "com.adobe.edge.identity"; static final String EXTENSION_VERSION = "1.0.0-alpha-1"; @@ -39,11 +39,11 @@ private EventType() { } } final class EventNames { - static final String IDENTITY_REQUEST_IDENTITY_ECID = "Identity Edge Request ECID"; - static final String IDENTITY_RESPONSE_CONTENT_ONE_TIME = "Identity Edge Response Content One Time"; - static final String UPDATE_IDENTITIES = "Identity Edge Update Identities"; - static final String REMOVE_IDENTITIES = "Identity Edge Remove Identities"; - static final String REQUEST_IDENTITIES = "Identity Edge Request Identities"; + static final String IDENTITY_REQUEST_IDENTITY_ECID = "Edge Identity Request ECID"; + static final String IDENTITY_RESPONSE_CONTENT_ONE_TIME = "Edge Identity Response Content One Time"; + static final String UPDATE_IDENTITIES = "Edge Identity Update Identities"; + static final String REMOVE_IDENTITIES = "Edge Identity Remove Identities"; + static final String REQUEST_IDENTITIES = "Edge Identity Request Identities"; static final String RESET_IDENTITIES_COMPLETE = "Edge Identity Reset Identities Complete"; private EventNames() { } } @@ -82,5 +82,5 @@ final class DataStoreKey { private DataStoreKey() { } } - private IdentityEdgeConstants() {} + private IdentityConstants() {} } diff --git a/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeExtension.java b/code/edgeidentity/src/main/java/com/adobe/marketing/mobile/edge/identity/IdentityExtension.java similarity index 69% rename from code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeExtension.java rename to code/edgeidentity/src/main/java/com/adobe/marketing/mobile/edge/identity/IdentityExtension.java index 9949e660..e8480ffb 100644 --- a/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeExtension.java +++ b/code/edgeidentity/src/main/java/com/adobe/marketing/mobile/edge/identity/IdentityExtension.java @@ -9,7 +9,7 @@ governing permissions and limitations under the License. */ -package com.adobe.marketing.mobile.identityedge; +package com.adobe.marketing.mobile.edge.identity; import com.adobe.marketing.mobile.Event; import com.adobe.marketing.mobile.Extension; @@ -22,9 +22,9 @@ import java.util.Map; -class IdentityEdgeExtension extends Extension { - private static final String LOG_TAG = "IdentityEdgeExtension"; - private final IdentityEdgeState state = new IdentityEdgeState(new IdentityEdgeProperties()); +class IdentityExtension extends Extension { + private static final String LOG_TAG = "IdentityExtension"; + private final IdentityState state = new IdentityState(new IdentityProperties()); /** * Constructor. @@ -33,42 +33,42 @@ class IdentityEdgeExtension extends Extension { * Called during the Identity extension's registration. * The following listeners are registered during this extension's registration. *
      - *
    • Listener {@link ListenerIdentityRequestIdentity} to listen for event with eventType {@link IdentityEdgeConstants.EventType#EDGE_IDENTITY} - * and EventSource {@link IdentityEdgeConstants.EventSource#REQUEST_IDENTITY}
    • - *
    • Listener {@link ListenerGenericIdentityRequestContent} to listen for event with eventType {@link IdentityEdgeConstants.EventType#GENERIC_IDENTITY} - * and EventSource {@link IdentityEdgeConstants.EventSource#REQUEST_CONTENT}
    • - *
    • Listener {@link ListenerIdentityEdgeUpdateIdentity} to listen for event with eventType {@link IdentityEdgeConstants.EventType#EDGE_IDENTITY} - * and EventSource {@link IdentityEdgeConstants.EventSource#UPDATE_IDENTITY}
    • - *
    • Listener {@link ListenerIdentityEdgeRemoveIdentity} to listen for event with eventType {@link IdentityEdgeConstants.EventType#EDGE_IDENTITY} - * and EventSource {@link IdentityEdgeConstants.EventSource#REMOVE_IDENTITY}
    • - *
    • Listener {@link ListenerIdentityRequestReset} to listen for event with eventType {@link IdentityEdgeConstants.EventType#EDGE_IDENTITY} - * and EventSource {@link IdentityEdgeConstants.EventSource#REQUEST_CONTENT}
    • - *
    • Listener {@link ListenerIdentityRequestReset} to listen for event with eventType {@link IdentityEdgeConstants.EventType#GENERIC_IDENTITY} - * and EventSource {@link IdentityEdgeConstants.EventSource#REQUEST_RESET}
    • - *
    • Listener {@link ListenerHubSharedState} to listen for event with eventType {@link IdentityEdgeConstants.EventType#HUB} - * and EventSource {@link IdentityEdgeConstants.EventSource#SHARED_STATE}
    • + *
    • Listener {@link ListenerEdgeIdentityRequestIdentity} to listen for event with eventType {@link IdentityConstants.EventType#EDGE_IDENTITY} + * and EventSource {@link IdentityConstants.EventSource#REQUEST_IDENTITY}
    • + *
    • Listener {@link ListenerGenericIdentityRequestContent} to listen for event with eventType {@link IdentityConstants.EventType#GENERIC_IDENTITY} + * and EventSource {@link IdentityConstants.EventSource#REQUEST_CONTENT}
    • + *
    • Listener {@link ListenerEdgeIdentityUpdateIdentity} to listen for event with eventType {@link IdentityConstants.EventType#EDGE_IDENTITY} + * and EventSource {@link IdentityConstants.EventSource#UPDATE_IDENTITY}
    • + *
    • Listener {@link ListenerEdgeIdentityRemoveIdentity} to listen for event with eventType {@link IdentityConstants.EventType#EDGE_IDENTITY} + * and EventSource {@link IdentityConstants.EventSource#REMOVE_IDENTITY}
    • + *
    • Listener {@link ListenerIdentityRequestReset} to listen for event with eventType {@link IdentityConstants.EventType#EDGE_IDENTITY} + * and EventSource {@link IdentityConstants.EventSource#REQUEST_CONTENT}
    • + *
    • Listener {@link ListenerIdentityRequestReset} to listen for event with eventType {@link IdentityConstants.EventType#GENERIC_IDENTITY} + * and EventSource {@link IdentityConstants.EventSource#REQUEST_RESET}
    • + *
    • Listener {@link ListenerHubSharedState} to listen for event with eventType {@link IdentityConstants.EventType#HUB} + * and EventSource {@link IdentityConstants.EventSource#SHARED_STATE}
    • *
    *

    * Thread : Background thread created by MobileCore * * @param extensionApi {@link ExtensionApi} instance */ - protected IdentityEdgeExtension(ExtensionApi extensionApi) { + protected IdentityExtension(ExtensionApi extensionApi) { super(extensionApi); ExtensionErrorCallback listenerErrorCallback = new ExtensionErrorCallback() { @Override public void error(final ExtensionError extensionError) { - MobileCore.log(LoggingMode.ERROR, IdentityEdgeConstants.LOG_TAG, String.format("Failed to register listener, error: %s", + MobileCore.log(LoggingMode.ERROR, IdentityConstants.LOG_TAG, String.format("Failed to register listener, error: %s", extensionError.getErrorName())); } }; - extensionApi.registerEventListener(IdentityEdgeConstants.EventType.EDGE_IDENTITY, IdentityEdgeConstants.EventSource.REQUEST_IDENTITY, ListenerIdentityRequestIdentity.class, listenerErrorCallback); - extensionApi.registerEventListener(IdentityEdgeConstants.EventType.GENERIC_IDENTITY, IdentityEdgeConstants.EventSource.REQUEST_CONTENT, ListenerGenericIdentityRequestContent.class, listenerErrorCallback); - extensionApi.registerEventListener(IdentityEdgeConstants.EventType.EDGE_IDENTITY, IdentityEdgeConstants.EventSource.UPDATE_IDENTITY, ListenerIdentityEdgeUpdateIdentity.class, listenerErrorCallback); - extensionApi.registerEventListener(IdentityEdgeConstants.EventType.EDGE_IDENTITY, IdentityEdgeConstants.EventSource.REMOVE_IDENTITY, ListenerIdentityEdgeRemoveIdentity.class, listenerErrorCallback); - extensionApi.registerEventListener(IdentityEdgeConstants.EventType.HUB, IdentityEdgeConstants.EventSource.SHARED_STATE, ListenerHubSharedState.class, listenerErrorCallback); - extensionApi.registerEventListener(IdentityEdgeConstants.EventType.GENERIC_IDENTITY, IdentityEdgeConstants.EventSource.REQUEST_RESET, ListenerIdentityRequestReset.class, listenerErrorCallback); + extensionApi.registerEventListener(IdentityConstants.EventType.EDGE_IDENTITY, IdentityConstants.EventSource.REQUEST_IDENTITY, ListenerEdgeIdentityRequestIdentity.class, listenerErrorCallback); + extensionApi.registerEventListener(IdentityConstants.EventType.GENERIC_IDENTITY, IdentityConstants.EventSource.REQUEST_CONTENT, ListenerGenericIdentityRequestContent.class, listenerErrorCallback); + extensionApi.registerEventListener(IdentityConstants.EventType.EDGE_IDENTITY, IdentityConstants.EventSource.UPDATE_IDENTITY, ListenerEdgeIdentityUpdateIdentity.class, listenerErrorCallback); + extensionApi.registerEventListener(IdentityConstants.EventType.EDGE_IDENTITY, IdentityConstants.EventSource.REMOVE_IDENTITY, ListenerEdgeIdentityRemoveIdentity.class, listenerErrorCallback); + extensionApi.registerEventListener(IdentityConstants.EventType.HUB, IdentityConstants.EventSource.SHARED_STATE, ListenerHubSharedState.class, listenerErrorCallback); + extensionApi.registerEventListener(IdentityConstants.EventType.GENERIC_IDENTITY, IdentityConstants.EventSource.REQUEST_RESET, ListenerIdentityRequestReset.class, listenerErrorCallback); } /** @@ -78,7 +78,7 @@ public void error(final ExtensionError extensionError) { */ @Override protected String getName() { - return IdentityEdgeConstants.EXTENSION_NAME; + return IdentityConstants.EXTENSION_NAME; } /** @@ -88,7 +88,7 @@ protected String getName() { */ @Override protected String getVersion() { - return IdentityEdgeConstants.EXTENSION_VERSION; + return IdentityConstants.EXTENSION_VERSION; } /** @@ -155,8 +155,8 @@ void handleHubSharedState(final Event event) { } try { - final String stateOwner = (String) event.getEventData().get(IdentityEdgeConstants.EventDataKeys.STATE_OWNER); - if (!IdentityEdgeConstants.SharedStateKeys.IDENTITY_DIRECT.equals(stateOwner)) { + final String stateOwner = (String) event.getEventData().get(IdentityConstants.EventDataKeys.STATE_OWNER); + if (!IdentityConstants.SharedStateKeys.IDENTITY_DIRECT.equals(stateOwner)) { return; } } catch (ClassCastException e) { @@ -165,7 +165,7 @@ void handleHubSharedState(final Event event) { } - final Map identityState = getSharedState(IdentityEdgeConstants.SharedStateKeys.IDENTITY_DIRECT, event); + final Map identityState = getSharedState(IdentityConstants.SharedStateKeys.IDENTITY_DIRECT, event); if (identityState == null) { MobileCore.log(LoggingMode.DEBUG, LOG_TAG, "Could not process direct Identity shared state change event, Identity shared state is null"); @@ -173,7 +173,7 @@ void handleHubSharedState(final Event event) { } try { - final String legacyEcidString = (String) identityState.get(IdentityEdgeConstants.EventDataKeys.VISITOR_ID_ECID); + final String legacyEcidString = (String) identityState.get(IdentityConstants.EventDataKeys.VISITOR_ID_ECID); final ECID legacyEcid = legacyEcidString == null ? null : new ECID(legacyEcidString); if (state.updateLegacyExperienceCloudId(legacyEcid)) { @@ -195,17 +195,17 @@ void handleIdentityRequest(final Event event) { return; } - Map xdmData = state.getIdentityEdgeProperties().toXDMData(true); - Event responseEvent = new Event.Builder(IdentityEdgeConstants.EventNames.IDENTITY_RESPONSE_CONTENT_ONE_TIME, - IdentityEdgeConstants.EventType.EDGE_IDENTITY, - IdentityEdgeConstants.EventSource.RESPONSE_IDENTITY) + Map xdmData = state.getIdentityProperties().toXDMData(true); + Event responseEvent = new Event.Builder(IdentityConstants.EventNames.IDENTITY_RESPONSE_CONTENT_ONE_TIME, + IdentityConstants.EventType.EDGE_IDENTITY, + IdentityConstants.EventSource.RESPONSE_IDENTITY) .setEventData(xdmData) .build(); MobileCore.dispatchResponseEvent(responseEvent, event, new ExtensionErrorCallback() { @Override public void error(ExtensionError extensionError) { - MobileCore.log(LoggingMode.DEBUG, LOG_TAG, "Failed to dispatch Identity Edge response event for event " + + MobileCore.log(LoggingMode.DEBUG, LOG_TAG, "Failed to dispatch Edge Identity response event for event " + event.getUniqueIdentifier() + " with error " + extensionError.getErrorName()); @@ -214,7 +214,7 @@ public void error(ExtensionError extensionError) { } /** - * Handles IdentityEdge request reset events. + * Handles Edge Identity request reset events. * * @param event the identity request reset {@link Event} */ @@ -249,7 +249,7 @@ public void error(final ExtensionError extensionError) { } /** - * Fetches the latest Identity Edge properties and shares the EdgeIdentity's XDMSharedState. + * Fetches the latest Identity properties and shares the XDMSharedState. * * @param event the {@link Event} that triggered the XDM shared state change */ @@ -268,17 +268,17 @@ public void error(final ExtensionError extensionError) { } }; - extensionApi.setXDMSharedEventState(state.getIdentityEdgeProperties().toXDMData(false), event, errorCallback); + extensionApi.setXDMSharedEventState(state.getIdentityProperties().toXDMData(false), event, errorCallback); // dispatch response event - final Event responseEvent = new Event.Builder(IdentityEdgeConstants.EventNames.RESET_IDENTITIES_COMPLETE, - IdentityEdgeConstants.EventType.EDGE_IDENTITY, - IdentityEdgeConstants.EventSource.RESET_COMPLETE).build(); + final Event responseEvent = new Event.Builder(IdentityConstants.EventNames.RESET_IDENTITIES_COMPLETE, + IdentityConstants.EventType.EDGE_IDENTITY, + IdentityConstants.EventSource.RESET_COMPLETE).build(); MobileCore.dispatchEvent(responseEvent, new ExtensionErrorCallback() { @Override public void error(ExtensionError extensionError) { - MobileCore.log(LoggingMode.DEBUG, LOG_TAG, "Failed to dispatch Identity Edge reset response event for event " + + MobileCore.log(LoggingMode.DEBUG, LOG_TAG, "Failed to dispatch Edge Identity reset response event for event " + event.getUniqueIdentifier() + " with error " + extensionError.getErrorName()); @@ -287,7 +287,7 @@ public void error(ExtensionError extensionError) { } /** - * Determines if Identity Edge is ready to handle events, this is determined by if the Identity Edge extension has booted up + * Determines if this Identity is ready to handle events, this is determined by if the extension has booted up * * @return True if we can process events, false otherwise */ @@ -303,7 +303,7 @@ private boolean canProcessEvents() { } if (state.bootupIfReady()) { - extensionApi.setXDMSharedEventState(state.getIdentityEdgeProperties().toXDMData(false), null, null); + extensionApi.setXDMSharedEventState(state.getIdentityProperties().toXDMData(false), null, null); return true; } diff --git a/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityItem.java b/code/edgeidentity/src/main/java/com/adobe/marketing/mobile/edge/identity/IdentityItem.java similarity index 85% rename from code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityItem.java rename to code/edgeidentity/src/main/java/com/adobe/marketing/mobile/edge/identity/IdentityItem.java index 7dc845ff..fc43680a 100644 --- a/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityItem.java +++ b/code/edgeidentity/src/main/java/com/adobe/marketing/mobile/edge/identity/IdentityItem.java @@ -9,7 +9,7 @@ governing permissions and limitations under the License. */ -package com.adobe.marketing.mobile.identityedge; +package com.adobe.marketing.mobile.edge.identity; import com.adobe.marketing.mobile.LoggingMode; import com.adobe.marketing.mobile.MobileCore; @@ -69,16 +69,16 @@ public IdentityItem(final IdentityItem item) { Map toObjectMap() { Map map = new HashMap<>(); if (id != null) { - map.put(IdentityEdgeConstants.XDMKeys.ID, id); + map.put(IdentityConstants.XDMKeys.ID, id); } if (authenticatedState != null) { - map.put(IdentityEdgeConstants.XDMKeys.AUTHENTICATED_STATE, authenticatedState.getName()); + map.put(IdentityConstants.XDMKeys.AUTHENTICATED_STATE, authenticatedState.getName()); } else { - map.put(IdentityEdgeConstants.XDMKeys.AUTHENTICATED_STATE, AuthenticatedState.AMBIGUOUS.getName()); + map.put(IdentityConstants.XDMKeys.AUTHENTICATED_STATE, AuthenticatedState.AMBIGUOUS.getName()); } - map.put(IdentityEdgeConstants.XDMKeys.PRIMARY, primary); + map.put(IdentityConstants.XDMKeys.PRIMARY, primary); return map; } @@ -112,15 +112,15 @@ static IdentityItem fromData(final Map data) { if (data == null) { return null; } try { - final String id = (String) data.get(IdentityEdgeConstants.XDMKeys.ID); - AuthenticatedState authenticatedState = AuthenticatedState.fromString((String) data.get(IdentityEdgeConstants.XDMKeys.AUTHENTICATED_STATE)); + final String id = (String) data.get(IdentityConstants.XDMKeys.ID); + AuthenticatedState authenticatedState = AuthenticatedState.fromString((String) data.get(IdentityConstants.XDMKeys.AUTHENTICATED_STATE)); if (authenticatedState == null) { authenticatedState = AuthenticatedState.AMBIGUOUS; } boolean primary = false; - if (data.get(IdentityEdgeConstants.XDMKeys.PRIMARY) != null) { - primary = (boolean) data.get(IdentityEdgeConstants.XDMKeys.PRIMARY); + if (data.get(IdentityConstants.XDMKeys.PRIMARY) != null) { + primary = (boolean) data.get(IdentityConstants.XDMKeys.PRIMARY); } return new IdentityItem(id, authenticatedState, primary); diff --git a/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityMap.java b/code/edgeidentity/src/main/java/com/adobe/marketing/mobile/edge/identity/IdentityMap.java similarity index 98% rename from code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityMap.java rename to code/edgeidentity/src/main/java/com/adobe/marketing/mobile/edge/identity/IdentityMap.java index 597b9569..f96c7d88 100644 --- a/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityMap.java +++ b/code/edgeidentity/src/main/java/com/adobe/marketing/mobile/edge/identity/IdentityMap.java @@ -9,7 +9,7 @@ governing permissions and limitations under the License. */ -package com.adobe.marketing.mobile.identityedge; +package com.adobe.marketing.mobile.edge.identity; import com.adobe.marketing.mobile.LoggingMode; import com.adobe.marketing.mobile.MobileCore; @@ -229,7 +229,7 @@ static IdentityMap fromData(Map data) { if (data == null) { return null; } - final Map identityMapDict = (HashMap) data.get(IdentityEdgeConstants.XDMKeys.IDENTITY_MAP); + final Map identityMapDict = (HashMap) data.get(IdentityConstants.XDMKeys.IDENTITY_MAP); if (identityMapDict == null) { return null; } diff --git a/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeProperties.java b/code/edgeidentity/src/main/java/com/adobe/marketing/mobile/edge/identity/IdentityProperties.java similarity index 81% rename from code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeProperties.java rename to code/edgeidentity/src/main/java/com/adobe/marketing/mobile/edge/identity/IdentityProperties.java index 6346b13b..322f6e67 100644 --- a/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeProperties.java +++ b/code/edgeidentity/src/main/java/com/adobe/marketing/mobile/edge/identity/IdentityProperties.java @@ -9,7 +9,7 @@ governing permissions and limitations under the License. */ -package com.adobe.marketing.mobile.identityedge; +package com.adobe.marketing.mobile.edge.identity; import com.adobe.marketing.mobile.LoggingMode; import com.adobe.marketing.mobile.MobileCore; @@ -20,29 +20,29 @@ import java.util.Map; /** - * Represents a type which contains instances variables for the Identity Edge extension + * Represents a type which contains instances variables for this Identity extension */ -class IdentityEdgeProperties { +class IdentityProperties { - private static final String LOG_TAG = "IdentityEdgeProperties"; + private static final String LOG_TAG = "IdentityProperties"; private static final List reservedNamespaces = new ArrayList() {{ - add(IdentityEdgeConstants.Namespaces.ECID); - add(IdentityEdgeConstants.Namespaces.GAID); - add(IdentityEdgeConstants.Namespaces.IDFA); + add(IdentityConstants.Namespaces.ECID); + add(IdentityConstants.Namespaces.GAID); + add(IdentityConstants.Namespaces.IDFA); }}; private final IdentityMap identityMap; - IdentityEdgeProperties() { + IdentityProperties() { this.identityMap = new IdentityMap(); } /** - * Creates a identity edge properties instance based on the map + * Constructor * - * @param xdmData a map representing an identity edge properties instance + * @param xdmData a map representing the initialization data for this {@code IdentityProperties} instance */ - IdentityEdgeProperties(final Map xdmData) { + IdentityProperties(final Map xdmData) { IdentityMap map = IdentityMap.fromData(xdmData); this.identityMap = map == null ? new IdentityMap() : map; // always keep an empty identity map so there is no need for null check } @@ -57,17 +57,17 @@ void setECID(final ECID newEcid) { final ECID currentECID = getECID(); if (currentECID != null) { final IdentityItem previousECIDItem = new IdentityItem(currentECID.toString()); - identityMap.removeItem(previousECIDItem, IdentityEdgeConstants.Namespaces.ECID); + identityMap.removeItem(previousECIDItem, IdentityConstants.Namespaces.ECID); } // if primary ecid is null, clear off all the existing ECID's if (newEcid == null) { setECIDSecondary(null); - identityMap.clearItemsForNamespace(IdentityEdgeConstants.Namespaces.ECID); + identityMap.clearItemsForNamespace(IdentityConstants.Namespaces.ECID); } else { // And add the new primary Ecid as a first element of Identity map final IdentityItem newECIDItem = new IdentityItem(newEcid.toString(), AuthenticatedState.AMBIGUOUS, false); - identityMap.addItem(newECIDItem, IdentityEdgeConstants.Namespaces.ECID, true); + identityMap.addItem(newECIDItem, IdentityConstants.Namespaces.ECID, true); } } @@ -77,7 +77,7 @@ void setECID(final ECID newEcid) { * @return current {@code ECID} */ ECID getECID() { - final List ecidItems = identityMap.getIdentityItemsForNamespace(IdentityEdgeConstants.Namespaces.ECID); + final List ecidItems = identityMap.getIdentityItemsForNamespace(IdentityConstants.Namespaces.ECID); if (ecidItems != null && !ecidItems.isEmpty() && ecidItems.get(0) != null && !Utils.isNullOrEmpty(ecidItems.get(0).getId())) { return new ECID(ecidItems.get(0).getId()); } @@ -94,7 +94,7 @@ void setECIDSecondary(final ECID newSecondaryEcid) { final ECID ecidSecondary = getECIDSecondary(); if (ecidSecondary != null) { final IdentityItem previousECIDItem = new IdentityItem(ecidSecondary.toString()); - identityMap.removeItem(previousECIDItem, IdentityEdgeConstants.Namespaces.ECID); + identityMap.removeItem(previousECIDItem, IdentityConstants.Namespaces.ECID); } // do not set secondary ECID if primary ECID is not set @@ -106,7 +106,7 @@ void setECIDSecondary(final ECID newSecondaryEcid) { // add the new secondary ECID to Identity map if (newSecondaryEcid != null) { final IdentityItem newSecondaryECIDItem = new IdentityItem(newSecondaryEcid.toString(), AuthenticatedState.AMBIGUOUS, false); - identityMap.addItem(newSecondaryECIDItem, IdentityEdgeConstants.Namespaces.ECID); + identityMap.addItem(newSecondaryECIDItem, IdentityConstants.Namespaces.ECID); } } @@ -116,7 +116,7 @@ void setECIDSecondary(final ECID newSecondaryEcid) { * @return secondary {@code ECID} */ ECID getECIDSecondary() { - final List ecidItems = identityMap.getIdentityItemsForNamespace(IdentityEdgeConstants.Namespaces.ECID); + final List ecidItems = identityMap.getIdentityItemsForNamespace(IdentityConstants.Namespaces.ECID); if (ecidItems != null && ecidItems.size() > 1 && ecidItems.get(1) != null && !Utils.isNullOrEmpty(ecidItems.get(1).getId())) { return new ECID(ecidItems.get(1).getId()); } @@ -159,7 +159,7 @@ void removeCustomerIdentifiers(final IdentityMap map) { /** * Converts this into an event data representation in XDM format * - * @param allowEmpty If this {@link IdentityEdgeProperties} contains no data, return a dictionary with a single {@link IdentityMap} key + * @param allowEmpty If this {@link IdentityProperties} contains no data, return a dictionary with a single {@link IdentityMap} key * @return A dictionary representing this in XDM format */ Map toXDMData(final boolean allowEmpty) { @@ -167,7 +167,7 @@ Map toXDMData(final boolean allowEmpty) { final Map>> dict = identityMap.toObjectMap(); if (dict != null && (!dict.isEmpty() || allowEmpty)) { - map.put(IdentityEdgeConstants.XDMKeys.IDENTITY_MAP, dict); + map.put(IdentityConstants.XDMKeys.IDENTITY_MAP, dict); } return map; diff --git a/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeState.java b/code/edgeidentity/src/main/java/com/adobe/marketing/mobile/edge/identity/IdentityState.java similarity index 70% rename from code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeState.java rename to code/edgeidentity/src/main/java/com/adobe/marketing/mobile/edge/identity/IdentityState.java index a03b574d..70da5171 100644 --- a/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeState.java +++ b/code/edgeidentity/src/main/java/com/adobe/marketing/mobile/edge/identity/IdentityState.java @@ -9,45 +9,45 @@ governing permissions and limitations under the License. */ -package com.adobe.marketing.mobile.identityedge; +package com.adobe.marketing.mobile.edge.identity; import com.adobe.marketing.mobile.LoggingMode; import com.adobe.marketing.mobile.MobileCore; /** - * Manages the business logic of the Identity Edge extension + * Manages the business logic of this Identity extension */ -class IdentityEdgeState { - private static String LOG_TAG = "IdentityEdgeState"; +class IdentityState { + private static String LOG_TAG = "IdentityState"; private boolean hasBooted = false; - private IdentityEdgeProperties identityProperties; + private IdentityProperties identityProperties; /** - * Creates a new {@link IdentityEdgeState} with the given {@link IdentityEdgeProperties} + * Creates a new {@link IdentityState} with the given {@link IdentityProperties} * - * @param identityProperties identity edge properties + * @param identityProperties identity properties */ - IdentityEdgeState(final IdentityEdgeProperties identityProperties) { + IdentityState(final IdentityProperties identityProperties) { this.identityProperties = identityProperties; } /** - * @return The current {@link IdentityEdgeProperties} for this identity state + * @return The current {@link IdentityProperties} for this identity state */ - IdentityEdgeProperties getIdentityEdgeProperties() { + IdentityProperties getIdentityProperties() { return identityProperties; } /** - * @return Returns true if IdentityEdge has booted, false otherwise + * @return Returns true if this extension has booted, false otherwise */ boolean hasBooted() { return hasBooted; } /** - * Completes init for the Identity Edge extension. + * Completes init for the Identity extension. * * @return True if we should share state after bootup, false otherwise */ @@ -56,15 +56,15 @@ boolean bootupIfReady() { return true; } // Load properties from local storage - identityProperties = IdentityEdgeStorageService.loadPropertiesFromPersistence(); + identityProperties = IdentityStorageService.loadPropertiesFromPersistence(); if (identityProperties == null) { - identityProperties = new IdentityEdgeProperties(); + identityProperties = new IdentityProperties(); } // Generate new ECID on first launch if (identityProperties.getECID() == null) { - final ECID directIdentityEcid = IdentityEdgeStorageService.loadEcidFromDirectIdentityPersistence(); + final ECID directIdentityEcid = IdentityStorageService.loadEcidFromDirectIdentityPersistence(); if (directIdentityEcid == null) { identityProperties.setECID(new ECID()); MobileCore.log(LoggingMode.DEBUG, LOG_TAG, "Bootup - Generating new ECID '" + identityProperties.getECID().toString() + "'"); @@ -72,11 +72,11 @@ boolean bootupIfReady() { identityProperties.setECID(directIdentityEcid); MobileCore.log(LoggingMode.DEBUG, LOG_TAG, "Bootup - Loading ECID from direct Identity extension '" + directIdentityEcid + "'"); } - IdentityEdgeStorageService.savePropertiesToPersistence(identityProperties); + IdentityStorageService.savePropertiesToPersistence(identityProperties); } hasBooted = true; - MobileCore.log(LoggingMode.DEBUG, LOG_TAG, "Identity Edge has successfully booted up"); + MobileCore.log(LoggingMode.DEBUG, LOG_TAG, "Edge Identity has successfully booted up"); return true; } @@ -86,12 +86,12 @@ boolean bootupIfReady() { void resetIdentifiers() { // TODO: AMSDK-11208 Determine if we should dispatch consent event - identityProperties = new IdentityEdgeProperties(); + identityProperties = new IdentityProperties(); identityProperties.setECID(new ECID()); identityProperties.setECIDSecondary(null); - IdentityEdgeStorageService.savePropertiesToPersistence(identityProperties); + IdentityStorageService.savePropertiesToPersistence(identityProperties); - // TODO: AMSDK-11208 Use return value to tell IdentityEdge to dispatch consent ad id update + // TODO: AMSDK-11208 Use return value to tell Identity to dispatch consent ad id update } @@ -102,7 +102,7 @@ void resetIdentifiers() { */ void updateCustomerIdentifiers(final IdentityMap map) { identityProperties.updateCustomerIdentifiers(map); - IdentityEdgeStorageService.savePropertiesToPersistence(identityProperties); + IdentityStorageService.savePropertiesToPersistence(identityProperties); } /** @@ -112,15 +112,15 @@ void updateCustomerIdentifiers(final IdentityMap map) { */ void removeCustomerIdentifiers(final IdentityMap map) { identityProperties.removeCustomerIdentifiers(map); - IdentityEdgeStorageService.savePropertiesToPersistence(identityProperties); + IdentityStorageService.savePropertiesToPersistence(identityProperties); } /** * Update the legacy ECID property with {@code legacyEcid} provided it does not equal the primary or secondary ECIDs - * currently in {@code IdentityEdgePoperties}. + * currently in {@code IdentityProperties}. * * @param legacyEcid the current ECID from the direct Identity extension - * @return true if the legacy ECID was updated in {@code IdentityEdgeProperties} + * @return true if the legacy ECID was updated in {@code IdentityProperties} */ boolean updateLegacyExperienceCloudId(final ECID legacyEcid) { final ECID ecid = identityProperties.getECID(); @@ -136,7 +136,7 @@ boolean updateLegacyExperienceCloudId(final ECID legacyEcid) { } identityProperties.setECIDSecondary(legacyEcid); - IdentityEdgeStorageService.savePropertiesToPersistence(identityProperties); + IdentityStorageService.savePropertiesToPersistence(identityProperties); MobileCore.log(LoggingMode.DEBUG, LOG_TAG, "Identity direct ECID updated to '" + legacyEcid + "', updating the IdentityMap"); return true; } diff --git a/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeStorageService.java b/code/edgeidentity/src/main/java/com/adobe/marketing/mobile/edge/identity/IdentityStorageService.java similarity index 82% rename from code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeStorageService.java rename to code/edgeidentity/src/main/java/com/adobe/marketing/mobile/edge/identity/IdentityStorageService.java index 1e1145cd..e06789e7 100644 --- a/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeStorageService.java +++ b/code/edgeidentity/src/main/java/com/adobe/marketing/mobile/edge/identity/IdentityStorageService.java @@ -9,7 +9,7 @@ governing permissions and limitations under the License. */ -package com.adobe.marketing.mobile.identityedge; +package com.adobe.marketing.mobile.edge.identity; import android.app.Application; import android.content.Context; @@ -24,24 +24,24 @@ import java.util.Map; /** - * Manages persistence for the Identity Edge extension + * Manages persistence for this Identity extension */ -class IdentityEdgeStorageService { - private static final String LOG_TAG = "IdentityEdgeStorageService"; +class IdentityStorageService { + private static final String LOG_TAG = "IdentityStorageService"; /** - * Loads identity edge properties from local storage, returns null if not found. + * Loads identity properties from local storage, returns null if not found. * * @return properties stored in local storage if present, otherwise null. */ - static IdentityEdgeProperties loadPropertiesFromPersistence() { - final SharedPreferences sharedPreferences = getSharedPreference(IdentityEdgeConstants.DataStoreKey.DATASTORE_NAME); + static IdentityProperties loadPropertiesFromPersistence() { + final SharedPreferences sharedPreferences = getSharedPreference(IdentityConstants.DataStoreKey.DATASTORE_NAME); if (sharedPreferences == null) { MobileCore.log(LoggingMode.DEBUG, LOG_TAG, "Shared Preference value is null. Unable to load saved identity properties from persistence."); return null; } - final String jsonString = sharedPreferences.getString(IdentityEdgeConstants.DataStoreKey.IDENTITY_PROPERTIES, null); + final String jsonString = sharedPreferences.getString(IdentityConstants.DataStoreKey.IDENTITY_PROPERTIES, null); if (jsonString == null) { MobileCore.log(LoggingMode.VERBOSE, LOG_TAG, "No previous properties were stored in persistence. Current identity properties are null"); @@ -51,7 +51,7 @@ static IdentityEdgeProperties loadPropertiesFromPersistence() { try { final JSONObject jsonObject = new JSONObject(jsonString); final Map propertyMap = Utils.toMap(jsonObject); - return new IdentityEdgeProperties(propertyMap); + return new IdentityProperties(propertyMap); } catch (JSONException exception) { MobileCore.log(LoggingMode.DEBUG, LOG_TAG, "Serialization error while reading properties jsonString from persistence. Unable to load saved identity properties from persistence."); return null; @@ -63,8 +63,8 @@ static IdentityEdgeProperties loadPropertiesFromPersistence() { * * @param properties properties to be stored */ - static void savePropertiesToPersistence(final IdentityEdgeProperties properties) { - final SharedPreferences sharedPreferences = getSharedPreference(IdentityEdgeConstants.DataStoreKey.DATASTORE_NAME); + static void savePropertiesToPersistence(final IdentityProperties properties) { + final SharedPreferences sharedPreferences = getSharedPreference(IdentityConstants.DataStoreKey.DATASTORE_NAME); if (sharedPreferences == null) { MobileCore.log(LoggingMode.DEBUG, LOG_TAG, "Shared Preference value is null. Unable to write identity properties to persistence."); return; @@ -79,14 +79,14 @@ static void savePropertiesToPersistence(final IdentityEdgeProperties properties) if (properties == null) { MobileCore.log(LoggingMode.DEBUG, LOG_TAG, "Identity Properties are null, removing them from persistence."); - editor.remove(IdentityEdgeConstants.DataStoreKey.IDENTITY_PROPERTIES); + editor.remove(IdentityConstants.DataStoreKey.IDENTITY_PROPERTIES); editor.apply(); return; } final JSONObject jsonObject = new JSONObject(properties.toXDMData(false)); final String jsonString = jsonObject.toString(); - editor.putString(IdentityEdgeConstants.DataStoreKey.IDENTITY_PROPERTIES, jsonString); + editor.putString(IdentityConstants.DataStoreKey.IDENTITY_PROPERTIES, jsonString); editor.apply(); } @@ -95,13 +95,13 @@ static void savePropertiesToPersistence(final IdentityEdgeProperties properties) * @return {@link ECID} stored in direct Identity extension's persistence, or null if no ECID value is stored. */ static ECID loadEcidFromDirectIdentityPersistence() { - final SharedPreferences sharedPreferences = getSharedPreference(IdentityEdgeConstants.DataStoreKey.IDENTITY_DIRECT_DATASTORE_NAME); + final SharedPreferences sharedPreferences = getSharedPreference(IdentityConstants.DataStoreKey.IDENTITY_DIRECT_DATASTORE_NAME); if (sharedPreferences == null) { MobileCore.log(LoggingMode.DEBUG, LOG_TAG, "Shared Preference value is null. Unable to load saved direct identity ECID from persistence."); return null; } - final String ecidString = sharedPreferences.getString(IdentityEdgeConstants.DataStoreKey.IDENTITY_DIRECT_ECID_KEY, null); + final String ecidString = sharedPreferences.getString(IdentityConstants.DataStoreKey.IDENTITY_DIRECT_ECID_KEY, null); if (ecidString == null || ecidString.isEmpty()) { return null; diff --git a/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/ListenerIdentityEdgeRemoveIdentity.java b/code/edgeidentity/src/main/java/com/adobe/marketing/mobile/edge/identity/ListenerEdgeIdentityRemoveIdentity.java similarity index 66% rename from code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/ListenerIdentityEdgeRemoveIdentity.java rename to code/edgeidentity/src/main/java/com/adobe/marketing/mobile/edge/identity/ListenerEdgeIdentityRemoveIdentity.java index c64de9e8..5b30359a 100644 --- a/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/ListenerIdentityEdgeRemoveIdentity.java +++ b/code/edgeidentity/src/main/java/com/adobe/marketing/mobile/edge/identity/ListenerEdgeIdentityRemoveIdentity.java @@ -9,7 +9,7 @@ governing permissions and limitations under the License. */ -package com.adobe.marketing.mobile.identityedge; +package com.adobe.marketing.mobile.edge.identity; import com.adobe.marketing.mobile.Event; import com.adobe.marketing.mobile.ExtensionApi; @@ -17,7 +17,7 @@ import com.adobe.marketing.mobile.LoggingMode; import com.adobe.marketing.mobile.MobileCore; -public class ListenerIdentityEdgeRemoveIdentity extends ExtensionListener { +public class ListenerEdgeIdentityRemoveIdentity extends ExtensionListener { /** * Constructor. @@ -26,29 +26,29 @@ public class ListenerIdentityEdgeRemoveIdentity extends ExtensionListener { * @param type the {@link String} eventType this listener is registered to handle * @param source the {@link String} eventSource this listener is registered to handle */ - ListenerIdentityEdgeRemoveIdentity(final ExtensionApi extensionApi, final String type, final String source) { + ListenerEdgeIdentityRemoveIdentity(final ExtensionApi extensionApi, final String type, final String source) { super(extensionApi, type, source); } /** - * Method that gets called when event with event type {@link IdentityEdgeConstants.EventType#EDGE_IDENTITY} - * and with event source {@link IdentityEdgeConstants.EventSource#REMOVE_IDENTITY} is dispatched through eventHub. + * Method that gets called when event with event type {@link IdentityConstants.EventType#EDGE_IDENTITY} + * and with event source {@link IdentityConstants.EventSource#REMOVE_IDENTITY} is dispatched through eventHub. * * @param event the remove identity {@link Event} to be processed */ @Override public void hear(final Event event) { if (event == null || event.getEventData() == null) { - MobileCore.log(LoggingMode.DEBUG, IdentityEdgeConstants.LOG_TAG, "Event or Event data is null. Ignoring the event listened by ListenerIdentityEdgeRemoveIdentity"); + MobileCore.log(LoggingMode.DEBUG, IdentityConstants.LOG_TAG, "Event or Event data is null. Ignoring the event listened by ListenerEdgeIdentityRemoveIdentity"); return; } - final IdentityEdgeExtension parentExtension = getIdentityEdgeExtension(); + final IdentityExtension parentExtension = getIdentityExtension(); if (parentExtension == null) { - MobileCore.log(LoggingMode.DEBUG, IdentityEdgeConstants.LOG_TAG, - "The parent extension, associated with the ListenerIdentityEdgeRemoveIdentity is null, ignoring event."); + MobileCore.log(LoggingMode.DEBUG, IdentityConstants.LOG_TAG, + "The parent extension, associated with the ListenerEdgeIdentityRemoveIdentity is null, ignoring event."); return; } @@ -58,9 +58,9 @@ public void hear(final Event event) { /** * Returns the parent extension associated with the listener. * - * @return a {@link IdentityEdgeExtension} object registered with the eventHub + * @return a {@link IdentityExtension} object registered with the eventHub */ - IdentityEdgeExtension getIdentityEdgeExtension() { - return (IdentityEdgeExtension) getParentExtension(); + IdentityExtension getIdentityExtension() { + return (IdentityExtension) getParentExtension(); } } diff --git a/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/ListenerIdentityRequestIdentity.java b/code/edgeidentity/src/main/java/com/adobe/marketing/mobile/edge/identity/ListenerEdgeIdentityRequestIdentity.java similarity index 64% rename from code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/ListenerIdentityRequestIdentity.java rename to code/edgeidentity/src/main/java/com/adobe/marketing/mobile/edge/identity/ListenerEdgeIdentityRequestIdentity.java index 7addca0e..47787b06 100644 --- a/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/ListenerIdentityRequestIdentity.java +++ b/code/edgeidentity/src/main/java/com/adobe/marketing/mobile/edge/identity/ListenerEdgeIdentityRequestIdentity.java @@ -9,7 +9,7 @@ governing permissions and limitations under the License. */ -package com.adobe.marketing.mobile.identityedge; +package com.adobe.marketing.mobile.edge.identity; import com.adobe.marketing.mobile.Event; import com.adobe.marketing.mobile.ExtensionApi; @@ -17,7 +17,7 @@ import com.adobe.marketing.mobile.LoggingMode; import com.adobe.marketing.mobile.MobileCore; -class ListenerIdentityRequestIdentity extends ExtensionListener { +class ListenerEdgeIdentityRequestIdentity extends ExtensionListener { /** * Constructor. @@ -26,29 +26,29 @@ class ListenerIdentityRequestIdentity extends ExtensionListener { * @param type the {@link String} eventType this listener is registered to handle * @param source the {@link String} eventSource this listener is registered to handle */ - ListenerIdentityRequestIdentity(final ExtensionApi extensionApi, final String type, final String source) { + ListenerEdgeIdentityRequestIdentity(final ExtensionApi extensionApi, final String type, final String source) { super(extensionApi, type, source); } /** - * Method that gets called when event with event type {@link IdentityEdgeConstants.EventType#GENERIC_IDENTITY} - * and with event source {@link IdentityEdgeConstants.EventSource#REQUEST_CONTENT} is dispatched through eventHub. + * Method that gets called when event with event type {@link IdentityConstants.EventType#GENERIC_IDENTITY} + * and with event source {@link IdentityConstants.EventSource#REQUEST_CONTENT} is dispatched through eventHub. * * @param event the request identity {@link Event} to be processed */ @Override public void hear(final Event event) { if (event == null || event.getEventData() == null) { - MobileCore.log(LoggingMode.DEBUG, IdentityEdgeConstants.LOG_TAG, "Event or Event data is null. Ignoring the event listened by ListenerIdentityRequestIdentity"); + MobileCore.log(LoggingMode.DEBUG, IdentityConstants.LOG_TAG, "Event or Event data is null. Ignoring the event listened by ListenerEdgeIdentityRequestIdentity"); return; } - final IdentityEdgeExtension parentExtension = getIdentityEdgeExtension(); + final IdentityExtension parentExtension = getIdentityExtension(); if (parentExtension == null) { - MobileCore.log(LoggingMode.DEBUG, IdentityEdgeConstants.LOG_TAG, - "The parent extension, associated with the ListenerIdentityRequestIdentity is null, ignoring request identity event."); + MobileCore.log(LoggingMode.DEBUG, IdentityConstants.LOG_TAG, + "The parent extension, associated with the ListenerEdgeIdentityRequestIdentity is null, ignoring request identity event."); return; } @@ -58,9 +58,9 @@ public void hear(final Event event) { /** * Returns the parent extension associated with the listener. * - * @return a {@link IdentityEdgeExtension} object registered with the eventHub + * @return a {@link IdentityExtension} object registered with the eventHub */ - IdentityEdgeExtension getIdentityEdgeExtension() { - return (IdentityEdgeExtension) getParentExtension(); + IdentityExtension getIdentityExtension() { + return (IdentityExtension) getParentExtension(); } } diff --git a/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/ListenerIdentityEdgeUpdateIdentity.java b/code/edgeidentity/src/main/java/com/adobe/marketing/mobile/edge/identity/ListenerEdgeIdentityUpdateIdentity.java similarity index 66% rename from code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/ListenerIdentityEdgeUpdateIdentity.java rename to code/edgeidentity/src/main/java/com/adobe/marketing/mobile/edge/identity/ListenerEdgeIdentityUpdateIdentity.java index 9caabfce..d20f1ba6 100644 --- a/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/ListenerIdentityEdgeUpdateIdentity.java +++ b/code/edgeidentity/src/main/java/com/adobe/marketing/mobile/edge/identity/ListenerEdgeIdentityUpdateIdentity.java @@ -9,7 +9,7 @@ governing permissions and limitations under the License. */ -package com.adobe.marketing.mobile.identityedge; +package com.adobe.marketing.mobile.edge.identity; import com.adobe.marketing.mobile.Event; import com.adobe.marketing.mobile.ExtensionApi; @@ -17,7 +17,7 @@ import com.adobe.marketing.mobile.LoggingMode; import com.adobe.marketing.mobile.MobileCore; -public class ListenerIdentityEdgeUpdateIdentity extends ExtensionListener { +public class ListenerEdgeIdentityUpdateIdentity extends ExtensionListener { /** * Constructor. @@ -26,29 +26,29 @@ public class ListenerIdentityEdgeUpdateIdentity extends ExtensionListener { * @param type the {@link String} eventType this listener is registered to handle * @param source the {@link String} eventSource this listener is registered to handle */ - ListenerIdentityEdgeUpdateIdentity(final ExtensionApi extensionApi, final String type, final String source) { + ListenerEdgeIdentityUpdateIdentity(final ExtensionApi extensionApi, final String type, final String source) { super(extensionApi, type, source); } /** - * Method that gets called when event with event type {@link IdentityEdgeConstants.EventType#EDGE_IDENTITY} - * and with event source {@link IdentityEdgeConstants.EventSource#UPDATE_IDENTITY} is dispatched through eventHub. + * Method that gets called when event with event type {@link IdentityConstants.EventType#EDGE_IDENTITY} + * and with event source {@link IdentityConstants.EventSource#UPDATE_IDENTITY} is dispatched through eventHub. * * @param event the udpate identity {@link Event} to be processed */ @Override public void hear(final Event event) { if (event == null || event.getEventData() == null) { - MobileCore.log(LoggingMode.DEBUG, IdentityEdgeConstants.LOG_TAG, "Event or Event data is null. Ignoring the event listened by ListenerIdentityEdgeUpdateIdentity"); + MobileCore.log(LoggingMode.DEBUG, IdentityConstants.LOG_TAG, "Event or Event data is null. Ignoring the event listened by ListenerEdgeIdentityUpdateIdentity"); return; } - final IdentityEdgeExtension parentExtension = getIdentityEdgeExtension(); + final IdentityExtension parentExtension = getIdentityExtension(); if (parentExtension == null) { - MobileCore.log(LoggingMode.DEBUG, IdentityEdgeConstants.LOG_TAG, - "The parent extension, associated with the ListenerIdentityEdgeUpdateIdentity is null, ignoring event."); + MobileCore.log(LoggingMode.DEBUG, IdentityConstants.LOG_TAG, + "The parent extension, associated with the ListenerEdgeIdentityUpdateIdentity is null, ignoring event."); return; } @@ -58,9 +58,9 @@ public void hear(final Event event) { /** * Returns the parent extension associated with the listener. * - * @return a {@link IdentityEdgeExtension} object registered with the eventHub + * @return a {@link IdentityExtension} object registered with the eventHub */ - IdentityEdgeExtension getIdentityEdgeExtension() { - return (IdentityEdgeExtension) getParentExtension(); + IdentityExtension getIdentityExtension() { + return (IdentityExtension) getParentExtension(); } } diff --git a/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/ListenerGenericIdentityRequestContent.java b/code/edgeidentity/src/main/java/com/adobe/marketing/mobile/edge/identity/ListenerGenericIdentityRequestContent.java similarity index 73% rename from code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/ListenerGenericIdentityRequestContent.java rename to code/edgeidentity/src/main/java/com/adobe/marketing/mobile/edge/identity/ListenerGenericIdentityRequestContent.java index f1bcbe1c..67cdbf6b 100644 --- a/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/ListenerGenericIdentityRequestContent.java +++ b/code/edgeidentity/src/main/java/com/adobe/marketing/mobile/edge/identity/ListenerGenericIdentityRequestContent.java @@ -9,7 +9,7 @@ governing permissions and limitations under the License. */ -package com.adobe.marketing.mobile.identityedge; +package com.adobe.marketing.mobile.edge.identity; import com.adobe.marketing.mobile.Event; import com.adobe.marketing.mobile.ExtensionApi; @@ -31,22 +31,22 @@ class ListenerGenericIdentityRequestContent extends ExtensionListener { } /** - * Method that gets called when event with event type {@link IdentityEdgeConstants.EventType#GENERIC_IDENTITY} - * and with event source {@link IdentityEdgeConstants.EventSource#REQUEST_CONTENT} is dispatched through eventHub. + * Method that gets called when event with event type {@link IdentityConstants.EventType#GENERIC_IDENTITY} + * and with event source {@link IdentityConstants.EventSource#REQUEST_CONTENT} is dispatched through eventHub. * * @param event the generic identity request content {@link Event} to be processed */ @Override public void hear(final Event event) { if (event == null || event.getEventData() == null) { - MobileCore.log(LoggingMode.DEBUG, IdentityEdgeConstants.LOG_TAG, "Event or Event data is null. Ignoring the event listened by ListenerGenericIdentityRequestContent"); + MobileCore.log(LoggingMode.DEBUG, IdentityConstants.LOG_TAG, "Event or Event data is null. Ignoring the event listened by ListenerGenericIdentityRequestContent"); return; } - final IdentityEdgeExtension parentExtension = getIdentityEdgeExtension(); + final IdentityExtension parentExtension = getIdentityExtension(); if (parentExtension == null) { - MobileCore.log(LoggingMode.DEBUG, IdentityEdgeConstants.LOG_TAG, + MobileCore.log(LoggingMode.DEBUG, IdentityConstants.LOG_TAG, "The parent extension, associated with the ListenerGenericIdentityRequestContent is null, ignoring the generic identity request content event."); return; } @@ -58,9 +58,9 @@ public void hear(final Event event) { /** * Returns the parent extension associated with the listener. * - * @return a {@link IdentityEdgeExtension} object registered with the eventHub + * @return a {@link IdentityExtension} object registered with the eventHub */ - IdentityEdgeExtension getIdentityEdgeExtension() { - return (IdentityEdgeExtension) getParentExtension(); + IdentityExtension getIdentityExtension() { + return (IdentityExtension) getParentExtension(); } } diff --git a/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/ListenerHubSharedState.java b/code/edgeidentity/src/main/java/com/adobe/marketing/mobile/edge/identity/ListenerHubSharedState.java similarity index 73% rename from code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/ListenerHubSharedState.java rename to code/edgeidentity/src/main/java/com/adobe/marketing/mobile/edge/identity/ListenerHubSharedState.java index bb5e8b59..c315d0ea 100644 --- a/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/ListenerHubSharedState.java +++ b/code/edgeidentity/src/main/java/com/adobe/marketing/mobile/edge/identity/ListenerHubSharedState.java @@ -9,7 +9,7 @@ governing permissions and limitations under the License. */ -package com.adobe.marketing.mobile.identityedge; +package com.adobe.marketing.mobile.edge.identity; import com.adobe.marketing.mobile.Event; import com.adobe.marketing.mobile.ExtensionApi; @@ -31,22 +31,22 @@ class ListenerHubSharedState extends ExtensionListener { /** - * Method that gets called when event with event type {@link IdentityEdgeConstants.EventType#HUB} - * and with event source {@link IdentityEdgeConstants.EventSource#SHARED_STATE} is dispatched through eventHub. + * Method that gets called when event with event type {@link IdentityConstants.EventType#HUB} + * and with event source {@link IdentityConstants.EventSource#SHARED_STATE} is dispatched through eventHub. * * @param event the hub shared state change {@link Event} to be processed */ @Override public void hear(final Event event) { if (event == null) { - MobileCore.log(LoggingMode.DEBUG, IdentityEdgeConstants.LOG_TAG, "Event is null. Ignoring the event listened by ListenerHubSharedState"); + MobileCore.log(LoggingMode.DEBUG, IdentityConstants.LOG_TAG, "Event is null. Ignoring the event listened by ListenerHubSharedState"); return; } - final IdentityEdgeExtension parentExtension = getIdentityEdgeExtension(); + final IdentityExtension parentExtension = getIdentityExtension(); if (parentExtension == null) { - MobileCore.log(LoggingMode.DEBUG, IdentityEdgeConstants.LOG_TAG, + MobileCore.log(LoggingMode.DEBUG, IdentityConstants.LOG_TAG, "The parent extension, associated with the ListenerHubSharedState is null, ignoring hub shared state event."); return; } @@ -57,9 +57,9 @@ public void hear(final Event event) { /** * Returns the parent extension associated with the listener. * - * @return a {@link IdentityEdgeExtension} object registered with the eventHub + * @return a {@link IdentityExtension} object registered with the eventHub */ - IdentityEdgeExtension getIdentityEdgeExtension() { - return (IdentityEdgeExtension) getParentExtension(); + IdentityExtension getIdentityExtension() { + return (IdentityExtension) getParentExtension(); } } diff --git a/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/ListenerIdentityRequestReset.java b/code/edgeidentity/src/main/java/com/adobe/marketing/mobile/edge/identity/ListenerIdentityRequestReset.java similarity index 73% rename from code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/ListenerIdentityRequestReset.java rename to code/edgeidentity/src/main/java/com/adobe/marketing/mobile/edge/identity/ListenerIdentityRequestReset.java index 2f839f4e..7aa6f9b8 100644 --- a/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/ListenerIdentityRequestReset.java +++ b/code/edgeidentity/src/main/java/com/adobe/marketing/mobile/edge/identity/ListenerIdentityRequestReset.java @@ -9,7 +9,7 @@ governing permissions and limitations under the License. */ -package com.adobe.marketing.mobile.identityedge; +package com.adobe.marketing.mobile.edge.identity; import com.adobe.marketing.mobile.Event; import com.adobe.marketing.mobile.ExtensionApi; @@ -31,22 +31,22 @@ public class ListenerIdentityRequestReset extends ExtensionListener { /** - * Method that gets called when event with event type {@link IdentityEdgeConstants.EventType#GENERIC_IDENTITY} - * and with event source {@link IdentityEdgeConstants.EventSource#REQUEST_RESET} is dispatched through eventHub. + * Method that gets called when event with event type {@link IdentityConstants.EventType#GENERIC_IDENTITY} + * and with event source {@link IdentityConstants.EventSource#REQUEST_RESET} is dispatched through eventHub. * * @param event the identity reset request {@link Event} to be processed */ @Override public void hear(final Event event) { if (event == null) { - MobileCore.log(LoggingMode.DEBUG, IdentityEdgeConstants.LOG_TAG, "Event is null. Ignoring the event listened by ListenerIdentityRequestReset"); + MobileCore.log(LoggingMode.DEBUG, IdentityConstants.LOG_TAG, "Event is null. Ignoring the event listened by ListenerIdentityRequestReset"); return; } - final IdentityEdgeExtension parentExtension = getIdentityEdgeExtension(); + final IdentityExtension parentExtension = getIdentityExtension(); if (parentExtension == null) { - MobileCore.log(LoggingMode.DEBUG, IdentityEdgeConstants.LOG_TAG, + MobileCore.log(LoggingMode.DEBUG, IdentityConstants.LOG_TAG, "The parent extension, associated with the ListenerIdentityRequestReset is null, ignoring request identity reset event."); return; } @@ -57,9 +57,9 @@ public void hear(final Event event) { /** * Returns the parent extension associated with the listener. * - * @return a {@link IdentityEdgeExtension} object registered with the eventHub + * @return a {@link IdentityExtension} object registered with the eventHub */ - IdentityEdgeExtension getIdentityEdgeExtension() { - return (IdentityEdgeExtension) getParentExtension(); + IdentityExtension getIdentityExtension() { + return (IdentityExtension) getParentExtension(); } } diff --git a/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/Utils.java b/code/edgeidentity/src/main/java/com/adobe/marketing/mobile/edge/identity/Utils.java similarity index 98% rename from code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/Utils.java rename to code/edgeidentity/src/main/java/com/adobe/marketing/mobile/edge/identity/Utils.java index f60b9403..ee89cf7c 100644 --- a/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/Utils.java +++ b/code/edgeidentity/src/main/java/com/adobe/marketing/mobile/edge/identity/Utils.java @@ -9,11 +9,10 @@ governing permissions and limitations under the License. */ -package com.adobe.marketing.mobile.identityedge; +package com.adobe.marketing.mobile.edge.identity; import com.adobe.marketing.mobile.LoggingMode; import com.adobe.marketing.mobile.MobileCore; -import com.adobe.marketing.mobile.MobilePrivacyStatus; import org.json.JSONArray; import org.json.JSONException; diff --git a/code/identityedge/src/sharedTestUtils/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeTestConstants.java b/code/edgeidentity/src/sharedTestUtils/java/com/adobe/marketing/mobile/edge/identity/IdentityTestConstants.java similarity index 87% rename from code/identityedge/src/sharedTestUtils/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeTestConstants.java rename to code/edgeidentity/src/sharedTestUtils/java/com/adobe/marketing/mobile/edge/identity/IdentityTestConstants.java index 1d813450..fba477e8 100644 --- a/code/identityedge/src/sharedTestUtils/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeTestConstants.java +++ b/code/edgeidentity/src/sharedTestUtils/java/com/adobe/marketing/mobile/edge/identity/IdentityTestConstants.java @@ -9,13 +9,13 @@ governing permissions and limitations under the License. */ -package com.adobe.marketing.mobile.identityedge; +package com.adobe.marketing.mobile.edge.identity; -public class IdentityEdgeTestConstants { +public class IdentityTestConstants { public final class DataStoreKey { public static final String CONFIG_DATASTORE = "AdobeMobile_ConfigState"; - public static final String IDENTITYEDGE_DATASTORE = "com.adobe.identityEdge"; + public static final String IDENTITY_DATASTORE = "com.adobe.edge.identity"; private DataStoreKey() { } } diff --git a/code/identityedge/src/sharedTestUtils/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeTestUtil.java b/code/edgeidentity/src/sharedTestUtils/java/com/adobe/marketing/mobile/edge/identity/IdentityTestUtil.java similarity index 91% rename from code/identityedge/src/sharedTestUtils/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeTestUtil.java rename to code/edgeidentity/src/sharedTestUtils/java/com/adobe/marketing/mobile/edge/identity/IdentityTestUtil.java index 60fa3a8c..92a970b1 100644 --- a/code/identityedge/src/sharedTestUtils/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeTestUtil.java +++ b/code/edgeidentity/src/sharedTestUtils/java/com/adobe/marketing/mobile/edge/identity/IdentityTestUtil.java @@ -9,7 +9,7 @@ governing permissions and limitations under the License. */ -package com.adobe.marketing.mobile.identityedge; +package com.adobe.marketing.mobile.edge.identity; import com.adobe.marketing.mobile.Event; import com.adobe.marketing.mobile.LoggingMode; @@ -35,7 +35,7 @@ /** * Util class used by both Functional and Unit tests */ -class IdentityEdgeTestUtil { +class IdentityTestUtil { /** * Helper method to create IdentityXDM Map using {@link TestItem}s @@ -44,9 +44,9 @@ static Map createXDMIdentityMap(TestItem... items) { final Map>> allItems = new HashMap<>(); for (TestItem item : items) { final Map itemMap = new HashMap<>(); - itemMap.put(IdentityEdgeConstants.XDMKeys.ID, item.id); - itemMap.put(IdentityEdgeConstants.XDMKeys.AUTHENTICATED_STATE, "ambiguous"); - itemMap.put(IdentityEdgeConstants.XDMKeys.PRIMARY, item.isPrimary); + itemMap.put(IdentityConstants.XDMKeys.ID, item.id); + itemMap.put(IdentityConstants.XDMKeys.AUTHENTICATED_STATE, "ambiguous"); + itemMap.put(IdentityConstants.XDMKeys.PRIMARY, item.isPrimary); List> nameSpaceItems = allItems.get(item.namespace); if (nameSpaceItems == null) { nameSpaceItems = new ArrayList<>(); @@ -56,7 +56,7 @@ static Map createXDMIdentityMap(TestItem... items) { } final Map identityMapDict = new HashMap<>(); - identityMapDict.put(IdentityEdgeConstants.XDMKeys.IDENTITY_MAP, allItems); + identityMapDict.put(IdentityConstants.XDMKeys.IDENTITY_MAP, allItems); return identityMapDict; } @@ -73,7 +73,7 @@ static Event buildRemoveIdentityRequestWithJSONString(final String jsonStr) thro * Helper method to build remove identity request event with XDM formatted Identity map */ static Event buildRemoveIdentityRequest(final Map map) { - return new Event.Builder("Remove Identity Event", IdentityEdgeConstants.EventType.EDGE_IDENTITY, IdentityEdgeConstants.EventSource.REMOVE_IDENTITY).setEventData(map).build(); + return new Event.Builder("Remove Identity Event", IdentityConstants.EventType.EDGE_IDENTITY, IdentityConstants.EventSource.REMOVE_IDENTITY).setEventData(map).build(); } /** @@ -89,7 +89,7 @@ static Event buildUpdateIdentityRequestJSONString(final String jsonStr) throws E * Helper method to build update identity request event with XDM formatted Identity map */ static Event buildUpdateIdentityRequest(final Map map) { - return new Event.Builder("Update Identity Event", IdentityEdgeConstants.EventType.EDGE_IDENTITY, IdentityEdgeConstants.EventSource.UPDATE_IDENTITY).setEventData(map).build(); + return new Event.Builder("Update Identity Event", IdentityConstants.EventType.EDGE_IDENTITY, IdentityConstants.EventSource.UPDATE_IDENTITY).setEventData(map).build(); } @@ -185,7 +185,7 @@ public TestItem(String namespace, String id) { public static class TestECIDItem extends TestItem { public TestECIDItem(final String ecid) { - super(IdentityEdgeConstants.Namespaces.ECID, ecid); + super(IdentityConstants.Namespaces.ECID, ecid); } } diff --git a/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/ECIDTests.java b/code/edgeidentity/src/test/java/com/adobe/marketing/mobile/edge/identity/ECIDTests.java similarity index 98% rename from code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/ECIDTests.java rename to code/edgeidentity/src/test/java/com/adobe/marketing/mobile/edge/identity/ECIDTests.java index 978c4a25..83493142 100644 --- a/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/ECIDTests.java +++ b/code/edgeidentity/src/test/java/com/adobe/marketing/mobile/edge/identity/ECIDTests.java @@ -9,7 +9,7 @@ governing permissions and limitations under the License. */ -package com.adobe.marketing.mobile.identityedge; +package com.adobe.marketing.mobile.edge.identity; import org.junit.Test; diff --git a/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeExtensionTests.java b/code/edgeidentity/src/test/java/com/adobe/marketing/mobile/edge/identity/IdentityExtensionTests.java similarity index 78% rename from code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeExtensionTests.java rename to code/edgeidentity/src/test/java/com/adobe/marketing/mobile/edge/identity/IdentityExtensionTests.java index 472d401e..2c18a9c8 100644 --- a/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeExtensionTests.java +++ b/code/edgeidentity/src/test/java/com/adobe/marketing/mobile/edge/identity/IdentityExtensionTests.java @@ -9,7 +9,7 @@ governing permissions and limitations under the License. */ -package com.adobe.marketing.mobile.identityedge; +package com.adobe.marketing.mobile.edge.identity; import android.app.Application; import android.content.Context; @@ -35,7 +35,7 @@ import java.util.HashMap; import java.util.Map; -import static com.adobe.marketing.mobile.identityedge.IdentityEdgeTestUtil.*; +import static com.adobe.marketing.mobile.edge.identity.IdentityTestUtil.*; import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertEquals; @@ -50,9 +50,9 @@ import static org.powermock.api.mockito.PowerMockito.when; @RunWith(PowerMockRunner.class) -@PrepareForTest({Event.class, MobileCore.class, ExtensionApi.class, IdentityEdgeState.class}) -public class IdentityEdgeExtensionTests { - private IdentityEdgeExtension extension; +@PrepareForTest({Event.class, MobileCore.class, ExtensionApi.class, IdentityState.class}) +public class IdentityExtensionTests { + private IdentityExtension extension; @Mock ExtensionApi mockExtensionApi; @@ -75,10 +75,10 @@ public void setup() { Mockito.when(MobileCore.getApplication()).thenReturn(mockApplication); Mockito.when(mockApplication.getApplicationContext()).thenReturn(mockContext); - Mockito.when(mockContext.getSharedPreferences(IdentityEdgeConstants.DataStoreKey.DATASTORE_NAME, 0)).thenReturn(mockSharedPreference); + Mockito.when(mockContext.getSharedPreferences(IdentityConstants.DataStoreKey.DATASTORE_NAME, 0)).thenReturn(mockSharedPreference); Mockito.when(mockSharedPreference.edit()).thenReturn(mockSharedPreferenceEditor); - extension = new IdentityEdgeExtension(mockExtensionApi); + extension = new IdentityExtension(mockExtensionApi); } // ======================================================================================== @@ -97,18 +97,18 @@ public void test_ListenersRegistration() { anyString(), any(Class.class), any(ExtensionErrorCallback.class)); // verify listeners are registered with correct event source and type - verify(mockExtensionApi, times(1)).registerEventListener(eq(IdentityEdgeConstants.EventType.EDGE_IDENTITY), - eq(IdentityEdgeConstants.EventSource.REQUEST_IDENTITY), eq(ListenerIdentityRequestIdentity.class), callbackCaptor.capture()); - verify(mockExtensionApi, times(1)).registerEventListener(eq(IdentityEdgeConstants.EventType.GENERIC_IDENTITY), - eq(IdentityEdgeConstants.EventSource.REQUEST_CONTENT), eq(ListenerGenericIdentityRequestContent.class), callbackCaptor.capture()); - verify(mockExtensionApi, times(1)).registerEventListener(eq(IdentityEdgeConstants.EventType.EDGE_IDENTITY), - eq(IdentityEdgeConstants.EventSource.UPDATE_IDENTITY), eq(ListenerIdentityEdgeUpdateIdentity.class), callbackCaptor.capture()); - verify(mockExtensionApi, times(1)).registerEventListener(eq(IdentityEdgeConstants.EventType.EDGE_IDENTITY), - eq(IdentityEdgeConstants.EventSource.REMOVE_IDENTITY), eq(ListenerIdentityEdgeRemoveIdentity.class), callbackCaptor.capture()); - verify(mockExtensionApi, times(1)).registerEventListener(eq(IdentityEdgeConstants.EventType.GENERIC_IDENTITY), - eq(IdentityEdgeConstants.EventSource.REQUEST_RESET), eq(ListenerIdentityRequestReset.class), callbackCaptor.capture()); - verify(mockExtensionApi, times(1)).registerEventListener(eq(IdentityEdgeConstants.EventType.HUB), - eq(IdentityEdgeConstants.EventSource.SHARED_STATE), eq(ListenerHubSharedState.class), callbackCaptor.capture()); + verify(mockExtensionApi, times(1)).registerEventListener(eq(IdentityConstants.EventType.EDGE_IDENTITY), + eq(IdentityConstants.EventSource.REQUEST_IDENTITY), eq(ListenerEdgeIdentityRequestIdentity.class), callbackCaptor.capture()); + verify(mockExtensionApi, times(1)).registerEventListener(eq(IdentityConstants.EventType.GENERIC_IDENTITY), + eq(IdentityConstants.EventSource.REQUEST_CONTENT), eq(ListenerGenericIdentityRequestContent.class), callbackCaptor.capture()); + verify(mockExtensionApi, times(1)).registerEventListener(eq(IdentityConstants.EventType.EDGE_IDENTITY), + eq(IdentityConstants.EventSource.UPDATE_IDENTITY), eq(ListenerEdgeIdentityUpdateIdentity.class), callbackCaptor.capture()); + verify(mockExtensionApi, times(1)).registerEventListener(eq(IdentityConstants.EventType.EDGE_IDENTITY), + eq(IdentityConstants.EventSource.REMOVE_IDENTITY), eq(ListenerEdgeIdentityRemoveIdentity.class), callbackCaptor.capture()); + verify(mockExtensionApi, times(1)).registerEventListener(eq(IdentityConstants.EventType.GENERIC_IDENTITY), + eq(IdentityConstants.EventSource.REQUEST_RESET), eq(ListenerIdentityRequestReset.class), callbackCaptor.capture()); + verify(mockExtensionApi, times(1)).registerEventListener(eq(IdentityConstants.EventType.HUB), + eq(IdentityConstants.EventSource.SHARED_STATE), eq(ListenerHubSharedState.class), callbackCaptor.capture()); // verify the callback ExtensionErrorCallback extensionErrorCallback = callbackCaptor.getValue(); @@ -125,7 +125,7 @@ public void test_ListenersRegistration() { public void test_getName() { // test String moduleName = extension.getName(); - assertEquals("getName should return the correct module name", IdentityEdgeConstants.EXTENSION_NAME, moduleName); + assertEquals("getName should return the correct module name", IdentityConstants.EXTENSION_NAME, moduleName); } // ======================================================================================== @@ -135,7 +135,7 @@ public void test_getName() { public void test_getVersion() { // test String moduleVersion = extension.getVersion(); - assertEquals("getVersion should return the correct module version", IdentityEdgeConstants.EXTENSION_VERSION, + assertEquals("getVersion should return the correct module version", IdentityConstants.EXTENSION_VERSION, moduleVersion); } @@ -152,7 +152,7 @@ public void test_handleIdentityRequest_nullEvent_shouldNotThrow() { @Test public void test_handleIdentityRequest_generatesNewECID() { // setup - Event event = new Event.Builder("Test event", IdentityEdgeConstants.EventType.EDGE_IDENTITY, IdentityEdgeConstants.EventSource.REQUEST_IDENTITY).build(); + Event event = new Event.Builder("Test event", IdentityConstants.EventType.EDGE_IDENTITY, IdentityConstants.EventSource.REQUEST_IDENTITY).build(); final ArgumentCaptor responseEventCaptor = ArgumentCaptor.forClass(Event.class); final ArgumentCaptor requestEventCaptor = ArgumentCaptor.forClass(Event.class); @@ -176,9 +176,9 @@ public void test_handleIdentityRequest_generatesNewECID() { public void test_handleIdentityRequest_loadsPersistedECID() { // setup final ECID existingECID = new ECID(); - setupExistingIdentityEdgeProps(existingECID); + setupExistingIdentityProps(existingECID); - Event event = new Event.Builder("Test event", IdentityEdgeConstants.EventType.EDGE_IDENTITY, IdentityEdgeConstants.EventSource.REQUEST_IDENTITY).build(); + Event event = new Event.Builder("Test event", IdentityConstants.EventType.EDGE_IDENTITY, IdentityConstants.EventSource.REQUEST_IDENTITY).build(); final ArgumentCaptor responseEventCaptor = ArgumentCaptor.forClass(Event.class); final ArgumentCaptor requestEventCaptor = ArgumentCaptor.forClass(Event.class); @@ -200,10 +200,10 @@ public void test_handleIdentityRequest_loadsPersistedECID() { @Test public void test_handleIdentityRequest_noIdentifiers_emptyIdentityMap() { // setup - IdentityEdgeProperties emptyProps = new IdentityEdgeProperties(); - PowerMockito.stub(PowerMockito.method(IdentityEdgeState.class, "getIdentityEdgeProperties")).toReturn(emptyProps); + IdentityProperties emptyProps = new IdentityProperties(); + PowerMockito.stub(PowerMockito.method(IdentityState.class, "getIdentityProperties")).toReturn(emptyProps); - Event event = new Event.Builder("Test event", IdentityEdgeConstants.EventType.EDGE_IDENTITY, IdentityEdgeConstants.EventSource.REQUEST_IDENTITY).build(); + Event event = new Event.Builder("Test event", IdentityConstants.EventType.EDGE_IDENTITY, IdentityConstants.EventSource.REQUEST_IDENTITY).build(); final ArgumentCaptor responseEventCaptor = ArgumentCaptor.forClass(Event.class); final ArgumentCaptor requestEventCaptor = ArgumentCaptor.forClass(Event.class); @@ -225,7 +225,7 @@ public void test_handleIdentityRequest_noIdentifiers_emptyIdentityMap() { @Test public void test_handleIdentityResetRequest() { // setup - Event event = new Event.Builder("Test event", IdentityEdgeConstants.EventType.GENERIC_IDENTITY, IdentityEdgeConstants.EventSource.REQUEST_RESET).build(); + Event event = new Event.Builder("Test event", IdentityConstants.EventType.GENERIC_IDENTITY, IdentityConstants.EventSource.REQUEST_RESET).build(); final ArgumentCaptor sharedStateCaptor = ArgumentCaptor.forClass(Map.class); final ArgumentCaptor eventCaptor = ArgumentCaptor.forClass(Event.class); @@ -243,18 +243,18 @@ public void test_handleIdentityResetRequest() { @Test public void test_handleHubSharedState_updateLegacyEcidOnDirectIdentityStateChange() { - when(mockExtensionApi.getSharedEventState(eq(IdentityEdgeConstants.SharedStateKeys.IDENTITY_DIRECT), + when(mockExtensionApi.getSharedEventState(eq(IdentityConstants.SharedStateKeys.IDENTITY_DIRECT), any(Event.class), any(ExtensionErrorCallback.class))) .thenReturn(new HashMap() {{ - put(IdentityEdgeConstants.EventDataKeys.VISITOR_ID_ECID, "1234"); + put(IdentityConstants.EventDataKeys.VISITOR_ID_ECID, "1234"); }}); Event event = new Event.Builder("Test event", - IdentityEdgeConstants.EventType.HUB, - IdentityEdgeConstants.EventSource.SHARED_STATE) + IdentityConstants.EventType.HUB, + IdentityConstants.EventSource.SHARED_STATE) .setEventData(new HashMap(){{ - put(IdentityEdgeConstants.EventDataKeys.STATE_OWNER, IdentityEdgeConstants.SharedStateKeys.IDENTITY_DIRECT); + put(IdentityConstants.EventDataKeys.STATE_OWNER, IdentityConstants.SharedStateKeys.IDENTITY_DIRECT); }}).build(); extension.handleHubSharedState(event); @@ -267,11 +267,11 @@ public void test_handleHubSharedState_updateLegacyEcidOnDirectIdentityStateChang @Test public void test_handleHubSharedState_noOpNullEvent() { - when(mockExtensionApi.getSharedEventState(eq(IdentityEdgeConstants.SharedStateKeys.IDENTITY_DIRECT), + when(mockExtensionApi.getSharedEventState(eq(IdentityConstants.SharedStateKeys.IDENTITY_DIRECT), any(Event.class), any(ExtensionErrorCallback.class))) .thenReturn(new HashMap() {{ - put(IdentityEdgeConstants.EventDataKeys.VISITOR_ID_ECID, "1234"); + put(IdentityConstants.EventDataKeys.VISITOR_ID_ECID, "1234"); }}); extension.handleHubSharedState(null); @@ -281,16 +281,16 @@ public void test_handleHubSharedState_noOpNullEvent() { @Test public void test_handleHubSharedState_noOpNullEventData() { - when(mockExtensionApi.getSharedEventState(eq(IdentityEdgeConstants.SharedStateKeys.IDENTITY_DIRECT), + when(mockExtensionApi.getSharedEventState(eq(IdentityConstants.SharedStateKeys.IDENTITY_DIRECT), any(Event.class), any(ExtensionErrorCallback.class))) .thenReturn(new HashMap() {{ - put(IdentityEdgeConstants.EventDataKeys.VISITOR_ID_ECID, "1234"); + put(IdentityConstants.EventDataKeys.VISITOR_ID_ECID, "1234"); }}); Event event = new Event.Builder("Test event", - IdentityEdgeConstants.EventType.HUB, - IdentityEdgeConstants.EventSource.SHARED_STATE) + IdentityConstants.EventType.HUB, + IdentityConstants.EventSource.SHARED_STATE) .build(); extension.handleHubSharedState(event); @@ -300,18 +300,18 @@ public void test_handleHubSharedState_noOpNullEventData() { @Test public void test_handleHubSharedState_noOpNotDirectIdentityStateChange() { - when(mockExtensionApi.getSharedEventState(eq(IdentityEdgeConstants.SharedStateKeys.IDENTITY_DIRECT), + when(mockExtensionApi.getSharedEventState(eq(IdentityConstants.SharedStateKeys.IDENTITY_DIRECT), any(Event.class), any(ExtensionErrorCallback.class))) .thenReturn(new HashMap() {{ - put(IdentityEdgeConstants.EventDataKeys.VISITOR_ID_ECID, "1234"); + put(IdentityConstants.EventDataKeys.VISITOR_ID_ECID, "1234"); }}); Event event = new Event.Builder("Test event", - IdentityEdgeConstants.EventType.HUB, - IdentityEdgeConstants.EventSource.SHARED_STATE) + IdentityConstants.EventType.HUB, + IdentityConstants.EventSource.SHARED_STATE) .setEventData(new HashMap(){{ - put(IdentityEdgeConstants.EventDataKeys.STATE_OWNER, "com.adobe.module.configuration"); + put(IdentityConstants.EventDataKeys.STATE_OWNER, "com.adobe.module.configuration"); }}).build(); extension.handleHubSharedState(event); @@ -321,16 +321,16 @@ public void test_handleHubSharedState_noOpNotDirectIdentityStateChange() { @Test public void test_handleHubSharedState_noOpNoDirectIdentitySharedState() { - when(mockExtensionApi.getSharedEventState(eq(IdentityEdgeConstants.SharedStateKeys.IDENTITY_DIRECT), + when(mockExtensionApi.getSharedEventState(eq(IdentityConstants.SharedStateKeys.IDENTITY_DIRECT), any(Event.class), any(ExtensionErrorCallback.class))) .thenReturn(null); Event event = new Event.Builder("Test event", - IdentityEdgeConstants.EventType.HUB, - IdentityEdgeConstants.EventSource.SHARED_STATE) + IdentityConstants.EventType.HUB, + IdentityConstants.EventSource.SHARED_STATE) .setEventData(new HashMap(){{ - put(IdentityEdgeConstants.EventDataKeys.STATE_OWNER, IdentityEdgeConstants.SharedStateKeys.IDENTITY_DIRECT); + put(IdentityConstants.EventDataKeys.STATE_OWNER, IdentityConstants.SharedStateKeys.IDENTITY_DIRECT); }}).build(); extension.handleHubSharedState(event); @@ -340,22 +340,22 @@ public void test_handleHubSharedState_noOpNoDirectIdentitySharedState() { @Test public void test_handleHubSharedState_doesNotShareStateIfLegacyECIDDoesNotChange() { - when(mockExtensionApi.getSharedEventState(eq(IdentityEdgeConstants.SharedStateKeys.IDENTITY_DIRECT), + when(mockExtensionApi.getSharedEventState(eq(IdentityConstants.SharedStateKeys.IDENTITY_DIRECT), any(Event.class), any(ExtensionErrorCallback.class))) .thenReturn(new HashMap() {{ - put(IdentityEdgeConstants.EventDataKeys.VISITOR_ID_ECID, "1234"); + put(IdentityConstants.EventDataKeys.VISITOR_ID_ECID, "1234"); }}); Event event = new Event.Builder("Test event", - IdentityEdgeConstants.EventType.HUB, - IdentityEdgeConstants.EventSource.SHARED_STATE) + IdentityConstants.EventType.HUB, + IdentityConstants.EventSource.SHARED_STATE) .setEventData(new HashMap(){{ - put(IdentityEdgeConstants.EventDataKeys.STATE_OWNER, IdentityEdgeConstants.SharedStateKeys.IDENTITY_DIRECT); + put(IdentityConstants.EventDataKeys.STATE_OWNER, IdentityConstants.SharedStateKeys.IDENTITY_DIRECT); }}).build(); // IdentityState.updateLegacyExperienceCloudId returns false if Legacy ECID was not updated - PowerMockito.stub(PowerMockito.method(IdentityEdgeState.class, "updateLegacyExperienceCloudId")).toReturn(false); + PowerMockito.stub(PowerMockito.method(IdentityState.class, "updateLegacyExperienceCloudId")).toReturn(false); extension.handleHubSharedState(event); @@ -388,7 +388,7 @@ public void test_handleUpdateIdentities() throws Exception { assertEquals("false", sharedState.get("identityMap.UserId[0].primary")); // verify persistence - verify(mockSharedPreferenceEditor, times(2)).putString(eq(IdentityEdgeConstants.DataStoreKey.IDENTITY_PROPERTIES), persistenceValueCaptor.capture()); + verify(mockSharedPreferenceEditor, times(2)).putString(eq(IdentityConstants.DataStoreKey.IDENTITY_PROPERTIES), persistenceValueCaptor.capture()); Map persistedData = flattenJSONString(persistenceValueCaptor.getAllValues().get(1)); assertEquals("secretID", persistedData.get("identityMap.UserId[0].id")); assertEquals("ambiguous", persistedData.get("identityMap.UserId[0].authenticatedState")); @@ -423,7 +423,7 @@ public void test_handleUpdateIdentities_DoNotUpdateReservedNamespace() throws Ex assertNotEquals("somevalue", sharedState.get("identityMap.ECID[0].id")); // verify that the ECID is not disturbed // verify persistence - verify(mockSharedPreferenceEditor, times(2)).putString(eq(IdentityEdgeConstants.DataStoreKey.IDENTITY_PROPERTIES), persistenceValueCaptor.capture()); + verify(mockSharedPreferenceEditor, times(2)).putString(eq(IdentityConstants.DataStoreKey.IDENTITY_PROPERTIES), persistenceValueCaptor.capture()); Map persistedData = flattenJSONString(persistenceValueCaptor.getAllValues().get(1)); assertEquals(6, persistedData.size()); // 3 represents id, primary and authState of USERID identifier and generated ECID assertEquals("somevalue", persistedData.get("identityMap.UserId[0].id")); @@ -475,11 +475,11 @@ public void test_handleRemoveIdentity() throws Exception { new TestItem("PushId", "token") ); JSONObject identityXDMJSON = new JSONObject(identityXDM); - Mockito.when(mockSharedPreference.getString(IdentityEdgeConstants.DataStoreKey.IDENTITY_PROPERTIES, null)).thenReturn(identityXDMJSON.toString()); + Mockito.when(mockSharedPreference.getString(IdentityConstants.DataStoreKey.IDENTITY_PROPERTIES, null)).thenReturn(identityXDMJSON.toString()); final ArgumentCaptor sharedStateCaptor = ArgumentCaptor.forClass(Map.class); final ArgumentCaptor persistenceValueCaptor = ArgumentCaptor.forClass(String.class); - extension = new IdentityEdgeExtension(mockExtensionApi); + extension = new IdentityExtension(mockExtensionApi); // test Map removedIdentityXDM = createXDMIdentityMap( @@ -496,7 +496,7 @@ public void test_handleRemoveIdentity() throws Exception { // verify persistence - verify(mockSharedPreferenceEditor, times(2)).putString(eq(IdentityEdgeConstants.DataStoreKey.IDENTITY_PROPERTIES), persistenceValueCaptor.capture()); + verify(mockSharedPreferenceEditor, times(2)).putString(eq(IdentityConstants.DataStoreKey.IDENTITY_PROPERTIES), persistenceValueCaptor.capture()); Map persistedData = flattenJSONString(persistenceValueCaptor.getAllValues().get(1)); assertNull(persistedData.get("identityMap.UserId[0].id")); assertEquals("token", persistedData.get("identityMap.PushId[0].id")); @@ -511,11 +511,11 @@ public void test_handleRemoveIdentity_DoNotRemoveReservedNamespace() throws Exce new TestItem("IDFA", "someIDFA") ); JSONObject identityXDMJSON = new JSONObject(identityXDM); - Mockito.when(mockSharedPreference.getString(IdentityEdgeConstants.DataStoreKey.IDENTITY_PROPERTIES, null)).thenReturn(identityXDMJSON.toString()); + Mockito.when(mockSharedPreference.getString(IdentityConstants.DataStoreKey.IDENTITY_PROPERTIES, null)).thenReturn(identityXDMJSON.toString()); final ArgumentCaptor sharedStateCaptor = ArgumentCaptor.forClass(Map.class); final ArgumentCaptor persistenceValueCaptor = ArgumentCaptor.forClass(String.class); - extension = new IdentityEdgeExtension(mockExtensionApi); + extension = new IdentityExtension(mockExtensionApi); // test Map removedIdentityXDM = createXDMIdentityMap( @@ -535,7 +535,7 @@ public void test_handleRemoveIdentity_DoNotRemoveReservedNamespace() throws Exce // verify persistence - verify(mockSharedPreferenceEditor, times(1)).putString(eq(IdentityEdgeConstants.DataStoreKey.IDENTITY_PROPERTIES), persistenceValueCaptor.capture()); + verify(mockSharedPreferenceEditor, times(1)).putString(eq(IdentityConstants.DataStoreKey.IDENTITY_PROPERTIES), persistenceValueCaptor.capture()); Map persistedData = flattenJSONString(persistenceValueCaptor.getValue()); assertEquals("someGAID", persistedData.get("identityMap.GAID[0].id")); assertEquals("someECID", persistedData.get("identityMap.ECID[0].id")); @@ -550,8 +550,8 @@ public void test_handleRemoveIdentity_NullData() throws Exception { new TestItem("PushId", "token") ); JSONObject identityXDMJSON = new JSONObject(identityXDM); - Mockito.when(mockSharedPreference.getString(IdentityEdgeConstants.DataStoreKey.IDENTITY_PROPERTIES, null)).thenReturn(identityXDMJSON.toString()); - extension = new IdentityEdgeExtension(mockExtensionApi); + Mockito.when(mockSharedPreference.getString(IdentityConstants.DataStoreKey.IDENTITY_PROPERTIES, null)).thenReturn(identityXDMJSON.toString()); + extension = new IdentityExtension(mockExtensionApi); // test Event removeIdentityEvent = buildRemoveIdentityRequest(null); @@ -561,7 +561,7 @@ public void test_handleRemoveIdentity_NullData() throws Exception { verify(mockExtensionApi, times(0)).setXDMSharedEventState(any(Map.class), eq(removeIdentityEvent), any(ExtensionErrorCallback.class)); // verify persistence - verify(mockSharedPreferenceEditor, times(1)).putString(eq(IdentityEdgeConstants.DataStoreKey.IDENTITY_PROPERTIES), anyString()); + verify(mockSharedPreferenceEditor, times(1)).putString(eq(IdentityConstants.DataStoreKey.IDENTITY_PROPERTIES), anyString()); } @@ -569,12 +569,12 @@ public void test_handleRemoveIdentity_NullData() throws Exception { // private helper methods // ======================================================================================== - private void setupExistingIdentityEdgeProps(final ECID ecid) { - IdentityEdgeProperties persistedProps = new IdentityEdgeProperties(); + private void setupExistingIdentityProps(final ECID ecid) { + IdentityProperties persistedProps = new IdentityProperties(); persistedProps.setECID(ecid); final JSONObject jsonObject = new JSONObject(persistedProps.toXDMData(false)); final String propsJSON = jsonObject.toString(); - Mockito.when(mockSharedPreference.getString(IdentityEdgeConstants.DataStoreKey.IDENTITY_PROPERTIES, null)).thenReturn(propsJSON); + Mockito.when(mockSharedPreference.getString(IdentityConstants.DataStoreKey.IDENTITY_PROPERTIES, null)).thenReturn(propsJSON); } } diff --git a/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeExtensionVersionTest.java b/code/edgeidentity/src/test/java/com/adobe/marketing/mobile/edge/identity/IdentityExtensionVersionTest.java similarity index 85% rename from code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeExtensionVersionTest.java rename to code/edgeidentity/src/test/java/com/adobe/marketing/mobile/edge/identity/IdentityExtensionVersionTest.java index a59e504e..cf416baf 100644 --- a/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeExtensionVersionTest.java +++ b/code/edgeidentity/src/test/java/com/adobe/marketing/mobile/edge/identity/IdentityExtensionVersionTest.java @@ -9,7 +9,7 @@ governing permissions and limitations under the License. */ -package com.adobe.marketing.mobile.identityedge; +package com.adobe.marketing.mobile.edge.identity; import org.junit.Test; @@ -22,7 +22,7 @@ import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; -public class IdentityEdgeExtensionVersionTest { +public class IdentityExtensionVersionTest { private static String GRADLE_PROPERTIES_PATH = "../gradle.properties"; private static String PROPERTY_MODULE_VERSION = "moduleVersion"; @@ -30,16 +30,16 @@ public class IdentityEdgeExtensionVersionTest { public void extensionVersion_verifyModuleVersionInPropertiesFile_asEqual() { Properties properties = loadProperties(GRADLE_PROPERTIES_PATH); - assertNotNull(IdentityEdge.extensionVersion()); - assertFalse(IdentityEdge.extensionVersion().isEmpty()); + assertNotNull(Identity.extensionVersion()); + assertFalse(Identity.extensionVersion().isEmpty()); String moduleVersion = properties.getProperty(PROPERTY_MODULE_VERSION); assertNotNull(moduleVersion); assertFalse(moduleVersion.isEmpty()); assertEquals(String.format("Expected version to match in gradle.properties (%s) and extensionVersion API (%s)", - moduleVersion, IdentityEdge.extensionVersion()), - moduleVersion, IdentityEdge.extensionVersion()); + moduleVersion, Identity.extensionVersion()), + moduleVersion, Identity.extensionVersion()); } diff --git a/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/IdentityItemTests.java b/code/edgeidentity/src/test/java/com/adobe/marketing/mobile/edge/identity/IdentityItemTests.java similarity index 98% rename from code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/IdentityItemTests.java rename to code/edgeidentity/src/test/java/com/adobe/marketing/mobile/edge/identity/IdentityItemTests.java index 0d08d833..828bbd3a 100644 --- a/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/IdentityItemTests.java +++ b/code/edgeidentity/src/test/java/com/adobe/marketing/mobile/edge/identity/IdentityItemTests.java @@ -9,7 +9,7 @@ governing permissions and limitations under the License. */ -package com.adobe.marketing.mobile.identityedge; +package com.adobe.marketing.mobile.edge.identity; import org.junit.Test; diff --git a/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/IdentityMapTests.java b/code/edgeidentity/src/test/java/com/adobe/marketing/mobile/edge/identity/IdentityMapTests.java similarity index 98% rename from code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/IdentityMapTests.java rename to code/edgeidentity/src/test/java/com/adobe/marketing/mobile/edge/identity/IdentityMapTests.java index 62f30408..b9264402 100644 --- a/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/IdentityMapTests.java +++ b/code/edgeidentity/src/test/java/com/adobe/marketing/mobile/edge/identity/IdentityMapTests.java @@ -9,7 +9,7 @@ governing permissions and limitations under the License. */ -package com.adobe.marketing.mobile.identityedge; +package com.adobe.marketing.mobile.edge.identity; import org.json.JSONObject; import org.junit.Test; @@ -278,7 +278,7 @@ public void test_FromData() throws Exception { IdentityMap map = IdentityMap.fromData(xdmData); // verify - Map flattenedMap = IdentityEdgeTestUtil.flattenMap(map.asEventData()); + Map flattenedMap = IdentityTestUtil.flattenMap(map.asEventData()); assertEquals("randomECID", flattenedMap.get("ECID[0].id")); assertEquals("AMBIGUOUS", flattenedMap.get("ECID[0].authenticatedState")); assertEquals("true", flattenedMap.get("ECID[0].primary")); diff --git a/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/IdentityEdgePropertiesTests.java b/code/edgeidentity/src/test/java/com/adobe/marketing/mobile/edge/identity/IdentityPropertiesTests.java similarity index 86% rename from code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/IdentityEdgePropertiesTests.java rename to code/edgeidentity/src/test/java/com/adobe/marketing/mobile/edge/identity/IdentityPropertiesTests.java index 04f685a4..c930ece7 100644 --- a/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/IdentityEdgePropertiesTests.java +++ b/code/edgeidentity/src/test/java/com/adobe/marketing/mobile/edge/identity/IdentityPropertiesTests.java @@ -9,17 +9,17 @@ governing permissions and limitations under the License. */ -package com.adobe.marketing.mobile.identityedge; +package com.adobe.marketing.mobile.edge.identity; import org.junit.Test; import java.util.Map; -import static com.adobe.marketing.mobile.identityedge.IdentityEdgeTestUtil.*; +import static com.adobe.marketing.mobile.edge.identity.IdentityTestUtil.*; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull; -public class IdentityEdgePropertiesTests { +public class IdentityPropertiesTests { // ====================================================================================================================== // Tests for method : toXDMData(final boolean allowEmpty) @@ -28,20 +28,20 @@ public class IdentityEdgePropertiesTests { @Test public void test_toXDMData_Empty() { // setup - IdentityEdgeProperties props = new IdentityEdgeProperties(); + IdentityProperties props = new IdentityProperties(); // test Map xdmMap = props.toXDMData(false); // verify - assertNull(xdmMap.get(IdentityEdgeConstants.XDMKeys.IDENTITY_MAP)); + assertNull(xdmMap.get(IdentityConstants.XDMKeys.IDENTITY_MAP)); } @Test public void test_toXDMData_Full() { // setup - IdentityEdgeProperties props = new IdentityEdgeProperties(); + IdentityProperties props = new IdentityProperties(); props.setECID(new ECID()); props.setECIDSecondary(new ECID()); @@ -63,7 +63,7 @@ public void test_toXDMData_Full() { @Test public void test_toXDMData_OnlyPrimaryECID() { // setup - IdentityEdgeProperties props = new IdentityEdgeProperties(); + IdentityProperties props = new IdentityProperties(); props.setECID(new ECID()); // test @@ -77,7 +77,7 @@ public void test_toXDMData_OnlyPrimaryECID() { public void test_toXDMData_OnlySecondaryECID() { // should not set secondary ECID if primary not set // setup - IdentityEdgeProperties props = new IdentityEdgeProperties(); + IdentityProperties props = new IdentityProperties(); props.setECIDSecondary(new ECID()); // test and verify, can't have secondary ECID without primary ECID @@ -86,7 +86,7 @@ public void test_toXDMData_OnlySecondaryECID() { // ====================================================================================================================== - // Tests for constructor : IdentityEdgeProperties(final Map xdmData) + // Tests for constructor : IdentityProperties(final Map xdmData) // ====================================================================================================================== @Test @@ -100,7 +100,7 @@ public void testConstruct_FromXDMData_LoadingDataFromPersistence() { ); // test - IdentityEdgeProperties props = new IdentityEdgeProperties(persistedIdentifiers); + IdentityProperties props = new IdentityProperties(persistedIdentifiers); // verify Map flatMap = flattenMap(props.toXDMData(false)); @@ -114,7 +114,7 @@ public void testConstruct_FromXDMData_LoadingDataFromPersistence() { @Test public void testConstruct_FromXDMData_NothingFromPersistence() { // test - IdentityEdgeProperties props = new IdentityEdgeProperties(null); + IdentityProperties props = new IdentityProperties(null); // verify assertEquals(0,flattenMap(props.toXDMData(false)).size()); @@ -128,7 +128,7 @@ public void testConstruct_FromXDMData_NothingFromPersistence() { @Test public void test_setECID_WillReplaceTheOldECID() { // setup - IdentityEdgeProperties props = new IdentityEdgeProperties(); + IdentityProperties props = new IdentityProperties(); // test 1 props.setECID(new ECID("primary")); @@ -154,7 +154,7 @@ public void test_setECID_WillReplaceTheOldECID() { @Test public void test_setECID_NullRemovesFromIdentityMap() { // setup - IdentityEdgeProperties props = new IdentityEdgeProperties(); + IdentityProperties props = new IdentityProperties(); // test 1 - set a valid ECID and then to null props.setECID(new ECID("primary")); @@ -173,7 +173,7 @@ public void test_setECID_NullRemovesFromIdentityMap() { @Test public void test_setECIDSecondary_WillReplaceTheOldECID() { // setup - IdentityEdgeProperties props = new IdentityEdgeProperties(); + IdentityProperties props = new IdentityProperties(); // test 1 props.setECID(new ECID("primary")); @@ -200,7 +200,7 @@ public void test_setECIDSecondary_WillReplaceTheOldECID() { @Test public void test_setECIDSecondary_NullRemovesFromIdentityMap() { // setup - IdentityEdgeProperties props = new IdentityEdgeProperties(createXDMIdentityMap( + IdentityProperties props = new IdentityProperties(createXDMIdentityMap( new TestECIDItem("primary"), new TestECIDItem("secondary") )); @@ -218,7 +218,7 @@ public void test_setECIDSecondary_NullRemovesFromIdentityMap() { @Test public void test_clearPrimaryECID_alsoClearsSecondaryECID() { // setup - IdentityEdgeProperties props = new IdentityEdgeProperties(createXDMIdentityMap( + IdentityProperties props = new IdentityProperties(createXDMIdentityMap( new TestECIDItem("primary"), new TestECIDItem("secondary") )); @@ -236,7 +236,7 @@ public void test_clearPrimaryECID_alsoClearsSecondaryECID() { @Test public void test_setPrimaryECIDPreservesSecondaryECID() { // setup - IdentityEdgeProperties props = new IdentityEdgeProperties(createXDMIdentityMap( + IdentityProperties props = new IdentityProperties(createXDMIdentityMap( new TestECIDItem("primary"), new TestECIDItem("secondary") )); @@ -253,7 +253,7 @@ public void test_setPrimaryECIDPreservesSecondaryECID() { @Test public void test_primaryECIDIsAlwaysTheFirstElement() { // setup - IdentityEdgeProperties props = new IdentityEdgeProperties(); + IdentityProperties props = new IdentityProperties(); // test props.setECID(new ECID("primary")); @@ -270,12 +270,12 @@ public void test_primaryECIDIsAlwaysTheFirstElement() { // ====================================================================================================================== - // Tests for "updateCustomerIdentifiers" is already covered in "handleUpdateRequest" tests in IdentityEdgeExtensionTests + // Tests for "updateCustomerIdentifiers" is already covered in "handleUpdateRequest" tests in IdentityExtensionTests // ====================================================================================================================== // ====================================================================================================================== - // Tests for "removeCustomerIdentifiers" is already covered in handleRemoveRequest tests in IdentityEdgeExtensionTests + // Tests for "removeCustomerIdentifiers" is already covered in handleRemoveRequest tests in IdentityExtensionTests // ====================================================================================================================== diff --git a/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeStateTests.java b/code/edgeidentity/src/test/java/com/adobe/marketing/mobile/edge/identity/IdentityStateTests.java similarity index 50% rename from code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeStateTests.java rename to code/edgeidentity/src/test/java/com/adobe/marketing/mobile/edge/identity/IdentityStateTests.java index a052abd3..3db3f138 100644 --- a/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeStateTests.java +++ b/code/edgeidentity/src/test/java/com/adobe/marketing/mobile/edge/identity/IdentityStateTests.java @@ -9,7 +9,7 @@ governing permissions and limitations under the License. */ -package com.adobe.marketing.mobile.identityedge; +package com.adobe.marketing.mobile.edge.identity; import android.app.Application; import android.content.Context; @@ -38,7 +38,7 @@ @RunWith(PowerMockRunner.class) @PrepareForTest({MobileCore.class}) -public class IdentityEdgeStateTests { +public class IdentityStateTests { @Mock Application mockApplication; @@ -58,16 +58,16 @@ public void before() throws Exception { Mockito.when(MobileCore.getApplication()).thenReturn(mockApplication); Mockito.when(mockApplication.getApplicationContext()).thenReturn(mockContext); - Mockito.when(mockContext.getSharedPreferences(IdentityEdgeConstants.DataStoreKey.DATASTORE_NAME, 0)).thenReturn(mockSharedPreference); + Mockito.when(mockContext.getSharedPreferences(IdentityConstants.DataStoreKey.DATASTORE_NAME, 0)).thenReturn(mockSharedPreference); Mockito.when(mockSharedPreference.edit()).thenReturn(mockSharedPreferenceEditor); } @Test - public void testIdentityEdgeState_BootupIfReadyGeneratesECID() { + public void testIdentityState_BootupIfReadyGeneratesECID() { // setup - IdentityEdgeState state = new IdentityEdgeState(new IdentityEdgeProperties()); - assertNull(state.getIdentityEdgeProperties().getECID()); + IdentityState state = new IdentityState(new IdentityProperties()); + assertNull(state.getIdentityProperties().getECID()); // test boolean result = state.bootupIfReady(); @@ -75,18 +75,18 @@ public void testIdentityEdgeState_BootupIfReadyGeneratesECID() { // verify assertTrue(result); - assertNotNull(state.getIdentityEdgeProperties().getECID()); + assertNotNull(state.getIdentityProperties().getECID()); } @Test - public void testIdentityEdgeState_BootupIfReadyLoadsDirectIdentityECID() { + public void testIdentityState_BootupIfReadyLoadsDirectIdentityECID() { // setup ECID ecid = new ECID(); - Mockito.when(mockContext.getSharedPreferences(IdentityEdgeConstants.DataStoreKey.IDENTITY_DIRECT_DATASTORE_NAME, 0)).thenReturn(mockSharedPreference); - Mockito.when(mockSharedPreference.getString(IdentityEdgeConstants.DataStoreKey.IDENTITY_DIRECT_ECID_KEY, null)).thenReturn(ecid.toString()); + Mockito.when(mockContext.getSharedPreferences(IdentityConstants.DataStoreKey.IDENTITY_DIRECT_DATASTORE_NAME, 0)).thenReturn(mockSharedPreference); + Mockito.when(mockSharedPreference.getString(IdentityConstants.DataStoreKey.IDENTITY_DIRECT_ECID_KEY, null)).thenReturn(ecid.toString()); - IdentityEdgeState state = new IdentityEdgeState(new IdentityEdgeProperties()); - assertNull(state.getIdentityEdgeProperties().getECID()); + IdentityState state = new IdentityState(new IdentityProperties()); + assertNull(state.getIdentityProperties().getECID()); // test boolean result = state.bootupIfReady(); @@ -94,17 +94,17 @@ public void testIdentityEdgeState_BootupIfReadyLoadsDirectIdentityECID() { // verify assertTrue(result); - assertEquals(ecid, state.getIdentityEdgeProperties().getECID()); + assertEquals(ecid, state.getIdentityProperties().getECID()); } @Test - public void testIdentityEdgeState_BootupIfReadyGenratesECIDWhenDirectECIDIsNull() { + public void testIdentityState_BootupIfReadyGenratesECIDWhenDirectECIDIsNull() { // setup - Mockito.when(mockContext.getSharedPreferences(IdentityEdgeConstants.DataStoreKey.IDENTITY_DIRECT_DATASTORE_NAME, 0)).thenReturn(mockSharedPreference); - Mockito.when(mockSharedPreference.getString(IdentityEdgeConstants.DataStoreKey.IDENTITY_DIRECT_ECID_KEY, null)).thenReturn(null); + Mockito.when(mockContext.getSharedPreferences(IdentityConstants.DataStoreKey.IDENTITY_DIRECT_DATASTORE_NAME, 0)).thenReturn(mockSharedPreference); + Mockito.when(mockSharedPreference.getString(IdentityConstants.DataStoreKey.IDENTITY_DIRECT_ECID_KEY, null)).thenReturn(null); - IdentityEdgeState state = new IdentityEdgeState(new IdentityEdgeProperties()); - assertNull(state.getIdentityEdgeProperties().getECID()); + IdentityState state = new IdentityState(new IdentityProperties()); + assertNull(state.getIdentityProperties().getECID()); // test boolean result = state.bootupIfReady(); @@ -112,19 +112,19 @@ public void testIdentityEdgeState_BootupIfReadyGenratesECIDWhenDirectECIDIsNull( // verify assertTrue(result); - assertNotNull(state.getIdentityEdgeProperties().getECID()); + assertNotNull(state.getIdentityProperties().getECID()); } @Test - public void testIdentityEdgeState_BootupIfReadyLoadsFromPersistence() { + public void testIdentityState_BootupIfReadyLoadsFromPersistence() { // setup - IdentityEdgeState state = new IdentityEdgeState(new IdentityEdgeProperties()); + IdentityState state = new IdentityState(new IdentityProperties()); - IdentityEdgeProperties persistedProps = new IdentityEdgeProperties(); + IdentityProperties persistedProps = new IdentityProperties(); persistedProps.setECID(new ECID()); final JSONObject jsonObject = new JSONObject(persistedProps.toXDMData(false)); final String propsJSON = jsonObject.toString(); - Mockito.when(mockSharedPreference.getString(IdentityEdgeConstants.DataStoreKey.IDENTITY_PROPERTIES, null)).thenReturn(propsJSON); + Mockito.when(mockSharedPreference.getString(IdentityConstants.DataStoreKey.IDENTITY_PROPERTIES, null)).thenReturn(propsJSON); // test boolean result = state.bootupIfReady(); @@ -132,23 +132,23 @@ public void testIdentityEdgeState_BootupIfReadyLoadsFromPersistence() { // verify assertTrue(result); - assertEquals(persistedProps.getECID().toString(), state.getIdentityEdgeProperties().getECID().toString()); + assertEquals(persistedProps.getECID().toString(), state.getIdentityProperties().getECID().toString()); } @Test - public void testIdentityEdgeState_BootupIfReadyLoadsFromPersistenceWhenDirectECIDIsValid() { + public void testIdentityState_BootupIfReadyLoadsFromPersistenceWhenDirectECIDIsValid() { // setup ECID ecid = new ECID(); - Mockito.when(mockContext.getSharedPreferences(IdentityEdgeConstants.DataStoreKey.IDENTITY_DIRECT_DATASTORE_NAME, 0)).thenReturn(mockSharedPreference); - Mockito.when(mockSharedPreference.getString(IdentityEdgeConstants.DataStoreKey.IDENTITY_DIRECT_ECID_KEY, null)).thenReturn(ecid.toString()); + Mockito.when(mockContext.getSharedPreferences(IdentityConstants.DataStoreKey.IDENTITY_DIRECT_DATASTORE_NAME, 0)).thenReturn(mockSharedPreference); + Mockito.when(mockSharedPreference.getString(IdentityConstants.DataStoreKey.IDENTITY_DIRECT_ECID_KEY, null)).thenReturn(ecid.toString()); - IdentityEdgeState state = new IdentityEdgeState(new IdentityEdgeProperties()); + IdentityState state = new IdentityState(new IdentityProperties()); - IdentityEdgeProperties persistedProps = new IdentityEdgeProperties(); + IdentityProperties persistedProps = new IdentityProperties(); persistedProps.setECID(new ECID()); final JSONObject jsonObject = new JSONObject(persistedProps.toXDMData(false)); final String propsJSON = jsonObject.toString(); - Mockito.when(mockSharedPreference.getString(IdentityEdgeConstants.DataStoreKey.IDENTITY_PROPERTIES, null)).thenReturn(propsJSON); + Mockito.when(mockSharedPreference.getString(IdentityConstants.DataStoreKey.IDENTITY_PROPERTIES, null)).thenReturn(propsJSON); // test boolean result = state.bootupIfReady(); @@ -156,97 +156,97 @@ public void testIdentityEdgeState_BootupIfReadyLoadsFromPersistenceWhenDirectECI // verify assertTrue(result); - assertEquals(persistedProps.getECID(), state.getIdentityEdgeProperties().getECID()); + assertEquals(persistedProps.getECID(), state.getIdentityProperties().getECID()); } @Test - public void testIdentityEdgeState_resetIdentifiers() { + public void testIdentityState_resetIdentifiers() { // setup - IdentityEdgeState state = new IdentityEdgeState(new IdentityEdgeProperties()); - state.getIdentityEdgeProperties().setECID(new ECID()); - state.getIdentityEdgeProperties().setECIDSecondary(new ECID()); - ECID existingEcid = state.getIdentityEdgeProperties().getECID(); + IdentityState state = new IdentityState(new IdentityProperties()); + state.getIdentityProperties().setECID(new ECID()); + state.getIdentityProperties().setECIDSecondary(new ECID()); + ECID existingEcid = state.getIdentityProperties().getECID(); // test state.resetIdentifiers(); // verify - assertNotEquals(existingEcid, state.getIdentityEdgeProperties().getECID()); // ECID should be regenerated - assertFalse(state.getIdentityEdgeProperties().getECID().toString().isEmpty()); // ECID should not be empty - assertNull(state.getIdentityEdgeProperties().getECIDSecondary()); // should be cleared + assertNotEquals(existingEcid, state.getIdentityProperties().getECID()); // ECID should be regenerated + assertFalse(state.getIdentityProperties().getECID().toString().isEmpty()); // ECID should not be empty + assertNull(state.getIdentityProperties().getECIDSecondary()); // should be cleared verify(mockSharedPreferenceEditor, Mockito.times(1)).apply(); // should save to data store } // ====================================================================================================================== - // Tests for "updateCustomerIdentifiers" is already covered in "handleUpdateRequest" tests in IdentityEdgeExtensionTests + // Tests for "updateCustomerIdentifiers" is already covered in "handleUpdateRequest" tests in IdentityExtensionTests // ====================================================================================================================== // ====================================================================================================================== - // Tests for "removeCustomerIdentifiers" is already covered in handleRemoveRequest tests in IdentityEdgeExtensionTests + // Tests for "removeCustomerIdentifiers" is already covered in handleRemoveRequest tests in IdentityExtensionTests // ====================================================================================================================== @Test - public void testIdentityEdgeState_updateLegacyExperienceCloudId() { - IdentityEdgeState state = new IdentityEdgeState(new IdentityEdgeProperties()); - state.getIdentityEdgeProperties().setECID(new ECID()); + public void testIdentityState_updateLegacyExperienceCloudId() { + IdentityState state = new IdentityState(new IdentityProperties()); + state.getIdentityProperties().setECID(new ECID()); ECID legacyEcid = new ECID(); // test state.updateLegacyExperienceCloudId(legacyEcid); // verify - assertEquals(legacyEcid, state.getIdentityEdgeProperties().getECIDSecondary()); + assertEquals(legacyEcid, state.getIdentityProperties().getECIDSecondary()); verify(mockSharedPreferenceEditor, Mockito.times(1)).apply(); } @Test - public void testIdentityEdgeState_updateLegacyExperienceCloudId_notSetWhenECIDSame() { - IdentityEdgeState state = new IdentityEdgeState(new IdentityEdgeProperties()); + public void testIdentityState_updateLegacyExperienceCloudId_notSetWhenECIDSame() { + IdentityState state = new IdentityState(new IdentityProperties()); ECID legacyEcid = new ECID(); - state.getIdentityEdgeProperties().setECID(legacyEcid); + state.getIdentityProperties().setECID(legacyEcid); state.updateLegacyExperienceCloudId(legacyEcid); // verify - assertNull(state.getIdentityEdgeProperties().getECIDSecondary()); + assertNull(state.getIdentityProperties().getECIDSecondary()); verify(mockSharedPreferenceEditor, Mockito.times(0)).apply(); } @Test - public void testIdentityEdgeState_updateLegacyExperienceCloudId_notSetWhenSecondaryECIDSame() { - IdentityEdgeState state = new IdentityEdgeState(new IdentityEdgeProperties()); - state.getIdentityEdgeProperties().setECID(new ECID()); + public void testIdentityState_updateLegacyExperienceCloudId_notSetWhenSecondaryECIDSame() { + IdentityState state = new IdentityState(new IdentityProperties()); + state.getIdentityProperties().setECID(new ECID()); ECID legacyEcid = new ECID(); - state.getIdentityEdgeProperties().setECIDSecondary(legacyEcid); + state.getIdentityProperties().setECIDSecondary(legacyEcid); state.updateLegacyExperienceCloudId(legacyEcid); - assertEquals(legacyEcid, state.getIdentityEdgeProperties().getECIDSecondary()); + assertEquals(legacyEcid, state.getIdentityProperties().getECIDSecondary()); verify(mockSharedPreferenceEditor, Mockito.times(0)).apply(); } @Test - public void testIdentityEdgeState_updateLegacyExperienceCloudId_clearsOnNull() { - IdentityEdgeState state = new IdentityEdgeState(new IdentityEdgeProperties()); - state.getIdentityEdgeProperties().setECID(new ECID()); - state.getIdentityEdgeProperties().setECIDSecondary(new ECID()); + public void testIdentityState_updateLegacyExperienceCloudId_clearsOnNull() { + IdentityState state = new IdentityState(new IdentityProperties()); + state.getIdentityProperties().setECID(new ECID()); + state.getIdentityProperties().setECIDSecondary(new ECID()); state.updateLegacyExperienceCloudId(null); - assertNull(state.getIdentityEdgeProperties().getECIDSecondary()); + assertNull(state.getIdentityProperties().getECIDSecondary()); verify(mockSharedPreferenceEditor, Mockito.times(1)).apply(); } @Test - public void testIdentityEdgeState_updateLegacyExperienceCloudId_notSetWhenExistingIsNull() { - IdentityEdgeState state = new IdentityEdgeState(new IdentityEdgeProperties()); - state.getIdentityEdgeProperties().setECID(new ECID()); + public void testIdentityState_updateLegacyExperienceCloudId_notSetWhenExistingIsNull() { + IdentityState state = new IdentityState(new IdentityProperties()); + state.getIdentityProperties().setECID(new ECID()); state.updateLegacyExperienceCloudId(null); - assertNull(state.getIdentityEdgeProperties().getECIDSecondary()); + assertNull(state.getIdentityProperties().getECIDSecondary()); verify(mockSharedPreferenceEditor, Mockito.times(0)).apply(); } } diff --git a/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeStorageServiceTests.java b/code/edgeidentity/src/test/java/com/adobe/marketing/mobile/edge/identity/IdentityStorageServiceTests.java similarity index 59% rename from code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeStorageServiceTests.java rename to code/edgeidentity/src/test/java/com/adobe/marketing/mobile/edge/identity/IdentityStorageServiceTests.java index e9fe6a64..10faee3b 100644 --- a/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeStorageServiceTests.java +++ b/code/edgeidentity/src/test/java/com/adobe/marketing/mobile/edge/identity/IdentityStorageServiceTests.java @@ -9,7 +9,7 @@ governing permissions and limitations under the License. */ -package com.adobe.marketing.mobile.identityedge; +package com.adobe.marketing.mobile.edge.identity; import android.app.Application; import android.content.Context; @@ -34,7 +34,7 @@ @RunWith(PowerMockRunner.class) @PrepareForTest({MobileCore.class}) -public class IdentityEdgeStorageServiceTests { +public class IdentityStorageServiceTests { @Mock Application mockApplication; @@ -54,7 +54,7 @@ public void before() throws Exception { Mockito.when(MobileCore.getApplication()).thenReturn(mockApplication); Mockito.when(mockApplication.getApplicationContext()).thenReturn(mockContext); - Mockito.when(mockContext.getSharedPreferences(IdentityEdgeConstants.DataStoreKey.DATASTORE_NAME, 0)).thenReturn(mockSharedPreference); + Mockito.when(mockContext.getSharedPreferences(IdentityConstants.DataStoreKey.DATASTORE_NAME, 0)).thenReturn(mockSharedPreference); Mockito.when(mockSharedPreference.edit()).thenReturn(mockSharedPreferenceEditor); } @@ -64,7 +64,7 @@ public void testStorageService_load_nullSharedPrefs() { Mockito.when(mockApplication.getApplicationContext()).thenReturn(null); // test - IdentityEdgeProperties props = IdentityEdgeStorageService.loadPropertiesFromPersistence(); + IdentityProperties props = IdentityStorageService.loadPropertiesFromPersistence(); // verify assertNull(props); @@ -73,10 +73,10 @@ public void testStorageService_load_nullSharedPrefs() { @Test public void testStorageService_load_emptyPrefs() { // setup - Mockito.when(mockSharedPreference.getString(IdentityEdgeConstants.DataStoreKey.IDENTITY_PROPERTIES, null)).thenReturn(null); + Mockito.when(mockSharedPreference.getString(IdentityConstants.DataStoreKey.IDENTITY_PROPERTIES, null)).thenReturn(null); // test - IdentityEdgeProperties props = IdentityEdgeStorageService.loadPropertiesFromPersistence(); + IdentityProperties props = IdentityStorageService.loadPropertiesFromPersistence(); // verify assertNull(props); @@ -85,10 +85,10 @@ public void testStorageService_load_emptyPrefs() { @Test public void testStorageService_load_invalidJSON() { // setup - Mockito.when(mockSharedPreference.getString(IdentityEdgeConstants.DataStoreKey.IDENTITY_PROPERTIES, null)).thenReturn("{"); + Mockito.when(mockSharedPreference.getString(IdentityConstants.DataStoreKey.IDENTITY_PROPERTIES, null)).thenReturn("{"); // test - IdentityEdgeProperties props = IdentityEdgeStorageService.loadPropertiesFromPersistence(); + IdentityProperties props = IdentityStorageService.loadPropertiesFromPersistence(); // verify assertNull(props); @@ -97,14 +97,14 @@ public void testStorageService_load_invalidJSON() { @Test public void testStorageService_load_validJSON() { // setup - IdentityEdgeProperties persistedProps = new IdentityEdgeProperties(); + IdentityProperties persistedProps = new IdentityProperties(); persistedProps.setECID(new ECID()); final JSONObject jsonObject = new JSONObject(persistedProps.toXDMData(false)); final String propsJSON = jsonObject.toString(); - Mockito.when(mockSharedPreference.getString(IdentityEdgeConstants.DataStoreKey.IDENTITY_PROPERTIES, null)).thenReturn(propsJSON); + Mockito.when(mockSharedPreference.getString(IdentityConstants.DataStoreKey.IDENTITY_PROPERTIES, null)).thenReturn(propsJSON); // test - IdentityEdgeProperties props = IdentityEdgeStorageService.loadPropertiesFromPersistence(); + IdentityProperties props = IdentityStorageService.loadPropertiesFromPersistence(); // verify assertEquals(persistedProps.toXDMData(false), props.toXDMData(false)); @@ -116,8 +116,8 @@ public void testStorageService_save_nullSharedPrefs() { Mockito.when(mockApplication.getApplicationContext()).thenReturn(null); // test - IdentityEdgeProperties props = new IdentityEdgeProperties(); - IdentityEdgeStorageService.savePropertiesToPersistence(props); + IdentityProperties props = new IdentityProperties(); + IdentityStorageService.savePropertiesToPersistence(props); // verify verify(mockSharedPreferenceEditor, never()).apply(); @@ -129,8 +129,8 @@ public void testStorageService_save_nullEditor() { Mockito.when(mockSharedPreference.edit()).thenReturn(null); // test - IdentityEdgeProperties props = new IdentityEdgeProperties(); - IdentityEdgeStorageService.savePropertiesToPersistence(props); + IdentityProperties props = new IdentityProperties(); + IdentityStorageService.savePropertiesToPersistence(props); // verify verify(mockSharedPreferenceEditor, never()).apply(); @@ -139,50 +139,50 @@ public void testStorageService_save_nullEditor() { @Test public void testStorageService_save_nullProps() { // test - IdentityEdgeStorageService.savePropertiesToPersistence(null); + IdentityStorageService.savePropertiesToPersistence(null); // verify - verify(mockSharedPreferenceEditor, Mockito.times(1)).remove(IdentityEdgeConstants.DataStoreKey.IDENTITY_PROPERTIES); + verify(mockSharedPreferenceEditor, Mockito.times(1)).remove(IdentityConstants.DataStoreKey.IDENTITY_PROPERTIES); verify(mockSharedPreferenceEditor, Mockito.times(1)).apply(); } @Test public void testStorageService_save_validProps() { // test - IdentityEdgeProperties props = new IdentityEdgeProperties(); + IdentityProperties props = new IdentityProperties(); props.setECID(new ECID()); - IdentityEdgeStorageService.savePropertiesToPersistence(props); + IdentityStorageService.savePropertiesToPersistence(props); // verify final JSONObject jsonObject = new JSONObject(props.toXDMData(false)); final String expectedJSON = jsonObject.toString(); - verify(mockSharedPreferenceEditor, Mockito.times(1)).putString(IdentityEdgeConstants.DataStoreKey.IDENTITY_PROPERTIES, expectedJSON); + verify(mockSharedPreferenceEditor, Mockito.times(1)).putString(IdentityConstants.DataStoreKey.IDENTITY_PROPERTIES, expectedJSON); verify(mockSharedPreferenceEditor, Mockito.times(1)).apply(); } @Test public void testStorageService_loadECID() { ECID ecid = new ECID(); - Mockito.when(mockContext.getSharedPreferences(IdentityEdgeConstants.DataStoreKey.IDENTITY_DIRECT_DATASTORE_NAME, 0)).thenReturn(mockSharedPreference); - Mockito.when(mockSharedPreference.getString(IdentityEdgeConstants.DataStoreKey.IDENTITY_DIRECT_ECID_KEY, null)).thenReturn(ecid.toString()); + Mockito.when(mockContext.getSharedPreferences(IdentityConstants.DataStoreKey.IDENTITY_DIRECT_DATASTORE_NAME, 0)).thenReturn(mockSharedPreference); + Mockito.when(mockSharedPreference.getString(IdentityConstants.DataStoreKey.IDENTITY_DIRECT_ECID_KEY, null)).thenReturn(ecid.toString()); - assertEquals(ecid, IdentityEdgeStorageService.loadEcidFromDirectIdentityPersistence()); + assertEquals(ecid, IdentityStorageService.loadEcidFromDirectIdentityPersistence()); } @Test public void testStorageService_loadECID_nullECID() { - Mockito.when(mockContext.getSharedPreferences(IdentityEdgeConstants.DataStoreKey.IDENTITY_DIRECT_DATASTORE_NAME, 0)).thenReturn(mockSharedPreference); - Mockito.when(mockSharedPreference.getString(IdentityEdgeConstants.DataStoreKey.IDENTITY_DIRECT_ECID_KEY, null)).thenReturn(null); + Mockito.when(mockContext.getSharedPreferences(IdentityConstants.DataStoreKey.IDENTITY_DIRECT_DATASTORE_NAME, 0)).thenReturn(mockSharedPreference); + Mockito.when(mockSharedPreference.getString(IdentityConstants.DataStoreKey.IDENTITY_DIRECT_ECID_KEY, null)).thenReturn(null); - assertNull(IdentityEdgeStorageService.loadEcidFromDirectIdentityPersistence()); + assertNull(IdentityStorageService.loadEcidFromDirectIdentityPersistence()); } @Test public void testStorageService_loadECID_emptyECID() { - Mockito.when(mockContext.getSharedPreferences(IdentityEdgeConstants.DataStoreKey.IDENTITY_DIRECT_DATASTORE_NAME, 0)).thenReturn(mockSharedPreference); - Mockito.when(mockSharedPreference.getString(IdentityEdgeConstants.DataStoreKey.IDENTITY_DIRECT_ECID_KEY, null)).thenReturn(""); + Mockito.when(mockContext.getSharedPreferences(IdentityConstants.DataStoreKey.IDENTITY_DIRECT_DATASTORE_NAME, 0)).thenReturn(mockSharedPreference); + Mockito.when(mockSharedPreference.getString(IdentityConstants.DataStoreKey.IDENTITY_DIRECT_ECID_KEY, null)).thenReturn(""); - assertNull(IdentityEdgeStorageService.loadEcidFromDirectIdentityPersistence()); + assertNull(IdentityStorageService.loadEcidFromDirectIdentityPersistence()); } } diff --git a/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeTests.java b/code/edgeidentity/src/test/java/com/adobe/marketing/mobile/edge/identity/IdentityTests.java similarity index 88% rename from code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeTests.java rename to code/edgeidentity/src/test/java/com/adobe/marketing/mobile/edge/identity/IdentityTests.java index e20453fe..a47efb72 100644 --- a/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeTests.java +++ b/code/edgeidentity/src/test/java/com/adobe/marketing/mobile/edge/identity/IdentityTests.java @@ -9,7 +9,7 @@ governing permissions and limitations under the License. */ -package com.adobe.marketing.mobile.identityedge; +package com.adobe.marketing.mobile.edge.identity; import com.adobe.marketing.mobile.AdobeCallback; import com.adobe.marketing.mobile.AdobeCallbackWithError; @@ -41,7 +41,7 @@ @RunWith(PowerMockRunner.class) @PrepareForTest({MobileCore.class}) -public class IdentityEdgeTests { +public class IdentityTests { @Before public void setup() { @@ -55,8 +55,8 @@ public void setup() { @Test public void test_extensionVersionAPI() { // test - String extensionVersion = IdentityEdge.extensionVersion(); - assertEquals("The Extension version API returns the correct value", IdentityEdgeConstants.EXTENSION_VERSION, + String extensionVersion = Identity.extensionVersion(); + assertEquals("The Extension version API returns the correct value", IdentityConstants.EXTENSION_VERSION, extensionVersion); } @@ -66,12 +66,12 @@ public void test_extensionVersionAPI() { @Test public void testRegistration() { // test - IdentityEdge.registerExtension(); + Identity.registerExtension(); final ArgumentCaptor callbackCaptor = ArgumentCaptor.forClass(ExtensionErrorCallback.class); - // The identity edge extension should register with core + // The identity extension should register with core PowerMockito.verifyStatic(MobileCore.class, Mockito.times(1)); - MobileCore.registerExtension(ArgumentMatchers.eq(IdentityEdgeExtension.class), callbackCaptor.capture()); + MobileCore.registerExtension(ArgumentMatchers.eq(IdentityExtension.class), callbackCaptor.capture()); // verify the callback ExtensionErrorCallback extensionErrorCallback = callbackCaptor.getValue(); @@ -94,7 +94,7 @@ public void testGetExperienceCloudId() { final List callbackReturnValues = new ArrayList<>(); // test - IdentityEdge.getExperienceCloudId(new AdobeCallback() { + Identity.getExperienceCloudId(new AdobeCallback() { @Override public void call(String s) { callbackReturnValues.add(s); @@ -107,9 +107,9 @@ public void call(String s) { // verify the dispatched event details Event dispatchedEvent = eventCaptor.getValue(); - assertEquals(IdentityEdgeConstants.EventNames.IDENTITY_REQUEST_IDENTITY_ECID, dispatchedEvent.getName()); - assertEquals(IdentityEdgeConstants.EventType.EDGE_IDENTITY.toLowerCase(), dispatchedEvent.getType()); - assertEquals(IdentityEdgeConstants.EventSource.REQUEST_IDENTITY.toLowerCase(), dispatchedEvent.getSource()); + assertEquals(IdentityConstants.EventNames.IDENTITY_REQUEST_IDENTITY_ECID, dispatchedEvent.getName()); + assertEquals(IdentityConstants.EventType.EDGE_IDENTITY.toLowerCase(), dispatchedEvent.getType()); + assertEquals(IdentityConstants.EventSource.REQUEST_IDENTITY.toLowerCase(), dispatchedEvent.getSource()); assertTrue(dispatchedEvent.getEventData().isEmpty()); // verify callback responses @@ -135,7 +135,7 @@ public void call(String s) { @Test public void testGetExperienceCloudId_nullCallback() { // test - IdentityEdge.getExperienceCloudId(null); + Identity.getExperienceCloudId(null); // verify PowerMockito.verifyStatic(MobileCore.class, Mockito.times(0)); @@ -162,7 +162,7 @@ public void call(Object o) { }; // test - IdentityEdge.getExperienceCloudId(callbackWithError); + Identity.getExperienceCloudId(callbackWithError); // verify if the event is dispatched PowerMockito.verifyStatic(MobileCore.class, Mockito.times(1)); @@ -196,7 +196,7 @@ public void call(Object o) { }; // test - IdentityEdge.getExperienceCloudId(callbackWithError); + Identity.getExperienceCloudId(callbackWithError); // verify if the event is dispatched PowerMockito.verifyStatic(MobileCore.class, Mockito.times(1)); @@ -232,7 +232,7 @@ public void call(Object o) { }; // test - IdentityEdge.getExperienceCloudId(callbackWithError); + Identity.getExperienceCloudId(callbackWithError); // verify if the event is dispatched PowerMockito.verifyStatic(MobileCore.class, Mockito.times(1)); @@ -260,7 +260,7 @@ public void testUpdateIdentities() { IdentityMap map = new IdentityMap(); map.addItem(new IdentityItem("id", AuthenticatedState.AUTHENTICATED, true),"mainspace"); map.addItem(new IdentityItem("idtwo", AuthenticatedState.LOGGED_OUT, false),"secondspace"); - IdentityEdge.updateIdentities(map); + Identity.updateIdentities(map); // verify PowerMockito.verifyStatic(MobileCore.class, Mockito.times(1)); @@ -272,9 +272,9 @@ public void testUpdateIdentities() { // verify the dispatched event details Event dispatchedEvent = eventCaptor.getValue(); - assertEquals(IdentityEdgeConstants.EventNames.UPDATE_IDENTITIES, dispatchedEvent.getName()); - assertEquals(IdentityEdgeConstants.EventType.EDGE_IDENTITY.toLowerCase(), dispatchedEvent.getType()); - assertEquals(IdentityEdgeConstants.EventSource.UPDATE_IDENTITY.toLowerCase(), dispatchedEvent.getSource()); + assertEquals(IdentityConstants.EventNames.UPDATE_IDENTITIES, dispatchedEvent.getName()); + assertEquals(IdentityConstants.EventType.EDGE_IDENTITY.toLowerCase(), dispatchedEvent.getType()); + assertEquals(IdentityConstants.EventSource.UPDATE_IDENTITY.toLowerCase(), dispatchedEvent.getSource()); assertEquals(map.asEventData(), dispatchedEvent.getEventData()); } @@ -282,8 +282,8 @@ public void testUpdateIdentities() { public void testUpdateIdentitiesNullAndEmptyMap() { // test IdentityMap map = new IdentityMap(); - IdentityEdge.updateIdentities(map); - IdentityEdge.updateIdentities(null); + Identity.updateIdentities(map); + Identity.updateIdentities(null); // verify none of these API calls dispatch an event PowerMockito.verifyStatic(MobileCore.class, Mockito.times(0)); @@ -299,16 +299,16 @@ public void testRemoveIdentity() { IdentityItem sampleItem = new IdentityItem("sample", AuthenticatedState.AMBIGUOUS, false); // test - IdentityEdge.removeIdentity(sampleItem, "namespace"); + Identity.removeIdentity(sampleItem, "namespace"); // verify dispatch event PowerMockito.verifyStatic(MobileCore.class, Mockito.times(1)); MobileCore.dispatchEvent(eventCaptor.capture(), extensionErrorCallbackCaptor.capture()); Event dispatchedEvent = eventCaptor.getValue(); - assertEquals(IdentityEdgeConstants.EventNames.REMOVE_IDENTITIES, dispatchedEvent.getName()); - assertEquals(IdentityEdgeConstants.EventType.EDGE_IDENTITY.toLowerCase(), dispatchedEvent.getType()); - assertEquals(IdentityEdgeConstants.EventSource.REMOVE_IDENTITY.toLowerCase(), dispatchedEvent.getSource()); + assertEquals(IdentityConstants.EventNames.REMOVE_IDENTITIES, dispatchedEvent.getName()); + assertEquals(IdentityConstants.EventType.EDGE_IDENTITY.toLowerCase(), dispatchedEvent.getType()); + assertEquals(IdentityConstants.EventSource.REMOVE_IDENTITY.toLowerCase(), dispatchedEvent.getSource()); IdentityMap sampleInputIdentitymap = new IdentityMap(); sampleInputIdentitymap.addItem(sampleItem,"namespace"); assertEquals(sampleInputIdentitymap.asEventData(), dispatchedEvent.getEventData()); @@ -324,9 +324,9 @@ public void testRemoveIdentity_WithInvalidInputs() { IdentityItem sampleItem = new IdentityItem("sample", AuthenticatedState.AMBIGUOUS, false); // test - IdentityEdge.removeIdentity(null, "namespace"); - IdentityEdge.removeIdentity(sampleItem, ""); - IdentityEdge.removeIdentity(sampleItem, null); + Identity.removeIdentity(null, "namespace"); + Identity.removeIdentity(sampleItem, ""); + Identity.removeIdentity(sampleItem, null); // verify none of these API calls dispatch an event PowerMockito.verifyStatic(MobileCore.class, Mockito.times(0)); @@ -346,7 +346,7 @@ public void testGetIdentities() throws Exception { final List callbackReturnValues = new ArrayList<>(); // test - IdentityEdge.getIdentities(new AdobeCallback() { + Identity.getIdentities(new AdobeCallback() { @Override public void call(IdentityMap map) { callbackReturnValues.add(map); @@ -359,9 +359,9 @@ public void call(IdentityMap map) { // verify the dispatched event details Event dispatchedEvent = eventCaptor.getValue(); - assertEquals(IdentityEdgeConstants.EventNames.REQUEST_IDENTITIES, dispatchedEvent.getName()); - assertEquals(IdentityEdgeConstants.EventType.EDGE_IDENTITY.toLowerCase(), dispatchedEvent.getType()); - assertEquals(IdentityEdgeConstants.EventSource.REQUEST_IDENTITY.toLowerCase(), dispatchedEvent.getSource()); + assertEquals(IdentityConstants.EventNames.REQUEST_IDENTITIES, dispatchedEvent.getName()); + assertEquals(IdentityConstants.EventType.EDGE_IDENTITY.toLowerCase(), dispatchedEvent.getType()); + assertEquals(IdentityConstants.EventSource.REQUEST_IDENTITY.toLowerCase(), dispatchedEvent.getSource()); assertTrue(dispatchedEvent.getEventData().isEmpty()); // verify callback responses @@ -409,7 +409,7 @@ public void call(IdentityMap map) { @Test public void testGetIdentities_nullCallback() { // test - IdentityEdge.getIdentities(null); + Identity.getIdentities(null); // verify PowerMockito.verifyStatic(MobileCore.class, Mockito.times(0)); @@ -436,7 +436,7 @@ public void call(Object o) { }; // test - IdentityEdge.getIdentities(callbackWithError); + Identity.getIdentities(callbackWithError); // verify if the event is dispatched PowerMockito.verifyStatic(MobileCore.class, Mockito.times(1)); @@ -470,7 +470,7 @@ public void call(Object o) { }; // test - IdentityEdge.getIdentities(callbackWithError); + Identity.getIdentities(callbackWithError); // verify if the event is dispatched PowerMockito.verifyStatic(MobileCore.class, Mockito.times(1)); @@ -506,7 +506,7 @@ public void call(Object o) { }; // test - IdentityEdge.getIdentities(callbackWithError); + Identity.getIdentities(callbackWithError); // verify if the event is dispatched PowerMockito.verifyStatic(MobileCore.class, Mockito.times(1)); @@ -525,7 +525,7 @@ public void call(Object o) { // Private method // ======================================================================================== private Event buildIdentityResponseEvent(final Map eventData) { - return new Event.Builder(IdentityEdgeConstants.EventNames.IDENTITY_REQUEST_IDENTITY_ECID, IdentityEdgeConstants.EventType.EDGE_IDENTITY, IdentityEdgeConstants.EventSource.RESPONSE_IDENTITY).setEventData(eventData).build(); + return new Event.Builder(IdentityConstants.EventNames.IDENTITY_REQUEST_IDENTITY_ECID, IdentityConstants.EventType.EDGE_IDENTITY, IdentityConstants.EventSource.RESPONSE_IDENTITY).setEventData(eventData).build(); } } diff --git a/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/ListenerIdentityEdgeRemoveIdentityTests.java b/code/edgeidentity/src/test/java/com/adobe/marketing/mobile/edge/identity/ListenerEdgeIdentityRemoveIdentityTests.java similarity index 56% rename from code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/ListenerIdentityEdgeRemoveIdentityTests.java rename to code/edgeidentity/src/test/java/com/adobe/marketing/mobile/edge/identity/ListenerEdgeIdentityRemoveIdentityTests.java index 72b0c75f..2fef866e 100644 --- a/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/ListenerIdentityEdgeRemoveIdentityTests.java +++ b/code/edgeidentity/src/test/java/com/adobe/marketing/mobile/edge/identity/ListenerEdgeIdentityRemoveIdentityTests.java @@ -9,7 +9,7 @@ governing permissions and limitations under the License. */ -package com.adobe.marketing.mobile.identityedge; +package com.adobe.marketing.mobile.edge.identity; import com.adobe.marketing.mobile.Event; import com.adobe.marketing.mobile.MobileCore; @@ -25,58 +25,58 @@ import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; -public class ListenerIdentityEdgeRemoveIdentityTests { +public class ListenerEdgeIdentityRemoveIdentityTests { @Mock - private IdentityEdgeExtension mockIdentityEdgeExtension; + private IdentityExtension mockIdentityExtension; - private ListenerIdentityEdgeRemoveIdentity listener; + private ListenerEdgeIdentityRemoveIdentity listener; @Before public void setup() { - mockIdentityEdgeExtension = Mockito.mock(IdentityEdgeExtension.class); + mockIdentityExtension = Mockito.mock(IdentityExtension.class); MobileCore.start(null); - listener = spy(new ListenerIdentityEdgeRemoveIdentity(null, IdentityEdgeConstants.EventType.EDGE_IDENTITY, IdentityEdgeConstants.EventSource.REMOVE_IDENTITY)); + listener = spy(new ListenerEdgeIdentityRemoveIdentity(null, IdentityConstants.EventType.EDGE_IDENTITY, IdentityConstants.EventSource.REMOVE_IDENTITY)); } @Test public void testHear() { // setup - Event event = new Event.Builder("Remove Identity", IdentityEdgeConstants.EventType.EDGE_IDENTITY, - IdentityEdgeConstants.EventSource.REMOVE_IDENTITY).build(); - doReturn(mockIdentityEdgeExtension).when(listener).getIdentityEdgeExtension(); + Event event = new Event.Builder("Remove Identity", IdentityConstants.EventType.EDGE_IDENTITY, + IdentityConstants.EventSource.REMOVE_IDENTITY).build(); + doReturn(mockIdentityExtension).when(listener).getIdentityExtension(); // test listener.hear(event); // verify - verify(mockIdentityEdgeExtension, times(1)).handleRemoveIdentity(event); + verify(mockIdentityExtension, times(1)).handleRemoveIdentity(event); } @Test public void testHear_WhenParentExtensionNull() { // setup - Event event = new Event.Builder("Remove Identity", IdentityEdgeConstants.EventType.EDGE_IDENTITY, - IdentityEdgeConstants.EventSource.REMOVE_IDENTITY).build(); - doReturn(null).when(listener).getIdentityEdgeExtension(); + Event event = new Event.Builder("Remove Identity", IdentityConstants.EventType.EDGE_IDENTITY, + IdentityConstants.EventSource.REMOVE_IDENTITY).build(); + doReturn(null).when(listener).getIdentityExtension(); // test listener.hear(event); // verify - verify(mockIdentityEdgeExtension, times(0)).handleRemoveIdentity(any(Event.class)); + verify(mockIdentityExtension, times(0)).handleRemoveIdentity(any(Event.class)); } @Test public void testHear_WhenEventNull() { // setup - doReturn(null).when(listener).getIdentityEdgeExtension(); - doReturn(mockIdentityEdgeExtension).when(listener).getIdentityEdgeExtension(); + doReturn(null).when(listener).getIdentityExtension(); + doReturn(mockIdentityExtension).when(listener).getIdentityExtension(); // test listener.hear(null); // verify - verify(mockIdentityEdgeExtension, times(0)).handleRemoveIdentity(any(Event.class)); + verify(mockIdentityExtension, times(0)).handleRemoveIdentity(any(Event.class)); } } diff --git a/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/ListenerIdentityRequestIdentityTests.java b/code/edgeidentity/src/test/java/com/adobe/marketing/mobile/edge/identity/ListenerEdgeIdentityRequestIdentityTests.java similarity index 56% rename from code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/ListenerIdentityRequestIdentityTests.java rename to code/edgeidentity/src/test/java/com/adobe/marketing/mobile/edge/identity/ListenerEdgeIdentityRequestIdentityTests.java index 3034a9e0..45db493b 100644 --- a/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/ListenerIdentityRequestIdentityTests.java +++ b/code/edgeidentity/src/test/java/com/adobe/marketing/mobile/edge/identity/ListenerEdgeIdentityRequestIdentityTests.java @@ -9,7 +9,7 @@ governing permissions and limitations under the License. */ -package com.adobe.marketing.mobile.identityedge; +package com.adobe.marketing.mobile.edge.identity; import com.adobe.marketing.mobile.Event; import com.adobe.marketing.mobile.MobileCore; @@ -25,58 +25,58 @@ import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; -public class ListenerIdentityRequestIdentityTests { +public class ListenerEdgeIdentityRequestIdentityTests { @Mock - private IdentityEdgeExtension mockIdentityEdgeExtension; + private IdentityExtension mockIdentityExtension; - private ListenerIdentityRequestIdentity listener; + private ListenerEdgeIdentityRequestIdentity listener; @Before public void setup() { - mockIdentityEdgeExtension = Mockito.mock(IdentityEdgeExtension.class); + mockIdentityExtension = Mockito.mock(IdentityExtension.class); MobileCore.start(null); - listener = spy(new ListenerIdentityRequestIdentity(null, IdentityEdgeConstants.EventType.IDENTITY, IdentityEdgeConstants.EventSource.REQUEST_IDENTITY)); + listener = spy(new ListenerEdgeIdentityRequestIdentity(null, IdentityConstants.EventType.IDENTITY, IdentityConstants.EventSource.REQUEST_IDENTITY)); } @Test public void testHear() { // setup - Event event = new Event.Builder("Request Identity", IdentityEdgeConstants.EventType.IDENTITY, - IdentityEdgeConstants.EventSource.REQUEST_IDENTITY).build(); - doReturn(mockIdentityEdgeExtension).when(listener).getIdentityEdgeExtension(); + Event event = new Event.Builder("Request Identity", IdentityConstants.EventType.IDENTITY, + IdentityConstants.EventSource.REQUEST_IDENTITY).build(); + doReturn(mockIdentityExtension).when(listener).getIdentityExtension(); // test listener.hear(event); // verify - verify(mockIdentityEdgeExtension, times(1)).handleIdentityRequest(event); + verify(mockIdentityExtension, times(1)).handleIdentityRequest(event); } @Test public void testHear_WhenParentExtensionNull() { // setup - Event event = new Event.Builder("Request Identity", IdentityEdgeConstants.EventType.IDENTITY, - IdentityEdgeConstants.EventSource.REQUEST_IDENTITY).build(); - doReturn(null).when(listener).getIdentityEdgeExtension(); + Event event = new Event.Builder("Request Identity", IdentityConstants.EventType.IDENTITY, + IdentityConstants.EventSource.REQUEST_IDENTITY).build(); + doReturn(null).when(listener).getIdentityExtension(); // test listener.hear(event); // verify - verify(mockIdentityEdgeExtension, times(0)).handleIdentityRequest(any(Event.class)); + verify(mockIdentityExtension, times(0)).handleIdentityRequest(any(Event.class)); } @Test public void testHear_WhenEventNull() { // setup - doReturn(null).when(listener).getIdentityEdgeExtension(); - doReturn(mockIdentityEdgeExtension).when(listener).getIdentityEdgeExtension(); + doReturn(null).when(listener).getIdentityExtension(); + doReturn(mockIdentityExtension).when(listener).getIdentityExtension(); // test listener.hear(null); // verify - verify(mockIdentityEdgeExtension, times(0)).handleIdentityRequest(any(Event.class)); + verify(mockIdentityExtension, times(0)).handleIdentityRequest(any(Event.class)); } } diff --git a/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/ListenerIdentityEdgeUpdateIdentityTests.java b/code/edgeidentity/src/test/java/com/adobe/marketing/mobile/edge/identity/ListenerEdgeIdentityUpdateIdentityTests.java similarity index 56% rename from code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/ListenerIdentityEdgeUpdateIdentityTests.java rename to code/edgeidentity/src/test/java/com/adobe/marketing/mobile/edge/identity/ListenerEdgeIdentityUpdateIdentityTests.java index 2e0ce62a..440a6bcc 100644 --- a/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/ListenerIdentityEdgeUpdateIdentityTests.java +++ b/code/edgeidentity/src/test/java/com/adobe/marketing/mobile/edge/identity/ListenerEdgeIdentityUpdateIdentityTests.java @@ -9,7 +9,7 @@ governing permissions and limitations under the License. */ -package com.adobe.marketing.mobile.identityedge; +package com.adobe.marketing.mobile.edge.identity; import com.adobe.marketing.mobile.Event; import com.adobe.marketing.mobile.MobileCore; @@ -25,58 +25,58 @@ import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; -public class ListenerIdentityEdgeUpdateIdentityTests { +public class ListenerEdgeIdentityUpdateIdentityTests { @Mock - private IdentityEdgeExtension mockIdentityEdgeExtension; + private IdentityExtension mockIdentityExtension; - private ListenerIdentityEdgeUpdateIdentity listener; + private ListenerEdgeIdentityUpdateIdentity listener; @Before public void setup() { - mockIdentityEdgeExtension = Mockito.mock(IdentityEdgeExtension.class); + mockIdentityExtension = Mockito.mock(IdentityExtension.class); MobileCore.start(null); - listener = spy(new ListenerIdentityEdgeUpdateIdentity(null, IdentityEdgeConstants.EventType.EDGE_IDENTITY, IdentityEdgeConstants.EventSource.UPDATE_IDENTITY)); + listener = spy(new ListenerEdgeIdentityUpdateIdentity(null, IdentityConstants.EventType.EDGE_IDENTITY, IdentityConstants.EventSource.UPDATE_IDENTITY)); } @Test public void testHear() { // setup - Event event = new Event.Builder("Update Identities", IdentityEdgeConstants.EventType.EDGE_IDENTITY, - IdentityEdgeConstants.EventSource.UPDATE_IDENTITY).build(); - doReturn(mockIdentityEdgeExtension).when(listener).getIdentityEdgeExtension(); + Event event = new Event.Builder("Update Identities", IdentityConstants.EventType.EDGE_IDENTITY, + IdentityConstants.EventSource.UPDATE_IDENTITY).build(); + doReturn(mockIdentityExtension).when(listener).getIdentityExtension(); // test listener.hear(event); // verify - verify(mockIdentityEdgeExtension, times(1)).handleUpdateIdentities(event); + verify(mockIdentityExtension, times(1)).handleUpdateIdentities(event); } @Test public void testHear_WhenParentExtensionNull() { // setup - Event event = new Event.Builder("Update Identities", IdentityEdgeConstants.EventType.EDGE_IDENTITY, - IdentityEdgeConstants.EventSource.UPDATE_IDENTITY).build(); - doReturn(null).when(listener).getIdentityEdgeExtension(); + Event event = new Event.Builder("Update Identities", IdentityConstants.EventType.EDGE_IDENTITY, + IdentityConstants.EventSource.UPDATE_IDENTITY).build(); + doReturn(null).when(listener).getIdentityExtension(); // test listener.hear(event); // verify - verify(mockIdentityEdgeExtension, times(0)).handleUpdateIdentities(any(Event.class)); + verify(mockIdentityExtension, times(0)).handleUpdateIdentities(any(Event.class)); } @Test public void testHear_WhenEventNull() { // setup - doReturn(null).when(listener).getIdentityEdgeExtension(); - doReturn(mockIdentityEdgeExtension).when(listener).getIdentityEdgeExtension(); + doReturn(null).when(listener).getIdentityExtension(); + doReturn(mockIdentityExtension).when(listener).getIdentityExtension(); // test listener.hear(null); // verify - verify(mockIdentityEdgeExtension, times(0)).handleUpdateIdentities(any(Event.class)); + verify(mockIdentityExtension, times(0)).handleUpdateIdentities(any(Event.class)); } } diff --git a/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/ListenerGenericIdentityRequestContentTests.java b/code/edgeidentity/src/test/java/com/adobe/marketing/mobile/edge/identity/ListenerGenericIdentityRequestContentTests.java similarity index 61% rename from code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/ListenerGenericIdentityRequestContentTests.java rename to code/edgeidentity/src/test/java/com/adobe/marketing/mobile/edge/identity/ListenerGenericIdentityRequestContentTests.java index fd93f91b..fbfab812 100644 --- a/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/ListenerGenericIdentityRequestContentTests.java +++ b/code/edgeidentity/src/test/java/com/adobe/marketing/mobile/edge/identity/ListenerGenericIdentityRequestContentTests.java @@ -9,7 +9,7 @@ governing permissions and limitations under the License. */ -package com.adobe.marketing.mobile.identityedge; +package com.adobe.marketing.mobile.edge.identity; import com.adobe.marketing.mobile.Event; import com.adobe.marketing.mobile.MobileCore; @@ -28,55 +28,55 @@ public class ListenerGenericIdentityRequestContentTests { @Mock - private IdentityEdgeExtension mockIdentityEdgeExtension; + private IdentityExtension mockIdentityExtension; private ListenerGenericIdentityRequestContent listener; @Before public void setup() { - mockIdentityEdgeExtension = Mockito.mock(IdentityEdgeExtension.class); + mockIdentityExtension = Mockito.mock(IdentityExtension.class); MobileCore.start(null); - listener = spy(new ListenerGenericIdentityRequestContent(null, IdentityEdgeConstants.EventType.GENERIC_IDENTITY, IdentityEdgeConstants.EventSource.REQUEST_CONTENT)); + listener = spy(new ListenerGenericIdentityRequestContent(null, IdentityConstants.EventType.GENERIC_IDENTITY, IdentityConstants.EventSource.REQUEST_CONTENT)); } @Test public void testHear() { // setup - Event event = new Event.Builder("Generic Identity Request event", IdentityEdgeConstants.EventType.GENERIC_IDENTITY, - IdentityEdgeConstants.EventSource.REQUEST_CONTENT).build(); - doReturn(mockIdentityEdgeExtension).when(listener).getIdentityEdgeExtension(); + Event event = new Event.Builder("Generic Identity Request event", IdentityConstants.EventType.GENERIC_IDENTITY, + IdentityConstants.EventSource.REQUEST_CONTENT).build(); + doReturn(mockIdentityExtension).when(listener).getIdentityExtension(); // test listener.hear(event); // verify - verify(mockIdentityEdgeExtension, times(1)).handleGenericIdentityRequest(event); + verify(mockIdentityExtension, times(1)).handleGenericIdentityRequest(event); } @Test public void testHear_WhenParentExtensionNull() { // setup - Event event = new Event.Builder("Generic Identity Request event", IdentityEdgeConstants.EventType.GENERIC_IDENTITY, - IdentityEdgeConstants.EventSource.REQUEST_CONTENT).build(); - doReturn(null).when(listener).getIdentityEdgeExtension(); + Event event = new Event.Builder("Generic Identity Request event", IdentityConstants.EventType.GENERIC_IDENTITY, + IdentityConstants.EventSource.REQUEST_CONTENT).build(); + doReturn(null).when(listener).getIdentityExtension(); // test listener.hear(event); // verify - verify(mockIdentityEdgeExtension, times(0)).handleGenericIdentityRequest(any(Event.class)); + verify(mockIdentityExtension, times(0)).handleGenericIdentityRequest(any(Event.class)); } @Test public void testHear_WhenEventNull() { // setup - doReturn(null).when(listener).getIdentityEdgeExtension(); - doReturn(mockIdentityEdgeExtension).when(listener).getIdentityEdgeExtension(); + doReturn(null).when(listener).getIdentityExtension(); + doReturn(mockIdentityExtension).when(listener).getIdentityExtension(); // test listener.hear(null); // verify - verify(mockIdentityEdgeExtension, times(0)).handleGenericIdentityRequest(any(Event.class)); + verify(mockIdentityExtension, times(0)).handleGenericIdentityRequest(any(Event.class)); } } diff --git a/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/ListenerHubSharedStateTests.java b/code/edgeidentity/src/test/java/com/adobe/marketing/mobile/edge/identity/ListenerHubSharedStateTests.java similarity index 62% rename from code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/ListenerHubSharedStateTests.java rename to code/edgeidentity/src/test/java/com/adobe/marketing/mobile/edge/identity/ListenerHubSharedStateTests.java index ae0c7911..fd2de2ad 100644 --- a/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/ListenerHubSharedStateTests.java +++ b/code/edgeidentity/src/test/java/com/adobe/marketing/mobile/edge/identity/ListenerHubSharedStateTests.java @@ -9,7 +9,7 @@ governing permissions and limitations under the License. */ -package com.adobe.marketing.mobile.identityedge; +package com.adobe.marketing.mobile.edge.identity; import com.adobe.marketing.mobile.Event; import com.adobe.marketing.mobile.MobileCore; @@ -27,55 +27,55 @@ public class ListenerHubSharedStateTests { @Mock - private IdentityEdgeExtension mockIdentityEdgeExtension; + private IdentityExtension mockIdentityExtension; private ListenerHubSharedState listener; @Before public void setup() { - mockIdentityEdgeExtension = Mockito.mock(IdentityEdgeExtension.class); + mockIdentityExtension = Mockito.mock(IdentityExtension.class); MobileCore.start(null); - listener = spy(new ListenerHubSharedState(null, IdentityEdgeConstants.EventType.HUB, IdentityEdgeConstants.EventSource.SHARED_STATE)); + listener = spy(new ListenerHubSharedState(null, IdentityConstants.EventType.HUB, IdentityConstants.EventSource.SHARED_STATE)); } @Test public void testHear() { // setup - Event event = new Event.Builder("Shared State Change", IdentityEdgeConstants.EventType.HUB, - IdentityEdgeConstants.EventSource.SHARED_STATE).build(); - doReturn(mockIdentityEdgeExtension).when(listener).getIdentityEdgeExtension(); + Event event = new Event.Builder("Shared State Change", IdentityConstants.EventType.HUB, + IdentityConstants.EventSource.SHARED_STATE).build(); + doReturn(mockIdentityExtension).when(listener).getIdentityExtension(); // test listener.hear(event); // verify - verify(mockIdentityEdgeExtension, times(1)).handleHubSharedState(event); + verify(mockIdentityExtension, times(1)).handleHubSharedState(event); } @Test public void testHear_WhenParentExtensionNull() { // setup - Event event = new Event.Builder("Shared State Change", IdentityEdgeConstants.EventType.HUB, - IdentityEdgeConstants.EventSource.SHARED_STATE).build(); - doReturn(null).when(listener).getIdentityEdgeExtension(); + Event event = new Event.Builder("Shared State Change", IdentityConstants.EventType.HUB, + IdentityConstants.EventSource.SHARED_STATE).build(); + doReturn(null).when(listener).getIdentityExtension(); // test listener.hear(event); // verify - verify(mockIdentityEdgeExtension, times(0)).handleHubSharedState(any(Event.class)); + verify(mockIdentityExtension, times(0)).handleHubSharedState(any(Event.class)); } @Test public void testHear_WhenEventNull() { // setup - doReturn(null).when(listener).getIdentityEdgeExtension(); - doReturn(mockIdentityEdgeExtension).when(listener).getIdentityEdgeExtension(); + doReturn(null).when(listener).getIdentityExtension(); + doReturn(mockIdentityExtension).when(listener).getIdentityExtension(); // test listener.hear(null); // verify - verify(mockIdentityEdgeExtension, times(0)).handleHubSharedState(any(Event.class)); + verify(mockIdentityExtension, times(0)).handleHubSharedState(any(Event.class)); } } diff --git a/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/ListenerIdentityRequestResetTests.java b/code/edgeidentity/src/test/java/com/adobe/marketing/mobile/edge/identity/ListenerIdentityRequestResetTests.java similarity index 62% rename from code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/ListenerIdentityRequestResetTests.java rename to code/edgeidentity/src/test/java/com/adobe/marketing/mobile/edge/identity/ListenerIdentityRequestResetTests.java index 3fef0700..ef420eca 100644 --- a/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/ListenerIdentityRequestResetTests.java +++ b/code/edgeidentity/src/test/java/com/adobe/marketing/mobile/edge/identity/ListenerIdentityRequestResetTests.java @@ -9,7 +9,7 @@ governing permissions and limitations under the License. */ -package com.adobe.marketing.mobile.identityedge; +package com.adobe.marketing.mobile.edge.identity; import com.adobe.marketing.mobile.Event; import com.adobe.marketing.mobile.MobileCore; @@ -27,55 +27,55 @@ public class ListenerIdentityRequestResetTests { @Mock - private IdentityEdgeExtension mockIdentityEdgeExtension; + private IdentityExtension mockIdentityExtension; private ListenerIdentityRequestReset listener; @Before public void setup() { - mockIdentityEdgeExtension = Mockito.mock(IdentityEdgeExtension.class); + mockIdentityExtension = Mockito.mock(IdentityExtension.class); MobileCore.start(null); - listener = spy(new ListenerIdentityRequestReset(null, IdentityEdgeConstants.EventType.EDGE_IDENTITY, IdentityEdgeConstants.EventSource.REQUEST_RESET)); + listener = spy(new ListenerIdentityRequestReset(null, IdentityConstants.EventType.EDGE_IDENTITY, IdentityConstants.EventSource.REQUEST_RESET)); } @Test public void testHear() { // setup - Event event = new Event.Builder("Request Identity", IdentityEdgeConstants.EventType.IDENTITY, - IdentityEdgeConstants.EventSource.REQUEST_IDENTITY).build(); - doReturn(mockIdentityEdgeExtension).when(listener).getIdentityEdgeExtension(); + Event event = new Event.Builder("Request Identity", IdentityConstants.EventType.IDENTITY, + IdentityConstants.EventSource.REQUEST_IDENTITY).build(); + doReturn(mockIdentityExtension).when(listener).getIdentityExtension(); // test listener.hear(event); // verify - verify(mockIdentityEdgeExtension, times(1)).handleRequestReset(event); + verify(mockIdentityExtension, times(1)).handleRequestReset(event); } @Test public void testHear_WhenParentExtensionNull() { // setup - Event event = new Event.Builder("Request Identity", IdentityEdgeConstants.EventType.IDENTITY, - IdentityEdgeConstants.EventSource.REQUEST_IDENTITY).build(); - doReturn(null).when(listener).getIdentityEdgeExtension(); + Event event = new Event.Builder("Request Identity", IdentityConstants.EventType.IDENTITY, + IdentityConstants.EventSource.REQUEST_IDENTITY).build(); + doReturn(null).when(listener).getIdentityExtension(); // test listener.hear(event); // verify - verify(mockIdentityEdgeExtension, times(0)).handleRequestReset(any(Event.class)); + verify(mockIdentityExtension, times(0)).handleRequestReset(any(Event.class)); } @Test public void testHear_WhenEventNull() { // setup - doReturn(null).when(listener).getIdentityEdgeExtension(); - doReturn(mockIdentityEdgeExtension).when(listener).getIdentityEdgeExtension(); + doReturn(null).when(listener).getIdentityExtension(); + doReturn(mockIdentityExtension).when(listener).getIdentityExtension(); // test listener.hear(null); // verify - verify(mockIdentityEdgeExtension, times(0)).handleRequestReset(any(Event.class)); + verify(mockIdentityExtension, times(0)).handleRequestReset(any(Event.class)); } } diff --git a/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/UtilsTests.java b/code/edgeidentity/src/test/java/com/adobe/marketing/mobile/edge/identity/UtilsTests.java similarity index 98% rename from code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/UtilsTests.java rename to code/edgeidentity/src/test/java/com/adobe/marketing/mobile/edge/identity/UtilsTests.java index 7621abeb..1c802a51 100644 --- a/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/UtilsTests.java +++ b/code/edgeidentity/src/test/java/com/adobe/marketing/mobile/edge/identity/UtilsTests.java @@ -9,9 +9,7 @@ governing permissions and limitations under the License. */ -package com.adobe.marketing.mobile.identityedge; - -import com.adobe.marketing.mobile.MobilePrivacyStatus; +package com.adobe.marketing.mobile.edge.identity; import org.junit.Test; diff --git a/code/gradle.properties b/code/gradle.properties index a7e35f41..9b84fdff 100644 --- a/code/gradle.properties +++ b/code/gradle.properties @@ -5,17 +5,17 @@ org.gradle.jvmargs=-Xmx2048m android.useAndroidX=true android.enableJetifier=true -moduleProjectName=identityedge -moduleName=identityedge -moduleAARName=identityedge-phone-release.aar +moduleProjectName=edgeidentity +moduleName=edgeidentity +moduleAARName=edgeidentity-phone-release.aar moduleVersion=1.0.0-alpha-1 -mavenRepoName=AdobeMobileIdentityEdgeSdk -mavenRepoDescription=Adobe Experience Platform Identity Edge extension for the Adobe Experience Platform Mobile SDK +mavenRepoName=AdobeMobileEdgeIdentitySdk +mavenRepoDescription=Adobe Experience Platform Edge Identity extension for the Adobe Experience Platform Mobile SDK mavenUploadDryRunFlag=false # production versions for production build -mavenCoreVersion=1.6.1-SNAPSHOT +mavenCoreVersion=1.7.0 #artifactory variables artifactoryUrl=https://dummyurl/ diff --git a/code/settings.gradle b/code/settings.gradle index 246d7dac..c53dddfb 100644 --- a/code/settings.gradle +++ b/code/settings.gradle @@ -1,3 +1,3 @@ -include ':identityedge' +include ':edgeidentity' include ':app' -rootProject.name = "identityedge-sdk" \ No newline at end of file +rootProject.name = "edgeidentity-sdk" \ No newline at end of file From 63d516177a2056d273b10d845a2c893ff358b5f6 Mon Sep 17 00:00:00 2001 From: Nick Porter <43650450+nporter-adbe@users.noreply.github.com> Date: Tue, 23 Mar 2021 10:34:08 -0600 Subject: [PATCH 017/101] Don't dispatch reset complete on boot/update/remove (#18) --- .../edge/identity/IdentityExtension.java | 32 +++++++++---------- .../edge/identity/IdentityExtensionTests.java | 19 ++++++++++- 2 files changed, 34 insertions(+), 17 deletions(-) diff --git a/code/edgeidentity/src/main/java/com/adobe/marketing/mobile/edge/identity/IdentityExtension.java b/code/edgeidentity/src/main/java/com/adobe/marketing/mobile/edge/identity/IdentityExtension.java index e8480ffb..930af1ae 100644 --- a/code/edgeidentity/src/main/java/com/adobe/marketing/mobile/edge/identity/IdentityExtension.java +++ b/code/edgeidentity/src/main/java/com/adobe/marketing/mobile/edge/identity/IdentityExtension.java @@ -191,7 +191,7 @@ void handleHubSharedState(final Event event) { */ void handleIdentityRequest(final Event event) { if (!canProcessEvents()) { - MobileCore.log(LoggingMode.DEBUG, LOG_TAG, "Unable to process request reset event. canProcessEvents returned false."); + MobileCore.log(LoggingMode.DEBUG, LOG_TAG, "Unable to process request identities event. canProcessEvents returned false."); return; } @@ -225,6 +225,21 @@ void handleRequestReset(final Event event) { } state.resetIdentifiers(); shareIdentityXDMSharedState(event); + + // dispatch reset complete event + final Event responseEvent = new Event.Builder(IdentityConstants.EventNames.RESET_IDENTITIES_COMPLETE, + IdentityConstants.EventType.EDGE_IDENTITY, + IdentityConstants.EventSource.RESET_COMPLETE).build(); + + MobileCore.dispatchEvent(responseEvent, new ExtensionErrorCallback() { + @Override + public void error(ExtensionError extensionError) { + MobileCore.log(LoggingMode.DEBUG, LOG_TAG, "Failed to dispatch Edge Identity reset response event for event " + + event.getUniqueIdentifier() + + " with error " + + extensionError.getErrorName()); + } + }); } /** @@ -269,21 +284,6 @@ public void error(final ExtensionError extensionError) { }; extensionApi.setXDMSharedEventState(state.getIdentityProperties().toXDMData(false), event, errorCallback); - - // dispatch response event - final Event responseEvent = new Event.Builder(IdentityConstants.EventNames.RESET_IDENTITIES_COMPLETE, - IdentityConstants.EventType.EDGE_IDENTITY, - IdentityConstants.EventSource.RESET_COMPLETE).build(); - - MobileCore.dispatchEvent(responseEvent, new ExtensionErrorCallback() { - @Override - public void error(ExtensionError extensionError) { - MobileCore.log(LoggingMode.DEBUG, LOG_TAG, "Failed to dispatch Edge Identity reset response event for event " + - event.getUniqueIdentifier() + - " with error " + - extensionError.getErrorName()); - } - }); } /** diff --git a/code/edgeidentity/src/test/java/com/adobe/marketing/mobile/edge/identity/IdentityExtensionTests.java b/code/edgeidentity/src/test/java/com/adobe/marketing/mobile/edge/identity/IdentityExtensionTests.java index 2c18a9c8..60811c64 100644 --- a/code/edgeidentity/src/test/java/com/adobe/marketing/mobile/edge/identity/IdentityExtensionTests.java +++ b/code/edgeidentity/src/test/java/com/adobe/marketing/mobile/edge/identity/IdentityExtensionTests.java @@ -263,6 +263,12 @@ public void test_handleHubSharedState_updateLegacyEcidOnDirectIdentityStateChang verify(mockExtensionApi, times(1)).setXDMSharedEventState(sharedStateCaptor.capture(), eq(event), any(ExtensionErrorCallback.class)); Map sharedState = sharedStateCaptor.getValue(); assertEquals("1234", flattenMap(sharedState).get("identityMap.ECID[1].id")); // Legacy ECID is set as a secondary ECID + + final ArgumentCaptor eventCaptor = ArgumentCaptor.forClass(Event.class); + // verify no event dispatched + PowerMockito.verifyStatic(MobileCore.class, Mockito.times(0)); + MobileCore.dispatchEvent(eventCaptor.capture(), any(ExtensionErrorCallback.class)); + assertTrue(eventCaptor.getAllValues().isEmpty()); } @Test @@ -374,7 +380,7 @@ public void test_handleUpdateIdentities() throws Exception { ); final ArgumentCaptor sharedStateCaptor = ArgumentCaptor.forClass(Map.class); final ArgumentCaptor persistenceValueCaptor = ArgumentCaptor.forClass(String.class); - + final ArgumentCaptor eventCaptor = ArgumentCaptor.forClass(Event.class); // test Event updateIdentityEvent = buildUpdateIdentityRequest(identityXDM); @@ -387,6 +393,11 @@ public void test_handleUpdateIdentities() throws Exception { assertEquals("ambiguous", sharedState.get("identityMap.UserId[0].authenticatedState")); assertEquals("false", sharedState.get("identityMap.UserId[0].primary")); + // verify no event dispatched + PowerMockito.verifyStatic(MobileCore.class, Mockito.times(0)); + MobileCore.dispatchEvent(eventCaptor.capture(), any(ExtensionErrorCallback.class)); + assertTrue(eventCaptor.getAllValues().isEmpty()); + // verify persistence verify(mockSharedPreferenceEditor, times(2)).putString(eq(IdentityConstants.DataStoreKey.IDENTITY_PROPERTIES), persistenceValueCaptor.capture()); Map persistedData = flattenJSONString(persistenceValueCaptor.getAllValues().get(1)); @@ -479,6 +490,8 @@ public void test_handleRemoveIdentity() throws Exception { final ArgumentCaptor sharedStateCaptor = ArgumentCaptor.forClass(Map.class); final ArgumentCaptor persistenceValueCaptor = ArgumentCaptor.forClass(String.class); + final ArgumentCaptor eventCaptor = ArgumentCaptor.forClass(Event.class); + extension = new IdentityExtension(mockExtensionApi); // test @@ -494,6 +507,10 @@ public void test_handleRemoveIdentity() throws Exception { assertNull(sharedState.get("identityMap.UserId[0].id")); assertEquals("token", sharedState.get("identityMap.PushId[0].id")); + // verify no event dispatched + PowerMockito.verifyStatic(MobileCore.class, Mockito.times(0)); + MobileCore.dispatchEvent(eventCaptor.capture(), any(ExtensionErrorCallback.class)); + assertTrue(eventCaptor.getAllValues().isEmpty()); // verify persistence verify(mockSharedPreferenceEditor, times(2)).putString(eq(IdentityConstants.DataStoreKey.IDENTITY_PROPERTIES), persistenceValueCaptor.capture()); From 15679d7d908959fc5eaa107f08d1f16ae0f54141 Mon Sep 17 00:00:00 2001 From: Pravin Prakash Kumar Date: Thu, 25 Mar 2021 10:20:19 -0700 Subject: [PATCH 018/101] [AMSDK-11312] - Handle boot event + Bugfixes (#19) * [AMSDK-11312] - Handle Boot event and share initial shared state * [AMSDK-11312] - Bug fix on merge Identities * [AMSDK-11312] - Bootsup during extension registration * [AMSDK-11312] - update tests for boot up change * Cleanup asXDMIdentityMap + unit test renaming --- .../mobile/edge/identity/Identity.java | 8 +- .../edge/identity/IdentityConstants.java | 1 + .../edge/identity/IdentityExtension.java | 75 ++++------ .../mobile/edge/identity/IdentityMap.java | 78 ++++++---- .../edge/identity/IdentityProperties.java | 13 +- .../mobile/edge/identity/IdentityState.java | 19 +-- .../ListenerEdgeIdentityRemoveIdentity.java | 2 +- .../ListenerEdgeIdentityUpdateIdentity.java | 2 +- .../edge/identity/ListenerEventHubBoot.java | 60 ++++++++ .../ListenerIdentityRequestReset.java | 2 +- .../edge/identity/IdentityExtensionTests.java | 21 +-- .../edge/identity/IdentityMapTests.java | 140 ++++++++++++------ .../identity/IdentityPropertiesTests.java | 4 +- .../edge/identity/IdentityStateTests.java | 37 ++--- .../identity/IdentityStorageServiceTests.java | 22 +-- .../mobile/edge/identity/IdentityTests.java | 4 +- .../identity/ListenerEventHubBootTest.java | 68 +++++++++ 17 files changed, 356 insertions(+), 200 deletions(-) create mode 100644 code/edgeidentity/src/main/java/com/adobe/marketing/mobile/edge/identity/ListenerEventHubBoot.java create mode 100644 code/edgeidentity/src/test/java/com/adobe/marketing/mobile/edge/identity/ListenerEventHubBootTest.java diff --git a/code/edgeidentity/src/main/java/com/adobe/marketing/mobile/edge/identity/Identity.java b/code/edgeidentity/src/main/java/com/adobe/marketing/mobile/edge/identity/Identity.java index 3ee00d3e..b3fc3750 100644 --- a/code/edgeidentity/src/main/java/com/adobe/marketing/mobile/edge/identity/Identity.java +++ b/code/edgeidentity/src/main/java/com/adobe/marketing/mobile/edge/identity/Identity.java @@ -84,7 +84,7 @@ public void call(Event responseEvent) { return; } - final IdentityMap identityMap = IdentityMap.fromData(responseEvent.getEventData()); + final IdentityMap identityMap = IdentityMap.fromXDMMap(responseEvent.getEventData()); if (identityMap == null) { MobileCore.log(LoggingMode.DEBUG, LOG_TAG, "Failed to read IdentityMap from response event, invoking error callback with AdobeError.UNEXPECTED_ERROR"); returnError(callback, AdobeError.UNEXPECTED_ERROR); @@ -125,7 +125,7 @@ public void error(final ExtensionError extensionError) { final Event updateIdentitiesEvent = new Event.Builder(IdentityConstants.EventNames.UPDATE_IDENTITIES, IdentityConstants.EventType.EDGE_IDENTITY, - IdentityConstants.EventSource.UPDATE_IDENTITY).setEventData(identityMap.asEventData()).build(); + IdentityConstants.EventSource.UPDATE_IDENTITY).setEventData(identityMap.asXDMMap(false)).build(); MobileCore.dispatchEvent(updateIdentitiesEvent, errorCallback); } @@ -162,7 +162,7 @@ public void error(final ExtensionError extensionError) { final Event removeIdentitiesEvent = new Event.Builder(IdentityConstants.EventNames.REMOVE_IDENTITIES, IdentityConstants.EventType.EDGE_IDENTITY, - IdentityConstants.EventSource.REMOVE_IDENTITY).setEventData(identityMap.asEventData()).build(); + IdentityConstants.EventSource.REMOVE_IDENTITY).setEventData(identityMap.asXDMMap(false)).build(); MobileCore.dispatchEvent(removeIdentitiesEvent, errorCallback); } @@ -200,7 +200,7 @@ public void call(Event responseEvent) { return; } - final IdentityMap identityMap = IdentityMap.fromData(responseEvent.getEventData()); + final IdentityMap identityMap = IdentityMap.fromXDMMap(responseEvent.getEventData()); if (identityMap == null) { MobileCore.log(LoggingMode.DEBUG, LOG_TAG, "Failed to read IdentityMap from response event, invoking error callback with AdobeError.UNEXPECTED_ERROR"); returnError(callback, AdobeError.UNEXPECTED_ERROR); diff --git a/code/edgeidentity/src/main/java/com/adobe/marketing/mobile/edge/identity/IdentityConstants.java b/code/edgeidentity/src/main/java/com/adobe/marketing/mobile/edge/identity/IdentityConstants.java index 0331b5d2..2351a1f4 100644 --- a/code/edgeidentity/src/main/java/com/adobe/marketing/mobile/edge/identity/IdentityConstants.java +++ b/code/edgeidentity/src/main/java/com/adobe/marketing/mobile/edge/identity/IdentityConstants.java @@ -27,6 +27,7 @@ final class EventSource { static final String REQUEST_RESET = "com.adobe.eventSource.requestReset"; static final String SHARED_STATE = "com.adobe.eventSource.sharedState"; static final String RESET_COMPLETE = "com.adobe.eventSource.resetComplete"; + static final String BOOTED = "com.adobe.eventSource.booted"; private EventSource() { } } diff --git a/code/edgeidentity/src/main/java/com/adobe/marketing/mobile/edge/identity/IdentityExtension.java b/code/edgeidentity/src/main/java/com/adobe/marketing/mobile/edge/identity/IdentityExtension.java index 930af1ae..5a05f490 100644 --- a/code/edgeidentity/src/main/java/com/adobe/marketing/mobile/edge/identity/IdentityExtension.java +++ b/code/edgeidentity/src/main/java/com/adobe/marketing/mobile/edge/identity/IdentityExtension.java @@ -33,6 +33,8 @@ class IdentityExtension extends Extension { * Called during the Identity extension's registration. * The following listeners are registered during this extension's registration. *

      + *
    • Listener {@link ListenerEventHubBoot} to listen for event with eventType {@link IdentityConstants.EventType#HUB} + * and EventSource {@link IdentityConstants.EventSource#BOOTED}
    • *
    • Listener {@link ListenerEdgeIdentityRequestIdentity} to listen for event with eventType {@link IdentityConstants.EventType#EDGE_IDENTITY} * and EventSource {@link IdentityConstants.EventSource#REQUEST_IDENTITY}
    • *
    • Listener {@link ListenerGenericIdentityRequestContent} to listen for event with eventType {@link IdentityConstants.EventType#GENERIC_IDENTITY} @@ -63,12 +65,14 @@ public void error(final ExtensionError extensionError) { } }; + extensionApi.registerEventListener(IdentityConstants.EventType.HUB, IdentityConstants.EventSource.BOOTED, ListenerEventHubBoot.class, listenerErrorCallback); extensionApi.registerEventListener(IdentityConstants.EventType.EDGE_IDENTITY, IdentityConstants.EventSource.REQUEST_IDENTITY, ListenerEdgeIdentityRequestIdentity.class, listenerErrorCallback); extensionApi.registerEventListener(IdentityConstants.EventType.GENERIC_IDENTITY, IdentityConstants.EventSource.REQUEST_CONTENT, ListenerGenericIdentityRequestContent.class, listenerErrorCallback); extensionApi.registerEventListener(IdentityConstants.EventType.EDGE_IDENTITY, IdentityConstants.EventSource.UPDATE_IDENTITY, ListenerEdgeIdentityUpdateIdentity.class, listenerErrorCallback); extensionApi.registerEventListener(IdentityConstants.EventType.EDGE_IDENTITY, IdentityConstants.EventSource.REMOVE_IDENTITY, ListenerEdgeIdentityRemoveIdentity.class, listenerErrorCallback); extensionApi.registerEventListener(IdentityConstants.EventType.HUB, IdentityConstants.EventSource.SHARED_STATE, ListenerHubSharedState.class, listenerErrorCallback); extensionApi.registerEventListener(IdentityConstants.EventType.GENERIC_IDENTITY, IdentityConstants.EventSource.REQUEST_RESET, ListenerIdentityRequestReset.class, listenerErrorCallback); + state.bootUp(); } /** @@ -91,18 +95,34 @@ protected String getVersion() { return IdentityConstants.EXTENSION_VERSION; } + + /** + * Call this method with the EventHub's Boot event to handle the boot operation of the {@code Identity} Extension. + *

      + * On boot share the initial identities loaded from persistence to XDM shared state. + * + * @param event the boot {@link Event} + */ + void handleEventHubBoot(final Event event) { + + // share the initial XDMSharedState on bootUp + final Map currentIdentities = state.getIdentityProperties().toXDMData(false); + if (currentIdentities == null || currentIdentities.isEmpty()) { + MobileCore.log(LoggingMode.WARNING, LOG_TAG, "Nothing loaded from persistence for initial Identity XDM shared state on boot"); + return; + } + + shareIdentityXDMSharedState(event); + } + /** * Handles update identity requests to add/update customer identifiers. * * @param event the edge update identity {@link Event} */ void handleUpdateIdentities(final Event event) { - if (!canProcessEvents()) { - MobileCore.log(LoggingMode.DEBUG, LOG_TAG, "Unable to process update identity event. canProcessEvents returned false."); - return; - } final Map eventData = event.getEventData(); // do not need to null check on eventData, as they are done on listeners - final IdentityMap map = IdentityMap.fromData(eventData); + final IdentityMap map = IdentityMap.fromXDMMap(eventData); if (map == null) { MobileCore.log(LoggingMode.DEBUG, LOG_TAG, "Failed to update identifiers as no identifiers were found in the event data."); return; @@ -118,12 +138,8 @@ void handleUpdateIdentities(final Event event) { * @param event the edge remove identity request {@link Event} */ void handleRemoveIdentity(final Event event) { - if (!canProcessEvents()) { - MobileCore.log(LoggingMode.DEBUG, LOG_TAG, "Unable to process remove identity event. canProcessEvents returned false."); - return; - } final Map eventData = event.getEventData(); // do not need to null check on eventData, as they are done on listeners - final IdentityMap map = IdentityMap.fromData(eventData); + final IdentityMap map = IdentityMap.fromXDMMap(eventData); if (map == null) { MobileCore.log(LoggingMode.DEBUG, LOG_TAG, "Failed to remove identifiers as no identifiers were found in the event data."); return; @@ -145,11 +161,6 @@ void handleGenericIdentityRequest(final Event event) { * @param event an event of type {@code com.adobe.eventType.hub} and source {@code com.adobe.eventSource.sharedState} */ void handleHubSharedState(final Event event) { - if (!canProcessEvents()) { - MobileCore.log(LoggingMode.DEBUG, LOG_TAG, "Unable to process direct Identity shared state change event. canProcessEvents returned false."); - return; - } - if (event == null || event.getEventData() == null) { return; } @@ -190,12 +201,7 @@ void handleHubSharedState(final Event event) { * @param event the identity request {@link Event} */ void handleIdentityRequest(final Event event) { - if (!canProcessEvents()) { - MobileCore.log(LoggingMode.DEBUG, LOG_TAG, "Unable to process request identities event. canProcessEvents returned false."); - return; - } - - Map xdmData = state.getIdentityProperties().toXDMData(true); + Map xdmData = state.getIdentityProperties().toXDMData(false); Event responseEvent = new Event.Builder(IdentityConstants.EventNames.IDENTITY_RESPONSE_CONTENT_ONE_TIME, IdentityConstants.EventType.EDGE_IDENTITY, IdentityConstants.EventSource.RESPONSE_IDENTITY) @@ -219,10 +225,6 @@ public void error(ExtensionError extensionError) { * @param event the identity request reset {@link Event} */ void handleRequestReset(final Event event) { - if (!canProcessEvents()) { - MobileCore.log(LoggingMode.DEBUG, LOG_TAG, "Unable to process request reset event. canProcessEvents returned false."); - return; - } state.resetIdentifiers(); shareIdentityXDMSharedState(event); @@ -286,27 +288,4 @@ public void error(final ExtensionError extensionError) { extensionApi.setXDMSharedEventState(state.getIdentityProperties().toXDMData(false), event, errorCallback); } - /** - * Determines if this Identity is ready to handle events, this is determined by if the extension has booted up - * - * @return True if we can process events, false otherwise - */ - private boolean canProcessEvents() { - if (state.hasBooted()) { - return true; - } // we have booted, return true - - final ExtensionApi extensionApi = super.getApi(); - if (extensionApi == null) { - MobileCore.log(LoggingMode.DEBUG, LOG_TAG, "ExtensionApi is null, unable to process events"); - return false; - } - - if (state.bootupIfReady()) { - extensionApi.setXDMSharedEventState(state.getIdentityProperties().toXDMData(false), null, null); - return true; - } - - return false; // cannot handle any events until we have booted - } } diff --git a/code/edgeidentity/src/main/java/com/adobe/marketing/mobile/edge/identity/IdentityMap.java b/code/edgeidentity/src/main/java/com/adobe/marketing/mobile/edge/identity/IdentityMap.java index f96c7d88..5bebcac4 100644 --- a/code/edgeidentity/src/main/java/com/adobe/marketing/mobile/edge/identity/IdentityMap.java +++ b/code/edgeidentity/src/main/java/com/adobe/marketing/mobile/edge/identity/IdentityMap.java @@ -129,25 +129,6 @@ void addItem(final IdentityItem item, final String namespace, final boolean isFi } - /** - * @return a {@link Map} representing this {@link IdentityMap} object - */ - Map>> toObjectMap() { - final Map>> map = new HashMap<>(); - - for (String namespace : identityItems.keySet()) { - final List> namespaceIds = new ArrayList<>(); - - for (IdentityItem identityItem : identityItems.get(namespace)) { - namespaceIds.add(identityItem.toObjectMap()); - } - - map.put(namespace, namespaceIds); - } - - return map; - } - /** * Merge the given map on to this {@link IdentityMap}. Any {@link IdentityItem} in map which shares the same * namespace and id as an item in this {@code IdentityMap} will replace that {@code IdentityItem}. @@ -211,25 +192,54 @@ boolean clearItemsForNamespace(final String namespace) { } /** - * Use this method to cast the {@code IdentityMap} as eventData for an SDK Event. + * Use this method to cast the {@link IdentityMap} as {@link Map} to be passed as EventData for an SDK Event. + * This method returns an empty map if the {@code IdentityMap} contains no data * - * @return {@link Map} representation of IdentityMap + * @return {@code Map} representation of xdm formatted IdentityMap */ - Map asEventData() { - return new HashMap(identityItems); + Map asXDMMap() { + return asXDMMap(true); } + /** + * Use this method to cast the {@link IdentityMap} as {@link Map} to be passed as EventData for an SDK Event. + * + * @param allowEmpty If false and if this {@code IdentityMap} contains no data, then returns a map with empty xdmFormatted Identity Map. + * If true and if this {@code IdentityMap} contains no data, then returns an empty map + * @return {@code Map} representation of xdm formatted IdentityMap + */ + Map asXDMMap(final boolean allowEmpty) { + final Map xdmMap = new HashMap<>(); + final Map>> identityMap = new HashMap<>(); + + for (String namespace : identityItems.keySet()) { + final List> namespaceIds = new ArrayList<>(); + + for (IdentityItem identityItem : identityItems.get(namespace)) { + namespaceIds.add(identityItem.toObjectMap()); + } + + identityMap.put(namespace, namespaceIds); + } + + if (!identityMap.isEmpty() || !allowEmpty) { + xdmMap.put(IdentityConstants.XDMKeys.IDENTITY_MAP, identityMap); + } + return xdmMap; + } /** - * Creates an {@link IdentityMap} from the + * Creates an {@link IdentityMap} from the given xdm formatted {@link Map} + * Returns null if the provided map is null/empty. + * Return null if the provided map is not in Identity Map's XDM format. * - * @return {@link Map} representation of IdentityMap + * @return {@link Map} XDM format representation of IdentityMap */ - static IdentityMap fromData(Map data) { - if (data == null) { + static IdentityMap fromXDMMap(final Map map) { + if (Utils.isNullOrEmpty(map)) { return null; } - final Map identityMapDict = (HashMap) data.get(IdentityConstants.XDMKeys.IDENTITY_MAP); + final Map identityMapDict = (HashMap) map.get(IdentityConstants.XDMKeys.IDENTITY_MAP); if (identityMapDict == null) { return null; } @@ -256,7 +266,7 @@ static IdentityMap fromData(Map data) { // ======================================================================================== // private methods // ======================================================================================== - private void addItemToMap(final IdentityItem item, final String namespace, final boolean isFirstItem) { + private void addItemToMap(final IdentityItem newItem, final String namespace, final boolean isFirstItem) { // check if namespace exists final List itemList; @@ -266,10 +276,14 @@ private void addItemToMap(final IdentityItem item, final String namespace, final itemList = new ArrayList<>(); } - if (isFirstItem) { - itemList.add(0, item); + // Check if the item already exist in the current ItemList + int index = itemList.indexOf(newItem); + if (index >= 0) { + itemList.set(index,newItem); + } else if (isFirstItem) { + itemList.add(0, newItem); } else { - itemList.add(item); + itemList.add(newItem); } identityItems.put(namespace, itemList); diff --git a/code/edgeidentity/src/main/java/com/adobe/marketing/mobile/edge/identity/IdentityProperties.java b/code/edgeidentity/src/main/java/com/adobe/marketing/mobile/edge/identity/IdentityProperties.java index 322f6e67..1da82e86 100644 --- a/code/edgeidentity/src/main/java/com/adobe/marketing/mobile/edge/identity/IdentityProperties.java +++ b/code/edgeidentity/src/main/java/com/adobe/marketing/mobile/edge/identity/IdentityProperties.java @@ -43,7 +43,7 @@ class IdentityProperties { * @param xdmData a map representing the initialization data for this {@code IdentityProperties} instance */ IdentityProperties(final Map xdmData) { - IdentityMap map = IdentityMap.fromData(xdmData); + IdentityMap map = IdentityMap.fromXDMMap(xdmData); this.identityMap = map == null ? new IdentityMap() : map; // always keep an empty identity map so there is no need for null check } @@ -160,17 +160,10 @@ void removeCustomerIdentifiers(final IdentityMap map) { * Converts this into an event data representation in XDM format * * @param allowEmpty If this {@link IdentityProperties} contains no data, return a dictionary with a single {@link IdentityMap} key - * @return A dictionary representing this in XDM format + * @return A {@link Map} representing this in XDM format */ Map toXDMData(final boolean allowEmpty) { - final Map map = new HashMap<>(); - - final Map>> dict = identityMap.toObjectMap(); - if (dict != null && (!dict.isEmpty() || allowEmpty)) { - map.put(IdentityConstants.XDMKeys.IDENTITY_MAP, dict); - } - - return map; + return identityMap.asXDMMap(allowEmpty); } /** diff --git a/code/edgeidentity/src/main/java/com/adobe/marketing/mobile/edge/identity/IdentityState.java b/code/edgeidentity/src/main/java/com/adobe/marketing/mobile/edge/identity/IdentityState.java index 70da5171..4b0e7dc0 100644 --- a/code/edgeidentity/src/main/java/com/adobe/marketing/mobile/edge/identity/IdentityState.java +++ b/code/edgeidentity/src/main/java/com/adobe/marketing/mobile/edge/identity/IdentityState.java @@ -20,7 +20,6 @@ */ class IdentityState { private static String LOG_TAG = "IdentityState"; - private boolean hasBooted = false; private IdentityProperties identityProperties; /** @@ -39,22 +38,14 @@ IdentityProperties getIdentityProperties() { return identityProperties; } - /** - * @return Returns true if this extension has booted, false otherwise - */ - boolean hasBooted() { - return hasBooted; - } /** * Completes init for the Identity extension. - * - * @return True if we should share state after bootup, false otherwise + * Attempts to load the already persisted identities from persistence into {@link #identityProperties} + * If no ECID is loaded from persistence (ideally meaning first launch), then we attempt to read ECID for the direct Identity Extension. + * If there is no ECID loaded from the persistence of direct Identity Extension, then and new ECID is generated and persisted finishing the bootUp sequence. */ - boolean bootupIfReady() { - if (hasBooted) { - return true; - } + void bootUp() { // Load properties from local storage identityProperties = IdentityStorageService.loadPropertiesFromPersistence(); @@ -75,9 +66,7 @@ boolean bootupIfReady() { IdentityStorageService.savePropertiesToPersistence(identityProperties); } - hasBooted = true; MobileCore.log(LoggingMode.DEBUG, LOG_TAG, "Edge Identity has successfully booted up"); - return true; } /** diff --git a/code/edgeidentity/src/main/java/com/adobe/marketing/mobile/edge/identity/ListenerEdgeIdentityRemoveIdentity.java b/code/edgeidentity/src/main/java/com/adobe/marketing/mobile/edge/identity/ListenerEdgeIdentityRemoveIdentity.java index 5b30359a..9257996b 100644 --- a/code/edgeidentity/src/main/java/com/adobe/marketing/mobile/edge/identity/ListenerEdgeIdentityRemoveIdentity.java +++ b/code/edgeidentity/src/main/java/com/adobe/marketing/mobile/edge/identity/ListenerEdgeIdentityRemoveIdentity.java @@ -17,7 +17,7 @@ import com.adobe.marketing.mobile.LoggingMode; import com.adobe.marketing.mobile.MobileCore; -public class ListenerEdgeIdentityRemoveIdentity extends ExtensionListener { +class ListenerEdgeIdentityRemoveIdentity extends ExtensionListener { /** * Constructor. diff --git a/code/edgeidentity/src/main/java/com/adobe/marketing/mobile/edge/identity/ListenerEdgeIdentityUpdateIdentity.java b/code/edgeidentity/src/main/java/com/adobe/marketing/mobile/edge/identity/ListenerEdgeIdentityUpdateIdentity.java index d20f1ba6..93721152 100644 --- a/code/edgeidentity/src/main/java/com/adobe/marketing/mobile/edge/identity/ListenerEdgeIdentityUpdateIdentity.java +++ b/code/edgeidentity/src/main/java/com/adobe/marketing/mobile/edge/identity/ListenerEdgeIdentityUpdateIdentity.java @@ -17,7 +17,7 @@ import com.adobe.marketing.mobile.LoggingMode; import com.adobe.marketing.mobile.MobileCore; -public class ListenerEdgeIdentityUpdateIdentity extends ExtensionListener { +class ListenerEdgeIdentityUpdateIdentity extends ExtensionListener { /** * Constructor. diff --git a/code/edgeidentity/src/main/java/com/adobe/marketing/mobile/edge/identity/ListenerEventHubBoot.java b/code/edgeidentity/src/main/java/com/adobe/marketing/mobile/edge/identity/ListenerEventHubBoot.java new file mode 100644 index 00000000..4b1fdd6d --- /dev/null +++ b/code/edgeidentity/src/main/java/com/adobe/marketing/mobile/edge/identity/ListenerEventHubBoot.java @@ -0,0 +1,60 @@ +/* + Copyright 2021 Adobe. All rights reserved. + This file is licensed to you under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. You may obtain a copy + of the License at http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software distributed under + the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + OF ANY KIND, either express or implied. See the License for the specific language + governing permissions and limitations under the License. +*/ + +package com.adobe.marketing.mobile.edge.identity; + +import com.adobe.marketing.mobile.Event; +import com.adobe.marketing.mobile.ExtensionApi; +import com.adobe.marketing.mobile.ExtensionListener; +import com.adobe.marketing.mobile.LoggingMode; +import com.adobe.marketing.mobile.MobileCore; + +class ListenerEventHubBoot extends ExtensionListener { + /** + * Constructor. + * + * @param extensionApi an instance of {@link ExtensionApi} + * @param type the {@link String} eventType this listener is registered to handle + * @param source the {@link String} eventSource this listener is registered to handle + */ + ListenerEventHubBoot(final ExtensionApi extensionApi, final String type, final String source) { + super(extensionApi, type, source); + } + + /** + * Method that gets called when event with event type {@link IdentityConstants.EventType#HUB} + * and with event source {@link IdentityConstants.EventSource#BOOTED} is dispatched through eventHub. + * + * @param event the boot {@link Event} + */ + @Override + public void hear(final Event event) { + + final IdentityExtension parentExtension = getIdentityExtension(); + + if (parentExtension == null) { + MobileCore.log(LoggingMode.DEBUG, IdentityConstants.LOG_TAG, + "The parent extension associated with the ListenerEventHubBoot is null, ignoring this event."); + return; + } + + parentExtension.handleEventHubBoot(event); + } + + /** + * Returns the parent extension associated with the listener. + * + * @return a {@link IdentityExtension} object registered with the eventHub + */ + IdentityExtension getIdentityExtension() { + return (IdentityExtension) getParentExtension(); + } +} diff --git a/code/edgeidentity/src/main/java/com/adobe/marketing/mobile/edge/identity/ListenerIdentityRequestReset.java b/code/edgeidentity/src/main/java/com/adobe/marketing/mobile/edge/identity/ListenerIdentityRequestReset.java index 7aa6f9b8..ac4d0158 100644 --- a/code/edgeidentity/src/main/java/com/adobe/marketing/mobile/edge/identity/ListenerIdentityRequestReset.java +++ b/code/edgeidentity/src/main/java/com/adobe/marketing/mobile/edge/identity/ListenerIdentityRequestReset.java @@ -17,7 +17,7 @@ import com.adobe.marketing.mobile.LoggingMode; import com.adobe.marketing.mobile.MobileCore; -public class ListenerIdentityRequestReset extends ExtensionListener { +class ListenerIdentityRequestReset extends ExtensionListener { /** * Constructor. * diff --git a/code/edgeidentity/src/test/java/com/adobe/marketing/mobile/edge/identity/IdentityExtensionTests.java b/code/edgeidentity/src/test/java/com/adobe/marketing/mobile/edge/identity/IdentityExtensionTests.java index 60811c64..272e9acd 100644 --- a/code/edgeidentity/src/test/java/com/adobe/marketing/mobile/edge/identity/IdentityExtensionTests.java +++ b/code/edgeidentity/src/test/java/com/adobe/marketing/mobile/edge/identity/IdentityExtensionTests.java @@ -93,7 +93,7 @@ public void test_ListenersRegistration() { // constructor is called in the setup step() // verify 2 listeners are registered - verify(mockExtensionApi, times(6)).registerEventListener(anyString(), + verify(mockExtensionApi, times(7)).registerEventListener(anyString(), anyString(), any(Class.class), any(ExtensionErrorCallback.class)); // verify listeners are registered with correct event source and type @@ -109,6 +109,8 @@ public void test_ListenersRegistration() { eq(IdentityConstants.EventSource.REQUEST_RESET), eq(ListenerIdentityRequestReset.class), callbackCaptor.capture()); verify(mockExtensionApi, times(1)).registerEventListener(eq(IdentityConstants.EventType.HUB), eq(IdentityConstants.EventSource.SHARED_STATE), eq(ListenerHubSharedState.class), callbackCaptor.capture()); + verify(mockExtensionApi, times(1)).registerEventListener(eq(IdentityConstants.EventType.HUB), + eq(IdentityConstants.EventSource.BOOTED), eq(ListenerEventHubBoot.class), callbackCaptor.capture()); // verify the callback ExtensionErrorCallback extensionErrorCallback = callbackCaptor.getValue(); @@ -165,7 +167,7 @@ public void test_handleIdentityRequest_generatesNewECID() { // verify response event containing ECID is dispatched Event ecidResponseEvent = responseEventCaptor.getAllValues().get(0); - final IdentityMap identityMap = IdentityMap.fromData(ecidResponseEvent.getEventData()); + final IdentityMap identityMap = IdentityMap.fromXDMMap(ecidResponseEvent.getEventData()); final String ecid = identityMap.getIdentityItemsForNamespace("ECID").get(0).getId(); assertNotNull(ecid); @@ -183,6 +185,7 @@ public void test_handleIdentityRequest_loadsPersistedECID() { final ArgumentCaptor requestEventCaptor = ArgumentCaptor.forClass(Event.class); // test + extension = new IdentityExtension(mockExtensionApi); extension.handleIdentityRequest(event); // verify @@ -191,14 +194,14 @@ public void test_handleIdentityRequest_loadsPersistedECID() { // verify response event containing ECID is dispatched Event ecidResponseEvent = responseEventCaptor.getAllValues().get(0); - final IdentityMap identityMap = IdentityMap.fromData(ecidResponseEvent.getEventData()); + final IdentityMap identityMap = IdentityMap.fromXDMMap(ecidResponseEvent.getEventData()); final String ecid = identityMap.getIdentityItemsForNamespace("ECID").get(0).getId(); assertEquals(existingECID.toString(), ecid); } @Test - public void test_handleIdentityRequest_noIdentifiers_emptyIdentityMap() { + public void test_handleIdentityRequest_noIdentifiers_emptyXDMIdentityMap() { // setup IdentityProperties emptyProps = new IdentityProperties(); PowerMockito.stub(PowerMockito.method(IdentityState.class, "getIdentityProperties")).toReturn(emptyProps); @@ -513,8 +516,8 @@ public void test_handleRemoveIdentity() throws Exception { assertTrue(eventCaptor.getAllValues().isEmpty()); // verify persistence - verify(mockSharedPreferenceEditor, times(2)).putString(eq(IdentityConstants.DataStoreKey.IDENTITY_PROPERTIES), persistenceValueCaptor.capture()); - Map persistedData = flattenJSONString(persistenceValueCaptor.getAllValues().get(1)); + verify(mockSharedPreferenceEditor, times(3)).putString(eq(IdentityConstants.DataStoreKey.IDENTITY_PROPERTIES), persistenceValueCaptor.capture()); + Map persistedData = flattenJSONString(persistenceValueCaptor.getAllValues().get(2)); assertNull(persistedData.get("identityMap.UserId[0].id")); assertEquals("token", persistedData.get("identityMap.PushId[0].id")); } @@ -552,8 +555,8 @@ public void test_handleRemoveIdentity_DoNotRemoveReservedNamespace() throws Exce // verify persistence - verify(mockSharedPreferenceEditor, times(1)).putString(eq(IdentityConstants.DataStoreKey.IDENTITY_PROPERTIES), persistenceValueCaptor.capture()); - Map persistedData = flattenJSONString(persistenceValueCaptor.getValue()); + verify(mockSharedPreferenceEditor, times(2)).putString(eq(IdentityConstants.DataStoreKey.IDENTITY_PROPERTIES), persistenceValueCaptor.capture()); + Map persistedData = flattenJSONString(persistenceValueCaptor.getAllValues().get(1)); assertEquals("someGAID", persistedData.get("identityMap.GAID[0].id")); assertEquals("someECID", persistedData.get("identityMap.ECID[0].id")); assertEquals("someIDFA", persistedData.get("identityMap.IDFA[0].id")); @@ -578,7 +581,7 @@ public void test_handleRemoveIdentity_NullData() throws Exception { verify(mockExtensionApi, times(0)).setXDMSharedEventState(any(Map.class), eq(removeIdentityEvent), any(ExtensionErrorCallback.class)); // verify persistence - verify(mockSharedPreferenceEditor, times(1)).putString(eq(IdentityConstants.DataStoreKey.IDENTITY_PROPERTIES), anyString()); + verify(mockSharedPreferenceEditor, times(2)).putString(eq(IdentityConstants.DataStoreKey.IDENTITY_PROPERTIES), anyString());// once during constructor and other during remove IdentityEvent } diff --git a/code/edgeidentity/src/test/java/com/adobe/marketing/mobile/edge/identity/IdentityMapTests.java b/code/edgeidentity/src/test/java/com/adobe/marketing/mobile/edge/identity/IdentityMapTests.java index b9264402..58f21743 100644 --- a/code/edgeidentity/src/test/java/com/adobe/marketing/mobile/edge/identity/IdentityMapTests.java +++ b/code/edgeidentity/src/test/java/com/adobe/marketing/mobile/edge/identity/IdentityMapTests.java @@ -32,7 +32,7 @@ public void test_AddItem() { map.addItem(new IdentityItem("California"),"location"); // verify - assertEquals("California", map.toObjectMap().get("location").get(0).get("id")); + IdentityTestUtil.flattenMap(map.asXDMMap()).get("identityMap.location[0].id"); } @Test @@ -44,7 +44,7 @@ public void test_AddItem_InvalidInputs() { map.addItem( null,"namespace"); // verify - assertTrue(map.toObjectMap().isEmpty()); + assertTrue(map.isEmpty()); } @Test @@ -92,17 +92,21 @@ public void test_RemoveItem() { // test sampleUserMap.removeItem( new IdentityItem("California"),"location"); + + // verify - assertEquals(1, sampleUserMap.toObjectMap().get("location").size()); - assertEquals(3, sampleUserMap.toObjectMap().get("login").size()); + Map> castedMap = getCastedIdentityMap(sampleUserMap); + assertEquals(1, castedMap.get("location").size()); + assertEquals(3, castedMap.get("login").size()); // test 2 sampleUserMap.removeItem(new IdentityItem("280 Highway Lane"),"location"); sampleUserMap.removeItem(new IdentityItem("Student"), "login"); // verify - assertNull(sampleUserMap.toObjectMap().get("location")); - assertEquals(2, sampleUserMap.toObjectMap().get("login").size()); + Map> castedMap2 = getCastedIdentityMap(sampleUserMap); + assertNull(castedMap2.get("location")); + assertEquals(2, castedMap2.get("login").size()); } @Test @@ -116,8 +120,9 @@ public void test_RemoveItem_InvalidInputs() { sampleUserMap.removeItem(null,"location"); // verify the existing identityMap is unchanged - assertEquals(2, sampleUserMap.toObjectMap().get("location").size()); - assertEquals(3, sampleUserMap.toObjectMap().get("login").size()); + Map> castedMap = getCastedIdentityMap(sampleUserMap); + assertEquals(2, castedMap.get("location").size()); + assertEquals(3, castedMap.get("login").size()); } @@ -132,11 +137,31 @@ public void test_merge() { sampleUserMap.merge(newMap); // verify the existing identityMap is unchanged - assertEquals(3, sampleUserMap.toObjectMap().get("location").size()); - assertEquals(3, sampleUserMap.toObjectMap().get("login").size()); + Map> castedMap = getCastedIdentityMap(sampleUserMap); + assertEquals(3, castedMap.get("location").size()); + assertEquals(3, castedMap.get("login").size()); } + @Test + public void test_merge_sameItem_GetsReplaced() { + // setup + IdentityMap baseMap = new IdentityMap(); + baseMap.addItem(new IdentityItem("California", AuthenticatedState.LOGGED_OUT, false),"location"); + + // test + IdentityMap newMap = new IdentityMap(); + newMap.addItem(new IdentityItem("California", AuthenticatedState.AUTHENTICATED, true),"location"); + baseMap.merge(newMap); + + // verify the existing identityMap is unchanged + Map flattenedMap = IdentityTestUtil.flattenMap(baseMap.asXDMMap()); + assertEquals(3, flattenedMap.size()); + assertEquals("California", flattenedMap.get("identityMap.location[0].id")); + assertEquals("authenticated", flattenedMap.get("identityMap.location[0].authenticatedState")); + assertEquals("true", flattenedMap.get("identityMap.location[0].primary")); + } + @Test public void test_merge_EmptyIdentityMap() { // setup @@ -146,8 +171,9 @@ public void test_merge_EmptyIdentityMap() { sampleUserMap.merge(new IdentityMap()); // verify the existing identityMap is unchanged - assertEquals(2, sampleUserMap.toObjectMap().get("location").size()); - assertEquals(3, sampleUserMap.toObjectMap().get("login").size()); + Map> castedMap = getCastedIdentityMap(sampleUserMap); + assertEquals(2, castedMap.get("location").size()); + assertEquals(3, castedMap.get("login").size()); } @Test @@ -159,8 +185,9 @@ public void test_merge_nullMap() { sampleUserMap.merge(null); // verify the existing identityMap is unchanged - assertEquals(2, sampleUserMap.toObjectMap().get("location").size()); - assertEquals(3, sampleUserMap.toObjectMap().get("login").size()); + Map> castedMap = getCastedIdentityMap(sampleUserMap); + assertEquals(2, castedMap.get("location").size()); + assertEquals(3, castedMap.get("login").size()); } @@ -172,9 +199,11 @@ public void test_removeAllIdentityItemsForNamespace() { // test sampleUserMap.clearItemsForNamespace("location"); - // verify the existing identityMap is unchanged - assertNull(sampleUserMap.toObjectMap().get("location")); - assertEquals(3, sampleUserMap.toObjectMap().get("login").size()); + // verify + Map> castedMap = getCastedIdentityMap(sampleUserMap); + assertNull(castedMap.get("location")); + assertEquals(3, castedMap.get("login").size()); + } @@ -187,9 +216,10 @@ public void test_removeAllIdentityItemsForNamespace_InvalidNamespace() { sampleUserMap.clearItemsForNamespace(null); sampleUserMap.clearItemsForNamespace(""); - // verify - assertEquals(2, sampleUserMap.toObjectMap().get("location").size()); - assertEquals(3, sampleUserMap.toObjectMap().get("login").size()); + // verify the existing identityMap is unchanged + Map> castedMap = getCastedIdentityMap(sampleUserMap); + assertEquals(2, castedMap.get("location").size()); + assertEquals(3, castedMap.get("login").size()); } @Test @@ -199,7 +229,7 @@ public void test_removeAllIdentityItemsForNamespace_onEmptyMap() { // test emptyMap.clearItemsForNamespace("location"); - assertTrue(emptyMap.toObjectMap().isEmpty()); + assertTrue(emptyMap.asXDMMap().isEmpty()); } @Test @@ -214,8 +244,9 @@ public void test_remove() { sampleUserMap.remove(tobeRemovedMap); // verify - assertEquals(1, sampleUserMap.toObjectMap().get("location").size()); - assertEquals(2, sampleUserMap.toObjectMap().get("login").size()); + Map> castedMap = getCastedIdentityMap(sampleUserMap); + assertEquals(1, castedMap.get("location").size()); + assertEquals(2, castedMap.get("login").size()); } @Test @@ -227,9 +258,10 @@ public void test_remove_NullAndEmptyMap() { sampleUserMap.remove(null); sampleUserMap.remove(new IdentityMap()); - // verify that the existing map is unchanged - assertEquals(2, sampleUserMap.toObjectMap().get("location").size()); - assertEquals(3, sampleUserMap.toObjectMap().get("login").size()); + // verify the existing identityMap is unchanged + Map> castedMap = getCastedIdentityMap(sampleUserMap); + assertEquals(2, castedMap.get("location").size()); + assertEquals(3, castedMap.get("login").size()); } @Test @@ -243,9 +275,10 @@ public void test_remove_nonexistentNamespaceAndItems() { // test sampleUserMap.remove(tobeRemovedMap); - // verify that the existing map is unchanged - assertEquals(2, sampleUserMap.toObjectMap().get("location").size()); - assertEquals(3, sampleUserMap.toObjectMap().get("login").size()); + // verify the existing identityMap is unchanged + Map> castedMap = getCastedIdentityMap(sampleUserMap); + assertEquals(2, castedMap.get("location").size()); + assertEquals(3, castedMap.get("login").size()); } @@ -275,28 +308,27 @@ public void test_FromData() throws Exception { final Map xdmData = Utils.toMap(jsonObject); // test - IdentityMap map = IdentityMap.fromData(xdmData); + IdentityMap map = IdentityMap.fromXDMMap(xdmData); // verify - Map flattenedMap = IdentityTestUtil.flattenMap(map.asEventData()); - assertEquals("randomECID", flattenedMap.get("ECID[0].id")); - assertEquals("AMBIGUOUS", flattenedMap.get("ECID[0].authenticatedState")); - assertEquals("true", flattenedMap.get("ECID[0].primary")); - assertEquals("someUserID", flattenedMap.get("USERID[0].id")); - assertEquals("AUTHENTICATED", flattenedMap.get("USERID[0].authenticatedState")); - assertEquals("false", flattenedMap.get("USERID[0].primary")); + Map flattenedMap = IdentityTestUtil.flattenMap(map.asXDMMap()); + assertEquals("randomECID", flattenedMap.get("identityMap.ECID[0].id")); + assertEquals("ambiguous", flattenedMap.get("identityMap.ECID[0].authenticatedState")); + assertEquals("true", flattenedMap.get("identityMap.ECID[0].primary")); + assertEquals("someUserID", flattenedMap.get("identityMap.USERID[0].id")); + assertEquals("authenticated", flattenedMap.get("identityMap.USERID[0].authenticatedState")); + assertEquals("false", flattenedMap.get("identityMap.USERID[0].primary")); } - @Test - public void test_FromData_NullAndEmptyData() { - assertNull(IdentityMap.fromData(null)); - assertNull(IdentityMap.fromData(new HashMap())); + public void testFromXDMMap_NullAndEmptyData() { + assertNull(IdentityMap.fromXDMMap(null)); + assertNull(IdentityMap.fromXDMMap(new HashMap())); } @Test - public void test_FromData_InvalidXDMData() throws Exception { + public void testFromXDMMap_InvalidXDMData() throws Exception { // setup // ECID is map instead of list final String invalidJsonStr = "{\n" + @@ -313,12 +345,34 @@ public void test_FromData_InvalidXDMData() throws Exception { final Map xdmData = Utils.toMap(jsonObject); // test - IdentityMap map = IdentityMap.fromData(xdmData); + IdentityMap map = IdentityMap.fromXDMMap(xdmData); // verify assertTrue(map.isEmpty()); } + @Test + public void testAsXDMMap_AllowEmptyTrue() { + IdentityMap map = new IdentityMap(); + Map xdmMap = map.asXDMMap(true); + assertTrue(xdmMap.isEmpty()); + } + + @Test + public void testAsXDMMap_AllowEmptyFalse() { + IdentityMap map = new IdentityMap(); + Map xdmMap = map.asXDMMap(false); + + // verify that the base xdm key identityMap is present + assertEquals(1, xdmMap.size()); + assertEquals(new HashMap<>(), xdmMap.get(IdentityConstants.XDMKeys.IDENTITY_MAP)); + } + + private Map> getCastedIdentityMap(final IdentityMap map) { + final Map xdmMap = map.asXDMMap(); + return (Map>) xdmMap.get(IdentityConstants.XDMKeys.IDENTITY_MAP); + } + private IdentityMap buildSampleIdentityMap() { // User Login Identity Items diff --git a/code/edgeidentity/src/test/java/com/adobe/marketing/mobile/edge/identity/IdentityPropertiesTests.java b/code/edgeidentity/src/test/java/com/adobe/marketing/mobile/edge/identity/IdentityPropertiesTests.java index c930ece7..0e18c6b4 100644 --- a/code/edgeidentity/src/test/java/com/adobe/marketing/mobile/edge/identity/IdentityPropertiesTests.java +++ b/code/edgeidentity/src/test/java/com/adobe/marketing/mobile/edge/identity/IdentityPropertiesTests.java @@ -26,12 +26,12 @@ public class IdentityPropertiesTests { // ====================================================================================================================== @Test - public void test_toXDMData_Empty() { + public void test_toXDMData_AllowEmpty() { // setup IdentityProperties props = new IdentityProperties(); // test - Map xdmMap = props.toXDMData(false); + Map xdmMap = props.toXDMData(true); // verify assertNull(xdmMap.get(IdentityConstants.XDMKeys.IDENTITY_MAP)); diff --git a/code/edgeidentity/src/test/java/com/adobe/marketing/mobile/edge/identity/IdentityStateTests.java b/code/edgeidentity/src/test/java/com/adobe/marketing/mobile/edge/identity/IdentityStateTests.java index 3db3f138..aa3330b1 100644 --- a/code/edgeidentity/src/test/java/com/adobe/marketing/mobile/edge/identity/IdentityStateTests.java +++ b/code/edgeidentity/src/test/java/com/adobe/marketing/mobile/edge/identity/IdentityStateTests.java @@ -64,22 +64,21 @@ public void before() throws Exception { @Test - public void testIdentityState_BootupIfReadyGeneratesECID() { + public void testBootUp_GeneratesECID() { // setup IdentityState state = new IdentityState(new IdentityProperties()); assertNull(state.getIdentityProperties().getECID()); // test - boolean result = state.bootupIfReady(); + state.bootUp(); verify(mockSharedPreferenceEditor, Mockito.times(1)).apply(); // saves to data store // verify - assertTrue(result); assertNotNull(state.getIdentityProperties().getECID()); } @Test - public void testIdentityState_BootupIfReadyLoadsDirectIdentityECID() { + public void testBootUp_LoadsDirectIdentityECID() { // setup ECID ecid = new ECID(); Mockito.when(mockContext.getSharedPreferences(IdentityConstants.DataStoreKey.IDENTITY_DIRECT_DATASTORE_NAME, 0)).thenReturn(mockSharedPreference); @@ -89,16 +88,15 @@ public void testIdentityState_BootupIfReadyLoadsDirectIdentityECID() { assertNull(state.getIdentityProperties().getECID()); // test - boolean result = state.bootupIfReady(); + state.bootUp(); verify(mockSharedPreferenceEditor, Mockito.times(1)).apply(); // saves to data store // verify - assertTrue(result); assertEquals(ecid, state.getIdentityProperties().getECID()); } @Test - public void testIdentityState_BootupIfReadyGenratesECIDWhenDirectECIDIsNull() { + public void testBootUp_GeneratesECIDWhenDirectECIDIsNull() { // setup Mockito.when(mockContext.getSharedPreferences(IdentityConstants.DataStoreKey.IDENTITY_DIRECT_DATASTORE_NAME, 0)).thenReturn(mockSharedPreference); Mockito.when(mockSharedPreference.getString(IdentityConstants.DataStoreKey.IDENTITY_DIRECT_ECID_KEY, null)).thenReturn(null); @@ -107,16 +105,15 @@ public void testIdentityState_BootupIfReadyGenratesECIDWhenDirectECIDIsNull() { assertNull(state.getIdentityProperties().getECID()); // test - boolean result = state.bootupIfReady(); + state.bootUp(); verify(mockSharedPreferenceEditor, Mockito.times(1)).apply(); // saves to data store // verify - assertTrue(result); assertNotNull(state.getIdentityProperties().getECID()); } @Test - public void testIdentityState_BootupIfReadyLoadsFromPersistence() { + public void testBootUp_LoadsFromPersistence() { // setup IdentityState state = new IdentityState(new IdentityProperties()); @@ -127,16 +124,15 @@ public void testIdentityState_BootupIfReadyLoadsFromPersistence() { Mockito.when(mockSharedPreference.getString(IdentityConstants.DataStoreKey.IDENTITY_PROPERTIES, null)).thenReturn(propsJSON); // test - boolean result = state.bootupIfReady(); + state.bootUp(); verify(mockSharedPreferenceEditor, never()).apply(); // verify - assertTrue(result); assertEquals(persistedProps.getECID().toString(), state.getIdentityProperties().getECID().toString()); } @Test - public void testIdentityState_BootupIfReadyLoadsFromPersistenceWhenDirectECIDIsValid() { + public void testBootUp_IfReadyLoadsFromPersistenceWhenDirectECIDIsValid() { // setup ECID ecid = new ECID(); Mockito.when(mockContext.getSharedPreferences(IdentityConstants.DataStoreKey.IDENTITY_DIRECT_DATASTORE_NAME, 0)).thenReturn(mockSharedPreference); @@ -151,16 +147,15 @@ public void testIdentityState_BootupIfReadyLoadsFromPersistenceWhenDirectECIDIsV Mockito.when(mockSharedPreference.getString(IdentityConstants.DataStoreKey.IDENTITY_PROPERTIES, null)).thenReturn(propsJSON); // test - boolean result = state.bootupIfReady(); + state. bootUp(); verify(mockSharedPreferenceEditor, never()).apply(); // verify - assertTrue(result); assertEquals(persistedProps.getECID(), state.getIdentityProperties().getECID()); } @Test - public void testIdentityState_resetIdentifiers() { + public void testResetIdentifiers() { // setup IdentityState state = new IdentityState(new IdentityProperties()); state.getIdentityProperties().setECID(new ECID()); @@ -188,7 +183,7 @@ public void testIdentityState_resetIdentifiers() { // ====================================================================================================================== @Test - public void testIdentityState_updateLegacyExperienceCloudId() { + public void testUpdateLegacyExperienceCloudId() { IdentityState state = new IdentityState(new IdentityProperties()); state.getIdentityProperties().setECID(new ECID()); ECID legacyEcid = new ECID(); @@ -202,7 +197,7 @@ public void testIdentityState_updateLegacyExperienceCloudId() { } @Test - public void testIdentityState_updateLegacyExperienceCloudId_notSetWhenECIDSame() { + public void testUpdateLegacyExperienceCloudId_notSetWhenECIDSame() { IdentityState state = new IdentityState(new IdentityProperties()); ECID legacyEcid = new ECID(); state.getIdentityProperties().setECID(legacyEcid); @@ -215,7 +210,7 @@ public void testIdentityState_updateLegacyExperienceCloudId_notSetWhenECIDSame() } @Test - public void testIdentityState_updateLegacyExperienceCloudId_notSetWhenSecondaryECIDSame() { + public void testUpdateLegacyExperienceCloudId_notSetWhenSecondaryECIDSame() { IdentityState state = new IdentityState(new IdentityProperties()); state.getIdentityProperties().setECID(new ECID()); ECID legacyEcid = new ECID(); @@ -228,7 +223,7 @@ public void testIdentityState_updateLegacyExperienceCloudId_notSetWhenSecondaryE } @Test - public void testIdentityState_updateLegacyExperienceCloudId_clearsOnNull() { + public void testUpdateLegacyExperienceCloudId_clearsOnNull() { IdentityState state = new IdentityState(new IdentityProperties()); state.getIdentityProperties().setECID(new ECID()); state.getIdentityProperties().setECIDSecondary(new ECID()); @@ -240,7 +235,7 @@ public void testIdentityState_updateLegacyExperienceCloudId_clearsOnNull() { } @Test - public void testIdentityState_updateLegacyExperienceCloudId_notSetWhenExistingIsNull() { + public void testUpdateLegacyExperienceCloudId_notSetWhenExistingIsNull() { IdentityState state = new IdentityState(new IdentityProperties()); state.getIdentityProperties().setECID(new ECID()); diff --git a/code/edgeidentity/src/test/java/com/adobe/marketing/mobile/edge/identity/IdentityStorageServiceTests.java b/code/edgeidentity/src/test/java/com/adobe/marketing/mobile/edge/identity/IdentityStorageServiceTests.java index 10faee3b..7be32151 100644 --- a/code/edgeidentity/src/test/java/com/adobe/marketing/mobile/edge/identity/IdentityStorageServiceTests.java +++ b/code/edgeidentity/src/test/java/com/adobe/marketing/mobile/edge/identity/IdentityStorageServiceTests.java @@ -59,7 +59,7 @@ public void before() throws Exception { } @Test - public void testStorageService_load_nullSharedPrefs() { + public void testLoadPropertiesFromPersistence_nullSharedPrefs() { // setup Mockito.when(mockApplication.getApplicationContext()).thenReturn(null); @@ -71,7 +71,7 @@ public void testStorageService_load_nullSharedPrefs() { } @Test - public void testStorageService_load_emptyPrefs() { + public void testLoadPropertiesFromPersistence_emptyPrefs() { // setup Mockito.when(mockSharedPreference.getString(IdentityConstants.DataStoreKey.IDENTITY_PROPERTIES, null)).thenReturn(null); @@ -83,7 +83,7 @@ public void testStorageService_load_emptyPrefs() { } @Test - public void testStorageService_load_invalidJSON() { + public void testLoadPropertiesFromPersistence_invalidJSON() { // setup Mockito.when(mockSharedPreference.getString(IdentityConstants.DataStoreKey.IDENTITY_PROPERTIES, null)).thenReturn("{"); @@ -95,7 +95,7 @@ public void testStorageService_load_invalidJSON() { } @Test - public void testStorageService_load_validJSON() { + public void testLoadPropertiesFromPersistence_validJSON() { // setup IdentityProperties persistedProps = new IdentityProperties(); persistedProps.setECID(new ECID()); @@ -111,7 +111,7 @@ public void testStorageService_load_validJSON() { } @Test - public void testStorageService_save_nullSharedPrefs() { + public void testSavePropertiesToPersistence_nullSharedPrefs() { // setup Mockito.when(mockApplication.getApplicationContext()).thenReturn(null); @@ -124,7 +124,7 @@ public void testStorageService_save_nullSharedPrefs() { } @Test - public void testStorageService_save_nullEditor() { + public void testSavePropertiesToPersistence_nullEditor() { // setup Mockito.when(mockSharedPreference.edit()).thenReturn(null); @@ -137,7 +137,7 @@ public void testStorageService_save_nullEditor() { } @Test - public void testStorageService_save_nullProps() { + public void testSavePropertiesToPersistence_nullProps() { // test IdentityStorageService.savePropertiesToPersistence(null); @@ -147,7 +147,7 @@ public void testStorageService_save_nullProps() { } @Test - public void testStorageService_save_validProps() { + public void testSavePropertiesToPersistence_validProps() { // test IdentityProperties props = new IdentityProperties(); props.setECID(new ECID()); @@ -161,7 +161,7 @@ public void testStorageService_save_validProps() { } @Test - public void testStorageService_loadECID() { + public void testLoadEcidFromDirectIdentityPersistence_loadECID() { ECID ecid = new ECID(); Mockito.when(mockContext.getSharedPreferences(IdentityConstants.DataStoreKey.IDENTITY_DIRECT_DATASTORE_NAME, 0)).thenReturn(mockSharedPreference); Mockito.when(mockSharedPreference.getString(IdentityConstants.DataStoreKey.IDENTITY_DIRECT_ECID_KEY, null)).thenReturn(ecid.toString()); @@ -170,7 +170,7 @@ public void testStorageService_loadECID() { } @Test - public void testStorageService_loadECID_nullECID() { + public void testLoadEcidFromDirectIdentityPersistence_whenNullECID() { Mockito.when(mockContext.getSharedPreferences(IdentityConstants.DataStoreKey.IDENTITY_DIRECT_DATASTORE_NAME, 0)).thenReturn(mockSharedPreference); Mockito.when(mockSharedPreference.getString(IdentityConstants.DataStoreKey.IDENTITY_DIRECT_ECID_KEY, null)).thenReturn(null); @@ -178,7 +178,7 @@ public void testStorageService_loadECID_nullECID() { } @Test - public void testStorageService_loadECID_emptyECID() { + public void testLoadEcidFromDirectIdentityPersistence_whenEmptyECID() { Mockito.when(mockContext.getSharedPreferences(IdentityConstants.DataStoreKey.IDENTITY_DIRECT_DATASTORE_NAME, 0)).thenReturn(mockSharedPreference); Mockito.when(mockSharedPreference.getString(IdentityConstants.DataStoreKey.IDENTITY_DIRECT_ECID_KEY, null)).thenReturn(""); diff --git a/code/edgeidentity/src/test/java/com/adobe/marketing/mobile/edge/identity/IdentityTests.java b/code/edgeidentity/src/test/java/com/adobe/marketing/mobile/edge/identity/IdentityTests.java index a47efb72..29213786 100644 --- a/code/edgeidentity/src/test/java/com/adobe/marketing/mobile/edge/identity/IdentityTests.java +++ b/code/edgeidentity/src/test/java/com/adobe/marketing/mobile/edge/identity/IdentityTests.java @@ -275,7 +275,7 @@ public void testUpdateIdentities() { assertEquals(IdentityConstants.EventNames.UPDATE_IDENTITIES, dispatchedEvent.getName()); assertEquals(IdentityConstants.EventType.EDGE_IDENTITY.toLowerCase(), dispatchedEvent.getType()); assertEquals(IdentityConstants.EventSource.UPDATE_IDENTITY.toLowerCase(), dispatchedEvent.getSource()); - assertEquals(map.asEventData(), dispatchedEvent.getEventData()); + assertEquals(map.asXDMMap(), dispatchedEvent.getEventData()); } @Test @@ -311,7 +311,7 @@ public void testRemoveIdentity() { assertEquals(IdentityConstants.EventSource.REMOVE_IDENTITY.toLowerCase(), dispatchedEvent.getSource()); IdentityMap sampleInputIdentitymap = new IdentityMap(); sampleInputIdentitymap.addItem(sampleItem,"namespace"); - assertEquals(sampleInputIdentitymap.asEventData(), dispatchedEvent.getEventData()); + assertEquals(sampleInputIdentitymap.asXDMMap(), dispatchedEvent.getEventData()); // TODO - enable when ExtensionError creation is available // should not crash on calling the callback diff --git a/code/edgeidentity/src/test/java/com/adobe/marketing/mobile/edge/identity/ListenerEventHubBootTest.java b/code/edgeidentity/src/test/java/com/adobe/marketing/mobile/edge/identity/ListenerEventHubBootTest.java new file mode 100644 index 00000000..b3fdfc63 --- /dev/null +++ b/code/edgeidentity/src/test/java/com/adobe/marketing/mobile/edge/identity/ListenerEventHubBootTest.java @@ -0,0 +1,68 @@ +/* + Copyright 2021 Adobe. All rights reserved. + This file is licensed to you under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. You may obtain a copy + of the License at http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software distributed under + the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + OF ANY KIND, either express or implied. See the License for the specific language + governing permissions and limitations under the License. +*/ + +package com.adobe.marketing.mobile.edge.identity; + +import com.adobe.marketing.mobile.Event; +import com.adobe.marketing.mobile.MobileCore; + +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.Mockito; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +public class ListenerEventHubBootTest { + + @Mock + private IdentityExtension mockConsentExtension; + + private ListenerEventHubBoot listener; + + @Before + public void setup() { + mockConsentExtension = Mockito.mock(IdentityExtension.class); + MobileCore.start(null); + listener = spy(new ListenerEventHubBoot(null, IdentityConstants.EventType.HUB, IdentityConstants.EventSource.BOOTED)); + } + + @Test + public void testHear() { + // setup + Event event = new Event.Builder("Event Hub Boot", IdentityConstants.EventType.HUB, IdentityConstants.EventSource.BOOTED).build(); + doReturn(mockConsentExtension).when(listener).getIdentityExtension(); + + // test + listener.hear(event); + + // verify + verify(mockConsentExtension, times(1)).handleEventHubBoot(event); + } + + @Test + public void testHear_WhenParentExtensionNull() { + // setup + Event event = new Event.Builder("Event Hub Boot", IdentityConstants.EventType.HUB, IdentityConstants.EventSource.BOOTED).build(); + doReturn(null).when(listener).getIdentityExtension(); + + // test + listener.hear(event); + + // verify + verify(mockConsentExtension, times(0)).handleEventHubBoot(any(Event.class)); + } + +} From 71848294bc8b4409264edbcb9e05b3a925a5bf89 Mon Sep 17 00:00:00 2001 From: Kevin Lind <40409666+kevinlind@users.noreply.github.com> Date: Thu, 25 Mar 2021 12:23:54 -0700 Subject: [PATCH 019/101] Add sample app (#20) * Use correct direct Identity data store name * Override toString in IdentityMap and IdentityItem * Add Kotlin test app for IdentityEdge * Add fragment for starting an Assurance session * Remove unused test files from sample app. * Add implementations for send event and reset identities buttons. * Add Application class to initialize SDK and extensions * Add network security config to AndroidManifest * Comment out call to resetIdentities as API in Core is not yet released * Remove Java app * Rename 'appkt' to 'app' and move files to 'code/app' * Remove launch environment ID * Rename test app package from 'appkt' to 'app' * fix IdentityMap.toString to handle case where map is empty. * Use correct AuthenticatedState.loggedOut string * Save custom identifier UI entries and update UI with saved values when page is viewed. * Remove copyright from non-source files (Manifest, layouts, drawables, etc). * Make StringBuilder final in IdentityMap.toString() --- code/app/build.gradle | 37 ++++- code/app/src/main/AndroidManifest.xml | 28 +--- .../identity/app/EdgeIdentityApplication.kt | 40 +++++ .../edge/identity/app/MainActivity.kt | 57 +++++++ .../identity/app/model/SharedViewModel.kt | 148 +++++++++++++++++ .../edge/identity/app/ui/AssuranceFragment.kt | 41 +++++ .../identity/app/ui/CustomIdentityFragment.kt | 94 +++++++++++ .../identity/app/ui/GetIdentityFragment.kt | 100 +++++++++++ .../app/ui/MultipleIdentityFragment.kt | 118 +++++++++++++ .../mobile/testApp/MainActivity.java | 31 ---- .../mobile/testApp/TestApplication.java | 71 -------- .../res/drawable-anydpi/ic_menu_assurance.xml | 11 ++ .../res/drawable-hdpi/ic_menu_assurance.png | Bin 0 -> 268 bytes .../res/drawable-mdpi/ic_menu_assurance.png | Bin 0 -> 204 bytes .../main/res/drawable-v21/ic_menu_camera.xml | 12 ++ .../main/res/drawable-v21/ic_menu_gallery.xml | 9 + .../res/drawable-v21/ic_menu_slideshow.xml | 9 + .../drawable-v24/ic_launcher_foreground.xml | 8 +- .../res/drawable-xhdpi/ic_menu_assurance.png | Bin 0 -> 326 bytes .../res/drawable-xxhdpi/ic_menu_assurance.png | Bin 0 -> 411 bytes code/app/src/main/res/drawable/border.xml | 7 + .../res/drawable/ic_launcher_background.xml | 133 +++++++-------- .../src/main/res/drawable/side_nav_bar.xml | 9 + .../app/src/main/res/layout/activity_main.xml | 27 +-- code/app/src/main/res/layout/app_bar_main.xml | 25 +++ code/app/src/main/res/layout/content_main.xml | 20 +++ .../main/res/layout/fragment_assurance.xml | 45 +++++ .../res/layout/fragment_custom_identity.xml | 156 ++++++++++++++++++ .../main/res/layout/fragment_get_identity.xml | 92 +++++++++++ .../res/layout/fragment_multiple_identity.xml | 107 ++++++++++++ .../src/main/res/layout/nav_header_main.xml | 36 ++++ .../main/res/menu/activity_main_drawer.xml | 24 +++ code/app/src/main/res/menu/main.xml | 9 + .../main/res/navigation/mobile_navigation.xml | 31 ++++ code/app/src/main/res/values/colors.xml | 1 + code/app/src/main/res/values/dimens.xml | 9 + code/app/src/main/res/values/drawables.xml | 9 + code/app/src/main/res/values/strings.xml | 35 +++- code/app/src/main/res/values/styles.xml | 10 ++ code/build.gradle | 4 + .../edge/identity/IdentityConstants.java | 2 +- .../mobile/edge/identity/IdentityItem.java | 9 + .../mobile/edge/identity/IdentityMap.java | 24 +++ code/settings.gradle | 2 +- 44 files changed, 1433 insertions(+), 207 deletions(-) create mode 100644 code/app/src/main/java/com/adobe/marketing/edge/identity/app/EdgeIdentityApplication.kt create mode 100644 code/app/src/main/java/com/adobe/marketing/edge/identity/app/MainActivity.kt create mode 100644 code/app/src/main/java/com/adobe/marketing/edge/identity/app/model/SharedViewModel.kt create mode 100644 code/app/src/main/java/com/adobe/marketing/edge/identity/app/ui/AssuranceFragment.kt create mode 100644 code/app/src/main/java/com/adobe/marketing/edge/identity/app/ui/CustomIdentityFragment.kt create mode 100644 code/app/src/main/java/com/adobe/marketing/edge/identity/app/ui/GetIdentityFragment.kt create mode 100644 code/app/src/main/java/com/adobe/marketing/edge/identity/app/ui/MultipleIdentityFragment.kt delete mode 100644 code/app/src/main/java/com/adobe/marketing/mobile/testApp/MainActivity.java delete mode 100644 code/app/src/main/java/com/adobe/marketing/mobile/testApp/TestApplication.java create mode 100644 code/app/src/main/res/drawable-anydpi/ic_menu_assurance.xml create mode 100644 code/app/src/main/res/drawable-hdpi/ic_menu_assurance.png create mode 100644 code/app/src/main/res/drawable-mdpi/ic_menu_assurance.png create mode 100644 code/app/src/main/res/drawable-v21/ic_menu_camera.xml create mode 100644 code/app/src/main/res/drawable-v21/ic_menu_gallery.xml create mode 100644 code/app/src/main/res/drawable-v21/ic_menu_slideshow.xml create mode 100644 code/app/src/main/res/drawable-xhdpi/ic_menu_assurance.png create mode 100644 code/app/src/main/res/drawable-xxhdpi/ic_menu_assurance.png create mode 100644 code/app/src/main/res/drawable/border.xml create mode 100644 code/app/src/main/res/drawable/side_nav_bar.xml create mode 100644 code/app/src/main/res/layout/app_bar_main.xml create mode 100644 code/app/src/main/res/layout/content_main.xml create mode 100644 code/app/src/main/res/layout/fragment_assurance.xml create mode 100644 code/app/src/main/res/layout/fragment_custom_identity.xml create mode 100644 code/app/src/main/res/layout/fragment_get_identity.xml create mode 100644 code/app/src/main/res/layout/fragment_multiple_identity.xml create mode 100644 code/app/src/main/res/layout/nav_header_main.xml create mode 100644 code/app/src/main/res/menu/activity_main_drawer.xml create mode 100644 code/app/src/main/res/menu/main.xml create mode 100644 code/app/src/main/res/navigation/mobile_navigation.xml create mode 100644 code/app/src/main/res/values/dimens.xml create mode 100644 code/app/src/main/res/values/drawables.xml diff --git a/code/app/build.gradle b/code/app/build.gradle index cdb6e30e..e88e1780 100644 --- a/code/app/build.gradle +++ b/code/app/build.gradle @@ -1,15 +1,30 @@ +/* + Copyright 2021 Adobe. All rights reserved. + This file is licensed to you under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. You may obtain a copy + of the License at http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software distributed under + the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + OF ANY KIND, either express or implied. See the License for the specific language + governing permissions and limitations under the License. + */ + apply plugin: 'com.android.application' +apply plugin: 'kotlin-android' +apply plugin: 'kotlin-android-extensions' android { compileSdkVersion 30 buildToolsVersion "30.0.2" defaultConfig { - applicationId "com.adobe.marketing.mobile.identitytestapp" + applicationId "com.adobe.marketing.edge.identity.app" minSdkVersion 19 targetSdkVersion 30 versionCode 1 versionName "1.0" + + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } buildTypes { @@ -18,16 +33,32 @@ android { proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' } } + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + kotlinOptions { + jvmTarget = '1.8' + } } dependencies { implementation fileTree(dir: "libs", include: ["*.jar"]) + implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" + implementation 'androidx.core:core-ktx:1.3.2' implementation 'androidx.appcompat:appcompat:1.2.0' + implementation 'androidx.legacy:legacy-support-v4:1.0.0' + implementation 'com.google.android.material:material:1.3.0' implementation 'androidx.constraintlayout:constraintlayout:2.0.4' + implementation 'androidx.navigation:navigation-fragment:2.3.3' + implementation 'androidx.navigation:navigation-ui:2.3.3' + implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0' + implementation 'androidx.navigation:navigation-fragment-ktx:2.3.3' + implementation 'androidx.navigation:navigation-ui-ktx:2.3.3' implementation project(':edgeidentity') implementation 'com.adobe.marketing.mobile:core:1+' - implementation 'com.adobe.marketing.mobile:signal:1+' + implementation 'com.adobe.marketing.mobile:identity:1+' implementation 'com.adobe.marketing.mobile:edge:1+' implementation 'com.adobe.marketing.mobile:assurance:1+' -} \ No newline at end of file +} diff --git a/code/app/src/main/AndroidManifest.xml b/code/app/src/main/AndroidManifest.xml index 5bb87b87..c2c09393 100644 --- a/code/app/src/main/AndroidManifest.xml +++ b/code/app/src/main/AndroidManifest.xml @@ -1,37 +1,27 @@ + + package="com.adobe.marketing.edge.identity.app"> - + android:networkSecurityConfig="@xml/network_security_config" + android:theme="@style/AppTheme"> + - - - - - - - - - - - - \ No newline at end of file diff --git a/code/app/src/main/java/com/adobe/marketing/edge/identity/app/EdgeIdentityApplication.kt b/code/app/src/main/java/com/adobe/marketing/edge/identity/app/EdgeIdentityApplication.kt new file mode 100644 index 00000000..bfd92c45 --- /dev/null +++ b/code/app/src/main/java/com/adobe/marketing/edge/identity/app/EdgeIdentityApplication.kt @@ -0,0 +1,40 @@ +/* + Copyright 2021 Adobe. All rights reserved. + This file is licensed to you under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. You may obtain a copy + of the License at http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software distributed under + the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + OF ANY KIND, either express or implied. See the License for the specific language + governing permissions and limitations under the License. + */ + +package com.adobe.marketing.edge.identity.app + +import android.app.Application +import com.adobe.marketing.mobile.Assurance +import com.adobe.marketing.mobile.Edge +import com.adobe.marketing.mobile.LoggingMode +import com.adobe.marketing.mobile.MobileCore +import com.adobe.marketing.mobile.edge.identity.Identity + +class EdgeIdentityApplication : Application() { + // Add your Launch Environment ID to configure the SDK from your Launch property + private var LAUNCH_ENVIRONMENT_ID: String = "" + + override fun onCreate() { + super.onCreate() + + // register AEP SDK extensions + MobileCore.setApplication(this) + MobileCore.setLogLevel(LoggingMode.VERBOSE) + + Identity.registerExtension() + Edge.registerExtension() + Assurance.registerExtension() + + MobileCore.start { + MobileCore.configureWithAppID(LAUNCH_ENVIRONMENT_ID) + } + } +} \ No newline at end of file diff --git a/code/app/src/main/java/com/adobe/marketing/edge/identity/app/MainActivity.kt b/code/app/src/main/java/com/adobe/marketing/edge/identity/app/MainActivity.kt new file mode 100644 index 00000000..65addbca --- /dev/null +++ b/code/app/src/main/java/com/adobe/marketing/edge/identity/app/MainActivity.kt @@ -0,0 +1,57 @@ +/* + Copyright 2021 Adobe. All rights reserved. + This file is licensed to you under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. You may obtain a copy + of the License at http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software distributed under + the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + OF ANY KIND, either express or implied. See the License for the specific language + governing permissions and limitations under the License. + */ + +package com.adobe.marketing.edge.identity.app + +import android.os.Bundle +import android.view.Menu +import com.google.android.material.navigation.NavigationView +import androidx.navigation.findNavController +import androidx.navigation.ui.AppBarConfiguration +import androidx.navigation.ui.navigateUp +import androidx.navigation.ui.setupActionBarWithNavController +import androidx.navigation.ui.setupWithNavController +import androidx.drawerlayout.widget.DrawerLayout +import androidx.appcompat.app.AppCompatActivity +import androidx.appcompat.widget.Toolbar + +class MainActivity : AppCompatActivity() { + + private lateinit var appBarConfiguration: AppBarConfiguration + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_main) + val toolbar: Toolbar = findViewById(R.id.toolbar) + setSupportActionBar(toolbar) + + val drawerLayout: DrawerLayout = findViewById(R.id.drawer_layout) + val navView: NavigationView = findViewById(R.id.nav_view) + val navController = findNavController(R.id.nav_host_fragment) + // Passing each menu ID as a set of Ids because each + // menu should be considered as top level destinations. + appBarConfiguration = AppBarConfiguration(setOf( + R.id.nav_get_identity, R.id.nav_custom_identity, R.id.nav_multiple_identity, R.id.nav_assurance), drawerLayout) + setupActionBarWithNavController(navController, appBarConfiguration) + navView.setupWithNavController(navController) + } + + override fun onCreateOptionsMenu(menu: Menu): Boolean { + // Inflate the menu; this adds items to the action bar if it is present. + menuInflater.inflate(R.menu.main, menu) + return true + } + + override fun onSupportNavigateUp(): Boolean { + val navController = findNavController(R.id.nav_host_fragment) + return navController.navigateUp(appBarConfiguration) || super.onSupportNavigateUp() + } +} diff --git a/code/app/src/main/java/com/adobe/marketing/edge/identity/app/model/SharedViewModel.kt b/code/app/src/main/java/com/adobe/marketing/edge/identity/app/model/SharedViewModel.kt new file mode 100644 index 00000000..b3432bf8 --- /dev/null +++ b/code/app/src/main/java/com/adobe/marketing/edge/identity/app/model/SharedViewModel.kt @@ -0,0 +1,148 @@ +/* + Copyright 2021 Adobe. All rights reserved. + This file is licensed to you under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. You may obtain a copy + of the License at http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software distributed under + the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + OF ANY KIND, either express or implied. See the License for the specific language + governing permissions and limitations under the License. + */ + +package com.adobe.marketing.edge.identity.app.model + +import androidx.lifecycle.LiveData +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel +import com.adobe.marketing.mobile.edge.identity.AuthenticatedState + +class SharedViewModel : ViewModel() { + companion object { + const val REGISTER_IDENTITY_STRING = "Register Identity" + const val REGISTER_EDGE_IDENTITY_STRING = "Register Edge Identity" + const val IDENTITY_IS_REGISTERED_STRING = "Identity is registered" + const val EDGE_IDENTITY_IS_REGISTERED_STRING = "Edge Identity is registered" + } + + // Models for Get Identities View + + private val _ecidText = MutableLiveData("") + val ecidText: LiveData = _ecidText + + private val _ecidLegacyText = MutableLiveData("") + val ecidLegacyText: LiveData = _ecidLegacyText + + private val _identitiesText = MutableLiveData("") + val identitiesText: LiveData = _identitiesText + + fun setEcidValue(value: String) { + _ecidText.value = value + } + + fun setEcidLegacyValue(value: String) { + _ecidLegacyText.value = value + } + + fun setIdentitiesValue(value: String) { + _identitiesText.value = value + } + + // Models for Update Identities View + + private val _identifier = MutableLiveData("") + val identifier: LiveData = _identifier + + private val _namespace = MutableLiveData("") + val namespace: LiveData = _namespace + + private val _isPrimary = MutableLiveData(false) + val isPrimary: LiveData = _isPrimary + + private val _authenticatedState = MutableLiveData(AuthenticatedState.AMBIGUOUS) + val authenticatedState: LiveData = _authenticatedState + + private val _authenticatedStateId = MutableLiveData(null) + val authenticatedStateId: LiveData = _authenticatedStateId + + fun setIdentifier(value: String) { + if (_identifier.value == value) { + return + } + _identifier.value = value + } + + fun setNamespace(value: String) { + if (_namespace.value == value) { + return + } + _namespace.value = value + } + + fun setIsPrimary(value: Boolean) { + if (_isPrimary.value == value) { + return + } + _isPrimary.value = value + } + + fun setAuthenticatedState(value: AuthenticatedState) { + if (_authenticatedState.value == value) { + return + } + _authenticatedState.value = value + } + + fun setAuthenticatedStateId(value: Int) { + if (_authenticatedStateId.value == value) { + return + } + _authenticatedStateId.value = value + } + + // Models for Multiple Identities View + + private val _isEdgeIdentityRegistered = MutableLiveData(true) + val isEdgeIdentityRegistered: LiveData = _isEdgeIdentityRegistered + + private val _edgeIdentityRegisteredText = MutableLiveData().apply { + value = if (_isEdgeIdentityRegistered.value == true) { + EDGE_IDENTITY_IS_REGISTERED_STRING + } else { + REGISTER_EDGE_IDENTITY_STRING + } + } + val edgeIdentityRegisteredText: LiveData = _edgeIdentityRegisteredText + + private val _isDirectIdentityRegistered = MutableLiveData(false) + val isDirectIdentityRegistered: LiveData = _isDirectIdentityRegistered + + private val _directIdentityRegisteredText = MutableLiveData().apply { + value = if (_isDirectIdentityRegistered.value == true) { + IDENTITY_IS_REGISTERED_STRING + } else { + REGISTER_IDENTITY_STRING + } + } + val directIdentityRegisteredText: LiveData = _directIdentityRegisteredText + + + fun toggleEdgeIdentityRegistration() { + if (_isEdgeIdentityRegistered.value == true) { + _isEdgeIdentityRegistered.value = false + _edgeIdentityRegisteredText.value = REGISTER_EDGE_IDENTITY_STRING + } else { + _isEdgeIdentityRegistered.value = true + _edgeIdentityRegisteredText.value = EDGE_IDENTITY_IS_REGISTERED_STRING + } + } + + fun toggleDirectIdentityRegistration() { + if (_isDirectIdentityRegistered.value == true) { + _isDirectIdentityRegistered.value = false + _directIdentityRegisteredText.value = REGISTER_IDENTITY_STRING + } else { + _isDirectIdentityRegistered.value = true + _directIdentityRegisteredText.value = IDENTITY_IS_REGISTERED_STRING + } + } +} \ No newline at end of file diff --git a/code/app/src/main/java/com/adobe/marketing/edge/identity/app/ui/AssuranceFragment.kt b/code/app/src/main/java/com/adobe/marketing/edge/identity/app/ui/AssuranceFragment.kt new file mode 100644 index 00000000..0fa690db --- /dev/null +++ b/code/app/src/main/java/com/adobe/marketing/edge/identity/app/ui/AssuranceFragment.kt @@ -0,0 +1,41 @@ +/* + Copyright 2021 Adobe. All rights reserved. + This file is licensed to you under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. You may obtain a copy + of the License at http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software distributed under + the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + OF ANY KIND, either express or implied. See the License for the specific language + governing permissions and limitations under the License. + */ + +package com.adobe.marketing.edge.identity.app.ui + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.Button +import android.widget.EditText +import androidx.fragment.app.Fragment +import com.adobe.marketing.edge.identity.app.R +import com.adobe.marketing.mobile.Assurance + +class AssuranceFragment : Fragment() { + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View? { + val root = inflater.inflate(R.layout.fragment_assurance, container, false) + + root.findViewById