From 05bf330e0f2c62979a8f11a16cf0b32c54c8e79a Mon Sep 17 00:00:00 2001 From: Pravin Prakash Kumar Date: Wed, 15 May 2024 10:32:14 -0700 Subject: [PATCH 001/159] Add gitignore --- .gitignore | 102 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 102 insertions(+) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..c3cb4a9b --- /dev/null +++ b/.gitignore @@ -0,0 +1,102 @@ +# Built application files +*.apk +*.aar +*.ap_ +*.aab + +# Files for the ART/Dalvik VM +*.dex + +# Java class files +*.class + +# Generated files +bin/ +gen/ +out/ +# Uncomment the following line in case you need and you don't have the release build type files in your app +# release/ + +# Gradle files +.gradle/ +build/ + +# Local configuration file (sdk path, etc) +local.properties + +# Proguard folder generated by Eclipse +proguard/ + +# Log Files +*.log + +# Android Studio Navigation editor temp files +.navigation/ + +# Android Studio captures folder +captures/ + +# IntelliJ +*.iml +.idea/workspace.xml +.idea/tasks.xml +.idea/gradle.xml +.idea/assetWizardSettings.xml +.idea/dictionaries +.idea/libraries +# Android Studio 3 in .gitignore file. +.idea/caches +.idea/modules.xml +# Comment next line if keeping position of elements in Navigation Editor is relevant for you +.idea/navEditor.xml + +# Keystore files +# Uncomment the following lines if you do not want to check your keystore files in. +#*.jks +#*.keystore + +# External native build folder generated in Android Studio 2.2 and later +.externalNativeBuild +.cxx/ + +# Google Services (e.g. APIs or Firebase) +# google-services.json + +# Freeline +freeline.py +freeline/ +freeline_project_description.json + +# fastlane +fastlane/report.xml +fastlane/Preview.html +fastlane/screenshots +fastlane/test_output +fastlane/readme.md + +# Version control +vcs.xml + +# lint +lint/intermediates/ +lint/generated/ +lint/outputs/ +lint/tmp/ +# lint/reports/ + +# IntelliJ +*.iml +.idea/workspace.xml +.idea/tasks.xml +.idea/gradle.xml +.idea/assetWizardSettings.xml +.idea/dictionaries +.idea/libraries +# Android Studio 3 in .gitignore file. +.idea/caches +.idea/modules.xml +# Comment next line if keeping position of elements in Navigation Editor is relevant for you +.idea/navEditor.xml +.idea +.vscode +.DS_Store \ No newline at end of file From a88c344616e1920f620960cfb4e161e2b9866583 Mon Sep 17 00:00:00 2001 From: Pravin Prakash Kumar Date: Wed, 15 May 2024 10:58:05 -0700 Subject: [PATCH 002/159] Code of conduct, copyright and license --- CODE_OF_CONDUCT.md | 79 ++++++++++++++++++ COPYRIGHT | 5 ++ LICENSE | 201 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 285 insertions(+) create mode 100644 CODE_OF_CONDUCT.md create mode 100644 COPYRIGHT create mode 100644 LICENSE diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 00000000..1ae2aa26 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,79 @@ +# Adobe Code of Conduct + +## Our Pledge + +We as members, contributors, and leaders pledge to make participation in our project and community a harassment-free experience for everyone, regardless of age, body size, visible or invisible disability, ethnicity, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, caste, color, religion, or sexual identity and orientation. + +We pledge to act and interact in ways that contribute to an open, welcoming, diverse, inclusive, and healthy community. + +## Our Standards + +Examples of behavior that contribute to a positive environment for our project and community include: + +* Demonstrating empathy and kindness toward other people +* Being respectful of differing opinions, viewpoints, and experiences +* Giving and gracefully accepting constructive feedback +* Accepting responsibility and apologizing to those affected by our mistakes, and learning from the experience +* Focusing on what is best, not just for us as individuals but for the overall community + +Examples of unacceptable behavior include: + +* The use of sexualized language or imagery, and sexual attention or advances of any kind +* Trolling, insulting or derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others’ private information, such as a physical or email address, without their explicit permission +* Other conduct which could reasonably be considered inappropriate in a professional setting + +## Our Responsibilities + +Project maintainers are responsible for clarifying and enforcing our standards of acceptable behavior and will 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 behaviors that they deem inappropriate, threatening, offensive, or harmful. + +## Scope + +This Code of Conduct applies when an individual is representing the project or its community both within project spaces and in public spaces. Examples of representing a project or community include using an official 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 first contacting the project team. Oversight of Adobe projects is handled by the Adobe Open Source Office, which has final say in any violations and enforcement of this Code of Conduct and can be reached at Grp-opensourceoffice@adobe.com. All complaints will be reviewed and investigated promptly and fairly. + +The project team must respect the privacy and security of the reporter of any incident. + +Project maintainers who do not follow or enforce the Code of Conduct may face temporary or permanent repercussions as determined by other members of the project's leadership or the Adobe Open Source Office. + +## Enforcement Guidelines + +Project maintainers will follow these Community Impact Guidelines in determining the consequences for any action they deem to be in violation of this Code of Conduct: + +**1. Correction** + +Community Impact: Use of inappropriate language or other behavior deemed unprofessional or unwelcome in the community. + +Consequence: A private, written warning from project maintainers describing the violation and why the behavior was unacceptable. A public apology may be requested from the violator before any further involvement in the project by violator. + +**2. Warning** + +Community Impact: A relatively minor violation through a single incident or series of actions. + +Consequence: A written warning from project maintainers that includes stated consequences for continued unacceptable behavior. Violator must refrain from interacting with the people involved for a specified period of time as determined by the project maintainers, including, but not limited to, unsolicited interaction with those enforcing the Code of Conduct through channels such as community spaces and social media. Continued violations may lead to a temporary or permanent ban. + +**3. Temporary Ban** + +Community Impact: A more serious violation of community standards, including sustained unacceptable behavior. + +Consequence: A temporary ban from any interaction or public communication with the community for a specified period of time. No public or private interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, is allowed during this period. Failure to comply with the temporary ban may lead to a permanent ban. + +**4. Permanent Ban** + +Community Impact: Demonstrating a consistent pattern of violation of community standards or an egregious violation of community standards, including, but not limited to, sustained inappropriate behavior, harassment of an individual, or aggression toward or disparagement of classes of individuals. + +Consequence: A permanent ban from any interaction with the community. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 2.1, +available at [https://contributor-covenant.org/version/2/1][version] + +[homepage]: https://contributor-covenant.org +[version]: https://contributor-covenant.org/version/2/1 \ No newline at end of file diff --git a/COPYRIGHT b/COPYRIGHT new file mode 100644 index 00000000..f071e0c7 --- /dev/null +++ b/COPYRIGHT @@ -0,0 +1,5 @@ +© Copyright 2015-2022 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..5ec202a6 --- /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 2019 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. From 5cc1e3a1d06c07f7c7ebc9dbecf091ca802f7174 Mon Sep 17 00:00:00 2001 From: Pravin Prakash Kumar Date: Wed, 15 May 2024 11:06:09 -0700 Subject: [PATCH 003/159] update year for copyright --- COPYRIGHT | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/COPYRIGHT b/COPYRIGHT index f071e0c7..88093633 100644 --- a/COPYRIGHT +++ b/COPYRIGHT @@ -1,4 +1,4 @@ -© Copyright 2015-2022 Adobe. All rights reserved. +© Copyright 2015-2024 Adobe. All rights reserved. Adobe holds the copyright for all the files found in this repository. From a7db064d0a44300c4b496633102f70fc89a43656 Mon Sep 17 00:00:00 2001 From: Pravin Prakash Kumar Date: Wed, 15 May 2024 13:51:22 -0700 Subject: [PATCH 004/159] GitHub Pull request templates --- .github/CONTRIBUTING.md | 52 ++++++++++++++++++++++++++++++++ .github/ISSUE_TEMPLATE.md | 16 ++++++++++ .github/PULL_REQUEST_TEMPLATE.md | 45 +++++++++++++++++++++++++++ 3 files changed, 113 insertions(+) create mode 100644 .github/CONTRIBUTING.md create mode 100644 .github/ISSUE_TEMPLATE.md create mode 100644 .github/PULL_REQUEST_TEMPLATE.md diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md new file mode 100644 index 00000000..26ea8e5d --- /dev/null +++ b/.github/CONTRIBUTING.md @@ -0,0 +1,52 @@ +# Contributing + +Thanks for choosing to contribute! + +The following are a set of guidelines to follow when contributing to this project. + +## Code Of Conduct + +This project adheres to the Adobe [code of conduct](../CODE_OF_CONDUCT.md). By participating, +you are expected to uphold this code. Please report unacceptable behavior to +[Grp-opensourceoffice@adobe.com](mailto:Grp-opensourceoffice@adobe.com). + +## Have A Question? + +Start by filing an issue. The existing committers on this project work to reach +consensus around project direction and issue solutions within issue threads +(when appropriate). + +## Contributor License Agreement + +All third-party contributions to this project must be accompanied by a signed contributor +license agreement. This gives Adobe permission to redistribute your contributions +as part of the project. [Sign our CLA](http://opensource.adobe.com/cla.html). You +only need to submit an Adobe CLA one time, so if you have submitted one previously, +you are good to go! + +## Code Reviews + +All submissions should come in the form of pull requests and need to be reviewed +by project committers. Read [GitHub's pull request documentation](https://help.github.com/articles/about-pull-requests/) +for more information on sending pull requests. + +Lastly, please follow the [pull request template](PULL_REQUEST_TEMPLATE.md) when +submitting a pull request! + +## Style Guide + +Code cleanliness and consistency is important. Please review and follow our code +[Style Guide](../Documentation/Contributing/StyleGuide.md) when contributing. + +## From Contributor To Committer + +We love contributions from our community! If you'd like to go a step beyond contributor +and become a committer with full write access and a say in the project, you must +be invited to the project. The existing committers employ an internal nomination +process that must reach lazy consensus (silence is approval) before invitations +are issued. If you feel you are qualified and want to get more deeply involved, +feel free to reach out to existing committers to have a conversation about that. + +## Security Issues + +Security issues shouldn't be reported on this issue tracker. Instead, [file an issue to our security experts](https://helpx.adobe.com/security/alertus.html) diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md new file mode 100644 index 00000000..524718e3 --- /dev/null +++ b/.github/ISSUE_TEMPLATE.md @@ -0,0 +1,16 @@ + + + +### Expected Behaviour + +### Actual Behaviour + +### Reproduce Scenario (including but not limited to) + +#### Steps to Reproduce + +#### Platform and Version + +#### Sample Code that illustrates the problem + +#### Logs taken while reproducing problem diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 00000000..dd6a02ab --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,45 @@ + + +## Description + + + +## Related Issue + + + + + + +## Motivation and Context + + + +## How Has This Been Tested? + + + + + +## Screenshots (if appropriate): + +## Types of changes + + + +- [ ] Bug fix (non-breaking change which fixes an issue) +- [ ] New feature (non-breaking change which adds functionality) +- [ ] Breaking change (fix or feature that would cause existing functionality to change) + +## Checklist: + + + + +- [ ] I have signed the [Adobe Open Source CLA](http://opensource.adobe.com/cla.html). +- [ ] My code follows the code style of this project. +- [ ] My change requires a change to the documentation. +- [ ] I have updated the documentation accordingly. +- [ ] I have read the **CONTRIBUTING** document. +- [ ] I have added tests to cover my changes. +- [ ] All new and existing tests passed. From 442562525d01f7eb488c87230ec7146fdd8ceba1 Mon Sep 17 00:00:00 2001 From: Pravin Prakash Kumar Date: Wed, 15 May 2024 13:51:48 -0700 Subject: [PATCH 005/159] Github actions release scripts --- .github/release-drafter.yml | 16 ++++ .github/workflows/maven-release.yml | 82 +++++++++++++++++ .github/workflows/maven-snapshot.yml | 57 ++++++++++++ .github/workflows/update-version.yml | 92 ++++++++++++++++++ scripts/version.sh | 133 +++++++++++++++++++++++++++ 5 files changed, 380 insertions(+) create mode 100644 .github/release-drafter.yml create mode 100644 .github/workflows/maven-release.yml create mode 100644 .github/workflows/maven-snapshot.yml create mode 100644 .github/workflows/update-version.yml create mode 100755 scripts/version.sh diff --git a/.github/release-drafter.yml b/.github/release-drafter.yml new file mode 100644 index 00000000..e053484e --- /dev/null +++ b/.github/release-drafter.yml @@ -0,0 +1,16 @@ +# +# Copyright 2024 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. +# + +template: | + ## What’s Changed + + $CHANGES \ No newline at end of file diff --git a/.github/workflows/maven-release.yml b/.github/workflows/maven-release.yml new file mode 100644 index 00000000..ea24b0e0 --- /dev/null +++ b/.github/workflows/maven-release.yml @@ -0,0 +1,82 @@ +# +# Copyright 2024 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. +# + +name: Publish Release +on: + workflow_dispatch: + inputs: + component: + type: choice + description: UI Component to release + options: + - notificationbuilder + + tag: + description: 'tag/version' + required: true + + action_tag: + description: 'Create tag? ("no" to skip)' + required: true + default: 'yes' +jobs: + publish: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + + - name: Set up Java + uses: actions/setup-java@v3 + with: + distribution: 'zulu' + java-version: 17 + + - name: Cache Gradle packages + uses: actions/cache@v2 + with: + path: ~/.gradle/caches + key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle') }} + restore-keys: ${{ runner.os }}-gradle + + - name: Verify version + run: | + set -eo pipefail + echo Release version: ${{ github.event.inputs.tag }} + (./scripts/version.sh -n ${{ github.event.inputs.component }} -v ${{ github.event.inputs.tag }}) + + - name: Create GH Release + id: create_release + uses: release-drafter/release-drafter@v5 + if: ${{ github.event.inputs.action_tag == 'yes' }} + with: + name: v${{ github.event.inputs.tag }}-${{ github.event.inputs.component }} + tag: v${{ github.event.inputs.tag }}-${{ github.event.inputs.component }} + version: v${{ github.event.inputs.tag }}-${{ github.event.inputs.component }} + publish: true + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Import GPG key + env: + GPG_SECRET_KEYS: ${{ secrets.GPG_SECRET_KEYS }} + GPG_OWNERTRUST: ${{ secrets.GPG_OWNERTRUST }} + run: | + echo $GPG_SECRET_KEYS | base64 --decode | gpg --import --no-tty --batch --yes + echo $GPG_OWNERTRUST | base64 --decode | gpg --import-ownertrust --no-tty --batch --yes + + - name: Publish to Maven Central Repository + run: make ${{ github.event.inputs.component }}-publish-main + env: + SONATYPE_USERNAME: ${{ secrets.SONATYPE_USERNAME }} + SONATYPE_PASSWORD: ${{ secrets.SONATYPE_PASSWORD }} + GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }} + GPG_KEY_ID: ${{ secrets.GPG_KEY_ID }} diff --git a/.github/workflows/maven-snapshot.yml b/.github/workflows/maven-snapshot.yml new file mode 100644 index 00000000..85b45f0b --- /dev/null +++ b/.github/workflows/maven-snapshot.yml @@ -0,0 +1,57 @@ +# +# Copyright 2024 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. +# + +name: Publish Snapshot +on: + workflow_dispatch: + inputs: + release_notificationbuilder: + required: false + type: boolean + default: false + description: Release Notification Builder + +jobs: + publish: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + + - name: Set up Java + uses: actions/setup-java@v3 + with: + distribution: 'zulu' + java-version: 17 + + - name: Cache Gradle packages + uses: actions/cache@v2 + with: + path: ~/.gradle/caches + key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle') }} + restore-keys: ${{ runner.os }}-gradle + + - name: Import GPG key + env: + GPG_SECRET_KEYS: ${{ secrets.GPG_SECRET_KEYS }} + GPG_OWNERTRUST: ${{ secrets.GPG_OWNERTRUST }} + run: | + echo $GPG_SECRET_KEYS | base64 --decode | gpg --import --no-tty --batch --yes + echo $GPG_OWNERTRUST | base64 --decode | gpg --import-ownertrust --no-tty --batch --yes + + - name: Publish NotificationBuilder to Maven Snapshot Repository + if: ${{ inputs.release_notificationbuilder }} + run: make notificationbuilder-publish-snapshot + env: + SONATYPE_USERNAME: ${{ secrets.SONATYPE_USERNAME }} + SONATYPE_PASSWORD: ${{ secrets.SONATYPE_PASSWORD }} + GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }} + GPG_KEY_ID: ${{ secrets.GPG_KEY_ID }} diff --git a/.github/workflows/update-version.yml b/.github/workflows/update-version.yml new file mode 100644 index 00000000..45ac6648 --- /dev/null +++ b/.github/workflows/update-version.yml @@ -0,0 +1,92 @@ +# +# Copyright 2024 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. +# + +name: Update Version +on: + workflow_dispatch: + inputs: + notificationbuilder-version: + description: 'New version to use for the NotificationBuilder. Example: 3.0.0' + required: true + + +jobs: + update-version: + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Update NotificationBuilder version + run: (./scripts/version.sh -u -n NotificationBuilder -v ${{ github.event.inputs.version }}") + + - name: Create Pull Request + uses: peter-evans/create-pull-request@v5 + with: + token: ${{ github.token }} + commit-message: Updating version to ${{ github.event.inputs.version }}. + branch: version-${{ github.event.inputs.version }}-update + delete-branch: true + title: Updating version to ${{ github.event.inputs.version }} + body: Updating version to ${{ github.event.inputs.version }} + + + +name: Update Versions + +on: + workflow_dispatch: + inputs: + notificationbuilder-version: + description: 'New version to use for the NotificationBuilder extension. Example: 3.0.0' + required: false + +jobs: + update-version: + runs-on: ubuntu-latest + + steps: + + - name: Checkout + uses: actions/checkout@v3 + + - if: ${{ github.event.inputs.notificationbuilder-version != '' }} + name: Update AEPNotificationBuilder version + run: (sh ./scripts/version.sh -u -n Core -v ${{ github.event.inputs.notificationbuilder-version }}) + + - name: Generate Commit Message + shell: bash + run: | + COMMIT_MSG="" + if [ "${{ github.event.inputs.core-version }}" ]; then + COMMIT_MSG="[Core-${{ github.event.inputs.core-version }}]" + fi + if [ "${{ github.event.inputs.identity-version }}" ]; then + COMMIT_MSG="$COMMIT_MSG [Identity-${{ github.event.inputs.identity-version }}]" + fi + if [ "${{ github.event.inputs.lifecycle-version }}" ]; then + COMMIT_MSG="$COMMIT_MSG [Lifecycle-${{ github.event.inputs.lifecycle-version }}]" + fi + if [ "${{ github.event.inputs.signal-version }}" ]; then + COMMIT_MSG="$COMMIT_MSG [Signal-${{ github.event.inputs.signal-version }}]" + fi + echo $COMMIT_MSG + echo COMMIT_MSG=$COMMIT_MSG >> $GITHUB_ENV + + - name: Create Pull Request + uses: peter-evans/create-pull-request@v5 + with: + delete-branch: true + commit-message: Update versions ${{ env.COMMIT_MSG }} + title: Update versions ${{ env.COMMIT_MSG }} + body: Update versions ${{ env.COMMIT_MSG }} \ No newline at end of file diff --git a/scripts/version.sh b/scripts/version.sh new file mode 100755 index 00000000..c82f6834 --- /dev/null +++ b/scripts/version.sh @@ -0,0 +1,133 @@ +#!/bin/bash + +# Make this script executable from terminal: +# chmod 755 version.sh +set -e # Any subsequent(*) commands which fail will cause the shell script to exit immediately + +ROOT_DIR=$(git rev-parse --show-toplevel) +LINE="================================================================================" +VERSION_REGEX="[0-9]+\.[0-9]+\.[0-9]+" + +GRADLE_PROPERTIES_FILE=$ROOT_DIR"/code/gradle.properties" + +# NotificationBuilder files +NOTIFICATION_BUILDER_CONSTANTS_FILE=$ROOT_DIR"/code/notificationbuilder/src/java/com/adobe/marketing/mobile/NotificationBuilder.kt" +NOTIFICATION_BUILDER_CONSTANTS_FILE_VERSION_REGEX="^ +const val VERSION *= *" + +help() +{ + echo "" + echo "Usage: $0 -n COMPONENT_NAME -v VERSION -d DEPENDENCIES" + echo "" + echo -e " -v\t- Version to update or verify for the component. \n\t Example: 3.0.2\n" + echo -e " -d\t- Comma seperated dependecies to update along with their version. \n\t Example: "Core 3.1.1, Edge 3.2.1"\n" + echo -e " -u\t- Updates the version. If this flag is absent, the script verifies if the version is correct\n" + exit 1 # Exit script after printing help +} + +sed_platform() { + # Ensure sed works properly in linux and mac-os. + if [[ "$OSTYPE" == "darwin"* ]]; then + sed -i '' "$@" + else + sed -i "$@" + fi +} + +update() { + echo "Changing $NAME version to $VERSION" + + # Replace version in Constants file + echo "Changing 'VERSION' to '$VERSION' in '$CONSTANTS_FILE'" + sed_platform -E "/$CONSTANTS_FILE_VERSION_REGEX/{s/$VERSION_REGEX/$VERSION/;}" $CONSTANTS_FILE + + # Replace version in gradle.properties + echo "Changing $GRADLE_TAG to '$VERSION' in '$GRADLE_PROPERTIES_FILE'" + sed_platform -E "/^$GRADLE_TAG/{s/$VERSION_REGEX/$VERSION/;}" $GRADLE_PROPERTIES_FILE + + # Replace dependencies in gradle.properties + if [ "$DEPENDENCIES" != "none" ]; then + IFS="," + dependenciesArray=($(echo "$DEPENDENCIES")) + + IFS=" " + for dependency in "${dependenciesArray[@]}"; do + dependencyArray=(${dependency// / }) + dependencyName=${dependencyArray[0]} + dependencyVersion=${dependencyArray[1]} + + if [ "$dependencyVersion" != "" ]; then + echo "Changing 'maven${dependencyName}Version' to '$dependencyVersion' in '$GRADLE_PROPERTIES_FILE'" + sed_platform -E "/^maven${dependencyName}Version/{s/$VERSION_REGEX/$dependencyVersion/;}" $GRADLE_PROPERTIES_FILE + fi + done + fi +} + +verify() { + echo "Verifing $NAME version is $VERSION" + + if ! grep -E "$CONSTANTS_FILE_VERSION_REGEX\"$VERSION\"" "$CONSTANTS_FILE" >/dev/null; then + echo "'VERSION' does not match '$VERSION' in '$CONSTANTS_FILE'" + exit 1 + fi + + if ! grep -E "^$GRADLE_TAG=.*$VERSION" "$GRADLE_PROPERTIES_FILE" >/dev/null; then + echo "'version' does not match '$VERSION' in '$GRADLE_PROPERTIES_FILE'" + exit 1 + fi + + if [ "$DEPENDENCIES" != "none" ]; then + IFS="," + dependenciesArray=($(echo "$DEPENDENCIES")) + + IFS=" " + for dependency in "${dependenciesArray[@]}"; do + dependencyArray=(${dependency// / }) + dependencyName=${dependencyArray[0]} + dependencyVersion=${dependencyArray[1]} + + if [ "$dependencyVersion" != "" ]; then + if ! grep -E "^maven${dependencyName}Version=.*$dependencyVersion" "$GRADLE_PROPERTIES_FILE" >/dev/null; then + echo "maven${dependencyName}Version does not match '$dependencyVersion' in '$GRADLE_PROPERTIES_FILE'" + exit 1 + fi + fi + done + fi + + echo "Success" +} + + +while getopts "n:v:d:u" opt +do + case "$opt" in + n ) NAME="$OPTARG" ;; + v ) VERSION="$OPTARG" ;; + d ) DEPENDENCIES="$OPTARG" ;; + u ) UPDATE="true" ;; + ? ) help ;; # Print help in case parameter is non-existent + esac +done + +# Print help in case parameters are empty +if [ -z "$NAME" ] || [ -z "$VERSION" ] +then + echo "********** USAGE ERROR **********" + echo "Some or all of the parameters are empty. See usage below:"; + help +fi + +eval CONSTANTS_FILE=\$$"$NAME_UC"_CONSTANTS_FILE +eval CONSTANTS_FILE_VERSION_REGEX=\$$"$NAME_UC"_CONSTANTS_FILE_VERSION_REGEX +GRADLE_TAG="$NAME_LC"Version + +echo "$LINE" +if [[ ${UPDATE} = "true" ]]; +then + update +else + verify +fi +echo "$LINE" \ No newline at end of file From 00121a34ca78d51ceb61f40645ef876dd62e9a1f Mon Sep 17 00:00:00 2001 From: Pravin Prakash Kumar Date: Wed, 15 May 2024 16:15:42 -0700 Subject: [PATCH 006/159] Removing repeated steps --- .github/workflows/update-version.yml | 55 +++------------------------- scripts/version.sh | 12 ++++-- 2 files changed, 14 insertions(+), 53 deletions(-) diff --git a/.github/workflows/update-version.yml b/.github/workflows/update-version.yml index 45ac6648..826d0527 100644 --- a/.github/workflows/update-version.yml +++ b/.github/workflows/update-version.yml @@ -10,76 +10,33 @@ # governing permissions and limitations under the License. # -name: Update Version -on: - workflow_dispatch: - inputs: - notificationbuilder-version: - description: 'New version to use for the NotificationBuilder. Example: 3.0.0' - required: true - - -jobs: - update-version: - runs-on: ubuntu-latest - - steps: - - name: Checkout - uses: actions/checkout@v3 - - - name: Update NotificationBuilder version - run: (./scripts/version.sh -u -n NotificationBuilder -v ${{ github.event.inputs.version }}") - - - name: Create Pull Request - uses: peter-evans/create-pull-request@v5 - with: - token: ${{ github.token }} - commit-message: Updating version to ${{ github.event.inputs.version }}. - branch: version-${{ github.event.inputs.version }}-update - delete-branch: true - title: Updating version to ${{ github.event.inputs.version }} - body: Updating version to ${{ github.event.inputs.version }} - - - name: Update Versions - on: workflow_dispatch: inputs: notificationbuilder-version: - description: 'New version to use for the NotificationBuilder extension. Example: 3.0.0' + description: 'New version to use for the NotificationBuilder. Example: 3.0.0' required: false jobs: update-version: runs-on: ubuntu-latest - steps: - + steps: - name: Checkout uses: actions/checkout@v3 - if: ${{ github.event.inputs.notificationbuilder-version != '' }} - name: Update AEPNotificationBuilder version - run: (sh ./scripts/version.sh -u -n Core -v ${{ github.event.inputs.notificationbuilder-version }}) + name: Update NotificationBuilder version + run: (sh ./scripts/version.sh -u -n NotificationBuilder -v ${{ github.event.inputs.notificationbuilder-version }}) - name: Generate Commit Message shell: bash run: | COMMIT_MSG="" - if [ "${{ github.event.inputs.core-version }}" ]; then - COMMIT_MSG="[Core-${{ github.event.inputs.core-version }}]" + if [ "${{ github.event.inputs.notificationbuilder-version }}" ]; then + COMMIT_MSG="[NotificationBuilder-${{ github.event.inputs.notificationbuilder-version }}]" fi - if [ "${{ github.event.inputs.identity-version }}" ]; then - COMMIT_MSG="$COMMIT_MSG [Identity-${{ github.event.inputs.identity-version }}]" - fi - if [ "${{ github.event.inputs.lifecycle-version }}" ]; then - COMMIT_MSG="$COMMIT_MSG [Lifecycle-${{ github.event.inputs.lifecycle-version }}]" - fi - if [ "${{ github.event.inputs.signal-version }}" ]; then - COMMIT_MSG="$COMMIT_MSG [Signal-${{ github.event.inputs.signal-version }}]" - fi echo $COMMIT_MSG echo COMMIT_MSG=$COMMIT_MSG >> $GITHUB_ENV diff --git a/scripts/version.sh b/scripts/version.sh index c82f6834..b543eac1 100755 --- a/scripts/version.sh +++ b/scripts/version.sh @@ -11,8 +11,8 @@ VERSION_REGEX="[0-9]+\.[0-9]+\.[0-9]+" GRADLE_PROPERTIES_FILE=$ROOT_DIR"/code/gradle.properties" # NotificationBuilder files -NOTIFICATION_BUILDER_CONSTANTS_FILE=$ROOT_DIR"/code/notificationbuilder/src/java/com/adobe/marketing/mobile/NotificationBuilder.kt" -NOTIFICATION_BUILDER_CONSTANTS_FILE_VERSION_REGEX="^ +const val VERSION *= *" +NOTIFICATIONBUILDER_CONSTANTFILE=$ROOT_DIR"/code/notificationbuilder/src/java/com/adobe/marketing/mobile/NotificationBuilder.kt" +NOTIFICATIONBUILDER_CONSTANTFILE_VERSION_REGEX="^ +const val VERSION *= *" help() { @@ -119,8 +119,12 @@ then help fi -eval CONSTANTS_FILE=\$$"$NAME_UC"_CONSTANTS_FILE -eval CONSTANTS_FILE_VERSION_REGEX=\$$"$NAME_UC"_CONSTANTS_FILE_VERSION_REGEX + +NAME_LC=$(echo "$NAME" | tr '[:upper:]' '[:lower:]') +NAME_UC=$(echo "$NAME" | tr '[:lower:]' '[:upper:]') + +eval CONSTANTS_FILE=\$$"$NAME_UC"_CONSTANTFILE +eval CONSTANTS_FILE_VERSION_REGEX=\$$"$NAME_UC"_CONSTANTFILE_VERSION_REGEX GRADLE_TAG="$NAME_LC"Version echo "$LINE" From 5e6875216923b9fb8b7215a142da38b8b8d78096 Mon Sep 17 00:00:00 2001 From: Pravin Prakash Kumar Date: Wed, 15 May 2024 19:04:45 -0700 Subject: [PATCH 007/159] Gradle, Project and test file --- code/build.gradle.kts | 24 +++ code/gradle.properties | 29 ++++ code/gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 53636 bytes code/gradle/wrapper/gradle-wrapper.properties | 6 + code/gradlew | 160 ++++++++++++++++++ code/gradlew.bat | 90 ++++++++++ code/notificationbuilder/build.gradle.kts | 40 +++++ .../src/main/AndroidManifest.xml | 17 ++ .../NotificationBuilder.kt | 24 +++ .../NotificationBuilderTests.java | 23 +++ code/settings.gradle.kts | 35 ++++ 11 files changed, 448 insertions(+) create mode 100644 code/build.gradle.kts 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/notificationbuilder/build.gradle.kts create mode 100755 code/notificationbuilder/src/main/AndroidManifest.xml create mode 100644 code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/NotificationBuilder.kt create mode 100644 code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/NotificationBuilderTests.java create mode 100755 code/settings.gradle.kts diff --git a/code/build.gradle.kts b/code/build.gradle.kts new file mode 100644 index 00000000..fec4663b --- /dev/null +++ b/code/build.gradle.kts @@ -0,0 +1,24 @@ +/* + * Copyright 2024 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. + */ + +buildscript { + repositories { + gradlePluginPortal() + google() + mavenCentral() + maven { url = uri("https://jitpack.io") } + mavenLocal() + } + dependencies { + classpath("com.github.adobe:aepsdk-commons:gp-3.0.0") + } +} diff --git a/code/gradle.properties b/code/gradle.properties new file mode 100644 index 00000000..f87e7936 --- /dev/null +++ b/code/gradle.properties @@ -0,0 +1,29 @@ +# +# Copyright 2024 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. +# + +org.gradle.jvmargs=-Xmx2048m +android.injected.testOnly = false +org.gradle.configureondemand=false +android.useAndroidX=true + +#Maven artifacts +#Notification Builder Module +notificationbuilderModuleName=notificationbuilder +notificationbuilderVersion=3.0.0 +notificationbuilderMavenRepoName=AdobeMobileNotificationBuilderSdk +notificationbuilderMavenRepoDescription=Android Notification Builder library for Adobe Mobile Marketing + +mavenCoreVersion=3.0.0 + + +android.nonTransitiveRClass=false +android.nonFinalResIds=false diff --git a/code/gradle/wrapper/gradle-wrapper.jar b/code/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..13372aef5e24af05341d49695ee84e5f9b594659 GIT binary patch literal 53636 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ⅅ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 H-+%o-(+5aQ 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..bf8503ad --- /dev/null +++ b/code/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Sun Jul 10 17:49:57 MDT 2022 +distributionBase=GRADLE_USER_HOME +distributionUrl=https\://services.gradle.org/distributions/gradle-8.2-bin.zip +distributionPath=wrapper/dists +zipStorePath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME diff --git a/code/gradlew b/code/gradlew new file mode 100755 index 00000000..9d82f789 --- /dev/null +++ b/code/gradlew @@ -0,0 +1,160 @@ +#!/usr/bin/env bash + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS="" + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# 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 +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; +esac + +# 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 + +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" ] ; 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 + +# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules +function splitJvmOpts() { + JVM_OPTS=("$@") +} +eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS +JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" + +exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" diff --git a/code/gradlew.bat b/code/gradlew.bat new file mode 100644 index 00000000..aec99730 --- /dev/null +++ b/code/gradlew.bat @@ -0,0 +1,90 @@ +@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 + +@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= + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@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 Windowz variants + +if not "%OS%" == "Windows_NT" goto win9xME_args +if "%@eval[2+2]" == "4" goto 4NT_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=%* +goto execute + +:4NT_args +@rem Get arguments from the 4NT Shell from JP Software +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/notificationbuilder/build.gradle.kts b/code/notificationbuilder/build.gradle.kts new file mode 100644 index 00000000..e4cd77da --- /dev/null +++ b/code/notificationbuilder/build.gradle.kts @@ -0,0 +1,40 @@ +/* + * Copyright 2024 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. + */ +plugins { + id("aep-library") +} + +val mavenCoreVersion: String by project +val notificationbuilderModuleName: String by project +val notificationbuilderVersion: String by project +val notificationbuilderMavenRepoName: String by project +val notificationbuilderMavenRepoDescription: String by project + + +aepLibrary { + namespace = "com.adobe.marketing.mobile.notificationbuilder" + moduleName = notificationbuilderModuleName + moduleVersion = notificationbuilderVersion + enableSpotless = true + enableCheckStyle = true + + publishing { + mavenRepoName = notificationbuilderMavenRepoName + mavenRepoDescription = notificationbuilderMavenRepoDescription + gitRepoName = "aepsdk-ui-android" + addCoreDependency(mavenCoreVersion) + } +} + +dependencies { + implementation("com.adobe.marketing.mobile:core:$mavenCoreVersion") +} diff --git a/code/notificationbuilder/src/main/AndroidManifest.xml b/code/notificationbuilder/src/main/AndroidManifest.xml new file mode 100755 index 00000000..f33b4529 --- /dev/null +++ b/code/notificationbuilder/src/main/AndroidManifest.xml @@ -0,0 +1,17 @@ + + + + + diff --git a/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/NotificationBuilder.kt b/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/NotificationBuilder.kt new file mode 100644 index 00000000..22d8315e --- /dev/null +++ b/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/NotificationBuilder.kt @@ -0,0 +1,24 @@ +/* + Copyright 2024 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.notificationbuilder; + +import kotlin.jvm.JvmStatic; + +object NotificationBuilder { + private const val SELF_TAG = "NotificationBuilder" + const val VERSION = "3.0.0" + + @JvmStatic + fun extensionVersion(): String { + return VERSION + } +} \ No newline at end of file diff --git a/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/NotificationBuilderTests.java b/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/NotificationBuilderTests.java new file mode 100644 index 00000000..5a22de1e --- /dev/null +++ b/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/NotificationBuilderTests.java @@ -0,0 +1,23 @@ +/* + Copyright 2024 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.notificationbuilder; + +import static org.junit.Assert.assertEquals; +import org.junit.Test; + +public class NotificationBuilderTests { + + @Test + public void test_extensionVersion() { + assertEquals("3.0.0", NotificationBuilder.extensionVersion()); + } +} diff --git a/code/settings.gradle.kts b/code/settings.gradle.kts new file mode 100755 index 00000000..f415c3b0 --- /dev/null +++ b/code/settings.gradle.kts @@ -0,0 +1,35 @@ +/* + * Copyright 2024 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. + */ +pluginManagement { + repositories { + gradlePluginPortal() + google() + mavenCentral() + mavenLocal() + } +} + +dependencyResolutionManagement { + repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) + repositories { + google() + mavenCentral() + mavenLocal() + maven { url = uri("https://oss.sonatype.org/content/repositories/snapshots/") } + maven { url = uri("https://jitpack.io") } + } +} + +rootProject.name = "aepsdk-notificationbuilder-android" +include ( + ":notificationbuilder" +) \ No newline at end of file From 528ca65a2b61e7f31780bf6dc6c9395f4ae3cab9 Mon Sep 17 00:00:00 2001 From: Pravin Prakash Kumar Date: Wed, 15 May 2024 22:45:12 -0700 Subject: [PATCH 008/159] update version script now includes core --- .github/workflows/update-version.yml | 6 +++++- scripts/version.sh | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/.github/workflows/update-version.yml b/.github/workflows/update-version.yml index 826d0527..999a7547 100644 --- a/.github/workflows/update-version.yml +++ b/.github/workflows/update-version.yml @@ -18,6 +18,10 @@ on: description: 'New version to use for the NotificationBuilder. Example: 3.0.0' required: false + core-dependency: + description: '[Optional] Update Core dependency in pom.xml. Example: 3.0.0' + required: false + jobs: update-version: runs-on: ubuntu-latest @@ -28,7 +32,7 @@ jobs: - if: ${{ github.event.inputs.notificationbuilder-version != '' }} name: Update NotificationBuilder version - run: (sh ./scripts/version.sh -u -n NotificationBuilder -v ${{ github.event.inputs.notificationbuilder-version }}) + run: (sh ./scripts/version.sh -u -n NotificationBuilder -v ${{ github.event.inputs.notificationbuilder-version }} -d "Core ${{ github.event.inputs.core-dependency }}") - name: Generate Commit Message shell: bash diff --git a/scripts/version.sh b/scripts/version.sh index b543eac1..4939ff52 100755 --- a/scripts/version.sh +++ b/scripts/version.sh @@ -11,7 +11,7 @@ VERSION_REGEX="[0-9]+\.[0-9]+\.[0-9]+" GRADLE_PROPERTIES_FILE=$ROOT_DIR"/code/gradle.properties" # NotificationBuilder files -NOTIFICATIONBUILDER_CONSTANTFILE=$ROOT_DIR"/code/notificationbuilder/src/java/com/adobe/marketing/mobile/NotificationBuilder.kt" +NOTIFICATIONBUILDER_CONSTANTFILE=$ROOT_DIR"/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/NotificationBuilder.kt" NOTIFICATIONBUILDER_CONSTANTFILE_VERSION_REGEX="^ +const val VERSION *= *" help() From 4db2926e9ccf906fb9c261e59b08991517ed53a2 Mon Sep 17 00:00:00 2001 From: Pravin Prakash Kumar Date: Wed, 15 May 2024 23:13:09 -0700 Subject: [PATCH 009/159] fix formatting --- .../mobile/notificationbuilder/NotificationBuilder.kt | 6 +++--- .../notificationbuilder/NotificationBuilderTests.java | 1 + 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/NotificationBuilder.kt b/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/NotificationBuilder.kt index 22d8315e..0d336df2 100644 --- a/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/NotificationBuilder.kt +++ b/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/NotificationBuilder.kt @@ -9,9 +9,9 @@ governing permissions and limitations under the License. */ -package com.adobe.marketing.mobile.notificationbuilder; +package com.adobe.marketing.mobile.notificationbuilder -import kotlin.jvm.JvmStatic; +import kotlin.jvm.JvmStatic object NotificationBuilder { private const val SELF_TAG = "NotificationBuilder" @@ -21,4 +21,4 @@ object NotificationBuilder { fun extensionVersion(): String { return VERSION } -} \ No newline at end of file +} diff --git a/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/NotificationBuilderTests.java b/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/NotificationBuilderTests.java index 5a22de1e..24efa5b6 100644 --- a/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/NotificationBuilderTests.java +++ b/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/NotificationBuilderTests.java @@ -12,6 +12,7 @@ package com.adobe.marketing.mobile.notificationbuilder; import static org.junit.Assert.assertEquals; + import org.junit.Test; public class NotificationBuilderTests { From a4fb71ae36e86e3bdff1afaa12559bad86295b80 Mon Sep 17 00:00:00 2001 From: Pravin Prakash Kumar Date: Thu, 16 May 2024 12:19:14 -0700 Subject: [PATCH 010/159] update API name and enable dokkadoc --- code/notificationbuilder/build.gradle.kts | 1 + .../marketing/mobile/notificationbuilder/NotificationBuilder.kt | 2 +- .../mobile/notificationbuilder/NotificationBuilderTests.java | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/code/notificationbuilder/build.gradle.kts b/code/notificationbuilder/build.gradle.kts index e4cd77da..3e4b5657 100644 --- a/code/notificationbuilder/build.gradle.kts +++ b/code/notificationbuilder/build.gradle.kts @@ -26,6 +26,7 @@ aepLibrary { moduleVersion = notificationbuilderVersion enableSpotless = true enableCheckStyle = true + enableDokkaDoc = true publishing { mavenRepoName = notificationbuilderMavenRepoName diff --git a/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/NotificationBuilder.kt b/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/NotificationBuilder.kt index 0d336df2..ea0c7de7 100644 --- a/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/NotificationBuilder.kt +++ b/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/NotificationBuilder.kt @@ -18,7 +18,7 @@ object NotificationBuilder { const val VERSION = "3.0.0" @JvmStatic - fun extensionVersion(): String { + fun version(): String { return VERSION } } diff --git a/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/NotificationBuilderTests.java b/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/NotificationBuilderTests.java index 24efa5b6..a0341a66 100644 --- a/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/NotificationBuilderTests.java +++ b/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/NotificationBuilderTests.java @@ -19,6 +19,6 @@ public class NotificationBuilderTests { @Test public void test_extensionVersion() { - assertEquals("3.0.0", NotificationBuilder.extensionVersion()); + assertEquals("3.0.0", NotificationBuilder.version()); } } From d65fd4bfafe230138075b46a39d662c2985df8e5 Mon Sep 17 00:00:00 2001 From: Pravin Prakash Kumar Date: Thu, 16 May 2024 08:18:14 -0700 Subject: [PATCH 011/159] Adding Circle CI and make files --- .circleci/config.yml | 104 ++++++++++++++++++++++++++++ .github/workflows/maven-release.yml | 2 +- Makefile | 45 ++++++++++++ jitpack.yml | 16 +++++ 4 files changed, 166 insertions(+), 1 deletion(-) create mode 100644 .circleci/config.yml create mode 100644 Makefile create mode 100644 jitpack.yml diff --git a/.circleci/config.yml b/.circleci/config.yml new file mode 100644 index 00000000..6ae67386 --- /dev/null +++ b/.circleci/config.yml @@ -0,0 +1,104 @@ +# +# Copyright 2024 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. +# + +version: 2.1 +orbs: + # Use the CircleCI Android orb version that supports OpenJDK17 required by AGP 8.2+ + android: circleci/android@2.4.0 + codecov: codecov/codecov@3.2.4 + +jobs: + build-and-unit-test: + executor: + name: android/android-docker + resource-class: large + tag: 2024.01.1 + + steps: + - checkout + + - run: + name: Check format + command: make checkformat + + - run: + name: Check style + command: make checkstyle + + - run: + name: Javadoc + command: make javadoc + + - store_artifacts: + path: code/userprofile/build/docs/javadoc + + - run: + name: Assemble phone + command: make assemble-phone + + - run: + name: Build Jitpack Library + command: make ci-publish-maven-local-jitpack + + - run: + name: Build Test app + command: make assemble-app + + - run: + name: Run Unit tests + command: make unit-test-coverage + + - store_test_results: + path: code/userprofile/build/test-results/testPhoneDebugUnitTest + + - codecov/upload: + file: code/userprofile/build/reports/coverage/test/phone/debug/report.xml + flags: unit-tests + + + functional-test: + executor: + name: android/android-machine + resource-class: large + tag: 2024.01.1 + + steps: + - checkout + + - android/create-avd: + avd-name: myavd + install: true + system-image: system-images;android-29;default;x86 + + - android/start-emulator: + avd-name: myavd + no-window: true + post-emulator-launch-assemble-command: "" + restore-gradle-cache-prefix: v1a + + - run: + name: Run Functional tests + command: | + make functional-test-coverage + + - codecov/upload: + file: code/core/build/reports/coverage/androidTest/phone/connected/debug/report.xml + flags: functional-tests + + - android/save-gradle-cache: + cache-prefix: v1a + +workflows: + build: + jobs: + - build-and-unit-test + # - functional-test diff --git a/.github/workflows/maven-release.yml b/.github/workflows/maven-release.yml index ea24b0e0..23978656 100644 --- a/.github/workflows/maven-release.yml +++ b/.github/workflows/maven-release.yml @@ -74,7 +74,7 @@ jobs: echo $GPG_OWNERTRUST | base64 --decode | gpg --import-ownertrust --no-tty --batch --yes - name: Publish to Maven Central Repository - run: make ${{ github.event.inputs.component }}-publish-main + run: make ${{ github.event.inputs.component }}-publish env: SONATYPE_USERNAME: ${{ secrets.SONATYPE_USERNAME }} SONATYPE_PASSWORD: ${{ secrets.SONATYPE_PASSWORD }} diff --git a/Makefile b/Makefile new file mode 100644 index 00000000..44c7e16b --- /dev/null +++ b/Makefile @@ -0,0 +1,45 @@ +checkstyle: + (./code/gradlew -p code/notificationBuilder checkstyle) + +checkformat: + (./code/gradlew -p code/notificationBuilder spotlessCheck) + +format: + (./code/gradlew -p code/notificationBuilder spotlessApply) + +format-license: + (./code/gradlew -p code licenseFormat) + +javadoc: + (./code/gradlew -p code/notificationBuilder javadocJar) + +unit-test: + (./code/gradlew -p code/notificationBuilder testPhoneDebugUnitTest) + +unit-test-coverage: + (./code/gradlew -p code/notificationBuilder createPhoneDebugUnitTestCoverageReport) + +functional-test: + (./code/gradlew -p code/notificationBuilder uninstallPhoneDebugAndroidTest) + (./code/gradlew -p code/notificationBuilder connectedPhoneDebugAndroidTest) + +functional-test-coverage: + (./code/gradlew -p code/notificationBuilder createPhoneDebugAndroidTestCoverageReport) + +assemble-phone: + (./code/gradlew -p code/notificationBuilder assemblePhone) + +assemble-phone-release: + (./code/gradlew -p code/notificationBuilder assemblePhoneRelease) + +assemble-app: + (./code/gradlew -p code/testapp assemble) + +notificationbuilder-publish-maven-local-jitpack: assemble-phone-release + (./code/gradlew -p code/notificationBuilder publishReleasePublicationToMavenLocal -Pjitpack -x signReleasePublication) + +notificationbuilder-publish-snapshot: assemble-phone-release + (./code/gradlew -p code/notificationBuilder publishReleasePublicationToSonatypeRepository) + +notificationbuilder-publish: assemble-phone-release + (./code/gradlew -p code/notificationBuilder publishReleasePublicationToSonatypeRepository -Prelease) diff --git a/jitpack.yml b/jitpack.yml new file mode 100644 index 00000000..179599db --- /dev/null +++ b/jitpack.yml @@ -0,0 +1,16 @@ +# +# Copyright 2024 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. +# + +jdk: + - openjdk17 +install: + - make notificationbuilder-publish-maven-local-jitpack From 0916fff6d6ef4b770b550cbcab1d1776daf25de0 Mon Sep 17 00:00:00 2001 From: Pravin Prakash Kumar Date: Thu, 16 May 2024 08:26:07 -0700 Subject: [PATCH 012/159] Adding test app --- code/settings.gradle.kts | 3 +- code/testapp/build.gradle.kts | 82 ++++++++ code/testapp/proguard-rules.pro | 21 ++ code/testapp/src/main/AndroidManifest.xml | 37 ++++ .../testapp/MainActivity.kt | 64 ++++++ .../notificationbuilder/testapp/MyApp.kt | 33 ++++ .../testapp/ui/theme/Color.kt | 18 ++ .../testapp/ui/theme/Shape.kt | 21 ++ .../testapp/ui/theme/Theme.kt | 57 ++++++ .../testapp/ui/theme/Type.kt | 38 ++++ .../drawable-v24/ic_launcher_foreground.xml | 43 ++++ .../res/drawable/ic_launcher_background.xml | 183 ++++++++++++++++++ .../res/mipmap-anydpi-v26/ic_launcher.xml | 18 ++ .../mipmap-anydpi-v26/ic_launcher_round.xml | 18 ++ .../src/main/res/mipmap-hdpi/ic_launcher.webp | Bin 0 -> 1404 bytes .../res/mipmap-hdpi/ic_launcher_round.webp | Bin 0 -> 2898 bytes .../src/main/res/mipmap-mdpi/ic_launcher.webp | Bin 0 -> 982 bytes .../res/mipmap-mdpi/ic_launcher_round.webp | Bin 0 -> 1772 bytes .../main/res/mipmap-xhdpi/ic_launcher.webp | Bin 0 -> 1900 bytes .../res/mipmap-xhdpi/ic_launcher_round.webp | Bin 0 -> 3918 bytes .../main/res/mipmap-xxhdpi/ic_launcher.webp | Bin 0 -> 2884 bytes .../res/mipmap-xxhdpi/ic_launcher_round.webp | Bin 0 -> 5914 bytes .../main/res/mipmap-xxxhdpi/ic_launcher.webp | Bin 0 -> 3844 bytes .../res/mipmap-xxxhdpi/ic_launcher_round.webp | Bin 0 -> 7778 bytes code/testapp/src/main/res/values/colors.xml | 23 +++ code/testapp/src/main/res/values/strings.xml | 16 ++ code/testapp/src/main/res/values/themes.xml | 20 ++ 27 files changed, 694 insertions(+), 1 deletion(-) create mode 100644 code/testapp/build.gradle.kts create mode 100644 code/testapp/proguard-rules.pro create mode 100644 code/testapp/src/main/AndroidManifest.xml create mode 100644 code/testapp/src/main/java/com/adobe/marketing/mobile/notificationbuilder/testapp/MainActivity.kt create mode 100644 code/testapp/src/main/java/com/adobe/marketing/mobile/notificationbuilder/testapp/MyApp.kt create mode 100644 code/testapp/src/main/java/com/adobe/marketing/mobile/notificationbuilder/testapp/ui/theme/Color.kt create mode 100644 code/testapp/src/main/java/com/adobe/marketing/mobile/notificationbuilder/testapp/ui/theme/Shape.kt create mode 100644 code/testapp/src/main/java/com/adobe/marketing/mobile/notificationbuilder/testapp/ui/theme/Theme.kt create mode 100644 code/testapp/src/main/java/com/adobe/marketing/mobile/notificationbuilder/testapp/ui/theme/Type.kt create mode 100644 code/testapp/src/main/res/drawable-v24/ic_launcher_foreground.xml create mode 100644 code/testapp/src/main/res/drawable/ic_launcher_background.xml create mode 100644 code/testapp/src/main/res/mipmap-anydpi-v26/ic_launcher.xml create mode 100644 code/testapp/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml create mode 100644 code/testapp/src/main/res/mipmap-hdpi/ic_launcher.webp create mode 100644 code/testapp/src/main/res/mipmap-hdpi/ic_launcher_round.webp create mode 100644 code/testapp/src/main/res/mipmap-mdpi/ic_launcher.webp create mode 100644 code/testapp/src/main/res/mipmap-mdpi/ic_launcher_round.webp create mode 100644 code/testapp/src/main/res/mipmap-xhdpi/ic_launcher.webp create mode 100644 code/testapp/src/main/res/mipmap-xhdpi/ic_launcher_round.webp create mode 100644 code/testapp/src/main/res/mipmap-xxhdpi/ic_launcher.webp create mode 100644 code/testapp/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp create mode 100644 code/testapp/src/main/res/mipmap-xxxhdpi/ic_launcher.webp create mode 100644 code/testapp/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp create mode 100644 code/testapp/src/main/res/values/colors.xml create mode 100644 code/testapp/src/main/res/values/strings.xml create mode 100644 code/testapp/src/main/res/values/themes.xml diff --git a/code/settings.gradle.kts b/code/settings.gradle.kts index f415c3b0..2ed6eb1e 100755 --- a/code/settings.gradle.kts +++ b/code/settings.gradle.kts @@ -31,5 +31,6 @@ dependencyResolutionManagement { rootProject.name = "aepsdk-notificationbuilder-android" include ( - ":notificationbuilder" + ":notificationbuilder", + ":testapp" ) \ No newline at end of file diff --git a/code/testapp/build.gradle.kts b/code/testapp/build.gradle.kts new file mode 100644 index 00000000..59b207aa --- /dev/null +++ b/code/testapp/build.gradle.kts @@ -0,0 +1,82 @@ +/* + * Copyright 2024 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. + */ +import com.adobe.marketing.mobile.gradle.BuildConstants + +plugins { + id("com.android.application") + id("org.jetbrains.kotlin.android") +} + +android { + namespace = "com.adobe.marketing.mobile.notificationbuilder.testapp" + + defaultConfig { + applicationId = "com.adobe.marketing.mobile.notificationbuilder.testapp" + minSdk = BuildConstants.Versions.MIN_SDK_VERSION + compileSdk = BuildConstants.Versions.COMPILE_SDK_VERSION + targetSdk = BuildConstants.Versions.TARGET_SDK_VERSION + + testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + vectorDrawables.useSupportLibrary = true + } + + buildTypes { + getByName(BuildConstants.BuildTypes.RELEASE) { + isMinifyEnabled = false + proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro") + } + } + + compileOptions { + sourceCompatibility = BuildConstants.Versions.JAVA_SOURCE_COMPATIBILITY + targetCompatibility = BuildConstants.Versions.JAVA_TARGET_COMPATIBILITY + } + + kotlinOptions { + jvmTarget = BuildConstants.Versions.KOTLIN_JVM_TARGET + languageVersion = BuildConstants.Versions.KOTLIN_LANGUAGE_VERSION + apiVersion = BuildConstants.Versions.KOTLIN_API_VERSION + } + + buildFeatures { + compose = true + } + + composeOptions { + kotlinCompilerExtensionVersion = BuildConstants.Versions.COMPOSE_COMPILER + } + + packaging { + resources { + excludes += "/META-INF/{AL2.0,LGPL2.1}" + } + } +} + +dependencies { + implementation("androidx.core:core-ktx:${BuildConstants.Versions.KOTLIN}") + implementation("androidx.compose.ui:ui:${BuildConstants.Versions.COMPOSE}") + implementation("androidx.compose.material:material:${BuildConstants.Versions.COMPOSE}") + implementation("androidx.compose.ui:ui-tooling-preview:${BuildConstants.Versions.COMPOSE}") + implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.3.1") + implementation("androidx.activity:activity-compose:1.3.1") + + implementation(project(":notificationbuilder")) + implementation("com.adobe.marketing.mobile:core:3.0.0") + implementation("com.adobe.marketing.mobile:campaignclassic:3.0.0") + + testImplementation("junit:junit:4.13.2") + androidTestImplementation("androidx.test.ext:junit:1.1.3") + androidTestImplementation("androidx.test.espresso:espresso-core:3.4.0") + androidTestImplementation("androidx.compose.ui:ui-test-junit4:${BuildConstants.Versions.COMPOSE}") + debugImplementation("androidx.compose.ui:ui-tooling:${BuildConstants.Versions.COMPOSE}") +} \ No newline at end of file diff --git a/code/testapp/proguard-rules.pro b/code/testapp/proguard-rules.pro new file mode 100644 index 00000000..481bb434 --- /dev/null +++ b/code/testapp/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/testapp/src/main/AndroidManifest.xml b/code/testapp/src/main/AndroidManifest.xml new file mode 100644 index 00000000..e4ce5d19 --- /dev/null +++ b/code/testapp/src/main/AndroidManifest.xml @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/code/testapp/src/main/java/com/adobe/marketing/mobile/notificationbuilder/testapp/MainActivity.kt b/code/testapp/src/main/java/com/adobe/marketing/mobile/notificationbuilder/testapp/MainActivity.kt new file mode 100644 index 00000000..bf5ce8f7 --- /dev/null +++ b/code/testapp/src/main/java/com/adobe/marketing/mobile/notificationbuilder/testapp/MainActivity.kt @@ -0,0 +1,64 @@ +/* + Copyright 2024 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.notificationbuilder.testapp + +import android.os.Bundle +import androidx.activity.ComponentActivity +import androidx.activity.compose.setContent +import androidx.compose.foundation.layout.* +import androidx.compose.material.* +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import com.adobe.marketing.mobile.notificationbuilder.testapp.ui.theme.AepsdkTheme + +class MainActivity : ComponentActivity() { + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContent { + AepsdkTheme { + // A surface container using the 'background' color from the theme + Surface( + modifier = Modifier.fillMaxSize(), + color = MaterialTheme.colors.background + ) { + Input() + } + } + } + } +} + +@Composable +fun Input() { + Spacer(modifier = Modifier.height(10.dp)) + Row { + Button(onClick = { + triggerNotification() + }) { + Text("Trigger Notification") + } + Spacer(modifier = Modifier.width(2.dp)) + } +} + +fun triggerNotification() { +} + + +@Preview(showBackground = true) +@Composable +fun DefaultPreview() { + AepsdkTheme { + Input() + } +} \ No newline at end of file diff --git a/code/testapp/src/main/java/com/adobe/marketing/mobile/notificationbuilder/testapp/MyApp.kt b/code/testapp/src/main/java/com/adobe/marketing/mobile/notificationbuilder/testapp/MyApp.kt new file mode 100644 index 00000000..8d166834 --- /dev/null +++ b/code/testapp/src/main/java/com/adobe/marketing/mobile/notificationbuilder/testapp/MyApp.kt @@ -0,0 +1,33 @@ +/* + Copyright 2024 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.notificationbuilder.testapp + +import android.app.Application +import com.adobe.marketing.mobile.LoggingMode +import com.adobe.marketing.mobile.MobileCore +import com.adobe.marketing.mobile.CampaignClassic + +class MyApp : Application() { + + override fun onCreate() { + super.onCreate() + MobileCore.setApplication(this) + MobileCore.setLogLevel(LoggingMode.VERBOSE) + MobileCore.registerExtensions( + listOf( + CampaignClassic.EXTENSION, + ) + ) { + } + } + +} \ No newline at end of file diff --git a/code/testapp/src/main/java/com/adobe/marketing/mobile/notificationbuilder/testapp/ui/theme/Color.kt b/code/testapp/src/main/java/com/adobe/marketing/mobile/notificationbuilder/testapp/ui/theme/Color.kt new file mode 100644 index 00000000..a0865fe1 --- /dev/null +++ b/code/testapp/src/main/java/com/adobe/marketing/mobile/notificationbuilder/testapp/ui/theme/Color.kt @@ -0,0 +1,18 @@ +/* + Copyright 2024 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.notificationbuilder.testapp.ui.theme + +import androidx.compose.ui.graphics.Color + +val Purple200 = Color(0xFFBB86FC) +val Purple500 = Color(0xFF6200EE) +val Purple700 = Color(0xFF3700B3) +val Teal200 = Color(0xFF03DAC5) \ No newline at end of file diff --git a/code/testapp/src/main/java/com/adobe/marketing/mobile/notificationbuilder/testapp/ui/theme/Shape.kt b/code/testapp/src/main/java/com/adobe/marketing/mobile/notificationbuilder/testapp/ui/theme/Shape.kt new file mode 100644 index 00000000..3ee434a5 --- /dev/null +++ b/code/testapp/src/main/java/com/adobe/marketing/mobile/notificationbuilder/testapp/ui/theme/Shape.kt @@ -0,0 +1,21 @@ +/* + Copyright 2024 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.notificationbuilder.testapp.ui.theme + +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.Shapes +import androidx.compose.ui.unit.dp + +val Shapes = Shapes( + small = RoundedCornerShape(4.dp), + medium = RoundedCornerShape(4.dp), + large = RoundedCornerShape(0.dp) +) \ No newline at end of file diff --git a/code/testapp/src/main/java/com/adobe/marketing/mobile/notificationbuilder/testapp/ui/theme/Theme.kt b/code/testapp/src/main/java/com/adobe/marketing/mobile/notificationbuilder/testapp/ui/theme/Theme.kt new file mode 100644 index 00000000..0d317f6c --- /dev/null +++ b/code/testapp/src/main/java/com/adobe/marketing/mobile/notificationbuilder/testapp/ui/theme/Theme.kt @@ -0,0 +1,57 @@ +/* + Copyright 2024 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.notificationbuilder.testapp.ui.theme + +import androidx.compose.foundation.isSystemInDarkTheme +import androidx.compose.material.MaterialTheme +import androidx.compose.material.darkColors +import androidx.compose.material.lightColors +import androidx.compose.runtime.Composable + +private val DarkColorPalette = darkColors( + primary = Purple200, + primaryVariant = Purple700, + secondary = Teal200 +) + +private val LightColorPalette = lightColors( + primary = Purple500, + primaryVariant = Purple700, + secondary = Teal200 + + /* Other default colors to override + background = Color.White, + surface = Color.White, + onPrimary = Color.White, + onSecondary = Color.Black, + onBackground = Color.Black, + onSurface = Color.Black, + */ +) + +@Composable +fun AepsdkTheme( + darkTheme: Boolean = isSystemInDarkTheme(), + content: @Composable () -> Unit +) { + val colors = if (darkTheme) { + DarkColorPalette + } else { + LightColorPalette + } + + MaterialTheme( + colors = colors, + typography = Typography, + shapes = Shapes, + content = content + ) +} \ No newline at end of file diff --git a/code/testapp/src/main/java/com/adobe/marketing/mobile/notificationbuilder/testapp/ui/theme/Type.kt b/code/testapp/src/main/java/com/adobe/marketing/mobile/notificationbuilder/testapp/ui/theme/Type.kt new file mode 100644 index 00000000..04f245c2 --- /dev/null +++ b/code/testapp/src/main/java/com/adobe/marketing/mobile/notificationbuilder/testapp/ui/theme/Type.kt @@ -0,0 +1,38 @@ +/* + Copyright 2024 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.notificationbuilder.testapp.ui.theme + +import androidx.compose.material.Typography +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.font.FontFamily +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.sp + +// Set of Material typography styles to start with +val Typography = Typography( + body1 = TextStyle( + fontFamily = FontFamily.Default, + fontWeight = FontWeight.Normal, + fontSize = 16.sp + ) + /* Other default text styles to override + button = TextStyle( + fontFamily = FontFamily.Default, + fontWeight = FontWeight.W500, + fontSize = 14.sp + ), + caption = TextStyle( + fontFamily = FontFamily.Default, + fontWeight = FontWeight.Normal, + fontSize = 12.sp + ) + */ +) \ No newline at end of file diff --git a/code/testapp/src/main/res/drawable-v24/ic_launcher_foreground.xml b/code/testapp/src/main/res/drawable-v24/ic_launcher_foreground.xml new file mode 100644 index 00000000..46caf340 --- /dev/null +++ b/code/testapp/src/main/res/drawable-v24/ic_launcher_foreground.xml @@ -0,0 +1,43 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/code/testapp/src/main/res/drawable/ic_launcher_background.xml b/code/testapp/src/main/res/drawable/ic_launcher_background.xml new file mode 100644 index 00000000..4b01d269 --- /dev/null +++ b/code/testapp/src/main/res/drawable/ic_launcher_background.xml @@ -0,0 +1,183 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/code/testapp/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/code/testapp/src/main/res/mipmap-anydpi-v26/ic_launcher.xml new file mode 100644 index 00000000..659c541a --- /dev/null +++ b/code/testapp/src/main/res/mipmap-anydpi-v26/ic_launcher.xml @@ -0,0 +1,18 @@ + + + + + + \ No newline at end of file diff --git a/code/testapp/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/code/testapp/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml new file mode 100644 index 00000000..659c541a --- /dev/null +++ b/code/testapp/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml @@ -0,0 +1,18 @@ + + + + + + \ No newline at end of file diff --git a/code/testapp/src/main/res/mipmap-hdpi/ic_launcher.webp b/code/testapp/src/main/res/mipmap-hdpi/ic_launcher.webp new file mode 100644 index 0000000000000000000000000000000000000000..c209e78ecd372343283f4157dcfd918ec5165bb3 GIT binary patch literal 1404 zcmV-?1%vuhNk&F=1pok7MM6+kP&il$0000G0000-002h-06|PpNX!5L00Dqw+t%{r zzW2vH!KF=w&cMnnN@{whkTw+#mAh0SV?YL=)3MimFYCWp#fpdtz~8$hD5VPuQgtcN zXl<@<#Cme5f5yr2h%@8TWh?)bSK`O z^Z@d={gn7J{iyxL_y_%J|L>ep{dUxUP8a{byupH&!UNR*OutO~0{*T4q5R6@ApLF! z5{w?Z150gC7#>(VHFJZ-^6O@PYp{t!jH(_Z*nzTK4 zkc{fLE4Q3|mA2`CWQ3{8;gxGizgM!zccbdQoOLZc8hThi-IhN90RFT|zlxh3Ty&VG z?Fe{#9RrRnxzsu|Lg2ddugg7k%>0JeD+{XZ7>Z~{=|M+sh1MF7~ zz>To~`~LVQe1nNoR-gEzkpe{Ak^7{{ZBk2i_<+`Bq<^GB!RYG+z)h;Y3+<{zlMUYd zrd*W4w&jZ0%kBuDZ1EW&KLpyR7r2=}fF2%0VwHM4pUs}ZI2egi#DRMYZPek*^H9YK zay4Iy3WXFG(F14xYsoDA|KXgGc5%2DhmQ1gFCkrgHBm!lXG8I5h*uf{rn48Z!_@ z4Bk6TJAB2CKYqPjiX&mWoW>OPFGd$wqroa($ne7EUK;#3VYkXaew%Kh^3OrMhtjYN?XEoY`tRPQsAkH-DSL^QqyN0>^ zmC>{#F14jz4GeW{pJoRpLFa_*GI{?T93^rX7SPQgT@LbLqpNA}<@2wH;q493)G=1Y z#-sCiRNX~qf3KgiFzB3I>4Z%AfS(3$`-aMIBU+6?gbgDb!)L~A)je+;fR0jWLL-Fu z4)P{c7{B4Hp91&%??2$v9iRSFnuckHUm}or9seH6 z>%NbT+5*@L5(I9j@06@(!{ZI?U0=pKn8uwIg&L{JV14+8s2hnvbRrU|hZCd}IJu7*;;ECgO%8_*W Kmw_-CKmY()leWbG literal 0 HcmV?d00001 diff --git a/code/testapp/src/main/res/mipmap-hdpi/ic_launcher_round.webp b/code/testapp/src/main/res/mipmap-hdpi/ic_launcher_round.webp new file mode 100644 index 0000000000000000000000000000000000000000..b2dfe3d1ba5cf3ee31b3ecc1ced89044a1f3b7a9 GIT binary patch literal 2898 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 literal 0 HcmV?d00001 diff --git a/code/testapp/src/main/res/mipmap-mdpi/ic_launcher.webp b/code/testapp/src/main/res/mipmap-mdpi/ic_launcher.webp new file mode 100644 index 0000000000000000000000000000000000000000..4f0f1d64e58ba64d180ce43ee13bf9a17835fbca GIT binary patch literal 982 zcmV;{11bDcNk&G_0{{S5MM6+kP&il$0000G0000l001ul06|PpNU8t;00Dqo+t#w^ z^1csucXz7-Qrhzl9HuHB%l>&>1tG2^vb*E&k^T3$FG1eQZ51g$uv4V+kI`0<^1Z@N zk?Jjh$olyC%l>)Xq;7!>{iBj&BjJ`P&$fsCfpve_epJOBkTF?nu-B7D!hO=2ZR}

C%4 zc_9eOXvPbC4kzU8YowIA8cW~Uv|eB&yYwAObSwL2vY~UYI7NXPvf3b+c^?wcs~_t{ ze_m66-0)^{JdOMKPwjpQ@Sna!*?$wTZ~su*tNv7o!gXT!GRgivP}ec?5>l1!7<(rT zds|8x(qGc673zrvYIz;J23FG{9nHMnAuP}NpAED^laz3mAN1sy+NXK)!6v1FxQ;lh zOBLA>$~P3r4b*NcqR;y6pwyhZ3_PiDb|%n1gGjl3ZU}ujInlP{eks-#oA6>rh&g+!f`hv#_%JrgYPu z(U^&XLW^QX7F9Z*SRPpQl{B%x)_AMp^}_v~?j7 zapvHMKxSf*Mtyx8I}-<*UGn3)oHd(nn=)BZ`d$lDBwq_GL($_TPaS{UeevT(AJ`p0 z9%+hQb6z)U9qjbuXjg|dExCLjpS8$VKQ55VsIC%@{N5t{NsW)=hNGI`J=x97_kbz@ E0Of=7!TQj4N+cqN`nQhxvX7dAV-`K|Ub$-q+H-5I?Tx0g9jWxd@A|?POE8`3b8fO$T))xP* z(X?&brZw({`)WU&rdAs1iTa0x6F@PIxJ&&L|dpySV!ID|iUhjCcKz(@mE z!x@~W#3H<)4Ae(4eQJRk`Iz3<1)6^m)0b_4_TRZ+cz#eD3f8V;2r-1fE!F}W zEi0MEkTTx}8i1{`l_6vo0(Vuh0HD$I4SjZ=?^?k82R51bC)2D_{y8mi_?X^=U?2|F{Vr7s!k(AZC$O#ZMyavHhlQ7 zUR~QXuH~#o#>(b$u4?s~HLF*3IcF7023AlwAYudn0FV~|odGH^05AYPEfR)8p`i{n zwg3zPVp{+wOsxKc>)(pMupKF!Y2HoUqQ3|Yu|8lwR=?5zZuhG6J?H`bSNk_wPoM{u zSL{c@pY7+c2kck>`^q1^^gR0QB7Y?KUD{vz-uVX~;V-rW)PDcI)$_UjgVV?S?=oLR zf4}zz{#*R_{LkiJ#0RdQLNC^2Vp%JPEUvG9ra2BVZ92(p9h7Ka@!yf9(lj#}>+|u* z;^_?KWdzkM`6gqPo9;;r6&JEa)}R3X{(CWv?NvgLeOTq$cZXqf7|sPImi-7cS8DCN zGf;DVt3Am`>hH3{4-WzH43Ftx)SofNe^-#|0HdCo<+8Qs!}TZP{HH8~z5n`ExcHuT zDL1m&|DVpIy=xsLO>8k92HcmfSKhflQ0H~9=^-{#!I1g(;+44xw~=* zxvNz35vfsQE)@)Zsp*6_GjYD};Squ83<_?^SbALb{a`j<0Gn%6JY!zhp=Fg}Ga2|8 z52e1WU%^L1}15Ex0fF$e@eCT(()_P zvV?CA%#Sy08_U6VPt4EtmVQraWJX` zh=N|WQ>LgrvF~R&qOfB$!%D3cGv?;Xh_z$z7k&s4N)$WYf*k=|*jCEkO19{h_(%W4 zPuOqbCw`SeAX*R}UUsbVsgtuG?xs(#Ikx9`JZoQFz0n*7ZG@Fv@kZk`gzO$HoA9kN z8U5{-yY zvV{`&WKU2$mZeoBmiJrEdzUZAv1sRxpePdg1)F*X^Y)zp^Y*R;;z~vOv-z&)&G)JQ{m!C9cmziu1^nHA z`#`0c>@PnQ9CJKgC5NjJD8HM3|KC(g5nnCq$n0Gsu_DXk36@ql%npEye|?%RmG)

FJ$wK}0tWNB{uH;AM~i literal 0 HcmV?d00001 diff --git a/code/testapp/src/main/res/mipmap-xhdpi/ic_launcher.webp b/code/testapp/src/main/res/mipmap-xhdpi/ic_launcher.webp new file mode 100644 index 0000000000000000000000000000000000000000..948a3070fe34c611c42c0d3ad3013a0dce358be0 GIT binary patch literal 1900 zcmV-y2b1_xNk&Fw2LJ$9MM6+kP&il$0000G0001A003VA06|PpNH75a00DqwTbm-~ zullQTcXxO9ki!OCRx^i?oR|n!<8G0=kI^!JSjFi-LL*`V;ET0H2IXfU0*i>o6o6Gy zRq6Ap5(_{XLdXcL-MzlN`ugSdZY_`jXhcENAu)N_0?GhF))9R;E`!bo9p?g?SRgw_ zEXHhFG$0{qYOqhdX<(wE4N@es3VIo$%il%6xP9gjiBri+2pI6aY4 zJbgh-Ud|V%3O!IcHKQx1FQH(_*TK;1>FQWbt^$K1zNn^cczkBs=QHCYZ8b&l!UV{K z{L0$KCf_&KR^}&2Fe|L&?1I7~pBENnCtCuH3sjcx6$c zwqkNkru);ie``q+_QI;IYLD9OV0ZxkuyBz|5<$1BH|vtey$> z5oto4=l-R-Aaq`Dk0}o9N0VrkqW_#;!u{!bJLDq%0092{Ghe=F;(kn} z+sQ@1=UlX30+2nWjkL$B^b!H2^QYO@iFc0{(-~yXj2TWz?VG{v`Jg zg}WyYnwGgn>{HFaG7E~pt=)sOO}*yd(UU-D(E&x{xKEl6OcU?pl)K%#U$dn1mDF19 zSw@l8G!GNFB3c3VVK0?uyqN&utT-D5%NM4g-3@Sii9tSXKtwce~uF zS&Jn746EW^wV~8zdQ1XC28~kXu8+Yo9p!<8h&(Q({J*4DBglPdpe4M_mD8AguZFn~ ztiuO~{6Bx?SfO~_ZV(GIboeR9~hAym{{fV|VM=77MxDrbW6`ujX z<3HF(>Zr;#*uCvC*bpoSr~C$h?_%nXps@A)=l_;({Fo#6Y1+Zv`!T5HB+)#^-Ud_; zBwftPN=d8Vx)*O1Mj+0oO=mZ+NVH*ptNDC-&zZ7Hwho6UQ#l-yNvc0Cm+2$$6YUk2D2t#vdZX-u3>-Be1u9gtTBiMB^xwWQ_rgvGpZ6(C@e23c!^K=>ai-Rqu zhqT`ZQof;9Bu!AD(i^PCbYV%yha9zuoKMp`U^z;3!+&d@Hud&_iy!O-$b9ZLcSRh? z)R|826w}TU!J#X6P%@Zh=La$I6zXa#h!B;{qfug}O%z@K{EZECu6zl)7CiNi%xti0 zB{OKfAj83~iJvmpTU|&q1^?^cIMn2RQ?jeSB95l}{DrEPTW{_gmU_pqTc)h@4T>~& zluq3)GM=xa(#^VU5}@FNqpc$?#SbVsX!~RH*5p0p@w z;~v{QMX0^bFT1!cXGM8K9FP+=9~-d~#TK#ZE{4umGT=;dfvWi?rYj;^l_Zxywze`W z^Cr{55U@*BalS}K%Czii_80e0#0#Zkhlij4-~I@}`-JFJ7$5{>LnoJSs??J8kWVl6|8A}RCGAu9^rAsfCE=2}tHwl93t0C?#+jMpvr7O3`2=tr{Hg$=HlnjVG^ewm|Js0J*kfPa6*GhtB>`fN!m#9J(sU!?(OSfzY*zS(FJ<-Vb zfAIg+`U)YaXv#sY(c--|X zEB+TVyZ%Ie4L$gi#Fc++`h6%vzsS$pjz9aLt+ZL(g;n$Dzy5=m=_TV(3H8^C{r0xd zp#a%}ht55dOq?yhwYPrtp-m1xXp;4X;)NhxxUpgP%XTLmO zcjaFva^}dP3$&sfFTIR_jC=2pHh9kpI@2(6V*GQo7Ws)`j)hd+tr@P~gR*2gO@+1? zG<`_tB+LJuF|SZ9tIec;h%}}6WClT`L>HSW?E{Hp1h^+mlbf_$9zA>!ug>NALJsO{ mU%z=YwVD?}XMya)Bp;vlyE5&E_6!fzx9pwrdz474!~g(M6R?N? literal 0 HcmV?d00001 diff --git a/code/testapp/src/main/res/mipmap-xhdpi/ic_launcher_round.webp b/code/testapp/src/main/res/mipmap-xhdpi/ic_launcher_round.webp new file mode 100644 index 0000000000000000000000000000000000000000..1b9a6956b3acdc11f40ce2bb3f6efbd845cc243f GIT binary patch literal 3918 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�(p-{x$&=IACIfoAR!za)F-^da-t3#0Dycnp zwO~NVXwXCl;jE<}>%@xz|=8fIJAB?>+E{7)|4l${4ngA3G|=r z2Dyv;VVWSgZx9Wj>qUjleGl3Ei9K4>h!(lPS%8VOG>Xu0%6VDz^O=bjJmuP7>DeUv zrbI}MlHB^^d?{zv6d=@_ZD2lg1&G7UjnVN{1}9WkaM3H~btX0GtSzB+tZ^qRgWo4m z!GmimlG$=wgXCnr6j@m<1gAL46#T~5Bnm=2{^@>|t&`9mkEPddj zAvG~@Tv~TAm2i%VW}R-g(Z0)z-Y|szHr@rk>4MAyG*Ma*7Yh#H7(!-5>DZ@8r;_dx z{prSe<>~099F8vsYd2xff7uAS%7{S)f(|@me3t2$iy&NEc7OUEchp@9A|X;;IA>8!oX+y(BKJ$EzV* znR$z;!L$s7uy@{OT~nG#B!NRraT8(X##Ho!0r_o@gg0CA-9H^;-uE&?$2$nHv_00o z%cbuUc-tCx$Uh&EZ4Nf4Zgqv)Y6>usG3>GeQnxx_Z6+PcbX-+ysbt1hQ`K1LDpOE? zrAhIZhSN9yVIAOa22gn577tbc&i3|3V8NWy&!tw##`}9*x}gtI^h1DzZRA>UuaJG) zaZ7j)dq!O}{?#8Y7~7i6fHh4{`pL?>-18|p!S75Y#^DM>-S3)vuZG+Q7l@ek zQP~#cBpWgg#mApc_sPYjpw8odQuRokmTkzcNl`^CcKB7e&;zViV;{Y{o^Y$%7i0m# z62%#1Lq!RC?}lK>%mp}T!3Xv;L*0v*>USLm``N%>w>@fwC+#T&Tx2bN4w(20JB}oU zuSa6v^kXi0xPs?pbaOHnyiqq6By1EZY9OZ^^QA>{q-Hsd&m`pbQ%8121aWG-F5xf zlZ%;B{;C>X19|`^_?dVyCq>n+41w7|!tUS!{9rHlbhX=SZO5CQ^;!Du_E7*`GiR^Q w)2!4MKjfSAeNo!9>IaV6aUZ*?W>} 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

UmY2RIYtNPVDpE$%vda+HD#3m z&VuXJ{BK&Qe+rBa7eq}Q(bq|tn(RrJAk|ztj2(i{d>nmQnM?;HF2k&9sA6up5tmjl z7lySlzMbifH17-m-Lwa_F&e7nOH?ESi3#ckR3tsM+jsck3`oG!uMS}|eAwVXv>}qxwq?QY%QJ0}r@^;fhuUA9W z*BVl>TGo&N004@xSiwDUXUvp51sVmqO3m)=B55aPwf@0=e}cN+$-BdKxY`YrT_4)0 z_d10#i44Q*rFr8MC>*)v$EJvz``(pb{e&*6k+b zsMz%($|1+8hn8c2?P(l@;Rb&CsZeYoCI3?2!LqjbwPXW3z4G$Qfj=cT5Yb%vY0(AX oeb?AaKtwrnc|$|zzw9vfvn^aJJ!zd)XFXqqy0000001=f@-~a#s literal 0 HcmV?d00001 diff --git a/code/testapp/src/main/res/values/colors.xml b/code/testapp/src/main/res/values/colors.xml new file mode 100644 index 00000000..c94ac6d2 --- /dev/null +++ b/code/testapp/src/main/res/values/colors.xml @@ -0,0 +1,23 @@ + + + + #FFBB86FC + #FF6200EE + #FF3700B3 + #FF03DAC5 + #FF018786 + #FF000000 + #FFFFFFFF + \ No newline at end of file diff --git a/code/testapp/src/main/res/values/strings.xml b/code/testapp/src/main/res/values/strings.xml new file mode 100644 index 00000000..bb358ad8 --- /dev/null +++ b/code/testapp/src/main/res/values/strings.xml @@ -0,0 +1,16 @@ + + + TestApp + \ No newline at end of file diff --git a/code/testapp/src/main/res/values/themes.xml b/code/testapp/src/main/res/values/themes.xml new file mode 100644 index 00000000..d12e231d --- /dev/null +++ b/code/testapp/src/main/res/values/themes.xml @@ -0,0 +1,20 @@ + + + + + + \ No newline at end of file From 1b399eeb0640ef1c5e213412e581d35a492d9f19 Mon Sep 17 00:00:00 2001 From: Pravin Prakash Kumar Date: Thu, 16 May 2024 14:44:44 -0700 Subject: [PATCH 013/159] fix copy-pasta error --- .circleci/config.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 6ae67386..5751f483 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -39,7 +39,7 @@ jobs: command: make javadoc - store_artifacts: - path: code/userprofile/build/docs/javadoc + path: code/notificationbuilder/build/docs/javadoc - run: name: Assemble phone @@ -58,10 +58,10 @@ jobs: command: make unit-test-coverage - store_test_results: - path: code/userprofile/build/test-results/testPhoneDebugUnitTest + path: code/notificationbuilder/build/test-results/testPhoneDebugUnitTest - codecov/upload: - file: code/userprofile/build/reports/coverage/test/phone/debug/report.xml + file: code/notificationbuilder/build/reports/coverage/test/phone/debug/report.xml flags: unit-tests From 79e1e9434d2148ff78ed27429c6d6116799db884 Mon Sep 17 00:00:00 2001 From: Ryan Morales Date: Fri, 17 May 2024 14:04:05 -0700 Subject: [PATCH 014/159] setup repo with latest push template code --- .gitignore | 25 +- .../NotificationBuilder.kt | 178 +++++- ...NotificationConstructionFailedException.kt | 19 + .../NotificationPriority.kt | 61 ++ .../NotificationVisibility.kt | 59 ++ .../notificationbuilder/PendingIntentUtils.kt | 83 +++ .../PushTemplateConstants.kt | 199 ++++++ .../PushTemplateImageUtils.kt | 315 +++++++++ .../notificationbuilder/PushTemplateType.kt | 37 ++ .../builders/AEPPushNotificationBuilder.kt | 122 ++++ .../AutoCarouselNotificationBuilder.kt | 165 +++++ .../builders/BasicNotificationBuilder.kt | 262 ++++++++ .../builders/InputBoxNotificationBuilder.kt | 280 ++++++++ .../builders/LegacyNotificationBuilder.kt | 105 +++ .../ManualCarouselNotificationBuilder.kt | 603 ++++++++++++++++++ .../builders/ZeroBezelNotificationBuilder.kt | 99 +++ .../extensions/AppResourceExtensions.kt | 74 +++ .../NotificationCompatBuilderExtensions.kt | 323 ++++++++++ .../NotificationManagerExtensions.kt | 88 +++ .../extensions/RemoteViewsExtensions.kt | 265 ++++++++ .../templates/AEPPushTemplate.kt | 328 ++++++++++ .../templates/AutoCarouselPushTemplate.kt | 19 + .../templates/BasicPushTemplate.kt | 178 ++++++ .../templates/CarouselPushTemplate.kt | 133 ++++ .../templates/InputBoxPushTemplate.kt | 70 ++ .../templates/ManualCarouselPushTemplate.kt | 43 ++ .../templates/ProductCatalogPushTemplate.kt | 174 +++++ .../templates/ZeroBezelPushTemplate.kt | 43 ++ .../src/main/res/drawable/skipleft.png | Bin 0 -> 2699 bytes .../src/main/res/drawable/skipright.png | Bin 0 -> 2902 bytes .../main/res/drawable/zero_bazel_gradient.xml | 7 + .../layout/push_template_auto_carousel.xml | 46 ++ .../layout/push_template_carousel_item.xml | 27 + .../res/layout/push_template_collapsed.xml | 34 + .../res/layout/push_template_expanded.xml | 42 ++ .../push_template_filmstrip_carousel.xml | 97 +++ .../layout/push_template_manual_carousel.xml | 58 ++ .../push_template_zero_bezel_collapsed.xml | 50 ++ .../push_template_zero_bezel_expanded.xml | 51 ++ .../src/main/res/values/dimens.xml | 20 + .../src/main/res/values/styles.xml | 30 + .../AEPPushTemplateTests.kt | 353 ++++++++++ .../PushTemplateTypeTest.kt | 30 + 43 files changed, 5178 insertions(+), 17 deletions(-) create mode 100644 code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/NotificationConstructionFailedException.kt create mode 100644 code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/NotificationPriority.kt create mode 100644 code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/NotificationVisibility.kt create mode 100644 code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/PendingIntentUtils.kt create mode 100644 code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/PushTemplateConstants.kt create mode 100644 code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/PushTemplateImageUtils.kt create mode 100644 code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/PushTemplateType.kt create mode 100644 code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/builders/AEPPushNotificationBuilder.kt create mode 100644 code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/builders/AutoCarouselNotificationBuilder.kt create mode 100644 code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/builders/BasicNotificationBuilder.kt create mode 100644 code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/builders/InputBoxNotificationBuilder.kt create mode 100644 code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/builders/LegacyNotificationBuilder.kt create mode 100644 code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/builders/ManualCarouselNotificationBuilder.kt create mode 100644 code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/builders/ZeroBezelNotificationBuilder.kt create mode 100644 code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/extensions/AppResourceExtensions.kt create mode 100644 code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/extensions/NotificationCompatBuilderExtensions.kt create mode 100644 code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/extensions/NotificationManagerExtensions.kt create mode 100644 code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/extensions/RemoteViewsExtensions.kt create mode 100644 code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/templates/AEPPushTemplate.kt create mode 100644 code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/templates/AutoCarouselPushTemplate.kt create mode 100644 code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/templates/BasicPushTemplate.kt create mode 100644 code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/templates/CarouselPushTemplate.kt create mode 100644 code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/templates/InputBoxPushTemplate.kt create mode 100644 code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/templates/ManualCarouselPushTemplate.kt create mode 100644 code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/templates/ProductCatalogPushTemplate.kt create mode 100644 code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/templates/ZeroBezelPushTemplate.kt create mode 100644 code/notificationbuilder/src/main/res/drawable/skipleft.png create mode 100644 code/notificationbuilder/src/main/res/drawable/skipright.png create mode 100644 code/notificationbuilder/src/main/res/drawable/zero_bazel_gradient.xml create mode 100644 code/notificationbuilder/src/main/res/layout/push_template_auto_carousel.xml create mode 100644 code/notificationbuilder/src/main/res/layout/push_template_carousel_item.xml create mode 100644 code/notificationbuilder/src/main/res/layout/push_template_collapsed.xml create mode 100644 code/notificationbuilder/src/main/res/layout/push_template_expanded.xml create mode 100644 code/notificationbuilder/src/main/res/layout/push_template_filmstrip_carousel.xml create mode 100644 code/notificationbuilder/src/main/res/layout/push_template_manual_carousel.xml create mode 100644 code/notificationbuilder/src/main/res/layout/push_template_zero_bezel_collapsed.xml create mode 100644 code/notificationbuilder/src/main/res/layout/push_template_zero_bezel_expanded.xml create mode 100644 code/notificationbuilder/src/main/res/values/dimens.xml create mode 100644 code/notificationbuilder/src/main/res/values/styles.xml create mode 100644 code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/AEPPushTemplateTests.kt create mode 100644 code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/PushTemplateTypeTest.kt diff --git a/.gitignore b/.gitignore index c3cb4a9b..12752376 100644 --- a/.gitignore +++ b/.gitignore @@ -49,6 +49,7 @@ captures/ .idea/modules.xml # Comment next line if keeping position of elements in Navigation Editor is relevant for you .idea/navEditor.xml +.idea # Keystore files # Uncomment the following lines if you do not want to check your keystore files in. @@ -84,19 +85,11 @@ lint/outputs/ lint/tmp/ # lint/reports/ -# IntelliJ -*.iml -.idea/workspace.xml -.idea/tasks.xml -.idea/gradle.xml -.idea/assetWizardSettings.xml -.idea/dictionaries -.idea/libraries -# Android Studio 3 in .gitignore file. -.idea/caches -.idea/modules.xml -# Comment next line if keeping position of elements in Navigation Editor is relevant for you -.idea/navEditor.xml -.idea -.vscode -.DS_Store \ No newline at end of file +# Ignore Gradle project-specific cache directory +.gradle + +# Ignore Gradle build output directory +build +jacoco.exec + +.DS_Store diff --git a/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/NotificationBuilder.kt b/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/NotificationBuilder.kt index ea0c7de7..2f7ae21e 100644 --- a/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/NotificationBuilder.kt +++ b/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/NotificationBuilder.kt @@ -11,8 +11,31 @@ package com.adobe.marketing.mobile.notificationbuilder -import kotlin.jvm.JvmStatic +import android.app.Activity +import android.content.BroadcastReceiver +import android.content.Intent +import androidx.core.app.NotificationCompat +import com.adobe.marketing.mobile.notificationbuilder.builders.AutoCarouselNotificationBuilder +import com.adobe.marketing.mobile.notificationbuilder.builders.BasicNotificationBuilder +import com.adobe.marketing.mobile.notificationbuilder.builders.InputBoxNotificationBuilder +import com.adobe.marketing.mobile.notificationbuilder.builders.LegacyNotificationBuilder +import com.adobe.marketing.mobile.notificationbuilder.builders.ManualCarouselNotificationBuilder +import com.adobe.marketing.mobile.notificationbuilder.builders.ZeroBezelNotificationBuilder +import com.adobe.marketing.mobile.notificationbuilder.templates.AEPPushTemplate +import com.adobe.marketing.mobile.notificationbuilder.templates.AutoCarouselPushTemplate +import com.adobe.marketing.mobile.notificationbuilder.templates.BasicPushTemplate +import com.adobe.marketing.mobile.notificationbuilder.templates.CarouselPushTemplate +import com.adobe.marketing.mobile.notificationbuilder.templates.InputBoxPushTemplate +import com.adobe.marketing.mobile.notificationbuilder.templates.ManualCarouselPushTemplate +import com.adobe.marketing.mobile.notificationbuilder.templates.ZeroBezelPushTemplate +import com.adobe.marketing.mobile.services.Log +import com.adobe.marketing.mobile.services.ServiceProvider +/** + * Public facing object to construct a [NotificationCompat.Builder] object for the specified [PushTemplateType]. + * The [constructNotificationBuilder] methods will build the appropriate notification based on the provided + * [AEPPushTemplate] or [Intent]. + */ object NotificationBuilder { private const val SELF_TAG = "NotificationBuilder" const val VERSION = "3.0.0" @@ -21,4 +44,157 @@ object NotificationBuilder { fun version(): String { return VERSION } + + @Throws(NotificationConstructionFailedException::class) + @JvmStatic + fun constructNotificationBuilder( + messageData: Map, + trackerActivityClass: Class?, + broadcastReceiverClass: Class? + ): NotificationCompat.Builder { + val context = ServiceProvider.getInstance().appContextService.applicationContext + ?: throw NotificationConstructionFailedException("Application context is null, cannot build a notification.") + val pushTemplateType = + PushTemplateType.fromString(messageData[PushTemplateConstants.PushPayloadKeys.TEMPLATE_TYPE]) + + when (pushTemplateType) { + PushTemplateType.BASIC -> { + val basicPushTemplate = BasicPushTemplate(messageData) + return BasicNotificationBuilder.construct( + context, + basicPushTemplate, + trackerActivityClass, + broadcastReceiverClass + ) + } + + PushTemplateType.CAROUSEL -> { + val carouselPushTemplate = + CarouselPushTemplate.createCarouselPushTemplate(messageData) + + when (carouselPushTemplate) { + is AutoCarouselPushTemplate -> { + return AutoCarouselNotificationBuilder.construct( + context, + carouselPushTemplate, + trackerActivityClass, + broadcastReceiverClass + ) + } + + is ManualCarouselPushTemplate -> { + return ManualCarouselNotificationBuilder.construct( + context, + carouselPushTemplate, + trackerActivityClass, + broadcastReceiverClass + ) + } + + else -> { + Log.trace( + PushTemplateConstants.LOG_TAG, + SELF_TAG, + "Unknown carousel push template type, creating a legacy style notification." + ) + return LegacyNotificationBuilder.construct( + context, + BasicPushTemplate(messageData), + trackerActivityClass + ) + } + } + } + + PushTemplateType.ZERO_BEZEL -> { + val zeroBezelPushTemplate = ZeroBezelPushTemplate(messageData) + return ZeroBezelNotificationBuilder.construct( + context, + zeroBezelPushTemplate, + trackerActivityClass + ) + } + + PushTemplateType.INPUT_BOX -> { + return InputBoxNotificationBuilder.construct( + context, + InputBoxPushTemplate(messageData), + trackerActivityClass, + broadcastReceiverClass + ) + } + + PushTemplateType.UNKNOWN -> { + return LegacyNotificationBuilder.construct( + context, + BasicPushTemplate(messageData), + trackerActivityClass + ) + } + } + } + + @Throws(NotificationConstructionFailedException::class) + @JvmStatic + fun constructNotificationBuilder( + intent: Intent, + trackerActivityClass: Class?, + broadcastReceiverClass: Class? + ): NotificationCompat.Builder { + val context = ServiceProvider.getInstance().appContextService.applicationContext + ?: throw NotificationConstructionFailedException("Application context is null, cannot build a notification.") + val pushTemplateType = + PushTemplateType.fromString(intent.getStringExtra(PushTemplateConstants.IntentKeys.TEMPLATE_TYPE)) + + when (pushTemplateType) { + PushTemplateType.BASIC -> { + Log.trace( + PushTemplateConstants.LOG_TAG, + SELF_TAG, + "Building a basic style push notification." + ) + return BasicNotificationBuilder.construct( + context, + BasicPushTemplate(intent), + trackerActivityClass, + broadcastReceiverClass + ) + } + + PushTemplateType.CAROUSEL -> { + return ManualCarouselNotificationBuilder.construct( + context, + ManualCarouselPushTemplate(intent), + trackerActivityClass, + broadcastReceiverClass + ) + } + + PushTemplateType.INPUT_BOX -> { + return InputBoxNotificationBuilder.construct( + context, + InputBoxPushTemplate(intent), + trackerActivityClass, + broadcastReceiverClass + ) + } + + PushTemplateType.UNKNOWN -> { + return LegacyNotificationBuilder.construct( + context, + BasicPushTemplate(intent), + trackerActivityClass + ) + } + + else -> { + // default to legacy notification + return LegacyNotificationBuilder.construct( + context, + BasicPushTemplate(intent), + trackerActivityClass + ) + } + } + } } diff --git a/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/NotificationConstructionFailedException.kt b/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/NotificationConstructionFailedException.kt new file mode 100644 index 00000000..79662e13 --- /dev/null +++ b/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/NotificationConstructionFailedException.kt @@ -0,0 +1,19 @@ +/* + Copyright 2024 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.notificationbuilder + +/** + * Exception indicating that construction of a push notification failed. + * + * @param message [String] containing the message for the new exception + */ +class NotificationConstructionFailedException(message: String) : Exception(message) diff --git a/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/NotificationPriority.kt b/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/NotificationPriority.kt new file mode 100644 index 00000000..cf9623c8 --- /dev/null +++ b/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/NotificationPriority.kt @@ -0,0 +1,61 @@ +/* + Copyright 2024 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.notificationbuilder + +import androidx.core.app.NotificationCompat + +enum class NotificationPriority(private val priority: Int, private val priorityString: String) { + PRIORITY_DEFAULT(NotificationCompat.PRIORITY_DEFAULT, "PRIORITY_DEFAULT"), + PRIORITY_MIN(NotificationCompat.PRIORITY_MIN, "PRIORITY_MIN"), + PRIORITY_LOW(NotificationCompat.PRIORITY_LOW, "PRIORITY_LOW"), + PRIORITY_HIGH(NotificationCompat.PRIORITY_HIGH, "PRIORITY_HIGH"), + PRIORITY_MAX(NotificationCompat.PRIORITY_MAX, "PRIORITY_MAX"); + + companion object { + private val notificationPriorityMap = values().associateBy( + NotificationPriority::priorityString, + NotificationPriority::priority + ) + + /** + * Returns the [NotificationCompat] priority value represented by the [String]. + * + * @param priority [String] representation of the [NotificationCompat] priority + * @return [Int] containing the [NotificationCompat] priority value + */ + internal fun getNotificationCompatPriorityFromString(priority: String?): Int { + return if (priority == null) NotificationCompat.PRIORITY_DEFAULT + else notificationPriorityMap[priority] ?: NotificationCompat.PRIORITY_DEFAULT + } + + private val notificationCompatPriorityMap: Map = values().associateBy( + NotificationPriority::priority, + NotificationPriority::priorityString + ) + + /** + * Returns the [String] representation for the [NotificationCompat] priority value. + * + * @param priority [Int] containing the [NotificationCompat] priority value + * @return [String] representation of the [NotificationCompat] priority + */ + @JvmStatic + fun getNotificationPriority(priority: Int?): String { + return if (priority == null) PRIORITY_DEFAULT.priorityString + else notificationCompatPriorityMap[priority] ?: PRIORITY_DEFAULT.priorityString + } + } + + override fun toString(): String { + return priorityString + } +} diff --git a/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/NotificationVisibility.kt b/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/NotificationVisibility.kt new file mode 100644 index 00000000..ac91d475 --- /dev/null +++ b/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/NotificationVisibility.kt @@ -0,0 +1,59 @@ +/* + Copyright 2024 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.notificationbuilder + +import androidx.core.app.NotificationCompat + +enum class NotificationVisibility(val visibility: Int, val visibilityString: String) { + VISIBILITY_PRIVATE(NotificationCompat.VISIBILITY_PRIVATE, "VISIBILITY_PRIVATE"), + VISIBILITY_PUBLIC(NotificationCompat.VISIBILITY_PUBLIC, "VISIBILITY_PUBLIC"), + VISIBILITY_SECRET(NotificationCompat.VISIBILITY_SECRET, "VISIBILITY_SECRET"); + + companion object { + private val notificationVisibilityMap = values().associateBy( + NotificationVisibility::visibilityString, + NotificationVisibility::visibility + ) + + /** + * Returns the [NotificationCompat.NotificationVisibility] value represented by the [String]. + * + * @param visibility [String] representation of the [NotificationCompat.NotificationVisibility] + * @return [Int] containing the [NotificationCompat.NotificationVisibility] value + */ + internal fun getNotificationCompatVisibilityFromString(visibility: String?): Int { + return if (visibility == null) NotificationCompat.VISIBILITY_PRIVATE + else notificationVisibilityMap[visibility] ?: NotificationCompat.VISIBILITY_PRIVATE + } + + private val notificationCompatVisibilityMap: Map = values().associateBy( + NotificationVisibility::visibility, + NotificationVisibility::visibilityString + ) + + /** + * Returns the [String] representation for the [NotificationCompat.NotificationVisibility] value. + * + * @param visibility [Int] containing the [NotificationCompat.NotificationVisibility] value + * @return [String] representation of the [NotificationCompat.NotificationVisibility] + */ + @JvmStatic + fun getNotificationVisibility(visibility: Int?): String { + return if (visibility == null) VISIBILITY_PRIVATE.visibilityString + else notificationCompatVisibilityMap[visibility] ?: VISIBILITY_PRIVATE.visibilityString + } + } + + override fun toString(): String { + return visibilityString + } +} diff --git a/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/PendingIntentUtils.kt b/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/PendingIntentUtils.kt new file mode 100644 index 00000000..c0203e6b --- /dev/null +++ b/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/PendingIntentUtils.kt @@ -0,0 +1,83 @@ +/* + Copyright 2024 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.notificationbuilder + +import android.app.Activity +import android.app.PendingIntent +import android.content.Context +import android.content.Intent +import java.util.Random + +internal object PendingIntentUtils { + + private const val SELF_TAG = "PendingIntentUtils" + + /** + * Creates a pending intent for a notification. + * + * @param context the application [Context] + * @param trackerActivityClass the [Class] of the activity to set in the created pending intent for tracking purposes + * notification + * @param actionUri the action uri + * @param actionID the action ID + * @param stickyNotification [Boolean] if false, remove the notification after it is interacted with + * @return the created [PendingIntent] + */ + internal fun createPendingIntent( + context: Context, + trackerActivityClass: Class?, + actionUri: String?, + actionID: String?, + tag: String?, + stickyNotification: Boolean + ): PendingIntent? { + val intent = Intent(PushTemplateConstants.NotificationAction.BUTTON_CLICKED) + trackerActivityClass?.let { + intent.setClass(context.applicationContext, trackerActivityClass) + } + intent.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP or Intent.FLAG_ACTIVITY_CLEAR_TOP) + intent.putExtra(PushTemplateConstants.PushPayloadKeys.TAG, tag) + intent.putExtra(PushTemplateConstants.PushPayloadKeys.STICKY, stickyNotification) + addActionDetailsToIntent( + intent, + actionUri, + actionID + ) + + return PendingIntent.getActivity( + context, + Random().nextInt(), + intent, + PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE + ) + } + + /** + * Adds action details to the provided [Intent]. + * + * @param intent the intent + * @param actionUri [String] containing the action uri + * @param actionId `String` containing the action ID + */ + private fun addActionDetailsToIntent( + intent: Intent, + actionUri: String?, + actionId: String? + ) { + if (!actionUri.isNullOrEmpty()) { + intent.putExtra(PushTemplateConstants.Tracking.TrackingKeys.ACTION_URI, actionUri) + } + if (!actionId.isNullOrEmpty()) { + intent.putExtra(PushTemplateConstants.Tracking.TrackingKeys.ACTION_ID, actionId) + } + } +} diff --git a/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/PushTemplateConstants.kt b/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/PushTemplateConstants.kt new file mode 100644 index 00000000..741b34bd --- /dev/null +++ b/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/PushTemplateConstants.kt @@ -0,0 +1,199 @@ +/* + Copyright 2024 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.notificationbuilder + +import java.util.concurrent.TimeUnit + +/** + * This object holds all constant values for handling out-of-the-box push template notifications + */ +internal object PushTemplateConstants { + const val LOG_TAG = "AEPSDKPushTemplates" + const val CACHE_BASE_DIR = "pushtemplates" + const val PUSH_IMAGE_CACHE = "pushimagecache" + const val DEFAULT_CHANNEL_ID = "AEPSDKPushChannel" + + // When no channel name is received from the push notification, this default channel name is + // used. + // This will appear in the notification settings for the app. + const val DEFAULT_CHANNEL_NAME = "AEPSDK Push Notifications" + const val SILENT_CHANNEL_NAME = "AEPSDK Silent Push Notifications" + + /** Enum to denote the type of action */ + enum class ActionType { + DEEPLINK, WEBURL, DISMISS, OPENAPP, NONE + } + + internal object ActionButtons { + const val LABEL = "label" + const val URI = "uri" + const val TYPE = "type" + } + + internal object NotificationAction { + const val DISMISSED = "Notification Dismissed" + const val OPENED = "Notification Opened" + const val BUTTON_CLICKED = "Notification Button Clicked" + } + + internal class Tracking private constructor() { + internal object TrackingKeys { + const val ACTION_ID = "actionId" + const val ACTION_URI = "actionUri" + } + } + + internal object DefaultValues { + const val SILENT_NOTIFICATION_CHANNEL_ID = "AEPSDK Silent Push Notifications" + const val CAROUSEL_MAX_BITMAP_WIDTH = 300 + const val CAROUSEL_MAX_BITMAP_HEIGHT = 200 + const val AUTO_CAROUSEL_MODE = "auto" + const val DEFAULT_MANUAL_CAROUSEL_MODE = "default" + const val FILMSTRIP_CAROUSEL_MODE = "filmstrip" + const val CAROUSEL_MINIMUM_IMAGE_COUNT = 3 + const val MANUAL_CAROUSEL_START_INDEX = 0 + const val FILMSTRIP_CAROUSEL_CENTER_INDEX = 1 + const val NO_CENTER_INDEX_SET = -1 + const val INPUT_BOX_DEFAULT_REPLY_TEXT = "Reply" + const val PRODUCT_CATALOG_START_INDEX = 0 + + // TODO: revisit this value. should cache time be configurable rather than have a static + // value? + val PUSH_NOTIFICATION_IMAGE_CACHE_EXPIRY_IN_MILLISECONDS: Long = + TimeUnit.DAYS.toMillis(3) // 3 days + } + + internal object IntentActions { + const val FILMSTRIP_LEFT_CLICKED = "filmstrip_left" + const val FILMSTRIP_RIGHT_CLICKED = "filmstrip_right" + const val REMIND_LATER_CLICKED = "remind_clicked" + const val MANUAL_CAROUSEL_LEFT_CLICKED = "manual_left" + const val MANUAL_CAROUSEL_RIGHT_CLICKED = "manual_right" + const val INPUT_RECEIVED = "input_received" + } + + internal object IntentKeys { + const val CENTER_IMAGE_INDEX = "centerImageIndex" + const val IMAGE_URI = "imageUri" + const val IMAGE_URLS = "imageUrls" + const val IMAGE_CAPTIONS = "imageCaptions" + const val IMAGE_CLICK_ACTIONS = "imageClickActions" + const val ACTION_URI = "actionUri" + const val ACTION_TYPE = "actionType" + const val CHANNEL_ID = "channelId" + const val CUSTOM_SOUND = "customSound" + const val TITLE_TEXT = "titleText" + const val BODY_TEXT = "bodyText" + const val EXPANDED_BODY_TEXT = "expandedBodyText" + const val NOTIFICATION_BACKGROUND_COLOR = "notificationBackgroundColor" + const val TITLE_TEXT_COLOR = "titleTextColor" + const val EXPANDED_BODY_TEXT_COLOR = "expandedBodyTextColor" + const val BADGE_COUNT = "badgeCount" + const val LARGE_ICON = "largeIcon" + const val SMALL_ICON = "smallIcon" + const val SMALL_ICON_COLOR = "smallIconColor" + const val PRIORITY = "priority" + const val VISIBILITY = "visibility" + const val IMPORTANCE = "importance" + const val REMIND_DELAY_SECONDS = "remindDelaySeconds" + const val REMIND_EPOCH_TS = "remindEpochTimestamp" + const val REMIND_LABEL = "remindLaterLabel" + const val ACTION_BUTTONS_STRING = "actionButtonsString" + const val STICKY = "sticky" + const val TAG = "tag" + const val TICKER = "ticker" + const val PAYLOAD_VERSION = "version" + const val TEMPLATE_TYPE = "templateType" + const val CAROUSEL_OPERATION_MODE = "carouselOperationMode" + const val CAROUSEL_LAYOUT_TYPE = "carouselLayoutType" + const val CAROUSEL_ITEMS = "carouselItems" + const val INPUT_BOX_HINT = "inputBoxHint" + const val INPUT_BOX_FEEDBACK_TEXT = "feedbackText" + const val INPUT_BOX_FEEDBACK_IMAGE = "feedbackImage" + const val INPUT_BOX_RECEIVER_NAME = "feedbackReceiverName" + const val CATALOG_CTA_BUTTON_TEXT = "ctaButtonText" + const val CATALOG_CTA_BUTTON_COLOR = "ctaButtonColor" + const val CATALOG_CTA_BUTTON_URI = "ctaButtonUri" + const val CATALOG_LAYOUT = "displayLayout" + const val CATALOG_ITEMS = "catalogItems" + const val CATALOG_ITEM_INDEX = "catalogIndex" + } + + internal object MethodNames { + const val SET_BACKGROUND_COLOR = "setBackgroundColor" + const val SET_TEXT_COLOR = "setTextColor" + } + + internal object FriendlyViewNames { + const val NOTIFICATION_BACKGROUND = "notification background" + const val NOTIFICATION_TITLE = "notification title" + const val NOTIFICATION_BODY_TEXT = "notification body text" + } + + internal object PushPayloadKeys { + const val TEMPLATE_TYPE = "adb_template_type" + const val TITLE = "adb_title" + const val BODY = "adb_body" + const val ACC_PAYLOAD_BODY = "_msg" + const val SOUND = "adb_sound" + const val BADGE_NUMBER = "adb_n_count" + const val NOTIFICATION_VISIBILITY = "adb_n_visibility" + const val NOTIFICATION_PRIORITY = "adb_n_priority" + const val CHANNEL_ID = "adb_channel_id" + const val LEGACY_SMALL_ICON = "adb_icon" + const val SMALL_ICON = "adb_small_icon" + const val LARGE_ICON = "adb_large_icon" + const val IMAGE_URL = "adb_image" + const val TAG = "adb_tag" + const val TICKER = "adb_ticker" + const val STICKY = "adb_sticky" + const val ACTION_TYPE = "adb_a_type" + const val ACTION_URI = "adb_uri" + const val ACTION_BUTTONS = "adb_act" + const val VERSION = "adb_version" + const val CAROUSEL_LAYOUT = "adb_car_layout" + const val CAROUSEL_ITEMS = "adb_items" + const val EXPANDED_BODY_TEXT = "adb_body_ex" + const val EXPANDED_BODY_TEXT_COLOR = "adb_clr_body" + const val TITLE_TEXT_COLOR = "adb_clr_title" + const val SMALL_ICON_COLOR = "adb_clr_icon" + const val NOTIFICATION_BACKGROUND_COLOR = "adb_clr_bg" + const val REMIND_LATER_TEXT = "adb_rem_txt" + const val REMIND_LATER_EPOCH_TIMESTAMP = "adb_rem_ts" + const val REMIND_LATER_DELAY_SECONDS = "adb_rem_sec" + const val CAROUSEL_OPERATION_MODE = "adb_car_mode" + const val INPUT_BOX_HINT = "adb_input_txt" + const val INPUT_BOX_FEEDBACK_TEXT = "adb_feedback_txt" + const val INPUT_BOX_FEEDBACK_IMAGE = "adb_feedback_img" + const val INPUT_BOX_RECEIVER_NAME = "adb_input_receiver" + const val ZERO_BEZEL_COLLAPSED_STYLE = "adb_col_style" + const val CATALOG_CTA_BUTTON_TEXT = "adb_cta_txt" + const val CATALOG_CTA_BUTTON_COLOR = "adb_cta_clr" + const val CATALOG_CTA_BUTTON_URI = "adb_cta_uri" + const val CATALOG_LAYOUT = "adb_display" + const val CATALOG_ITEMS = "adb_items" + } + + internal object CarouselItemKeys { + const val IMAGE = "img" + const val TEXT = "txt" + const val URI = "uri" + } + + internal object CatalogItemKeys { + const val TITLE = "title" + const val BODY = "body" + const val IMAGE = "img" + const val PRICE = "price" + const val URI = "uri" + } +} diff --git a/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/PushTemplateImageUtils.kt b/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/PushTemplateImageUtils.kt new file mode 100644 index 00000000..cf3bcedd --- /dev/null +++ b/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/PushTemplateImageUtils.kt @@ -0,0 +1,315 @@ +/* + Copyright 2024 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.notificationbuilder + +import android.graphics.Bitmap +import android.graphics.BitmapFactory +import android.graphics.Matrix +import android.graphics.RectF +import com.adobe.marketing.mobile.services.HttpConnecting +import com.adobe.marketing.mobile.services.HttpMethod +import com.adobe.marketing.mobile.services.Log +import com.adobe.marketing.mobile.services.NetworkCallback +import com.adobe.marketing.mobile.services.NetworkRequest +import com.adobe.marketing.mobile.services.ServiceProvider +import com.adobe.marketing.mobile.services.caching.CacheEntry +import com.adobe.marketing.mobile.services.caching.CacheExpiry +import com.adobe.marketing.mobile.services.caching.CacheService +import com.adobe.marketing.mobile.util.UrlUtils +import java.io.ByteArrayInputStream +import java.io.ByteArrayOutputStream +import java.io.File +import java.io.IOException +import java.io.InputStream +import java.net.HttpURLConnection +import java.util.concurrent.CountDownLatch +import java.util.concurrent.TimeUnit +import java.util.concurrent.atomic.AtomicBoolean +import java.util.concurrent.atomic.AtomicInteger + +/** + * Utility functions to assist in downloading and caching images for push template notifications. + */ + +internal object PushTemplateImageUtils { + private const val SELF_TAG = "PushTemplateImageUtil" + private const val FULL_BITMAP_QUALITY = 100 + private const val DOWNLOAD_TIMEOUT_SECS = 10 + + /** + * Downloads and caches images provided in the [urlList]. Prior to downloading, the image url + * is used to retrieve a [CacheResult] containing a previously cached image. + * If a valid cache result is returned then no image is downloaded. + * If no cache result is returned, a call to [downloadImage] is made to download then cache the image. + * + * This is a blocking method that returns only after the download for all images + * have finished either by failing or successfully downloading, or the timeout has been reached. + * + * @param urlList [String] containing an image asset url + * @return [Int] number of images that were found in cache or successfully downloaded + */ + internal fun cacheImages( + urlList: List + ): Int { + val assetCacheLocation = getAssetCacheLocation() + if (urlList.isEmpty() || assetCacheLocation.isNullOrEmpty()) { + return 0 + } + + val cacheService = ServiceProvider.getInstance().cacheService + val downloadedImageCount = AtomicInteger(0) + val latchAborted = AtomicBoolean(false) + val latch = CountDownLatch(urlList.size) + for (url in urlList) { + if (url == null || !UrlUtils.isValidUrl(url)) { + latch.countDown() + continue + } + + val cacheResult = cacheService[assetCacheLocation, url] + if (cacheResult != null) { + Log.trace( + PushTemplateConstants.LOG_TAG, + SELF_TAG, + "Found cached image for $url" + ) + downloadedImageCount.incrementAndGet() + latch.countDown() + continue + } + + downloadImage(url) { connection -> + if (!latchAborted.get()) { + val image = handleDownloadResponse(url, connection) + // scale down the bitmap to 300dp x 200dp as we don't want to use a full + // size image due to memory constraints + image?.let { + val pushImage = scaleBitmap(it) + // write bitmap to cache + try { + bitmapToInputStream(pushImage).use { bitmapInputStream -> + cacheBitmapInputStream( + cacheService, + bitmapInputStream, + url + ) + } + downloadedImageCount.incrementAndGet() + } catch (exception: IOException) { + Log.trace( + PushTemplateConstants.LOG_TAG, + SELF_TAG, + "Exception occurred creating an input stream from a bitmap for {$url}: ${exception.localizedMessage}." + ) + } + } + latch.countDown() + } + connection?.close() + } + } + try { + if (latch.await(DOWNLOAD_TIMEOUT_SECS.toLong(), TimeUnit.SECONDS)) { + Log.trace( + PushTemplateConstants.LOG_TAG, + SELF_TAG, + "All image downloads have completed." + ) + } else { + Log.warning( + PushTemplateConstants.LOG_TAG, + SELF_TAG, + "Timed out waiting for image downloads to complete." + ) + latchAborted.set(true) + } + } catch (e: InterruptedException) { + Log.warning( + PushTemplateConstants.LOG_TAG, + SELF_TAG, + "Interrupted while waiting for image downloads to complete: ${e.localizedMessage}" + ) + latchAborted.set(true) + } + return downloadedImageCount.get() + } + + /** + * Initiates a network request to download the image provided by the url `String`. + * + * @param url [String] containing the image url to download + * @param completionCallback callback to be invoked with the [HttpConnecting] object + * when download is complete + */ + private fun downloadImage( + url: String, + completionCallback: (HttpConnecting?) -> Unit + ) { + val networkRequest = NetworkRequest( + url, + HttpMethod.GET, + null, + null, + DOWNLOAD_TIMEOUT_SECS, + DOWNLOAD_TIMEOUT_SECS + ) + + val networkCallback = NetworkCallback { connection: HttpConnecting? -> + completionCallback.invoke(connection) + } + + ServiceProvider.getInstance() + .networkService + .connectAsync(networkRequest, networkCallback) + } + + /** + * Retrieves an image from the cache using the provided url `String`. + * + * @param url [String] containing the image url to retrieve from cache + * @return [Bitmap] containing the image retrieved from cache, or `null` if no image is found + */ + internal fun getCachedImage(url: String?): Bitmap? { + val assetCacheLocation = getAssetCacheLocation() + if (url == null || !UrlUtils.isValidUrl(url) || assetCacheLocation.isNullOrEmpty()) { + return null + } + val cacheResult = ServiceProvider.getInstance().cacheService[assetCacheLocation, url] + if (cacheResult == null) { + Log.trace(PushTemplateConstants.LOG_TAG, SELF_TAG, "Image not found in cache for $url") + return null + } + Log.trace(PushTemplateConstants.LOG_TAG, SELF_TAG, "Found cached image for $url") + return BitmapFactory.decodeStream(cacheResult.data) + } + + private fun handleDownloadResponse(url: String?, connection: HttpConnecting?): Bitmap? { + if (connection == null) { + Log.warning( + PushTemplateConstants.LOG_TAG, + SELF_TAG, + "Failed to download push notification image from url ($url), received a null connection." + ) + return null + } + if ((connection.responseCode != HttpURLConnection.HTTP_OK)) { + Log.debug( + PushTemplateConstants.LOG_TAG, + SELF_TAG, + "Failed to download push notification image from url ($url). Response code was: ${connection.responseCode}." + ) + return null + } + val bitmap = BitmapFactory.decodeStream(connection.inputStream) + bitmap?.let { + Log.trace( + PushTemplateConstants.LOG_TAG, + SELF_TAG, + "Downloaded push notification image from url ($url)" + ) + } + return bitmap + } + + /** + * Converts a [Bitmap] into an [InputStream] to be used in caching images. + * + * @param bitmap [Bitmap] to be converted into an [InputStream] + * @return an `InputStream` created from the provided bitmap + */ + private fun bitmapToInputStream(bitmap: Bitmap): InputStream { + val byteArrayOutputStream = ByteArrayOutputStream() + bitmap.compress(Bitmap.CompressFormat.PNG, FULL_BITMAP_QUALITY, byteArrayOutputStream) + val bitmapData = byteArrayOutputStream.toByteArray() + return ByteArrayInputStream(bitmapData) + } + + /** + * Writes the provided [InputStream] to the downloaded push template image [assetCacheLocation]. + * + * @param cacheService [CacheService] the AEPSDK cache service + * @param bitmapInputStream [InputStream] created from a download [Bitmap] + * @param imageUrl [String] containing the image url to be used a cache key + */ + private fun cacheBitmapInputStream( + cacheService: CacheService, + bitmapInputStream: InputStream, + imageUrl: String + ) { + Log.trace( + PushTemplateConstants.LOG_TAG, + SELF_TAG, + "Caching image downloaded from $imageUrl." + ) + getAssetCacheLocation()?.let { + // cache push notification images for 3 days + val cacheEntry = CacheEntry( + bitmapInputStream, + CacheExpiry.after( + PushTemplateConstants.DefaultValues.PUSH_NOTIFICATION_IMAGE_CACHE_EXPIRY_IN_MILLISECONDS + ), + null + ) + cacheService[it, imageUrl] = cacheEntry + } + } + + /** + * Scales a downloaded [Bitmap] to a maximum width and height of 300dp x 200dp. + * The scaling is done using a [Matrix] object to maintain the aspect ratio of the original + * image. + * + * @param downloadedBitmap [Bitmap] to be scaled + * @return [Bitmap] containing the scaled image + */ + private fun scaleBitmap(downloadedBitmap: Bitmap): Bitmap { + val matrix = Matrix() + matrix.setRectToRect( + RectF(0f, 0f, downloadedBitmap.width.toFloat(), downloadedBitmap.height.toFloat()), + RectF( + 0f, + 0f, + PushTemplateConstants.DefaultValues.CAROUSEL_MAX_BITMAP_WIDTH.toFloat(), + PushTemplateConstants.DefaultValues.CAROUSEL_MAX_BITMAP_HEIGHT.toFloat() + ), + Matrix.ScaleToFit.CENTER + ) + return Bitmap.createBitmap( + downloadedBitmap, + 0, + 0, + downloadedBitmap.width, + downloadedBitmap.height, + matrix, + true + ) + } + + /** + * Retrieves the asset cache location to use for downloaded push template images. + * + * @return [String] containing the asset cache location to use for storing downloaded push template images. + */ + internal fun getAssetCacheLocation(): String? { + val deviceInfoService = ServiceProvider.getInstance().deviceInfoService + ?: return null + val applicationCacheDir = deviceInfoService.applicationCacheDir + return if ((applicationCacheDir == null)) null else ( + ( + applicationCacheDir + .toString() + File.separator + + PushTemplateConstants.CACHE_BASE_DIR + ) + File.separator + + PushTemplateConstants.PUSH_IMAGE_CACHE + ) + } +} diff --git a/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/PushTemplateType.kt b/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/PushTemplateType.kt new file mode 100644 index 00000000..a8478636 --- /dev/null +++ b/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/PushTemplateType.kt @@ -0,0 +1,37 @@ +/* + Copyright 2024 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.notificationbuilder + +/** + * Enum class representing the different types of out-of-the-box push templates. + */ +internal enum class PushTemplateType(val value: String) { + BASIC("basic"), CAROUSEL("car"), INPUT_BOX("input"), ZERO_BEZEL("zb"), UNKNOWN("unknown"); + + companion object { + /** + * Returns the [PushTemplateType] for the given string value. + * @param value the string value to convert to [PushTemplateType] + * @return the [PushTemplateType] for the given string value + */ + @JvmStatic + fun fromString(value: String?): PushTemplateType { + return when (value) { + "basic" -> BASIC + "car" -> CAROUSEL + "input" -> INPUT_BOX + "zb" -> ZERO_BEZEL + else -> UNKNOWN + } + } + } +} diff --git a/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/builders/AEPPushNotificationBuilder.kt b/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/builders/AEPPushNotificationBuilder.kt new file mode 100644 index 00000000..ef54b257 --- /dev/null +++ b/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/builders/AEPPushNotificationBuilder.kt @@ -0,0 +1,122 @@ +/* + Copyright 2024 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.notificationbuilder.builders + +import android.app.Activity +import android.content.Context +import android.os.Build +import android.widget.RemoteViews +import androidx.core.app.NotificationCompat +import com.adobe.marketing.mobile.notificationbuilder.NotificationConstructionFailedException +import com.adobe.marketing.mobile.notificationbuilder.R +import com.adobe.marketing.mobile.notificationbuilder.extensions.setNotificationBackgroundColor +import com.adobe.marketing.mobile.notificationbuilder.extensions.setNotificationBodyTextColor +import com.adobe.marketing.mobile.notificationbuilder.extensions.setNotificationClickAction +import com.adobe.marketing.mobile.notificationbuilder.extensions.setNotificationDeleteAction +import com.adobe.marketing.mobile.notificationbuilder.extensions.setNotificationTitleTextColor +import com.adobe.marketing.mobile.notificationbuilder.extensions.setRemoteViewLargeIcon +import com.adobe.marketing.mobile.notificationbuilder.extensions.setSmallIcon +import com.adobe.marketing.mobile.notificationbuilder.extensions.setSound +import com.adobe.marketing.mobile.notificationbuilder.extensions.setVisibility +import com.adobe.marketing.mobile.notificationbuilder.templates.AEPPushTemplate + +// TODO: The utilities provided by this builder assumes the id's for various common elements (R.id.basic_small_layout, +// R.id.notification_title, R.id.notification_body_expanded) are the same across templates. +// We will need to figure out a way to enforce this somehow either programmatically, structurally in the layout or via documentation. +internal object AEPPushNotificationBuilder { + @Throws(NotificationConstructionFailedException::class) + fun construct( + context: Context, + pushTemplate: AEPPushTemplate, + channelIdToUse: String, + trackerActivityClass: Class?, + smallLayout: RemoteViews, + expandedLayout: RemoteViews, + containerLayoutViewId: Int + ): NotificationCompat.Builder { + + // set the title and body text on the notification + val titleText = pushTemplate.title + val smallBodyText = pushTemplate.body + val expandedBodyText = pushTemplate.expandedBodyText + smallLayout.setTextViewText(R.id.notification_title, titleText) + smallLayout.setTextViewText(R.id.notification_body, smallBodyText) + expandedLayout.setTextViewText(R.id.notification_title, titleText) + expandedLayout.setTextViewText(R.id.notification_body_expanded, expandedBodyText) + + // set custom colors on the notification background, title text, and body text + smallLayout.setNotificationBackgroundColor( + pushTemplate.notificationBackgroundColor, + R.id.basic_small_layout + ) + + expandedLayout.setNotificationBackgroundColor( + pushTemplate.notificationBackgroundColor, + containerLayoutViewId + ) + + smallLayout.setNotificationTitleTextColor( + pushTemplate.titleTextColor, + R.id.notification_title + ) + + expandedLayout.setNotificationTitleTextColor( + pushTemplate.titleTextColor, + R.id.notification_title + ) + + smallLayout.setNotificationBodyTextColor( + pushTemplate.expandedBodyTextColor, + R.id.notification_body + ) + + expandedLayout.setNotificationBodyTextColor( + pushTemplate.expandedBodyTextColor, + R.id.notification_body_expanded + ) + + // set a large icon if one is present + smallLayout.setRemoteViewLargeIcon(pushTemplate.largeIcon) + expandedLayout.setRemoteViewLargeIcon(pushTemplate.largeIcon) + + val builder = NotificationCompat.Builder(context, channelIdToUse) + .setTicker(pushTemplate.ticker) + .setNumber(pushTemplate.badgeCount) + .setStyle(NotificationCompat.DecoratedCustomViewStyle()) + .setCustomContentView(smallLayout) + .setCustomBigContentView(expandedLayout) + // small icon must be present, otherwise the notification will not be displayed. + .setSmallIcon(context, pushTemplate.smallIcon, pushTemplate.smallIconColor) + // set notification visibility + .setVisibility(pushTemplate) + // set custom sound, note this applies to API 25 and lower only as API 26 and up set the + // sound on the notification channel + .setSound(context, pushTemplate.sound) + .setNotificationClickAction( + context, + trackerActivityClass, + pushTemplate.actionUri, + pushTemplate.tag, + pushTemplate.isNotificationSticky ?: false + ) + .setNotificationDeleteAction(context, trackerActivityClass) + + // if API level is below 26 (prior to notification channels) then notification priority is + // set on the notification builder + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) { + builder.setPriority(NotificationCompat.PRIORITY_HIGH) + .setVibrate(LongArray(0)) // hack to enable heads up notifications as a HUD style + // notification requires a tone or vibration + } + return builder + } +} diff --git a/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/builders/AutoCarouselNotificationBuilder.kt b/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/builders/AutoCarouselNotificationBuilder.kt new file mode 100644 index 00000000..870f3d36 --- /dev/null +++ b/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/builders/AutoCarouselNotificationBuilder.kt @@ -0,0 +1,165 @@ +/* + Copyright 2024 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.notificationbuilder.builders + +import android.app.Activity +import android.app.NotificationManager +import android.content.BroadcastReceiver +import android.content.Context +import android.graphics.Bitmap +import android.widget.RemoteViews +import androidx.core.app.NotificationCompat +import com.adobe.marketing.mobile.notificationbuilder.PushTemplateConstants +import com.adobe.marketing.mobile.notificationbuilder.PushTemplateImageUtils +import com.adobe.marketing.mobile.notificationbuilder.R +import com.adobe.marketing.mobile.notificationbuilder.extensions.createNotificationChannelIfRequired +import com.adobe.marketing.mobile.notificationbuilder.extensions.setRemoteViewClickAction +import com.adobe.marketing.mobile.notificationbuilder.templates.AutoCarouselPushTemplate +import com.adobe.marketing.mobile.notificationbuilder.templates.CarouselPushTemplate +import com.adobe.marketing.mobile.services.Log + +/** + * Object responsible for constructing a [NotificationCompat.Builder] object containing a auto carousel push template notification. + */ +internal object AutoCarouselNotificationBuilder { + private const val SELF_TAG = "AutoCarouselTemplateNotificationBuilder" + + fun construct( + context: Context, + pushTemplate: AutoCarouselPushTemplate, + trackerActivityClass: Class?, + broadcastReceiverClass: Class?, + ): NotificationCompat.Builder { + Log.trace( + PushTemplateConstants.LOG_TAG, + SELF_TAG, + "Building an auto carousel template push notification." + ) + + val packageName = context.packageName + val smallLayout = RemoteViews(packageName, R.layout.push_template_collapsed) + val expandedLayout = RemoteViews(packageName, R.layout.push_template_auto_carousel) + + // load images into the carousel + val downloadedImageCount = PushTemplateImageUtils.cacheImages( + pushTemplate.carouselItems.map { it.imageUri } + ) + + // load images into the carousel + val downloadedImageUris = populateAutoCarouselImages( + context, + trackerActivityClass, + expandedLayout, + pushTemplate, + pushTemplate.carouselItems, + packageName + ) + + // fallback to a basic push template notification builder if less than 3 images were able to be downloaded + if (downloadedImageCount < PushTemplateConstants.DefaultValues.CAROUSEL_MINIMUM_IMAGE_COUNT) { + Log.trace( + PushTemplateConstants.LOG_TAG, + SELF_TAG, + "Less than 3 images are available for the auto carousel push template, falling back to a basic push template." + ) + if (downloadedImageCount > 0) { + pushTemplate.messageData[PushTemplateConstants.PushPayloadKeys.IMAGE_URL] = + downloadedImageUris[0] + } + return BasicNotificationBuilder.fallbackToBasicNotification( + context, + trackerActivityClass, + broadcastReceiverClass, + pushTemplate.messageData + ) + } + + val notificationManager = + context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager + + // Create the notification channel if needed + val channelIdToUse = notificationManager.createNotificationChannelIfRequired( + context, + pushTemplate.channelId, + pushTemplate.sound, + pushTemplate.getNotificationImportance(), + pushTemplate.isFromIntent + ) + + // create the notification builder with the common settings applied + return AEPPushNotificationBuilder.construct( + context, + pushTemplate, + channelIdToUse, + trackerActivityClass, + smallLayout, + expandedLayout, + R.id.carousel_container_layout + ) + } + + /** + * Populates the images for a automatic carousel push template. + * + * @param context the current [Context] of the application + * @param trackerActivityClass the [Class] of the activity that will be used for tracking interactions with the carousel item + * @param expandedLayout the [RemoteViews] containing the expanded layout of the notification + * @param pushTemplate the [CarouselPushTemplate] object containing the push template data + * @param items the list of [CarouselPushTemplate.CarouselItem] objects to be displayed in the carousel + * @param packageName the `String` name of the application package used to locate the layout resources + * @return a [List] of downloaded image URIs + */ + private fun populateAutoCarouselImages( + context: Context, + trackerActivityClass: Class?, + expandedLayout: RemoteViews, + pushTemplate: CarouselPushTemplate, + items: MutableList, + packageName: String? + ): List { + val downloadedImageUris = mutableListOf() + for (item: CarouselPushTemplate.CarouselItem in items) { + val imageUri: String = item.imageUri + val pushImage: Bitmap? = PushTemplateImageUtils.getCachedImage(imageUri) + if (pushImage == null) { + Log.trace( + PushTemplateConstants.LOG_TAG, + SELF_TAG, + "Failed to retrieve an image from $imageUri, will not create a new carousel item." + ) + continue + } + val carouselItem = RemoteViews(packageName, R.layout.push_template_carousel_item) + downloadedImageUris.add(imageUri) + carouselItem.setImageViewBitmap(R.id.carousel_item_image_view, pushImage) + carouselItem.setTextViewText(R.id.carousel_item_caption, item.captionText) + + // assign a click action pending intent for each carousel item if we have a tracker activity + trackerActivityClass?.let { + val interactionUri = item.interactionUri ?: pushTemplate.actionUri + carouselItem.setRemoteViewClickAction( + context, + trackerActivityClass, + R.id.carousel_item_image_view, + interactionUri, + pushTemplate.tag, + pushTemplate.isNotificationSticky ?: false + ) + } + + // add the carousel item to the view flipper + expandedLayout.addView(R.id.auto_carousel_view_flipper, carouselItem) + } + + return downloadedImageUris + } +} diff --git a/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/builders/BasicNotificationBuilder.kt b/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/builders/BasicNotificationBuilder.kt new file mode 100644 index 00000000..86de47a0 --- /dev/null +++ b/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/builders/BasicNotificationBuilder.kt @@ -0,0 +1,262 @@ +/* + Copyright 2024 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.notificationbuilder.builders + +import android.app.Activity +import android.app.NotificationManager +import android.app.PendingIntent +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import android.view.View +import android.widget.RemoteViews +import androidx.core.app.NotificationCompat +import com.adobe.marketing.mobile.notificationbuilder.NotificationConstructionFailedException +import com.adobe.marketing.mobile.notificationbuilder.PushTemplateConstants +import com.adobe.marketing.mobile.notificationbuilder.PushTemplateImageUtils +import com.adobe.marketing.mobile.notificationbuilder.R +import com.adobe.marketing.mobile.notificationbuilder.extensions.addActionButtons +import com.adobe.marketing.mobile.notificationbuilder.extensions.createNotificationChannelIfRequired +import com.adobe.marketing.mobile.notificationbuilder.templates.BasicPushTemplate +import com.adobe.marketing.mobile.services.Log + +/** + * Object responsible for constructing a [NotificationCompat.Builder] object containing a basic push template notification. + */ +internal object BasicNotificationBuilder { + private const val SELF_TAG = "BasicTemplateNotificationBuilder" + + @Throws(NotificationConstructionFailedException::class) + fun construct( + context: Context, + pushTemplate: BasicPushTemplate, + trackerActivityClass: Class?, + broadcastReceiverClass: Class? + ): NotificationCompat.Builder { + Log.trace( + PushTemplateConstants.LOG_TAG, + SELF_TAG, + "Building a basic template push notification." + ) + val packageName = context.packageName + val smallLayout = RemoteViews(packageName, R.layout.push_template_collapsed) + val expandedLayout = RemoteViews(packageName, R.layout.push_template_expanded) + + val notificationManager = + context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager + val channelIdToUse: String = notificationManager.createNotificationChannelIfRequired( + context, + pushTemplate.channelId, + pushTemplate.sound, + pushTemplate.getNotificationImportance(), + pushTemplate.isFromIntent + ) + + // create the notification builder with the common settings applied + val notificationBuilder = AEPPushNotificationBuilder.construct( + context, + pushTemplate, + channelIdToUse, + trackerActivityClass, + smallLayout, + expandedLayout, + R.id.basic_expanded_layout + ) + + // set the image on the notification + val imageUri = pushTemplate.imageUrl + val downloadedImageCount = PushTemplateImageUtils.cacheImages(listOf(imageUri)) + + if (downloadedImageCount == 0) { + Log.trace( + PushTemplateConstants.LOG_TAG, + SELF_TAG, + "No image found for basic push template." + ) + expandedLayout.setViewVisibility(R.id.expanded_template_image, View.GONE) + } else { + expandedLayout.setImageViewBitmap( + R.id.expanded_template_image, + PushTemplateImageUtils.getCachedImage(imageUri) + ) + } + + // add any action buttons defined for the notification + notificationBuilder.addActionButtons( + context, + trackerActivityClass, + pushTemplate.actionButtonsList, + pushTemplate.tag, + pushTemplate.isNotificationSticky ?: false + ) + + // add a remind later button if we have a label and an epoch or delay timestamp + pushTemplate.remindLaterText?.let { remindLaterText -> + if (pushTemplate.remindLaterEpochTimestamp != null || + pushTemplate.remindLaterDelaySeconds != null + ) { + val remindIntent = createRemindPendingIntent( + context, + broadcastReceiverClass, + channelIdToUse, + pushTemplate + ) + notificationBuilder.addAction(0, remindLaterText, remindIntent) + } + } + + return notificationBuilder + } + + @Throws(NotificationConstructionFailedException::class) + internal fun fallbackToBasicNotification( + context: Context, + trackerActivityClass: Class?, + broadcastReceiverClass: Class?, + dataMap: MutableMap + ): NotificationCompat.Builder { + val basicPushTemplate = BasicPushTemplate(dataMap) + return construct( + context, + basicPushTemplate, + trackerActivityClass, + broadcastReceiverClass + ) + } + + /** + * Creates a pending intent for remind later button in a notification. + * + * @param context the application [Context] + * @param broadcastReceiverClass the [Class] of the broadcast receiver to set in the created pending intent + * @param channelId [String] containing the notification channel ID + * @param pushTemplate the [BasicPushTemplate] object containing the basic push template data + * @return the created remind later [PendingIntent] + */ + private fun createRemindPendingIntent( + context: Context, + broadcastReceiverClass: Class?, + channelId: String, + pushTemplate: BasicPushTemplate + ): PendingIntent? { + if (broadcastReceiverClass == null) { + return null + } + Log.trace( + PushTemplateConstants.LOG_TAG, + SELF_TAG, + "Creating a remind later pending intent from a push template object." + ) + + val remindIntent = Intent(PushTemplateConstants.IntentActions.REMIND_LATER_CLICKED).apply { + setClass(context.applicationContext, broadcastReceiverClass) + + flags = Intent.FLAG_ACTIVITY_SINGLE_TOP + putExtra( + PushTemplateConstants.IntentKeys.TEMPLATE_TYPE, pushTemplate.templateType?.value + ) + putExtra( + PushTemplateConstants.IntentKeys.IMAGE_URI, pushTemplate.imageUrl + ) + putExtra( + PushTemplateConstants.IntentKeys.ACTION_URI, pushTemplate.actionUri + ) + putExtra(PushTemplateConstants.IntentKeys.CHANNEL_ID, channelId) + putExtra( + PushTemplateConstants.IntentKeys.CUSTOM_SOUND, pushTemplate.sound + ) + putExtra( + PushTemplateConstants.IntentKeys.TITLE_TEXT, + pushTemplate.title + ) + putExtra( + PushTemplateConstants.IntentKeys.BODY_TEXT, + pushTemplate.body + ) + putExtra( + PushTemplateConstants.IntentKeys.EXPANDED_BODY_TEXT, + pushTemplate.expandedBodyText + ) + putExtra( + PushTemplateConstants.IntentKeys.NOTIFICATION_BACKGROUND_COLOR, + pushTemplate.notificationBackgroundColor + ) + putExtra( + PushTemplateConstants.IntentKeys.TITLE_TEXT_COLOR, + pushTemplate.titleTextColor + ) + putExtra( + PushTemplateConstants.IntentKeys.EXPANDED_BODY_TEXT_COLOR, + pushTemplate.expandedBodyTextColor + ) + putExtra( + PushTemplateConstants.IntentKeys.SMALL_ICON, pushTemplate.smallIcon + ) + putExtra( + PushTemplateConstants.IntentKeys.SMALL_ICON_COLOR, + pushTemplate.smallIconColor + ) + putExtra( + PushTemplateConstants.IntentKeys.LARGE_ICON, pushTemplate.largeIcon + ) + putExtra( + PushTemplateConstants.IntentKeys.VISIBILITY, + pushTemplate.getNotificationVisibility() + ) + putExtra( + PushTemplateConstants.IntentKeys.IMPORTANCE, + pushTemplate.getNotificationImportance() + ) + putExtra( + PushTemplateConstants.IntentKeys.BADGE_COUNT, pushTemplate.badgeCount + ) + putExtra( + PushTemplateConstants.IntentKeys.REMIND_EPOCH_TS, + pushTemplate.remindLaterEpochTimestamp + ) + putExtra( + PushTemplateConstants.IntentKeys.REMIND_DELAY_SECONDS, + pushTemplate.remindLaterDelaySeconds + ) + putExtra( + PushTemplateConstants.IntentKeys.REMIND_LABEL, pushTemplate.remindLaterText + ) + putExtra( + PushTemplateConstants.IntentKeys.ACTION_BUTTONS_STRING, + pushTemplate.actionButtonsString + ) + putExtra( + PushTemplateConstants.IntentKeys.STICKY, pushTemplate.isNotificationSticky + ) + putExtra( + PushTemplateConstants.IntentKeys.TAG, pushTemplate.tag + ) + putExtra( + PushTemplateConstants.IntentKeys.TICKER, pushTemplate.ticker + ) + putExtra( + PushTemplateConstants.IntentKeys.PAYLOAD_VERSION, pushTemplate.payloadVersion + ) + putExtra( + PushTemplateConstants.IntentKeys.PRIORITY, + pushTemplate.notificationPriority + ) + } + + return PendingIntent.getBroadcast( + context, + 0, + remindIntent, + PendingIntent.FLAG_MUTABLE or PendingIntent.FLAG_UPDATE_CURRENT + ) + } +} diff --git a/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/builders/InputBoxNotificationBuilder.kt b/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/builders/InputBoxNotificationBuilder.kt new file mode 100644 index 00000000..efc6dabd --- /dev/null +++ b/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/builders/InputBoxNotificationBuilder.kt @@ -0,0 +1,280 @@ +/* + Copyright 2024 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.notificationbuilder.builders + +import android.app.Activity +import android.app.NotificationManager +import android.app.PendingIntent +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import android.view.View +import android.widget.RemoteViews +import androidx.core.app.NotificationCompat +import androidx.core.app.RemoteInput +import com.adobe.marketing.mobile.notificationbuilder.NotificationConstructionFailedException +import com.adobe.marketing.mobile.notificationbuilder.PushTemplateConstants +import com.adobe.marketing.mobile.notificationbuilder.PushTemplateImageUtils +import com.adobe.marketing.mobile.notificationbuilder.R +import com.adobe.marketing.mobile.notificationbuilder.extensions.createNotificationChannelIfRequired +import com.adobe.marketing.mobile.notificationbuilder.templates.InputBoxPushTemplate +import com.adobe.marketing.mobile.services.Log + +/** + * Object responsible for constructing a [NotificationCompat.Builder] object containing an input box push template notification. + */ +internal object InputBoxNotificationBuilder { + private const val SELF_TAG = "InputBoxNotificationBuilder" + + @Throws(NotificationConstructionFailedException::class) + fun construct( + context: Context, + pushTemplate: InputBoxPushTemplate, + trackerActivityClass: Class?, + broadcastReceiverClass: Class? + ): NotificationCompat.Builder { + Log.trace( + PushTemplateConstants.LOG_TAG, + SELF_TAG, + "Building an input box template push notification." + ) + val packageName = context.packageName + val smallLayout = RemoteViews(packageName, R.layout.push_template_collapsed) + val expandedLayout = RemoteViews(packageName, R.layout.push_template_expanded) + + val notificationManager = + context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager + val channelIdToUse: String = notificationManager.createNotificationChannelIfRequired( + context, + pushTemplate.channelId, + pushTemplate.sound, + pushTemplate.getNotificationImportance(), + pushTemplate.isFromIntent + ) + + // create the notification builder with the common settings applied + val notificationBuilder = AEPPushNotificationBuilder.construct( + context, + pushTemplate, + channelIdToUse, + trackerActivityClass, + smallLayout, + expandedLayout, + R.id.basic_expanded_layout + ) + + // get push payload data. if we are handling an intent then we know that we should be building a feedback received notification. + val imageUri = + if (pushTemplate.isFromIntent) pushTemplate.feedbackImage else pushTemplate.imageUrl + val downloadedImageCount = PushTemplateImageUtils.cacheImages(listOf(imageUri)) + + if (downloadedImageCount == 1) { + val pushImage = PushTemplateImageUtils.getCachedImage(imageUri) + expandedLayout.setImageViewBitmap(R.id.expanded_template_image, pushImage) + } else { + Log.trace( + PushTemplateConstants.LOG_TAG, + SELF_TAG, + "No image found for input box push template." + ) + expandedLayout.setViewVisibility(R.id.expanded_template_image, View.GONE) + } + + val expandedBodyText = + if (pushTemplate.isFromIntent) pushTemplate.feedbackText else pushTemplate.expandedBodyText + val collapsedBodyText = + if (pushTemplate.isFromIntent) pushTemplate.feedbackText else pushTemplate.body + smallLayout.setTextViewText(R.id.notification_title, pushTemplate.title) + smallLayout.setTextViewText(R.id.notification_body, collapsedBodyText) + expandedLayout.setTextViewText(R.id.notification_title, pushTemplate.title) + expandedLayout.setTextViewText( + R.id.notification_body_expanded, expandedBodyText + ) + + // add an input box to capture user feedback if the push template is not from an intent + // otherwise, we are done building the notification + if (pushTemplate.isFromIntent) { + return notificationBuilder + } + Log.trace( + PushTemplateConstants.LOG_TAG, + SELF_TAG, + "Adding an input box to capture text input. The input box receiver name is ${pushTemplate.inputBoxReceiverName}." + ) + addInputTextAction( + context, + broadcastReceiverClass, + notificationBuilder, + channelIdToUse, + pushTemplate + ) + + return notificationBuilder + } + + /** + * Adds an input text action for the notification. + * + * @param context the application [Context] + * @param broadcastReceiverClass the [BroadcastReceiver] class to use as the broadcast receiver + * @param builder the [NotificationCompat.Builder] to attach the action buttons + * @param channelId the [String] containing the channel ID to use for the notification + * @param pushTemplate the [InputBoxPushTemplate] object containing the input box push template data + * button is pressed + */ + private fun addInputTextAction( + context: Context, + broadcastReceiverClass: Class?, + builder: NotificationCompat.Builder, + channelId: String, + pushTemplate: InputBoxPushTemplate + ) { + val inputHint = + if (pushTemplate.inputTextHint.isNullOrEmpty()) PushTemplateConstants.DefaultValues.INPUT_BOX_DEFAULT_REPLY_TEXT else pushTemplate.inputTextHint + val remoteInput = RemoteInput.Builder(pushTemplate.inputBoxReceiverName) + .setLabel(inputHint) + .build() + + val inputReceivedIntent = createInputReceivedIntent( + context, + broadcastReceiverClass, + channelId, + pushTemplate + ) + + val replyPendingIntent = + inputReceivedIntent?.let { + PendingIntent.getBroadcast( + context, + pushTemplate.tag.hashCode(), + it, + PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_MUTABLE + ) + } + + val action = + NotificationCompat.Action.Builder(null, inputHint, replyPendingIntent) + .addRemoteInput(remoteInput) + .build() + + builder.addAction(action) + } + + private fun createInputReceivedIntent( + context: Context, + broadcastReceiverClass: Class?, + channelId: String, + pushTemplate: InputBoxPushTemplate + ): Intent? { + if (broadcastReceiverClass == null) { + return null + } + Log.trace( + PushTemplateConstants.LOG_TAG, + SELF_TAG, + "Creating a text input received intent from a push template object." + ) + + val inputReceivedIntent = Intent(PushTemplateConstants.IntentActions.INPUT_RECEIVED) + broadcastReceiverClass.let { + inputReceivedIntent.setClass(context.applicationContext, broadcastReceiverClass) + } + + inputReceivedIntent.flags = Intent.FLAG_ACTIVITY_SINGLE_TOP + inputReceivedIntent.putExtra( + PushTemplateConstants.IntentKeys.TEMPLATE_TYPE, pushTemplate.templateType?.value + ) + inputReceivedIntent.putExtra( + PushTemplateConstants.IntentKeys.IMAGE_URI, pushTemplate.imageUrl + ) + inputReceivedIntent.putExtra( + PushTemplateConstants.IntentKeys.ACTION_URI, pushTemplate.actionUri + ) + inputReceivedIntent.putExtra(PushTemplateConstants.IntentKeys.CHANNEL_ID, channelId) + inputReceivedIntent.putExtra( + PushTemplateConstants.IntentKeys.CUSTOM_SOUND, pushTemplate.sound + ) + inputReceivedIntent.putExtra( + PushTemplateConstants.IntentKeys.TITLE_TEXT, + pushTemplate.title + ) + inputReceivedIntent.putExtra( + PushTemplateConstants.IntentKeys.BODY_TEXT, + pushTemplate.body + ) + inputReceivedIntent.putExtra( + PushTemplateConstants.IntentKeys.EXPANDED_BODY_TEXT, + pushTemplate.expandedBodyText + ) + inputReceivedIntent.putExtra( + PushTemplateConstants.IntentKeys.NOTIFICATION_BACKGROUND_COLOR, + pushTemplate.notificationBackgroundColor + ) + inputReceivedIntent.putExtra( + PushTemplateConstants.IntentKeys.TITLE_TEXT_COLOR, + pushTemplate.titleTextColor + ) + inputReceivedIntent.putExtra( + PushTemplateConstants.IntentKeys.EXPANDED_BODY_TEXT_COLOR, + pushTemplate.expandedBodyTextColor + ) + inputReceivedIntent.putExtra( + PushTemplateConstants.IntentKeys.SMALL_ICON, pushTemplate.smallIcon + ) + inputReceivedIntent.putExtra( + PushTemplateConstants.IntentKeys.SMALL_ICON_COLOR, + pushTemplate.smallIconColor + ) + inputReceivedIntent.putExtra( + PushTemplateConstants.IntentKeys.LARGE_ICON, pushTemplate.largeIcon + ) + inputReceivedIntent.putExtra( + PushTemplateConstants.IntentKeys.VISIBILITY, + pushTemplate.getNotificationVisibility() + ) + inputReceivedIntent.putExtra( + PushTemplateConstants.IntentKeys.IMPORTANCE, + pushTemplate.getNotificationImportance() + ) + inputReceivedIntent.putExtra( + PushTemplateConstants.IntentKeys.BADGE_COUNT, pushTemplate.badgeCount + ) + inputReceivedIntent.putExtra( + PushTemplateConstants.IntentKeys.INPUT_BOX_FEEDBACK_TEXT, pushTemplate.feedbackText + ) + inputReceivedIntent.putExtra( + PushTemplateConstants.IntentKeys.INPUT_BOX_FEEDBACK_IMAGE, pushTemplate.feedbackImage + ) + inputReceivedIntent.putExtra( + PushTemplateConstants.IntentKeys.INPUT_BOX_RECEIVER_NAME, + pushTemplate.inputBoxReceiverName + ) + inputReceivedIntent.putExtra( + PushTemplateConstants.IntentKeys.STICKY, pushTemplate.isNotificationSticky + ) + inputReceivedIntent.putExtra( + PushTemplateConstants.IntentKeys.TAG, pushTemplate.tag + ) + inputReceivedIntent.putExtra( + PushTemplateConstants.IntentKeys.TICKER, pushTemplate.ticker + ) + inputReceivedIntent.putExtra( + PushTemplateConstants.IntentKeys.PAYLOAD_VERSION, pushTemplate.payloadVersion + ) + inputReceivedIntent.putExtra( + PushTemplateConstants.IntentKeys.PRIORITY, + pushTemplate.notificationPriority + ) + + return inputReceivedIntent + } +} diff --git a/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/builders/LegacyNotificationBuilder.kt b/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/builders/LegacyNotificationBuilder.kt new file mode 100644 index 00000000..f2d19d98 --- /dev/null +++ b/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/builders/LegacyNotificationBuilder.kt @@ -0,0 +1,105 @@ +/* + Copyright 2024 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.notificationbuilder.builders + +import android.app.Activity +import android.app.NotificationManager +import android.content.Context +import android.os.Build +import androidx.core.app.NotificationCompat +import com.adobe.marketing.mobile.notificationbuilder.PushTemplateConstants +import com.adobe.marketing.mobile.notificationbuilder.extensions.addActionButtons +import com.adobe.marketing.mobile.notificationbuilder.extensions.createNotificationChannelIfRequired +import com.adobe.marketing.mobile.notificationbuilder.extensions.setLargeIcon +import com.adobe.marketing.mobile.notificationbuilder.extensions.setNotificationClickAction +import com.adobe.marketing.mobile.notificationbuilder.extensions.setNotificationDeleteAction +import com.adobe.marketing.mobile.notificationbuilder.extensions.setSmallIcon +import com.adobe.marketing.mobile.notificationbuilder.extensions.setSound +import com.adobe.marketing.mobile.notificationbuilder.templates.BasicPushTemplate +import com.adobe.marketing.mobile.services.Log + +/** + * Object responsible for constructing a legacy push notification. + */ +internal object LegacyNotificationBuilder { + private const val SELF_TAG = "LegacyNotificationBuilder" + + fun construct( + context: Context, + pushTemplate: BasicPushTemplate, + trackerActivityClass: Class? + ): NotificationCompat.Builder { + Log.trace( + PushTemplateConstants.LOG_TAG, + SELF_TAG, + "Building a legacy style push notification." + ) + + // create the notification channel + val notificationManager = + context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager + val channelId = notificationManager.createNotificationChannelIfRequired( + context, + pushTemplate.channelId, + pushTemplate.sound, + pushTemplate.getNotificationImportance(), + pushTemplate.isFromIntent + ) + + // Create the notification builder object and set the ticker, title, body, and badge count + val builder = NotificationCompat.Builder(context, channelId) + .setTicker(pushTemplate.ticker) + .setContentTitle(pushTemplate.title) + .setContentText(pushTemplate.body) + .setNumber(pushTemplate.badgeCount) + // set a large icon if one is present + .setLargeIcon(pushTemplate.imageUrl, pushTemplate.title, pushTemplate.expandedBodyText) + // small Icon must be present, otherwise the notification will not be displayed. + .setSmallIcon(context, pushTemplate.smallIcon, pushTemplate.smallIconColor) + // set notification visibility + .setVisibility(pushTemplate.getNotificationVisibility()) + // add any action buttons defined for the notification + .addActionButtons( + context, + trackerActivityClass, + pushTemplate.actionButtonsList, + pushTemplate.tag, + pushTemplate.isNotificationSticky ?: false + ) + // set custom sound, note this applies to API 25 and lower only as API 26 and up set the + // sound on the notification channel + .setSound(context, pushTemplate.sound) + // assign a click action pending intent to the notification + .setNotificationClickAction( + context, + trackerActivityClass, + pushTemplate.actionUri, + pushTemplate.tag, + pushTemplate.isNotificationSticky ?: false + ) + // set notification delete action + .setNotificationDeleteAction( + context, + trackerActivityClass + ) + + // if API level is below 26 (prior to notification channels) then notification priority is + // set on the notification builder + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) { + builder.setPriority(NotificationCompat.PRIORITY_HIGH) + .setVibrate(LongArray(0)) // hack to enable heads up notifications as a HUD style + // notification requires a tone or vibration + } + + return builder + } +} diff --git a/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/builders/ManualCarouselNotificationBuilder.kt b/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/builders/ManualCarouselNotificationBuilder.kt new file mode 100644 index 00000000..12d5ac52 --- /dev/null +++ b/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/builders/ManualCarouselNotificationBuilder.kt @@ -0,0 +1,603 @@ +/* + Copyright 2024 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.notificationbuilder.builders + +import android.app.Activity +import android.app.NotificationManager +import android.app.PendingIntent +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import android.graphics.Bitmap +import android.widget.RemoteViews +import androidx.core.app.NotificationCompat +import com.adobe.marketing.mobile.notificationbuilder.NotificationConstructionFailedException +import com.adobe.marketing.mobile.notificationbuilder.PushTemplateConstants +import com.adobe.marketing.mobile.notificationbuilder.PushTemplateImageUtils +import com.adobe.marketing.mobile.notificationbuilder.R +import com.adobe.marketing.mobile.notificationbuilder.extensions.createNotificationChannelIfRequired +import com.adobe.marketing.mobile.notificationbuilder.extensions.setRemoteViewClickAction +import com.adobe.marketing.mobile.notificationbuilder.templates.CarouselPushTemplate +import com.adobe.marketing.mobile.notificationbuilder.templates.ManualCarouselPushTemplate +import com.adobe.marketing.mobile.services.Log +import com.adobe.marketing.mobile.services.caching.CacheService + +/** + * Object responsible for constructing a [NotificationCompat.Builder] object containing a manual or filmstrip carousel push template notification. + */ +internal object ManualCarouselNotificationBuilder { + private const val SELF_TAG = "ManualCarouselTemplateNotificationBuilder" + + @Throws(NotificationConstructionFailedException::class) + fun construct( + context: Context, + pushTemplate: ManualCarouselPushTemplate, + trackerActivityClass: Class?, + broadcastReceiverClass: Class? + ): NotificationCompat.Builder { + Log.trace( + PushTemplateConstants.LOG_TAG, + SELF_TAG, + "Building a manual carousel template push notification." + ) + + // download carousel images + val downloadedImagesCount = PushTemplateImageUtils.cacheImages( + pushTemplate.carouselItems.map { it.imageUri } + ) + + val validCarouselItems = downloadCarouselItems(pushTemplate.carouselItems) + + // fallback to a basic push template notification builder if less than 3 images were able + // to be downloaded + if (downloadedImagesCount < PushTemplateConstants.DefaultValues.CAROUSEL_MINIMUM_IMAGE_COUNT) { + Log.trace( + PushTemplateConstants.LOG_TAG, + SELF_TAG, + "Less than 3 images are available for the manual carousel push template, falling back to a basic push template." + ) + if (downloadedImagesCount > 0) { + val imageUris = validCarouselItems.map { it.imageUri } + pushTemplate.messageData[PushTemplateConstants.PushPayloadKeys.IMAGE_URL] = + imageUris[0] + } + return BasicNotificationBuilder.fallbackToBasicNotification( + context, + trackerActivityClass, + broadcastReceiverClass, + pushTemplate.messageData + ) + } + + // set the expanded layout depending on the carousel type + val packageName = context.packageName + val smallLayout = RemoteViews(packageName, R.layout.push_template_collapsed) + val expandedLayout = + if (pushTemplate.carouselLayoutType == PushTemplateConstants.DefaultValues.FILMSTRIP_CAROUSEL_MODE) + RemoteViews( + packageName, + R.layout.push_template_filmstrip_carousel + ) else RemoteViews(packageName, R.layout.push_template_manual_carousel) + + // extract image uris, captions, and interaction uris from the validated carousel items + val imageUris = validCarouselItems.map { it.imageUri } + val captions = validCarouselItems.map { it.captionText } + val interactionUris = validCarouselItems.map { it.interactionUri } + val fallbackActionUri = pushTemplate.actionUri + + // get the indices for the carousel + val carouselIndices = getCarouselIndices(pushTemplate, imageUris) + + // store the updated center image index + pushTemplate.centerImageIndex = carouselIndices.second + + // populate the images for the manual carousel + setupCarouselImages( + context, + captions, + interactionUris, + carouselIndices, + pushTemplate, + trackerActivityClass, + expandedLayout, + validCarouselItems, + packageName, + fallbackActionUri + ) + + val notificationManager = + context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager + + // create the notification channel if needed + val channelIdToUse = notificationManager.createNotificationChannelIfRequired( + context, + pushTemplate.channelId, + pushTemplate.sound, + pushTemplate.getNotificationImportance(), + pushTemplate.isFromIntent + ) + + // create the notification builder with the common settings applied + val notificationBuilder = AEPPushNotificationBuilder.construct( + context, + pushTemplate, + channelIdToUse, + trackerActivityClass, + smallLayout, + expandedLayout, + R.id.carousel_container_layout + ) + + // handle left and right navigation buttons + setupNavigationButtons( + context, + pushTemplate, + broadcastReceiverClass, + imageUris, + captions, + interactionUris, + expandedLayout, + channelIdToUse + ) + + return notificationBuilder + } + + /** + * Downloads the images for a carousel push template. + * + * @param items the list of [CarouselPushTemplate.CarouselItem] objects to be displayed in the filmstrip carousel + * @return a list of `CarouselPushTemplate.CarouselItem` objects that were successfully downloaded + */ + private fun downloadCarouselItems( + items: List + ): List { + val validCarouselItems = mutableListOf() + for (item: CarouselPushTemplate.CarouselItem in items) { + val imageUri: String = item.imageUri + val pushImage: Bitmap? = PushTemplateImageUtils.getCachedImage(imageUri) + if (pushImage == null) { + Log.trace( + PushTemplateConstants.LOG_TAG, + SELF_TAG, + "Failed to retrieve an image from $imageUri, will not create a new carousel item." + ) + continue + } + validCarouselItems.add(item) + } + return validCarouselItems + } + + private fun getCarouselIndices( + pushTemplate: ManualCarouselPushTemplate, + imageUris: List + ): Triple { + val carouselIndices: Triple + if (pushTemplate.intentAction?.isNotEmpty() == true) { + carouselIndices = + if (pushTemplate.intentAction == PushTemplateConstants.IntentActions.MANUAL_CAROUSEL_LEFT_CLICKED || pushTemplate.intentAction == PushTemplateConstants.IntentActions.FILMSTRIP_LEFT_CLICKED) { + getNewIndicesForNavigateLeft(pushTemplate.centerImageIndex, imageUris.size) + } else { + getNewIndicesForNavigateRight(pushTemplate.centerImageIndex, imageUris.size) + } + } else { // setup default indices if not building the notification from an intent + carouselIndices = + if (pushTemplate.carouselLayoutType == PushTemplateConstants.DefaultValues.FILMSTRIP_CAROUSEL_MODE) { + Triple( + PushTemplateConstants.DefaultValues.FILMSTRIP_CAROUSEL_CENTER_INDEX - 1, + PushTemplateConstants.DefaultValues.FILMSTRIP_CAROUSEL_CENTER_INDEX, + PushTemplateConstants.DefaultValues.FILMSTRIP_CAROUSEL_CENTER_INDEX + 1 + ) + } else { + Triple( + imageUris.size - 1, + PushTemplateConstants.DefaultValues.MANUAL_CAROUSEL_START_INDEX, + PushTemplateConstants.DefaultValues.MANUAL_CAROUSEL_START_INDEX + 1 + ) + } + } + + return carouselIndices + } + + private fun setupCarouselImages( + context: Context, + captions: List, + interactionUris: List, + newIndices: Triple, + pushTemplate: ManualCarouselPushTemplate, + trackerActivityClass: Class?, + expandedLayout: RemoteViews, + validCarouselItems: List, + packageName: String?, + fallbackActionUri: String? + ) { + if (pushTemplate.carouselLayoutType == PushTemplateConstants.DefaultValues.FILMSTRIP_CAROUSEL_MODE) { + populateFilmstripCarouselImages( + context, + captions, + interactionUris, + newIndices, + pushTemplate, + trackerActivityClass, + expandedLayout + ) + } else { + populateManualCarouselImages( + context, + trackerActivityClass, + expandedLayout, + validCarouselItems, + packageName, + pushTemplate.tag, + fallbackActionUri, + pushTemplate.isNotificationSticky, + newIndices.second + ) + } + } + + private fun setupNavigationButtons( + context: Context, + pushTemplate: ManualCarouselPushTemplate, + broadcastReceiverClass: Class?, + imageUris: List, + captions: List, + interactionUris: List, + expandedLayout: RemoteViews, + channelId: String + ) { + val clickPair = + if (pushTemplate.carouselLayoutType == PushTemplateConstants.DefaultValues.DEFAULT_MANUAL_CAROUSEL_MODE) { + Pair( + PushTemplateConstants.IntentActions.MANUAL_CAROUSEL_LEFT_CLICKED, + PushTemplateConstants.IntentActions.MANUAL_CAROUSEL_RIGHT_CLICKED + ) + } else { + Pair( + PushTemplateConstants.IntentActions.FILMSTRIP_LEFT_CLICKED, + PushTemplateConstants.IntentActions.FILMSTRIP_RIGHT_CLICKED + ) + } + + val pendingIntentLeftButton = createCarouselNavigationClickPendingIntent( + context, + pushTemplate, + clickPair.first, + broadcastReceiverClass, + imageUris, + captions, + interactionUris, + channelId + ) + + val pendingIntentRightButton = createCarouselNavigationClickPendingIntent( + context, + pushTemplate, + clickPair.second, + broadcastReceiverClass, + imageUris, + captions, + interactionUris, + channelId + ) + + expandedLayout.setOnClickPendingIntent(R.id.leftImageButton, pendingIntentLeftButton) + expandedLayout.setOnClickPendingIntent(R.id.rightImageButton, pendingIntentRightButton) + } + + /** + * Populates the images for a manual carousel push template. + * + * @param context the current [Context] of the application + * @param trackerActivityClass the [Class] of the activity that will be used for tracking interactions with the carousel item + * @param cacheService the [CacheService] used to cache the downloaded images + * @param expandedLayout the [RemoteViews] containing the expanded layout of the notification + * @param items the list of [CarouselPushTemplate.CarouselItem] objects to be displayed in the carousel + * @param packageName the `String` name of the application package used to locate the layout resources + * @param tag the `String` tag used to identify the notification + * @param actionUri the `String` URI to be used when the carousel item is clicked + * @param autoCancel the `Boolean` value to determine if the notification should be automatically canceled when clicked + */ + private fun populateManualCarouselImages( + context: Context, + trackerActivityClass: Class?, + expandedLayout: RemoteViews, + items: List, + packageName: String?, + tag: String?, + actionUri: String?, + autoCancel: Boolean?, + centerIndex: Int + ) { + for (item: CarouselPushTemplate.CarouselItem in items) { + val imageUri = item.imageUri + val pushImage: Bitmap? = PushTemplateImageUtils.getCachedImage(imageUri) + if (pushImage == null) { + Log.trace( + PushTemplateConstants.LOG_TAG, + SELF_TAG, + "Failed to retrieve an image from $imageUri, will not create a new carousel item." + ) + continue + } + val carouselItemRemoteView = + RemoteViews(packageName, R.layout.push_template_carousel_item) + carouselItemRemoteView.setImageViewBitmap(R.id.carousel_item_image_view, pushImage) + carouselItemRemoteView.setTextViewText(R.id.carousel_item_caption, item.captionText) + + // assign a click action pending intent for each carousel item + val interactionUri = + if (item.interactionUri.isNullOrEmpty()) actionUri else item.interactionUri + interactionUri?.let { + carouselItemRemoteView.setRemoteViewClickAction( + context, + trackerActivityClass, + R.id.carousel_item_image_view, + interactionUri, + tag, + autoCancel ?: true + ) + } + + // add the carousel item to the view flipper + expandedLayout.addView(R.id.manual_carousel_view_flipper, carouselItemRemoteView) + + // set the center image + expandedLayout.setDisplayedChild( + R.id.manual_carousel_view_flipper, + centerIndex + ) + } + } + + /** + * Populates the images for a manual filmstrip carousel push template. + * + * @param context the current [Context] of the application + * @param cacheService the [CacheService] used to cache the downloaded images + * @param imageCaptions the list of [String] captions for each filmstrip carousel image + * @param imageClickActions the list of [String] click actions for each filmstrip carousel image + * @param newIndices a [Triple] of [Int] indices for the new left, center, and right images + * @param pushTemplate the [ManualCarouselPushTemplate] object containing the push template data + * @param trackerActivityClass the [Class] of the activity that will be used for tracking interactions with the carousel item + * @param expandedLayout the [RemoteViews] containing the expanded layout of the notification + */ + private fun populateFilmstripCarouselImages( + context: Context, + imageCaptions: List, + imageClickActions: List, + newIndices: Triple, + pushTemplate: ManualCarouselPushTemplate, + trackerActivityClass: Class?, + expandedLayout: RemoteViews + ) { + // get all captions present then set center caption text + val centerCaptionText = imageCaptions[newIndices.second] + expandedLayout.setTextViewText( + R.id.manual_carousel_filmstrip_caption, + centerCaptionText + ) + + // set the downloaded bitmaps in the filmstrip image views + val assetCacheLocation = PushTemplateImageUtils.getAssetCacheLocation() + if (assetCacheLocation.isNullOrEmpty()) { + Log.trace( + PushTemplateConstants.LOG_TAG, + SELF_TAG, + "Asset cache location is null or empty, unable to retrieve filmstrip carousel images." + ) + return + } + + val newLeftImage = PushTemplateImageUtils.getCachedImage( + pushTemplate.carouselItems[newIndices.first].imageUri + ) + expandedLayout.setImageViewBitmap( + R.id.manual_carousel_filmstrip_left, newLeftImage + ) + + val newCenterImage = PushTemplateImageUtils.getCachedImage( + pushTemplate.carouselItems[newIndices.second].imageUri + ) + expandedLayout.setImageViewBitmap( + R.id.manual_carousel_filmstrip_center, newCenterImage + ) + + val newRightImage = PushTemplateImageUtils.getCachedImage( + pushTemplate.carouselItems[newIndices.third].imageUri + ) + expandedLayout.setImageViewBitmap( + R.id.manual_carousel_filmstrip_right, newRightImage + ) + + // assign a click action pending intent to the center image view + val interactionUri = + if (!imageClickActions[newIndices.second].isNullOrEmpty()) imageClickActions[newIndices.second] else pushTemplate.actionUri + expandedLayout.setRemoteViewClickAction( + context, + trackerActivityClass, + R.id.manual_carousel_filmstrip_center, + interactionUri, + pushTemplate.tag, + pushTemplate.isNotificationSticky ?: false + ) + } + + /** + * Calculates a new left, center, and right index for a carousel skip left press given the current center index and total number + * of images + * + * @param centerIndex [Int] containing the current center image index + * @param listSize `Int` containing the total number of images + * @return [Triple] containing the calculated left, center, and right indices + */ + private fun getNewIndicesForNavigateLeft( + centerIndex: Int, + listSize: Int + ): Triple { + val newCenterIndex = (centerIndex - 1 + listSize) % listSize + val newLeftIndex = (newCenterIndex - 1 + listSize) % listSize + Log.trace( + PushTemplateConstants.LOG_TAG, + SELF_TAG, + "Calculated new indices. New center index is $newCenterIndex, new left index is $newLeftIndex, and new right index is $centerIndex." + ) + return Triple(newLeftIndex, newCenterIndex, centerIndex) + } + + /** + * Calculates a new left, center, and right index for a carousel skip right press given the current center index and total number + * of images + * + * @param centerIndex [Int] containing the current center image index + * @param listSize `Int` containing the total number of images + * @return [Triple] containing the calculated left, center, and right indices + */ + private fun getNewIndicesForNavigateRight( + centerIndex: Int, + listSize: Int + ): Triple { + val newCenterIndex = (centerIndex + 1) % listSize + val newRightIndex = (newCenterIndex + 1) % listSize + Log.trace( + PushTemplateConstants.LOG_TAG, + SELF_TAG, + "Calculated new indices. New center index is $newCenterIndex, new left index is $centerIndex, and new right index is $newRightIndex." + ) + return Triple(centerIndex, newCenterIndex, newRightIndex) + } + + /** + * Creates a click intent for the specified [Intent] action. This intent is used to handle interactions + * with the skip left and skip right buttons in a filmstrip or manual carousel push template notification. + * + * @param context the application [Context] + * @param pushTemplate the [ManualCarouselPushTemplate] object containing the manual carousel push template data + * @param intentAction [String] containing the intent action + * @param broadcastReceiverClass the [Class] of the broadcast receiver to set in the created pending intent + * @param downloadedImageUris [List] of String` containing the downloaded image URIs + * @param imageCaptions `List` of String` containing the image captions + * @param imageClickActions `List` of String` containing the image click actions + * @return the created click [Intent] + */ + private fun createCarouselNavigationClickPendingIntent( + context: Context, + pushTemplate: ManualCarouselPushTemplate, + intentAction: String, + broadcastReceiverClass: Class?, + downloadedImageUris: List, + imageCaptions: List, + imageClickActions: List, + channelId: String + ): PendingIntent { + val clickIntent = Intent(intentAction).apply { + broadcastReceiverClass?.let { + setClass(context, broadcastReceiverClass) + } + + flags = Intent.FLAG_ACTIVITY_SINGLE_TOP + putExtra( + PushTemplateConstants.IntentKeys.TEMPLATE_TYPE, + pushTemplate.templateType?.value + ) + putExtra( + PushTemplateConstants.IntentKeys.CHANNEL_ID, + channelId + ) + putExtra( + PushTemplateConstants.IntentKeys.CUSTOM_SOUND, pushTemplate.sound + ) + putExtra( + PushTemplateConstants.IntentKeys.CENTER_IMAGE_INDEX, + pushTemplate.centerImageIndex + ) + putExtra( + PushTemplateConstants.IntentKeys.IMAGE_URLS, + downloadedImageUris.toTypedArray() + ) + putExtra( + PushTemplateConstants.IntentKeys.IMAGE_CAPTIONS, + imageCaptions.toTypedArray() + ) + putExtra( + PushTemplateConstants.IntentKeys.IMAGE_CLICK_ACTIONS, + imageClickActions.toTypedArray() + ) + putExtra(PushTemplateConstants.IntentKeys.TITLE_TEXT, pushTemplate.title) + putExtra(PushTemplateConstants.IntentKeys.BODY_TEXT, pushTemplate.body) + putExtra( + PushTemplateConstants.IntentKeys.EXPANDED_BODY_TEXT, + pushTemplate.expandedBodyText + ) + putExtra( + PushTemplateConstants.IntentKeys.NOTIFICATION_BACKGROUND_COLOR, + pushTemplate.notificationBackgroundColor + ) + putExtra( + PushTemplateConstants.IntentKeys.TITLE_TEXT_COLOR, + pushTemplate.titleTextColor + ) + putExtra( + PushTemplateConstants.IntentKeys.EXPANDED_BODY_TEXT_COLOR, + pushTemplate.expandedBodyTextColor + ) + putExtra( + PushTemplateConstants.IntentKeys.SMALL_ICON, pushTemplate.smallIcon + ) + putExtra( + PushTemplateConstants.IntentKeys.LARGE_ICON, pushTemplate.largeIcon + ) + putExtra( + PushTemplateConstants.IntentKeys.SMALL_ICON_COLOR, + pushTemplate.smallIconColor + ) + putExtra( + PushTemplateConstants.IntentKeys.VISIBILITY, + pushTemplate.getNotificationVisibility() + ) + putExtra( + PushTemplateConstants.IntentKeys.IMPORTANCE, + pushTemplate.getNotificationImportance() + ) + putExtra( + PushTemplateConstants.IntentKeys.TICKER, pushTemplate.ticker + ) + putExtra( + PushTemplateConstants.IntentKeys.TAG, pushTemplate.tag + ) + putExtra( + PushTemplateConstants.IntentKeys.STICKY, pushTemplate.isNotificationSticky + ) + putExtra(PushTemplateConstants.IntentKeys.ACTION_URI, pushTemplate.actionUri) + putExtra( + PushTemplateConstants.IntentKeys.PAYLOAD_VERSION, pushTemplate.payloadVersion + ) + putExtra( + PushTemplateConstants.IntentKeys.CAROUSEL_ITEMS, + pushTemplate.rawCarouselItems + ) + putExtra( + PushTemplateConstants.IntentKeys.CAROUSEL_LAYOUT_TYPE, + pushTemplate.carouselLayoutType + ) + } + + return PendingIntent.getBroadcast( + context, + 0, + clickIntent, + PendingIntent.FLAG_MUTABLE or PendingIntent.FLAG_UPDATE_CURRENT + ) + } +} diff --git a/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/builders/ZeroBezelNotificationBuilder.kt b/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/builders/ZeroBezelNotificationBuilder.kt new file mode 100644 index 00000000..fb534d36 --- /dev/null +++ b/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/builders/ZeroBezelNotificationBuilder.kt @@ -0,0 +1,99 @@ +/* + Copyright 2024 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.notificationbuilder.builders + +import android.app.Activity +import android.app.NotificationManager +import android.content.Context +import android.view.View +import android.widget.RemoteViews +import androidx.core.app.NotificationCompat +import com.adobe.marketing.mobile.notificationbuilder.NotificationConstructionFailedException +import com.adobe.marketing.mobile.notificationbuilder.PushTemplateConstants +import com.adobe.marketing.mobile.notificationbuilder.PushTemplateImageUtils +import com.adobe.marketing.mobile.notificationbuilder.R +import com.adobe.marketing.mobile.notificationbuilder.extensions.createNotificationChannelIfRequired +import com.adobe.marketing.mobile.notificationbuilder.templates.ZeroBezelPushTemplate +import com.adobe.marketing.mobile.services.Log + +internal object ZeroBezelNotificationBuilder { + private const val SELF_TAG = "BasicTemplateNotificationBuilder" + + @Throws(NotificationConstructionFailedException::class) + fun construct( + context: Context, + pushTemplate: ZeroBezelPushTemplate, + trackerActivityClass: Class? + ): NotificationCompat.Builder { + Log.trace( + PushTemplateConstants.LOG_TAG, + SELF_TAG, + "Building a zero bezel template push notification." + ) + val packageName = context.packageName + val smallLayout = RemoteViews(packageName, R.layout.push_template_zero_bezel_collapsed) + val expandedLayout = RemoteViews(packageName, R.layout.push_template_zero_bezel_expanded) + + // download and cache the image used in the notification + val downloadedImageCount = + PushTemplateImageUtils.cacheImages(listOf(pushTemplate.imageUrl)) + + // Check if the image was downloaded + if (downloadedImageCount > 0) { + // set the image on the notification if it was downloaded + val pushImage = + PushTemplateImageUtils.getCachedImage(pushTemplate.imageUrl) + expandedLayout.setImageViewBitmap(R.id.expanded_template_image, pushImage) + + // only set image on the collapsed view if the style is "img" + if (pushTemplate.collapsedStyle == ZeroBezelPushTemplate.ZeroBezelStyle.IMAGE) { + smallLayout.setImageViewBitmap(R.id.collapsed_template_image, pushImage) + } else { + smallLayout.setViewVisibility(R.id.collapsed_template_image, View.GONE) + smallLayout.setViewVisibility(R.id.gradient_template_image, View.GONE) + } + } else { + Log.trace( + PushTemplateConstants.LOG_TAG, + SELF_TAG, + "No image found for zero bezel push template." + ) + // hide the image views if no image was downloaded + expandedLayout.setViewVisibility(R.id.expanded_template_image, View.GONE) + expandedLayout.setViewVisibility(R.id.gradient_template_image, View.GONE) + smallLayout.setViewVisibility(R.id.collapsed_template_image, View.GONE) + smallLayout.setViewVisibility(R.id.gradient_template_image, View.GONE) + } + + // create the notification channel if required + val notificationManager = + context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager + val channelIdToUse: String = notificationManager.createNotificationChannelIfRequired( + context, + pushTemplate.channelId, + pushTemplate.sound, + pushTemplate.getNotificationImportance(), + pushTemplate.isFromIntent + ) + + // create the notification builder with the common settings applied + return AEPPushNotificationBuilder.construct( + context, + pushTemplate, + channelIdToUse, + trackerActivityClass, + smallLayout, + expandedLayout, + R.id.zero_bezel_expanded_layout + ) + } +} diff --git a/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/extensions/AppResourceExtensions.kt b/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/extensions/AppResourceExtensions.kt new file mode 100644 index 00000000..8e36d7ae --- /dev/null +++ b/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/extensions/AppResourceExtensions.kt @@ -0,0 +1,74 @@ +/* + Copyright 2024 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.notificationbuilder.extensions + +import android.content.ContentResolver +import android.content.Context +import android.content.pm.PackageManager +import android.net.Uri +import com.adobe.marketing.mobile.notificationbuilder.PushTemplateConstants +import com.adobe.marketing.mobile.services.Log + +private const val SELF_TAG = "AppResourceExtensions" + +/** + * Returns the resource id for the drawable with the given name. The file must be in the + * res/drawable directory. If the drawable file is not found, 0 is returned. + * + * @param iconName the name of the icon file + * @return the resource id for the icon with the given name + */ +internal fun Context.getIconWithResourceName( + iconName: String? +): Int { + return if (iconName.isNullOrEmpty()) { + 0 + } else resources.getIdentifier(iconName, "drawable", packageName) +} + +/** + * Returns the default application icon. + * + * @return the resource id for the default application icon + */ +internal fun Context.getDefaultAppIcon(): Int { + val packageName = packageName + try { + return packageManager.getApplicationInfo(packageName, 0).icon + } catch (e: PackageManager.NameNotFoundException) { + Log.warning( + PushTemplateConstants.LOG_TAG, + SELF_TAG, + "Package manager NameNotFoundException while reading default application icon: ${e.localizedMessage}" + ) + } + return -1 +} + +/** + * Returns the Uri for the sound file with the given name. The sound file must be in the res/raw + * directory. The sound file should be in format of .mp3, .wav, or .ogg + * + * @param soundName [String] containing the name of the sound file + * @return the [Uri] for the sound file with the given name + */ +internal fun Context.getSoundUriForResourceName( + soundName: String? +): Uri { + return Uri.parse( + ContentResolver.SCHEME_ANDROID_RESOURCE + + "://" + + packageName + + "/raw/" + + soundName + ) +} diff --git a/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/extensions/NotificationCompatBuilderExtensions.kt b/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/extensions/NotificationCompatBuilderExtensions.kt new file mode 100644 index 00000000..bfd10e88 --- /dev/null +++ b/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/extensions/NotificationCompatBuilderExtensions.kt @@ -0,0 +1,323 @@ +/* + Copyright 2024 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.notificationbuilder.extensions + +import android.app.Activity +import android.app.PendingIntent +import android.content.Context +import android.content.Intent +import android.graphics.Color +import android.media.RingtoneManager +import androidx.core.app.NotificationCompat +import com.adobe.marketing.mobile.MobileCore +import com.adobe.marketing.mobile.notificationbuilder.PendingIntentUtils +import com.adobe.marketing.mobile.notificationbuilder.PushTemplateConstants +import com.adobe.marketing.mobile.notificationbuilder.PushTemplateImageUtils +import com.adobe.marketing.mobile.notificationbuilder.templates.AEPPushTemplate +import com.adobe.marketing.mobile.notificationbuilder.templates.BasicPushTemplate +import com.adobe.marketing.mobile.services.Log +import java.util.Random + +private const val SELF_TAG = "RemoteViewExtensions" + +/** + * Sets the small icon for the notification. If a small icon is received from the payload, the + * same is used. If a small icon is not received from the payload, we use the icon set using + * MobileCore.setSmallIcon(). If a small icon is not set using MobileCore.setSmallIcon(), we use + * the default small icon of the application. + * + * @param context the application [Context] + * @param smallIcon `String` containing the small icon to use + * @param iconColor `String` containing the small icon color code to use + */ +internal fun NotificationCompat.Builder.setSmallIcon( + context: Context, + smallIcon: String?, + iconColor: String? +): NotificationCompat.Builder { + val iconFromPayload = context.getIconWithResourceName(smallIcon) + val iconFromMobileCore = MobileCore.getSmallIconResourceID() + val iconResourceId: Int + if (isValidIcon(iconFromPayload)) { + iconResourceId = iconFromPayload + } else if (isValidIcon(iconFromMobileCore)) { + iconResourceId = iconFromMobileCore + } else { + val iconFromApp = context.getDefaultAppIcon() + if (isValidIcon(iconFromApp)) { + iconResourceId = iconFromApp + } else { + Log.warning( + PushTemplateConstants.LOG_TAG, + SELF_TAG, + "No valid small icon found. Notification will not be displayed." + ) + return this + } + } + setSmallIcon(iconResourceId) + setSmallIconColor(iconColor) + return this +} + +/** + * Sets a custom color to the notification's small icon. + * + * @param iconColorHex `String` containing a color code to be used in customizing the + * small icon color + */ +private fun NotificationCompat.Builder.setSmallIconColor( + iconColorHex: String? +) { + if (iconColorHex.isNullOrEmpty()) { + Log.trace( + PushTemplateConstants.LOG_TAG, + SELF_TAG, + "Empty icon color hex string found, custom color will not be applied to the notification icon." + ) + return + } + + try { + // sets the icon color if provided + setColorized(true).color = Color.parseColor("#$iconColorHex") + } catch (exception: IllegalArgumentException) { + Log.trace( + PushTemplateConstants.LOG_TAG, + SELF_TAG, + "Unrecognized hex string passed to Color.parseColor(), custom color will not be applied to the notification icon." + ) + } + return +} + +/** + * Sets the visibility of the notification. If a visibility is received from the payload, the + * same is used. If a visibility is not received from the payload, the default visibility is used. + * + * @param pushTemplate the [AEPPushTemplate] containing the visibility value + */ +internal fun NotificationCompat.Builder.setVisibility( + pushTemplate: AEPPushTemplate +): NotificationCompat.Builder { + when (pushTemplate.getNotificationVisibility()) { + NotificationCompat.VISIBILITY_PUBLIC -> setVisibility( + NotificationCompat.VISIBILITY_PUBLIC + ) + + NotificationCompat.VISIBILITY_PRIVATE -> setVisibility( + NotificationCompat.VISIBILITY_PRIVATE + ) + + NotificationCompat.VISIBILITY_SECRET -> setVisibility( + NotificationCompat.VISIBILITY_SECRET + ) + + else -> { + setVisibility(NotificationCompat.VISIBILITY_PRIVATE) + Log.debug( + PushTemplateConstants.LOG_TAG, + SELF_TAG, + "Invalid visibility value received from the payload. Using the default visibility value." + ) + } + } + return this +} + +/** + * Sets the sound for the legacy style notification or notification on a device less than API 25. + * If a sound is received from the payload, the same is used. + * If a sound is not received from the payload, the default sound is used. + * + * @param context the application [Context] + * @param customSound [String] containing the custom sound file name to load from the + * bundled assets + */ +internal fun NotificationCompat.Builder.setSound( + context: Context, + customSound: String? +): NotificationCompat.Builder { + if (customSound.isNullOrEmpty()) { + Log.trace( + PushTemplateConstants.LOG_TAG, + SELF_TAG, + "No custom sound found in the push template, using the default notification sound." + ) + setSound( + RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION) + ) + return this + } + Log.trace( + PushTemplateConstants.LOG_TAG, + SELF_TAG, + "Setting sound from bundle named $customSound." + ) + setSound( + context.getSoundUriForResourceName( + customSound + ) + ) + return this +} + +/** + * Sets the provided image url as the large icon for the legacy style notification. If a large icon url is received + * from the payload, the image is downloaded and the notification style is set to + * BigPictureStyle. If large icon url is not received from the payload, default style is used + * for the notification. + * + * @param imageUrl [String] containing the image url + * @param title `String` containing the title + * @param bodyText `String` containing the body text + */ +internal fun NotificationCompat.Builder.setLargeIcon( + imageUrl: String?, + title: String?, + bodyText: String? +): NotificationCompat.Builder { + // Quick bail out if there is no image url + if (imageUrl.isNullOrEmpty()) return this + val downloadedIconCount: Int = PushTemplateImageUtils.cacheImages( + listOf(imageUrl) + ) + + // Bail out if the download fails + if (downloadedIconCount == 0) { + return this + } + + val bitmap = PushTemplateImageUtils.getCachedImage(imageUrl) + setLargeIcon(bitmap) + val bigPictureStyle = NotificationCompat.BigPictureStyle() + bigPictureStyle.bigPicture(bitmap) + bigPictureStyle.bigLargeIcon(null) + bigPictureStyle.setBigContentTitle(title) + bigPictureStyle.setSummaryText(bodyText) + setStyle(bigPictureStyle) + return this +} + +/** + * Sets the click action for the notification. + * + * @param context the application [Context] + * @param trackerActivityClass the [Class] of the activity to set in the created pending intent for tracking purposes + * @param actionUri `String` containing the action uri + * @param tag `String` containing the tag to use when scheduling the notification + * @param stickyNotification `boolean` if false, remove the notification after the `RemoteViews` is pressed + */ +internal fun NotificationCompat.Builder.setNotificationClickAction( + context: Context, + trackerActivityClass: Class?, + actionUri: String?, + tag: String?, + stickyNotification: Boolean +): NotificationCompat.Builder { + val pendingIntent: PendingIntent? = PendingIntentUtils.createPendingIntent( + context, + trackerActivityClass, + actionUri, + null, + tag, + stickyNotification + ) + setContentIntent(pendingIntent) + return this +} + +/** + * Sets the delete action for the notification. + * + * @param context the application [Context] + * @param trackerActivityClass the [Class] of the activity to set in the created pending intent for tracking purposes + * notification + */ +internal fun NotificationCompat.Builder.setNotificationDeleteAction( + context: Context, + trackerActivityClass: Class? +): NotificationCompat.Builder { + val deleteIntent = Intent(PushTemplateConstants.NotificationAction.DISMISSED) + trackerActivityClass?.let { + deleteIntent.setClass(context.applicationContext, trackerActivityClass) + } + deleteIntent.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP) + val intent = PendingIntent.getActivity( + context, + Random().nextInt(), + deleteIntent, + PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE + ) + setDeleteIntent(intent) + return this +} + +/** + * Adds action buttons for the notification. + * + * @param context the application [Context] + * @param trackerActivityClass the [Activity] class to use as the tracker activity + * @param actionButtonsString `String` a JSON string containing action buttons to attach + * to the notification + * notification + * @param tag `String` containing the tag to use when scheduling the notification + * @param stickyNotification [Boolean] if false, remove the notification after the action + * button is pressed + */ +internal fun NotificationCompat.Builder.addActionButtons( + context: Context, + trackerActivityClass: Class?, + actionButtons: List?, + tag: String?, + stickyNotification: Boolean +): NotificationCompat.Builder { + if (actionButtons.isNullOrEmpty()) { + return this + } + for (eachButton in actionButtons) { + val pendingIntent: PendingIntent? = + if (eachButton.type === PushTemplateConstants.ActionType.DEEPLINK || + eachButton.type === PushTemplateConstants.ActionType.WEBURL + ) { + PendingIntentUtils.createPendingIntent( + context, + trackerActivityClass, + eachButton.link, + eachButton.label, + tag, + stickyNotification + ) + } else { + PendingIntentUtils.createPendingIntent( + context, + trackerActivityClass, + null, + eachButton.label, + tag, + stickyNotification + ) + } + addAction(0, eachButton.label, pendingIntent) + } + return this +} + +/** + * Checks if the icon is valid. + * + * @param icon the icon to be checked + * @return true if the icon is valid, false otherwise + */ +private fun isValidIcon(icon: Int): Boolean { + return icon > 0 +} diff --git a/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/extensions/NotificationManagerExtensions.kt b/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/extensions/NotificationManagerExtensions.kt new file mode 100644 index 00000000..433b75d4 --- /dev/null +++ b/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/extensions/NotificationManagerExtensions.kt @@ -0,0 +1,88 @@ +/* + Copyright 2024 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.notificationbuilder.extensions + +import android.app.NotificationChannel +import android.app.NotificationManager +import android.content.Context +import android.media.RingtoneManager +import android.os.Build +import com.adobe.marketing.mobile.notificationbuilder.PushTemplateConstants +import com.adobe.marketing.mobile.services.Log + +private const val SELF_TAG = "NotificationManagerExtensions" + +/** + * Creates a notification channel if the device is running on Android O or higher. If the channel + * already exists, the same channel is used. A default channel ID will be used if no channel ID + * is received from the payload. + * + * @param context the application [Context] + * @param channelId [String] containing the notification channel id + * @param customSound `String` containing the custom sound to apply on the notification channel + * @param importance [Int] containing the notification importance + * @param isFromIntent `Boolean` flag indicating if the push is from an intent + * @return A [String] containing the created or existing channel ID + */ +internal fun NotificationManager.createNotificationChannelIfRequired( + context: Context, + channelId: String?, + customSound: String?, + importance: Int, + isFromIntent: Boolean +): String { + // create a silent notification channel if push is from intent + // if not from intent and channel id is not provided, use the default channel id + val channelIdToUse = + if (isFromIntent) PushTemplateConstants.DefaultValues.SILENT_NOTIFICATION_CHANNEL_ID + else channelId ?: PushTemplateConstants.DEFAULT_CHANNEL_ID + + // No channel creation required. + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) { + return channelIdToUse + } + + // Don't create a channel if it already exists + if (getNotificationChannel(channelIdToUse) != null) { + Log.trace( + PushTemplateConstants.LOG_TAG, + SELF_TAG, + "Using previously created notification channel: $channelIdToUse." + ) + return channelIdToUse + } + + // Create a channel + val channel = NotificationChannel( + channelIdToUse, + if (isFromIntent) PushTemplateConstants.SILENT_CHANNEL_NAME else PushTemplateConstants.DEFAULT_CHANNEL_NAME, + importance + ) + + // Add a sound if required. + if (isFromIntent) { + channel.setSound(null, null) + } else { + val sound = if (customSound.isNullOrEmpty()) { + RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION) + } else context.getSoundUriForResourceName(customSound) + channel.setSound(sound, null) + } + + Log.trace( + PushTemplateConstants.LOG_TAG, + SELF_TAG, + "Creating a new notification channel with ID: $channelId. ${if (customSound.isNullOrEmpty()) "and default sound." else "and custom sound: $customSound."}" + ) + createNotificationChannel(channel) + return channelIdToUse +} diff --git a/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/extensions/RemoteViewsExtensions.kt b/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/extensions/RemoteViewsExtensions.kt new file mode 100644 index 00000000..0ff4042e --- /dev/null +++ b/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/extensions/RemoteViewsExtensions.kt @@ -0,0 +1,265 @@ +/* + Copyright 2024 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.notificationbuilder.extensions + +import android.app.Activity +import android.app.PendingIntent +import android.content.Context +import android.graphics.Color +import android.view.View +import android.widget.RemoteViews +import androidx.core.app.NotificationCompat +import com.adobe.marketing.mobile.notificationbuilder.PendingIntentUtils +import com.adobe.marketing.mobile.notificationbuilder.PushTemplateConstants +import com.adobe.marketing.mobile.notificationbuilder.PushTemplateImageUtils +import com.adobe.marketing.mobile.notificationbuilder.R +import com.adobe.marketing.mobile.services.Log +import com.adobe.marketing.mobile.services.ServiceProvider +import com.adobe.marketing.mobile.util.UrlUtils + +private const val SELF_TAG = "RemoteViewExtensions" + +/** + * Sets a provided color hex string to a UI element contained in a specified [RemoteViews] + * view. + * + * @param elementId [Int] containing the resource id of the UI element + * @param colorHex [String] containing the color hex string + * @param methodName `String` containing the method to be called on the UI element to + * update the color + * @param viewFriendlyName `String` containing the friendly name of the view to be used + * for logging purposes + */ +internal fun RemoteViews.setElementColor( + elementId: Int, + colorHex: String, + methodName: String, + viewFriendlyName: String +) { + if (colorHex.isEmpty()) { + Log.trace( + PushTemplateConstants.LOG_TAG, + SELF_TAG, + "Empty color hex string found, custom color will not be applied to $viewFriendlyName." + ) + return + } + + try { + setInt(elementId, methodName, Color.parseColor(colorHex)) + } catch (exception: IllegalArgumentException) { + Log.trace( + PushTemplateConstants.LOG_TAG, + SELF_TAG, + "Unrecognized hex string passed to Color.parseColor(), custom color will not be applied to $viewFriendlyName." + ) + } +} + +/** + * Sets custom colors to the notification background. + * + * @param backgroundColor [String] containing the hex color code for the notification background + * @param containerViewId [Int] containing the resource id of the push template notification RemoteViews + */ +internal fun RemoteViews.setNotificationBackgroundColor( + backgroundColor: String?, + containerViewId: Int +) { + // get custom color from hex string and set it the notification background + if (backgroundColor.isNullOrEmpty()) { + Log.trace( + PushTemplateConstants.LOG_TAG, + SELF_TAG, + "Empty background color hex string found, custom color will not be applied to the notification background." + ) + return + } + setElementColor( + containerViewId, + "#$backgroundColor", + PushTemplateConstants.MethodNames.SET_BACKGROUND_COLOR, + PushTemplateConstants.FriendlyViewNames.NOTIFICATION_BACKGROUND + ) +} + +/** + * Sets custom colors to the notification title text. + * + * @param titleTextColor [String] containing the hex color code for the notification title text + * @param containerViewId [Int] containing the resource id of the push template notification RemoteViews + */ +internal fun RemoteViews.setNotificationTitleTextColor( + titleTextColor: String?, + containerViewId: Int +) { + // get custom color from hex string and set it the notification title + if (titleTextColor.isNullOrEmpty()) { + Log.trace( + PushTemplateConstants.LOG_TAG, + SELF_TAG, + "Empty title text color hex string found, custom color will not be applied to the notification title text." + ) + return + } + setElementColor( + containerViewId, + "#$titleTextColor", + PushTemplateConstants.MethodNames.SET_TEXT_COLOR, + PushTemplateConstants.FriendlyViewNames.NOTIFICATION_TITLE + ) +} + +/** + * Sets custom colors to the notification body text. + * + * @param expandedBodyTextColor [String] containing the hex color code for the expanded + * notification body text + * @param containerViewId [Int] containing the resource id of the push template notification RemoteViews + */ +internal fun RemoteViews.setNotificationBodyTextColor( + expandedBodyTextColor: String?, + containerViewId: Int +) { + // get custom color from hex string and set it the notification body text + if (expandedBodyTextColor.isNullOrEmpty()) { + Log.trace( + PushTemplateConstants.LOG_TAG, + SELF_TAG, + "Empty expanded body text color hex string found, custom color will not be applied to the notification body text." + ) + return + } + setElementColor( + containerViewId, + "#$expandedBodyTextColor", + PushTemplateConstants.MethodNames.SET_TEXT_COLOR, + PushTemplateConstants.FriendlyViewNames.NOTIFICATION_BODY_TEXT + ) +} + +/** + * Sets the large icon for the provided [RemoteViews]. If a large icon contains a filename + * only then the large icon is set from a bundle image resource. If a large icon contains a URL, + * the large icon is downloaded then set. + * + * @param largeIcon `String` containing the large icon to use + */ +internal fun RemoteViews.setRemoteViewLargeIcon(largeIcon: String?) { + if (largeIcon.isNullOrEmpty()) { + Log.trace( + PushTemplateConstants.LOG_TAG, + SELF_TAG, + "Null or empty large icon string found, large icon will not be applied." + ) + setViewVisibility(R.id.large_icon, View.GONE) + return + } + + if (UrlUtils.isValidUrl(largeIcon)) { + setRemoteLargeIcon(largeIcon) + } else { + setBundledLargeIcon(largeIcon) + } +} + +/** + * Sets the click action for the specified view in the custom push template [RemoteViews]. + * + * @param context the application [Context] + * @param trackerActivityClass the [Class] of the activity to set in the created pending intent for tracking purposes + * template notification + * @param targetViewResourceId [Int] containing the resource id of the view to attach the click action + * @param actionUri `String` containing the action uri defined for the push template image + * @param tag `String` containing the tag to use when scheduling the notification + * @param stickyNotification [Boolean] if false, remove the [NotificationCompat] after the `RemoteViews` is pressed + */ +internal fun RemoteViews.setRemoteViewClickAction( + context: Context, + trackerActivityClass: Class?, + targetViewResourceId: Int, + actionUri: String?, + tag: String?, + stickyNotification: Boolean +) { + if (actionUri.isNullOrEmpty()) { + Log.trace( + PushTemplateConstants.LOG_TAG, + SELF_TAG, + "No valid action uri found for the clicked view with id $targetViewResourceId. No click action will be assigned." + ) + return + } + Log.trace( + PushTemplateConstants.LOG_TAG, + SELF_TAG, + "Setting remote view click action uri: $actionUri." + ) + + val pendingIntent: PendingIntent? = + PendingIntentUtils.createPendingIntent( + context, + trackerActivityClass, + actionUri, + null, + tag, + stickyNotification + ) + setOnClickPendingIntent(targetViewResourceId, pendingIntent) +} + +/** + * Sets the large icon for the provided [RemoteViews] by downloading the image from the provided URL. + * If the image cannot be downloaded, the large icon visibility is set to [View.GONE]. + * + * @param largeIcon `String` containing the large icon URL to download and use + */ +internal fun RemoteViews.setRemoteLargeIcon(largeIcon: String?) { + if (!UrlUtils.isValidUrl(largeIcon)) { + return + } + val downloadedIconCount = PushTemplateImageUtils.cacheImages(listOf(largeIcon)) + if (downloadedIconCount == 0) { + Log.trace( + PushTemplateConstants.LOG_TAG, + SELF_TAG, + "Unable to download an image from $largeIcon, large icon will not be applied." + ) + setViewVisibility(R.id.large_icon, View.GONE) + return + } + setImageViewBitmap( + R.id.large_icon, + PushTemplateImageUtils.getCachedImage(largeIcon) + ) +} + +/** + * Sets the large icon resource bundled with the app for the provided [RemoteViews]. + * If the resource does not exist, the [RemoteViews] visibility is set to [View.GONE]. + * + * @param largeIcon `String` containing the large icon to use + */ +internal fun RemoteViews.setBundledLargeIcon(largeIcon: String?) { + val bundledIconId: Int? = ServiceProvider.getInstance() + .appContextService.applicationContext?.getIconWithResourceName(largeIcon) + if (bundledIconId == null || bundledIconId == 0) { + Log.trace( + PushTemplateConstants.LOG_TAG, + SELF_TAG, + "Unable to find a bundled image with name $largeIcon, large icon will not be applied." + ) + setViewVisibility(R.id.large_icon, View.GONE) + return + } + setImageViewResource(R.id.large_icon, bundledIconId) +} diff --git a/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/templates/AEPPushTemplate.kt b/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/templates/AEPPushTemplate.kt new file mode 100644 index 00000000..dc9d6653 --- /dev/null +++ b/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/templates/AEPPushTemplate.kt @@ -0,0 +1,328 @@ +/* + Copyright 2024 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.notificationbuilder.templates + +import android.app.NotificationManager +import android.content.Intent +import android.os.Build +import androidx.annotation.RequiresApi +import androidx.core.app.NotificationCompat +import com.adobe.marketing.mobile.notificationbuilder.NotificationPriority +import com.adobe.marketing.mobile.notificationbuilder.NotificationVisibility +import com.adobe.marketing.mobile.notificationbuilder.PushTemplateConstants +import com.adobe.marketing.mobile.notificationbuilder.PushTemplateType +import com.adobe.marketing.mobile.services.Log +import com.adobe.marketing.mobile.util.DataReader + +/** + * This class is used to parse the push template data payload or an intent and provide the necessary information + * to build a notification. + */ +internal sealed class AEPPushTemplate { + + // Message data payload for the push template + internal lateinit var messageData: MutableMap + private set + + // Required, title of the message shown in the collapsed and expanded push template layouts + internal val title: String + + // Required, body of the message shown in the collapsed push template layout + internal val body: String + + // Required, Version of the payload assigned by the authoring UI. + internal var payloadVersion: Int? + private set + + // begin optional values + // Optional, sound to play when the notification is shown + internal var sound: String? + private set + + // Optional, number to show on the badge of the app + internal var badgeCount: Int = 0 + private set + + // Optional, priority of the notification + internal var notificationPriority: Int? + private set + + // Optional, importance of the notification. Only used on Android N and above. + private var notificationImportance: Int? + + // Optional, visibility of the notification + private var notificationVisibility: Int? + + // Optional, notification channel to use when displaying the notification. Only used on Android O and above. + internal var channelId: String? + private set + + // Optional, small icon for the notification + internal var smallIcon: String? + private set + + // Optional, large icon for the notification + internal var largeIcon: String? + private set + + // Optional, image to show in the notification + internal var imageUrl: String? + private set + + // Optional, action type for the notification + private var actionType: PushTemplateConstants.ActionType? + + // Optional, action uri for the notification + internal var actionUri: String? + private set + + // Optional, Body of the message shown in the expanded message layout (setCustomBigContentView) + internal var expandedBodyText: String? + private set + + // Optional, Text color for adb_body and adb_body_ex. Represented as six character hex, e.g. 00FF00 + internal var expandedBodyTextColor: String? + private set + + // Optional, Text color for adb_title. Represented as six character hex, e.g. 00FF00 + internal var titleTextColor: String? + private set + + // Optional, Color for the notification's small icon. Represented as six character hex, e.g. + // 00FF00 + internal var smallIconColor: String? + private set + + // Optional, Color for the notification's background. Represented as six character hex, e.g. + // 00FF00 + internal var notificationBackgroundColor: String? + private set + + // Optional, If present and a notification with the same tag is already being shown, the new + // notification replaces the existing one in the notification drawer. + internal var tag: String? + private set + + // Optional, If present sets the "ticker" text, which is sent to accessibility services. + internal var ticker: String? + private set + + // Optional, the type of push template this payload contains + internal var templateType: PushTemplateType? + private set + + // Optional, when set to false or unset, the notification is automatically dismissed when the + // user clicks it in the panel. When set to true, the notification persists even when the user + // clicks it. + internal var isNotificationSticky: Boolean? + private set + + // flag to denote if the PushTemplate was built from an intent + internal var isFromIntent: Boolean + private set + + /** + * Constructor to create a push template object from the data payload. + * + * @param data [Map] of key-value pairs representing the push template data payload + */ + constructor(data: Map?) { + // fast fail (via IllegalArgumentException) if required data is not present + if (data.isNullOrEmpty()) throw IllegalArgumentException("Push template data is null.") + this.messageData = data.toMutableMap() + val title = DataReader.optString(data, PushTemplateConstants.PushPayloadKeys.TITLE, null) + if (title.isNullOrEmpty()) throw IllegalArgumentException("Required field \"${PushTemplateConstants.PushPayloadKeys.TITLE}\" not found.") + this.title = title + + val bodyText = DataReader.optString(data, PushTemplateConstants.PushPayloadKeys.BODY, null) + if (bodyText.isNullOrEmpty()) throw IllegalArgumentException("Required field \"${PushTemplateConstants.PushPayloadKeys.BODY}\" or \"${PushTemplateConstants.PushPayloadKeys.ACC_PAYLOAD_BODY}\" not found.") + this.body = bodyText + + val payloadVersion = + DataReader.optString(data, PushTemplateConstants.PushPayloadKeys.VERSION, null) + if (payloadVersion.isNullOrEmpty()) throw IllegalArgumentException("Required field \"${PushTemplateConstants.PushPayloadKeys.VERSION}\" not found.") + this.payloadVersion = payloadVersion.toInt() + + // optional push template data + sound = DataReader.optString(data, PushTemplateConstants.PushPayloadKeys.SOUND, null) + imageUrl = DataReader.optString(data, PushTemplateConstants.PushPayloadKeys.IMAGE_URL, null) + actionUri = + DataReader.optString(data, PushTemplateConstants.PushPayloadKeys.ACTION_URI, null) + actionType = PushTemplateConstants.ActionType.valueOf( + DataReader.optString(data, PushTemplateConstants.PushPayloadKeys.ACTION_TYPE, null) + ?: PushTemplateConstants.ActionType.NONE.name + ) + var smallIcon = + DataReader.optString(data, PushTemplateConstants.PushPayloadKeys.SMALL_ICON, null) + if (smallIcon.isNullOrEmpty()) { + Log.debug( + PushTemplateConstants.LOG_TAG, + SELF_TAG, + "The \"${PushTemplateConstants.PushPayloadKeys.SMALL_ICON}\" key is not present in the message data payload, attempting to use \"${PushTemplateConstants.PushPayloadKeys.SMALL_ICON}\" key instead." + ) + smallIcon = DataReader.optString( + data, + PushTemplateConstants.PushPayloadKeys.LEGACY_SMALL_ICON, + null + ) + } + this.smallIcon = smallIcon + largeIcon = + DataReader.optString(data, PushTemplateConstants.PushPayloadKeys.LARGE_ICON, null) + try { + val count = + DataReader.optString(data, PushTemplateConstants.PushPayloadKeys.BADGE_NUMBER, null) + count?.let { + badgeCount = count.toInt() + } + } catch (e: NumberFormatException) { + Log.debug( + PushTemplateConstants.LOG_TAG, + SELF_TAG, + "Exception in converting notification badge count to int - ${e.localizedMessage}." + ) + } + notificationPriority = NotificationPriority.getNotificationCompatPriorityFromString( + DataReader.optString( + data, + PushTemplateConstants.PushPayloadKeys.NOTIFICATION_PRIORITY, + null + ) + ) + notificationImportance = + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) getNotificationImportanceFromString( + DataReader.optString( + data, + PushTemplateConstants.PushPayloadKeys.NOTIFICATION_PRIORITY, + null + ) + ) else null + + notificationVisibility = NotificationVisibility.getNotificationCompatVisibilityFromString( + DataReader.optString( + data, + PushTemplateConstants.PushPayloadKeys.NOTIFICATION_VISIBILITY, + null + ) + ) + channelId = + DataReader.optString(data, PushTemplateConstants.PushPayloadKeys.CHANNEL_ID, null) + templateType = + DataReader.optString(data, PushTemplateConstants.PushPayloadKeys.TEMPLATE_TYPE, null) + ?.let { PushTemplateType.fromString(it) } + tag = DataReader.optString(data, PushTemplateConstants.PushPayloadKeys.TAG, null) + val stickyValue = + DataReader.optString(data, PushTemplateConstants.PushPayloadKeys.STICKY, null) + isNotificationSticky = + if (stickyValue.isNullOrEmpty()) false else java.lang.Boolean.parseBoolean( + stickyValue + ) + ticker = DataReader.optString(data, PushTemplateConstants.PushPayloadKeys.TICKER, null) + expandedBodyText = DataReader.optString( + data, PushTemplateConstants.PushPayloadKeys.EXPANDED_BODY_TEXT, null + ) + expandedBodyTextColor = DataReader.optString( + data, PushTemplateConstants.PushPayloadKeys.EXPANDED_BODY_TEXT_COLOR, null + ) + titleTextColor = DataReader.optString( + data, PushTemplateConstants.PushPayloadKeys.TITLE_TEXT_COLOR, null + ) + smallIconColor = DataReader.optString( + data, PushTemplateConstants.PushPayloadKeys.SMALL_ICON_COLOR, null + ) + notificationBackgroundColor = DataReader.optString( + data, + PushTemplateConstants.PushPayloadKeys.NOTIFICATION_BACKGROUND_COLOR, + null + ) + isFromIntent = false + } + + /** + * Constructor to create a push template object from an [Intent]. + * + * @param intent [Intent] containing key value pairs extracted from a push template data payload + */ + constructor(intent: Intent) { + val intentExtras = + intent.extras ?: throw IllegalArgumentException("Intent extras are null.") + // required values + title = intentExtras.getString(PushTemplateConstants.IntentKeys.TITLE_TEXT) + ?: throw IllegalArgumentException("Required field \"${PushTemplateConstants.IntentKeys.TITLE_TEXT}\" not found.") + body = intentExtras.getString(PushTemplateConstants.IntentKeys.BODY_TEXT) + ?: throw IllegalArgumentException("Required field \"${PushTemplateConstants.IntentKeys.BODY_TEXT}\" not found.") + payloadVersion = intentExtras.getInt(PushTemplateConstants.IntentKeys.PAYLOAD_VERSION) + payloadVersion?.let { + if (it < 1) throw IllegalArgumentException("Invalid \"${PushTemplateConstants.IntentKeys.PAYLOAD_VERSION}\" found.") + } + + // optional values + sound = intentExtras.getString(PushTemplateConstants.IntentKeys.CUSTOM_SOUND) + imageUrl = intentExtras.getString(PushTemplateConstants.IntentKeys.IMAGE_URI) + actionUri = intentExtras.getString(PushTemplateConstants.IntentKeys.ACTION_URI) + actionType = PushTemplateConstants.ActionType.valueOf( + intentExtras.getString(PushTemplateConstants.IntentKeys.ACTION_TYPE) + ?: PushTemplateConstants.ActionType.NONE.name + ) + smallIcon = intentExtras.getString(PushTemplateConstants.IntentKeys.SMALL_ICON) + largeIcon = intentExtras.getString(PushTemplateConstants.IntentKeys.LARGE_ICON) + badgeCount = intentExtras.getInt(PushTemplateConstants.IntentKeys.BADGE_COUNT) + notificationPriority = intentExtras.getInt(PushTemplateConstants.IntentKeys.PRIORITY) + notificationImportance = intentExtras.getInt(PushTemplateConstants.IntentKeys.IMPORTANCE) + notificationVisibility = intentExtras.getInt(PushTemplateConstants.IntentKeys.VISIBILITY) + channelId = intentExtras.getString(PushTemplateConstants.IntentKeys.CHANNEL_ID) + templateType = + PushTemplateType.fromString(intentExtras.getString(PushTemplateConstants.IntentKeys.TEMPLATE_TYPE)) + tag = intentExtras.getString(PushTemplateConstants.IntentKeys.TAG) + isNotificationSticky = intentExtras.getBoolean(PushTemplateConstants.IntentKeys.STICKY) + ticker = intentExtras.getString(PushTemplateConstants.IntentKeys.TICKER) + expandedBodyText = + intentExtras.getString(PushTemplateConstants.IntentKeys.EXPANDED_BODY_TEXT) + expandedBodyTextColor = + intentExtras.getString(PushTemplateConstants.IntentKeys.EXPANDED_BODY_TEXT_COLOR) + titleTextColor = intentExtras.getString(PushTemplateConstants.IntentKeys.TITLE_TEXT_COLOR) + smallIconColor = intentExtras.getString(PushTemplateConstants.IntentKeys.SMALL_ICON_COLOR) + notificationBackgroundColor = + intentExtras.getString(PushTemplateConstants.IntentKeys.NOTIFICATION_BACKGROUND_COLOR) + isFromIntent = true + } + + fun getNotificationImportance(): Int { + notificationImportance?.let { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) return notificationImportance as Int + } + return NotificationManager.IMPORTANCE_DEFAULT + } + + fun getNotificationVisibility(): Int { + return notificationVisibility ?: NotificationCompat.VISIBILITY_PRIVATE + } + + @RequiresApi(api = Build.VERSION_CODES.N) + private fun getNotificationImportanceFromString(priority: String?): Int { + return if (priority.isNullOrEmpty()) NotificationManager.IMPORTANCE_DEFAULT else notificationImportanceMap[priority] + ?: return NotificationManager.IMPORTANCE_DEFAULT + } + + companion object { + private const val SELF_TAG = "AEPPushTemplate" + + @RequiresApi(api = Build.VERSION_CODES.N) + internal val notificationImportanceMap: Map = mapOf( + NotificationPriority.PRIORITY_MIN.toString() to NotificationManager.IMPORTANCE_MIN, + NotificationPriority.PRIORITY_LOW.toString() to NotificationManager.IMPORTANCE_LOW, + NotificationPriority.PRIORITY_DEFAULT.toString() to NotificationManager.IMPORTANCE_DEFAULT, + NotificationPriority.PRIORITY_HIGH.toString() to NotificationManager.IMPORTANCE_HIGH, + NotificationPriority.PRIORITY_MAX.toString() to NotificationManager.IMPORTANCE_MAX + ) + } +} diff --git a/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/templates/AutoCarouselPushTemplate.kt b/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/templates/AutoCarouselPushTemplate.kt new file mode 100644 index 00000000..4582207f --- /dev/null +++ b/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/templates/AutoCarouselPushTemplate.kt @@ -0,0 +1,19 @@ +/* + Copyright 2024 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.notificationbuilder.templates + +import android.content.Intent + +internal class AutoCarouselPushTemplate : CarouselPushTemplate { + constructor(data: Map) : super(data) + constructor(intent: Intent) : super(intent) +} diff --git a/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/templates/BasicPushTemplate.kt b/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/templates/BasicPushTemplate.kt new file mode 100644 index 00000000..e5cc237f --- /dev/null +++ b/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/templates/BasicPushTemplate.kt @@ -0,0 +1,178 @@ +/* + Copyright 2024 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.notificationbuilder.templates + +import android.content.Intent +import androidx.annotation.VisibleForTesting +import com.adobe.marketing.mobile.notificationbuilder.PushTemplateConstants +import com.adobe.marketing.mobile.services.Log +import com.adobe.marketing.mobile.util.DataReader +import org.json.JSONArray +import org.json.JSONException +import org.json.JSONObject + +internal class BasicPushTemplate : AEPPushTemplate { + + private val SELF_TAG = "BasicPushTemplate" + + /** Class representing the action button with label, link and type */ + class ActionButton(val label: String, val link: String?, type: String?) { + val type: PushTemplateConstants.ActionType + + init { + this.type = try { + PushTemplateConstants.ActionType.valueOf( + type ?: PushTemplateConstants.ActionType.NONE.name + ) + } catch (e: IllegalArgumentException) { + Log.warning( + PushTemplateConstants.LOG_TAG, + SELF_TAG, + "Invalid action button type provided, defaulting to NONE. Error : ${e.localizedMessage}" + ) + PushTemplateConstants.ActionType.NONE + } + } + + companion object { + private const val SELF_TAG = "ActionButton" + + /** + * Converts the json object representing an action button to an [ActionButton]. + * Action button must have a non-empty label, type and uri + * + * @param jsonObject [JSONObject] containing the action button details + * @return an [ActionButton] or null if the conversion fails + */ + fun getActionButtonFromJSONObject(jsonObject: JSONObject): ActionButton? { + return try { + val label = jsonObject.getString(PushTemplateConstants.ActionButtons.LABEL) + if (label.isEmpty()) { + Log.debug( + PushTemplateConstants.LOG_TAG, + SELF_TAG, "Label is empty" + ) + return null + } + var uri: String? = null + val type = jsonObject.getString(PushTemplateConstants.ActionButtons.TYPE) + if (type == PushTemplateConstants.ActionType.WEBURL.name || type == PushTemplateConstants.ActionType.DEEPLINK.name) { + uri = jsonObject.optString(PushTemplateConstants.ActionButtons.URI) + } + Log.trace( + PushTemplateConstants.LOG_TAG, + SELF_TAG, + "Creating an ActionButton with label ($label), uri ($uri), and type ($type)." + ) + ActionButton(label, uri, type) + } catch (e: JSONException) { + Log.warning( + PushTemplateConstants.LOG_TAG, + SELF_TAG, + "Exception in converting actionButtons json string to json object, Error : ${e.localizedMessage}." + ) + null + } + } + } + } + + // Optional, action buttons for the notification + internal var actionButtonsString: String? + private set + + // Optional, list of ActionButton for the notification + internal var actionButtonsList: List? + private set + + // Optional, If present, show a "remind later" button using the value provided as its label + internal var remindLaterText: String? + private set + + // Optional, If present, schedule this notification to be re-delivered at this epoch timestamp (in seconds) provided. + internal var remindLaterEpochTimestamp: Long? + private set + + // Optional, If present, schedule this notification to be re-delivered after this provided time (in seconds). + internal var remindLaterDelaySeconds: Int? + private set + + constructor(data: Map) : super(data) { + actionButtonsString = + DataReader.optString(data, PushTemplateConstants.PushPayloadKeys.ACTION_BUTTONS, null) + actionButtonsList = getActionButtonsFromString(actionButtonsString) + remindLaterText = DataReader.optString( + data, PushTemplateConstants.PushPayloadKeys.REMIND_LATER_TEXT, null + ) + val epochTimestampString = DataReader.optString( + data, PushTemplateConstants.PushPayloadKeys.REMIND_LATER_EPOCH_TIMESTAMP, null + ) + remindLaterEpochTimestamp = + if (epochTimestampString.isNullOrEmpty()) null else epochTimestampString.toLong() + + val delaySeconds = DataReader.optString( + data, + PushTemplateConstants.PushPayloadKeys.REMIND_LATER_DELAY_SECONDS, null + ) + remindLaterDelaySeconds = if (delaySeconds.isNullOrEmpty()) null else delaySeconds.toInt() + } + + constructor(intent: Intent) : super(intent) { + val intentExtras = + intent.extras ?: throw IllegalArgumentException("Intent extras are null") + actionButtonsString = + intentExtras.getString(PushTemplateConstants.IntentKeys.ACTION_BUTTONS_STRING) + actionButtonsList = getActionButtonsFromString(actionButtonsString) + remindLaterEpochTimestamp = + intentExtras.getLong(PushTemplateConstants.IntentKeys.REMIND_EPOCH_TS) + remindLaterDelaySeconds = + intentExtras.getInt(PushTemplateConstants.IntentKeys.REMIND_DELAY_SECONDS) + remindLaterText = + intentExtras.getString(PushTemplateConstants.IntentKeys.REMIND_LABEL) + } + + /** + * Converts the string containing json array of actionButtons to a list of [ActionButton]. + * + * @param actionButtons [String] containing the action buttons json string + * @return a list of [ActionButton] or null if the conversion fails + */ + @VisibleForTesting + internal fun getActionButtonsFromString(actionButtons: String?): List? { + if (actionButtons == null) { + Log.debug( + PushTemplateConstants.LOG_TAG, + SELF_TAG, + "Exception in converting actionButtons json string to json object, Error :" + + " actionButtons is null" + ) + return null + } + val actionButtonList = mutableListOf() + try { + val jsonArray = JSONArray(actionButtons) + for (i in 0 until jsonArray.length()) { + val jsonObject = jsonArray.getJSONObject(i) + val button = ActionButton.getActionButtonFromJSONObject(jsonObject) ?: continue + actionButtonList.add(button) + } + } catch (e: JSONException) { + Log.warning( + PushTemplateConstants.LOG_TAG, + SELF_TAG, + "Exception in converting actionButtons json string to json object, Error : ${e.localizedMessage}" + ) + return null + } + return actionButtonList + } +} diff --git a/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/templates/CarouselPushTemplate.kt b/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/templates/CarouselPushTemplate.kt new file mode 100644 index 00000000..2fc3db07 --- /dev/null +++ b/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/templates/CarouselPushTemplate.kt @@ -0,0 +1,133 @@ +/* + Copyright 2024 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.notificationbuilder.templates + +import android.content.Intent +import com.adobe.marketing.mobile.notificationbuilder.PushTemplateConstants +import com.adobe.marketing.mobile.services.Log +import com.adobe.marketing.mobile.util.DataReader +import org.json.JSONArray +import org.json.JSONException + +internal open class CarouselPushTemplate : AEPPushTemplate { + // Optional, Determines how the carousel will be operated. Valid values are "auto" or "manual". + // Default is "auto". + internal var carouselOperationMode: String + private set + + // Required, One or more Items in the carousel defined by the CarouselItem class + internal var carouselItems = mutableListOf() + private set + + // Required, "default" or "filmstrip" + internal var carouselLayoutType: String + private set + + // Contains the carousel items as a string + internal var rawCarouselItems: String + private set + + data class CarouselItem( + // Required, URI to an image to be shown for the carousel item + val imageUri: String, + // Optional, caption to show when the carousel item is visible + val captionText: String?, + // Optional, URI to handle when the item is touched by the user. If no uri is provided for the item, adb_uri will be handled instead. + val interactionUri: String? + ) + + protected constructor(data: Map) : super(data) { + carouselLayoutType = DataReader.optString( + data, PushTemplateConstants.PushPayloadKeys.CAROUSEL_LAYOUT, null + ) + ?: throw IllegalArgumentException("Required field \"${PushTemplateConstants.PushPayloadKeys.CAROUSEL_LAYOUT}\" not found.") + + rawCarouselItems = DataReader.optString( + data, PushTemplateConstants.PushPayloadKeys.CAROUSEL_ITEMS, null + ) + ?: throw IllegalArgumentException("Required field \"${PushTemplateConstants.PushPayloadKeys.CAROUSEL_ITEMS}\" not found.") + + carouselOperationMode = DataReader.optString( + data, + PushTemplateConstants.PushPayloadKeys.CAROUSEL_OPERATION_MODE, + PushTemplateConstants.DefaultValues.AUTO_CAROUSEL_MODE + ) + carouselItems = parseCarouselItemsFromString(rawCarouselItems) + } + + constructor(intent: Intent) : super(intent) { + val intentExtras = + intent.extras ?: throw IllegalArgumentException("Intent extras are null") + carouselOperationMode = + intentExtras.getString(PushTemplateConstants.IntentKeys.CAROUSEL_OPERATION_MODE) + ?: PushTemplateConstants.DefaultValues.AUTO_CAROUSEL_MODE + carouselLayoutType = + intentExtras.getString(PushTemplateConstants.IntentKeys.CAROUSEL_LAYOUT_TYPE) + ?: PushTemplateConstants.DefaultValues.DEFAULT_MANUAL_CAROUSEL_MODE + rawCarouselItems = + intentExtras.getString(PushTemplateConstants.IntentKeys.CAROUSEL_ITEMS) ?: "" + carouselItems = parseCarouselItemsFromString(rawCarouselItems) + } + + companion object { + private const val SELF_TAG = "CarouselPushTemplate" + + fun createCarouselPushTemplate(data: Map): CarouselPushTemplate { + val carouselOperationMode = DataReader.optString( + data, + PushTemplateConstants.PushPayloadKeys.CAROUSEL_OPERATION_MODE, + PushTemplateConstants.DefaultValues.AUTO_CAROUSEL_MODE + ) + return if (carouselOperationMode == PushTemplateConstants.DefaultValues.AUTO_CAROUSEL_MODE) { + AutoCarouselPushTemplate(data) + } else + ManualCarouselPushTemplate(data) + } + + private fun parseCarouselItemsFromString(carouselItemsString: String?): MutableList { + val carouselItems = mutableListOf() + if (carouselItemsString.isNullOrEmpty()) { + Log.debug( + PushTemplateConstants.LOG_TAG, + SELF_TAG, + "No carousel items found in the push template." + ) + return carouselItems + } + try { + val jsonArray = JSONArray(carouselItemsString) + for (i in 0 until jsonArray.length()) { + val item = jsonArray.getJSONObject(i) + val imageUri = item.getString(PushTemplateConstants.CarouselItemKeys.IMAGE) + val captionText = + item.optString(PushTemplateConstants.CarouselItemKeys.TEXT, "") + val interactionUri = + item.optString(PushTemplateConstants.CarouselItemKeys.URI, "") + carouselItems.add( + CarouselItem( + imageUri, + captionText, + interactionUri + ) + ) + } + } catch (e: JSONException) { + Log.debug( + PushTemplateConstants.LOG_TAG, + SELF_TAG, + "Failed to parse carousel items from the push template: ${e.localizedMessage}" + ) + } + return carouselItems + } + } +} diff --git a/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/templates/InputBoxPushTemplate.kt b/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/templates/InputBoxPushTemplate.kt new file mode 100644 index 00000000..97d1d138 --- /dev/null +++ b/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/templates/InputBoxPushTemplate.kt @@ -0,0 +1,70 @@ +/* + Copyright 2024 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.notificationbuilder.templates + +import android.content.Intent +import com.adobe.marketing.mobile.notificationbuilder.PushTemplateConstants +import com.adobe.marketing.mobile.util.DataReader + +/** + * This class is used to parse the push template data payload or an intent and provide the necessary information + * to build a notification containing an input box. + */ +internal class InputBoxPushTemplate : AEPPushTemplate { + // Required, the intent action name to be used when the user submits the feedback. + internal var inputBoxReceiverName: String + + // Optional, If present, use it as the placeholder text for the text input field. Otherwise, use the default placeholder text of "Reply". + internal var inputTextHint: String? + private set + + // Optional, once feedback has been submitted, use this text as the notification's body + internal var feedbackText: String? + private set + + // Optional, once feedback has been submitted, use this as the notification's image + internal var feedbackImage: String? + private set + + constructor(data: Map) : super(data) { + inputBoxReceiverName = DataReader.optString( + data, PushTemplateConstants.PushPayloadKeys.INPUT_BOX_RECEIVER_NAME, null + ) + ?: throw IllegalArgumentException("Required field \"${PushTemplateConstants.PushPayloadKeys.INPUT_BOX_RECEIVER_NAME}\" not found.") + inputTextHint = DataReader.optString( + data, PushTemplateConstants.PushPayloadKeys.INPUT_BOX_HINT, null + ) + feedbackText = DataReader.optString( + data, PushTemplateConstants.PushPayloadKeys.INPUT_BOX_FEEDBACK_TEXT, null + ) + feedbackImage = DataReader.optString( + data, PushTemplateConstants.PushPayloadKeys.INPUT_BOX_FEEDBACK_IMAGE, null + ) + } + + constructor(intent: Intent) : super(intent) { + val intentExtras = + intent.extras ?: throw IllegalArgumentException("Intent extras are null") + val receiverName = + intentExtras.getString(PushTemplateConstants.IntentKeys.INPUT_BOX_RECEIVER_NAME) + inputBoxReceiverName = receiverName + ?: throw IllegalArgumentException("Required field \"${PushTemplateConstants.IntentKeys.INPUT_BOX_RECEIVER_NAME}\" not found.") + inputTextHint = intentExtras.getString( + PushTemplateConstants.IntentKeys.INPUT_BOX_HINT, + PushTemplateConstants.DefaultValues.INPUT_BOX_DEFAULT_REPLY_TEXT + ) + feedbackText = + intentExtras.getString(PushTemplateConstants.IntentKeys.INPUT_BOX_FEEDBACK_TEXT) + feedbackImage = + intentExtras.getString(PushTemplateConstants.IntentKeys.INPUT_BOX_FEEDBACK_IMAGE) + } +} diff --git a/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/templates/ManualCarouselPushTemplate.kt b/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/templates/ManualCarouselPushTemplate.kt new file mode 100644 index 00000000..27016926 --- /dev/null +++ b/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/templates/ManualCarouselPushTemplate.kt @@ -0,0 +1,43 @@ +/* + Copyright 2024 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.notificationbuilder.templates + +import android.content.Intent +import com.adobe.marketing.mobile.notificationbuilder.PushTemplateConstants + +internal class ManualCarouselPushTemplate : CarouselPushTemplate { + internal var intentAction: String? = null + private set + internal var centerImageIndex: Int = PushTemplateConstants.DefaultValues.NO_CENTER_INDEX_SET + + constructor(data: Map) : super(data) { + centerImageIndex = getDefaultCarouselIndex(carouselLayoutType) + } + + constructor(intent: Intent) : super(intent) { + intentAction = intent.action + centerImageIndex = intent.getIntExtra( + PushTemplateConstants.IntentKeys.CENTER_IMAGE_INDEX, + getDefaultCarouselIndex(carouselLayoutType) + ) + } + + companion object { + private fun getDefaultCarouselIndex(carouselLayoutType: String): Int { + return if (carouselLayoutType == PushTemplateConstants.DefaultValues.FILMSTRIP_CAROUSEL_MODE) { + PushTemplateConstants.DefaultValues.FILMSTRIP_CAROUSEL_CENTER_INDEX + } else { + PushTemplateConstants.DefaultValues.MANUAL_CAROUSEL_START_INDEX + } + } + } +} diff --git a/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/templates/ProductCatalogPushTemplate.kt b/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/templates/ProductCatalogPushTemplate.kt new file mode 100644 index 00000000..5e422d41 --- /dev/null +++ b/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/templates/ProductCatalogPushTemplate.kt @@ -0,0 +1,174 @@ +/* + Copyright 2024 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.notificationbuilder.templates + +import android.content.Intent +import com.adobe.marketing.mobile.notificationbuilder.PushTemplateConstants +import com.adobe.marketing.mobile.services.Log +import com.adobe.marketing.mobile.util.DataReader +import org.json.JSONArray +import org.json.JSONException + +internal class ProductCatalogPushTemplate : AEPPushTemplate { + // Required, Text to be shown on the CTA button + internal var ctaButtonText: String + private set + + // Required, Color for the CTA button. Represented as six character hex, e.g. 00FF00 + internal var ctaButtonColor: String + private set + + // Required, URI to be handled when the user clicks the CTA button + internal var ctaButtonUri: String + private set + + // Required, Determines if the layout of the catalog goes left-to-right or top-to-bottom. + // Value will either be "horizontal" (left-to-right) or "vertical" (top-to-bottom). + internal var displayLayout: String + private set + + // Required, Three entries describing the items in the product catalog. + // The value is an encoded JSON string. + internal var rawCatalogItems: String + private set + + // Required, One or more items in the product catalog defined by the CatalogItem class + internal var catalogItems = mutableListOf() + private set + + internal var currentIndex: Int = PushTemplateConstants.DefaultValues.PRODUCT_CATALOG_START_INDEX + + data class CatalogItem( + // Required, Text to use in the title if this product is selected + val title: String, + + // Required, Text to use in the body if this product is selected + val body: String, + + // Required, URI to an image to use in notification when this product is selected + val img: String, + + // Required, Price of this product to display when the notification is selected + val price: String, + + // Required, URI to be handled when the user clicks the large image of the selected item + val uri: String + ) + + constructor(data: Map) : super(data) { + ctaButtonText = DataReader.optString( + data, PushTemplateConstants.PushPayloadKeys.CATALOG_CTA_BUTTON_TEXT, null + ) + ?: throw IllegalArgumentException("Required field \"${PushTemplateConstants.PushPayloadKeys.CATALOG_CTA_BUTTON_TEXT}\" not found.") + ctaButtonColor = DataReader.optString( + data, PushTemplateConstants.PushPayloadKeys.CATALOG_CTA_BUTTON_COLOR, null + ) + ?: throw IllegalArgumentException("Required field \"${PushTemplateConstants.PushPayloadKeys.CATALOG_CTA_BUTTON_COLOR}\" not found.") + ctaButtonUri = DataReader.optString( + data, PushTemplateConstants.PushPayloadKeys.CATALOG_CTA_BUTTON_URI, null + ) + ?: throw IllegalArgumentException("Required field \"${PushTemplateConstants.PushPayloadKeys.CATALOG_CTA_BUTTON_URI}\" not found.") + displayLayout = DataReader.optString( + data, PushTemplateConstants.PushPayloadKeys.CATALOG_LAYOUT, null + ) + ?: throw IllegalArgumentException("Required field \"${PushTemplateConstants.PushPayloadKeys.CATALOG_LAYOUT}\" not found.") + rawCatalogItems = DataReader.optString( + data, PushTemplateConstants.PushPayloadKeys.CATALOG_ITEMS, null + ) + ?: throw IllegalArgumentException("Required field \"${PushTemplateConstants.PushPayloadKeys.CATALOG_ITEMS}\" not found.") + catalogItems = parseCatalogItemsFromString(rawCatalogItems) + } + + constructor(intent: Intent) : super(intent) { + val buttonText = + intent.getStringExtra(PushTemplateConstants.IntentKeys.CATALOG_CTA_BUTTON_TEXT) + ctaButtonText = buttonText + ?: throw IllegalArgumentException("Required field \"${PushTemplateConstants.IntentKeys.CATALOG_CTA_BUTTON_TEXT}\" not found.") + val buttonColor = + intent.getStringExtra(PushTemplateConstants.IntentKeys.CATALOG_CTA_BUTTON_COLOR) + ctaButtonColor = buttonColor + ?: throw IllegalArgumentException("Required field \"${PushTemplateConstants.IntentKeys.CATALOG_CTA_BUTTON_COLOR}\" not found.") + val buttonUri = + intent.getStringExtra(PushTemplateConstants.IntentKeys.CATALOG_CTA_BUTTON_URI) + ctaButtonUri = buttonUri + ?: throw IllegalArgumentException("Required field \"${PushTemplateConstants.IntentKeys.CATALOG_CTA_BUTTON_URI}\" not found.") + val layout = intent.getStringExtra(PushTemplateConstants.IntentKeys.CATALOG_LAYOUT) + displayLayout = layout + ?: throw IllegalArgumentException("Required field \"${PushTemplateConstants.IntentKeys.CATALOG_LAYOUT}\" not found.") + val items = intent.getStringExtra(PushTemplateConstants.IntentKeys.CATALOG_ITEMS) + rawCatalogItems = items + ?: throw IllegalArgumentException("Required field \"${PushTemplateConstants.IntentKeys.CATALOG_ITEMS}\" not found.") + catalogItems = parseCatalogItemsFromString(rawCatalogItems) + currentIndex = intent.getIntExtra( + PushTemplateConstants.IntentKeys.CATALOG_ITEM_INDEX, + PushTemplateConstants.DefaultValues.PRODUCT_CATALOG_START_INDEX + ) + } + + companion object { + private const val SELF_TAG = "ProductCatalogPushTemplate" + + private fun parseCatalogItemsFromString(catalogItemsString: String?): MutableList { + val catalogItems = mutableListOf() + val jsonArray: JSONArray? + try { + jsonArray = JSONArray(catalogItemsString) + } catch (e: JSONException) { + Log.error( + PushTemplateConstants.LOG_TAG, + SELF_TAG, + "Exception occurred when creating json array from the catalog items string: ${e.localizedMessage}" + ) + throw IllegalArgumentException("Catalog items string containing a valid json array was not found.") + } + + // fast fail if the array is not the expected size + if (jsonArray.length() != 3) { + throw IllegalArgumentException("3 catalog items are required for a Product Catalog notification.") + } + + for (i in 0 until jsonArray.length()) { + try { + val item = jsonArray.getJSONObject(i) + // all values are required for a catalog item. if any are missing we have an invalid catalog item and we know the notification as a whole is invalid + // as three catalog items are required. + val title = item.getString(PushTemplateConstants.CatalogItemKeys.TITLE) + val body = + item.getString(PushTemplateConstants.CatalogItemKeys.BODY) + val image = + item.getString(PushTemplateConstants.CatalogItemKeys.IMAGE) + val price = + item.getString(PushTemplateConstants.CatalogItemKeys.PRICE) + val uri = item.getString(PushTemplateConstants.CatalogItemKeys.URI) + + catalogItems.add( + CatalogItem( + title, + body, + image, + price, + uri + ) + ) + } catch (e: JSONException) { + Log.error( + PushTemplateConstants.LOG_TAG, + SELF_TAG, + "Failed to parse catalog item at index $i: ${e.localizedMessage}" + ) + throw IllegalArgumentException("3 catalog items are required for a Product Catalog notification.") + } + } + return catalogItems + } + } +} diff --git a/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/templates/ZeroBezelPushTemplate.kt b/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/templates/ZeroBezelPushTemplate.kt new file mode 100644 index 00000000..5d949fb0 --- /dev/null +++ b/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/templates/ZeroBezelPushTemplate.kt @@ -0,0 +1,43 @@ +/* + Copyright 2024 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.notificationbuilder.templates + +import com.adobe.marketing.mobile.notificationbuilder.PushTemplateConstants +import com.adobe.marketing.mobile.util.DataReader + +internal class ZeroBezelPushTemplate : AEPPushTemplate { + + internal enum class ZeroBezelStyle(val collapsedStyle: String) { + IMAGE("img"), + TEXT("txt"); + + companion object { + private val zeroBezelStyleMap = values().associateBy { it.collapsedStyle } + internal fun getCollapsedStyleFromString(style: String): ZeroBezelStyle { + return zeroBezelStyleMap[style] ?: TEXT + } + } + } + + internal var collapsedStyle: ZeroBezelStyle + private set + + constructor(data: Map) : super(data) { + collapsedStyle = ZeroBezelStyle.getCollapsedStyleFromString( + DataReader.optString( + data, + PushTemplateConstants.PushPayloadKeys.ZERO_BEZEL_COLLAPSED_STYLE, + "txt" + ) + ) + } +} diff --git a/code/notificationbuilder/src/main/res/drawable/skipleft.png b/code/notificationbuilder/src/main/res/drawable/skipleft.png new file mode 100644 index 0000000000000000000000000000000000000000..214c69fbaccaa4acc68c2bac23cfdba2c18c8c64 GIT binary patch literal 2699 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 + + \ No newline at end of file diff --git a/code/notificationbuilder/src/main/res/layout/push_template_auto_carousel.xml b/code/notificationbuilder/src/main/res/layout/push_template_auto_carousel.xml new file mode 100644 index 00000000..b5561c14 --- /dev/null +++ b/code/notificationbuilder/src/main/res/layout/push_template_auto_carousel.xml @@ -0,0 +1,46 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/code/notificationbuilder/src/main/res/layout/push_template_carousel_item.xml b/code/notificationbuilder/src/main/res/layout/push_template_carousel_item.xml new file mode 100644 index 00000000..f1501f92 --- /dev/null +++ b/code/notificationbuilder/src/main/res/layout/push_template_carousel_item.xml @@ -0,0 +1,27 @@ + + + + + + + diff --git a/code/notificationbuilder/src/main/res/layout/push_template_collapsed.xml b/code/notificationbuilder/src/main/res/layout/push_template_collapsed.xml new file mode 100644 index 00000000..b81e87fc --- /dev/null +++ b/code/notificationbuilder/src/main/res/layout/push_template_collapsed.xml @@ -0,0 +1,34 @@ + + + + + + + + + \ No newline at end of file diff --git a/code/notificationbuilder/src/main/res/layout/push_template_expanded.xml b/code/notificationbuilder/src/main/res/layout/push_template_expanded.xml new file mode 100644 index 00000000..6184fca9 --- /dev/null +++ b/code/notificationbuilder/src/main/res/layout/push_template_expanded.xml @@ -0,0 +1,42 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/code/notificationbuilder/src/main/res/layout/push_template_filmstrip_carousel.xml b/code/notificationbuilder/src/main/res/layout/push_template_filmstrip_carousel.xml new file mode 100644 index 00000000..08c936d6 --- /dev/null +++ b/code/notificationbuilder/src/main/res/layout/push_template_filmstrip_carousel.xml @@ -0,0 +1,97 @@ + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/code/notificationbuilder/src/main/res/layout/push_template_manual_carousel.xml b/code/notificationbuilder/src/main/res/layout/push_template_manual_carousel.xml new file mode 100644 index 00000000..c29905ab --- /dev/null +++ b/code/notificationbuilder/src/main/res/layout/push_template_manual_carousel.xml @@ -0,0 +1,58 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/code/notificationbuilder/src/main/res/layout/push_template_zero_bezel_collapsed.xml b/code/notificationbuilder/src/main/res/layout/push_template_zero_bezel_collapsed.xml new file mode 100644 index 00000000..37d1ea4b --- /dev/null +++ b/code/notificationbuilder/src/main/res/layout/push_template_zero_bezel_collapsed.xml @@ -0,0 +1,50 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/code/notificationbuilder/src/main/res/layout/push_template_zero_bezel_expanded.xml b/code/notificationbuilder/src/main/res/layout/push_template_zero_bezel_expanded.xml new file mode 100644 index 00000000..27372a7a --- /dev/null +++ b/code/notificationbuilder/src/main/res/layout/push_template_zero_bezel_expanded.xml @@ -0,0 +1,51 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/code/notificationbuilder/src/main/res/values/dimens.xml b/code/notificationbuilder/src/main/res/values/dimens.xml new file mode 100644 index 00000000..5c641ddd --- /dev/null +++ b/code/notificationbuilder/src/main/res/values/dimens.xml @@ -0,0 +1,20 @@ + + + 35dp + 35dp + 200dp + 200dp + 250dp + 20dp + 15dp + 200dp + 200dp + 175dp + 100dp + 2dp + 15dp + 16dp + 3dp + 3dp + 3dp + \ No newline at end of file diff --git a/code/notificationbuilder/src/main/res/values/styles.xml b/code/notificationbuilder/src/main/res/values/styles.xml new file mode 100644 index 00000000..2b86ed3a --- /dev/null +++ b/code/notificationbuilder/src/main/res/values/styles.xml @@ -0,0 +1,30 @@ + + + + + + + \ No newline at end of file diff --git a/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/AEPPushTemplateTests.kt b/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/AEPPushTemplateTests.kt new file mode 100644 index 00000000..fba9ae35 --- /dev/null +++ b/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/AEPPushTemplateTests.kt @@ -0,0 +1,353 @@ +/* + Copyright 2024 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.notificationbuilder + +import com.adobe.marketing.mobile.notificationbuilder.templates.AEPPushTemplate +import org.junit.Before +import org.mockito.MockitoAnnotations + +class AEPPushTemplateTests { + private lateinit var aepPushTemplate: AEPPushTemplate + private lateinit var messageData: HashMap + private lateinit var basicMessageData: HashMap + + @Before + fun setUp() { + MockitoAnnotations.openMocks(this) + messageData = hashMapOf( + PushTemplateConstants.PushPayloadKeys.TAG to "notificationTag", + PushTemplateConstants.PushPayloadKeys.TEMPLATE_TYPE to "basic", + PushTemplateConstants.PushPayloadKeys.ACTION_URI to "actionUri", + PushTemplateConstants.PushPayloadKeys.ACC_PAYLOAD_BODY to "accPayloadBody", + PushTemplateConstants.PushPayloadKeys.ACTION_TYPE to "actionType", + PushTemplateConstants.PushPayloadKeys.ACTION_BUTTONS to "[{\"label\":\"Go to chess.com\",\"uri\":\"https://chess.com/games/552\",\"type\":\"DEEPLINK\"},{\"label\":\"Open the app\",\"uri\":\"\",\"type\":\"OPENAPP\"}]", + PushTemplateConstants.PushPayloadKeys.BADGE_NUMBER to "5", + PushTemplateConstants.PushPayloadKeys.BODY to "body", + PushTemplateConstants.PushPayloadKeys.CHANNEL_ID to "channelId", + PushTemplateConstants.PushPayloadKeys.EXPANDED_BODY_TEXT to "expandedBodyText", + PushTemplateConstants.PushPayloadKeys.EXPANDED_BODY_TEXT_COLOR to "FFD966", + PushTemplateConstants.PushPayloadKeys.IMAGE_URL to "imageUrl", + PushTemplateConstants.PushPayloadKeys.LARGE_ICON to "largeIcon", + PushTemplateConstants.PushPayloadKeys.NOTIFICATION_BACKGROUND_COLOR to "FFD966", + PushTemplateConstants.PushPayloadKeys.NOTIFICATION_PRIORITY to "PRIORITY_HIGH", + PushTemplateConstants.PushPayloadKeys.NOTIFICATION_VISIBILITY to "PUBLIC", + PushTemplateConstants.PushPayloadKeys.REMIND_LATER_TEXT to "remind me", + PushTemplateConstants.PushPayloadKeys.REMIND_LATER_EPOCH_TIMESTAMP to "1234567890", + PushTemplateConstants.PushPayloadKeys.SOUND to "bell", + PushTemplateConstants.PushPayloadKeys.SMALL_ICON to "notificationIcon", + PushTemplateConstants.PushPayloadKeys.SMALL_ICON_COLOR to "FFD966", + PushTemplateConstants.PushPayloadKeys.TITLE to "title", + PushTemplateConstants.PushPayloadKeys.TITLE_TEXT_COLOR to "FFD966", + PushTemplateConstants.PushPayloadKeys.TICKER to "ticker", + PushTemplateConstants.PushPayloadKeys.VERSION to "1", + PushTemplateConstants.PushPayloadKeys.STICKY to "true" + ) + } + +// @Test +// fun `Test create BasicPushTemplate with complete message data`() { +// aepPushTemplate = BasicPushTemplate(messageData) +// assertNotNull(aepPushTemplate) +// assertEquals("messageId", aepPushTemplate.getMessageId()) +// assertEquals("deliveryId", aepPushTemplate.getDeliveryId()) +// assertEquals("notificationTag", aepPushTemplate.getTag()) +// assertEquals("channelId", aepPushTemplate.getChannelId()) +// assertEquals("title", aepPushTemplate.getTitle()) +// assertEquals("body", aepPushTemplate.getBody()) +// assertEquals("actionUri", aepPushTemplate.getActionUri()) +// assertEquals( +// "[{\"label\":\"Go to chess.com\",\"uri\":\"https://chess.com/games/552\",\"type\":\"DEEPLINK\"},{\"label\":\"Open the app\",\"uri\":\"\",\"type\":\"OPENAPP\"}]", +// aepPushTemplate.getActionButtons() +// ) +// assertEquals("expandedBodyText", aepPushTemplate.getExpandedBodyText()) +// assertEquals("FFD966", aepPushTemplate.getExpandedBodyTextColor()) +// assertEquals("FFD966", aepPushTemplate.getNotificationBackgroundColor()) +// assertEquals("FFD966", aepPushTemplate.getTitleTextColor()) +// assertEquals("notificationIcon", aepPushTemplate.getSmallIcon()) +// assertEquals("FFD966", aepPushTemplate.getSmallIconColor()) +// assertEquals("imageUrl", aepPushTemplate.getImageUrl()) +// assertEquals("largeIcon", aepPushTemplate.getLargeIcon()) +// assertEquals("ticker", aepPushTemplate.getTicker()) +// assertEquals("remind me", aepPushTemplate.getRemindLaterText()) +// assertEquals(1234567890, aepPushTemplate.getRemindLaterTimestamp()) +// assertEquals(5, aepPushTemplate.getBadgeCount()) +// assertEquals(true, aepPushTemplate.getStickyStatus()) +// assertEquals("bell", aepPushTemplate.getSound()) +// // TODO: disabled this assert as Build.VERSION returns 0 in unit tests +// // assertEquals( +// // NotificationCompat.PRIORITY_HIGH, +// // aepPushTemplate.getNotificationImportance() +// // ) +// assertEquals( +// NotificationCompat.VISIBILITY_PUBLIC, +// aepPushTemplate.getNotificationVisibility() +// ) +// } +// +// @Test +// fun `Test create BasicPushTemplate with no small icon key then legacy icon key used`() { +// messageData.remove(PushTemplateConstants.PushPayloadKeys.SMALL_ICON) +// messageData[PushTemplateConstants.PushPayloadKeys.LEGACY_SMALL_ICON] = "legacy_icon" +// aepPushTemplate = BasicPushTemplate(messageData) +// assertNotNull(aepPushTemplate) +// assertEquals("messageId", aepPushTemplate.getMessageId()) +// assertEquals("deliveryId", aepPushTemplate.getDeliveryId()) +// assertEquals("notificationTag", aepPushTemplate.getTag()) +// assertEquals("channelId", aepPushTemplate.getChannelId()) +// assertEquals("title", aepPushTemplate.getTitle()) +// assertEquals("body", aepPushTemplate.getBody()) +// assertEquals("actionUri", aepPushTemplate.getActionUri()) +// assertEquals( +// "[{\"label\":\"Go to chess.com\",\"uri\":\"https://chess.com/games/552\",\"type\":\"DEEPLINK\"},{\"label\":\"Open the app\",\"uri\":\"\",\"type\":\"OPENAPP\"}]", +// aepPushTemplate.getActionButtons() +// ) +// assertEquals("expandedBodyText", aepPushTemplate.getExpandedBodyText()) +// assertEquals("FFD966", aepPushTemplate.getExpandedBodyTextColor()) +// assertEquals("FFD966", aepPushTemplate.getNotificationBackgroundColor()) +// assertEquals("FFD966", aepPushTemplate.getTitleTextColor()) +// assertEquals("legacy_icon", aepPushTemplate.getSmallIcon()) +// assertEquals("FFD966", aepPushTemplate.getSmallIconColor()) +// assertEquals("imageUrl", aepPushTemplate.getImageUrl()) +// assertEquals("largeIcon", aepPushTemplate.getLargeIcon()) +// assertEquals("ticker", aepPushTemplate.getTicker()) +// assertEquals("remind me", aepPushTemplate.getRemindLaterText()) +// assertEquals(1234567890, aepPushTemplate.getRemindLaterTimestamp()) +// assertEquals(5, aepPushTemplate.getBadgeCount()) +// assertEquals(true, aepPushTemplate.getStickyStatus()) +// assertEquals("bell", aepPushTemplate.getSound()) +// assertEquals( +// NotificationCompat.VISIBILITY_PUBLIC, +// aepPushTemplate.getNotificationVisibility() +// ) +// } +// +// @Test +// fun `Test create BasicPushTemplate with no message id key`() { +// messageData.remove(PushTemplateConstants.Tracking.Keys.MESSAGE_ID) +// assertFailsWith { +// BasicPushTemplate(messageData) +// } +// } +// +// @Test +// fun `Test create BasicPushTemplate with empty message id value`() { +// messageData[PushTemplateConstants.Tracking.Keys.MESSAGE_ID] = "" +// assertFailsWith { +// BasicPushTemplate(messageData) +// } +// } +// +// @Test +// fun `Test create BasicPushTemplate with no delivery id key`() { +// messageData.remove(PushTemplateConstants.Tracking.Keys.DELIVERY_ID) +// assertFailsWith { +// BasicPushTemplate(messageData) +// } +// } +// +// @Test +// fun `Test create BasicPushTemplate with empty delivery id value`() { +// messageData[PushTemplateConstants.Tracking.Keys.DELIVERY_ID] = "" +// assertFailsWith { +// BasicPushTemplate(messageData) +// } +// } +// +// @Test +// fun `Test create BasicPushTemplate with no title key`() { +// messageData.remove(PushTemplateConstants.PushPayloadKeys.TITLE) +// assertFailsWith { +// BasicPushTemplate(messageData) +// } +// } +// +// @Test +// fun `Test create BasicPushTemplate with empty title value`() { +// messageData[PushTemplateConstants.PushPayloadKeys.TITLE] = "" +// assertFailsWith { +// BasicPushTemplate(messageData) +// } +// } +// +// @Test +// fun `Test create BasicPushTemplate with no body or acc body key`() { +// messageData.remove(PushTemplateConstants.PushPayloadKeys.BODY) +// messageData.remove(PushTemplateConstants.PushPayloadKeys.ACC_PAYLOAD_BODY) +// assertFailsWith { +// BasicPushTemplate(messageData) +// } +// } +// +// @Test +// fun `Test create BasicPushTemplate with no body key then acc body value used`() { +// messageData.remove(PushTemplateConstants.PushPayloadKeys.BODY) +// aepPushTemplate = BasicPushTemplate(messageData) +// assertNotNull(aepPushTemplate) +// assertEquals("messageId", aepPushTemplate.getMessageId()) +// assertEquals("deliveryId", aepPushTemplate.getDeliveryId()) +// assertEquals("notificationTag", aepPushTemplate.getTag()) +// assertEquals("channelId", aepPushTemplate.getChannelId()) +// assertEquals("title", aepPushTemplate.getTitle()) +// assertEquals("accPayloadBody", aepPushTemplate.getBody()) +// assertEquals("actionUri", aepPushTemplate.getActionUri()) +// assertEquals( +// "[{\"label\":\"Go to chess.com\",\"uri\":\"https://chess.com/games/552\",\"type\":\"DEEPLINK\"},{\"label\":\"Open the app\",\"uri\":\"\",\"type\":\"OPENAPP\"}]", +// aepPushTemplate.getActionButtons() +// ) +// assertEquals("expandedBodyText", aepPushTemplate.getExpandedBodyText()) +// assertEquals("FFD966", aepPushTemplate.getExpandedBodyTextColor()) +// assertEquals("FFD966", aepPushTemplate.getNotificationBackgroundColor()) +// assertEquals("FFD966", aepPushTemplate.getTitleTextColor()) +// assertEquals("notificationIcon", aepPushTemplate.getSmallIcon()) +// assertEquals("FFD966", aepPushTemplate.getSmallIconColor()) +// assertEquals("imageUrl", aepPushTemplate.getImageUrl()) +// assertEquals("largeIcon", aepPushTemplate.getLargeIcon()) +// assertEquals("ticker", aepPushTemplate.getTicker()) +// assertEquals("remind me", aepPushTemplate.getRemindLaterText()) +// assertEquals(1234567890, aepPushTemplate.getRemindLaterTimestamp()) +// assertEquals(5, aepPushTemplate.getBadgeCount()) +// assertEquals(true, aepPushTemplate.getStickyStatus()) +// assertEquals("bell", aepPushTemplate.getSound()) +// assertEquals( +// NotificationCompat.VISIBILITY_PUBLIC, +// aepPushTemplate.getNotificationVisibility() +// ) +// } +// +// @Test +// fun `Test create BasicPushTemplate with empty body value then acc body value used`() { +// messageData[PushTemplateConstants.PushPayloadKeys.BODY] = "" +// aepPushTemplate = BasicPushTemplate(messageData) +// assertNotNull(aepPushTemplate) +// assertEquals("messageId", aepPushTemplate.getMessageId()) +// assertEquals("deliveryId", aepPushTemplate.getDeliveryId()) +// assertEquals("notificationTag", aepPushTemplate.getTag()) +// assertEquals("channelId", aepPushTemplate.getChannelId()) +// assertEquals("title", aepPushTemplate.getTitle()) +// assertEquals("accPayloadBody", aepPushTemplate.getBody()) +// assertEquals("actionUri", aepPushTemplate.getActionUri()) +// assertEquals( +// "[{\"label\":\"Go to chess.com\",\"uri\":\"https://chess.com/games/552\",\"type\":\"DEEPLINK\"},{\"label\":\"Open the app\",\"uri\":\"\",\"type\":\"OPENAPP\"}]", +// aepPushTemplate.getActionButtons() +// ) +// assertEquals("expandedBodyText", aepPushTemplate.getExpandedBodyText()) +// assertEquals("FFD966", aepPushTemplate.getExpandedBodyTextColor()) +// assertEquals("FFD966", aepPushTemplate.getNotificationBackgroundColor()) +// assertEquals("FFD966", aepPushTemplate.getTitleTextColor()) +// assertEquals("notificationIcon", aepPushTemplate.getSmallIcon()) +// assertEquals("FFD966", aepPushTemplate.getSmallIconColor()) +// assertEquals("imageUrl", aepPushTemplate.getImageUrl()) +// assertEquals("largeIcon", aepPushTemplate.getLargeIcon()) +// assertEquals("ticker", aepPushTemplate.getTicker()) +// assertEquals("remind me", aepPushTemplate.getRemindLaterText()) +// assertEquals(1234567890, aepPushTemplate.getRemindLaterTimestamp()) +// assertEquals(5, aepPushTemplate.getBadgeCount()) +// assertEquals(true, aepPushTemplate.getStickyStatus()) +// assertEquals("bell", aepPushTemplate.getSound()) +// assertEquals( +// NotificationCompat.VISIBILITY_PUBLIC, +// aepPushTemplate.getNotificationVisibility() +// ) +// } +// +// @Test +// fun `Test create BasicPushTemplate with no version key`() { +// messageData.remove(PushTemplateConstants.PushPayloadKeys.VERSION) +// assertFailsWith { +// BasicPushTemplate(messageData) +// } +// } +// +// @Test +// fun `Test create BasicPushTemplate with empty version value`() { +// messageData.set(PushTemplateConstants.PushPayloadKeys.VERSION, "") +// assertFailsWith { +// BasicPushTemplate(messageData) +// } +// } +// +// @Test +// fun `Test create BasicPushTemplate with invalid badge number then badge number is 0`() { +// messageData[PushTemplateConstants.PushPayloadKeys.BADGE_NUMBER] = "invalid" +// aepPushTemplate = BasicPushTemplate(messageData) +// assertNotNull(aepPushTemplate) +// assertEquals("messageId", aepPushTemplate.getMessageId()) +// assertEquals("deliveryId", aepPushTemplate.getDeliveryId()) +// assertEquals("notificationTag", aepPushTemplate.getTag()) +// assertEquals("channelId", aepPushTemplate.getChannelId()) +// assertEquals("title", aepPushTemplate.getTitle()) +// assertEquals("body", aepPushTemplate.getBody()) +// assertEquals("actionUri", aepPushTemplate.getActionUri()) +// assertEquals( +// "[{\"label\":\"Go to chess.com\",\"uri\":\"https://chess.com/games/552\",\"type\":\"DEEPLINK\"},{\"label\":\"Open the app\",\"uri\":\"\",\"type\":\"OPENAPP\"}]", +// aepPushTemplate.getActionButtons() +// ) +// assertEquals("expandedBodyText", aepPushTemplate.getExpandedBodyText()) +// assertEquals("FFD966", aepPushTemplate.getExpandedBodyTextColor()) +// assertEquals("FFD966", aepPushTemplate.getNotificationBackgroundColor()) +// assertEquals("FFD966", aepPushTemplate.getTitleTextColor()) +// assertEquals("notificationIcon", aepPushTemplate.getSmallIcon()) +// assertEquals("FFD966", aepPushTemplate.getSmallIconColor()) +// assertEquals("imageUrl", aepPushTemplate.getImageUrl()) +// assertEquals("largeIcon", aepPushTemplate.getLargeIcon()) +// assertEquals("ticker", aepPushTemplate.getTicker()) +// assertEquals("remind me", aepPushTemplate.getRemindLaterText()) +// assertEquals(1234567890, aepPushTemplate.getRemindLaterTimestamp()) +// assertEquals(0, aepPushTemplate.getBadgeCount()) +// assertEquals(true, aepPushTemplate.getStickyStatus()) +// assertEquals("bell", aepPushTemplate.getSound()) +// assertEquals( +// NotificationCompat.VISIBILITY_PUBLIC, +// aepPushTemplate.getNotificationVisibility() +// ) +// } +// +// @Test +// fun `Test get action buttons from BasicPushTemplate with complete message data`() { +// aepPushTemplate = BasicPushTemplate(messageData) +// assertNotNull(aepPushTemplate) +// assertEquals("messageId", aepPushTemplate.getMessageId()) +// assertEquals("deliveryId", aepPushTemplate.getDeliveryId()) +// assertEquals("notificationTag", aepPushTemplate.getTag()) +// assertEquals("channelId", aepPushTemplate.getChannelId()) +// assertEquals("title", aepPushTemplate.getTitle()) +// assertEquals("body", aepPushTemplate.getBody()) +// assertEquals("actionUri", aepPushTemplate.getActionUri()) +// assertEquals( +// "[{\"label\":\"Go to chess.com\",\"uri\":\"https://chess.com/games/552\",\"type\":\"DEEPLINK\"},{\"label\":\"Open the app\",\"uri\":\"\",\"type\":\"OPENAPP\"}]", +// aepPushTemplate.getActionButtons() +// ) +// assertEquals("expandedBodyText", aepPushTemplate.getExpandedBodyText()) +// assertEquals("FFD966", aepPushTemplate.getExpandedBodyTextColor()) +// assertEquals("FFD966", aepPushTemplate.getNotificationBackgroundColor()) +// assertEquals("FFD966", aepPushTemplate.getTitleTextColor()) +// assertEquals("notificationIcon", aepPushTemplate.getSmallIcon()) +// assertEquals("FFD966", aepPushTemplate.getSmallIconColor()) +// assertEquals("imageUrl", aepPushTemplate.getImageUrl()) +// assertEquals("largeIcon", aepPushTemplate.getLargeIcon()) +// assertEquals("ticker", aepPushTemplate.getTicker()) +// assertEquals("remind me", aepPushTemplate.getRemindLaterText()) +// assertEquals(1234567890, aepPushTemplate.getRemindLaterTimestamp()) +// assertEquals(5, aepPushTemplate.getBadgeCount()) +// assertEquals(true, aepPushTemplate.getStickyStatus()) +// assertEquals("bell", aepPushTemplate.getSound()) +// // TODO: disabled this assert as Build.VERSION returns 0 in unit tests +// // assertEquals( +// // NotificationCompat.PRIORITY_HIGH, +// // aepPushTemplate.getNotificationImportance() +// // ) +// assertEquals( +// NotificationCompat.VISIBILITY_PUBLIC, +// aepPushTemplate.getNotificationVisibility() +// ) +// } +} diff --git a/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/PushTemplateTypeTest.kt b/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/PushTemplateTypeTest.kt new file mode 100644 index 00000000..73f2cd9e --- /dev/null +++ b/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/PushTemplateTypeTest.kt @@ -0,0 +1,30 @@ +/* + Copyright 2024 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.notificationbuilder + +import org.junit.Test +import kotlin.test.assertEquals + +class PushTemplateTypeTest { + + @Test + fun `test for fromString for each of the enum values`() { + PushTemplateType.values().forEach { value -> + assertEquals(value, PushTemplateType.fromString(value.value)) + } + } + + @Test + fun `test to ensure that invalid values map to unknown value`() { + assertEquals(PushTemplateType.UNKNOWN, PushTemplateType.fromString("SomeRandomValue")) + } +} From 89ca874b0ff6f656394de3d0789cec745073d378 Mon Sep 17 00:00:00 2001 From: Ryan Morales Date: Fri, 17 May 2024 14:11:56 -0700 Subject: [PATCH 015/159] move non public classes to "internal" package --- .../NotificationBuilder.kt | 29 ++++++++++--------- ...NotificationConstructionFailedException.kt | 2 +- .../{ => internal}/NotificationPriority.kt | 2 +- .../{ => internal}/NotificationVisibility.kt | 2 +- .../{ => internal}/PendingIntentUtils.kt | 2 +- .../{ => internal}/PushTemplateConstants.kt | 2 +- .../{ => internal}/PushTemplateImageUtils.kt | 6 ++-- .../{ => internal}/PushTemplateType.kt | 2 +- .../builders/AEPPushNotificationBuilder.kt | 24 +++++++-------- .../AutoCarouselNotificationBuilder.kt | 14 ++++----- .../builders/BasicNotificationBuilder.kt | 14 ++++----- .../builders/InputBoxNotificationBuilder.kt | 12 ++++---- .../builders/LegacyNotificationBuilder.kt | 20 ++++++------- .../ManualCarouselNotificationBuilder.kt | 16 +++++----- .../builders/ZeroBezelNotificationBuilder.kt | 12 ++++---- .../extensions/AppResourceExtensions.kt | 4 +-- .../NotificationCompatBuilderExtensions.kt | 12 ++++---- .../NotificationManagerExtensions.kt | 4 +-- .../extensions/RemoteViewsExtensions.kt | 8 ++--- .../templates/AEPPushTemplate.kt | 10 +++---- .../templates/AutoCarouselPushTemplate.kt | 2 +- .../templates/BasicPushTemplate.kt | 4 +-- .../templates/CarouselPushTemplate.kt | 4 +-- .../templates/InputBoxPushTemplate.kt | 4 +-- .../templates/ManualCarouselPushTemplate.kt | 4 +-- .../templates/ProductCatalogPushTemplate.kt | 4 +-- .../templates/ZeroBezelPushTemplate.kt | 4 +-- .../{ => internal}/AEPPushTemplateTests.kt | 4 +-- .../NotificationBuilderTests.java | 4 ++- .../{ => internal}/PushTemplateTypeTest.kt | 2 +- 30 files changed, 119 insertions(+), 114 deletions(-) rename code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/{ => internal}/NotificationConstructionFailedException.kt (92%) rename code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/{ => internal}/NotificationPriority.kt (97%) rename code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/{ => internal}/NotificationVisibility.kt (97%) rename code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/{ => internal}/PendingIntentUtils.kt (97%) rename code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/{ => internal}/PushTemplateConstants.kt (99%) rename code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/{ => internal}/PushTemplateImageUtils.kt (98%) rename code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/{ => internal}/PushTemplateType.kt (95%) rename code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/{ => internal}/builders/AEPPushNotificationBuilder.kt (79%) rename code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/{ => internal}/builders/AutoCarouselNotificationBuilder.kt (91%) rename code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/{ => internal}/builders/BasicNotificationBuilder.kt (93%) rename code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/{ => internal}/builders/InputBoxNotificationBuilder.kt (95%) rename code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/{ => internal}/builders/LegacyNotificationBuilder.kt (80%) rename code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/{ => internal}/builders/ManualCarouselNotificationBuilder.kt (96%) rename code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/{ => internal}/builders/ZeroBezelNotificationBuilder.kt (87%) rename code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/{ => internal}/extensions/AppResourceExtensions.kt (93%) rename code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/{ => internal}/extensions/NotificationCompatBuilderExtensions.kt (95%) rename code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/{ => internal}/extensions/NotificationManagerExtensions.kt (95%) rename code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/{ => internal}/extensions/RemoteViewsExtensions.kt (96%) rename code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/{ => internal}/templates/AEPPushTemplate.kt (97%) rename code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/{ => internal}/templates/AutoCarouselPushTemplate.kt (91%) rename code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/{ => internal}/templates/BasicPushTemplate.kt (97%) rename code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/{ => internal}/templates/CarouselPushTemplate.kt (97%) rename code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/{ => internal}/templates/InputBoxPushTemplate.kt (95%) rename code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/{ => internal}/templates/ManualCarouselPushTemplate.kt (91%) rename code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/{ => internal}/templates/ProductCatalogPushTemplate.kt (98%) rename code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/{ => internal}/templates/ZeroBezelPushTemplate.kt (90%) rename code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/{ => internal}/AEPPushTemplateTests.kt (99%) rename code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/{ => internal}/NotificationBuilderTests.java (85%) rename code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/{ => internal}/PushTemplateTypeTest.kt (94%) diff --git a/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/NotificationBuilder.kt b/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/NotificationBuilder.kt index 2f7ae21e..346580f8 100644 --- a/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/NotificationBuilder.kt +++ b/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/NotificationBuilder.kt @@ -15,19 +15,22 @@ import android.app.Activity import android.content.BroadcastReceiver import android.content.Intent import androidx.core.app.NotificationCompat -import com.adobe.marketing.mobile.notificationbuilder.builders.AutoCarouselNotificationBuilder -import com.adobe.marketing.mobile.notificationbuilder.builders.BasicNotificationBuilder -import com.adobe.marketing.mobile.notificationbuilder.builders.InputBoxNotificationBuilder -import com.adobe.marketing.mobile.notificationbuilder.builders.LegacyNotificationBuilder -import com.adobe.marketing.mobile.notificationbuilder.builders.ManualCarouselNotificationBuilder -import com.adobe.marketing.mobile.notificationbuilder.builders.ZeroBezelNotificationBuilder -import com.adobe.marketing.mobile.notificationbuilder.templates.AEPPushTemplate -import com.adobe.marketing.mobile.notificationbuilder.templates.AutoCarouselPushTemplate -import com.adobe.marketing.mobile.notificationbuilder.templates.BasicPushTemplate -import com.adobe.marketing.mobile.notificationbuilder.templates.CarouselPushTemplate -import com.adobe.marketing.mobile.notificationbuilder.templates.InputBoxPushTemplate -import com.adobe.marketing.mobile.notificationbuilder.templates.ManualCarouselPushTemplate -import com.adobe.marketing.mobile.notificationbuilder.templates.ZeroBezelPushTemplate +import com.adobe.marketing.mobile.notificationbuilder.internal.NotificationConstructionFailedException +import com.adobe.marketing.mobile.notificationbuilder.internal.PushTemplateConstants +import com.adobe.marketing.mobile.notificationbuilder.internal.PushTemplateType +import com.adobe.marketing.mobile.notificationbuilder.internal.builders.AutoCarouselNotificationBuilder +import com.adobe.marketing.mobile.notificationbuilder.internal.builders.BasicNotificationBuilder +import com.adobe.marketing.mobile.notificationbuilder.internal.builders.InputBoxNotificationBuilder +import com.adobe.marketing.mobile.notificationbuilder.internal.builders.LegacyNotificationBuilder +import com.adobe.marketing.mobile.notificationbuilder.internal.builders.ManualCarouselNotificationBuilder +import com.adobe.marketing.mobile.notificationbuilder.internal.builders.ZeroBezelNotificationBuilder +import com.adobe.marketing.mobile.notificationbuilder.internal.templates.AEPPushTemplate +import com.adobe.marketing.mobile.notificationbuilder.internal.templates.AutoCarouselPushTemplate +import com.adobe.marketing.mobile.notificationbuilder.internal.templates.BasicPushTemplate +import com.adobe.marketing.mobile.notificationbuilder.internal.templates.CarouselPushTemplate +import com.adobe.marketing.mobile.notificationbuilder.internal.templates.InputBoxPushTemplate +import com.adobe.marketing.mobile.notificationbuilder.internal.templates.ManualCarouselPushTemplate +import com.adobe.marketing.mobile.notificationbuilder.internal.templates.ZeroBezelPushTemplate import com.adobe.marketing.mobile.services.Log import com.adobe.marketing.mobile.services.ServiceProvider diff --git a/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/NotificationConstructionFailedException.kt b/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/NotificationConstructionFailedException.kt similarity index 92% rename from code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/NotificationConstructionFailedException.kt rename to code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/NotificationConstructionFailedException.kt index 79662e13..b456acb2 100644 --- a/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/NotificationConstructionFailedException.kt +++ b/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/NotificationConstructionFailedException.kt @@ -9,7 +9,7 @@ governing permissions and limitations under the License. */ -package com.adobe.marketing.mobile.notificationbuilder +package com.adobe.marketing.mobile.notificationbuilder.internal /** * Exception indicating that construction of a push notification failed. diff --git a/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/NotificationPriority.kt b/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/NotificationPriority.kt similarity index 97% rename from code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/NotificationPriority.kt rename to code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/NotificationPriority.kt index cf9623c8..ba117654 100644 --- a/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/NotificationPriority.kt +++ b/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/NotificationPriority.kt @@ -9,7 +9,7 @@ governing permissions and limitations under the License. */ -package com.adobe.marketing.mobile.notificationbuilder +package com.adobe.marketing.mobile.notificationbuilder.internal import androidx.core.app.NotificationCompat diff --git a/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/NotificationVisibility.kt b/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/NotificationVisibility.kt similarity index 97% rename from code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/NotificationVisibility.kt rename to code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/NotificationVisibility.kt index ac91d475..ed5fa47f 100644 --- a/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/NotificationVisibility.kt +++ b/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/NotificationVisibility.kt @@ -9,7 +9,7 @@ governing permissions and limitations under the License. */ -package com.adobe.marketing.mobile.notificationbuilder +package com.adobe.marketing.mobile.notificationbuilder.internal import androidx.core.app.NotificationCompat diff --git a/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/PendingIntentUtils.kt b/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/PendingIntentUtils.kt similarity index 97% rename from code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/PendingIntentUtils.kt rename to code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/PendingIntentUtils.kt index c0203e6b..c656f9a0 100644 --- a/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/PendingIntentUtils.kt +++ b/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/PendingIntentUtils.kt @@ -9,7 +9,7 @@ governing permissions and limitations under the License. */ -package com.adobe.marketing.mobile.notificationbuilder +package com.adobe.marketing.mobile.notificationbuilder.internal import android.app.Activity import android.app.PendingIntent diff --git a/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/PushTemplateConstants.kt b/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/PushTemplateConstants.kt similarity index 99% rename from code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/PushTemplateConstants.kt rename to code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/PushTemplateConstants.kt index 741b34bd..87bb6839 100644 --- a/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/PushTemplateConstants.kt +++ b/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/PushTemplateConstants.kt @@ -9,7 +9,7 @@ governing permissions and limitations under the License. */ -package com.adobe.marketing.mobile.notificationbuilder +package com.adobe.marketing.mobile.notificationbuilder.internal import java.util.concurrent.TimeUnit diff --git a/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/PushTemplateImageUtils.kt b/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/PushTemplateImageUtils.kt similarity index 98% rename from code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/PushTemplateImageUtils.kt rename to code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/PushTemplateImageUtils.kt index cf3bcedd..713409b2 100644 --- a/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/PushTemplateImageUtils.kt +++ b/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/PushTemplateImageUtils.kt @@ -9,7 +9,7 @@ governing permissions and limitations under the License. */ -package com.adobe.marketing.mobile.notificationbuilder +package com.adobe.marketing.mobile.notificationbuilder.internal import android.graphics.Bitmap import android.graphics.BitmapFactory @@ -307,9 +307,9 @@ internal object PushTemplateImageUtils { ( applicationCacheDir .toString() + File.separator + - PushTemplateConstants.CACHE_BASE_DIR + PushTemplateConstants.CACHE_BASE_DIR ) + File.separator + - PushTemplateConstants.PUSH_IMAGE_CACHE + PushTemplateConstants.PUSH_IMAGE_CACHE ) } } diff --git a/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/PushTemplateType.kt b/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/PushTemplateType.kt similarity index 95% rename from code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/PushTemplateType.kt rename to code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/PushTemplateType.kt index a8478636..35b023ee 100644 --- a/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/PushTemplateType.kt +++ b/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/PushTemplateType.kt @@ -9,7 +9,7 @@ governing permissions and limitations under the License. */ -package com.adobe.marketing.mobile.notificationbuilder +package com.adobe.marketing.mobile.notificationbuilder.internal /** * Enum class representing the different types of out-of-the-box push templates. diff --git a/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/builders/AEPPushNotificationBuilder.kt b/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/AEPPushNotificationBuilder.kt similarity index 79% rename from code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/builders/AEPPushNotificationBuilder.kt rename to code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/AEPPushNotificationBuilder.kt index ef54b257..af440062 100644 --- a/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/builders/AEPPushNotificationBuilder.kt +++ b/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/AEPPushNotificationBuilder.kt @@ -9,25 +9,25 @@ governing permissions and limitations under the License. */ -package com.adobe.marketing.mobile.notificationbuilder.builders +package com.adobe.marketing.mobile.notificationbuilder.internal.builders import android.app.Activity import android.content.Context import android.os.Build import android.widget.RemoteViews import androidx.core.app.NotificationCompat -import com.adobe.marketing.mobile.notificationbuilder.NotificationConstructionFailedException +import com.adobe.marketing.mobile.notificationbuilder.internal.NotificationConstructionFailedException import com.adobe.marketing.mobile.notificationbuilder.R -import com.adobe.marketing.mobile.notificationbuilder.extensions.setNotificationBackgroundColor -import com.adobe.marketing.mobile.notificationbuilder.extensions.setNotificationBodyTextColor -import com.adobe.marketing.mobile.notificationbuilder.extensions.setNotificationClickAction -import com.adobe.marketing.mobile.notificationbuilder.extensions.setNotificationDeleteAction -import com.adobe.marketing.mobile.notificationbuilder.extensions.setNotificationTitleTextColor -import com.adobe.marketing.mobile.notificationbuilder.extensions.setRemoteViewLargeIcon -import com.adobe.marketing.mobile.notificationbuilder.extensions.setSmallIcon -import com.adobe.marketing.mobile.notificationbuilder.extensions.setSound -import com.adobe.marketing.mobile.notificationbuilder.extensions.setVisibility -import com.adobe.marketing.mobile.notificationbuilder.templates.AEPPushTemplate +import com.adobe.marketing.mobile.notificationbuilder.internal.builders.extensions.setNotificationBackgroundColor +import com.adobe.marketing.mobile.notificationbuilder.internal.builders.extensions.setNotificationBodyTextColor +import com.adobe.marketing.mobile.notificationbuilder.internal.builders.extensions.setNotificationClickAction +import com.adobe.marketing.mobile.notificationbuilder.internal.builders.extensions.setNotificationDeleteAction +import com.adobe.marketing.mobile.notificationbuilder.internal.builders.extensions.setNotificationTitleTextColor +import com.adobe.marketing.mobile.notificationbuilder.internal.builders.extensions.setRemoteViewLargeIcon +import com.adobe.marketing.mobile.notificationbuilder.internal.builders.extensions.setSmallIcon +import com.adobe.marketing.mobile.notificationbuilder.internal.builders.extensions.setSound +import com.adobe.marketing.mobile.notificationbuilder.internal.builders.extensions.setVisibility +import com.adobe.marketing.mobile.notificationbuilder.internal.templates.AEPPushTemplate // TODO: The utilities provided by this builder assumes the id's for various common elements (R.id.basic_small_layout, // R.id.notification_title, R.id.notification_body_expanded) are the same across templates. diff --git a/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/builders/AutoCarouselNotificationBuilder.kt b/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/AutoCarouselNotificationBuilder.kt similarity index 91% rename from code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/builders/AutoCarouselNotificationBuilder.kt rename to code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/AutoCarouselNotificationBuilder.kt index 870f3d36..4de664a8 100644 --- a/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/builders/AutoCarouselNotificationBuilder.kt +++ b/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/AutoCarouselNotificationBuilder.kt @@ -9,7 +9,7 @@ governing permissions and limitations under the License. */ -package com.adobe.marketing.mobile.notificationbuilder.builders +package com.adobe.marketing.mobile.notificationbuilder.internal.builders import android.app.Activity import android.app.NotificationManager @@ -18,13 +18,13 @@ import android.content.Context import android.graphics.Bitmap import android.widget.RemoteViews import androidx.core.app.NotificationCompat -import com.adobe.marketing.mobile.notificationbuilder.PushTemplateConstants -import com.adobe.marketing.mobile.notificationbuilder.PushTemplateImageUtils +import com.adobe.marketing.mobile.notificationbuilder.internal.PushTemplateConstants +import com.adobe.marketing.mobile.notificationbuilder.internal.PushTemplateImageUtils import com.adobe.marketing.mobile.notificationbuilder.R -import com.adobe.marketing.mobile.notificationbuilder.extensions.createNotificationChannelIfRequired -import com.adobe.marketing.mobile.notificationbuilder.extensions.setRemoteViewClickAction -import com.adobe.marketing.mobile.notificationbuilder.templates.AutoCarouselPushTemplate -import com.adobe.marketing.mobile.notificationbuilder.templates.CarouselPushTemplate +import com.adobe.marketing.mobile.notificationbuilder.internal.builders.extensions.createNotificationChannelIfRequired +import com.adobe.marketing.mobile.notificationbuilder.internal.builders.extensions.setRemoteViewClickAction +import com.adobe.marketing.mobile.notificationbuilder.internal.templates.AutoCarouselPushTemplate +import com.adobe.marketing.mobile.notificationbuilder.internal.templates.CarouselPushTemplate import com.adobe.marketing.mobile.services.Log /** diff --git a/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/builders/BasicNotificationBuilder.kt b/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/BasicNotificationBuilder.kt similarity index 93% rename from code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/builders/BasicNotificationBuilder.kt rename to code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/BasicNotificationBuilder.kt index 86de47a0..a742ef3d 100644 --- a/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/builders/BasicNotificationBuilder.kt +++ b/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/BasicNotificationBuilder.kt @@ -9,7 +9,7 @@ governing permissions and limitations under the License. */ -package com.adobe.marketing.mobile.notificationbuilder.builders +package com.adobe.marketing.mobile.notificationbuilder.internal.builders import android.app.Activity import android.app.NotificationManager @@ -20,13 +20,13 @@ import android.content.Intent import android.view.View import android.widget.RemoteViews import androidx.core.app.NotificationCompat -import com.adobe.marketing.mobile.notificationbuilder.NotificationConstructionFailedException -import com.adobe.marketing.mobile.notificationbuilder.PushTemplateConstants -import com.adobe.marketing.mobile.notificationbuilder.PushTemplateImageUtils +import com.adobe.marketing.mobile.notificationbuilder.internal.NotificationConstructionFailedException +import com.adobe.marketing.mobile.notificationbuilder.internal.PushTemplateConstants +import com.adobe.marketing.mobile.notificationbuilder.internal.PushTemplateImageUtils import com.adobe.marketing.mobile.notificationbuilder.R -import com.adobe.marketing.mobile.notificationbuilder.extensions.addActionButtons -import com.adobe.marketing.mobile.notificationbuilder.extensions.createNotificationChannelIfRequired -import com.adobe.marketing.mobile.notificationbuilder.templates.BasicPushTemplate +import com.adobe.marketing.mobile.notificationbuilder.internal.builders.extensions.addActionButtons +import com.adobe.marketing.mobile.notificationbuilder.internal.builders.extensions.createNotificationChannelIfRequired +import com.adobe.marketing.mobile.notificationbuilder.internal.templates.BasicPushTemplate import com.adobe.marketing.mobile.services.Log /** diff --git a/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/builders/InputBoxNotificationBuilder.kt b/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/InputBoxNotificationBuilder.kt similarity index 95% rename from code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/builders/InputBoxNotificationBuilder.kt rename to code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/InputBoxNotificationBuilder.kt index efc6dabd..49c646f5 100644 --- a/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/builders/InputBoxNotificationBuilder.kt +++ b/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/InputBoxNotificationBuilder.kt @@ -9,7 +9,7 @@ governing permissions and limitations under the License. */ -package com.adobe.marketing.mobile.notificationbuilder.builders +package com.adobe.marketing.mobile.notificationbuilder.internal.builders import android.app.Activity import android.app.NotificationManager @@ -21,12 +21,12 @@ import android.view.View import android.widget.RemoteViews import androidx.core.app.NotificationCompat import androidx.core.app.RemoteInput -import com.adobe.marketing.mobile.notificationbuilder.NotificationConstructionFailedException -import com.adobe.marketing.mobile.notificationbuilder.PushTemplateConstants -import com.adobe.marketing.mobile.notificationbuilder.PushTemplateImageUtils +import com.adobe.marketing.mobile.notificationbuilder.internal.NotificationConstructionFailedException +import com.adobe.marketing.mobile.notificationbuilder.internal.PushTemplateConstants +import com.adobe.marketing.mobile.notificationbuilder.internal.PushTemplateImageUtils import com.adobe.marketing.mobile.notificationbuilder.R -import com.adobe.marketing.mobile.notificationbuilder.extensions.createNotificationChannelIfRequired -import com.adobe.marketing.mobile.notificationbuilder.templates.InputBoxPushTemplate +import com.adobe.marketing.mobile.notificationbuilder.internal.builders.extensions.createNotificationChannelIfRequired +import com.adobe.marketing.mobile.notificationbuilder.internal.templates.InputBoxPushTemplate import com.adobe.marketing.mobile.services.Log /** diff --git a/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/builders/LegacyNotificationBuilder.kt b/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/LegacyNotificationBuilder.kt similarity index 80% rename from code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/builders/LegacyNotificationBuilder.kt rename to code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/LegacyNotificationBuilder.kt index f2d19d98..51255cf0 100644 --- a/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/builders/LegacyNotificationBuilder.kt +++ b/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/LegacyNotificationBuilder.kt @@ -9,22 +9,22 @@ governing permissions and limitations under the License. */ -package com.adobe.marketing.mobile.notificationbuilder.builders +package com.adobe.marketing.mobile.notificationbuilder.internal.builders import android.app.Activity import android.app.NotificationManager import android.content.Context import android.os.Build import androidx.core.app.NotificationCompat -import com.adobe.marketing.mobile.notificationbuilder.PushTemplateConstants -import com.adobe.marketing.mobile.notificationbuilder.extensions.addActionButtons -import com.adobe.marketing.mobile.notificationbuilder.extensions.createNotificationChannelIfRequired -import com.adobe.marketing.mobile.notificationbuilder.extensions.setLargeIcon -import com.adobe.marketing.mobile.notificationbuilder.extensions.setNotificationClickAction -import com.adobe.marketing.mobile.notificationbuilder.extensions.setNotificationDeleteAction -import com.adobe.marketing.mobile.notificationbuilder.extensions.setSmallIcon -import com.adobe.marketing.mobile.notificationbuilder.extensions.setSound -import com.adobe.marketing.mobile.notificationbuilder.templates.BasicPushTemplate +import com.adobe.marketing.mobile.notificationbuilder.internal.PushTemplateConstants +import com.adobe.marketing.mobile.notificationbuilder.internal.builders.extensions.addActionButtons +import com.adobe.marketing.mobile.notificationbuilder.internal.builders.extensions.createNotificationChannelIfRequired +import com.adobe.marketing.mobile.notificationbuilder.internal.builders.extensions.setLargeIcon +import com.adobe.marketing.mobile.notificationbuilder.internal.builders.extensions.setNotificationClickAction +import com.adobe.marketing.mobile.notificationbuilder.internal.builders.extensions.setNotificationDeleteAction +import com.adobe.marketing.mobile.notificationbuilder.internal.builders.extensions.setSmallIcon +import com.adobe.marketing.mobile.notificationbuilder.internal.builders.extensions.setSound +import com.adobe.marketing.mobile.notificationbuilder.internal.templates.BasicPushTemplate import com.adobe.marketing.mobile.services.Log /** diff --git a/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/builders/ManualCarouselNotificationBuilder.kt b/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/ManualCarouselNotificationBuilder.kt similarity index 96% rename from code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/builders/ManualCarouselNotificationBuilder.kt rename to code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/ManualCarouselNotificationBuilder.kt index 12d5ac52..c370ef86 100644 --- a/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/builders/ManualCarouselNotificationBuilder.kt +++ b/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/ManualCarouselNotificationBuilder.kt @@ -9,7 +9,7 @@ governing permissions and limitations under the License. */ -package com.adobe.marketing.mobile.notificationbuilder.builders +package com.adobe.marketing.mobile.notificationbuilder.internal.builders import android.app.Activity import android.app.NotificationManager @@ -20,14 +20,14 @@ import android.content.Intent import android.graphics.Bitmap import android.widget.RemoteViews import androidx.core.app.NotificationCompat -import com.adobe.marketing.mobile.notificationbuilder.NotificationConstructionFailedException -import com.adobe.marketing.mobile.notificationbuilder.PushTemplateConstants -import com.adobe.marketing.mobile.notificationbuilder.PushTemplateImageUtils +import com.adobe.marketing.mobile.notificationbuilder.internal.NotificationConstructionFailedException +import com.adobe.marketing.mobile.notificationbuilder.internal.PushTemplateConstants +import com.adobe.marketing.mobile.notificationbuilder.internal.PushTemplateImageUtils import com.adobe.marketing.mobile.notificationbuilder.R -import com.adobe.marketing.mobile.notificationbuilder.extensions.createNotificationChannelIfRequired -import com.adobe.marketing.mobile.notificationbuilder.extensions.setRemoteViewClickAction -import com.adobe.marketing.mobile.notificationbuilder.templates.CarouselPushTemplate -import com.adobe.marketing.mobile.notificationbuilder.templates.ManualCarouselPushTemplate +import com.adobe.marketing.mobile.notificationbuilder.internal.builders.extensions.createNotificationChannelIfRequired +import com.adobe.marketing.mobile.notificationbuilder.internal.builders.extensions.setRemoteViewClickAction +import com.adobe.marketing.mobile.notificationbuilder.internal.templates.CarouselPushTemplate +import com.adobe.marketing.mobile.notificationbuilder.internal.templates.ManualCarouselPushTemplate import com.adobe.marketing.mobile.services.Log import com.adobe.marketing.mobile.services.caching.CacheService diff --git a/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/builders/ZeroBezelNotificationBuilder.kt b/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/ZeroBezelNotificationBuilder.kt similarity index 87% rename from code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/builders/ZeroBezelNotificationBuilder.kt rename to code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/ZeroBezelNotificationBuilder.kt index fb534d36..365d5e9d 100644 --- a/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/builders/ZeroBezelNotificationBuilder.kt +++ b/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/ZeroBezelNotificationBuilder.kt @@ -9,7 +9,7 @@ governing permissions and limitations under the License. */ -package com.adobe.marketing.mobile.notificationbuilder.builders +package com.adobe.marketing.mobile.notificationbuilder.internal.builders import android.app.Activity import android.app.NotificationManager @@ -17,12 +17,12 @@ import android.content.Context import android.view.View import android.widget.RemoteViews import androidx.core.app.NotificationCompat -import com.adobe.marketing.mobile.notificationbuilder.NotificationConstructionFailedException -import com.adobe.marketing.mobile.notificationbuilder.PushTemplateConstants -import com.adobe.marketing.mobile.notificationbuilder.PushTemplateImageUtils +import com.adobe.marketing.mobile.notificationbuilder.internal.NotificationConstructionFailedException +import com.adobe.marketing.mobile.notificationbuilder.internal.PushTemplateConstants +import com.adobe.marketing.mobile.notificationbuilder.internal.PushTemplateImageUtils import com.adobe.marketing.mobile.notificationbuilder.R -import com.adobe.marketing.mobile.notificationbuilder.extensions.createNotificationChannelIfRequired -import com.adobe.marketing.mobile.notificationbuilder.templates.ZeroBezelPushTemplate +import com.adobe.marketing.mobile.notificationbuilder.internal.builders.extensions.createNotificationChannelIfRequired +import com.adobe.marketing.mobile.notificationbuilder.internal.templates.ZeroBezelPushTemplate import com.adobe.marketing.mobile.services.Log internal object ZeroBezelNotificationBuilder { diff --git a/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/extensions/AppResourceExtensions.kt b/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/extensions/AppResourceExtensions.kt similarity index 93% rename from code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/extensions/AppResourceExtensions.kt rename to code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/extensions/AppResourceExtensions.kt index 8e36d7ae..169bd2d4 100644 --- a/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/extensions/AppResourceExtensions.kt +++ b/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/extensions/AppResourceExtensions.kt @@ -9,13 +9,13 @@ governing permissions and limitations under the License. */ -package com.adobe.marketing.mobile.notificationbuilder.extensions +package com.adobe.marketing.mobile.notificationbuilder.internal.builders.extensions import android.content.ContentResolver import android.content.Context import android.content.pm.PackageManager import android.net.Uri -import com.adobe.marketing.mobile.notificationbuilder.PushTemplateConstants +import com.adobe.marketing.mobile.notificationbuilder.internal.PushTemplateConstants import com.adobe.marketing.mobile.services.Log private const val SELF_TAG = "AppResourceExtensions" diff --git a/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/extensions/NotificationCompatBuilderExtensions.kt b/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/extensions/NotificationCompatBuilderExtensions.kt similarity index 95% rename from code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/extensions/NotificationCompatBuilderExtensions.kt rename to code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/extensions/NotificationCompatBuilderExtensions.kt index bfd10e88..d80bb7f2 100644 --- a/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/extensions/NotificationCompatBuilderExtensions.kt +++ b/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/extensions/NotificationCompatBuilderExtensions.kt @@ -9,7 +9,7 @@ governing permissions and limitations under the License. */ -package com.adobe.marketing.mobile.notificationbuilder.extensions +package com.adobe.marketing.mobile.notificationbuilder.internal.builders.extensions import android.app.Activity import android.app.PendingIntent @@ -19,11 +19,11 @@ import android.graphics.Color import android.media.RingtoneManager import androidx.core.app.NotificationCompat import com.adobe.marketing.mobile.MobileCore -import com.adobe.marketing.mobile.notificationbuilder.PendingIntentUtils -import com.adobe.marketing.mobile.notificationbuilder.PushTemplateConstants -import com.adobe.marketing.mobile.notificationbuilder.PushTemplateImageUtils -import com.adobe.marketing.mobile.notificationbuilder.templates.AEPPushTemplate -import com.adobe.marketing.mobile.notificationbuilder.templates.BasicPushTemplate +import com.adobe.marketing.mobile.notificationbuilder.internal.PendingIntentUtils +import com.adobe.marketing.mobile.notificationbuilder.internal.PushTemplateConstants +import com.adobe.marketing.mobile.notificationbuilder.internal.PushTemplateImageUtils +import com.adobe.marketing.mobile.notificationbuilder.internal.templates.AEPPushTemplate +import com.adobe.marketing.mobile.notificationbuilder.internal.templates.BasicPushTemplate import com.adobe.marketing.mobile.services.Log import java.util.Random diff --git a/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/extensions/NotificationManagerExtensions.kt b/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/extensions/NotificationManagerExtensions.kt similarity index 95% rename from code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/extensions/NotificationManagerExtensions.kt rename to code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/extensions/NotificationManagerExtensions.kt index 433b75d4..9cba2531 100644 --- a/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/extensions/NotificationManagerExtensions.kt +++ b/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/extensions/NotificationManagerExtensions.kt @@ -9,14 +9,14 @@ governing permissions and limitations under the License. */ -package com.adobe.marketing.mobile.notificationbuilder.extensions +package com.adobe.marketing.mobile.notificationbuilder.internal.builders.extensions import android.app.NotificationChannel import android.app.NotificationManager import android.content.Context import android.media.RingtoneManager import android.os.Build -import com.adobe.marketing.mobile.notificationbuilder.PushTemplateConstants +import com.adobe.marketing.mobile.notificationbuilder.internal.PushTemplateConstants import com.adobe.marketing.mobile.services.Log private const val SELF_TAG = "NotificationManagerExtensions" diff --git a/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/extensions/RemoteViewsExtensions.kt b/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/extensions/RemoteViewsExtensions.kt similarity index 96% rename from code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/extensions/RemoteViewsExtensions.kt rename to code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/extensions/RemoteViewsExtensions.kt index 0ff4042e..59f17af4 100644 --- a/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/extensions/RemoteViewsExtensions.kt +++ b/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/extensions/RemoteViewsExtensions.kt @@ -9,7 +9,7 @@ governing permissions and limitations under the License. */ -package com.adobe.marketing.mobile.notificationbuilder.extensions +package com.adobe.marketing.mobile.notificationbuilder.internal.builders.extensions import android.app.Activity import android.app.PendingIntent @@ -18,9 +18,9 @@ import android.graphics.Color import android.view.View import android.widget.RemoteViews import androidx.core.app.NotificationCompat -import com.adobe.marketing.mobile.notificationbuilder.PendingIntentUtils -import com.adobe.marketing.mobile.notificationbuilder.PushTemplateConstants -import com.adobe.marketing.mobile.notificationbuilder.PushTemplateImageUtils +import com.adobe.marketing.mobile.notificationbuilder.internal.PendingIntentUtils +import com.adobe.marketing.mobile.notificationbuilder.internal.PushTemplateConstants +import com.adobe.marketing.mobile.notificationbuilder.internal.PushTemplateImageUtils import com.adobe.marketing.mobile.notificationbuilder.R import com.adobe.marketing.mobile.services.Log import com.adobe.marketing.mobile.services.ServiceProvider diff --git a/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/templates/AEPPushTemplate.kt b/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/templates/AEPPushTemplate.kt similarity index 97% rename from code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/templates/AEPPushTemplate.kt rename to code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/templates/AEPPushTemplate.kt index dc9d6653..c3e96d81 100644 --- a/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/templates/AEPPushTemplate.kt +++ b/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/templates/AEPPushTemplate.kt @@ -9,17 +9,17 @@ governing permissions and limitations under the License. */ -package com.adobe.marketing.mobile.notificationbuilder.templates +package com.adobe.marketing.mobile.notificationbuilder.internal.templates import android.app.NotificationManager import android.content.Intent import android.os.Build import androidx.annotation.RequiresApi import androidx.core.app.NotificationCompat -import com.adobe.marketing.mobile.notificationbuilder.NotificationPriority -import com.adobe.marketing.mobile.notificationbuilder.NotificationVisibility -import com.adobe.marketing.mobile.notificationbuilder.PushTemplateConstants -import com.adobe.marketing.mobile.notificationbuilder.PushTemplateType +import com.adobe.marketing.mobile.notificationbuilder.internal.NotificationPriority +import com.adobe.marketing.mobile.notificationbuilder.internal.NotificationVisibility +import com.adobe.marketing.mobile.notificationbuilder.internal.PushTemplateConstants +import com.adobe.marketing.mobile.notificationbuilder.internal.PushTemplateType import com.adobe.marketing.mobile.services.Log import com.adobe.marketing.mobile.util.DataReader diff --git a/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/templates/AutoCarouselPushTemplate.kt b/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/templates/AutoCarouselPushTemplate.kt similarity index 91% rename from code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/templates/AutoCarouselPushTemplate.kt rename to code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/templates/AutoCarouselPushTemplate.kt index 4582207f..a5aac42b 100644 --- a/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/templates/AutoCarouselPushTemplate.kt +++ b/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/templates/AutoCarouselPushTemplate.kt @@ -9,7 +9,7 @@ governing permissions and limitations under the License. */ -package com.adobe.marketing.mobile.notificationbuilder.templates +package com.adobe.marketing.mobile.notificationbuilder.internal.templates import android.content.Intent diff --git a/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/templates/BasicPushTemplate.kt b/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/templates/BasicPushTemplate.kt similarity index 97% rename from code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/templates/BasicPushTemplate.kt rename to code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/templates/BasicPushTemplate.kt index e5cc237f..954be915 100644 --- a/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/templates/BasicPushTemplate.kt +++ b/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/templates/BasicPushTemplate.kt @@ -9,11 +9,11 @@ governing permissions and limitations under the License. */ -package com.adobe.marketing.mobile.notificationbuilder.templates +package com.adobe.marketing.mobile.notificationbuilder.internal.templates import android.content.Intent import androidx.annotation.VisibleForTesting -import com.adobe.marketing.mobile.notificationbuilder.PushTemplateConstants +import com.adobe.marketing.mobile.notificationbuilder.internal.PushTemplateConstants import com.adobe.marketing.mobile.services.Log import com.adobe.marketing.mobile.util.DataReader import org.json.JSONArray diff --git a/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/templates/CarouselPushTemplate.kt b/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/templates/CarouselPushTemplate.kt similarity index 97% rename from code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/templates/CarouselPushTemplate.kt rename to code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/templates/CarouselPushTemplate.kt index 2fc3db07..ed62920d 100644 --- a/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/templates/CarouselPushTemplate.kt +++ b/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/templates/CarouselPushTemplate.kt @@ -9,10 +9,10 @@ governing permissions and limitations under the License. */ -package com.adobe.marketing.mobile.notificationbuilder.templates +package com.adobe.marketing.mobile.notificationbuilder.internal.templates import android.content.Intent -import com.adobe.marketing.mobile.notificationbuilder.PushTemplateConstants +import com.adobe.marketing.mobile.notificationbuilder.internal.PushTemplateConstants import com.adobe.marketing.mobile.services.Log import com.adobe.marketing.mobile.util.DataReader import org.json.JSONArray diff --git a/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/templates/InputBoxPushTemplate.kt b/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/templates/InputBoxPushTemplate.kt similarity index 95% rename from code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/templates/InputBoxPushTemplate.kt rename to code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/templates/InputBoxPushTemplate.kt index 97d1d138..46e0b5e2 100644 --- a/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/templates/InputBoxPushTemplate.kt +++ b/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/templates/InputBoxPushTemplate.kt @@ -9,10 +9,10 @@ governing permissions and limitations under the License. */ -package com.adobe.marketing.mobile.notificationbuilder.templates +package com.adobe.marketing.mobile.notificationbuilder.internal.templates import android.content.Intent -import com.adobe.marketing.mobile.notificationbuilder.PushTemplateConstants +import com.adobe.marketing.mobile.notificationbuilder.internal.PushTemplateConstants import com.adobe.marketing.mobile.util.DataReader /** diff --git a/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/templates/ManualCarouselPushTemplate.kt b/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/templates/ManualCarouselPushTemplate.kt similarity index 91% rename from code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/templates/ManualCarouselPushTemplate.kt rename to code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/templates/ManualCarouselPushTemplate.kt index 27016926..d9d87709 100644 --- a/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/templates/ManualCarouselPushTemplate.kt +++ b/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/templates/ManualCarouselPushTemplate.kt @@ -9,10 +9,10 @@ governing permissions and limitations under the License. */ -package com.adobe.marketing.mobile.notificationbuilder.templates +package com.adobe.marketing.mobile.notificationbuilder.internal.templates import android.content.Intent -import com.adobe.marketing.mobile.notificationbuilder.PushTemplateConstants +import com.adobe.marketing.mobile.notificationbuilder.internal.PushTemplateConstants internal class ManualCarouselPushTemplate : CarouselPushTemplate { internal var intentAction: String? = null diff --git a/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/templates/ProductCatalogPushTemplate.kt b/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/templates/ProductCatalogPushTemplate.kt similarity index 98% rename from code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/templates/ProductCatalogPushTemplate.kt rename to code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/templates/ProductCatalogPushTemplate.kt index 5e422d41..ddf6c561 100644 --- a/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/templates/ProductCatalogPushTemplate.kt +++ b/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/templates/ProductCatalogPushTemplate.kt @@ -9,10 +9,10 @@ governing permissions and limitations under the License. */ -package com.adobe.marketing.mobile.notificationbuilder.templates +package com.adobe.marketing.mobile.notificationbuilder.internal.templates import android.content.Intent -import com.adobe.marketing.mobile.notificationbuilder.PushTemplateConstants +import com.adobe.marketing.mobile.notificationbuilder.internal.PushTemplateConstants import com.adobe.marketing.mobile.services.Log import com.adobe.marketing.mobile.util.DataReader import org.json.JSONArray diff --git a/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/templates/ZeroBezelPushTemplate.kt b/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/templates/ZeroBezelPushTemplate.kt similarity index 90% rename from code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/templates/ZeroBezelPushTemplate.kt rename to code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/templates/ZeroBezelPushTemplate.kt index 5d949fb0..4b4b790a 100644 --- a/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/templates/ZeroBezelPushTemplate.kt +++ b/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/templates/ZeroBezelPushTemplate.kt @@ -9,9 +9,9 @@ governing permissions and limitations under the License. */ -package com.adobe.marketing.mobile.notificationbuilder.templates +package com.adobe.marketing.mobile.notificationbuilder.internal.templates -import com.adobe.marketing.mobile.notificationbuilder.PushTemplateConstants +import com.adobe.marketing.mobile.notificationbuilder.internal.PushTemplateConstants import com.adobe.marketing.mobile.util.DataReader internal class ZeroBezelPushTemplate : AEPPushTemplate { diff --git a/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/AEPPushTemplateTests.kt b/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/AEPPushTemplateTests.kt similarity index 99% rename from code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/AEPPushTemplateTests.kt rename to code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/AEPPushTemplateTests.kt index fba9ae35..83536d76 100644 --- a/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/AEPPushTemplateTests.kt +++ b/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/AEPPushTemplateTests.kt @@ -9,9 +9,9 @@ governing permissions and limitations under the License. */ -package com.adobe.marketing.mobile.notificationbuilder +package com.adobe.marketing.mobile.notificationbuilder.internal -import com.adobe.marketing.mobile.notificationbuilder.templates.AEPPushTemplate +import com.adobe.marketing.mobile.notificationbuilder.internal.templates.AEPPushTemplate import org.junit.Before import org.mockito.MockitoAnnotations diff --git a/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/NotificationBuilderTests.java b/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/NotificationBuilderTests.java similarity index 85% rename from code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/NotificationBuilderTests.java rename to code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/NotificationBuilderTests.java index a0341a66..f2af66fb 100644 --- a/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/NotificationBuilderTests.java +++ b/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/NotificationBuilderTests.java @@ -9,10 +9,12 @@ governing permissions and limitations under the License. */ -package com.adobe.marketing.mobile.notificationbuilder; +package com.adobe.marketing.mobile.notificationbuilder.internal; import static org.junit.Assert.assertEquals; +import com.adobe.marketing.mobile.notificationbuilder.NotificationBuilder; + import org.junit.Test; public class NotificationBuilderTests { diff --git a/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/PushTemplateTypeTest.kt b/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/PushTemplateTypeTest.kt similarity index 94% rename from code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/PushTemplateTypeTest.kt rename to code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/PushTemplateTypeTest.kt index 73f2cd9e..2acf2f18 100644 --- a/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/PushTemplateTypeTest.kt +++ b/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/PushTemplateTypeTest.kt @@ -9,7 +9,7 @@ governing permissions and limitations under the License. */ -package com.adobe.marketing.mobile.notificationbuilder +package com.adobe.marketing.mobile.notificationbuilder.internal import org.junit.Test import kotlin.test.assertEquals From 5d2aaf7f43944f427d6753f3ca84fa369ee3d0fc Mon Sep 17 00:00:00 2001 From: Ryan Morales Date: Fri, 17 May 2024 14:18:47 -0700 Subject: [PATCH 016/159] run formatter --- .../notificationbuilder/internal/PushTemplateImageUtils.kt | 4 ++-- .../internal/builders/AEPPushNotificationBuilder.kt | 2 +- .../internal/builders/AutoCarouselNotificationBuilder.kt | 2 +- .../internal/builders/BasicNotificationBuilder.kt | 2 +- .../internal/builders/InputBoxNotificationBuilder.kt | 2 +- .../internal/builders/ManualCarouselNotificationBuilder.kt | 2 +- .../internal/builders/ZeroBezelNotificationBuilder.kt | 2 +- .../internal/extensions/RemoteViewsExtensions.kt | 2 +- .../internal/NotificationBuilderTests.java | 1 - 9 files changed, 9 insertions(+), 10 deletions(-) diff --git a/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/PushTemplateImageUtils.kt b/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/PushTemplateImageUtils.kt index 713409b2..7e450195 100644 --- a/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/PushTemplateImageUtils.kt +++ b/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/PushTemplateImageUtils.kt @@ -307,9 +307,9 @@ internal object PushTemplateImageUtils { ( applicationCacheDir .toString() + File.separator + - PushTemplateConstants.CACHE_BASE_DIR + PushTemplateConstants.CACHE_BASE_DIR ) + File.separator + - PushTemplateConstants.PUSH_IMAGE_CACHE + PushTemplateConstants.PUSH_IMAGE_CACHE ) } } diff --git a/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/AEPPushNotificationBuilder.kt b/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/AEPPushNotificationBuilder.kt index af440062..7164763c 100644 --- a/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/AEPPushNotificationBuilder.kt +++ b/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/AEPPushNotificationBuilder.kt @@ -16,8 +16,8 @@ import android.content.Context import android.os.Build import android.widget.RemoteViews import androidx.core.app.NotificationCompat -import com.adobe.marketing.mobile.notificationbuilder.internal.NotificationConstructionFailedException import com.adobe.marketing.mobile.notificationbuilder.R +import com.adobe.marketing.mobile.notificationbuilder.internal.NotificationConstructionFailedException import com.adobe.marketing.mobile.notificationbuilder.internal.builders.extensions.setNotificationBackgroundColor import com.adobe.marketing.mobile.notificationbuilder.internal.builders.extensions.setNotificationBodyTextColor import com.adobe.marketing.mobile.notificationbuilder.internal.builders.extensions.setNotificationClickAction diff --git a/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/AutoCarouselNotificationBuilder.kt b/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/AutoCarouselNotificationBuilder.kt index 4de664a8..3a217ccb 100644 --- a/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/AutoCarouselNotificationBuilder.kt +++ b/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/AutoCarouselNotificationBuilder.kt @@ -18,9 +18,9 @@ import android.content.Context import android.graphics.Bitmap import android.widget.RemoteViews import androidx.core.app.NotificationCompat +import com.adobe.marketing.mobile.notificationbuilder.R import com.adobe.marketing.mobile.notificationbuilder.internal.PushTemplateConstants import com.adobe.marketing.mobile.notificationbuilder.internal.PushTemplateImageUtils -import com.adobe.marketing.mobile.notificationbuilder.R import com.adobe.marketing.mobile.notificationbuilder.internal.builders.extensions.createNotificationChannelIfRequired import com.adobe.marketing.mobile.notificationbuilder.internal.builders.extensions.setRemoteViewClickAction import com.adobe.marketing.mobile.notificationbuilder.internal.templates.AutoCarouselPushTemplate diff --git a/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/BasicNotificationBuilder.kt b/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/BasicNotificationBuilder.kt index a742ef3d..0baf09da 100644 --- a/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/BasicNotificationBuilder.kt +++ b/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/BasicNotificationBuilder.kt @@ -20,10 +20,10 @@ import android.content.Intent import android.view.View import android.widget.RemoteViews import androidx.core.app.NotificationCompat +import com.adobe.marketing.mobile.notificationbuilder.R import com.adobe.marketing.mobile.notificationbuilder.internal.NotificationConstructionFailedException import com.adobe.marketing.mobile.notificationbuilder.internal.PushTemplateConstants import com.adobe.marketing.mobile.notificationbuilder.internal.PushTemplateImageUtils -import com.adobe.marketing.mobile.notificationbuilder.R import com.adobe.marketing.mobile.notificationbuilder.internal.builders.extensions.addActionButtons import com.adobe.marketing.mobile.notificationbuilder.internal.builders.extensions.createNotificationChannelIfRequired import com.adobe.marketing.mobile.notificationbuilder.internal.templates.BasicPushTemplate diff --git a/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/InputBoxNotificationBuilder.kt b/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/InputBoxNotificationBuilder.kt index 49c646f5..f314692e 100644 --- a/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/InputBoxNotificationBuilder.kt +++ b/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/InputBoxNotificationBuilder.kt @@ -21,10 +21,10 @@ import android.view.View import android.widget.RemoteViews import androidx.core.app.NotificationCompat import androidx.core.app.RemoteInput +import com.adobe.marketing.mobile.notificationbuilder.R import com.adobe.marketing.mobile.notificationbuilder.internal.NotificationConstructionFailedException import com.adobe.marketing.mobile.notificationbuilder.internal.PushTemplateConstants import com.adobe.marketing.mobile.notificationbuilder.internal.PushTemplateImageUtils -import com.adobe.marketing.mobile.notificationbuilder.R import com.adobe.marketing.mobile.notificationbuilder.internal.builders.extensions.createNotificationChannelIfRequired import com.adobe.marketing.mobile.notificationbuilder.internal.templates.InputBoxPushTemplate import com.adobe.marketing.mobile.services.Log diff --git a/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/ManualCarouselNotificationBuilder.kt b/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/ManualCarouselNotificationBuilder.kt index c370ef86..b24b5686 100644 --- a/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/ManualCarouselNotificationBuilder.kt +++ b/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/ManualCarouselNotificationBuilder.kt @@ -20,10 +20,10 @@ import android.content.Intent import android.graphics.Bitmap import android.widget.RemoteViews import androidx.core.app.NotificationCompat +import com.adobe.marketing.mobile.notificationbuilder.R import com.adobe.marketing.mobile.notificationbuilder.internal.NotificationConstructionFailedException import com.adobe.marketing.mobile.notificationbuilder.internal.PushTemplateConstants import com.adobe.marketing.mobile.notificationbuilder.internal.PushTemplateImageUtils -import com.adobe.marketing.mobile.notificationbuilder.R import com.adobe.marketing.mobile.notificationbuilder.internal.builders.extensions.createNotificationChannelIfRequired import com.adobe.marketing.mobile.notificationbuilder.internal.builders.extensions.setRemoteViewClickAction import com.adobe.marketing.mobile.notificationbuilder.internal.templates.CarouselPushTemplate diff --git a/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/ZeroBezelNotificationBuilder.kt b/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/ZeroBezelNotificationBuilder.kt index 365d5e9d..4e313fe8 100644 --- a/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/ZeroBezelNotificationBuilder.kt +++ b/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/ZeroBezelNotificationBuilder.kt @@ -17,10 +17,10 @@ import android.content.Context import android.view.View import android.widget.RemoteViews import androidx.core.app.NotificationCompat +import com.adobe.marketing.mobile.notificationbuilder.R import com.adobe.marketing.mobile.notificationbuilder.internal.NotificationConstructionFailedException import com.adobe.marketing.mobile.notificationbuilder.internal.PushTemplateConstants import com.adobe.marketing.mobile.notificationbuilder.internal.PushTemplateImageUtils -import com.adobe.marketing.mobile.notificationbuilder.R import com.adobe.marketing.mobile.notificationbuilder.internal.builders.extensions.createNotificationChannelIfRequired import com.adobe.marketing.mobile.notificationbuilder.internal.templates.ZeroBezelPushTemplate import com.adobe.marketing.mobile.services.Log diff --git a/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/extensions/RemoteViewsExtensions.kt b/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/extensions/RemoteViewsExtensions.kt index 59f17af4..21a157c3 100644 --- a/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/extensions/RemoteViewsExtensions.kt +++ b/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/extensions/RemoteViewsExtensions.kt @@ -18,10 +18,10 @@ import android.graphics.Color import android.view.View import android.widget.RemoteViews import androidx.core.app.NotificationCompat +import com.adobe.marketing.mobile.notificationbuilder.R import com.adobe.marketing.mobile.notificationbuilder.internal.PendingIntentUtils import com.adobe.marketing.mobile.notificationbuilder.internal.PushTemplateConstants import com.adobe.marketing.mobile.notificationbuilder.internal.PushTemplateImageUtils -import com.adobe.marketing.mobile.notificationbuilder.R import com.adobe.marketing.mobile.services.Log import com.adobe.marketing.mobile.services.ServiceProvider import com.adobe.marketing.mobile.util.UrlUtils diff --git a/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/NotificationBuilderTests.java b/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/NotificationBuilderTests.java index f2af66fb..2b9a81cf 100644 --- a/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/NotificationBuilderTests.java +++ b/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/NotificationBuilderTests.java @@ -14,7 +14,6 @@ import static org.junit.Assert.assertEquals; import com.adobe.marketing.mobile.notificationbuilder.NotificationBuilder; - import org.junit.Test; public class NotificationBuilderTests { From 76e505f00970f0da6dae495b0ff81d0462850d1f Mon Sep 17 00:00:00 2001 From: Ryan Morales Date: Fri, 17 May 2024 14:39:16 -0700 Subject: [PATCH 017/159] fix notificationBuilder > notificationbuilder in makefile --- Makefile | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/Makefile b/Makefile index 44c7e16b..e82a5b52 100644 --- a/Makefile +++ b/Makefile @@ -1,45 +1,45 @@ checkstyle: - (./code/gradlew -p code/notificationBuilder checkstyle) + (./code/gradlew -p code/notificationbuilder checkstyle) checkformat: - (./code/gradlew -p code/notificationBuilder spotlessCheck) + (./code/gradlew -p code/notificationbuilder spotlessCheck) format: - (./code/gradlew -p code/notificationBuilder spotlessApply) + (./code/gradlew -p code/notificationbuilder spotlessApply) format-license: (./code/gradlew -p code licenseFormat) javadoc: - (./code/gradlew -p code/notificationBuilder javadocJar) + (./code/gradlew -p code/notificationbuilder javadocJar) unit-test: - (./code/gradlew -p code/notificationBuilder testPhoneDebugUnitTest) + (./code/gradlew -p code/notificationbuilder testPhoneDebugUnitTest) unit-test-coverage: - (./code/gradlew -p code/notificationBuilder createPhoneDebugUnitTestCoverageReport) + (./code/gradlew -p code/notificationbuilder createPhoneDebugUnitTestCoverageReport) functional-test: - (./code/gradlew -p code/notificationBuilder uninstallPhoneDebugAndroidTest) - (./code/gradlew -p code/notificationBuilder connectedPhoneDebugAndroidTest) + (./code/gradlew -p code/notificationbuilder uninstallPhoneDebugAndroidTest) + (./code/gradlew -p code/notificationbuilder connectedPhoneDebugAndroidTest) functional-test-coverage: - (./code/gradlew -p code/notificationBuilder createPhoneDebugAndroidTestCoverageReport) + (./code/gradlew -p code/notificationbuilder createPhoneDebugAndroidTestCoverageReport) assemble-phone: - (./code/gradlew -p code/notificationBuilder assemblePhone) + (./code/gradlew -p code/notificationbuilder assemblePhone) assemble-phone-release: - (./code/gradlew -p code/notificationBuilder assemblePhoneRelease) + (./code/gradlew -p code/notificationbuilder assemblePhoneRelease) assemble-app: (./code/gradlew -p code/testapp assemble) notificationbuilder-publish-maven-local-jitpack: assemble-phone-release - (./code/gradlew -p code/notificationBuilder publishReleasePublicationToMavenLocal -Pjitpack -x signReleasePublication) + (./code/gradlew -p code/notificationbuilder publishReleasePublicationToMavenLocal -Pjitpack -x signReleasePublication) notificationbuilder-publish-snapshot: assemble-phone-release - (./code/gradlew -p code/notificationBuilder publishReleasePublicationToSonatypeRepository) + (./code/gradlew -p code/notificationbuilder publishReleasePublicationToSonatypeRepository) notificationbuilder-publish: assemble-phone-release - (./code/gradlew -p code/notificationBuilder publishReleasePublicationToSonatypeRepository -Prelease) + (./code/gradlew -p code/notificationbuilder publishReleasePublicationToSonatypeRepository -Prelease) From 18353febfd689da1990aedc4ecb230e0590f773b Mon Sep 17 00:00:00 2001 From: Ryan Morales Date: Fri, 17 May 2024 14:45:14 -0700 Subject: [PATCH 018/159] update jitpack command --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 5751f483..71cc5b37 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -47,7 +47,7 @@ jobs: - run: name: Build Jitpack Library - command: make ci-publish-maven-local-jitpack + command: make notificationbuilder-publish-maven-local-jitpack - run: name: Build Test app From 162983e7ea14498a7abeec809da99fad643a9fb5 Mon Sep 17 00:00:00 2001 From: Ryan Morales Date: Fri, 17 May 2024 15:43:52 -0700 Subject: [PATCH 019/159] make NotificationConstructionFailedException public - caller extensions need to handle this exception --- .../marketing/mobile/notificationbuilder/NotificationBuilder.kt | 1 - .../{internal => }/NotificationConstructionFailedException.kt | 2 +- .../internal/builders/AEPPushNotificationBuilder.kt | 2 +- .../internal/builders/BasicNotificationBuilder.kt | 2 +- .../internal/builders/InputBoxNotificationBuilder.kt | 2 +- .../internal/builders/ManualCarouselNotificationBuilder.kt | 2 +- .../internal/builders/ZeroBezelNotificationBuilder.kt | 2 +- 7 files changed, 6 insertions(+), 7 deletions(-) rename code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/{internal => }/NotificationConstructionFailedException.kt (92%) diff --git a/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/NotificationBuilder.kt b/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/NotificationBuilder.kt index 346580f8..ea933d58 100644 --- a/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/NotificationBuilder.kt +++ b/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/NotificationBuilder.kt @@ -15,7 +15,6 @@ import android.app.Activity import android.content.BroadcastReceiver import android.content.Intent import androidx.core.app.NotificationCompat -import com.adobe.marketing.mobile.notificationbuilder.internal.NotificationConstructionFailedException import com.adobe.marketing.mobile.notificationbuilder.internal.PushTemplateConstants import com.adobe.marketing.mobile.notificationbuilder.internal.PushTemplateType import com.adobe.marketing.mobile.notificationbuilder.internal.builders.AutoCarouselNotificationBuilder diff --git a/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/NotificationConstructionFailedException.kt b/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/NotificationConstructionFailedException.kt similarity index 92% rename from code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/NotificationConstructionFailedException.kt rename to code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/NotificationConstructionFailedException.kt index b456acb2..79662e13 100644 --- a/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/NotificationConstructionFailedException.kt +++ b/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/NotificationConstructionFailedException.kt @@ -9,7 +9,7 @@ governing permissions and limitations under the License. */ -package com.adobe.marketing.mobile.notificationbuilder.internal +package com.adobe.marketing.mobile.notificationbuilder /** * Exception indicating that construction of a push notification failed. diff --git a/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/AEPPushNotificationBuilder.kt b/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/AEPPushNotificationBuilder.kt index 7164763c..7cb26ef9 100644 --- a/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/AEPPushNotificationBuilder.kt +++ b/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/AEPPushNotificationBuilder.kt @@ -17,7 +17,7 @@ import android.os.Build import android.widget.RemoteViews import androidx.core.app.NotificationCompat import com.adobe.marketing.mobile.notificationbuilder.R -import com.adobe.marketing.mobile.notificationbuilder.internal.NotificationConstructionFailedException +import com.adobe.marketing.mobile.notificationbuilder.NotificationConstructionFailedException import com.adobe.marketing.mobile.notificationbuilder.internal.builders.extensions.setNotificationBackgroundColor import com.adobe.marketing.mobile.notificationbuilder.internal.builders.extensions.setNotificationBodyTextColor import com.adobe.marketing.mobile.notificationbuilder.internal.builders.extensions.setNotificationClickAction diff --git a/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/BasicNotificationBuilder.kt b/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/BasicNotificationBuilder.kt index 0baf09da..a8e38fe9 100644 --- a/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/BasicNotificationBuilder.kt +++ b/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/BasicNotificationBuilder.kt @@ -21,7 +21,7 @@ import android.view.View import android.widget.RemoteViews import androidx.core.app.NotificationCompat import com.adobe.marketing.mobile.notificationbuilder.R -import com.adobe.marketing.mobile.notificationbuilder.internal.NotificationConstructionFailedException +import com.adobe.marketing.mobile.notificationbuilder.NotificationConstructionFailedException import com.adobe.marketing.mobile.notificationbuilder.internal.PushTemplateConstants import com.adobe.marketing.mobile.notificationbuilder.internal.PushTemplateImageUtils import com.adobe.marketing.mobile.notificationbuilder.internal.builders.extensions.addActionButtons diff --git a/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/InputBoxNotificationBuilder.kt b/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/InputBoxNotificationBuilder.kt index f314692e..18aed1bd 100644 --- a/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/InputBoxNotificationBuilder.kt +++ b/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/InputBoxNotificationBuilder.kt @@ -22,7 +22,7 @@ import android.widget.RemoteViews import androidx.core.app.NotificationCompat import androidx.core.app.RemoteInput import com.adobe.marketing.mobile.notificationbuilder.R -import com.adobe.marketing.mobile.notificationbuilder.internal.NotificationConstructionFailedException +import com.adobe.marketing.mobile.notificationbuilder.NotificationConstructionFailedException import com.adobe.marketing.mobile.notificationbuilder.internal.PushTemplateConstants import com.adobe.marketing.mobile.notificationbuilder.internal.PushTemplateImageUtils import com.adobe.marketing.mobile.notificationbuilder.internal.builders.extensions.createNotificationChannelIfRequired diff --git a/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/ManualCarouselNotificationBuilder.kt b/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/ManualCarouselNotificationBuilder.kt index b24b5686..dcca342d 100644 --- a/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/ManualCarouselNotificationBuilder.kt +++ b/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/ManualCarouselNotificationBuilder.kt @@ -21,7 +21,7 @@ import android.graphics.Bitmap import android.widget.RemoteViews import androidx.core.app.NotificationCompat import com.adobe.marketing.mobile.notificationbuilder.R -import com.adobe.marketing.mobile.notificationbuilder.internal.NotificationConstructionFailedException +import com.adobe.marketing.mobile.notificationbuilder.NotificationConstructionFailedException import com.adobe.marketing.mobile.notificationbuilder.internal.PushTemplateConstants import com.adobe.marketing.mobile.notificationbuilder.internal.PushTemplateImageUtils import com.adobe.marketing.mobile.notificationbuilder.internal.builders.extensions.createNotificationChannelIfRequired diff --git a/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/ZeroBezelNotificationBuilder.kt b/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/ZeroBezelNotificationBuilder.kt index 4e313fe8..25405f07 100644 --- a/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/ZeroBezelNotificationBuilder.kt +++ b/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/ZeroBezelNotificationBuilder.kt @@ -18,7 +18,7 @@ import android.view.View import android.widget.RemoteViews import androidx.core.app.NotificationCompat import com.adobe.marketing.mobile.notificationbuilder.R -import com.adobe.marketing.mobile.notificationbuilder.internal.NotificationConstructionFailedException +import com.adobe.marketing.mobile.notificationbuilder.NotificationConstructionFailedException import com.adobe.marketing.mobile.notificationbuilder.internal.PushTemplateConstants import com.adobe.marketing.mobile.notificationbuilder.internal.PushTemplateImageUtils import com.adobe.marketing.mobile.notificationbuilder.internal.builders.extensions.createNotificationChannelIfRequired From d32fd4e77701354b7e713dfde90dd9c9ed92e3fa Mon Sep 17 00:00:00 2001 From: Ryan Morales Date: Fri, 17 May 2024 15:57:11 -0700 Subject: [PATCH 020/159] run formatter (again) --- .../internal/builders/AEPPushNotificationBuilder.kt | 2 +- .../internal/builders/BasicNotificationBuilder.kt | 2 +- .../internal/builders/InputBoxNotificationBuilder.kt | 2 +- .../internal/builders/ManualCarouselNotificationBuilder.kt | 2 +- .../internal/builders/ZeroBezelNotificationBuilder.kt | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/AEPPushNotificationBuilder.kt b/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/AEPPushNotificationBuilder.kt index 7cb26ef9..3f0e53f7 100644 --- a/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/AEPPushNotificationBuilder.kt +++ b/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/AEPPushNotificationBuilder.kt @@ -16,8 +16,8 @@ import android.content.Context import android.os.Build import android.widget.RemoteViews import androidx.core.app.NotificationCompat -import com.adobe.marketing.mobile.notificationbuilder.R import com.adobe.marketing.mobile.notificationbuilder.NotificationConstructionFailedException +import com.adobe.marketing.mobile.notificationbuilder.R import com.adobe.marketing.mobile.notificationbuilder.internal.builders.extensions.setNotificationBackgroundColor import com.adobe.marketing.mobile.notificationbuilder.internal.builders.extensions.setNotificationBodyTextColor import com.adobe.marketing.mobile.notificationbuilder.internal.builders.extensions.setNotificationClickAction diff --git a/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/BasicNotificationBuilder.kt b/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/BasicNotificationBuilder.kt index a8e38fe9..3da20cf8 100644 --- a/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/BasicNotificationBuilder.kt +++ b/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/BasicNotificationBuilder.kt @@ -20,8 +20,8 @@ import android.content.Intent import android.view.View import android.widget.RemoteViews import androidx.core.app.NotificationCompat -import com.adobe.marketing.mobile.notificationbuilder.R import com.adobe.marketing.mobile.notificationbuilder.NotificationConstructionFailedException +import com.adobe.marketing.mobile.notificationbuilder.R import com.adobe.marketing.mobile.notificationbuilder.internal.PushTemplateConstants import com.adobe.marketing.mobile.notificationbuilder.internal.PushTemplateImageUtils import com.adobe.marketing.mobile.notificationbuilder.internal.builders.extensions.addActionButtons diff --git a/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/InputBoxNotificationBuilder.kt b/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/InputBoxNotificationBuilder.kt index 18aed1bd..88d3bedc 100644 --- a/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/InputBoxNotificationBuilder.kt +++ b/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/InputBoxNotificationBuilder.kt @@ -21,8 +21,8 @@ import android.view.View import android.widget.RemoteViews import androidx.core.app.NotificationCompat import androidx.core.app.RemoteInput -import com.adobe.marketing.mobile.notificationbuilder.R import com.adobe.marketing.mobile.notificationbuilder.NotificationConstructionFailedException +import com.adobe.marketing.mobile.notificationbuilder.R import com.adobe.marketing.mobile.notificationbuilder.internal.PushTemplateConstants import com.adobe.marketing.mobile.notificationbuilder.internal.PushTemplateImageUtils import com.adobe.marketing.mobile.notificationbuilder.internal.builders.extensions.createNotificationChannelIfRequired diff --git a/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/ManualCarouselNotificationBuilder.kt b/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/ManualCarouselNotificationBuilder.kt index dcca342d..25a019b5 100644 --- a/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/ManualCarouselNotificationBuilder.kt +++ b/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/ManualCarouselNotificationBuilder.kt @@ -20,8 +20,8 @@ import android.content.Intent import android.graphics.Bitmap import android.widget.RemoteViews import androidx.core.app.NotificationCompat -import com.adobe.marketing.mobile.notificationbuilder.R import com.adobe.marketing.mobile.notificationbuilder.NotificationConstructionFailedException +import com.adobe.marketing.mobile.notificationbuilder.R import com.adobe.marketing.mobile.notificationbuilder.internal.PushTemplateConstants import com.adobe.marketing.mobile.notificationbuilder.internal.PushTemplateImageUtils import com.adobe.marketing.mobile.notificationbuilder.internal.builders.extensions.createNotificationChannelIfRequired diff --git a/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/ZeroBezelNotificationBuilder.kt b/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/ZeroBezelNotificationBuilder.kt index 25405f07..b8f4102c 100644 --- a/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/ZeroBezelNotificationBuilder.kt +++ b/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/ZeroBezelNotificationBuilder.kt @@ -17,8 +17,8 @@ import android.content.Context import android.view.View import android.widget.RemoteViews import androidx.core.app.NotificationCompat -import com.adobe.marketing.mobile.notificationbuilder.R import com.adobe.marketing.mobile.notificationbuilder.NotificationConstructionFailedException +import com.adobe.marketing.mobile.notificationbuilder.R import com.adobe.marketing.mobile.notificationbuilder.internal.PushTemplateConstants import com.adobe.marketing.mobile.notificationbuilder.internal.PushTemplateImageUtils import com.adobe.marketing.mobile.notificationbuilder.internal.builders.extensions.createNotificationChannelIfRequired From 74a6ae4d9fd704a528edd4372ee8f7dc1609df30 Mon Sep 17 00:00:00 2001 From: Ryan Morales Date: Mon, 20 May 2024 13:51:28 -0700 Subject: [PATCH 021/159] post migration cleanup, add support for dark / light mode - Added a PushTemplateIntentConstants class which exposes the Intent actions and keys KVP. Other internal KVP remain in PushTemplateConstants. - Fix log tags to match the updated class names - Add support for dark mode. Dark mode is supported on API29+ only and any custom color modifications made to the push payload will override the dark/light mode colors. The following keys will overwrite the dark/light settings: "adb_clr_body" / body text color, "adb_clr_title" / title text color, "adb_clr_bg" / notification background color, and "adb_clr_icon" / small icon color --- .../NotificationBuilder.kt | 3 +- .../PushTemplateIntentConstants.kt | 70 +++++++ .../internal/PushTemplateConstants.kt | 72 +------ .../builders/AEPPushNotificationBuilder.kt | 18 +- .../AutoCarouselNotificationBuilder.kt | 6 +- .../builders/BasicNotificationBuilder.kt | 197 +++++++++--------- .../builders/InputBoxNotificationBuilder.kt | 57 ++--- .../builders/LegacyNotificationBuilder.kt | 14 +- .../ManualCarouselNotificationBuilder.kt | 67 +++--- .../builders/ZeroBezelNotificationBuilder.kt | 4 +- .../extensions/AppResourceExtensions.kt | 2 +- .../NotificationCompatBuilderExtensions.kt | 2 +- .../NotificationManagerExtensions.kt | 6 +- .../extensions/RemoteViewsExtensions.kt | 2 +- .../internal/templates/AEPPushTemplate.kt | 58 +++--- .../internal/templates/BasicPushTemplate.kt | 9 +- .../templates/CarouselPushTemplate.kt | 7 +- .../templates/InputBoxPushTemplate.kt | 11 +- .../templates/ManualCarouselPushTemplate.kt | 3 +- .../templates/ProductCatalogPushTemplate.kt | 23 +- .../layout/push_template_auto_carousel.xml | 3 +- .../layout/push_template_carousel_item.xml | 3 +- .../res/layout/push_template_collapsed.xml | 3 +- .../res/layout/push_template_expanded.xml | 3 +- .../push_template_filmstrip_carousel.xml | 3 +- .../layout/push_template_manual_carousel.xml | 3 +- .../push_template_zero_bezel_collapsed.xml | 3 +- .../push_template_zero_bezel_expanded.xml | 3 +- .../src/main/res/values/colors.xml | 6 + .../src/main/res/values/styles.xml | 12 ++ 30 files changed, 366 insertions(+), 307 deletions(-) create mode 100644 code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/PushTemplateIntentConstants.kt create mode 100644 code/notificationbuilder/src/main/res/values/colors.xml diff --git a/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/NotificationBuilder.kt b/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/NotificationBuilder.kt index ea933d58..88b5e418 100644 --- a/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/NotificationBuilder.kt +++ b/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/NotificationBuilder.kt @@ -15,6 +15,7 @@ import android.app.Activity import android.content.BroadcastReceiver import android.content.Intent import androidx.core.app.NotificationCompat +import com.adobe.marketing.mobile.notificationbuilder.NotificationBuilder.constructNotificationBuilder import com.adobe.marketing.mobile.notificationbuilder.internal.PushTemplateConstants import com.adobe.marketing.mobile.notificationbuilder.internal.PushTemplateType import com.adobe.marketing.mobile.notificationbuilder.internal.builders.AutoCarouselNotificationBuilder @@ -146,7 +147,7 @@ object NotificationBuilder { val context = ServiceProvider.getInstance().appContextService.applicationContext ?: throw NotificationConstructionFailedException("Application context is null, cannot build a notification.") val pushTemplateType = - PushTemplateType.fromString(intent.getStringExtra(PushTemplateConstants.IntentKeys.TEMPLATE_TYPE)) + PushTemplateType.fromString(intent.getStringExtra(PushTemplateIntentConstants.IntentKeys.TEMPLATE_TYPE)) when (pushTemplateType) { PushTemplateType.BASIC -> { diff --git a/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/PushTemplateIntentConstants.kt b/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/PushTemplateIntentConstants.kt new file mode 100644 index 00000000..99a65982 --- /dev/null +++ b/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/PushTemplateIntentConstants.kt @@ -0,0 +1,70 @@ +/* + Copyright 2024 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.notificationbuilder + +object PushTemplateIntentConstants { + object IntentActions { + const val FILMSTRIP_LEFT_CLICKED = "filmstrip_left" + const val FILMSTRIP_RIGHT_CLICKED = "filmstrip_right" + const val REMIND_LATER_CLICKED = "remind_clicked" + const val MANUAL_CAROUSEL_LEFT_CLICKED = "manual_left" + const val MANUAL_CAROUSEL_RIGHT_CLICKED = "manual_right" + const val INPUT_RECEIVED = "input_received" + } + + object IntentKeys { + const val CENTER_IMAGE_INDEX = "centerImageIndex" + const val IMAGE_URI = "imageUri" + const val IMAGE_URLS = "imageUrls" + const val IMAGE_CAPTIONS = "imageCaptions" + const val IMAGE_CLICK_ACTIONS = "imageClickActions" + const val ACTION_URI = "actionUri" + const val ACTION_TYPE = "actionType" + const val CHANNEL_ID = "channelId" + const val CUSTOM_SOUND = "customSound" + const val TITLE_TEXT = "titleText" + const val BODY_TEXT = "bodyText" + const val EXPANDED_BODY_TEXT = "expandedBodyText" + const val NOTIFICATION_BACKGROUND_COLOR = "notificationBackgroundColor" + const val TITLE_TEXT_COLOR = "titleTextColor" + const val EXPANDED_BODY_TEXT_COLOR = "expandedBodyTextColor" + const val BADGE_COUNT = "badgeCount" + const val LARGE_ICON = "largeIcon" + const val SMALL_ICON = "smallIcon" + const val SMALL_ICON_COLOR = "smallIconColor" + const val PRIORITY = "priority" + const val VISIBILITY = "visibility" + const val IMPORTANCE = "importance" + const val REMIND_DELAY_SECONDS = "remindDelaySeconds" + const val REMIND_EPOCH_TS = "remindEpochTimestamp" + const val REMIND_LABEL = "remindLaterLabel" + const val ACTION_BUTTONS_STRING = "actionButtonsString" + const val STICKY = "sticky" + const val TAG = "tag" + const val TICKER = "ticker" + const val PAYLOAD_VERSION = "version" + const val TEMPLATE_TYPE = "templateType" + const val CAROUSEL_OPERATION_MODE = "carouselOperationMode" + const val CAROUSEL_LAYOUT_TYPE = "carouselLayoutType" + const val CAROUSEL_ITEMS = "carouselItems" + const val INPUT_BOX_HINT = "inputBoxHint" + const val INPUT_BOX_FEEDBACK_TEXT = "feedbackText" + const val INPUT_BOX_FEEDBACK_IMAGE = "feedbackImage" + const val INPUT_BOX_RECEIVER_NAME = "feedbackReceiverName" + const val CATALOG_CTA_BUTTON_TEXT = "ctaButtonText" + const val CATALOG_CTA_BUTTON_COLOR = "ctaButtonColor" + const val CATALOG_CTA_BUTTON_URI = "ctaButtonUri" + const val CATALOG_LAYOUT = "displayLayout" + const val CATALOG_ITEMS = "catalogItems" + const val CATALOG_ITEM_INDEX = "catalogIndex" + } +} diff --git a/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/PushTemplateConstants.kt b/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/PushTemplateConstants.kt index 87bb6839..03e498ae 100644 --- a/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/PushTemplateConstants.kt +++ b/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/PushTemplateConstants.kt @@ -17,16 +17,9 @@ import java.util.concurrent.TimeUnit * This object holds all constant values for handling out-of-the-box push template notifications */ internal object PushTemplateConstants { - const val LOG_TAG = "AEPSDKPushTemplates" + const val LOG_TAG = "PushTemplateConstants" const val CACHE_BASE_DIR = "pushtemplates" const val PUSH_IMAGE_CACHE = "pushimagecache" - const val DEFAULT_CHANNEL_ID = "AEPSDKPushChannel" - - // When no channel name is received from the push notification, this default channel name is - // used. - // This will appear in the notification settings for the app. - const val DEFAULT_CHANNEL_NAME = "AEPSDK Push Notifications" - const val SILENT_CHANNEL_NAME = "AEPSDK Silent Push Notifications" /** Enum to denote the type of action */ enum class ActionType { @@ -53,7 +46,12 @@ internal object PushTemplateConstants { } internal object DefaultValues { - const val SILENT_NOTIFICATION_CHANNEL_ID = "AEPSDK Silent Push Notifications" + // When no channel name is received from the push notification, this default channel name is used. + // This will appear in the notification settings for the app. + const val DEFAULT_CHANNEL_NAME = "AEPSDK Push Notifications" + const val SILENT_CHANNEL_NAME = "AEPSDK Silent Push Notifications" + const val DEFAULT_CHANNEL_ID = "AEPSDKPushChannel" + const val SILENT_NOTIFICATION_CHANNEL_ID = "AEPSDKSilentPushChannel" const val CAROUSEL_MAX_BITMAP_WIDTH = 300 const val CAROUSEL_MAX_BITMAP_HEIGHT = 200 const val AUTO_CAROUSEL_MODE = "auto" @@ -72,62 +70,6 @@ internal object PushTemplateConstants { TimeUnit.DAYS.toMillis(3) // 3 days } - internal object IntentActions { - const val FILMSTRIP_LEFT_CLICKED = "filmstrip_left" - const val FILMSTRIP_RIGHT_CLICKED = "filmstrip_right" - const val REMIND_LATER_CLICKED = "remind_clicked" - const val MANUAL_CAROUSEL_LEFT_CLICKED = "manual_left" - const val MANUAL_CAROUSEL_RIGHT_CLICKED = "manual_right" - const val INPUT_RECEIVED = "input_received" - } - - internal object IntentKeys { - const val CENTER_IMAGE_INDEX = "centerImageIndex" - const val IMAGE_URI = "imageUri" - const val IMAGE_URLS = "imageUrls" - const val IMAGE_CAPTIONS = "imageCaptions" - const val IMAGE_CLICK_ACTIONS = "imageClickActions" - const val ACTION_URI = "actionUri" - const val ACTION_TYPE = "actionType" - const val CHANNEL_ID = "channelId" - const val CUSTOM_SOUND = "customSound" - const val TITLE_TEXT = "titleText" - const val BODY_TEXT = "bodyText" - const val EXPANDED_BODY_TEXT = "expandedBodyText" - const val NOTIFICATION_BACKGROUND_COLOR = "notificationBackgroundColor" - const val TITLE_TEXT_COLOR = "titleTextColor" - const val EXPANDED_BODY_TEXT_COLOR = "expandedBodyTextColor" - const val BADGE_COUNT = "badgeCount" - const val LARGE_ICON = "largeIcon" - const val SMALL_ICON = "smallIcon" - const val SMALL_ICON_COLOR = "smallIconColor" - const val PRIORITY = "priority" - const val VISIBILITY = "visibility" - const val IMPORTANCE = "importance" - const val REMIND_DELAY_SECONDS = "remindDelaySeconds" - const val REMIND_EPOCH_TS = "remindEpochTimestamp" - const val REMIND_LABEL = "remindLaterLabel" - const val ACTION_BUTTONS_STRING = "actionButtonsString" - const val STICKY = "sticky" - const val TAG = "tag" - const val TICKER = "ticker" - const val PAYLOAD_VERSION = "version" - const val TEMPLATE_TYPE = "templateType" - const val CAROUSEL_OPERATION_MODE = "carouselOperationMode" - const val CAROUSEL_LAYOUT_TYPE = "carouselLayoutType" - const val CAROUSEL_ITEMS = "carouselItems" - const val INPUT_BOX_HINT = "inputBoxHint" - const val INPUT_BOX_FEEDBACK_TEXT = "feedbackText" - const val INPUT_BOX_FEEDBACK_IMAGE = "feedbackImage" - const val INPUT_BOX_RECEIVER_NAME = "feedbackReceiverName" - const val CATALOG_CTA_BUTTON_TEXT = "ctaButtonText" - const val CATALOG_CTA_BUTTON_COLOR = "ctaButtonColor" - const val CATALOG_CTA_BUTTON_URI = "ctaButtonUri" - const val CATALOG_LAYOUT = "displayLayout" - const val CATALOG_ITEMS = "catalogItems" - const val CATALOG_ITEM_INDEX = "catalogIndex" - } - internal object MethodNames { const val SET_BACKGROUND_COLOR = "setBackgroundColor" const val SET_TEXT_COLOR = "setTextColor" diff --git a/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/AEPPushNotificationBuilder.kt b/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/AEPPushNotificationBuilder.kt index 3f0e53f7..a7bd24ec 100644 --- a/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/AEPPushNotificationBuilder.kt +++ b/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/AEPPushNotificationBuilder.kt @@ -18,15 +18,15 @@ import android.widget.RemoteViews import androidx.core.app.NotificationCompat import com.adobe.marketing.mobile.notificationbuilder.NotificationConstructionFailedException import com.adobe.marketing.mobile.notificationbuilder.R -import com.adobe.marketing.mobile.notificationbuilder.internal.builders.extensions.setNotificationBackgroundColor -import com.adobe.marketing.mobile.notificationbuilder.internal.builders.extensions.setNotificationBodyTextColor -import com.adobe.marketing.mobile.notificationbuilder.internal.builders.extensions.setNotificationClickAction -import com.adobe.marketing.mobile.notificationbuilder.internal.builders.extensions.setNotificationDeleteAction -import com.adobe.marketing.mobile.notificationbuilder.internal.builders.extensions.setNotificationTitleTextColor -import com.adobe.marketing.mobile.notificationbuilder.internal.builders.extensions.setRemoteViewLargeIcon -import com.adobe.marketing.mobile.notificationbuilder.internal.builders.extensions.setSmallIcon -import com.adobe.marketing.mobile.notificationbuilder.internal.builders.extensions.setSound -import com.adobe.marketing.mobile.notificationbuilder.internal.builders.extensions.setVisibility +import com.adobe.marketing.mobile.notificationbuilder.internal.extensions.setNotificationBackgroundColor +import com.adobe.marketing.mobile.notificationbuilder.internal.extensions.setNotificationBodyTextColor +import com.adobe.marketing.mobile.notificationbuilder.internal.extensions.setNotificationClickAction +import com.adobe.marketing.mobile.notificationbuilder.internal.extensions.setNotificationDeleteAction +import com.adobe.marketing.mobile.notificationbuilder.internal.extensions.setNotificationTitleTextColor +import com.adobe.marketing.mobile.notificationbuilder.internal.extensions.setRemoteViewLargeIcon +import com.adobe.marketing.mobile.notificationbuilder.internal.extensions.setSmallIcon +import com.adobe.marketing.mobile.notificationbuilder.internal.extensions.setSound +import com.adobe.marketing.mobile.notificationbuilder.internal.extensions.setVisibility import com.adobe.marketing.mobile.notificationbuilder.internal.templates.AEPPushTemplate // TODO: The utilities provided by this builder assumes the id's for various common elements (R.id.basic_small_layout, diff --git a/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/AutoCarouselNotificationBuilder.kt b/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/AutoCarouselNotificationBuilder.kt index 3a217ccb..7d6fe12e 100644 --- a/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/AutoCarouselNotificationBuilder.kt +++ b/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/AutoCarouselNotificationBuilder.kt @@ -21,8 +21,8 @@ import androidx.core.app.NotificationCompat import com.adobe.marketing.mobile.notificationbuilder.R import com.adobe.marketing.mobile.notificationbuilder.internal.PushTemplateConstants import com.adobe.marketing.mobile.notificationbuilder.internal.PushTemplateImageUtils -import com.adobe.marketing.mobile.notificationbuilder.internal.builders.extensions.createNotificationChannelIfRequired -import com.adobe.marketing.mobile.notificationbuilder.internal.builders.extensions.setRemoteViewClickAction +import com.adobe.marketing.mobile.notificationbuilder.internal.extensions.createNotificationChannelIfRequired +import com.adobe.marketing.mobile.notificationbuilder.internal.extensions.setRemoteViewClickAction import com.adobe.marketing.mobile.notificationbuilder.internal.templates.AutoCarouselPushTemplate import com.adobe.marketing.mobile.notificationbuilder.internal.templates.CarouselPushTemplate import com.adobe.marketing.mobile.services.Log @@ -31,7 +31,7 @@ import com.adobe.marketing.mobile.services.Log * Object responsible for constructing a [NotificationCompat.Builder] object containing a auto carousel push template notification. */ internal object AutoCarouselNotificationBuilder { - private const val SELF_TAG = "AutoCarouselTemplateNotificationBuilder" + private const val SELF_TAG = "AutoCarouselNotificationBuilder" fun construct( context: Context, diff --git a/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/BasicNotificationBuilder.kt b/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/BasicNotificationBuilder.kt index 3da20cf8..a34bd04e 100644 --- a/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/BasicNotificationBuilder.kt +++ b/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/BasicNotificationBuilder.kt @@ -21,11 +21,12 @@ import android.view.View import android.widget.RemoteViews import androidx.core.app.NotificationCompat import com.adobe.marketing.mobile.notificationbuilder.NotificationConstructionFailedException +import com.adobe.marketing.mobile.notificationbuilder.PushTemplateIntentConstants import com.adobe.marketing.mobile.notificationbuilder.R import com.adobe.marketing.mobile.notificationbuilder.internal.PushTemplateConstants import com.adobe.marketing.mobile.notificationbuilder.internal.PushTemplateImageUtils -import com.adobe.marketing.mobile.notificationbuilder.internal.builders.extensions.addActionButtons -import com.adobe.marketing.mobile.notificationbuilder.internal.builders.extensions.createNotificationChannelIfRequired +import com.adobe.marketing.mobile.notificationbuilder.internal.extensions.addActionButtons +import com.adobe.marketing.mobile.notificationbuilder.internal.extensions.createNotificationChannelIfRequired import com.adobe.marketing.mobile.notificationbuilder.internal.templates.BasicPushTemplate import com.adobe.marketing.mobile.services.Log @@ -33,7 +34,7 @@ import com.adobe.marketing.mobile.services.Log * Object responsible for constructing a [NotificationCompat.Builder] object containing a basic push template notification. */ internal object BasicNotificationBuilder { - private const val SELF_TAG = "BasicTemplateNotificationBuilder" + private const val SELF_TAG = "BasicNotificationBuilder" @Throws(NotificationConstructionFailedException::class) fun construct( @@ -157,100 +158,104 @@ internal object BasicNotificationBuilder { "Creating a remind later pending intent from a push template object." ) - val remindIntent = Intent(PushTemplateConstants.IntentActions.REMIND_LATER_CLICKED).apply { - setClass(context.applicationContext, broadcastReceiverClass) + val remindIntent = + Intent(PushTemplateIntentConstants.IntentActions.REMIND_LATER_CLICKED).apply { + setClass(context.applicationContext, broadcastReceiverClass) - flags = Intent.FLAG_ACTIVITY_SINGLE_TOP - putExtra( - PushTemplateConstants.IntentKeys.TEMPLATE_TYPE, pushTemplate.templateType?.value - ) - putExtra( - PushTemplateConstants.IntentKeys.IMAGE_URI, pushTemplate.imageUrl - ) - putExtra( - PushTemplateConstants.IntentKeys.ACTION_URI, pushTemplate.actionUri - ) - putExtra(PushTemplateConstants.IntentKeys.CHANNEL_ID, channelId) - putExtra( - PushTemplateConstants.IntentKeys.CUSTOM_SOUND, pushTemplate.sound - ) - putExtra( - PushTemplateConstants.IntentKeys.TITLE_TEXT, - pushTemplate.title - ) - putExtra( - PushTemplateConstants.IntentKeys.BODY_TEXT, - pushTemplate.body - ) - putExtra( - PushTemplateConstants.IntentKeys.EXPANDED_BODY_TEXT, - pushTemplate.expandedBodyText - ) - putExtra( - PushTemplateConstants.IntentKeys.NOTIFICATION_BACKGROUND_COLOR, - pushTemplate.notificationBackgroundColor - ) - putExtra( - PushTemplateConstants.IntentKeys.TITLE_TEXT_COLOR, - pushTemplate.titleTextColor - ) - putExtra( - PushTemplateConstants.IntentKeys.EXPANDED_BODY_TEXT_COLOR, - pushTemplate.expandedBodyTextColor - ) - putExtra( - PushTemplateConstants.IntentKeys.SMALL_ICON, pushTemplate.smallIcon - ) - putExtra( - PushTemplateConstants.IntentKeys.SMALL_ICON_COLOR, - pushTemplate.smallIconColor - ) - putExtra( - PushTemplateConstants.IntentKeys.LARGE_ICON, pushTemplate.largeIcon - ) - putExtra( - PushTemplateConstants.IntentKeys.VISIBILITY, - pushTemplate.getNotificationVisibility() - ) - putExtra( - PushTemplateConstants.IntentKeys.IMPORTANCE, - pushTemplate.getNotificationImportance() - ) - putExtra( - PushTemplateConstants.IntentKeys.BADGE_COUNT, pushTemplate.badgeCount - ) - putExtra( - PushTemplateConstants.IntentKeys.REMIND_EPOCH_TS, - pushTemplate.remindLaterEpochTimestamp - ) - putExtra( - PushTemplateConstants.IntentKeys.REMIND_DELAY_SECONDS, - pushTemplate.remindLaterDelaySeconds - ) - putExtra( - PushTemplateConstants.IntentKeys.REMIND_LABEL, pushTemplate.remindLaterText - ) - putExtra( - PushTemplateConstants.IntentKeys.ACTION_BUTTONS_STRING, - pushTemplate.actionButtonsString - ) - putExtra( - PushTemplateConstants.IntentKeys.STICKY, pushTemplate.isNotificationSticky - ) - putExtra( - PushTemplateConstants.IntentKeys.TAG, pushTemplate.tag - ) - putExtra( - PushTemplateConstants.IntentKeys.TICKER, pushTemplate.ticker - ) - putExtra( - PushTemplateConstants.IntentKeys.PAYLOAD_VERSION, pushTemplate.payloadVersion - ) - putExtra( - PushTemplateConstants.IntentKeys.PRIORITY, - pushTemplate.notificationPriority - ) - } + flags = Intent.FLAG_ACTIVITY_SINGLE_TOP + putExtra( + PushTemplateIntentConstants.IntentKeys.TEMPLATE_TYPE, + pushTemplate.templateType?.value + ) + putExtra( + PushTemplateIntentConstants.IntentKeys.IMAGE_URI, pushTemplate.imageUrl + ) + putExtra( + PushTemplateIntentConstants.IntentKeys.ACTION_URI, pushTemplate.actionUri + ) + putExtra(PushTemplateIntentConstants.IntentKeys.CHANNEL_ID, channelId) + putExtra( + PushTemplateIntentConstants.IntentKeys.CUSTOM_SOUND, pushTemplate.sound + ) + putExtra( + PushTemplateIntentConstants.IntentKeys.TITLE_TEXT, + pushTemplate.title + ) + putExtra( + PushTemplateIntentConstants.IntentKeys.BODY_TEXT, + pushTemplate.body + ) + putExtra( + PushTemplateIntentConstants.IntentKeys.EXPANDED_BODY_TEXT, + pushTemplate.expandedBodyText + ) + putExtra( + PushTemplateIntentConstants.IntentKeys.NOTIFICATION_BACKGROUND_COLOR, + pushTemplate.notificationBackgroundColor + ) + putExtra( + PushTemplateIntentConstants.IntentKeys.TITLE_TEXT_COLOR, + pushTemplate.titleTextColor + ) + putExtra( + PushTemplateIntentConstants.IntentKeys.EXPANDED_BODY_TEXT_COLOR, + pushTemplate.expandedBodyTextColor + ) + putExtra( + PushTemplateIntentConstants.IntentKeys.SMALL_ICON, pushTemplate.smallIcon + ) + putExtra( + PushTemplateIntentConstants.IntentKeys.SMALL_ICON_COLOR, + pushTemplate.smallIconColor + ) + putExtra( + PushTemplateIntentConstants.IntentKeys.LARGE_ICON, pushTemplate.largeIcon + ) + putExtra( + PushTemplateIntentConstants.IntentKeys.VISIBILITY, + pushTemplate.getNotificationVisibility() + ) + putExtra( + PushTemplateIntentConstants.IntentKeys.IMPORTANCE, + pushTemplate.getNotificationImportance() + ) + putExtra( + PushTemplateIntentConstants.IntentKeys.BADGE_COUNT, pushTemplate.badgeCount + ) + putExtra( + PushTemplateIntentConstants.IntentKeys.REMIND_EPOCH_TS, + pushTemplate.remindLaterEpochTimestamp + ) + putExtra( + PushTemplateIntentConstants.IntentKeys.REMIND_DELAY_SECONDS, + pushTemplate.remindLaterDelaySeconds + ) + putExtra( + PushTemplateIntentConstants.IntentKeys.REMIND_LABEL, + pushTemplate.remindLaterText + ) + putExtra( + PushTemplateIntentConstants.IntentKeys.ACTION_BUTTONS_STRING, + pushTemplate.actionButtonsString + ) + putExtra( + PushTemplateIntentConstants.IntentKeys.STICKY, pushTemplate.isNotificationSticky + ) + putExtra( + PushTemplateIntentConstants.IntentKeys.TAG, pushTemplate.tag + ) + putExtra( + PushTemplateIntentConstants.IntentKeys.TICKER, pushTemplate.ticker + ) + putExtra( + PushTemplateIntentConstants.IntentKeys.PAYLOAD_VERSION, + pushTemplate.payloadVersion + ) + putExtra( + PushTemplateIntentConstants.IntentKeys.PRIORITY, + pushTemplate.notificationPriority + ) + } return PendingIntent.getBroadcast( context, diff --git a/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/InputBoxNotificationBuilder.kt b/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/InputBoxNotificationBuilder.kt index 88d3bedc..2a1071dc 100644 --- a/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/InputBoxNotificationBuilder.kt +++ b/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/InputBoxNotificationBuilder.kt @@ -22,10 +22,11 @@ import android.widget.RemoteViews import androidx.core.app.NotificationCompat import androidx.core.app.RemoteInput import com.adobe.marketing.mobile.notificationbuilder.NotificationConstructionFailedException +import com.adobe.marketing.mobile.notificationbuilder.PushTemplateIntentConstants import com.adobe.marketing.mobile.notificationbuilder.R import com.adobe.marketing.mobile.notificationbuilder.internal.PushTemplateConstants import com.adobe.marketing.mobile.notificationbuilder.internal.PushTemplateImageUtils -import com.adobe.marketing.mobile.notificationbuilder.internal.builders.extensions.createNotificationChannelIfRequired +import com.adobe.marketing.mobile.notificationbuilder.internal.extensions.createNotificationChannelIfRequired import com.adobe.marketing.mobile.notificationbuilder.internal.templates.InputBoxPushTemplate import com.adobe.marketing.mobile.services.Log @@ -184,94 +185,96 @@ internal object InputBoxNotificationBuilder { "Creating a text input received intent from a push template object." ) - val inputReceivedIntent = Intent(PushTemplateConstants.IntentActions.INPUT_RECEIVED) + val inputReceivedIntent = Intent(PushTemplateIntentConstants.IntentActions.INPUT_RECEIVED) broadcastReceiverClass.let { inputReceivedIntent.setClass(context.applicationContext, broadcastReceiverClass) } inputReceivedIntent.flags = Intent.FLAG_ACTIVITY_SINGLE_TOP inputReceivedIntent.putExtra( - PushTemplateConstants.IntentKeys.TEMPLATE_TYPE, pushTemplate.templateType?.value + PushTemplateIntentConstants.IntentKeys.TEMPLATE_TYPE, pushTemplate.templateType?.value ) inputReceivedIntent.putExtra( - PushTemplateConstants.IntentKeys.IMAGE_URI, pushTemplate.imageUrl + PushTemplateIntentConstants.IntentKeys.IMAGE_URI, pushTemplate.imageUrl ) inputReceivedIntent.putExtra( - PushTemplateConstants.IntentKeys.ACTION_URI, pushTemplate.actionUri + PushTemplateIntentConstants.IntentKeys.ACTION_URI, pushTemplate.actionUri ) - inputReceivedIntent.putExtra(PushTemplateConstants.IntentKeys.CHANNEL_ID, channelId) + inputReceivedIntent.putExtra(PushTemplateIntentConstants.IntentKeys.CHANNEL_ID, channelId) inputReceivedIntent.putExtra( - PushTemplateConstants.IntentKeys.CUSTOM_SOUND, pushTemplate.sound + PushTemplateIntentConstants.IntentKeys.CUSTOM_SOUND, pushTemplate.sound ) inputReceivedIntent.putExtra( - PushTemplateConstants.IntentKeys.TITLE_TEXT, + PushTemplateIntentConstants.IntentKeys.TITLE_TEXT, pushTemplate.title ) inputReceivedIntent.putExtra( - PushTemplateConstants.IntentKeys.BODY_TEXT, + PushTemplateIntentConstants.IntentKeys.BODY_TEXT, pushTemplate.body ) inputReceivedIntent.putExtra( - PushTemplateConstants.IntentKeys.EXPANDED_BODY_TEXT, + PushTemplateIntentConstants.IntentKeys.EXPANDED_BODY_TEXT, pushTemplate.expandedBodyText ) inputReceivedIntent.putExtra( - PushTemplateConstants.IntentKeys.NOTIFICATION_BACKGROUND_COLOR, + PushTemplateIntentConstants.IntentKeys.NOTIFICATION_BACKGROUND_COLOR, pushTemplate.notificationBackgroundColor ) inputReceivedIntent.putExtra( - PushTemplateConstants.IntentKeys.TITLE_TEXT_COLOR, + PushTemplateIntentConstants.IntentKeys.TITLE_TEXT_COLOR, pushTemplate.titleTextColor ) inputReceivedIntent.putExtra( - PushTemplateConstants.IntentKeys.EXPANDED_BODY_TEXT_COLOR, + PushTemplateIntentConstants.IntentKeys.EXPANDED_BODY_TEXT_COLOR, pushTemplate.expandedBodyTextColor ) inputReceivedIntent.putExtra( - PushTemplateConstants.IntentKeys.SMALL_ICON, pushTemplate.smallIcon + PushTemplateIntentConstants.IntentKeys.SMALL_ICON, pushTemplate.smallIcon ) inputReceivedIntent.putExtra( - PushTemplateConstants.IntentKeys.SMALL_ICON_COLOR, + PushTemplateIntentConstants.IntentKeys.SMALL_ICON_COLOR, pushTemplate.smallIconColor ) inputReceivedIntent.putExtra( - PushTemplateConstants.IntentKeys.LARGE_ICON, pushTemplate.largeIcon + PushTemplateIntentConstants.IntentKeys.LARGE_ICON, pushTemplate.largeIcon ) inputReceivedIntent.putExtra( - PushTemplateConstants.IntentKeys.VISIBILITY, + PushTemplateIntentConstants.IntentKeys.VISIBILITY, pushTemplate.getNotificationVisibility() ) inputReceivedIntent.putExtra( - PushTemplateConstants.IntentKeys.IMPORTANCE, + PushTemplateIntentConstants.IntentKeys.IMPORTANCE, pushTemplate.getNotificationImportance() ) inputReceivedIntent.putExtra( - PushTemplateConstants.IntentKeys.BADGE_COUNT, pushTemplate.badgeCount + PushTemplateIntentConstants.IntentKeys.BADGE_COUNT, pushTemplate.badgeCount ) inputReceivedIntent.putExtra( - PushTemplateConstants.IntentKeys.INPUT_BOX_FEEDBACK_TEXT, pushTemplate.feedbackText + PushTemplateIntentConstants.IntentKeys.INPUT_BOX_FEEDBACK_TEXT, + pushTemplate.feedbackText ) inputReceivedIntent.putExtra( - PushTemplateConstants.IntentKeys.INPUT_BOX_FEEDBACK_IMAGE, pushTemplate.feedbackImage + PushTemplateIntentConstants.IntentKeys.INPUT_BOX_FEEDBACK_IMAGE, + pushTemplate.feedbackImage ) inputReceivedIntent.putExtra( - PushTemplateConstants.IntentKeys.INPUT_BOX_RECEIVER_NAME, + PushTemplateIntentConstants.IntentKeys.INPUT_BOX_RECEIVER_NAME, pushTemplate.inputBoxReceiverName ) inputReceivedIntent.putExtra( - PushTemplateConstants.IntentKeys.STICKY, pushTemplate.isNotificationSticky + PushTemplateIntentConstants.IntentKeys.STICKY, pushTemplate.isNotificationSticky ) inputReceivedIntent.putExtra( - PushTemplateConstants.IntentKeys.TAG, pushTemplate.tag + PushTemplateIntentConstants.IntentKeys.TAG, pushTemplate.tag ) inputReceivedIntent.putExtra( - PushTemplateConstants.IntentKeys.TICKER, pushTemplate.ticker + PushTemplateIntentConstants.IntentKeys.TICKER, pushTemplate.ticker ) inputReceivedIntent.putExtra( - PushTemplateConstants.IntentKeys.PAYLOAD_VERSION, pushTemplate.payloadVersion + PushTemplateIntentConstants.IntentKeys.PAYLOAD_VERSION, pushTemplate.payloadVersion ) inputReceivedIntent.putExtra( - PushTemplateConstants.IntentKeys.PRIORITY, + PushTemplateIntentConstants.IntentKeys.PRIORITY, pushTemplate.notificationPriority ) diff --git a/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/LegacyNotificationBuilder.kt b/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/LegacyNotificationBuilder.kt index 51255cf0..9bf6f40d 100644 --- a/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/LegacyNotificationBuilder.kt +++ b/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/LegacyNotificationBuilder.kt @@ -17,13 +17,13 @@ import android.content.Context import android.os.Build import androidx.core.app.NotificationCompat import com.adobe.marketing.mobile.notificationbuilder.internal.PushTemplateConstants -import com.adobe.marketing.mobile.notificationbuilder.internal.builders.extensions.addActionButtons -import com.adobe.marketing.mobile.notificationbuilder.internal.builders.extensions.createNotificationChannelIfRequired -import com.adobe.marketing.mobile.notificationbuilder.internal.builders.extensions.setLargeIcon -import com.adobe.marketing.mobile.notificationbuilder.internal.builders.extensions.setNotificationClickAction -import com.adobe.marketing.mobile.notificationbuilder.internal.builders.extensions.setNotificationDeleteAction -import com.adobe.marketing.mobile.notificationbuilder.internal.builders.extensions.setSmallIcon -import com.adobe.marketing.mobile.notificationbuilder.internal.builders.extensions.setSound +import com.adobe.marketing.mobile.notificationbuilder.internal.extensions.addActionButtons +import com.adobe.marketing.mobile.notificationbuilder.internal.extensions.createNotificationChannelIfRequired +import com.adobe.marketing.mobile.notificationbuilder.internal.extensions.setLargeIcon +import com.adobe.marketing.mobile.notificationbuilder.internal.extensions.setNotificationClickAction +import com.adobe.marketing.mobile.notificationbuilder.internal.extensions.setNotificationDeleteAction +import com.adobe.marketing.mobile.notificationbuilder.internal.extensions.setSmallIcon +import com.adobe.marketing.mobile.notificationbuilder.internal.extensions.setSound import com.adobe.marketing.mobile.notificationbuilder.internal.templates.BasicPushTemplate import com.adobe.marketing.mobile.services.Log diff --git a/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/ManualCarouselNotificationBuilder.kt b/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/ManualCarouselNotificationBuilder.kt index 25a019b5..835d7f56 100644 --- a/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/ManualCarouselNotificationBuilder.kt +++ b/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/ManualCarouselNotificationBuilder.kt @@ -21,11 +21,12 @@ import android.graphics.Bitmap import android.widget.RemoteViews import androidx.core.app.NotificationCompat import com.adobe.marketing.mobile.notificationbuilder.NotificationConstructionFailedException +import com.adobe.marketing.mobile.notificationbuilder.PushTemplateIntentConstants import com.adobe.marketing.mobile.notificationbuilder.R import com.adobe.marketing.mobile.notificationbuilder.internal.PushTemplateConstants import com.adobe.marketing.mobile.notificationbuilder.internal.PushTemplateImageUtils -import com.adobe.marketing.mobile.notificationbuilder.internal.builders.extensions.createNotificationChannelIfRequired -import com.adobe.marketing.mobile.notificationbuilder.internal.builders.extensions.setRemoteViewClickAction +import com.adobe.marketing.mobile.notificationbuilder.internal.extensions.createNotificationChannelIfRequired +import com.adobe.marketing.mobile.notificationbuilder.internal.extensions.setRemoteViewClickAction import com.adobe.marketing.mobile.notificationbuilder.internal.templates.CarouselPushTemplate import com.adobe.marketing.mobile.notificationbuilder.internal.templates.ManualCarouselPushTemplate import com.adobe.marketing.mobile.services.Log @@ -35,7 +36,7 @@ import com.adobe.marketing.mobile.services.caching.CacheService * Object responsible for constructing a [NotificationCompat.Builder] object containing a manual or filmstrip carousel push template notification. */ internal object ManualCarouselNotificationBuilder { - private const val SELF_TAG = "ManualCarouselTemplateNotificationBuilder" + private const val SELF_TAG = "ManualCarouselNotificationBuilder" @Throws(NotificationConstructionFailedException::class) fun construct( @@ -185,7 +186,7 @@ internal object ManualCarouselNotificationBuilder { val carouselIndices: Triple if (pushTemplate.intentAction?.isNotEmpty() == true) { carouselIndices = - if (pushTemplate.intentAction == PushTemplateConstants.IntentActions.MANUAL_CAROUSEL_LEFT_CLICKED || pushTemplate.intentAction == PushTemplateConstants.IntentActions.FILMSTRIP_LEFT_CLICKED) { + if (pushTemplate.intentAction == PushTemplateIntentConstants.IntentActions.MANUAL_CAROUSEL_LEFT_CLICKED || pushTemplate.intentAction == PushTemplateIntentConstants.IntentActions.FILMSTRIP_LEFT_CLICKED) { getNewIndicesForNavigateLeft(pushTemplate.centerImageIndex, imageUris.size) } else { getNewIndicesForNavigateRight(pushTemplate.centerImageIndex, imageUris.size) @@ -260,13 +261,13 @@ internal object ManualCarouselNotificationBuilder { val clickPair = if (pushTemplate.carouselLayoutType == PushTemplateConstants.DefaultValues.DEFAULT_MANUAL_CAROUSEL_MODE) { Pair( - PushTemplateConstants.IntentActions.MANUAL_CAROUSEL_LEFT_CLICKED, - PushTemplateConstants.IntentActions.MANUAL_CAROUSEL_RIGHT_CLICKED + PushTemplateIntentConstants.IntentActions.MANUAL_CAROUSEL_LEFT_CLICKED, + PushTemplateIntentConstants.IntentActions.MANUAL_CAROUSEL_RIGHT_CLICKED ) } else { Pair( - PushTemplateConstants.IntentActions.FILMSTRIP_LEFT_CLICKED, - PushTemplateConstants.IntentActions.FILMSTRIP_RIGHT_CLICKED + PushTemplateIntentConstants.IntentActions.FILMSTRIP_LEFT_CLICKED, + PushTemplateIntentConstants.IntentActions.FILMSTRIP_RIGHT_CLICKED ) } @@ -508,87 +509,87 @@ internal object ManualCarouselNotificationBuilder { flags = Intent.FLAG_ACTIVITY_SINGLE_TOP putExtra( - PushTemplateConstants.IntentKeys.TEMPLATE_TYPE, + PushTemplateIntentConstants.IntentKeys.TEMPLATE_TYPE, pushTemplate.templateType?.value ) putExtra( - PushTemplateConstants.IntentKeys.CHANNEL_ID, + PushTemplateIntentConstants.IntentKeys.CHANNEL_ID, channelId ) putExtra( - PushTemplateConstants.IntentKeys.CUSTOM_SOUND, pushTemplate.sound + PushTemplateIntentConstants.IntentKeys.CUSTOM_SOUND, pushTemplate.sound ) putExtra( - PushTemplateConstants.IntentKeys.CENTER_IMAGE_INDEX, + PushTemplateIntentConstants.IntentKeys.CENTER_IMAGE_INDEX, pushTemplate.centerImageIndex ) putExtra( - PushTemplateConstants.IntentKeys.IMAGE_URLS, + PushTemplateIntentConstants.IntentKeys.IMAGE_URLS, downloadedImageUris.toTypedArray() ) putExtra( - PushTemplateConstants.IntentKeys.IMAGE_CAPTIONS, + PushTemplateIntentConstants.IntentKeys.IMAGE_CAPTIONS, imageCaptions.toTypedArray() ) putExtra( - PushTemplateConstants.IntentKeys.IMAGE_CLICK_ACTIONS, + PushTemplateIntentConstants.IntentKeys.IMAGE_CLICK_ACTIONS, imageClickActions.toTypedArray() ) - putExtra(PushTemplateConstants.IntentKeys.TITLE_TEXT, pushTemplate.title) - putExtra(PushTemplateConstants.IntentKeys.BODY_TEXT, pushTemplate.body) + putExtra(PushTemplateIntentConstants.IntentKeys.TITLE_TEXT, pushTemplate.title) + putExtra(PushTemplateIntentConstants.IntentKeys.BODY_TEXT, pushTemplate.body) putExtra( - PushTemplateConstants.IntentKeys.EXPANDED_BODY_TEXT, + PushTemplateIntentConstants.IntentKeys.EXPANDED_BODY_TEXT, pushTemplate.expandedBodyText ) putExtra( - PushTemplateConstants.IntentKeys.NOTIFICATION_BACKGROUND_COLOR, + PushTemplateIntentConstants.IntentKeys.NOTIFICATION_BACKGROUND_COLOR, pushTemplate.notificationBackgroundColor ) putExtra( - PushTemplateConstants.IntentKeys.TITLE_TEXT_COLOR, + PushTemplateIntentConstants.IntentKeys.TITLE_TEXT_COLOR, pushTemplate.titleTextColor ) putExtra( - PushTemplateConstants.IntentKeys.EXPANDED_BODY_TEXT_COLOR, + PushTemplateIntentConstants.IntentKeys.EXPANDED_BODY_TEXT_COLOR, pushTemplate.expandedBodyTextColor ) putExtra( - PushTemplateConstants.IntentKeys.SMALL_ICON, pushTemplate.smallIcon + PushTemplateIntentConstants.IntentKeys.SMALL_ICON, pushTemplate.smallIcon ) putExtra( - PushTemplateConstants.IntentKeys.LARGE_ICON, pushTemplate.largeIcon + PushTemplateIntentConstants.IntentKeys.LARGE_ICON, pushTemplate.largeIcon ) putExtra( - PushTemplateConstants.IntentKeys.SMALL_ICON_COLOR, + PushTemplateIntentConstants.IntentKeys.SMALL_ICON_COLOR, pushTemplate.smallIconColor ) putExtra( - PushTemplateConstants.IntentKeys.VISIBILITY, + PushTemplateIntentConstants.IntentKeys.VISIBILITY, pushTemplate.getNotificationVisibility() ) putExtra( - PushTemplateConstants.IntentKeys.IMPORTANCE, + PushTemplateIntentConstants.IntentKeys.IMPORTANCE, pushTemplate.getNotificationImportance() ) putExtra( - PushTemplateConstants.IntentKeys.TICKER, pushTemplate.ticker + PushTemplateIntentConstants.IntentKeys.TICKER, pushTemplate.ticker ) putExtra( - PushTemplateConstants.IntentKeys.TAG, pushTemplate.tag + PushTemplateIntentConstants.IntentKeys.TAG, pushTemplate.tag ) putExtra( - PushTemplateConstants.IntentKeys.STICKY, pushTemplate.isNotificationSticky + PushTemplateIntentConstants.IntentKeys.STICKY, pushTemplate.isNotificationSticky ) - putExtra(PushTemplateConstants.IntentKeys.ACTION_URI, pushTemplate.actionUri) + putExtra(PushTemplateIntentConstants.IntentKeys.ACTION_URI, pushTemplate.actionUri) putExtra( - PushTemplateConstants.IntentKeys.PAYLOAD_VERSION, pushTemplate.payloadVersion + PushTemplateIntentConstants.IntentKeys.PAYLOAD_VERSION, pushTemplate.payloadVersion ) putExtra( - PushTemplateConstants.IntentKeys.CAROUSEL_ITEMS, + PushTemplateIntentConstants.IntentKeys.CAROUSEL_ITEMS, pushTemplate.rawCarouselItems ) putExtra( - PushTemplateConstants.IntentKeys.CAROUSEL_LAYOUT_TYPE, + PushTemplateIntentConstants.IntentKeys.CAROUSEL_LAYOUT_TYPE, pushTemplate.carouselLayoutType ) } diff --git a/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/ZeroBezelNotificationBuilder.kt b/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/ZeroBezelNotificationBuilder.kt index b8f4102c..9c43ddf6 100644 --- a/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/ZeroBezelNotificationBuilder.kt +++ b/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/ZeroBezelNotificationBuilder.kt @@ -21,12 +21,12 @@ import com.adobe.marketing.mobile.notificationbuilder.NotificationConstructionFa import com.adobe.marketing.mobile.notificationbuilder.R import com.adobe.marketing.mobile.notificationbuilder.internal.PushTemplateConstants import com.adobe.marketing.mobile.notificationbuilder.internal.PushTemplateImageUtils -import com.adobe.marketing.mobile.notificationbuilder.internal.builders.extensions.createNotificationChannelIfRequired +import com.adobe.marketing.mobile.notificationbuilder.internal.extensions.createNotificationChannelIfRequired import com.adobe.marketing.mobile.notificationbuilder.internal.templates.ZeroBezelPushTemplate import com.adobe.marketing.mobile.services.Log internal object ZeroBezelNotificationBuilder { - private const val SELF_TAG = "BasicTemplateNotificationBuilder" + private const val SELF_TAG = "ZeroBezelNotificationBuilder" @Throws(NotificationConstructionFailedException::class) fun construct( diff --git a/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/extensions/AppResourceExtensions.kt b/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/extensions/AppResourceExtensions.kt index 169bd2d4..a569ed5c 100644 --- a/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/extensions/AppResourceExtensions.kt +++ b/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/extensions/AppResourceExtensions.kt @@ -9,7 +9,7 @@ governing permissions and limitations under the License. */ -package com.adobe.marketing.mobile.notificationbuilder.internal.builders.extensions +package com.adobe.marketing.mobile.notificationbuilder.internal.extensions import android.content.ContentResolver import android.content.Context diff --git a/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/extensions/NotificationCompatBuilderExtensions.kt b/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/extensions/NotificationCompatBuilderExtensions.kt index d80bb7f2..6092eb73 100644 --- a/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/extensions/NotificationCompatBuilderExtensions.kt +++ b/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/extensions/NotificationCompatBuilderExtensions.kt @@ -9,7 +9,7 @@ governing permissions and limitations under the License. */ -package com.adobe.marketing.mobile.notificationbuilder.internal.builders.extensions +package com.adobe.marketing.mobile.notificationbuilder.internal.extensions import android.app.Activity import android.app.PendingIntent diff --git a/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/extensions/NotificationManagerExtensions.kt b/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/extensions/NotificationManagerExtensions.kt index 9cba2531..aee2b943 100644 --- a/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/extensions/NotificationManagerExtensions.kt +++ b/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/extensions/NotificationManagerExtensions.kt @@ -9,7 +9,7 @@ governing permissions and limitations under the License. */ -package com.adobe.marketing.mobile.notificationbuilder.internal.builders.extensions +package com.adobe.marketing.mobile.notificationbuilder.internal.extensions import android.app.NotificationChannel import android.app.NotificationManager @@ -44,7 +44,7 @@ internal fun NotificationManager.createNotificationChannelIfRequired( // if not from intent and channel id is not provided, use the default channel id val channelIdToUse = if (isFromIntent) PushTemplateConstants.DefaultValues.SILENT_NOTIFICATION_CHANNEL_ID - else channelId ?: PushTemplateConstants.DEFAULT_CHANNEL_ID + else channelId ?: PushTemplateConstants.DefaultValues.DEFAULT_CHANNEL_ID // No channel creation required. if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) { @@ -64,7 +64,7 @@ internal fun NotificationManager.createNotificationChannelIfRequired( // Create a channel val channel = NotificationChannel( channelIdToUse, - if (isFromIntent) PushTemplateConstants.SILENT_CHANNEL_NAME else PushTemplateConstants.DEFAULT_CHANNEL_NAME, + if (isFromIntent) PushTemplateConstants.DefaultValues.SILENT_CHANNEL_NAME else PushTemplateConstants.DefaultValues.DEFAULT_CHANNEL_NAME, importance ) diff --git a/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/extensions/RemoteViewsExtensions.kt b/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/extensions/RemoteViewsExtensions.kt index 21a157c3..30a5488f 100644 --- a/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/extensions/RemoteViewsExtensions.kt +++ b/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/extensions/RemoteViewsExtensions.kt @@ -9,7 +9,7 @@ governing permissions and limitations under the License. */ -package com.adobe.marketing.mobile.notificationbuilder.internal.builders.extensions +package com.adobe.marketing.mobile.notificationbuilder.internal.extensions import android.app.Activity import android.app.PendingIntent diff --git a/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/templates/AEPPushTemplate.kt b/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/templates/AEPPushTemplate.kt index c3e96d81..3d5d4d86 100644 --- a/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/templates/AEPPushTemplate.kt +++ b/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/templates/AEPPushTemplate.kt @@ -16,6 +16,7 @@ import android.content.Intent import android.os.Build import androidx.annotation.RequiresApi import androidx.core.app.NotificationCompat +import com.adobe.marketing.mobile.notificationbuilder.PushTemplateIntentConstants import com.adobe.marketing.mobile.notificationbuilder.internal.NotificationPriority import com.adobe.marketing.mobile.notificationbuilder.internal.NotificationVisibility import com.adobe.marketing.mobile.notificationbuilder.internal.PushTemplateConstants @@ -256,43 +257,48 @@ internal sealed class AEPPushTemplate { val intentExtras = intent.extras ?: throw IllegalArgumentException("Intent extras are null.") // required values - title = intentExtras.getString(PushTemplateConstants.IntentKeys.TITLE_TEXT) - ?: throw IllegalArgumentException("Required field \"${PushTemplateConstants.IntentKeys.TITLE_TEXT}\" not found.") - body = intentExtras.getString(PushTemplateConstants.IntentKeys.BODY_TEXT) - ?: throw IllegalArgumentException("Required field \"${PushTemplateConstants.IntentKeys.BODY_TEXT}\" not found.") - payloadVersion = intentExtras.getInt(PushTemplateConstants.IntentKeys.PAYLOAD_VERSION) + title = intentExtras.getString(PushTemplateIntentConstants.IntentKeys.TITLE_TEXT) + ?: throw IllegalArgumentException("Required field \"${PushTemplateIntentConstants.IntentKeys.TITLE_TEXT}\" not found.") + body = intentExtras.getString(PushTemplateIntentConstants.IntentKeys.BODY_TEXT) + ?: throw IllegalArgumentException("Required field \"${PushTemplateIntentConstants.IntentKeys.BODY_TEXT}\" not found.") + payloadVersion = intentExtras.getInt(PushTemplateIntentConstants.IntentKeys.PAYLOAD_VERSION) payloadVersion?.let { - if (it < 1) throw IllegalArgumentException("Invalid \"${PushTemplateConstants.IntentKeys.PAYLOAD_VERSION}\" found.") + if (it < 1) throw IllegalArgumentException("Invalid \"${PushTemplateIntentConstants.IntentKeys.PAYLOAD_VERSION}\" found.") } // optional values - sound = intentExtras.getString(PushTemplateConstants.IntentKeys.CUSTOM_SOUND) - imageUrl = intentExtras.getString(PushTemplateConstants.IntentKeys.IMAGE_URI) - actionUri = intentExtras.getString(PushTemplateConstants.IntentKeys.ACTION_URI) + sound = intentExtras.getString(PushTemplateIntentConstants.IntentKeys.CUSTOM_SOUND) + imageUrl = intentExtras.getString(PushTemplateIntentConstants.IntentKeys.IMAGE_URI) + actionUri = intentExtras.getString(PushTemplateIntentConstants.IntentKeys.ACTION_URI) actionType = PushTemplateConstants.ActionType.valueOf( - intentExtras.getString(PushTemplateConstants.IntentKeys.ACTION_TYPE) + intentExtras.getString(PushTemplateIntentConstants.IntentKeys.ACTION_TYPE) ?: PushTemplateConstants.ActionType.NONE.name ) - smallIcon = intentExtras.getString(PushTemplateConstants.IntentKeys.SMALL_ICON) - largeIcon = intentExtras.getString(PushTemplateConstants.IntentKeys.LARGE_ICON) - badgeCount = intentExtras.getInt(PushTemplateConstants.IntentKeys.BADGE_COUNT) - notificationPriority = intentExtras.getInt(PushTemplateConstants.IntentKeys.PRIORITY) - notificationImportance = intentExtras.getInt(PushTemplateConstants.IntentKeys.IMPORTANCE) - notificationVisibility = intentExtras.getInt(PushTemplateConstants.IntentKeys.VISIBILITY) - channelId = intentExtras.getString(PushTemplateConstants.IntentKeys.CHANNEL_ID) + smallIcon = intentExtras.getString(PushTemplateIntentConstants.IntentKeys.SMALL_ICON) + largeIcon = intentExtras.getString(PushTemplateIntentConstants.IntentKeys.LARGE_ICON) + badgeCount = intentExtras.getInt(PushTemplateIntentConstants.IntentKeys.BADGE_COUNT) + notificationPriority = intentExtras.getInt(PushTemplateIntentConstants.IntentKeys.PRIORITY) + notificationImportance = + intentExtras.getInt(PushTemplateIntentConstants.IntentKeys.IMPORTANCE) + notificationVisibility = + intentExtras.getInt(PushTemplateIntentConstants.IntentKeys.VISIBILITY) + channelId = intentExtras.getString(PushTemplateIntentConstants.IntentKeys.CHANNEL_ID) templateType = - PushTemplateType.fromString(intentExtras.getString(PushTemplateConstants.IntentKeys.TEMPLATE_TYPE)) - tag = intentExtras.getString(PushTemplateConstants.IntentKeys.TAG) - isNotificationSticky = intentExtras.getBoolean(PushTemplateConstants.IntentKeys.STICKY) - ticker = intentExtras.getString(PushTemplateConstants.IntentKeys.TICKER) + PushTemplateType.fromString(intentExtras.getString(PushTemplateIntentConstants.IntentKeys.TEMPLATE_TYPE)) + tag = intentExtras.getString(PushTemplateIntentConstants.IntentKeys.TAG) + isNotificationSticky = + intentExtras.getBoolean(PushTemplateIntentConstants.IntentKeys.STICKY) + ticker = intentExtras.getString(PushTemplateIntentConstants.IntentKeys.TICKER) expandedBodyText = - intentExtras.getString(PushTemplateConstants.IntentKeys.EXPANDED_BODY_TEXT) + intentExtras.getString(PushTemplateIntentConstants.IntentKeys.EXPANDED_BODY_TEXT) expandedBodyTextColor = - intentExtras.getString(PushTemplateConstants.IntentKeys.EXPANDED_BODY_TEXT_COLOR) - titleTextColor = intentExtras.getString(PushTemplateConstants.IntentKeys.TITLE_TEXT_COLOR) - smallIconColor = intentExtras.getString(PushTemplateConstants.IntentKeys.SMALL_ICON_COLOR) + intentExtras.getString(PushTemplateIntentConstants.IntentKeys.EXPANDED_BODY_TEXT_COLOR) + titleTextColor = + intentExtras.getString(PushTemplateIntentConstants.IntentKeys.TITLE_TEXT_COLOR) + smallIconColor = + intentExtras.getString(PushTemplateIntentConstants.IntentKeys.SMALL_ICON_COLOR) notificationBackgroundColor = - intentExtras.getString(PushTemplateConstants.IntentKeys.NOTIFICATION_BACKGROUND_COLOR) + intentExtras.getString(PushTemplateIntentConstants.IntentKeys.NOTIFICATION_BACKGROUND_COLOR) isFromIntent = true } diff --git a/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/templates/BasicPushTemplate.kt b/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/templates/BasicPushTemplate.kt index 954be915..cb00b11f 100644 --- a/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/templates/BasicPushTemplate.kt +++ b/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/templates/BasicPushTemplate.kt @@ -13,6 +13,7 @@ package com.adobe.marketing.mobile.notificationbuilder.internal.templates import android.content.Intent import androidx.annotation.VisibleForTesting +import com.adobe.marketing.mobile.notificationbuilder.PushTemplateIntentConstants import com.adobe.marketing.mobile.notificationbuilder.internal.PushTemplateConstants import com.adobe.marketing.mobile.services.Log import com.adobe.marketing.mobile.util.DataReader @@ -130,14 +131,14 @@ internal class BasicPushTemplate : AEPPushTemplate { val intentExtras = intent.extras ?: throw IllegalArgumentException("Intent extras are null") actionButtonsString = - intentExtras.getString(PushTemplateConstants.IntentKeys.ACTION_BUTTONS_STRING) + intentExtras.getString(PushTemplateIntentConstants.IntentKeys.ACTION_BUTTONS_STRING) actionButtonsList = getActionButtonsFromString(actionButtonsString) remindLaterEpochTimestamp = - intentExtras.getLong(PushTemplateConstants.IntentKeys.REMIND_EPOCH_TS) + intentExtras.getLong(PushTemplateIntentConstants.IntentKeys.REMIND_EPOCH_TS) remindLaterDelaySeconds = - intentExtras.getInt(PushTemplateConstants.IntentKeys.REMIND_DELAY_SECONDS) + intentExtras.getInt(PushTemplateIntentConstants.IntentKeys.REMIND_DELAY_SECONDS) remindLaterText = - intentExtras.getString(PushTemplateConstants.IntentKeys.REMIND_LABEL) + intentExtras.getString(PushTemplateIntentConstants.IntentKeys.REMIND_LABEL) } /** diff --git a/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/templates/CarouselPushTemplate.kt b/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/templates/CarouselPushTemplate.kt index ed62920d..9debc7f0 100644 --- a/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/templates/CarouselPushTemplate.kt +++ b/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/templates/CarouselPushTemplate.kt @@ -12,6 +12,7 @@ package com.adobe.marketing.mobile.notificationbuilder.internal.templates import android.content.Intent +import com.adobe.marketing.mobile.notificationbuilder.PushTemplateIntentConstants import com.adobe.marketing.mobile.notificationbuilder.internal.PushTemplateConstants import com.adobe.marketing.mobile.services.Log import com.adobe.marketing.mobile.util.DataReader @@ -68,13 +69,13 @@ internal open class CarouselPushTemplate : AEPPushTemplate { val intentExtras = intent.extras ?: throw IllegalArgumentException("Intent extras are null") carouselOperationMode = - intentExtras.getString(PushTemplateConstants.IntentKeys.CAROUSEL_OPERATION_MODE) + intentExtras.getString(PushTemplateIntentConstants.IntentKeys.CAROUSEL_OPERATION_MODE) ?: PushTemplateConstants.DefaultValues.AUTO_CAROUSEL_MODE carouselLayoutType = - intentExtras.getString(PushTemplateConstants.IntentKeys.CAROUSEL_LAYOUT_TYPE) + intentExtras.getString(PushTemplateIntentConstants.IntentKeys.CAROUSEL_LAYOUT_TYPE) ?: PushTemplateConstants.DefaultValues.DEFAULT_MANUAL_CAROUSEL_MODE rawCarouselItems = - intentExtras.getString(PushTemplateConstants.IntentKeys.CAROUSEL_ITEMS) ?: "" + intentExtras.getString(PushTemplateIntentConstants.IntentKeys.CAROUSEL_ITEMS) ?: "" carouselItems = parseCarouselItemsFromString(rawCarouselItems) } diff --git a/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/templates/InputBoxPushTemplate.kt b/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/templates/InputBoxPushTemplate.kt index 46e0b5e2..d4273d1d 100644 --- a/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/templates/InputBoxPushTemplate.kt +++ b/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/templates/InputBoxPushTemplate.kt @@ -12,6 +12,7 @@ package com.adobe.marketing.mobile.notificationbuilder.internal.templates import android.content.Intent +import com.adobe.marketing.mobile.notificationbuilder.PushTemplateIntentConstants import com.adobe.marketing.mobile.notificationbuilder.internal.PushTemplateConstants import com.adobe.marketing.mobile.util.DataReader @@ -55,16 +56,16 @@ internal class InputBoxPushTemplate : AEPPushTemplate { val intentExtras = intent.extras ?: throw IllegalArgumentException("Intent extras are null") val receiverName = - intentExtras.getString(PushTemplateConstants.IntentKeys.INPUT_BOX_RECEIVER_NAME) + intentExtras.getString(PushTemplateIntentConstants.IntentKeys.INPUT_BOX_RECEIVER_NAME) inputBoxReceiverName = receiverName - ?: throw IllegalArgumentException("Required field \"${PushTemplateConstants.IntentKeys.INPUT_BOX_RECEIVER_NAME}\" not found.") + ?: throw IllegalArgumentException("Required field \"${PushTemplateIntentConstants.IntentKeys.INPUT_BOX_RECEIVER_NAME}\" not found.") inputTextHint = intentExtras.getString( - PushTemplateConstants.IntentKeys.INPUT_BOX_HINT, + PushTemplateIntentConstants.IntentKeys.INPUT_BOX_HINT, PushTemplateConstants.DefaultValues.INPUT_BOX_DEFAULT_REPLY_TEXT ) feedbackText = - intentExtras.getString(PushTemplateConstants.IntentKeys.INPUT_BOX_FEEDBACK_TEXT) + intentExtras.getString(PushTemplateIntentConstants.IntentKeys.INPUT_BOX_FEEDBACK_TEXT) feedbackImage = - intentExtras.getString(PushTemplateConstants.IntentKeys.INPUT_BOX_FEEDBACK_IMAGE) + intentExtras.getString(PushTemplateIntentConstants.IntentKeys.INPUT_BOX_FEEDBACK_IMAGE) } } diff --git a/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/templates/ManualCarouselPushTemplate.kt b/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/templates/ManualCarouselPushTemplate.kt index d9d87709..b09a003c 100644 --- a/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/templates/ManualCarouselPushTemplate.kt +++ b/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/templates/ManualCarouselPushTemplate.kt @@ -12,6 +12,7 @@ package com.adobe.marketing.mobile.notificationbuilder.internal.templates import android.content.Intent +import com.adobe.marketing.mobile.notificationbuilder.PushTemplateIntentConstants import com.adobe.marketing.mobile.notificationbuilder.internal.PushTemplateConstants internal class ManualCarouselPushTemplate : CarouselPushTemplate { @@ -26,7 +27,7 @@ internal class ManualCarouselPushTemplate : CarouselPushTemplate { constructor(intent: Intent) : super(intent) { intentAction = intent.action centerImageIndex = intent.getIntExtra( - PushTemplateConstants.IntentKeys.CENTER_IMAGE_INDEX, + PushTemplateIntentConstants.IntentKeys.CENTER_IMAGE_INDEX, getDefaultCarouselIndex(carouselLayoutType) ) } diff --git a/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/templates/ProductCatalogPushTemplate.kt b/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/templates/ProductCatalogPushTemplate.kt index ddf6c561..7cc195b1 100644 --- a/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/templates/ProductCatalogPushTemplate.kt +++ b/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/templates/ProductCatalogPushTemplate.kt @@ -12,6 +12,7 @@ package com.adobe.marketing.mobile.notificationbuilder.internal.templates import android.content.Intent +import com.adobe.marketing.mobile.notificationbuilder.PushTemplateIntentConstants import com.adobe.marketing.mobile.notificationbuilder.internal.PushTemplateConstants import com.adobe.marketing.mobile.services.Log import com.adobe.marketing.mobile.util.DataReader @@ -90,26 +91,26 @@ internal class ProductCatalogPushTemplate : AEPPushTemplate { constructor(intent: Intent) : super(intent) { val buttonText = - intent.getStringExtra(PushTemplateConstants.IntentKeys.CATALOG_CTA_BUTTON_TEXT) + intent.getStringExtra(PushTemplateIntentConstants.IntentKeys.CATALOG_CTA_BUTTON_TEXT) ctaButtonText = buttonText - ?: throw IllegalArgumentException("Required field \"${PushTemplateConstants.IntentKeys.CATALOG_CTA_BUTTON_TEXT}\" not found.") + ?: throw IllegalArgumentException("Required field \"${PushTemplateIntentConstants.IntentKeys.CATALOG_CTA_BUTTON_TEXT}\" not found.") val buttonColor = - intent.getStringExtra(PushTemplateConstants.IntentKeys.CATALOG_CTA_BUTTON_COLOR) + intent.getStringExtra(PushTemplateIntentConstants.IntentKeys.CATALOG_CTA_BUTTON_COLOR) ctaButtonColor = buttonColor - ?: throw IllegalArgumentException("Required field \"${PushTemplateConstants.IntentKeys.CATALOG_CTA_BUTTON_COLOR}\" not found.") + ?: throw IllegalArgumentException("Required field \"${PushTemplateIntentConstants.IntentKeys.CATALOG_CTA_BUTTON_COLOR}\" not found.") val buttonUri = - intent.getStringExtra(PushTemplateConstants.IntentKeys.CATALOG_CTA_BUTTON_URI) + intent.getStringExtra(PushTemplateIntentConstants.IntentKeys.CATALOG_CTA_BUTTON_URI) ctaButtonUri = buttonUri - ?: throw IllegalArgumentException("Required field \"${PushTemplateConstants.IntentKeys.CATALOG_CTA_BUTTON_URI}\" not found.") - val layout = intent.getStringExtra(PushTemplateConstants.IntentKeys.CATALOG_LAYOUT) + ?: throw IllegalArgumentException("Required field \"${PushTemplateIntentConstants.IntentKeys.CATALOG_CTA_BUTTON_URI}\" not found.") + val layout = intent.getStringExtra(PushTemplateIntentConstants.IntentKeys.CATALOG_LAYOUT) displayLayout = layout - ?: throw IllegalArgumentException("Required field \"${PushTemplateConstants.IntentKeys.CATALOG_LAYOUT}\" not found.") - val items = intent.getStringExtra(PushTemplateConstants.IntentKeys.CATALOG_ITEMS) + ?: throw IllegalArgumentException("Required field \"${PushTemplateIntentConstants.IntentKeys.CATALOG_LAYOUT}\" not found.") + val items = intent.getStringExtra(PushTemplateIntentConstants.IntentKeys.CATALOG_ITEMS) rawCatalogItems = items - ?: throw IllegalArgumentException("Required field \"${PushTemplateConstants.IntentKeys.CATALOG_ITEMS}\" not found.") + ?: throw IllegalArgumentException("Required field \"${PushTemplateIntentConstants.IntentKeys.CATALOG_ITEMS}\" not found.") catalogItems = parseCatalogItemsFromString(rawCatalogItems) currentIndex = intent.getIntExtra( - PushTemplateConstants.IntentKeys.CATALOG_ITEM_INDEX, + PushTemplateIntentConstants.IntentKeys.CATALOG_ITEM_INDEX, PushTemplateConstants.DefaultValues.PRODUCT_CATALOG_START_INDEX ) } diff --git a/code/notificationbuilder/src/main/res/layout/push_template_auto_carousel.xml b/code/notificationbuilder/src/main/res/layout/push_template_auto_carousel.xml index b5561c14..b153b429 100644 --- a/code/notificationbuilder/src/main/res/layout/push_template_auto_carousel.xml +++ b/code/notificationbuilder/src/main/res/layout/push_template_auto_carousel.xml @@ -5,7 +5,8 @@ android:layout_height="match_parent" android:clipToPadding="false" android:clipChildren="false" - android:orientation="vertical"> + android:orientation="vertical" + android:theme="@style/AppTheme"> + android:orientation="vertical" + android:theme="@style/AppTheme"> + android:orientation="vertical" + android:theme="@style/AppTheme"> + android:orientation="vertical" + android:theme="@style/AppTheme"> + android:orientation="vertical" + android:theme="@style/AppTheme"> + android:orientation="vertical" + android:theme="@style/AppTheme"> + android:layout_height="match_parent" + android:theme="@style/AppTheme"> + android:layout_height="match_parent" + android:theme="@style/AppTheme"> + + #000000 + #ffffff + #03DAC5 + \ No newline at end of file diff --git a/code/notificationbuilder/src/main/res/values/styles.xml b/code/notificationbuilder/src/main/res/values/styles.xml index 2b86ed3a..bdce5bf1 100644 --- a/code/notificationbuilder/src/main/res/values/styles.xml +++ b/code/notificationbuilder/src/main/res/values/styles.xml @@ -27,4 +27,16 @@ 1 end + + \ No newline at end of file From ab6d0f0fc5f0a394862ce75715dbb98e6a14c34e Mon Sep 17 00:00:00 2001 From: Ryan Morales Date: Mon, 20 May 2024 14:02:14 -0700 Subject: [PATCH 022/159] remove unneeded import --- .../marketing/mobile/notificationbuilder/NotificationBuilder.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/NotificationBuilder.kt b/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/NotificationBuilder.kt index 88b5e418..ad5bbaef 100644 --- a/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/NotificationBuilder.kt +++ b/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/NotificationBuilder.kt @@ -15,7 +15,6 @@ import android.app.Activity import android.content.BroadcastReceiver import android.content.Intent import androidx.core.app.NotificationCompat -import com.adobe.marketing.mobile.notificationbuilder.NotificationBuilder.constructNotificationBuilder import com.adobe.marketing.mobile.notificationbuilder.internal.PushTemplateConstants import com.adobe.marketing.mobile.notificationbuilder.internal.PushTemplateType import com.adobe.marketing.mobile.notificationbuilder.internal.builders.AutoCarouselNotificationBuilder From 4a317edbfc12b074f2ab703099471e8dd45ec4d0 Mon Sep 17 00:00:00 2001 From: Ryan Morales Date: Mon, 20 May 2024 14:06:38 -0700 Subject: [PATCH 023/159] remove unused style --- code/notificationbuilder/src/main/res/values/styles.xml | 7 ------- 1 file changed, 7 deletions(-) diff --git a/code/notificationbuilder/src/main/res/values/styles.xml b/code/notificationbuilder/src/main/res/values/styles.xml index bdce5bf1..603d47e6 100644 --- a/code/notificationbuilder/src/main/res/values/styles.xml +++ b/code/notificationbuilder/src/main/res/values/styles.xml @@ -27,13 +27,6 @@ 1 end - - - + + + \ No newline at end of file diff --git a/code/testapp/src/main/res/values-v23/themes.xml b/code/testapp/src/main/res/values-v23/themes.xml new file mode 100644 index 00000000..d435763b --- /dev/null +++ b/code/testapp/src/main/res/values-v23/themes.xml @@ -0,0 +1,7 @@ + + + \ No newline at end of file diff --git a/code/testapp/src/main/res/values/strings.xml b/code/testapp/src/main/res/values/strings.xml index bb358ad8..a5a8e1e3 100644 --- a/code/testapp/src/main/res/values/strings.xml +++ b/code/testapp/src/main/res/values/strings.xml @@ -13,4 +13,5 @@ --> TestApp + UINotificationBuilderActivity \ No newline at end of file diff --git a/code/testapp/src/main/res/values/themes.xml b/code/testapp/src/main/res/values/themes.xml index d12e231d..874acf0d 100644 --- a/code/testapp/src/main/res/values/themes.xml +++ b/code/testapp/src/main/res/values/themes.xml @@ -13,8 +13,9 @@ --> - + \ No newline at end of file From c0bb155a3c23f07ba557685dd50f62d0dbbdfc53 Mon Sep 17 00:00:00 2001 From: Pravin Prakash Kumar Date: Wed, 5 Jun 2024 11:35:47 -0700 Subject: [PATCH 110/159] Add five icon template --- .../src/main/assets/fiveicon/fiveicon.json | 22 ++++++++++++++++++ .../main/res/drawable/rating_star_filled.png | Bin 0 -> 4150 bytes .../main/res/drawable/rating_star_outline.png | Bin 0 -> 3272 bytes 3 files changed, 22 insertions(+) create mode 100644 code/testapp/src/main/assets/fiveicon/fiveicon.json create mode 100644 code/testapp/src/main/res/drawable/rating_star_filled.png create mode 100644 code/testapp/src/main/res/drawable/rating_star_outline.png diff --git a/code/testapp/src/main/assets/fiveicon/fiveicon.json b/code/testapp/src/main/assets/fiveicon/fiveicon.json new file mode 100644 index 00000000..ea727105 --- /dev/null +++ b/code/testapp/src/main/assets/fiveicon/fiveicon.json @@ -0,0 +1,22 @@ +{ + "adb_version": "1", + "adb_template_type": "icon", + "adb_title": "Game request", + "adb_body": "Shall we play a game?", + "adb_sound": "bells", + "adb_n_count": "1", + "adb_n_priority": "PRIORITY_HIGH", + "adb_image": "https://images.pexels.com/photos/260024/pexels-photo-260024.jpeg?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=2", + "adb_uri": "https://chess.com/games", + "adb_a_type": "WEBURL", + "adb_body_ex": "Basic push template with remind later button.", + "adb_clr_body": "873600", + "adb_clr_title": "283747", + "adb_clr_icon": "123456", + "adb_clr_bg": "FFFFFF", + "customKey": "custom data", + "adb_cancel_image": "delete", + "adb_channel_id": "2024", + "adb_items": "[{\"img\":\"train\",\"uri\":\"myapp://chooseShoeType/shoe1\",\"type\":\"DEEPLINK\"},{\"img\":\"bus\",\"uri\":\"myapp://chooseShoeType/shoe2\",\"type\":\"DEEPLINK\"},{\"img\":\"car\",\"uri\":\"myapp://chooseShoeType/shoe3\",\"type\":\"DEEPLINK\"},{\"img\":\"tempo\",\"uri\":\"myapp://chooseShoeType/shoe4\",\"type\":\"DEEPLINK\"},{\"img\":\"airplane\",\"uri\":\"myapp://chooseShoeType/shoe5\",\"type\":\"DEEPLINK\"}]", + "adb_sticky": "true" +} \ No newline at end of file diff --git a/code/testapp/src/main/res/drawable/rating_star_filled.png b/code/testapp/src/main/res/drawable/rating_star_filled.png new file mode 100644 index 0000000000000000000000000000000000000000..21eb3740c61a9bf961f75e9525a8dd12a1f3b2a3 GIT binary patch literal 4150 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 AO8@`> literal 0 HcmV?d00001 diff --git a/code/testapp/src/main/res/drawable/rating_star_outline.png b/code/testapp/src/main/res/drawable/rating_star_outline.png new file mode 100644 index 0000000000000000000000000000000000000000..1275040bc9155114dec3602063afa5860023a77b GIT binary patch literal 3272 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 H%Y**_W(Yn1 literal 0 HcmV?d00001 From d1dcc39f0a97d00ca3936e1d1e6d2ad6cd7933e4 Mon Sep 17 00:00:00 2001 From: Pravin Prakash Kumar Date: Wed, 5 Jun 2024 12:24:22 -0700 Subject: [PATCH 111/159] Adding five icons to the app --- code/testapp/src/main/assets/fiveicon/fiveicon.json | 2 +- code/testapp/src/main/res/drawable/bus.xml | 5 +++++ code/testapp/src/main/res/drawable/drive.xml | 5 +++++ code/testapp/src/main/res/drawable/fly.xml | 5 +++++ code/testapp/src/main/res/drawable/taxi.xml | 5 +++++ code/testapp/src/main/res/drawable/walk.xml | 5 +++++ 6 files changed, 26 insertions(+), 1 deletion(-) create mode 100644 code/testapp/src/main/res/drawable/bus.xml create mode 100644 code/testapp/src/main/res/drawable/drive.xml create mode 100644 code/testapp/src/main/res/drawable/fly.xml create mode 100644 code/testapp/src/main/res/drawable/taxi.xml create mode 100644 code/testapp/src/main/res/drawable/walk.xml diff --git a/code/testapp/src/main/assets/fiveicon/fiveicon.json b/code/testapp/src/main/assets/fiveicon/fiveicon.json index ea727105..41b23583 100644 --- a/code/testapp/src/main/assets/fiveicon/fiveicon.json +++ b/code/testapp/src/main/assets/fiveicon/fiveicon.json @@ -17,6 +17,6 @@ "customKey": "custom data", "adb_cancel_image": "delete", "adb_channel_id": "2024", - "adb_items": "[{\"img\":\"train\",\"uri\":\"myapp://chooseShoeType/shoe1\",\"type\":\"DEEPLINK\"},{\"img\":\"bus\",\"uri\":\"myapp://chooseShoeType/shoe2\",\"type\":\"DEEPLINK\"},{\"img\":\"car\",\"uri\":\"myapp://chooseShoeType/shoe3\",\"type\":\"DEEPLINK\"},{\"img\":\"tempo\",\"uri\":\"myapp://chooseShoeType/shoe4\",\"type\":\"DEEPLINK\"},{\"img\":\"airplane\",\"uri\":\"myapp://chooseShoeType/shoe5\",\"type\":\"DEEPLINK\"}]", + "adb_items": "[{\"img\":\"walk\",\"uri\":\"myapp://chooseShoeType/shoe1\",\"type\":\"DEEPLINK\"},{\"img\":\"drive\",\"uri\":\"myapp://chooseShoeType/bus\",\"type\":\"DEEPLINK\"},{\"img\":\"bus\",\"uri\":\"myapp://chooseShoeType/shoe3\",\"type\":\"DEEPLINK\"},{\"img\":\"taxi\",\"uri\":\"myapp://chooseShoeType/shoe4\",\"type\":\"DEEPLINK\"},{\"img\":\"fly\",\"uri\":\"myapp://chooseShoeType/shoe5\",\"type\":\"DEEPLINK\"}]", "adb_sticky": "true" } \ No newline at end of file diff --git a/code/testapp/src/main/res/drawable/bus.xml b/code/testapp/src/main/res/drawable/bus.xml new file mode 100644 index 00000000..c623cfa1 --- /dev/null +++ b/code/testapp/src/main/res/drawable/bus.xml @@ -0,0 +1,5 @@ + + + diff --git a/code/testapp/src/main/res/drawable/drive.xml b/code/testapp/src/main/res/drawable/drive.xml new file mode 100644 index 00000000..b203b5f0 --- /dev/null +++ b/code/testapp/src/main/res/drawable/drive.xml @@ -0,0 +1,5 @@ + + + diff --git a/code/testapp/src/main/res/drawable/fly.xml b/code/testapp/src/main/res/drawable/fly.xml new file mode 100644 index 00000000..67ef457b --- /dev/null +++ b/code/testapp/src/main/res/drawable/fly.xml @@ -0,0 +1,5 @@ + + + diff --git a/code/testapp/src/main/res/drawable/taxi.xml b/code/testapp/src/main/res/drawable/taxi.xml new file mode 100644 index 00000000..496ef9ea --- /dev/null +++ b/code/testapp/src/main/res/drawable/taxi.xml @@ -0,0 +1,5 @@ + + + diff --git a/code/testapp/src/main/res/drawable/walk.xml b/code/testapp/src/main/res/drawable/walk.xml new file mode 100644 index 00000000..fa912977 --- /dev/null +++ b/code/testapp/src/main/res/drawable/walk.xml @@ -0,0 +1,5 @@ + + + From 17f35d99e0c850692d083c5d1add1164b6882f09 Mon Sep 17 00:00:00 2001 From: Pravin Prakash Kumar Date: Wed, 5 Jun 2024 14:25:09 -0700 Subject: [PATCH 112/159] update tracker activity for testApp --- .../src/main/assets/fiveicon/fiveicon.json | 6 +- .../NotificationTrackerActivity.kt | 65 +++++++++++++++++++ 2 files changed, 66 insertions(+), 5 deletions(-) diff --git a/code/testapp/src/main/assets/fiveicon/fiveicon.json b/code/testapp/src/main/assets/fiveicon/fiveicon.json index 41b23583..d7d169a8 100644 --- a/code/testapp/src/main/assets/fiveicon/fiveicon.json +++ b/code/testapp/src/main/assets/fiveicon/fiveicon.json @@ -10,12 +10,8 @@ "adb_uri": "https://chess.com/games", "adb_a_type": "WEBURL", "adb_body_ex": "Basic push template with remind later button.", - "adb_clr_body": "873600", - "adb_clr_title": "283747", - "adb_clr_icon": "123456", - "adb_clr_bg": "FFFFFF", + "adb_tag" : "multi_icon", "customKey": "custom data", - "adb_cancel_image": "delete", "adb_channel_id": "2024", "adb_items": "[{\"img\":\"walk\",\"uri\":\"myapp://chooseShoeType/shoe1\",\"type\":\"DEEPLINK\"},{\"img\":\"drive\",\"uri\":\"myapp://chooseShoeType/bus\",\"type\":\"DEEPLINK\"},{\"img\":\"bus\",\"uri\":\"myapp://chooseShoeType/shoe3\",\"type\":\"DEEPLINK\"},{\"img\":\"taxi\",\"uri\":\"myapp://chooseShoeType/shoe4\",\"type\":\"DEEPLINK\"},{\"img\":\"fly\",\"uri\":\"myapp://chooseShoeType/shoe5\",\"type\":\"DEEPLINK\"}]", "adb_sticky": "true" diff --git a/code/testapp/src/main/java/com/adobe/marketing/mobile/notificationbuilder/testapp/notificationBuilder/NotificationTrackerActivity.kt b/code/testapp/src/main/java/com/adobe/marketing/mobile/notificationbuilder/testapp/notificationBuilder/NotificationTrackerActivity.kt index 3c4fbc2d..9538521d 100644 --- a/code/testapp/src/main/java/com/adobe/marketing/mobile/notificationbuilder/testapp/notificationBuilder/NotificationTrackerActivity.kt +++ b/code/testapp/src/main/java/com/adobe/marketing/mobile/notificationbuilder/testapp/notificationBuilder/NotificationTrackerActivity.kt @@ -11,13 +11,78 @@ package com.adobe.marketing.mobile.notificationbuilder.testapp.notificationBuilder +import android.content.ActivityNotFoundException +import android.content.Intent +import android.net.Uri import android.os.Bundle import androidx.appcompat.app.AppCompatActivity +import androidx.core.app.NotificationManagerCompat +import com.adobe.marketing.mobile.notificationbuilder.PushTemplateConstants +import com.adobe.marketing.mobile.services.ServiceProvider class NotificationTrackerActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) + when (intent?.action) { + PushTemplateConstants.NotificationAction.CLICKED -> executePushAction(intent) + else -> {} + } + finish() return } + + private fun executePushAction(intent: Intent) { + val actionUri = + intent.getStringExtra(PushTemplateConstants.TrackingKeys.ACTION_URI) + if (actionUri.isNullOrEmpty()) { + openApplication() + } else { + openUri(actionUri) + } + + // remove the notification if sticky notifications are false + val isStickyNotification = intent.getStringExtra(PushTemplateConstants.PushPayloadKeys.STICKY)?.toBoolean() ?: false + if (isStickyNotification) { + return + } + val tag = intent.getStringExtra(PushTemplateConstants.PushPayloadKeys.TAG) + val context = ServiceProvider.getInstance().appContextService.applicationContext ?: return + val notificationManager = NotificationManagerCompat.from(context) + notificationManager.cancel(tag.hashCode()) + } + + private fun openApplication() { + val currentActivity = ServiceProvider.getInstance().appContextService.currentActivity + val launchIntent = if (currentActivity != null) { + Intent(currentActivity, currentActivity.javaClass) + } else { + packageManager.getLaunchIntentForPackage(packageName) + } + if (launchIntent != null) { + launchIntent.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP or Intent.FLAG_ACTIVITY_CLEAR_TOP) + if (intent.extras != null) { + launchIntent.putExtras(intent.extras!!) + } + startActivity(launchIntent) + } + } + + /** + * Use this method to create an intent to open the the provided URI. + * + * @param uri the uri to open + */ + private fun openUri(uri: String) { + try { + val deeplinkIntent = Intent(Intent.ACTION_VIEW, Uri.parse(uri)) + deeplinkIntent.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP or Intent.FLAG_ACTIVITY_CLEAR_TOP) + if (intent.extras != null) { + deeplinkIntent.putExtras(intent.extras!!) + } + startActivity(deeplinkIntent) + } catch (e: ActivityNotFoundException) { + + } + } } \ No newline at end of file From a68b92943575e2aa702ec7a5561a54c105e290f0 Mon Sep 17 00:00:00 2001 From: Pravin Prakash Kumar Date: Wed, 5 Jun 2024 14:32:07 -0700 Subject: [PATCH 113/159] Copy the bundleData while editing the NotifiationData for actions --- .../internal/builders/MultiIconNotificationBuilder.kt | 5 ++++- .../internal/builders/ProductRatingNotificationBuilder.kt | 7 ++++--- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/MultiIconNotificationBuilder.kt b/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/MultiIconNotificationBuilder.kt index fa868bae..fc57bb02 100644 --- a/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/MultiIconNotificationBuilder.kt +++ b/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/MultiIconNotificationBuilder.kt @@ -14,6 +14,7 @@ package com.adobe.marketing.mobile.notificationbuilder.internal.builders import android.app.Activity import android.app.NotificationManager import android.content.Context +import android.os.Bundle import android.widget.RemoteViews import androidx.core.app.NotificationCompat import com.adobe.marketing.mobile.notificationbuilder.NotificationConstructionFailedException @@ -64,13 +65,15 @@ internal object MultiIconNotificationBuilder { pushTemplate ) + val closeButtonIntentExtra = Bundle(pushTemplate.data.getBundle()) // copy the bundle + closeButtonIntentExtra.putString(PushTemplateConstants.PushPayloadKeys.STICKY, "false") notificationLayout.setRemoteViewClickAction( context, trackerActivityClass, R.id.five_icon_close_button, null, null, - pushTemplate.data.getBundle() + closeButtonIntentExtra ) return AEPPushNotificationBuilder.construct( diff --git a/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/ProductRatingNotificationBuilder.kt b/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/ProductRatingNotificationBuilder.kt index 4cce8472..ceff33da 100644 --- a/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/ProductRatingNotificationBuilder.kt +++ b/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/ProductRatingNotificationBuilder.kt @@ -16,6 +16,7 @@ import android.app.NotificationManager import android.app.PendingIntent import android.content.BroadcastReceiver import android.content.Context +import android.os.Bundle import android.view.View import android.widget.RemoteViews import androidx.core.app.NotificationCompat @@ -106,8 +107,8 @@ internal object ProductRatingNotificationBuilder { // add pending intent for confirm click // sticky is set to false as the notification will be dismissed after confirm click - val intentExtras = pushTemplate.data.getBundle() - intentExtras.putString(PushTemplateConstants.PushPayloadKeys.STICKY, "false") + val ratingConfirmedIntentExtras = Bundle(pushTemplate.data.getBundle()) // copy the bundle + ratingConfirmedIntentExtras.putString(PushTemplateConstants.PushPayloadKeys.STICKY, "false") val selectedRatingAction = pushTemplate.ratingActionList[pushTemplate.ratingSelected] expandedLayout.setRemoteViewClickAction( context, @@ -115,7 +116,7 @@ internal object ProductRatingNotificationBuilder { R.id.rating_confirm, selectedRatingAction.link, pushTemplate.ratingSelected.toString(), - intentExtras + ratingConfirmedIntentExtras ) } else { // hide confirm if no rating is selected From 6f29c2dda135292f4c2682c7616d2a5568c3796b Mon Sep 17 00:00:00 2001 From: Pravin Prakash Kumar Date: Wed, 5 Jun 2024 15:11:24 -0700 Subject: [PATCH 114/159] Timer Notification support from Android N and above only --- .../NotificationBuilder.kt | 2 +- .../internal/PendingIntentUtils.kt | 2 +- .../builders/TimerNotificationBuilder.kt | 31 ++++++++++++------- 3 files changed, 22 insertions(+), 13 deletions(-) diff --git a/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/NotificationBuilder.kt b/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/NotificationBuilder.kt index e54cd5c7..5520b6ba 100644 --- a/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/NotificationBuilder.kt +++ b/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/NotificationBuilder.kt @@ -172,7 +172,7 @@ object NotificationBuilder { val scheduledIntent = Intent(PushTemplateConstants.IntentActions.SCHEDULED_NOTIFICATION_BROADCAST) scheduledIntent.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP) scheduledIntent.putExtras(intentExtras) - PendingIntentUtils.createPendingIntentForScheduledNotifications(context, scheduledIntent, broadcastReceiverClass, triggerTimeInSeconds) + PendingIntentUtils.scheduledNotifications(context, scheduledIntent, broadcastReceiverClass, triggerTimeInSeconds) // cancel the displayed notification tag?.let { notificationManager.cancel(tag.hashCode()) } diff --git a/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/PendingIntentUtils.kt b/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/PendingIntentUtils.kt index d0bce975..2debc98e 100644 --- a/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/PendingIntentUtils.kt +++ b/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/PendingIntentUtils.kt @@ -27,7 +27,7 @@ internal object PendingIntentUtils { private const val SELF_TAG = "PendingIntentUtils" - internal fun createPendingIntentForScheduledNotifications( + internal fun scheduledNotifications( context: Context, scheduledIntent: Intent, broadcastReceiverClass: Class?, diff --git a/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/TimerNotificationBuilder.kt b/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/TimerNotificationBuilder.kt index 40e2336d..bd4d73d4 100644 --- a/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/TimerNotificationBuilder.kt +++ b/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/TimerNotificationBuilder.kt @@ -14,7 +14,6 @@ package com.adobe.marketing.mobile.notificationbuilder.internal.builders import android.app.Activity import android.app.AlarmManager import android.app.NotificationManager -import android.app.PendingIntent import android.content.BroadcastReceiver import android.content.Context import android.content.Intent @@ -27,6 +26,7 @@ import com.adobe.marketing.mobile.notificationbuilder.PushTemplateConstants import com.adobe.marketing.mobile.notificationbuilder.PushTemplateConstants.LOG_TAG import com.adobe.marketing.mobile.notificationbuilder.PushTemplateConstants.PushPayloadKeys.TimerKeys import com.adobe.marketing.mobile.notificationbuilder.R +import com.adobe.marketing.mobile.notificationbuilder.internal.PendingIntentUtils import com.adobe.marketing.mobile.notificationbuilder.internal.extensions.createNotificationChannelIfRequired import com.adobe.marketing.mobile.notificationbuilder.internal.extensions.setRemoteViewImage import com.adobe.marketing.mobile.notificationbuilder.internal.extensions.setTimerTextColor @@ -58,12 +58,16 @@ internal object TimerNotificationBuilder { broadcastReceiverClass: Class? ): NotificationCompat.Builder { - Log.trace(LOG_TAG, TAG, "Building a timer template push notification.") + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) { + throw NotificationConstructionFailedException("Timer push notification on devices below Android N is not supported.") + } if (!isExactAlarmsAllowed(context)) { - throw NotificationConstructionFailedException("Exact alarms are not allowed on this device.") + throw NotificationConstructionFailedException("Exact alarms are not allowed on this device. Ignoring to build Timer template push notifications.") } + Log.trace(LOG_TAG, TAG, "Building a timer template push notification.") + val notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager @@ -106,15 +110,20 @@ internal object TimerNotificationBuilder { } // create the pending intent for the timer expiry - val pendingIntent = PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_MUTABLE or PendingIntent.FLAG_UPDATE_CURRENT) - - // todo replace this with PendingIntentUtils.createPendingIntentForScheduledNotifications - // set the alarm manager to trigger the intent at the expiry time - val alarmManager = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager - val triggerTime = TimeUtils.getUnixTimeInSeconds() + remainingTimeInSeconds - val triggerTimeAtMillis = triggerTime * 1000 - alarmManager.setExact(AlarmManager.RTC_WAKEUP, triggerTimeAtMillis, pendingIntent) + PendingIntentUtils.scheduledNotifications(context, intent, broadcastReceiverClass, TimeUtils.getUnixTimeInSeconds() + remainingTimeInSeconds) + } else { + // Before displaying the expired view, check if the notification is still active + val notification = notificationManager.activeNotifications.find { it.id == template.tag?.hashCode() } + if (notification == null) { + Log.debug( + LOG_TAG, TAG, + "Notification with tag '${template.tag}' is not present in the system tray. " + + "The timer notification has already been dismissed. The expired view will not be displayed." + ) + throw NotificationConstructionFailedException("Timer Notification cancelled. Expired view will not be displayed.") + } } + return notificationBuilder } From 82b09a6d41ce1f4a0d5071467556f28d746ea550 Mon Sep 17 00:00:00 2001 From: Pravin Prakash Kumar Date: Wed, 5 Jun 2024 15:25:55 -0700 Subject: [PATCH 115/159] better name --- .../marketing/mobile/notificationbuilder/NotificationBuilder.kt | 2 +- .../mobile/notificationbuilder/internal/PendingIntentUtils.kt | 2 +- .../internal/builders/TimerNotificationBuilder.kt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/NotificationBuilder.kt b/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/NotificationBuilder.kt index 5520b6ba..ad8f4af9 100644 --- a/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/NotificationBuilder.kt +++ b/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/NotificationBuilder.kt @@ -172,7 +172,7 @@ object NotificationBuilder { val scheduledIntent = Intent(PushTemplateConstants.IntentActions.SCHEDULED_NOTIFICATION_BROADCAST) scheduledIntent.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP) scheduledIntent.putExtras(intentExtras) - PendingIntentUtils.scheduledNotifications(context, scheduledIntent, broadcastReceiverClass, triggerTimeInSeconds) + PendingIntentUtils.scheduleNotification(context, scheduledIntent, broadcastReceiverClass, triggerTimeInSeconds) // cancel the displayed notification tag?.let { notificationManager.cancel(tag.hashCode()) } diff --git a/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/PendingIntentUtils.kt b/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/PendingIntentUtils.kt index 2debc98e..e19b64b0 100644 --- a/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/PendingIntentUtils.kt +++ b/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/PendingIntentUtils.kt @@ -27,7 +27,7 @@ internal object PendingIntentUtils { private const val SELF_TAG = "PendingIntentUtils" - internal fun scheduledNotifications( + internal fun scheduleNotification( context: Context, scheduledIntent: Intent, broadcastReceiverClass: Class?, diff --git a/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/TimerNotificationBuilder.kt b/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/TimerNotificationBuilder.kt index bd4d73d4..9845ba90 100644 --- a/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/TimerNotificationBuilder.kt +++ b/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/TimerNotificationBuilder.kt @@ -110,7 +110,7 @@ internal object TimerNotificationBuilder { } // create the pending intent for the timer expiry - PendingIntentUtils.scheduledNotifications(context, intent, broadcastReceiverClass, TimeUtils.getUnixTimeInSeconds() + remainingTimeInSeconds) + PendingIntentUtils.scheduleNotification(context, intent, broadcastReceiverClass, TimeUtils.getUnixTimeInSeconds() + remainingTimeInSeconds) } else { // Before displaying the expired view, check if the notification is still active val notification = notificationManager.activeNotifications.find { it.id == template.tag?.hashCode() } From 09eb90f335e8d5e9b28364bccfac22a985078933 Mon Sep 17 00:00:00 2001 From: Spoorthi Pujari <63024083+spoorthipujariadobe@users.noreply.github.com> Date: Wed, 5 Jun 2024 16:35:09 -0700 Subject: [PATCH 116/159] fix zero bezel collapse text crop + filmstrip centre image height crop (#31) * Add all notification data, as received from caller extension, to PendingIntents for tracking * change PendingIntent for input box template * code review comments * fix LegacyNotificationBuilderTest test using IntentData * revert test to as before * Remove unused imports * remove mocking bundle in tests and use actual class * remove mocking bundle in tests and use actual class * fix formatting * one more commit to change manual carousel mocks * remove repeated keys in PendingIntent extras * change default channel name * fix zero bezel collapse text crop + filmstrip centre image height crop * make zero bezel image centerCrop --- .../main/res/layout/push_template_filmstrip_carousel.xml | 2 +- .../main/res/layout/push_template_zero_bezel_collapsed.xml | 7 ++----- .../main/res/layout/push_template_zero_bezel_expanded.xml | 7 ++----- 3 files changed, 5 insertions(+), 11 deletions(-) diff --git a/code/notificationbuilder/src/main/res/layout/push_template_filmstrip_carousel.xml b/code/notificationbuilder/src/main/res/layout/push_template_filmstrip_carousel.xml index 1d7e5893..3f7bdc55 100644 --- a/code/notificationbuilder/src/main/res/layout/push_template_filmstrip_carousel.xml +++ b/code/notificationbuilder/src/main/res/layout/push_template_filmstrip_carousel.xml @@ -46,7 +46,7 @@ + android:paddingEnd="@dimen/zero_bezel_padding_horizontal"> diff --git a/code/notificationbuilder/src/main/res/layout/push_template_zero_bezel_expanded.xml b/code/notificationbuilder/src/main/res/layout/push_template_zero_bezel_expanded.xml index 6b45f673..f0f02ab4 100644 --- a/code/notificationbuilder/src/main/res/layout/push_template_zero_bezel_expanded.xml +++ b/code/notificationbuilder/src/main/res/layout/push_template_zero_bezel_expanded.xml @@ -9,7 +9,7 @@ android:id="@+id/expanded_template_image" android:layout_height="match_parent" android:layout_width="match_parent" - android:scaleType="fitCenter" + android:scaleType="centerCrop" android:adjustViewBounds="true"/> + android:paddingEnd="@dimen/zero_bezel_padding_horizontal"> From e2ee0734f316bf4620156166e54fdfccbc4c1984 Mon Sep 17 00:00:00 2001 From: Spoorthi Pujari <63024083+spoorthipujariadobe@users.noreply.github.com> Date: Wed, 5 Jun 2024 17:06:37 -0700 Subject: [PATCH 117/159] change some trace logs to warning/debug (#32) * Add all notification data, as received from caller extension, to PendingIntents for tracking * change PendingIntent for input box template * code review comments * fix LegacyNotificationBuilderTest test using IntentData * revert test to as before * Remove unused imports * remove mocking bundle in tests and use actual class * remove mocking bundle in tests and use actual class * fix formatting * one more commit to change manual carousel mocks * remove repeated keys in PendingIntent extras * change default channel name * fix zero bezel collapse text crop + filmstrip centre image height crop * make zero bezel image centerCrop * change some trace logs to warning/debug * change to use setRemoteImage --- .../NotificationBuilder.kt | 4 ++-- .../internal/PushTemplateImageUtils.kt | 4 ++-- .../AutoCarouselNotificationBuilder.kt | 2 +- .../builders/BasicNotificationBuilder.kt | 19 +++++-------------- .../builders/InputBoxNotificationBuilder.kt | 13 ++----------- .../ManualCarouselNotificationBuilder.kt | 8 ++++---- .../NotificationCompatBuilderExtensions.kt | 2 +- .../extensions/RemoteViewsExtensions.kt | 6 +++--- 8 files changed, 20 insertions(+), 38 deletions(-) diff --git a/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/NotificationBuilder.kt b/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/NotificationBuilder.kt index ad8f4af9..d6c0799f 100644 --- a/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/NotificationBuilder.kt +++ b/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/NotificationBuilder.kt @@ -161,7 +161,7 @@ object NotificationBuilder { // schedule a pending intent to be broadcast at the specified timestamp if (broadcastReceiverClass == null) { - Log.trace( + Log.warning( LOG_TAG, SELF_TAG, "Broadcast receiver class is null, cannot schedule notification for later." @@ -223,7 +223,7 @@ object NotificationBuilder { } else -> { - Log.trace( + Log.warning( LOG_TAG, SELF_TAG, "Unknown carousel push template type, creating a legacy style notification." diff --git a/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/PushTemplateImageUtils.kt b/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/PushTemplateImageUtils.kt index 96b9992b..8144e654 100644 --- a/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/PushTemplateImageUtils.kt +++ b/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/PushTemplateImageUtils.kt @@ -107,7 +107,7 @@ internal object PushTemplateImageUtils { } downloadedImageCount.incrementAndGet() } catch (exception: IOException) { - Log.trace( + Log.warning( LOG_TAG, SELF_TAG, "Exception occurred creating an input stream from a bitmap for {$url}: ${exception.localizedMessage}." @@ -187,7 +187,7 @@ internal object PushTemplateImageUtils { } val cacheResult = ServiceProvider.getInstance().cacheService[assetCacheLocation, url] if (cacheResult == null) { - Log.trace(LOG_TAG, SELF_TAG, "Image not found in cache for $url") + Log.warning(LOG_TAG, SELF_TAG, "Image not found in cache for $url") return null } Log.trace(LOG_TAG, SELF_TAG, "Found cached image for $url") diff --git a/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/AutoCarouselNotificationBuilder.kt b/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/AutoCarouselNotificationBuilder.kt index 582fb3ce..a3ec9a50 100644 --- a/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/AutoCarouselNotificationBuilder.kt +++ b/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/AutoCarouselNotificationBuilder.kt @@ -53,7 +53,7 @@ internal object AutoCarouselNotificationBuilder { // fallback to a basic push template notification builder if less than 3 images were able to be downloaded if (downloadedImageCount < PushTemplateConstants.DefaultValues.CAROUSEL_MINIMUM_IMAGE_COUNT) { - Log.trace(LOG_TAG, SELF_TAG, "Less than 3 images are available for the auto carousel push template, falling back to a basic push template.") + Log.warning(LOG_TAG, SELF_TAG, "Less than 3 images are available for the auto carousel push template, falling back to a basic push template.") return BasicNotificationBuilder.fallbackToBasicNotification( context, trackerActivityClass, diff --git a/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/BasicNotificationBuilder.kt b/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/BasicNotificationBuilder.kt index b7887698..cfa30bce 100644 --- a/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/BasicNotificationBuilder.kt +++ b/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/BasicNotificationBuilder.kt @@ -16,7 +16,6 @@ import android.app.NotificationManager import android.app.PendingIntent import android.content.BroadcastReceiver import android.content.Context -import android.view.View import android.widget.RemoteViews import androidx.annotation.VisibleForTesting import androidx.core.app.NotificationCompat @@ -25,9 +24,9 @@ import com.adobe.marketing.mobile.notificationbuilder.PushTemplateConstants import com.adobe.marketing.mobile.notificationbuilder.PushTemplateConstants.LOG_TAG import com.adobe.marketing.mobile.notificationbuilder.PushTemplateConstants.PushPayloadKeys import com.adobe.marketing.mobile.notificationbuilder.R -import com.adobe.marketing.mobile.notificationbuilder.internal.PushTemplateImageUtils import com.adobe.marketing.mobile.notificationbuilder.internal.extensions.addActionButtons import com.adobe.marketing.mobile.notificationbuilder.internal.extensions.createNotificationChannelIfRequired +import com.adobe.marketing.mobile.notificationbuilder.internal.extensions.setRemoteImage import com.adobe.marketing.mobile.notificationbuilder.internal.templates.BasicPushTemplate import com.adobe.marketing.mobile.notificationbuilder.internal.util.NotificationData import com.adobe.marketing.mobile.services.Log @@ -67,18 +66,10 @@ internal object BasicNotificationBuilder { ) // set the image on the notification - val imageUri = pushTemplate.imageUrl - val downloadedImageCount = PushTemplateImageUtils.cacheImages(listOf(imageUri)) - - if (downloadedImageCount == 0) { - Log.trace(LOG_TAG, SELF_TAG, "No image found for basic push template.") - expandedLayout.setViewVisibility(R.id.expanded_template_image, View.GONE) - } else { - expandedLayout.setImageViewBitmap( - R.id.expanded_template_image, - PushTemplateImageUtils.getCachedImage(imageUri) - ) - } + expandedLayout.setRemoteImage( + pushTemplate.imageUrl, + R.id.expanded_template_image, + ) // add any action buttons defined for the notification notificationBuilder.addActionButtons( diff --git a/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/InputBoxNotificationBuilder.kt b/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/InputBoxNotificationBuilder.kt index 54ac6150..97a4d595 100644 --- a/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/InputBoxNotificationBuilder.kt +++ b/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/InputBoxNotificationBuilder.kt @@ -17,7 +17,6 @@ import android.app.PendingIntent import android.content.BroadcastReceiver import android.content.Context import android.content.Intent -import android.view.View import android.widget.RemoteViews import androidx.core.app.NotificationCompat import androidx.core.app.RemoteInput @@ -26,8 +25,8 @@ import com.adobe.marketing.mobile.notificationbuilder.PushTemplateConstants import com.adobe.marketing.mobile.notificationbuilder.PushTemplateConstants.LOG_TAG import com.adobe.marketing.mobile.notificationbuilder.PushTemplateConstants.PushPayloadKeys import com.adobe.marketing.mobile.notificationbuilder.R -import com.adobe.marketing.mobile.notificationbuilder.internal.PushTemplateImageUtils import com.adobe.marketing.mobile.notificationbuilder.internal.extensions.createNotificationChannelIfRequired +import com.adobe.marketing.mobile.notificationbuilder.internal.extensions.setRemoteImage import com.adobe.marketing.mobile.notificationbuilder.internal.templates.InputBoxPushTemplate import com.adobe.marketing.mobile.services.Log import java.util.Random @@ -69,15 +68,7 @@ internal object InputBoxNotificationBuilder { // get push payload data. if we are handling an intent then we know that we should be building a feedback received notification. val imageUri = if (pushTemplate.isFromIntent) pushTemplate.feedbackImage else pushTemplate.imageUrl - val downloadedImageCount = PushTemplateImageUtils.cacheImages(listOf(imageUri)) - - if (downloadedImageCount == 1) { - val pushImage = PushTemplateImageUtils.getCachedImage(imageUri) - expandedLayout.setImageViewBitmap(R.id.expanded_template_image, pushImage) - } else { - Log.trace(LOG_TAG, SELF_TAG, "No image found for input box push template.") - expandedLayout.setViewVisibility(R.id.expanded_template_image, View.GONE) - } + expandedLayout.setRemoteImage(imageUri, R.id.expanded_template_image) val expandedBodyText = if (pushTemplate.isFromIntent) pushTemplate.feedbackText else pushTemplate.expandedBodyText diff --git a/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/ManualCarouselNotificationBuilder.kt b/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/ManualCarouselNotificationBuilder.kt index 6502899e..c6c30e5d 100644 --- a/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/ManualCarouselNotificationBuilder.kt +++ b/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/ManualCarouselNotificationBuilder.kt @@ -57,7 +57,7 @@ internal object ManualCarouselNotificationBuilder { // fallback to a basic push template notification builder if less than 3 images were able // to be downloaded if (downloadedImagesCount < PushTemplateConstants.DefaultValues.CAROUSEL_MINIMUM_IMAGE_COUNT) { - Log.trace(LOG_TAG, SELF_TAG, "Less than 3 images are available for the manual carousel push template, falling back to a basic push template.") + Log.warning(LOG_TAG, SELF_TAG, "Less than 3 images are available for the manual carousel push template, falling back to a basic push template.") return BasicNotificationBuilder.fallbackToBasicNotification( context, trackerActivityClass, @@ -144,7 +144,7 @@ internal object ManualCarouselNotificationBuilder { val imageUri: String = item.imageUri val pushImage: Bitmap? = PushTemplateImageUtils.getCachedImage(imageUri) if (pushImage == null) { - Log.trace( + Log.warning( LOG_TAG, SELF_TAG, "Failed to retrieve an image from $imageUri, will not create a new carousel item." @@ -286,7 +286,7 @@ internal object ManualCarouselNotificationBuilder { val imageUri = item.imageUri val pushImage: Bitmap? = PushTemplateImageUtils.getCachedImage(imageUri) if (pushImage == null) { - Log.trace( + Log.warning( LOG_TAG, SELF_TAG, "Failed to retrieve an image from $imageUri, will not create a new carousel item." @@ -353,7 +353,7 @@ internal object ManualCarouselNotificationBuilder { // set the downloaded bitmaps in the filmstrip image views val assetCacheLocation = PushTemplateImageUtils.getAssetCacheLocation() if (assetCacheLocation.isNullOrEmpty()) { - Log.trace( + Log.warning( LOG_TAG, SELF_TAG, "Asset cache location is null or empty, unable to retrieve filmstrip carousel images." diff --git a/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/extensions/NotificationCompatBuilderExtensions.kt b/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/extensions/NotificationCompatBuilderExtensions.kt index 71e255c0..15d4fdf9 100644 --- a/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/extensions/NotificationCompatBuilderExtensions.kt +++ b/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/extensions/NotificationCompatBuilderExtensions.kt @@ -93,7 +93,7 @@ private fun NotificationCompat.Builder.setSmallIconColor( // sets the icon color if provided setColorized(true).color = Color.parseColor("#$iconColorHex") } catch (exception: IllegalArgumentException) { - Log.trace( + Log.warning( LOG_TAG, SELF_TAG, "Unrecognized hex string passed to Color.parseColor(), custom color will not be applied to the notification icon." diff --git a/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/extensions/RemoteViewsExtensions.kt b/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/extensions/RemoteViewsExtensions.kt index 26cbc8cc..40ac66b9 100644 --- a/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/extensions/RemoteViewsExtensions.kt +++ b/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/extensions/RemoteViewsExtensions.kt @@ -46,7 +46,7 @@ internal fun RemoteViews.setElementColor( viewFriendlyName: String ) { if (colorHex.isNullOrEmpty()) { - Log.trace( + Log.debug( LOG_TAG, SELF_TAG, "Empty color hex string found, custom color will not be applied to $viewFriendlyName." @@ -57,7 +57,7 @@ internal fun RemoteViews.setElementColor( try { setInt(elementId, methodName, Color.parseColor(colorHex)) } catch (exception: IllegalArgumentException) { - Log.trace( + Log.warning( LOG_TAG, SELF_TAG, "Unrecognized hex string passed to Color.parseColor(), custom color will not be applied to $viewFriendlyName." @@ -156,7 +156,7 @@ internal fun RemoteViews.setRemoteViewImage( containerViewId: Int ): Boolean { if (image.isNullOrEmpty()) { - Log.trace( + Log.warning( LOG_TAG, SELF_TAG, "Null or empty image string found, image will not be applied." From 66e8ab917fc12d6df137386c5e1104ca1418ddc7 Mon Sep 17 00:00:00 2001 From: Prattham Arora <55047418+PratthamArora@users.noreply.github.com> Date: Fri, 7 Jun 2024 09:22:30 +0530 Subject: [PATCH 118/159] Added unit tests for AppResourceExtensions (#33) * added unit tests for AppResourceExtensions * migrate test cases to use Mockito instead of Roboelectric * code refactor * code refactor --- .../extensions/AppResourceExtensions.kt | 2 +- .../extensions/AppResourceExtensionsTest.kt | 149 ++++++++++++++++++ 2 files changed, 150 insertions(+), 1 deletion(-) create mode 100644 code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/extensions/AppResourceExtensionsTest.kt diff --git a/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/extensions/AppResourceExtensions.kt b/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/extensions/AppResourceExtensions.kt index 34c06ade..8b4c7c40 100644 --- a/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/extensions/AppResourceExtensions.kt +++ b/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/extensions/AppResourceExtensions.kt @@ -69,6 +69,6 @@ internal fun Context.getSoundUriForResourceName( "://" + packageName + "/raw/" + - soundName + (soundName ?: "") ) } diff --git a/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/extensions/AppResourceExtensionsTest.kt b/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/extensions/AppResourceExtensionsTest.kt new file mode 100644 index 00000000..a5905a0d --- /dev/null +++ b/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/extensions/AppResourceExtensionsTest.kt @@ -0,0 +1,149 @@ +/* + Copyright 2024 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.notificationbuilder.internal.extensions + +import android.content.Context +import android.content.pm.ApplicationInfo +import android.content.pm.PackageManager +import android.content.res.Resources +import android.net.Uri +import org.junit.After +import org.junit.Assert.assertEquals +import org.junit.Before +import org.junit.Test +import org.mockito.Mock +import org.mockito.MockedStatic +import org.mockito.Mockito.mock +import org.mockito.Mockito.mockStatic +import org.mockito.Mockito.`when` +import org.mockito.MockitoAnnotations +import org.mockito.kotlin.any + +class AppResourceExtensionsTest { + + @Mock + private lateinit var mockContext: Context + + @Mock + private lateinit var mockPackageManager: PackageManager + + @Mock + private lateinit var mockResources: Resources + + @Mock + private lateinit var mockUri: Uri + private lateinit var mockedStaticUri: MockedStatic + + @Before + fun setup() { + MockitoAnnotations.openMocks(this) + mockedStaticUri = mockStatic(Uri::class.java) + mockedStaticUri.`when` { Uri.parse(any()) }.thenReturn(mockUri) + `when`(mockContext.packageManager).thenReturn(mockPackageManager) + `when`(mockContext.resources).thenReturn(mockResources) + } + + @After + fun teardown() { + mockedStaticUri.close() + } + + @Test + fun `test getIconWithResourceName for valid icon name`() { + val iconName = "skipleft" + `when`( + mockContext.resources.getIdentifier( + iconName, + "drawable", + mockContext.packageName + ) + ).thenReturn(1234) + + val iconId = mockContext.getIconWithResourceName(iconName) + assertEquals(1234, iconId) + } + + @Test + fun `test getIconWithResourceName for invalid icon name`() { + `when`( + mockContext.resources.getIdentifier( + "", + "drawable", + mockContext.packageName + ) + ).thenReturn(0) + `when`( + mockContext.resources.getIdentifier( + null, + "drawable", + mockContext.packageName + ) + ).thenReturn(0) + `when`( + mockContext.resources.getIdentifier( + "invalid_icon", + "drawable", + mockContext.packageName + ) + ).thenReturn(0) + + val emptyIconId = mockContext.getIconWithResourceName("") + val nullIconId = mockContext.getIconWithResourceName(null) + val invalidIconId = mockContext.getIconWithResourceName("invalid_icon") + assertEquals(0, emptyIconId) + assertEquals(0, nullIconId) + assertEquals(0, invalidIconId) + } + + @Test + fun `test getDefaultAppIcon`() { + `when`(mockPackageManager.getApplicationInfo(mockContext.packageName, 0)).thenReturn( + mock( + ApplicationInfo::class.java + ).apply { icon = 1234 } + ) + + val defaultAppIcon = mockContext.getDefaultAppIcon() + assertEquals(1234, defaultAppIcon) + } + + @Test + fun `test getDefaultAppIcon when PackageManager throws NameNotFoundException`() { + val packageName = "com.adobe.marketing.mobile.notificationbuilder" + `when`(mockContext.packageName).thenReturn(packageName) + `when`( + mockPackageManager.getApplicationInfo( + packageName, + 0 + ) + ).thenThrow(PackageManager.NameNotFoundException()) + + val iconId = mockContext.getDefaultAppIcon() + assertEquals(-1, iconId) + } + + @Test + fun `test getSoundUriForResourceName for valid sound resource`() { + val expectedUri = + Uri.parse("android.resource://com.adobe.marketing.mobile.notificationbuilder.test/raw/test_sound") + val resultUri = mockContext.getSoundUriForResourceName("test_sound") + assertEquals(expectedUri, resultUri) + } + + @Test + fun `test getSoundUriForResourceName for null sound name`() { + val expectedUri = + Uri.parse("android.resource://com.adobe.marketing.mobile.notificationbuilder.test/raw/") + val resultUri = mockContext.getSoundUriForResourceName(null) + assertEquals(expectedUri, resultUri) + } +} From d311f02ac4fb2a199ec1116e72e367292e637ca3 Mon Sep 17 00:00:00 2001 From: Navratan Soni Date: Fri, 7 Jun 2024 23:56:21 +0530 Subject: [PATCH 119/159] Added test cases for validating Icons count --- .../PushTemplateConstants.kt | 1 - .../internal/templates/MockDataUtils.kt | 39 ++++++++++++-- .../MockMultiIconTemplateDataProvider.kt | 26 ++++++++++ .../templates/MultiIconPushTemplateTests.kt | 51 +++++++++++++++++++ 4 files changed, 111 insertions(+), 6 deletions(-) create mode 100644 code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/templates/MockMultiIconTemplateDataProvider.kt create mode 100644 code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/templates/MultiIconPushTemplateTests.kt diff --git a/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/PushTemplateConstants.kt b/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/PushTemplateConstants.kt index dcecc75b..4c5bb17e 100644 --- a/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/PushTemplateConstants.kt +++ b/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/PushTemplateConstants.kt @@ -68,7 +68,6 @@ object PushTemplateConstants { internal const val INPUT_BOX_DEFAULT_REPLY_TEXT = "Reply" internal const val PRODUCT_CATALOG_START_INDEX = 0 internal const val PRODUCT_CATALOG_VERTICAL_LAYOUT = "vertical" - internal const val ICON_TEMPLATE_CANCEL_IMAGE_NAME = "cross" internal const val ICON_TEMPLATE_MIN_IMAGE_COUNT = 3 internal const val ICON_TEMPLATE_MAX_IMAGE_COUNT = 5 diff --git a/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/templates/MockDataUtils.kt b/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/templates/MockDataUtils.kt index e8b417bb..abbb0e54 100644 --- a/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/templates/MockDataUtils.kt +++ b/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/templates/MockDataUtils.kt @@ -28,7 +28,8 @@ const val MOCKED_PRIORITY = "PRIORITY_HIGH" const val MOCKED_TICKER = "ticker" const val MOCKED_TAG = "tag" const val MOCKED_URI = "https://www.adobe.com" -const val MOCKED_CAROUSEL_LAYOUT_DATA = "[{\"img\":\"https://i.imgur.com/7ZolaOv.jpeg\",\"txt\":\"Basketball Shoes\"},{\"img\":\"https://i.imgur.com/mZvLuzU.jpg\",\"txt\":\"Red Jersey\",\"uri\":\"https://firefly.adobe.com/red_jersey\"},{\"img\":\"https://i.imgur.com/X5yjy09.jpg\",\"txt\":\"Volleyball\", \"uri\":\"https://firefly.adobe.com/volleyball\"},{\"img\":\"https://i.imgur.com/35B0mkh.jpg\",\"txt\":\"Basketball\",\"uri\":\"https://firefly.adobe.com/basketball\"},{\"img\":\"https://i.imgur.com/Cs5hmfb.jpg\",\"txt\":\"Black Batting Helmet\",\"uri\":\"https://firefly.adobe.com/black_helmet\"}]" +const val MOCKED_CAROUSEL_LAYOUT_DATA = + "[{\"img\":\"https://i.imgur.com/7ZolaOv.jpeg\",\"txt\":\"Basketball Shoes\"},{\"img\":\"https://i.imgur.com/mZvLuzU.jpg\",\"txt\":\"Red Jersey\",\"uri\":\"https://firefly.adobe.com/red_jersey\"},{\"img\":\"https://i.imgur.com/X5yjy09.jpg\",\"txt\":\"Volleyball\", \"uri\":\"https://firefly.adobe.com/volleyball\"},{\"img\":\"https://i.imgur.com/35B0mkh.jpg\",\"txt\":\"Basketball\",\"uri\":\"https://firefly.adobe.com/basketball\"},{\"img\":\"https://i.imgur.com/Cs5hmfb.jpg\",\"txt\":\"Black Batting Helmet\",\"uri\":\"https://firefly.adobe.com/black_helmet\"}]" const val MOCKED_IMAGE_URI = "https://images.pexels.com/photos/260024/pexels-photo-260024.jpeg?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=2" const val MOCKED_ACTION_URI = "https://chess.com/games" @@ -40,10 +41,26 @@ const val MOCK_REMIND_LATER_TEXT = "remind me" const val MOCK_REMIND_LATER_TIME = "1234567890" const val MOCK_REMIND_LATER_DURATION = "6000" const val MOCKED_MALFORMED_JSON_ACTION_BUTTON = "[" + - "{\"label\":\"\",\"uri\":\"https://chess.com/games/552\",\"type\":\"DEEPLINK\"}," + - "{}," + - "{\"label\":\"Open the app\",\"uri\":\"\",\"type\":\"GO_TO_WEB_PAGE\"}," + - "{\"label\":\"Go to chess.com\",\"uri\":\"https://chess.com/games/552\",\"type\":\"DEEPLINK\"}]" + "{\"label\":\"\",\"uri\":\"https://chess.com/games/552\",\"type\":\"DEEPLINK\"}," + + "{}," + + "{\"label\":\"Open the app\",\"uri\":\"\",\"type\":\"GO_TO_WEB_PAGE\"}," + + "{\"label\":\"Go to chess.com\",\"uri\":\"https://chess.com/games/552\",\"type\":\"DEEPLINK\"}]" + +const val MOCK_MULTI_ICON_ITEM_PAYLOAD = "[" + + "{\"img\":\"train\",\"uri\":\"myapp://chooseShoeType/shoe1\",\"type\":\"DEEPLINK\"}," + + "{\"img\":\"bus\",\"uri\":\"myapp://chooseShoeType/shoe2\",\"type\":\"DEEPLINK\"}," + + "{\"img\":\"car\",\"uri\":\"myapp://chooseShoeType/shoe3\",\"type\":\"DEEPLINK\"}," + + "{\"img\":\"tempo\",\"uri\":\"myapp://chooseShoeType/shoe4\",\"type\":\"DEEPLINK\"}," + + "{\"img\":\"airplane\",\"uri\":\"myapp://chooseShoeType/shoe5\",\"type\":\"DEEPLINK\"}" + + "]" +const val MOCK_MULTI_ICON_ITEM_PAYLOAD_INVALID_IMAGE = "[" + + "{\"img\":\"\",\"uri\":\"myapp://chooseShoeType/shoe1\",\"type\":\"DEEPLINK\"}," + + "{\"img\":\"bus\",\"uri\":\"myapp://chooseShoeType/shoe2\",\"type\":\"DEEPLINK\"}," + + "{\"img\":\"\",\"uri\":\"myapp://chooseShoeType/shoe3\",\"type\":\"DEEPLINK\"}," + + "{\"img\":\"tempo\",\"uri\":\"myapp://chooseShoeType/shoe4\",\"type\":\"DEEPLINK\"}," + + "{\"img\":\"airplane\",\"uri\":\"myapp://chooseShoeType/shoe5\",\"type\":\"DEEPLINK\"}" + + "]" + const val MOCKED_CHANNEL_ID = "AEPSDKPushChannel" fun MutableMap.removeKeysFromMap(listOfKeys: List) { @@ -57,6 +74,7 @@ fun MutableMap.removeKeysFromMap(vararg keys: K) { this.remove(key) } } + fun MutableMap.replaceValueInMap(mapOfNewKeySet: Map) { for ((key, value) in mapOfNewKeySet) { this[key] = value @@ -69,6 +87,10 @@ fun MutableMap.replaceValueInMap(vararg keyValues: Pair) { } } +fun MutableMap.replaceValueInMap(key: K, value: V) { + this[key] = value +} + internal fun provideMockedBasicPushTemplateWithRequiredData(isFromIntent: Boolean = false): BasicPushTemplate { val data: NotificationData if (isFromIntent) { @@ -116,3 +138,10 @@ internal fun provideMockedAutoCarousalTemplate(isFromIntent: Boolean = false): A } return CarouselPushTemplate(data) as AutoCarouselPushTemplate } + +internal fun provideMockedMultiIconTemplateWithAllKeys(): MultiIconPushTemplate { + val data: NotificationData + val dataMap = MockMultiIconTemplateDataProvider.getMockedDataMapWithForMultiIcon() + data = MapData(dataMap) + return MultiIconPushTemplate(data) +} diff --git a/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/templates/MockMultiIconTemplateDataProvider.kt b/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/templates/MockMultiIconTemplateDataProvider.kt new file mode 100644 index 00000000..c6b1f06f --- /dev/null +++ b/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/templates/MockMultiIconTemplateDataProvider.kt @@ -0,0 +1,26 @@ +package com.adobe.marketing.mobile.notificationbuilder.internal.templates + +import com.adobe.marketing.mobile.notificationbuilder.PushTemplateConstants +import com.adobe.marketing.mobile.notificationbuilder.internal.PushTemplateType + +object MockMultiIconTemplateDataProvider { + + fun getMockedDataMapWithForMultiIcon(): MutableMap { + return mutableMapOf( + PushTemplateConstants.PushPayloadKeys.TAG to MOCKED_TAG, + PushTemplateConstants.PushPayloadKeys.TITLE to MOCKED_TITLE, + PushTemplateConstants.PushPayloadKeys.TEMPLATE_TYPE to PushTemplateType.MULTI_ICON.value, + PushTemplateConstants.PushPayloadKeys.BODY to "", + PushTemplateConstants.PushPayloadKeys.CHANNEL_ID to MOCKED_CHANNEL_ID, + PushTemplateConstants.PushPayloadKeys.PRIORITY to MOCKED_PRIORITY, + PushTemplateConstants.PushPayloadKeys.VISIBILITY to MOCKED_VISIBILITY, + PushTemplateConstants.PushPayloadKeys.SOUND to "bell", + PushTemplateConstants.PushPayloadKeys.SMALL_ICON to MOCKED_SMALL_ICON, + PushTemplateConstants.PushPayloadKeys.VERSION to MOCKED_PAYLOAD_VERSION, + PushTemplateConstants.PushPayloadKeys.STICKY to "true", + PushTemplateConstants.PushPayloadKeys.MULTI_ICON_CLOSE_BUTTON to "delete", + PushTemplateConstants.PushPayloadKeys.MULTI_ICON_ITEMS to MOCK_MULTI_ICON_ITEM_PAYLOAD, + ) + } + +} \ No newline at end of file diff --git a/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/templates/MultiIconPushTemplateTests.kt b/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/templates/MultiIconPushTemplateTests.kt new file mode 100644 index 00000000..49c0054e --- /dev/null +++ b/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/templates/MultiIconPushTemplateTests.kt @@ -0,0 +1,51 @@ +package com.adobe.marketing.mobile.notificationbuilder.internal.templates + +import com.adobe.marketing.mobile.notificationbuilder.PushTemplateConstants +import com.adobe.marketing.mobile.notificationbuilder.PushTemplateConstants.DEFAULT_DELETE_ICON_NAME +import com.adobe.marketing.mobile.notificationbuilder.internal.templates.MockMultiIconTemplateDataProvider.getMockedDataMapWithForMultiIcon +import com.adobe.marketing.mobile.notificationbuilder.internal.util.MapData +import org.junit.Assert.assertEquals +import org.junit.Before +import org.junit.runner.RunWith +import org.mockito.MockitoAnnotations +import org.mockito.junit.MockitoJUnitRunner +import kotlin.test.Test + +@RunWith(MockitoJUnitRunner::class) +class MultiIconPushTemplateTests { + + @Before + fun setup() { + MockitoAnnotations.initMocks(this) + } + @Test + fun testMultiIconNotificationWithAllKeys() { + val multiIconPushTemplate = provideMockedMultiIconTemplateWithAllKeys() + assertEquals(5, multiIconPushTemplate.templateItemList.size) + assertEquals("delete", multiIconPushTemplate.cancelIcon) + } + + @Test + fun testMultiIconPushTemplateWithNoCrossButtonIconKey() { + // Arrange + val dataMap = getMockedDataMapWithForMultiIcon() + dataMap.remove(PushTemplateConstants.PushPayloadKeys.MULTI_ICON_CLOSE_BUTTON) + val data = MapData(dataMap) + val multiIconPushTemplate = MultiIconPushTemplate(data) + assertEquals(5, multiIconPushTemplate.templateItemList.size) + assertEquals(DEFAULT_DELETE_ICON_NAME, multiIconPushTemplate.cancelIcon) + } + + @Test + fun testMultiIconPushTemplateOneInvalidUri() { + // Arrange + val dataMap = getMockedDataMapWithForMultiIcon() + dataMap.replaceValueInMap( + PushTemplateConstants.PushPayloadKeys.MULTI_ICON_ITEMS, + MOCK_MULTI_ICON_ITEM_PAYLOAD_INVALID_IMAGE + ) + val data = MapData(dataMap) + val multiIconPushTemplate = MultiIconPushTemplate(data) + assertEquals(3, multiIconPushTemplate.templateItemList.size) + } +} \ No newline at end of file From 8ab89d2fae120adabb62bc377900412f6a806098 Mon Sep 17 00:00:00 2001 From: Navratan Soni Date: Sat, 8 Jun 2024 00:52:29 +0530 Subject: [PATCH 120/159] Added MultiIconTest for error cases --- .../internal/templates/MockDataUtils.kt | 8 ++- .../templates/MultiIconPushTemplateTests.kt | 66 ++++++++++++++++++- 2 files changed, 72 insertions(+), 2 deletions(-) diff --git a/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/templates/MockDataUtils.kt b/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/templates/MockDataUtils.kt index abbb0e54..60c6b643 100644 --- a/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/templates/MockDataUtils.kt +++ b/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/templates/MockDataUtils.kt @@ -55,11 +55,17 @@ const val MOCK_MULTI_ICON_ITEM_PAYLOAD = "[" + "]" const val MOCK_MULTI_ICON_ITEM_PAYLOAD_INVALID_IMAGE = "[" + "{\"img\":\"\",\"uri\":\"myapp://chooseShoeType/shoe1\",\"type\":\"DEEPLINK\"}," + - "{\"img\":\"bus\",\"uri\":\"myapp://chooseShoeType/shoe2\",\"type\":\"DEEPLINK\"}," + + "{\"img\":\"bus\",\"uri\":\"myapp://chooseShoeType/shoe2\",\"type\":\"\"}," + "{\"img\":\"\",\"uri\":\"myapp://chooseShoeType/shoe3\",\"type\":\"DEEPLINK\"}," + "{\"img\":\"tempo\",\"uri\":\"myapp://chooseShoeType/shoe4\",\"type\":\"DEEPLINK\"}," + "{\"img\":\"airplane\",\"uri\":\"myapp://chooseShoeType/shoe5\",\"type\":\"DEEPLINK\"}" + "]" +const val MOCK_MULTI_ICON_ITEM_PAYLOAD_INCOMPLETE_JSON = "[" + + "{\"img\":\"train\",\"uri\":\"\",\"type\":\"DEEPLINK\"}," + + "{\"img\":\"bus\",\"uri\":\"myapp://chooseShoeType/shoe2\",\"type\":\"DEEPLINK\"}," + + "{\"img\":\"tempo\",\"uri\":\"myapp://chooseShoeType/shoe4\",\"type\":\"DEEPLINK\"}," + + "{\"img\":\"airplane\",\"uri\":\"myapp://chooseShoeType/shoe5\",\"type\":\"DEEPLINK\"}" + + "]" const val MOCKED_CHANNEL_ID = "AEPSDKPushChannel" diff --git a/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/templates/MultiIconPushTemplateTests.kt b/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/templates/MultiIconPushTemplateTests.kt index 49c0054e..b819fbf1 100644 --- a/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/templates/MultiIconPushTemplateTests.kt +++ b/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/templates/MultiIconPushTemplateTests.kt @@ -10,6 +10,7 @@ import org.junit.runner.RunWith import org.mockito.MockitoAnnotations import org.mockito.junit.MockitoJUnitRunner import kotlin.test.Test +import kotlin.test.assertFailsWith @RunWith(MockitoJUnitRunner::class) class MultiIconPushTemplateTests { @@ -37,7 +38,7 @@ class MultiIconPushTemplateTests { } @Test - fun testMultiIconPushTemplateOneInvalidUri() { + fun testMultiIconPushTemplateWithInvalidUrisAndEmptyAction() { // Arrange val dataMap = getMockedDataMapWithForMultiIcon() dataMap.replaceValueInMap( @@ -47,5 +48,68 @@ class MultiIconPushTemplateTests { val data = MapData(dataMap) val multiIconPushTemplate = MultiIconPushTemplate(data) assertEquals(3, multiIconPushTemplate.templateItemList.size) + assertEquals(multiIconPushTemplate.templateItemList[0].actionType, PushTemplateConstants.ActionType.NONE) + } + + @Test + fun testMultiIconPushTemplateNoJson() { + val dataMap = getMockedDataMapWithForMultiIcon() + dataMap.replaceValueInMap( + PushTemplateConstants.PushPayloadKeys.MULTI_ICON_ITEMS, + MOCK_MULTI_ICON_ITEM_PAYLOAD_INCOMPLETE_JSON + ) + val multiIconPushTemplate = MultiIconPushTemplate(MapData(dataMap)) + assertEquals(3, multiIconPushTemplate.templateItemList.size) + } + + @Test + fun testMultiIconPushTemplateEmptyJson() { + val dataMap = getMockedDataMapWithForMultiIcon() + dataMap.replaceValueInMap(PushTemplateConstants.PushPayloadKeys.MULTI_ICON_ITEMS, "") + val data = MapData(dataMap) + val exception = assertFailsWith { + MultiIconPushTemplate(data) + } + assertEquals( + "Required field \"${PushTemplateConstants.PushPayloadKeys.MULTI_ICON_ITEMS}\" is invalid.", + exception.message + ) + } + + @Test + fun testMultiIconPushTemplateIncompleteJson() { + val dataMap = getMockedDataMapWithForMultiIcon() + dataMap.replaceValueInMap(PushTemplateConstants.PushPayloadKeys.MULTI_ICON_ITEMS, + MOCK_MULTI_ICON_ITEM_PAYLOAD_INCOMPLETE_JSON) + val multiIconPushTemplate = MultiIconPushTemplate(MapData(dataMap)) + assertEquals(3, multiIconPushTemplate.templateItemList.size) + } + + @Test + fun testAMultiIconPushTemplateInvalidJson() { + val dataMap = getMockedDataMapWithForMultiIcon() + dataMap.replaceValueInMap(PushTemplateConstants.PushPayloadKeys.MULTI_ICON_ITEMS, MOCKED_MALFORMED_JSON_ACTION_BUTTON) + val data = MapData(dataMap) + val exception = assertFailsWith { + MultiIconPushTemplate(data) + } + assertEquals( + "\"${PushTemplateConstants.PushPayloadKeys.MULTI_ICON_ITEMS}\" field must have 3 to 5 valid items", + exception.message + ) + } + + @Test + fun testBMultiIconPushTemplateEmptyJson2() { + val dataMap = getMockedDataMapWithForMultiIcon() + dataMap.replaceValueInMap(PushTemplateConstants.PushPayloadKeys.MULTI_ICON_ITEMS, "{}") + val data = MapData(dataMap) + val exception = assertFailsWith { + MultiIconPushTemplate(data) + } + assertEquals( + "Required field \"${PushTemplateConstants.PushPayloadKeys.MULTI_ICON_ITEMS}\" is invalid.", + exception.message + ) } } \ No newline at end of file From d956e545968b720f75c2fa83bfb4bec9cd7a8852 Mon Sep 17 00:00:00 2001 From: Navratan Soni Date: Sat, 8 Jun 2024 01:05:21 +0530 Subject: [PATCH 121/159] Spotless apply --- .../internal/templates/MockDataUtils.kt | 44 +++++++++---------- .../MockMultiIconTemplateDataProvider.kt | 14 +++++- .../templates/MultiIconPushTemplateTests.kt | 19 ++++++-- 3 files changed, 50 insertions(+), 27 deletions(-) diff --git a/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/templates/MockDataUtils.kt b/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/templates/MockDataUtils.kt index 60c6b643..f5601bfb 100644 --- a/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/templates/MockDataUtils.kt +++ b/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/templates/MockDataUtils.kt @@ -41,31 +41,31 @@ const val MOCK_REMIND_LATER_TEXT = "remind me" const val MOCK_REMIND_LATER_TIME = "1234567890" const val MOCK_REMIND_LATER_DURATION = "6000" const val MOCKED_MALFORMED_JSON_ACTION_BUTTON = "[" + - "{\"label\":\"\",\"uri\":\"https://chess.com/games/552\",\"type\":\"DEEPLINK\"}," + - "{}," + - "{\"label\":\"Open the app\",\"uri\":\"\",\"type\":\"GO_TO_WEB_PAGE\"}," + - "{\"label\":\"Go to chess.com\",\"uri\":\"https://chess.com/games/552\",\"type\":\"DEEPLINK\"}]" + "{\"label\":\"\",\"uri\":\"https://chess.com/games/552\",\"type\":\"DEEPLINK\"}," + + "{}," + + "{\"label\":\"Open the app\",\"uri\":\"\",\"type\":\"GO_TO_WEB_PAGE\"}," + + "{\"label\":\"Go to chess.com\",\"uri\":\"https://chess.com/games/552\",\"type\":\"DEEPLINK\"}]" const val MOCK_MULTI_ICON_ITEM_PAYLOAD = "[" + - "{\"img\":\"train\",\"uri\":\"myapp://chooseShoeType/shoe1\",\"type\":\"DEEPLINK\"}," + - "{\"img\":\"bus\",\"uri\":\"myapp://chooseShoeType/shoe2\",\"type\":\"DEEPLINK\"}," + - "{\"img\":\"car\",\"uri\":\"myapp://chooseShoeType/shoe3\",\"type\":\"DEEPLINK\"}," + - "{\"img\":\"tempo\",\"uri\":\"myapp://chooseShoeType/shoe4\",\"type\":\"DEEPLINK\"}," + - "{\"img\":\"airplane\",\"uri\":\"myapp://chooseShoeType/shoe5\",\"type\":\"DEEPLINK\"}" + - "]" + "{\"img\":\"train\",\"uri\":\"myapp://chooseShoeType/shoe1\",\"type\":\"DEEPLINK\"}," + + "{\"img\":\"bus\",\"uri\":\"myapp://chooseShoeType/shoe2\",\"type\":\"DEEPLINK\"}," + + "{\"img\":\"car\",\"uri\":\"myapp://chooseShoeType/shoe3\",\"type\":\"DEEPLINK\"}," + + "{\"img\":\"tempo\",\"uri\":\"myapp://chooseShoeType/shoe4\",\"type\":\"DEEPLINK\"}," + + "{\"img\":\"airplane\",\"uri\":\"myapp://chooseShoeType/shoe5\",\"type\":\"DEEPLINK\"}" + + "]" const val MOCK_MULTI_ICON_ITEM_PAYLOAD_INVALID_IMAGE = "[" + - "{\"img\":\"\",\"uri\":\"myapp://chooseShoeType/shoe1\",\"type\":\"DEEPLINK\"}," + - "{\"img\":\"bus\",\"uri\":\"myapp://chooseShoeType/shoe2\",\"type\":\"\"}," + - "{\"img\":\"\",\"uri\":\"myapp://chooseShoeType/shoe3\",\"type\":\"DEEPLINK\"}," + - "{\"img\":\"tempo\",\"uri\":\"myapp://chooseShoeType/shoe4\",\"type\":\"DEEPLINK\"}," + - "{\"img\":\"airplane\",\"uri\":\"myapp://chooseShoeType/shoe5\",\"type\":\"DEEPLINK\"}" + - "]" + "{\"img\":\"\",\"uri\":\"myapp://chooseShoeType/shoe1\",\"type\":\"DEEPLINK\"}," + + "{\"img\":\"bus\",\"uri\":\"myapp://chooseShoeType/shoe2\",\"type\":\"\"}," + + "{\"img\":\"\",\"uri\":\"myapp://chooseShoeType/shoe3\",\"type\":\"DEEPLINK\"}," + + "{\"img\":\"tempo\",\"uri\":\"myapp://chooseShoeType/shoe4\",\"type\":\"DEEPLINK\"}," + + "{\"img\":\"airplane\",\"uri\":\"myapp://chooseShoeType/shoe5\",\"type\":\"DEEPLINK\"}" + + "]" const val MOCK_MULTI_ICON_ITEM_PAYLOAD_INCOMPLETE_JSON = "[" + - "{\"img\":\"train\",\"uri\":\"\",\"type\":\"DEEPLINK\"}," + - "{\"img\":\"bus\",\"uri\":\"myapp://chooseShoeType/shoe2\",\"type\":\"DEEPLINK\"}," + - "{\"img\":\"tempo\",\"uri\":\"myapp://chooseShoeType/shoe4\",\"type\":\"DEEPLINK\"}," + - "{\"img\":\"airplane\",\"uri\":\"myapp://chooseShoeType/shoe5\",\"type\":\"DEEPLINK\"}" + - "]" + "{\"img\":\"train\",\"uri\":\"\",\"type\":\"DEEPLINK\"}," + + "{\"img\":\"bus\",\"uri\":\"myapp://chooseShoeType/shoe2\",\"type\":\"DEEPLINK\"}," + + "{\"img\":\"tempo\",\"uri\":\"myapp://chooseShoeType/shoe4\",\"type\":\"DEEPLINK\"}," + + "{\"img\":\"airplane\",\"uri\":\"myapp://chooseShoeType/shoe5\",\"type\":\"DEEPLINK\"}" + + "]" const val MOCKED_CHANNEL_ID = "AEPSDKPushChannel" @@ -93,7 +93,7 @@ fun MutableMap.replaceValueInMap(vararg keyValues: Pair) { } } -fun MutableMap.replaceValueInMap(key: K, value: V) { +fun MutableMap.replaceValueInMap(key: K, value: V) { this[key] = value } diff --git a/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/templates/MockMultiIconTemplateDataProvider.kt b/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/templates/MockMultiIconTemplateDataProvider.kt index c6b1f06f..d6ba1c24 100644 --- a/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/templates/MockMultiIconTemplateDataProvider.kt +++ b/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/templates/MockMultiIconTemplateDataProvider.kt @@ -1,3 +1,14 @@ +/* + Copyright 2024 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.notificationbuilder.internal.templates import com.adobe.marketing.mobile.notificationbuilder.PushTemplateConstants @@ -22,5 +33,4 @@ object MockMultiIconTemplateDataProvider { PushTemplateConstants.PushPayloadKeys.MULTI_ICON_ITEMS to MOCK_MULTI_ICON_ITEM_PAYLOAD, ) } - -} \ No newline at end of file +} diff --git a/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/templates/MultiIconPushTemplateTests.kt b/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/templates/MultiIconPushTemplateTests.kt index b819fbf1..a0f1845d 100644 --- a/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/templates/MultiIconPushTemplateTests.kt +++ b/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/templates/MultiIconPushTemplateTests.kt @@ -1,3 +1,14 @@ +/* + Copyright 2024 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.notificationbuilder.internal.templates import com.adobe.marketing.mobile.notificationbuilder.PushTemplateConstants @@ -79,8 +90,10 @@ class MultiIconPushTemplateTests { @Test fun testMultiIconPushTemplateIncompleteJson() { val dataMap = getMockedDataMapWithForMultiIcon() - dataMap.replaceValueInMap(PushTemplateConstants.PushPayloadKeys.MULTI_ICON_ITEMS, - MOCK_MULTI_ICON_ITEM_PAYLOAD_INCOMPLETE_JSON) + dataMap.replaceValueInMap( + PushTemplateConstants.PushPayloadKeys.MULTI_ICON_ITEMS, + MOCK_MULTI_ICON_ITEM_PAYLOAD_INCOMPLETE_JSON + ) val multiIconPushTemplate = MultiIconPushTemplate(MapData(dataMap)) assertEquals(3, multiIconPushTemplate.templateItemList.size) } @@ -112,4 +125,4 @@ class MultiIconPushTemplateTests { exception.message ) } -} \ No newline at end of file +} From 2807be7892a253105fe472a7ce759bd52a8980ca Mon Sep 17 00:00:00 2001 From: Ishita Gambhir Date: Mon, 10 Jun 2024 10:38:57 +0530 Subject: [PATCH 122/159] MOB-21015: Add unit tests for NotificationManagerExtensions (#34) * MOB-21015: Add unit tests for NotificationManagerExtensions * add additional test cases --- .../NotificationManagerExtensionsTest.kt | 100 ++++++++++++++++++ .../internal/templates/MockDataUtils.kt | 2 +- 2 files changed, 101 insertions(+), 1 deletion(-) create mode 100644 code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/extensions/NotificationManagerExtensionsTest.kt diff --git a/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/extensions/NotificationManagerExtensionsTest.kt b/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/extensions/NotificationManagerExtensionsTest.kt new file mode 100644 index 00000000..4b578343 --- /dev/null +++ b/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/extensions/NotificationManagerExtensionsTest.kt @@ -0,0 +1,100 @@ +/* + Copyright 2024 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.notificationbuilder.internal.extensions + +import android.app.NotificationChannel +import android.app.NotificationManager +import android.content.Context +import android.media.RingtoneManager +import com.adobe.marketing.mobile.notificationbuilder.PushTemplateConstants +import com.adobe.marketing.mobile.notificationbuilder.internal.templates.BasicPushTemplate +import com.adobe.marketing.mobile.notificationbuilder.internal.templates.MOCKED_CHANNEL_ID +import com.adobe.marketing.mobile.notificationbuilder.internal.templates.MockAEPPushTemplateDataProvider +import com.adobe.marketing.mobile.notificationbuilder.internal.templates.provideMockedBasicPushTemplateWithAllKeys +import com.adobe.marketing.mobile.notificationbuilder.internal.templates.provideMockedBasicPushTemplateWithRequiredData +import com.adobe.marketing.mobile.notificationbuilder.internal.util.MapData +import org.junit.Assert.assertEquals +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.ArgumentMatchers.anyString +import org.mockito.Mock +import org.mockito.Mockito +import org.mockito.MockitoAnnotations +import org.robolectric.RobolectricTestRunner +import org.robolectric.RuntimeEnvironment +import org.robolectric.annotation.Config + +@RunWith(RobolectricTestRunner::class) +@Config(sdk = [31]) +class NotificationManagerExtensionsTest { + @Mock + private lateinit var context: Context + + @Before + fun setUp() { + MockitoAnnotations.openMocks(this) + context = RuntimeEnvironment.getApplication() + } + + @Test + fun `createNotificationChannelIfRequired should create a new channel if it does not exist`() { + val template = provideMockedBasicPushTemplateWithRequiredData() + + val notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager + val notificationChannelId = notificationManager.createNotificationChannelIfRequired(context, template) + + assertEquals(PushTemplateConstants.DefaultValues.DEFAULT_CHANNEL_ID, notificationChannelId) + } + + @Test + fun `createNotificationChannelIfRequired should not create a new channel if it already exists`() { + val template = provideMockedBasicPushTemplateWithAllKeys() + val notificationManager = Mockito.mock(NotificationManager::class.java) + Mockito.`when`(notificationManager.getNotificationChannel(anyString())).thenReturn(NotificationChannel(MOCKED_CHANNEL_ID, "default_channel_name", NotificationManager.IMPORTANCE_DEFAULT)) + val notificationChannelId = notificationManager.createNotificationChannelIfRequired(context, template) + + assertEquals(MOCKED_CHANNEL_ID, notificationChannelId) + } + + @Test + fun `createNotificationChannelIfRequired should set a default sound if template sound is null`() { + val template = provideMockedBasicPushTemplateWithRequiredData() + val notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager + val notificationChannelId = notificationManager.createNotificationChannelIfRequired(context, template) + val notificationChannel = notificationManager.getNotificationChannel(notificationChannelId) + + assertEquals(notificationChannel.sound, RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION)) + } + + @Test + fun `createNotificationChannelIfRequired should set a default sound if template sound is empty`() { + val dataMap = MockAEPPushTemplateDataProvider.getMockedAEPDataMapWithAllKeys() + dataMap[PushTemplateConstants.PushPayloadKeys.SOUND] = "" + val template = BasicPushTemplate(MapData(dataMap)) + val notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager + val notificationChannelId = notificationManager.createNotificationChannelIfRequired(context, template) + val notificationChannel = notificationManager.getNotificationChannel(notificationChannelId) + + assertEquals(notificationChannel.sound, RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION)) + } + + @Test + fun `createNotificationChannelIfRequired should create a silent notification channel when template is created from intent`() { + val template = provideMockedBasicPushTemplateWithRequiredData(true) + + val notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager + val notificationChannelId = notificationManager.createNotificationChannelIfRequired(context, template) + + assertEquals(PushTemplateConstants.DefaultValues.SILENT_NOTIFICATION_CHANNEL_ID, notificationChannelId) + } +} diff --git a/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/templates/MockDataUtils.kt b/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/templates/MockDataUtils.kt index e8b417bb..717669a9 100644 --- a/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/templates/MockDataUtils.kt +++ b/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/templates/MockDataUtils.kt @@ -44,7 +44,7 @@ const val MOCKED_MALFORMED_JSON_ACTION_BUTTON = "[" + "{}," + "{\"label\":\"Open the app\",\"uri\":\"\",\"type\":\"GO_TO_WEB_PAGE\"}," + "{\"label\":\"Go to chess.com\",\"uri\":\"https://chess.com/games/552\",\"type\":\"DEEPLINK\"}]" -const val MOCKED_CHANNEL_ID = "AEPSDKPushChannel" +const val MOCKED_CHANNEL_ID = "AEPSDKPushChannel1" fun MutableMap.removeKeysFromMap(listOfKeys: List) { for (key in listOfKeys) { From adc2293f3c292cc8daceef50ba9bc269de56bcae Mon Sep 17 00:00:00 2001 From: Navratan Soni Date: Mon, 10 Jun 2024 12:09:40 +0530 Subject: [PATCH 123/159] Spotless apply --- .../notificationbuilder/internal/templates/MockDataUtils.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/templates/MockDataUtils.kt b/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/templates/MockDataUtils.kt index bae68a0f..21a45ac7 100644 --- a/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/templates/MockDataUtils.kt +++ b/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/templates/MockDataUtils.kt @@ -68,7 +68,6 @@ const val MOCK_MULTI_ICON_ITEM_PAYLOAD_INCOMPLETE_JSON = "[" + "{\"img\":\"airplane\",\"uri\":\"myapp://chooseShoeType/shoe5\",\"type\":\"DEEPLINK\"}" + "]" - fun MutableMap.removeKeysFromMap(listOfKeys: List) { for (key in listOfKeys) { this.remove(key) From 6a2db7d229977f8f41d8250eb04ace18ce598681 Mon Sep 17 00:00:00 2001 From: Spoorthi Pujari <63024083+spoorthipujariadobe@users.noreply.github.com> Date: Tue, 11 Jun 2024 08:55:17 -0700 Subject: [PATCH 124/159] Fix filmstrip carousel display when images fail to download (#37) * Add all notification data, as received from caller extension, to PendingIntents for tracking * change PendingIntent for input box template * code review comments * fix LegacyNotificationBuilderTest test using IntentData * revert test to as before * Remove unused imports * remove mocking bundle in tests and use actual class * remove mocking bundle in tests and use actual class * fix formatting * one more commit to change manual carousel mocks * remove repeated keys in PendingIntent extras * change default channel name * fix zero bezel collapse text crop + filmstrip centre image height crop * make zero bezel image centerCrop * change some trace logs to warning/debug * change to use setRemoteImage * Fix filmstrip carousel display when images fail to download --- .../builders/BasicNotificationBuilder.kt | 4 +- .../ManualCarouselNotificationBuilder.kt | 38 +++++++------------ .../src/main/res/values/styles.xml | 8 ---- code/testapp/src/main/res/values/styles.xml | 11 ++++++ 4 files changed, 27 insertions(+), 34 deletions(-) create mode 100644 code/testapp/src/main/res/values/styles.xml diff --git a/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/BasicNotificationBuilder.kt b/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/BasicNotificationBuilder.kt index cfa30bce..a9b00bae 100644 --- a/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/BasicNotificationBuilder.kt +++ b/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/BasicNotificationBuilder.kt @@ -26,7 +26,7 @@ import com.adobe.marketing.mobile.notificationbuilder.PushTemplateConstants.Push import com.adobe.marketing.mobile.notificationbuilder.R import com.adobe.marketing.mobile.notificationbuilder.internal.extensions.addActionButtons import com.adobe.marketing.mobile.notificationbuilder.internal.extensions.createNotificationChannelIfRequired -import com.adobe.marketing.mobile.notificationbuilder.internal.extensions.setRemoteImage +import com.adobe.marketing.mobile.notificationbuilder.internal.extensions.setRemoteViewImage import com.adobe.marketing.mobile.notificationbuilder.internal.templates.BasicPushTemplate import com.adobe.marketing.mobile.notificationbuilder.internal.util.NotificationData import com.adobe.marketing.mobile.services.Log @@ -66,7 +66,7 @@ internal object BasicNotificationBuilder { ) // set the image on the notification - expandedLayout.setRemoteImage( + expandedLayout.setRemoteViewImage( pushTemplate.imageUrl, R.id.expanded_template_image, ) diff --git a/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/ManualCarouselNotificationBuilder.kt b/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/ManualCarouselNotificationBuilder.kt index c6c30e5d..b46eb833 100644 --- a/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/ManualCarouselNotificationBuilder.kt +++ b/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/ManualCarouselNotificationBuilder.kt @@ -52,8 +52,6 @@ internal object ManualCarouselNotificationBuilder { pushTemplate.carouselItems.map { it.imageUri } ) - val validCarouselItems = downloadCarouselItems(pushTemplate.carouselItems) - // fallback to a basic push template notification builder if less than 3 images were able // to be downloaded if (downloadedImagesCount < PushTemplateConstants.DefaultValues.CAROUSEL_MINIMUM_IMAGE_COUNT) { @@ -76,13 +74,10 @@ internal object ManualCarouselNotificationBuilder { R.layout.push_template_filmstrip_carousel ) else RemoteViews(packageName, R.layout.push_template_manual_carousel) - // extract image uris, captions, and interaction uris from the validated carousel items - val imageUris = validCarouselItems.map { it.imageUri } - val captions = validCarouselItems.map { it.captionText } - val interactionUris = validCarouselItems.map { it.interactionUri } + val validCarouselItems = downloadCarouselItems(pushTemplate.carouselItems) // get the indices for the carousel - val carouselIndices = getCarouselIndices(pushTemplate, imageUris) + val carouselIndices = getCarouselIndices(pushTemplate, validCarouselItems.size) // store the updated center image index pushTemplate.centerImageIndex = carouselIndices.second @@ -90,8 +85,6 @@ internal object ManualCarouselNotificationBuilder { // populate the images for the manual carousel setupCarouselImages( context, - captions, - interactionUris, carouselIndices, pushTemplate, trackerActivityClass, @@ -158,15 +151,15 @@ internal object ManualCarouselNotificationBuilder { private fun getCarouselIndices( pushTemplate: ManualCarouselPushTemplate, - imageUris: List + carouselSize: Int ): Triple { val carouselIndices: Triple if (pushTemplate.intentAction?.isNotEmpty() == true) { carouselIndices = if (pushTemplate.intentAction == PushTemplateConstants.IntentActions.MANUAL_CAROUSEL_LEFT_CLICKED || pushTemplate.intentAction == PushTemplateConstants.IntentActions.FILMSTRIP_LEFT_CLICKED) { - getNewIndicesForNavigateLeft(pushTemplate.centerImageIndex, imageUris.size) + getNewIndicesForNavigateLeft(pushTemplate.centerImageIndex, carouselSize) } else { - getNewIndicesForNavigateRight(pushTemplate.centerImageIndex, imageUris.size) + getNewIndicesForNavigateRight(pushTemplate.centerImageIndex, carouselSize) } } else { // setup default indices if not building the notification from an intent carouselIndices = @@ -178,7 +171,7 @@ internal object ManualCarouselNotificationBuilder { ) } else { Triple( - imageUris.size - 1, + carouselSize - 1, PushTemplateConstants.DefaultValues.MANUAL_CAROUSEL_START_INDEX, PushTemplateConstants.DefaultValues.MANUAL_CAROUSEL_START_INDEX + 1 ) @@ -190,8 +183,6 @@ internal object ManualCarouselNotificationBuilder { private fun setupCarouselImages( context: Context, - captions: List, - interactionUris: List, newIndices: Triple, pushTemplate: ManualCarouselPushTemplate, trackerActivityClass: Class?, @@ -202,8 +193,7 @@ internal object ManualCarouselNotificationBuilder { if (pushTemplate.carouselLayout == PushTemplateConstants.DefaultValues.FILMSTRIP_CAROUSEL_MODE) { populateFilmstripCarouselImages( context, - captions, - interactionUris, + validCarouselItems, newIndices, pushTemplate, trackerActivityClass, @@ -336,15 +326,14 @@ internal object ManualCarouselNotificationBuilder { */ private fun populateFilmstripCarouselImages( context: Context, - imageCaptions: List, - imageClickActions: List, + validCarouselItems: List, newIndices: Triple, pushTemplate: ManualCarouselPushTemplate, trackerActivityClass: Class?, expandedLayout: RemoteViews ) { // get all captions present then set center caption text - val centerCaptionText = imageCaptions[newIndices.second] + val centerCaptionText = validCarouselItems[newIndices.second].captionText expandedLayout.setTextViewText( R.id.manual_carousel_filmstrip_caption, centerCaptionText @@ -362,21 +351,21 @@ internal object ManualCarouselNotificationBuilder { } val newLeftImage = PushTemplateImageUtils.getCachedImage( - pushTemplate.carouselItems[newIndices.first].imageUri + validCarouselItems[newIndices.first].imageUri ) expandedLayout.setImageViewBitmap( R.id.manual_carousel_filmstrip_left, newLeftImage ) val newCenterImage = PushTemplateImageUtils.getCachedImage( - pushTemplate.carouselItems[newIndices.second].imageUri + validCarouselItems[newIndices.second].imageUri ) expandedLayout.setImageViewBitmap( R.id.manual_carousel_filmstrip_center, newCenterImage ) val newRightImage = PushTemplateImageUtils.getCachedImage( - pushTemplate.carouselItems[newIndices.third].imageUri + validCarouselItems[newIndices.third].imageUri ) expandedLayout.setImageViewBitmap( R.id.manual_carousel_filmstrip_right, newRightImage @@ -384,7 +373,8 @@ internal object ManualCarouselNotificationBuilder { // assign a click action pending intent to the center image view val interactionUri = - if (!imageClickActions[newIndices.second].isNullOrEmpty()) imageClickActions[newIndices.second] else pushTemplate.actionUri + if (!validCarouselItems[newIndices.second].interactionUri.isNullOrEmpty()) validCarouselItems[newIndices.second].interactionUri + else pushTemplate.actionUri expandedLayout.setRemoteViewClickAction( context, trackerActivityClass, diff --git a/code/notificationbuilder/src/main/res/values/styles.xml b/code/notificationbuilder/src/main/res/values/styles.xml index aa9567d8..aec29723 100644 --- a/code/notificationbuilder/src/main/res/values/styles.xml +++ b/code/notificationbuilder/src/main/res/values/styles.xml @@ -13,14 +13,6 @@ --> - + \ No newline at end of file From 9c1d529044557467b26ca9c1a36e9c7362ef8d84 Mon Sep 17 00:00:00 2001 From: saquib-adobe Date: Tue, 4 Jun 2024 11:26:34 +0530 Subject: [PATCH 125/159] [MOB-20955] ManualCarouselNotificationBuilder unit tests added [MOB-20955] ManualCarouselNotificationBuilder unit tests added [MOB-20955] ManualCarouselNotificationBuilder unit tests added --- .../internal/PushTemplateImageUtils.kt | 3 +- .../ManualCarouselNotificationBuilder.kt | 2 +- .../ManualCarouselNotificationBuilderTest.kt | 75 +++++++++++++++++++ 3 files changed, 78 insertions(+), 2 deletions(-) create mode 100644 code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/ManualCarouselNotificationBuilderTest.kt diff --git a/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/PushTemplateImageUtils.kt b/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/PushTemplateImageUtils.kt index 8144e654..23311957 100644 --- a/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/PushTemplateImageUtils.kt +++ b/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/PushTemplateImageUtils.kt @@ -42,7 +42,7 @@ import java.util.concurrent.atomic.AtomicInteger * Utility functions to assist in downloading and caching images for push template notifications. */ -internal object PushTemplateImageUtils { +object PushTemplateImageUtils { private const val SELF_TAG = "PushTemplateImageUtil" private const val FULL_BITMAP_QUALITY = 100 private const val DOWNLOAD_TIMEOUT_SECS = 10 @@ -180,6 +180,7 @@ internal object PushTemplateImageUtils { * @param url [String] containing the image url to retrieve from cache * @return [Bitmap] containing the image retrieved from cache, or `null` if no image is found */ + @JvmStatic internal fun getCachedImage(url: String?): Bitmap? { val assetCacheLocation = getAssetCacheLocation() if (url == null || !UrlUtils.isValidUrl(url) || assetCacheLocation.isNullOrEmpty()) { diff --git a/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/ManualCarouselNotificationBuilder.kt b/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/ManualCarouselNotificationBuilder.kt index b46eb833..6c5eb2fb 100644 --- a/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/ManualCarouselNotificationBuilder.kt +++ b/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/ManualCarouselNotificationBuilder.kt @@ -129,7 +129,7 @@ internal object ManualCarouselNotificationBuilder { * @param items the list of [CarouselPushTemplate.CarouselItem] objects to be displayed in the filmstrip carousel * @return a list of `CarouselPushTemplate.CarouselItem` objects that were successfully downloaded */ - private fun downloadCarouselItems( + internal fun downloadCarouselItems( items: List ): List { val validCarouselItems = mutableListOf() diff --git a/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/ManualCarouselNotificationBuilderTest.kt b/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/ManualCarouselNotificationBuilderTest.kt new file mode 100644 index 00000000..7aea2c28 --- /dev/null +++ b/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/ManualCarouselNotificationBuilderTest.kt @@ -0,0 +1,75 @@ +/* + Copyright 2024 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.notificationbuilder.internal.builders + +import android.app.Activity +import android.content.BroadcastReceiver +import android.content.Context +import android.graphics.Bitmap +import com.adobe.marketing.mobile.notificationbuilder.internal.PushTemplateImageUtils +import com.adobe.marketing.mobile.notificationbuilder.internal.PushTemplateImageUtils.getCachedImage +import com.adobe.marketing.mobile.notificationbuilder.internal.builders.ManualCarouselNotificationBuilder.downloadCarouselItems +import com.adobe.marketing.mobile.notificationbuilder.internal.templates.ManualCarouselPushTemplate +import com.adobe.marketing.mobile.notificationbuilder.internal.templates.provideMockedManualCarousalTemplate +import org.junit.Assert.assertNotNull +import org.junit.Assert.assertTrue +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.ArgumentMatchers.anyString +import org.mockito.Mockito.mock +import org.mockito.Mockito.mockStatic +import org.mockito.Mockito.`when` +import org.robolectric.RobolectricTestRunner +import org.robolectric.RuntimeEnvironment +import org.robolectric.annotation.Config + +@RunWith(RobolectricTestRunner::class) +@Config(sdk = [31]) +class ManualCarouselNotificationBuilderTest { + + private lateinit var context: Context + private lateinit var pushTemplate: ManualCarouselPushTemplate + private lateinit var trackerActivityClass: Class + private lateinit var broadcastReceiverClass: Class + + @Before + fun setUp() { + context = RuntimeEnvironment.getApplication() + pushTemplate = provideMockedManualCarousalTemplate(false) + trackerActivityClass = mock(Activity::class.java).javaClass + broadcastReceiverClass = mock(BroadcastReceiver::class.java).javaClass + } + @Test + fun `construct returns NotificationCompat Builder for valid inputs`() { + val result = ManualCarouselNotificationBuilder.construct( + context, + pushTemplate, + trackerActivityClass, + broadcastReceiverClass + ) + assertNotNull(result) + } + + @Test + fun `test to verify the number of valid image urls available is exactly equals to the number of images downloaded`() { + mockStatic(PushTemplateImageUtils::class.java) + val bitmapMock = mock(Bitmap::class.java) + var invocationCount = 0 + `when`(getCachedImage(anyString())).thenAnswer { + invocationCount++ + if (invocationCount <= 3) bitmapMock else null + } + val imageList = downloadCarouselItems(pushTemplate.carouselItems) + assertTrue(imageList.size == 3) + } +} From 2a3f119042654bb30880101c693efd05207ce481 Mon Sep 17 00:00:00 2001 From: saquib-adobe Date: Wed, 5 Jun 2024 16:14:25 +0530 Subject: [PATCH 126/159] [MOB-20955] ManualCarouselNotificationBuilder unit tests added for fallback if condition and mockk added [MOB-20955] ManualCarouselNotificationBuilder unit tests added for fallback if condition and mockk added --- code/notificationbuilder/build.gradle.kts | 1 + .../internal/PushTemplateImageUtils.kt | 3 +- .../ManualCarouselNotificationBuilderTest.kt | 63 +++++++++++++------ 3 files changed, 46 insertions(+), 21 deletions(-) diff --git a/code/notificationbuilder/build.gradle.kts b/code/notificationbuilder/build.gradle.kts index 746c5108..60943c91 100644 --- a/code/notificationbuilder/build.gradle.kts +++ b/code/notificationbuilder/build.gradle.kts @@ -39,4 +39,5 @@ aepLibrary { dependencies { implementation("com.adobe.marketing.mobile:core:$mavenCoreVersion") testImplementation("org.robolectric:robolectric:4.7") + testImplementation("io.mockk:mockk:1.13.11") } diff --git a/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/PushTemplateImageUtils.kt b/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/PushTemplateImageUtils.kt index 23311957..8144e654 100644 --- a/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/PushTemplateImageUtils.kt +++ b/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/PushTemplateImageUtils.kt @@ -42,7 +42,7 @@ import java.util.concurrent.atomic.AtomicInteger * Utility functions to assist in downloading and caching images for push template notifications. */ -object PushTemplateImageUtils { +internal object PushTemplateImageUtils { private const val SELF_TAG = "PushTemplateImageUtil" private const val FULL_BITMAP_QUALITY = 100 private const val DOWNLOAD_TIMEOUT_SECS = 10 @@ -180,7 +180,6 @@ object PushTemplateImageUtils { * @param url [String] containing the image url to retrieve from cache * @return [Bitmap] containing the image retrieved from cache, or `null` if no image is found */ - @JvmStatic internal fun getCachedImage(url: String?): Bitmap? { val assetCacheLocation = getAssetCacheLocation() if (url == null || !UrlUtils.isValidUrl(url) || assetCacheLocation.isNullOrEmpty()) { diff --git a/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/ManualCarouselNotificationBuilderTest.kt b/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/ManualCarouselNotificationBuilderTest.kt index 7aea2c28..a7f495a2 100644 --- a/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/ManualCarouselNotificationBuilderTest.kt +++ b/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/ManualCarouselNotificationBuilderTest.kt @@ -14,21 +14,18 @@ package com.adobe.marketing.mobile.notificationbuilder.internal.builders import android.app.Activity import android.content.BroadcastReceiver import android.content.Context -import android.graphics.Bitmap import com.adobe.marketing.mobile.notificationbuilder.internal.PushTemplateImageUtils -import com.adobe.marketing.mobile.notificationbuilder.internal.PushTemplateImageUtils.getCachedImage -import com.adobe.marketing.mobile.notificationbuilder.internal.builders.ManualCarouselNotificationBuilder.downloadCarouselItems +import com.adobe.marketing.mobile.notificationbuilder.internal.PushTemplateImageUtils.cacheImages import com.adobe.marketing.mobile.notificationbuilder.internal.templates.ManualCarouselPushTemplate import com.adobe.marketing.mobile.notificationbuilder.internal.templates.provideMockedManualCarousalTemplate +import io.mockk.every +import io.mockk.mockkClass +import io.mockk.mockkObject +import io.mockk.verify import org.junit.Assert.assertNotNull -import org.junit.Assert.assertTrue import org.junit.Before import org.junit.Test import org.junit.runner.RunWith -import org.mockito.ArgumentMatchers.anyString -import org.mockito.Mockito.mock -import org.mockito.Mockito.mockStatic -import org.mockito.Mockito.`when` import org.robolectric.RobolectricTestRunner import org.robolectric.RuntimeEnvironment import org.robolectric.annotation.Config @@ -46,9 +43,12 @@ class ManualCarouselNotificationBuilderTest { fun setUp() { context = RuntimeEnvironment.getApplication() pushTemplate = provideMockedManualCarousalTemplate(false) - trackerActivityClass = mock(Activity::class.java).javaClass - broadcastReceiverClass = mock(BroadcastReceiver::class.java).javaClass + trackerActivityClass = mockkClass(Activity::class, relaxed = true).javaClass + broadcastReceiverClass = mockkClass(BroadcastReceiver::class, relaxed = true).javaClass + mockkObject(PushTemplateImageUtils) + mockkObject(BasicNotificationBuilder) } + @Test fun `construct returns NotificationCompat Builder for valid inputs`() { val result = ManualCarouselNotificationBuilder.construct( @@ -61,15 +61,40 @@ class ManualCarouselNotificationBuilderTest { } @Test - fun `test to verify the number of valid image urls available is exactly equals to the number of images downloaded`() { - mockStatic(PushTemplateImageUtils::class.java) - val bitmapMock = mock(Bitmap::class.java) - var invocationCount = 0 - `when`(getCachedImage(anyString())).thenAnswer { - invocationCount++ - if (invocationCount <= 3) bitmapMock else null + fun `construct returns BasicNotificationBuilder if download image count is less than 3`() { + every { cacheImages(any()) } answers { 2 } + ManualCarouselNotificationBuilder.construct( + context, + pushTemplate, + trackerActivityClass, + broadcastReceiverClass + ) + verify { + BasicNotificationBuilder.fallbackToBasicNotification( + context, + trackerActivityClass, + broadcastReceiverClass, + pushTemplate.data + ) + } + } + + @Test + fun `construct returns AEPPushNotificationBuilder if download image count is greater than equal to 3`() { + every { cacheImages(any()) } answers { 3 } + ManualCarouselNotificationBuilder.construct( + context, + pushTemplate, + trackerActivityClass, + broadcastReceiverClass + ) + verify(exactly = 0) { + BasicNotificationBuilder.fallbackToBasicNotification( + context, + trackerActivityClass, + broadcastReceiverClass, + pushTemplate.data + ) } - val imageList = downloadCarouselItems(pushTemplate.carouselItems) - assertTrue(imageList.size == 3) } } From 25d1901a23920ca9d472a5dd43258d952b8296dc Mon Sep 17 00:00:00 2001 From: saquib-adobe Date: Thu, 6 Jun 2024 00:38:19 +0530 Subject: [PATCH 127/159] [MOB-20955] ManualCarouselNotificationBuilder unit tests added for downloadCarouselItems --- .../builders/ManualCarouselNotificationBuilderTest.kt | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/ManualCarouselNotificationBuilderTest.kt b/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/ManualCarouselNotificationBuilderTest.kt index a7f495a2..ccca16d3 100644 --- a/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/ManualCarouselNotificationBuilderTest.kt +++ b/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/ManualCarouselNotificationBuilderTest.kt @@ -14,14 +14,17 @@ package com.adobe.marketing.mobile.notificationbuilder.internal.builders import android.app.Activity import android.content.BroadcastReceiver import android.content.Context +import android.graphics.Bitmap import com.adobe.marketing.mobile.notificationbuilder.internal.PushTemplateImageUtils import com.adobe.marketing.mobile.notificationbuilder.internal.PushTemplateImageUtils.cacheImages +import com.adobe.marketing.mobile.notificationbuilder.internal.PushTemplateImageUtils.getCachedImage import com.adobe.marketing.mobile.notificationbuilder.internal.templates.ManualCarouselPushTemplate import com.adobe.marketing.mobile.notificationbuilder.internal.templates.provideMockedManualCarousalTemplate import io.mockk.every import io.mockk.mockkClass import io.mockk.mockkObject import io.mockk.verify +import org.junit.Assert.assertFalse import org.junit.Assert.assertNotNull import org.junit.Before import org.junit.Test @@ -97,4 +100,12 @@ class ManualCarouselNotificationBuilderTest { ) } } + + @Test + fun `test downloadCarouselItems return non empty list for it retrieve the image bitmap from cache successfully`() { + every { getCachedImage(any()) } answers { mockkClass(Bitmap::class) } + val imagesList = + ManualCarouselNotificationBuilder.downloadCarouselItems(pushTemplate.carouselItems) + assertFalse(imagesList.isEmpty()) + } } From ae3761bba2bf18efd95430b13d24b5b273508fc8 Mon Sep 17 00:00:00 2001 From: saquib-adobe Date: Mon, 10 Jun 2024 11:42:27 +0530 Subject: [PATCH 128/159] [MOB-20955] Tests added for getCarouselIndices method of ManualCarouselNotificationBuilder class [MOB-20955] Tests added for getCarouselIndices method of ManualCarouselNotificationBuilder class --- .../ManualCarouselNotificationBuilder.kt | 2 +- .../templates/ManualCarouselPushTemplate.kt | 2 +- .../ManualCarouselNotificationBuilderTest.kt | 34 +++++++++++++++++++ 3 files changed, 36 insertions(+), 2 deletions(-) diff --git a/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/ManualCarouselNotificationBuilder.kt b/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/ManualCarouselNotificationBuilder.kt index 6c5eb2fb..8e00d303 100644 --- a/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/ManualCarouselNotificationBuilder.kt +++ b/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/ManualCarouselNotificationBuilder.kt @@ -149,7 +149,7 @@ internal object ManualCarouselNotificationBuilder { return validCarouselItems } - private fun getCarouselIndices( + internal fun getCarouselIndices( pushTemplate: ManualCarouselPushTemplate, carouselSize: Int ): Triple { diff --git a/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/templates/ManualCarouselPushTemplate.kt b/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/templates/ManualCarouselPushTemplate.kt index 35448e9e..ec287d44 100644 --- a/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/templates/ManualCarouselPushTemplate.kt +++ b/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/templates/ManualCarouselPushTemplate.kt @@ -17,7 +17,7 @@ import com.adobe.marketing.mobile.notificationbuilder.internal.util.Notification internal class ManualCarouselPushTemplate(data: NotificationData) : CarouselPushTemplate(data) { internal var intentAction: String? = null - private set + internal set internal var centerImageIndex: Int = PushTemplateConstants.DefaultValues.NO_CENTER_INDEX_SET /** diff --git a/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/ManualCarouselNotificationBuilderTest.kt b/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/ManualCarouselNotificationBuilderTest.kt index ccca16d3..50ae2caa 100644 --- a/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/ManualCarouselNotificationBuilderTest.kt +++ b/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/ManualCarouselNotificationBuilderTest.kt @@ -15,15 +15,18 @@ import android.app.Activity import android.content.BroadcastReceiver import android.content.Context import android.graphics.Bitmap +import com.adobe.marketing.mobile.notificationbuilder.PushTemplateConstants import com.adobe.marketing.mobile.notificationbuilder.internal.PushTemplateImageUtils import com.adobe.marketing.mobile.notificationbuilder.internal.PushTemplateImageUtils.cacheImages import com.adobe.marketing.mobile.notificationbuilder.internal.PushTemplateImageUtils.getCachedImage +import com.adobe.marketing.mobile.notificationbuilder.internal.builders.ManualCarouselNotificationBuilder.getCarouselIndices import com.adobe.marketing.mobile.notificationbuilder.internal.templates.ManualCarouselPushTemplate import com.adobe.marketing.mobile.notificationbuilder.internal.templates.provideMockedManualCarousalTemplate import io.mockk.every import io.mockk.mockkClass import io.mockk.mockkObject import io.mockk.verify +import org.junit.Assert.assertEquals import org.junit.Assert.assertFalse import org.junit.Assert.assertNotNull import org.junit.Before @@ -108,4 +111,35 @@ class ManualCarouselNotificationBuilderTest { ManualCarouselNotificationBuilder.downloadCarouselItems(pushTemplate.carouselItems) assertFalse(imagesList.isEmpty()) } + + @Test + fun `test getCarouselIndices with left click intent action`() { + pushTemplate.intentAction = PushTemplateConstants.IntentActions.MANUAL_CAROUSEL_LEFT_CLICKED + val imageUris = listOf("image1", "image2", "image3") + val result = getCarouselIndices(pushTemplate, imageUris) + assertEquals(Triple(1, 2, 0), result) + } + + @Test + fun `test getCarouselIndices with filmstrip left click intent action`() { + pushTemplate.intentAction = PushTemplateConstants.IntentActions.FILMSTRIP_LEFT_CLICKED + val imageUris = listOf("image1", "image2", "image3") + val result = getCarouselIndices(pushTemplate, imageUris) + assertEquals(Triple(1, 2, 0), result) + } + + @Test + fun `test getCarouselIndices with no intent action and filmstrip layout`() { + pushTemplate.intentAction = PushTemplateConstants.DefaultValues.FILMSTRIP_CAROUSEL_MODE + val imageUris = listOf("image1", "image2", "image3") + val result = getCarouselIndices(pushTemplate, imageUris) + assertEquals( + Triple( + PushTemplateConstants.DefaultValues.FILMSTRIP_CAROUSEL_CENTER_INDEX - 1, + PushTemplateConstants.DefaultValues.FILMSTRIP_CAROUSEL_CENTER_INDEX, + PushTemplateConstants.DefaultValues.FILMSTRIP_CAROUSEL_CENTER_INDEX + 1 + ), + result + ) + } } From 168e6f83400d04c77bdb2fa679ef74eb1fc3b8ce Mon Sep 17 00:00:00 2001 From: saquib-adobe Date: Mon, 10 Jun 2024 14:33:51 +0530 Subject: [PATCH 129/159] [MOB-20955] Tests added for populateFilmstripCarouselImages method of ManualCarouselNotificationBuilder class [MOB-20955] Tests added for populateFilmstripCarouselImages method of ManualCarouselNotificationBuilder class [MOB-20955] Tests added for populateManualCarouselImages method of ManualCarouselNotificationBuilder class --- .../ManualCarouselNotificationBuilder.kt | 4 +- .../ManualCarouselNotificationBuilderTest.kt | 135 ++++++++++++++++++ 2 files changed, 137 insertions(+), 2 deletions(-) diff --git a/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/ManualCarouselNotificationBuilder.kt b/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/ManualCarouselNotificationBuilder.kt index 8e00d303..9b741882 100644 --- a/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/ManualCarouselNotificationBuilder.kt +++ b/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/ManualCarouselNotificationBuilder.kt @@ -263,7 +263,7 @@ internal object ManualCarouselNotificationBuilder { * @param trackerActivityClass the [Class] of the activity that will be used for tracking interactions with the carousel item * @param expandedLayout the [RemoteViews] containing the expanded layout of the notification */ - private fun populateManualCarouselImages( + internal fun populateManualCarouselImages( context: Context, items: List, packageName: String?, @@ -324,7 +324,7 @@ internal object ManualCarouselNotificationBuilder { * @param trackerActivityClass the [Class] of the activity that will be used for tracking interactions with the carousel item * @param expandedLayout the [RemoteViews] containing the expanded layout of the notification */ - private fun populateFilmstripCarouselImages( + internal fun populateFilmstripCarouselImages( context: Context, validCarouselItems: List, newIndices: Triple, diff --git a/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/ManualCarouselNotificationBuilderTest.kt b/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/ManualCarouselNotificationBuilderTest.kt index 50ae2caa..f9a79b85 100644 --- a/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/ManualCarouselNotificationBuilderTest.kt +++ b/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/ManualCarouselNotificationBuilderTest.kt @@ -15,14 +15,22 @@ import android.app.Activity import android.content.BroadcastReceiver import android.content.Context import android.graphics.Bitmap +import android.widget.RemoteViews import com.adobe.marketing.mobile.notificationbuilder.PushTemplateConstants +import com.adobe.marketing.mobile.notificationbuilder.internal.PendingIntentUtils import com.adobe.marketing.mobile.notificationbuilder.internal.PushTemplateImageUtils import com.adobe.marketing.mobile.notificationbuilder.internal.PushTemplateImageUtils.cacheImages +import com.adobe.marketing.mobile.notificationbuilder.internal.PushTemplateImageUtils.getAssetCacheLocation import com.adobe.marketing.mobile.notificationbuilder.internal.PushTemplateImageUtils.getCachedImage import com.adobe.marketing.mobile.notificationbuilder.internal.builders.ManualCarouselNotificationBuilder.getCarouselIndices +import com.adobe.marketing.mobile.notificationbuilder.internal.builders.ManualCarouselNotificationBuilder.populateFilmstripCarouselImages +import com.adobe.marketing.mobile.notificationbuilder.internal.builders.ManualCarouselNotificationBuilder.populateManualCarouselImages +import com.adobe.marketing.mobile.notificationbuilder.internal.extensions.setRemoteViewClickAction import com.adobe.marketing.mobile.notificationbuilder.internal.templates.ManualCarouselPushTemplate import com.adobe.marketing.mobile.notificationbuilder.internal.templates.provideMockedManualCarousalTemplate +import io.mockk.Runs import io.mockk.every +import io.mockk.just import io.mockk.mockkClass import io.mockk.mockkObject import io.mockk.verify @@ -42,6 +50,7 @@ class ManualCarouselNotificationBuilderTest { private lateinit var context: Context private lateinit var pushTemplate: ManualCarouselPushTemplate + private lateinit var expandedLayout: RemoteViews private lateinit var trackerActivityClass: Class private lateinit var broadcastReceiverClass: Class @@ -53,6 +62,8 @@ class ManualCarouselNotificationBuilderTest { broadcastReceiverClass = mockkClass(BroadcastReceiver::class, relaxed = true).javaClass mockkObject(PushTemplateImageUtils) mockkObject(BasicNotificationBuilder) + mockkObject(PendingIntentUtils) + expandedLayout = mockkClass(RemoteViews::class) } @Test @@ -142,4 +153,128 @@ class ManualCarouselNotificationBuilderTest { result ) } + + @Test + fun `test populateManualCarouselImages with valid images`() { + val packageName = context.packageName + val centerIndex = 1 + every { getCachedImage(any()) } answers { mockkClass(Bitmap::class) } + every { expandedLayout.addView(any(), any()) } just Runs + every { expandedLayout.setDisplayedChild(any(), any()) } just Runs + + populateManualCarouselImages( + context, + pushTemplate.carouselItems, + packageName, + centerIndex, + pushTemplate, + trackerActivityClass, + expandedLayout + ) + + verify { expandedLayout.addView(any(), any()) } + verify { expandedLayout.setDisplayedChild(any(), centerIndex) } + } + + @Test + fun `test populateManualCarouselImages with null images`() { + val packageName = context.packageName + val centerIndex = 1 + every { getCachedImage(any()) } returns null + + populateManualCarouselImages( + context, + pushTemplate.carouselItems, + packageName, + centerIndex, + pushTemplate, + trackerActivityClass, + expandedLayout + ) + + verify(exactly = 0) { expandedLayout.addView(any(), any()) } + verify(exactly = 0) { expandedLayout.setDisplayedChild(any(), centerIndex) } + } + + @Test + fun `test populateFilmstripCarouselImages with valid images`() { + val mockBitmap = mockkClass(Bitmap::class) + val imageCaptions = listOf("Caption 1", "Caption 2", "Caption 3") + val imageClickActions = listOf("Action 1", "Action 2", "Action 3") + val newIndices = Triple(0, 1, 2) + every { getAssetCacheLocation() } answers { "assetCacheLocation" } + every { getCachedImage(any()) } answers { mockBitmap } + every { expandedLayout.setTextViewText(any(), "Caption 2") } returns Unit + every { expandedLayout.setImageViewBitmap(any(), mockBitmap) } returns Unit + every { + expandedLayout.setRemoteViewClickAction( + context, + trackerActivityClass, + any(), + any(), + null, + any() + ) + } returns Unit + populateFilmstripCarouselImages( + context, + imageCaptions, + imageClickActions, + newIndices, + pushTemplate, + trackerActivityClass, + expandedLayout + ) + verify(exactly = 1) { + expandedLayout.setRemoteViewClickAction( + context, + trackerActivityClass, + any(), + any(), + null, + any() + ) + } + } + + @Test + fun `test populateFilmstripCarouselImages with null asset cached location`() { + val mockBitmap = mockkClass(Bitmap::class) + val imageCaptions = listOf("Caption 1", "Caption 2", "Caption 3") + val imageClickActions = listOf("Action 1", "Action 2", "Action 3") + val newIndices = Triple(0, 1, 2) + every { getAssetCacheLocation() } answers { null } + every { getCachedImage(any()) } answers { mockBitmap } + every { expandedLayout.setTextViewText(any(), "Caption 2") } returns Unit + every { expandedLayout.setImageViewBitmap(any(), mockBitmap) } returns Unit + every { + expandedLayout.setRemoteViewClickAction( + context, + trackerActivityClass, + any(), + any(), + null, + any() + ) + } returns Unit + populateFilmstripCarouselImages( + context, + imageCaptions, + imageClickActions, + newIndices, + pushTemplate, + trackerActivityClass, + expandedLayout + ) + verify(exactly = 0) { + expandedLayout.setRemoteViewClickAction( + context, + trackerActivityClass, + any(), + any(), + null, + any() + ) + } + } } From 5e4130d4dbed88e24c87ba3b076bed5c19585d61 Mon Sep 17 00:00:00 2001 From: saquib-adobe Date: Wed, 12 Jun 2024 16:20:12 +0530 Subject: [PATCH 130/159] [MOB-20955] PR comments resolved --- .../ManualCarouselNotificationBuilder.kt | 5 ++ .../templates/ManualCarouselPushTemplate.kt | 2 +- .../ManualCarouselNotificationBuilderTest.kt | 66 +++++++++++++------ 3 files changed, 53 insertions(+), 20 deletions(-) diff --git a/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/ManualCarouselNotificationBuilder.kt b/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/ManualCarouselNotificationBuilder.kt index 9b741882..f0ab8e0b 100644 --- a/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/ManualCarouselNotificationBuilder.kt +++ b/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/ManualCarouselNotificationBuilder.kt @@ -19,6 +19,7 @@ import android.content.Context import android.content.Intent import android.graphics.Bitmap import android.widget.RemoteViews +import androidx.annotation.VisibleForTesting import androidx.core.app.NotificationCompat import com.adobe.marketing.mobile.notificationbuilder.NotificationConstructionFailedException import com.adobe.marketing.mobile.notificationbuilder.PushTemplateConstants @@ -129,6 +130,7 @@ internal object ManualCarouselNotificationBuilder { * @param items the list of [CarouselPushTemplate.CarouselItem] objects to be displayed in the filmstrip carousel * @return a list of `CarouselPushTemplate.CarouselItem` objects that were successfully downloaded */ + @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) internal fun downloadCarouselItems( items: List ): List { @@ -149,6 +151,7 @@ internal object ManualCarouselNotificationBuilder { return validCarouselItems } + @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) internal fun getCarouselIndices( pushTemplate: ManualCarouselPushTemplate, carouselSize: Int @@ -263,6 +266,7 @@ internal object ManualCarouselNotificationBuilder { * @param trackerActivityClass the [Class] of the activity that will be used for tracking interactions with the carousel item * @param expandedLayout the [RemoteViews] containing the expanded layout of the notification */ + @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) internal fun populateManualCarouselImages( context: Context, items: List, @@ -324,6 +328,7 @@ internal object ManualCarouselNotificationBuilder { * @param trackerActivityClass the [Class] of the activity that will be used for tracking interactions with the carousel item * @param expandedLayout the [RemoteViews] containing the expanded layout of the notification */ + @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) internal fun populateFilmstripCarouselImages( context: Context, validCarouselItems: List, diff --git a/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/templates/ManualCarouselPushTemplate.kt b/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/templates/ManualCarouselPushTemplate.kt index ec287d44..35448e9e 100644 --- a/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/templates/ManualCarouselPushTemplate.kt +++ b/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/templates/ManualCarouselPushTemplate.kt @@ -17,7 +17,7 @@ import com.adobe.marketing.mobile.notificationbuilder.internal.util.Notification internal class ManualCarouselPushTemplate(data: NotificationData) : CarouselPushTemplate(data) { internal var intentAction: String? = null - internal set + private set internal var centerImageIndex: Int = PushTemplateConstants.DefaultValues.NO_CENTER_INDEX_SET /** diff --git a/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/ManualCarouselNotificationBuilderTest.kt b/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/ManualCarouselNotificationBuilderTest.kt index f9a79b85..727bc699 100644 --- a/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/ManualCarouselNotificationBuilderTest.kt +++ b/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/ManualCarouselNotificationBuilderTest.kt @@ -26,17 +26,21 @@ import com.adobe.marketing.mobile.notificationbuilder.internal.builders.ManualCa import com.adobe.marketing.mobile.notificationbuilder.internal.builders.ManualCarouselNotificationBuilder.populateFilmstripCarouselImages import com.adobe.marketing.mobile.notificationbuilder.internal.builders.ManualCarouselNotificationBuilder.populateManualCarouselImages import com.adobe.marketing.mobile.notificationbuilder.internal.extensions.setRemoteViewClickAction +import com.adobe.marketing.mobile.notificationbuilder.internal.templates.CarouselPushTemplate import com.adobe.marketing.mobile.notificationbuilder.internal.templates.ManualCarouselPushTemplate +import com.adobe.marketing.mobile.notificationbuilder.internal.templates.MockCarousalTemplateDataProvider import com.adobe.marketing.mobile.notificationbuilder.internal.templates.provideMockedManualCarousalTemplate +import com.adobe.marketing.mobile.notificationbuilder.internal.util.IntentData import io.mockk.Runs import io.mockk.every import io.mockk.just import io.mockk.mockkClass +import io.mockk.mockkConstructor import io.mockk.mockkObject import io.mockk.verify import org.junit.Assert.assertEquals -import org.junit.Assert.assertFalse import org.junit.Assert.assertNotNull +import org.junit.Assert.assertTrue import org.junit.Before import org.junit.Test import org.junit.runner.RunWith @@ -63,6 +67,7 @@ class ManualCarouselNotificationBuilderTest { mockkObject(PushTemplateImageUtils) mockkObject(BasicNotificationBuilder) mockkObject(PendingIntentUtils) + mockkConstructor(RemoteViews::class) expandedLayout = mockkClass(RemoteViews::class) } @@ -97,7 +102,7 @@ class ManualCarouselNotificationBuilderTest { } @Test - fun `construct returns AEPPushNotificationBuilder if download image count is greater than equal to 3`() { + fun `construct returns ManualCarouselNotificationBuilder if download image count is greater than equal to 3`() { every { cacheImages(any()) } answers { 3 } ManualCarouselNotificationBuilder.construct( context, @@ -116,34 +121,36 @@ class ManualCarouselNotificationBuilderTest { } @Test - fun `test downloadCarouselItems return non empty list for it retrieve the image bitmap from cache successfully`() { + fun `test downloadCarouselItems returns non empty list when image bitmap is present in the cache`() { every { getCachedImage(any()) } answers { mockkClass(Bitmap::class) } - val imagesList = - ManualCarouselNotificationBuilder.downloadCarouselItems(pushTemplate.carouselItems) - assertFalse(imagesList.isEmpty()) + val imagesList = ManualCarouselNotificationBuilder.downloadCarouselItems(pushTemplate.carouselItems) + assertTrue(imagesList == pushTemplate.carouselItems) } @Test fun `test getCarouselIndices with left click intent action`() { - pushTemplate.intentAction = PushTemplateConstants.IntentActions.MANUAL_CAROUSEL_LEFT_CLICKED + val mockBundle = MockCarousalTemplateDataProvider.getMockedBundleWithManualCarouselData() + val data = IntentData(mockBundle, PushTemplateConstants.IntentActions.MANUAL_CAROUSEL_LEFT_CLICKED) val imageUris = listOf("image1", "image2", "image3") - val result = getCarouselIndices(pushTemplate, imageUris) + val result = getCarouselIndices(CarouselPushTemplate(data) as ManualCarouselPushTemplate, imageUris) assertEquals(Triple(1, 2, 0), result) } @Test fun `test getCarouselIndices with filmstrip left click intent action`() { - pushTemplate.intentAction = PushTemplateConstants.IntentActions.FILMSTRIP_LEFT_CLICKED + val mockBundle = MockCarousalTemplateDataProvider.getMockedBundleWithManualCarouselData() + val data = IntentData(mockBundle, PushTemplateConstants.IntentActions.FILMSTRIP_LEFT_CLICKED) val imageUris = listOf("image1", "image2", "image3") - val result = getCarouselIndices(pushTemplate, imageUris) + val result = getCarouselIndices(CarouselPushTemplate(data) as ManualCarouselPushTemplate, imageUris) assertEquals(Triple(1, 2, 0), result) } @Test fun `test getCarouselIndices with no intent action and filmstrip layout`() { - pushTemplate.intentAction = PushTemplateConstants.DefaultValues.FILMSTRIP_CAROUSEL_MODE + val mockBundle = MockCarousalTemplateDataProvider.getMockedBundleWithManualCarouselData() + val data = IntentData(mockBundle, PushTemplateConstants.DefaultValues.FILMSTRIP_CAROUSEL_MODE) val imageUris = listOf("image1", "image2", "image3") - val result = getCarouselIndices(pushTemplate, imageUris) + val result = getCarouselIndices(CarouselPushTemplate(data) as ManualCarouselPushTemplate, imageUris) assertEquals( Triple( PushTemplateConstants.DefaultValues.FILMSTRIP_CAROUSEL_CENTER_INDEX - 1, @@ -154,14 +161,30 @@ class ManualCarouselNotificationBuilderTest { ) } + @Test + fun `test getCarouselIndices with no intent action and manual layout`() { + val imageUris = listOf("image1", "image2", "image3") + val result = getCarouselIndices(pushTemplate, imageUris) + assertEquals( + Triple( + imageUris.size - 1, + PushTemplateConstants.DefaultValues.MANUAL_CAROUSEL_START_INDEX, + PushTemplateConstants.DefaultValues.MANUAL_CAROUSEL_START_INDEX + 1 + ), + result + ) + } + @Test fun `test populateManualCarouselImages with valid images`() { val packageName = context.packageName val centerIndex = 1 every { getCachedImage(any()) } answers { mockkClass(Bitmap::class) } + every { anyConstructed().setTextViewText(any(), any()) } just Runs + every { anyConstructed().setImageViewBitmap(any(), any()) } just Runs + every { anyConstructed().setRemoteViewClickAction(context, trackerActivityClass, any(), any(), null, any()) } just Runs every { expandedLayout.addView(any(), any()) } just Runs every { expandedLayout.setDisplayedChild(any(), any()) } just Runs - populateManualCarouselImages( context, pushTemplate.carouselItems, @@ -171,17 +194,18 @@ class ManualCarouselNotificationBuilderTest { trackerActivityClass, expandedLayout ) - - verify { expandedLayout.addView(any(), any()) } - verify { expandedLayout.setDisplayedChild(any(), centerIndex) } + val carouselItemsCount = pushTemplate.carouselItems.size + verify(exactly = carouselItemsCount) { anyConstructed().setTextViewText(any(), any()) } + verify(exactly = carouselItemsCount) { anyConstructed().setImageViewBitmap(any(), any()) } + verify(exactly = carouselItemsCount) { anyConstructed().setRemoteViewClickAction(context, trackerActivityClass, any(), any(), null, any()) } + verify(exactly = carouselItemsCount) { expandedLayout.addView(any(), any()) } + verify(exactly = carouselItemsCount) { expandedLayout.setDisplayedChild(any(), centerIndex) } } @Test fun `test populateManualCarouselImages with null images`() { val packageName = context.packageName val centerIndex = 1 - every { getCachedImage(any()) } returns null - populateManualCarouselImages( context, pushTemplate.carouselItems, @@ -192,6 +216,9 @@ class ManualCarouselNotificationBuilderTest { expandedLayout ) + verify(exactly = 0) { anyConstructed().setTextViewText(any(), any()) } + verify(exactly = 0) { anyConstructed().setImageViewBitmap(any(), any()) } + verify(exactly = 0) { anyConstructed().setRemoteViewClickAction(context, trackerActivityClass, any(), any(), null, any()) } verify(exactly = 0) { expandedLayout.addView(any(), any()) } verify(exactly = 0) { expandedLayout.setDisplayedChild(any(), centerIndex) } } @@ -205,7 +232,7 @@ class ManualCarouselNotificationBuilderTest { every { getAssetCacheLocation() } answers { "assetCacheLocation" } every { getCachedImage(any()) } answers { mockBitmap } every { expandedLayout.setTextViewText(any(), "Caption 2") } returns Unit - every { expandedLayout.setImageViewBitmap(any(), mockBitmap) } returns Unit + every { expandedLayout.setImageViewBitmap(any(), any()) } just Runs every { expandedLayout.setRemoteViewClickAction( context, @@ -225,6 +252,7 @@ class ManualCarouselNotificationBuilderTest { trackerActivityClass, expandedLayout ) + verify(exactly = 3) { expandedLayout.setImageViewBitmap(any(), any()) } verify(exactly = 1) { expandedLayout.setRemoteViewClickAction( context, From ab942b13d679b12d722306e0ef8d13ccacd13f56 Mon Sep 17 00:00:00 2001 From: saquib-adobe Date: Wed, 12 Jun 2024 19:58:06 +0530 Subject: [PATCH 131/159] [MOB-20955] Tests updated as per the latest dev branch changes merged --- .../ManualCarouselNotificationBuilderTest.kt | 33 ++++++++----------- 1 file changed, 14 insertions(+), 19 deletions(-) diff --git a/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/ManualCarouselNotificationBuilderTest.kt b/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/ManualCarouselNotificationBuilderTest.kt index 727bc699..a1e9ef02 100644 --- a/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/ManualCarouselNotificationBuilderTest.kt +++ b/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/ManualCarouselNotificationBuilderTest.kt @@ -131,8 +131,8 @@ class ManualCarouselNotificationBuilderTest { fun `test getCarouselIndices with left click intent action`() { val mockBundle = MockCarousalTemplateDataProvider.getMockedBundleWithManualCarouselData() val data = IntentData(mockBundle, PushTemplateConstants.IntentActions.MANUAL_CAROUSEL_LEFT_CLICKED) - val imageUris = listOf("image1", "image2", "image3") - val result = getCarouselIndices(CarouselPushTemplate(data) as ManualCarouselPushTemplate, imageUris) + val mcPushTemplate = CarouselPushTemplate(data) as ManualCarouselPushTemplate + val result = getCarouselIndices(mcPushTemplate, 3) assertEquals(Triple(1, 2, 0), result) } @@ -140,8 +140,8 @@ class ManualCarouselNotificationBuilderTest { fun `test getCarouselIndices with filmstrip left click intent action`() { val mockBundle = MockCarousalTemplateDataProvider.getMockedBundleWithManualCarouselData() val data = IntentData(mockBundle, PushTemplateConstants.IntentActions.FILMSTRIP_LEFT_CLICKED) - val imageUris = listOf("image1", "image2", "image3") - val result = getCarouselIndices(CarouselPushTemplate(data) as ManualCarouselPushTemplate, imageUris) + val mcPushTemplate = CarouselPushTemplate(data) as ManualCarouselPushTemplate + val result = getCarouselIndices(mcPushTemplate, 3) assertEquals(Triple(1, 2, 0), result) } @@ -149,8 +149,8 @@ class ManualCarouselNotificationBuilderTest { fun `test getCarouselIndices with no intent action and filmstrip layout`() { val mockBundle = MockCarousalTemplateDataProvider.getMockedBundleWithManualCarouselData() val data = IntentData(mockBundle, PushTemplateConstants.DefaultValues.FILMSTRIP_CAROUSEL_MODE) - val imageUris = listOf("image1", "image2", "image3") - val result = getCarouselIndices(CarouselPushTemplate(data) as ManualCarouselPushTemplate, imageUris) + val mcPushTemplate = CarouselPushTemplate(data) as ManualCarouselPushTemplate + val result = getCarouselIndices(mcPushTemplate, 3) assertEquals( Triple( PushTemplateConstants.DefaultValues.FILMSTRIP_CAROUSEL_CENTER_INDEX - 1, @@ -163,11 +163,10 @@ class ManualCarouselNotificationBuilderTest { @Test fun `test getCarouselIndices with no intent action and manual layout`() { - val imageUris = listOf("image1", "image2", "image3") - val result = getCarouselIndices(pushTemplate, imageUris) + val result = getCarouselIndices(pushTemplate, pushTemplate.carouselItems.size) assertEquals( Triple( - imageUris.size - 1, + pushTemplate.carouselItems.size - 1, PushTemplateConstants.DefaultValues.MANUAL_CAROUSEL_START_INDEX, PushTemplateConstants.DefaultValues.MANUAL_CAROUSEL_START_INDEX + 1 ), @@ -226,12 +225,10 @@ class ManualCarouselNotificationBuilderTest { @Test fun `test populateFilmstripCarouselImages with valid images`() { val mockBitmap = mockkClass(Bitmap::class) - val imageCaptions = listOf("Caption 1", "Caption 2", "Caption 3") - val imageClickActions = listOf("Action 1", "Action 2", "Action 3") val newIndices = Triple(0, 1, 2) every { getAssetCacheLocation() } answers { "assetCacheLocation" } every { getCachedImage(any()) } answers { mockBitmap } - every { expandedLayout.setTextViewText(any(), "Caption 2") } returns Unit + every { expandedLayout.setTextViewText(any(), any()) } returns Unit every { expandedLayout.setImageViewBitmap(any(), any()) } just Runs every { expandedLayout.setRemoteViewClickAction( @@ -245,13 +242,14 @@ class ManualCarouselNotificationBuilderTest { } returns Unit populateFilmstripCarouselImages( context, - imageCaptions, - imageClickActions, + pushTemplate.carouselItems, newIndices, pushTemplate, trackerActivityClass, expandedLayout ) + + verify(exactly = 1) { expandedLayout.setTextViewText(any(), any()) } verify(exactly = 3) { expandedLayout.setImageViewBitmap(any(), any()) } verify(exactly = 1) { expandedLayout.setRemoteViewClickAction( @@ -268,12 +266,10 @@ class ManualCarouselNotificationBuilderTest { @Test fun `test populateFilmstripCarouselImages with null asset cached location`() { val mockBitmap = mockkClass(Bitmap::class) - val imageCaptions = listOf("Caption 1", "Caption 2", "Caption 3") - val imageClickActions = listOf("Action 1", "Action 2", "Action 3") val newIndices = Triple(0, 1, 2) every { getAssetCacheLocation() } answers { null } every { getCachedImage(any()) } answers { mockBitmap } - every { expandedLayout.setTextViewText(any(), "Caption 2") } returns Unit + every { expandedLayout.setTextViewText(any(), any()) } returns Unit every { expandedLayout.setImageViewBitmap(any(), mockBitmap) } returns Unit every { expandedLayout.setRemoteViewClickAction( @@ -287,8 +283,7 @@ class ManualCarouselNotificationBuilderTest { } returns Unit populateFilmstripCarouselImages( context, - imageCaptions, - imageClickActions, + pushTemplate.carouselItems, newIndices, pushTemplate, trackerActivityClass, From 036a4d979267d57e39eafa72e6474b21d6a6c0a7 Mon Sep 17 00:00:00 2001 From: Spoorthi Pujari <63024083+spoorthipujariadobe@users.noreply.github.com> Date: Tue, 18 Jun 2024 11:21:27 -0700 Subject: [PATCH 132/159] move remind later handling to different public class (#40) --- .../NotificationBuilder.kt | 68 -------------- .../notificationbuilder/RemindLaterHandler.kt | 92 +++++++++++++++++++ 2 files changed, 92 insertions(+), 68 deletions(-) create mode 100644 code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/RemindLaterHandler.kt diff --git a/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/NotificationBuilder.kt b/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/NotificationBuilder.kt index d6c0799f..ba64f9a5 100644 --- a/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/NotificationBuilder.kt +++ b/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/NotificationBuilder.kt @@ -12,15 +12,12 @@ package com.adobe.marketing.mobile.notificationbuilder import android.app.Activity -import android.app.PendingIntent import android.content.BroadcastReceiver import android.content.Context import android.content.Intent import androidx.core.app.NotificationCompat -import androidx.core.app.NotificationManagerCompat import com.adobe.marketing.mobile.notificationbuilder.NotificationBuilder.constructNotificationBuilder import com.adobe.marketing.mobile.notificationbuilder.PushTemplateConstants.LOG_TAG -import com.adobe.marketing.mobile.notificationbuilder.internal.PendingIntentUtils import com.adobe.marketing.mobile.notificationbuilder.internal.PushTemplateType import com.adobe.marketing.mobile.notificationbuilder.internal.builders.AutoCarouselNotificationBuilder import com.adobe.marketing.mobile.notificationbuilder.internal.builders.BasicNotificationBuilder @@ -48,7 +45,6 @@ import com.adobe.marketing.mobile.notificationbuilder.internal.util.MapData import com.adobe.marketing.mobile.notificationbuilder.internal.util.NotificationData import com.adobe.marketing.mobile.services.Log import com.adobe.marketing.mobile.services.ServiceProvider -import com.adobe.marketing.mobile.util.TimeUtils /** * Public facing object to construct a [NotificationCompat.Builder] object for the specified [PushTemplateType]. @@ -114,70 +110,6 @@ object NotificationBuilder { return createNotificationBuilder(context, intentData, trackerActivityClass, broadcastReceiverClass) } - /** - * Handles the remind later intent by scheduling a [PendingIntent] to the [broadcastReceiverClass] - * which will be fired at a time specified in the [remindLaterIntent]. - * - * Once the PendingIntent is fired, the [broadcastReceiverClass] is responsible for - * reconstructing the notification and displaying it. - * - * @param remindLaterIntent [Intent] containing the data needed to schedule and recreate the notification - * @param broadcastReceiverClass [Class] of the [BroadcastReceiver] that will be fired when the [PendingIntent] resolves at a later time - */ - @Throws(NotificationConstructionFailedException::class, IllegalArgumentException::class) - @JvmStatic - fun handleRemindIntent( - remindLaterIntent: Intent, - broadcastReceiverClass: Class? - ) { - val context = ServiceProvider.getInstance().appContextService.applicationContext - ?: throw NotificationConstructionFailedException("Application context is null, cannot schedule notification for later.") - - // get the time for remind later from the intent extras - val intentExtras = remindLaterIntent.extras - ?: throw NotificationConstructionFailedException("Intent extras are null, cannot schedule notification for later.") - val remindLaterTimestamp = - intentExtras.getString(PushTemplateConstants.PushPayloadKeys.REMIND_LATER_TIMESTAMP)?.toLongOrNull() ?: 0 - val remindLaterDuration = - intentExtras.getString(PushTemplateConstants.PushPayloadKeys.REMIND_LATER_DURATION)?.toLongOrNull() ?: 0 - - // calculate difference in fire date from the current date if timestamp is provided - val secondsUntilFireDate: Long = if (remindLaterDuration > 0) remindLaterDuration - else remindLaterTimestamp - TimeUtils.getUnixTimeInSeconds() - - val notificationManager = NotificationManagerCompat.from(context) - val tag = intentExtras.getString(PushTemplateConstants.PushPayloadKeys.TAG) - - // if fire date is greater than 0 then we want to schedule a reminder notification. - if (secondsUntilFireDate <= 0) { - tag?.let { notificationManager.cancel(tag.hashCode()) } - throw IllegalArgumentException("Remind later timestamp or duration is less than or equal to current timestamp, cannot schedule notification for later.") - } - Log.trace(LOG_TAG, SELF_TAG, "Remind later pressed, will reschedule the notification to be displayed $secondsUntilFireDate seconds from now") - - // calculate the trigger time - val triggerTimeInSeconds: Long = if (remindLaterDuration > 0) remindLaterDuration + TimeUtils.getUnixTimeInSeconds() - else remindLaterTimestamp - - // schedule a pending intent to be broadcast at the specified timestamp - if (broadcastReceiverClass == null) { - Log.warning( - LOG_TAG, - SELF_TAG, - "Broadcast receiver class is null, cannot schedule notification for later." - ) - tag?.let { notificationManager.cancel(tag.hashCode()) } - return - } - val scheduledIntent = Intent(PushTemplateConstants.IntentActions.SCHEDULED_NOTIFICATION_BROADCAST) - scheduledIntent.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP) - scheduledIntent.putExtras(intentExtras) - PendingIntentUtils.scheduleNotification(context, scheduledIntent, broadcastReceiverClass, triggerTimeInSeconds) - - // cancel the displayed notification - tag?.let { notificationManager.cancel(tag.hashCode()) } - } - private fun createNotificationBuilder( context: Context, notificationData: NotificationData, diff --git a/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/RemindLaterHandler.kt b/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/RemindLaterHandler.kt new file mode 100644 index 00000000..a82fd8a8 --- /dev/null +++ b/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/RemindLaterHandler.kt @@ -0,0 +1,92 @@ +/* + Copyright 2024 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.notificationbuilder + +import android.app.PendingIntent +import android.content.BroadcastReceiver +import android.content.Intent +import androidx.core.app.NotificationManagerCompat +import com.adobe.marketing.mobile.notificationbuilder.internal.PendingIntentUtils +import com.adobe.marketing.mobile.services.Log +import com.adobe.marketing.mobile.services.ServiceProvider +import com.adobe.marketing.mobile.util.TimeUtils + +/** + * Public facing object to handle the remind later intent. + */ +object RemindLaterHandler { + private const val SELF_TAG = "RemindLaterHandler" + + /** + * Handles the remind later intent by scheduling a [PendingIntent] to the [broadcastReceiverClass] + * which will be fired at a time specified in the [remindLaterIntent]. + * + * Once the PendingIntent is fired, the [broadcastReceiverClass] is responsible for + * reconstructing the notification and displaying it. + * + * @param remindLaterIntent [Intent] containing the data needed to schedule and recreate the notification + * @param broadcastReceiverClass [Class] of the [BroadcastReceiver] that will be fired when the [PendingIntent] resolves at a later time + */ + @Throws(NotificationConstructionFailedException::class, IllegalArgumentException::class) + @JvmStatic + fun handleRemindIntent( + remindLaterIntent: Intent, + broadcastReceiverClass: Class? + ) { + val context = ServiceProvider.getInstance().appContextService.applicationContext + ?: throw NotificationConstructionFailedException("Application context is null, cannot schedule notification for later.") + + // get the time for remind later from the intent extras + val intentExtras = remindLaterIntent.extras + ?: throw NotificationConstructionFailedException("Intent extras are null, cannot schedule notification for later.") + val remindLaterTimestamp = + intentExtras.getString(PushTemplateConstants.PushPayloadKeys.REMIND_LATER_TIMESTAMP)?.toLongOrNull() ?: 0 + val remindLaterDuration = + intentExtras.getString(PushTemplateConstants.PushPayloadKeys.REMIND_LATER_DURATION)?.toLongOrNull() ?: 0 + + // calculate difference in fire date from the current date if timestamp is provided + val secondsUntilFireDate: Long = if (remindLaterDuration > 0) remindLaterDuration + else remindLaterTimestamp - TimeUtils.getUnixTimeInSeconds() + + val notificationManager = NotificationManagerCompat.from(context) + val tag = intentExtras.getString(PushTemplateConstants.PushPayloadKeys.TAG) + + // if fire date is greater than 0 then we want to schedule a reminder notification. + if (secondsUntilFireDate <= 0) { + tag?.let { notificationManager.cancel(tag.hashCode()) } + throw IllegalArgumentException("Remind later timestamp or duration is less than or equal to current timestamp, cannot schedule notification for later.") + } + Log.trace(PushTemplateConstants.LOG_TAG, SELF_TAG, "Remind later pressed, will reschedule the notification to be displayed $secondsUntilFireDate seconds from now") + + // calculate the trigger time + val triggerTimeInSeconds: Long = if (remindLaterDuration > 0) remindLaterDuration + TimeUtils.getUnixTimeInSeconds() + else remindLaterTimestamp + + // schedule a pending intent to be broadcast at the specified timestamp + if (broadcastReceiverClass == null) { + Log.warning( + PushTemplateConstants.LOG_TAG, + SELF_TAG, + "Broadcast receiver class is null, cannot schedule notification for later." + ) + tag?.let { notificationManager.cancel(tag.hashCode()) } + return + } + val scheduledIntent = Intent(PushTemplateConstants.IntentActions.SCHEDULED_NOTIFICATION_BROADCAST) + scheduledIntent.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP) + scheduledIntent.putExtras(intentExtras) + PendingIntentUtils.scheduleNotification(context, scheduledIntent, broadcastReceiverClass, triggerTimeInSeconds) + + // cancel the displayed notification + tag?.let { notificationManager.cancel(tag.hashCode()) } + } +} From 85dd6f5368c5e52fdbe032f931f516626ae19ff0 Mon Sep 17 00:00:00 2001 From: Ryan Morales Date: Tue, 18 Jun 2024 16:58:34 -0700 Subject: [PATCH 133/159] add TimerNotificationBuilderTests unit tests --- .../builders/TimerNotificationBuilderTests.kt | 226 ++++++++++++++++++ .../internal/templates/MockDataUtils.kt | 67 ++++-- .../MockTimerTemplateDataProvider.kt | 111 +++++++++ 3 files changed, 383 insertions(+), 21 deletions(-) create mode 100644 code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/TimerNotificationBuilderTests.kt create mode 100644 code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/templates/MockTimerTemplateDataProvider.kt diff --git a/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/TimerNotificationBuilderTests.kt b/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/TimerNotificationBuilderTests.kt new file mode 100644 index 00000000..5b561de7 --- /dev/null +++ b/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/TimerNotificationBuilderTests.kt @@ -0,0 +1,226 @@ +/* + Copyright 2024 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.notificationbuilder.internal.builders + +import android.app.Activity +import android.app.AlarmManager +import android.app.NotificationManager +import android.content.BroadcastReceiver +import android.content.Context +import android.os.Build +import android.service.notification.StatusBarNotification +import android.widget.RemoteViews +import com.adobe.marketing.mobile.notificationbuilder.NotificationConstructionFailedException +import com.adobe.marketing.mobile.notificationbuilder.internal.PendingIntentUtils +import com.adobe.marketing.mobile.notificationbuilder.internal.PushTemplateImageUtils +import com.adobe.marketing.mobile.notificationbuilder.internal.templates.MOCKED_ALT_BODY +import com.adobe.marketing.mobile.notificationbuilder.internal.templates.MOCKED_ALT_EXPANDED_BODY +import com.adobe.marketing.mobile.notificationbuilder.internal.templates.MOCKED_ALT_TITLE +import com.adobe.marketing.mobile.notificationbuilder.internal.templates.TimerPushTemplate +import com.adobe.marketing.mobile.notificationbuilder.internal.templates.provideMockedTimerTemplate +import io.mockk.Runs +import io.mockk.every +import io.mockk.just +import io.mockk.mockk +import io.mockk.mockkClass +import io.mockk.mockkConstructor +import io.mockk.mockkObject +import io.mockk.verify +import org.junit.Assert.assertNotNull +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.robolectric.RobolectricTestRunner +import org.robolectric.annotation.Config +import org.robolectric.util.ReflectionHelpers +import kotlin.test.assertNull + + +@RunWith(RobolectricTestRunner::class) +@Config(sdk = [31]) +class TimerNotificationBuilderTests { + + private lateinit var context: Context + private lateinit var pushTemplate: TimerPushTemplate + private lateinit var trackerActivityClass: Class + private lateinit var broadcastReceiverClass: Class + private lateinit var alarmManager: AlarmManager + private lateinit var notificationManager: NotificationManager + + @Before + fun setUp() { + context = mockk(relaxed = true) + alarmManager = mockk(relaxed = true) + notificationManager = mockk(relaxed = true) + every { alarmManager.canScheduleExactAlarms() } returns true + every { context.getSystemService(Context.NOTIFICATION_SERVICE) } returns notificationManager + every { context.getSystemService(Context.ALARM_SERVICE) } returns alarmManager + every { context.packageName } answers { callOriginal() } + pushTemplate = provideMockedTimerTemplate(false, true) + trackerActivityClass = mockkClass(Activity::class, relaxed = true).javaClass + broadcastReceiverClass = mockkClass(BroadcastReceiver::class, relaxed = true).javaClass + mockkObject(PushTemplateImageUtils) + mockkObject(PendingIntentUtils) + mockkConstructor(RemoteViews::class) + every { anyConstructed().setTextViewText(any(), any()) } just Runs + every { anyConstructed().setImageViewBitmap(any(), any()) } just Runs + } + + @Test(expected = NotificationConstructionFailedException::class) + fun `construct throws exception when schedule exact alarm permission false`() { + every { alarmManager.canScheduleExactAlarms() } returns false + val result = TimerNotificationBuilder.construct( + context, + pushTemplate, + trackerActivityClass, + broadcastReceiverClass + ) + assertNull(result) + } + + @Test(expected = NotificationConstructionFailedException::class) + fun `construct throws exception when API is less than 24`() { + ReflectionHelpers.setStaticField(Build.VERSION::class.java, "SDK_INT", 23) + val result = TimerNotificationBuilder.construct( + context, + pushTemplate, + trackerActivityClass, + broadcastReceiverClass + ) + assertNull(result) + } + + @Test + fun `construct returns NotificationCompat Builder for valid inputs`() { + val result = TimerNotificationBuilder.construct( + context, + pushTemplate, + trackerActivityClass, + broadcastReceiverClass + ) + assertNotNull(result) + } + + @Test + fun `construct returns NotificationCompat Builder when using Intent for TimerTemplate`() { + pushTemplate = provideMockedTimerTemplate(true, false) + val result = TimerNotificationBuilder.construct( + context, + pushTemplate, + trackerActivityClass, + broadcastReceiverClass + ) + assertNotNull(result) + } + + @Test + fun `construct returns NotificationCompat Builder containing alternate message content when timer is expired`() { + // using a negative duration to guarantee that the timer is expired + pushTemplate = provideMockedTimerTemplate(false, true, "-10") + val notification: StatusBarNotification = mockkClass(StatusBarNotification::class) + every { notification.id } returns pushTemplate.tag.hashCode() + val notifications = arrayOf(notification) + every { notificationManager.activeNotifications } returns notifications + val result = TimerNotificationBuilder.construct( + context, + pushTemplate, + trackerActivityClass, + broadcastReceiverClass + ) + assertNotNull(result) + verify(exactly = 2) { + anyConstructed().setTextViewText( + any(), + MOCKED_ALT_TITLE + ) + } + verify(exactly = 1) { + anyConstructed().setTextViewText( + any(), + MOCKED_ALT_BODY + ) + } + verify(exactly = 1) { + anyConstructed().setTextViewText( + any(), + MOCKED_ALT_EXPANDED_BODY + ) + } + } + + @Test(expected = NotificationConstructionFailedException::class) + fun `construct throws exception when timer is expired but notification with a matching tag is not found`() { + // using a negative duration to guarantee that the timer is expired + pushTemplate = provideMockedTimerTemplate(false, true, "-10") + val notification: StatusBarNotification = mockkClass(StatusBarNotification::class) + every { notification.id } returns -1111 + val notifications = arrayOf(notification) + every { notificationManager.activeNotifications } returns notifications + val result = TimerNotificationBuilder.construct( + context, + pushTemplate, + trackerActivityClass, + broadcastReceiverClass + ) + assertNull(result) + } + + @Test(expected = NotificationConstructionFailedException::class) + fun `construct throws exception when timer is expired but no notification is currently displayed`() { + // using a negative duration to guarantee that the timer is expired + pushTemplate = provideMockedTimerTemplate(false, true, "-10") + val notifications = emptyArray() + every { notificationManager.activeNotifications } returns notifications + val result = TimerNotificationBuilder.construct( + context, + pushTemplate, + trackerActivityClass, + broadcastReceiverClass + ) + assertNull(result) + } + + @Test + fun `construct returns NotificationCompat Builder and alarm is set if duration present in TimerTemplate`() { + val result = TimerNotificationBuilder.construct( + context, + pushTemplate, + trackerActivityClass, + broadcastReceiverClass + ) + assertNotNull(result) + val expectedTime = + System.currentTimeMillis() + (60 * 1000) // current time + 60 seconds in milliseconds + verify(exactly = 1) { + alarmManager.setExactAndAllowWhileIdle( + any(), + range(System.currentTimeMillis(), expectedTime, true, true), + any() + ) + } + } + + @Test + fun `construct returns NotificationCompat Builder and alarm is set using future timestamp if no duration in TimerTemplate`() { + pushTemplate = provideMockedTimerTemplate(true, false) + val result = TimerNotificationBuilder.construct( + context, + pushTemplate, + trackerActivityClass, + broadcastReceiverClass + ) + assertNotNull(result) + val expectedTime = + 2665428926 * 1000 // Thursday, June 18, 2054 8:55:26 PM GMT in milliseconds + verify(exactly = 1) { alarmManager.setExactAndAllowWhileIdle(any(), expectedTime, any()) } + } +} diff --git a/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/templates/MockDataUtils.kt b/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/templates/MockDataUtils.kt index 21a45ac7..8a62b631 100644 --- a/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/templates/MockDataUtils.kt +++ b/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/templates/MockDataUtils.kt @@ -16,13 +16,19 @@ import com.adobe.marketing.mobile.notificationbuilder.internal.util.MapData import com.adobe.marketing.mobile.notificationbuilder.internal.util.NotificationData const val MOCKED_TITLE = "Mocked Title" +const val MOCKED_ALT_TITLE = "Mocked Alternate Title" const val MOCKED_BODY = "Mocked Body" +const val MOCKED_ALT_BODY = "Mocked Alternate Body" +const val MOCKED_ALT_EXPANDED_BODY = "Mocked Alternate Expanded Body" const val MOCKED_PAYLOAD_VERSION = "1" const val MOCKED_CAROUSEL_LAYOUT = "default" const val MOCKED_BODY_TEXT_COLOR = "#FFFFFF" const val MOCKED_SMALL_ICON = "skipleft" const val MOCKED_LARGE_ICON = "https://cdn-icons-png.flaticon.com/128/864/864639.png" const val MOCKED_SMALL_ICON_COLOR = "#000000" +const val MOCKED_TIMER_COLOR = "#FFFF00" +const val MOCKED_TIMER_DURATION = "60" +const val MOCKED_TIMER_EXPIRY_TIME = "2665428926" // Thursday, June 18, 2054 8:55:26 PM GMT const val MOCKED_VISIBILITY = "PUBLIC" const val MOCKED_PRIORITY = "PRIORITY_HIGH" const val MOCKED_TICKER = "ticker" @@ -32,6 +38,8 @@ const val MOCKED_CAROUSEL_LAYOUT_DATA = "[{\"img\":\"https://i.imgur.com/7ZolaOv.jpeg\",\"txt\":\"Basketball Shoes\"},{\"img\":\"https://i.imgur.com/mZvLuzU.jpg\",\"txt\":\"Red Jersey\",\"uri\":\"https://firefly.adobe.com/red_jersey\"},{\"img\":\"https://i.imgur.com/X5yjy09.jpg\",\"txt\":\"Volleyball\", \"uri\":\"https://firefly.adobe.com/volleyball\"},{\"img\":\"https://i.imgur.com/35B0mkh.jpg\",\"txt\":\"Basketball\",\"uri\":\"https://firefly.adobe.com/basketball\"},{\"img\":\"https://i.imgur.com/Cs5hmfb.jpg\",\"txt\":\"Black Batting Helmet\",\"uri\":\"https://firefly.adobe.com/black_helmet\"}]" const val MOCKED_IMAGE_URI = "https://images.pexels.com/photos/260024/pexels-photo-260024.jpeg?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=2" +const val MOCKED_ALT_IMAGE_URI = + "https://images2.pexels.com/photos/260024/pexels-photo-260024.jpeg?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=2" const val MOCKED_ACTION_URI = "https://chess.com/games" const val MOCKED_BASIC_TEMPLATE_BODY_EXPANDED = "Basic push template with action buttons." const val MOCKED_ACTION_BUTTON_DATA = @@ -41,32 +49,32 @@ const val MOCK_REMIND_LATER_TEXT = "remind me" const val MOCK_REMIND_LATER_TIME = "1234567890" const val MOCK_REMIND_LATER_DURATION = "6000" const val MOCKED_MALFORMED_JSON_ACTION_BUTTON = "[" + - "{\"label\":\"\",\"uri\":\"https://chess.com/games/552\",\"type\":\"DEEPLINK\"}," + - "{}," + - "{\"label\":\"Open the app\",\"uri\":\"\",\"type\":\"GO_TO_WEB_PAGE\"}," + - "{\"label\":\"Go to chess.com\",\"uri\":\"https://chess.com/games/552\",\"type\":\"DEEPLINK\"}]" + "{\"label\":\"\",\"uri\":\"https://chess.com/games/552\",\"type\":\"DEEPLINK\"}," + + "{}," + + "{\"label\":\"Open the app\",\"uri\":\"\",\"type\":\"GO_TO_WEB_PAGE\"}," + + "{\"label\":\"Go to chess.com\",\"uri\":\"https://chess.com/games/552\",\"type\":\"DEEPLINK\"}]" const val MOCKED_CHANNEL_ID = "AEPSDKPushChannel1" const val MOCK_MULTI_ICON_ITEM_PAYLOAD = "[" + - "{\"img\":\"train\",\"uri\":\"myapp://chooseShoeType/shoe1\",\"type\":\"DEEPLINK\"}," + - "{\"img\":\"bus\",\"uri\":\"myapp://chooseShoeType/shoe2\",\"type\":\"DEEPLINK\"}," + - "{\"img\":\"car\",\"uri\":\"myapp://chooseShoeType/shoe3\",\"type\":\"DEEPLINK\"}," + - "{\"img\":\"tempo\",\"uri\":\"myapp://chooseShoeType/shoe4\",\"type\":\"DEEPLINK\"}," + - "{\"img\":\"airplane\",\"uri\":\"myapp://chooseShoeType/shoe5\",\"type\":\"DEEPLINK\"}" + - "]" + "{\"img\":\"train\",\"uri\":\"myapp://chooseShoeType/shoe1\",\"type\":\"DEEPLINK\"}," + + "{\"img\":\"bus\",\"uri\":\"myapp://chooseShoeType/shoe2\",\"type\":\"DEEPLINK\"}," + + "{\"img\":\"car\",\"uri\":\"myapp://chooseShoeType/shoe3\",\"type\":\"DEEPLINK\"}," + + "{\"img\":\"tempo\",\"uri\":\"myapp://chooseShoeType/shoe4\",\"type\":\"DEEPLINK\"}," + + "{\"img\":\"airplane\",\"uri\":\"myapp://chooseShoeType/shoe5\",\"type\":\"DEEPLINK\"}" + + "]" const val MOCK_MULTI_ICON_ITEM_PAYLOAD_INVALID_IMAGE = "[" + - "{\"img\":\"\",\"uri\":\"myapp://chooseShoeType/shoe1\",\"type\":\"DEEPLINK\"}," + - "{\"img\":\"bus\",\"uri\":\"myapp://chooseShoeType/shoe2\",\"type\":\"\"}," + - "{\"img\":\"\",\"uri\":\"myapp://chooseShoeType/shoe3\",\"type\":\"DEEPLINK\"}," + - "{\"img\":\"tempo\",\"uri\":\"myapp://chooseShoeType/shoe4\",\"type\":\"DEEPLINK\"}," + - "{\"img\":\"airplane\",\"uri\":\"myapp://chooseShoeType/shoe5\",\"type\":\"DEEPLINK\"}" + - "]" + "{\"img\":\"\",\"uri\":\"myapp://chooseShoeType/shoe1\",\"type\":\"DEEPLINK\"}," + + "{\"img\":\"bus\",\"uri\":\"myapp://chooseShoeType/shoe2\",\"type\":\"\"}," + + "{\"img\":\"\",\"uri\":\"myapp://chooseShoeType/shoe3\",\"type\":\"DEEPLINK\"}," + + "{\"img\":\"tempo\",\"uri\":\"myapp://chooseShoeType/shoe4\",\"type\":\"DEEPLINK\"}," + + "{\"img\":\"airplane\",\"uri\":\"myapp://chooseShoeType/shoe5\",\"type\":\"DEEPLINK\"}" + + "]" const val MOCK_MULTI_ICON_ITEM_PAYLOAD_INCOMPLETE_JSON = "[" + - "{\"img\":\"train\",\"uri\":\"\",\"type\":\"DEEPLINK\"}," + - "{\"img\":\"bus\",\"uri\":\"myapp://chooseShoeType/shoe2\",\"type\":\"DEEPLINK\"}," + - "{\"img\":\"tempo\",\"uri\":\"myapp://chooseShoeType/shoe4\",\"type\":\"DEEPLINK\"}," + - "{\"img\":\"airplane\",\"uri\":\"myapp://chooseShoeType/shoe5\",\"type\":\"DEEPLINK\"}" + - "]" + "{\"img\":\"train\",\"uri\":\"\",\"type\":\"DEEPLINK\"}," + + "{\"img\":\"bus\",\"uri\":\"myapp://chooseShoeType/shoe2\",\"type\":\"DEEPLINK\"}," + + "{\"img\":\"tempo\",\"uri\":\"myapp://chooseShoeType/shoe4\",\"type\":\"DEEPLINK\"}," + + "{\"img\":\"airplane\",\"uri\":\"myapp://chooseShoeType/shoe5\",\"type\":\"DEEPLINK\"}" + + "]" fun MutableMap.removeKeysFromMap(listOfKeys: List) { for (key in listOfKeys) { @@ -150,3 +158,20 @@ internal fun provideMockedMultiIconTemplateWithAllKeys(): MultiIconPushTemplate data = MapData(dataMap) return MultiIconPushTemplate(data) } + +internal fun provideMockedTimerTemplate( + isFromIntent: Boolean = false, + isUsingDuration: Boolean = false, + duration: String = MOCKED_TIMER_DURATION +): TimerPushTemplate { + val data: NotificationData = if (isFromIntent) { + val mockBundle = + MockTimerTemplateDataProvider.getMockedBundleWithTimerData(isUsingDuration, duration) + IntentData(mockBundle, null) + } else { + val dataMap = + MockTimerTemplateDataProvider.getMockedMapWithTimerData(isUsingDuration, duration) + MapData(dataMap) + } + return TimerPushTemplate(data) +} diff --git a/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/templates/MockTimerTemplateDataProvider.kt b/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/templates/MockTimerTemplateDataProvider.kt new file mode 100644 index 00000000..3ba26820 --- /dev/null +++ b/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/templates/MockTimerTemplateDataProvider.kt @@ -0,0 +1,111 @@ +/* + Copyright 2024 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.notificationbuilder.internal.templates + +import android.os.Bundle +import com.adobe.marketing.mobile.notificationbuilder.PushTemplateConstants +import com.adobe.marketing.mobile.notificationbuilder.internal.PushTemplateType + +object MockTimerTemplateDataProvider { + internal fun getMockedMapWithTimerData( + isUsingDuration: Boolean, + duration: String + ): MutableMap { + val map = mutableMapOf( + PushTemplateConstants.PushPayloadKeys.TITLE to MOCKED_TITLE, + PushTemplateConstants.PushPayloadKeys.BODY to MOCKED_BODY, + PushTemplateConstants.PushPayloadKeys.VERSION to MOCKED_PAYLOAD_VERSION, + PushTemplateConstants.PushPayloadKeys.TEMPLATE_TYPE to PushTemplateType.TIMER.value, + PushTemplateConstants.PushPayloadKeys.BODY_TEXT_COLOR to MOCKED_BODY_TEXT_COLOR, + PushTemplateConstants.PushPayloadKeys.SMALL_ICON to MOCKED_SMALL_ICON, + PushTemplateConstants.PushPayloadKeys.LARGE_ICON to MOCKED_LARGE_ICON, + PushTemplateConstants.PushPayloadKeys.SMALL_ICON_COLOR to MOCKED_SMALL_ICON_COLOR, + PushTemplateConstants.PushPayloadKeys.VISIBILITY to MOCKED_VISIBILITY, + PushTemplateConstants.PushPayloadKeys.PRIORITY to MOCKED_PRIORITY, + PushTemplateConstants.PushPayloadKeys.TICKER to MOCKED_TICKER, + PushTemplateConstants.PushPayloadKeys.STICKY to "true", + PushTemplateConstants.PushPayloadKeys.TAG to MOCKED_TAG, + PushTemplateConstants.PushPayloadKeys.ACTION_URI to MOCKED_URI, + PushTemplateConstants.PushPayloadKeys.TimerKeys.ALTERNATE_EXPANDED_BODY to MOCKED_ALT_EXPANDED_BODY, + PushTemplateConstants.PushPayloadKeys.TimerKeys.ALTERNATE_TITLE to MOCKED_ALT_TITLE, + PushTemplateConstants.PushPayloadKeys.TimerKeys.ALTERNATE_BODY to MOCKED_ALT_BODY, + PushTemplateConstants.PushPayloadKeys.TimerKeys.ALTERNATE_IMAGE to MOCKED_ALT_IMAGE_URI, + PushTemplateConstants.PushPayloadKeys.TimerKeys.TIMER_COLOR to MOCKED_TIMER_COLOR, + ) + if (isUsingDuration) { + map[PushTemplateConstants.PushPayloadKeys.TimerKeys.TIMER_DURATION] = duration + } else { + map[PushTemplateConstants.PushPayloadKeys.TimerKeys.TIMER_END_TIME] = + MOCKED_TIMER_EXPIRY_TIME + } + return map + } + + internal fun getMockedBundleWithTimerData(isUsingDuration: Boolean, duration: String): Bundle { + val mockBundle = Bundle() + mockBundle.putString(PushTemplateConstants.PushPayloadKeys.TITLE, MOCKED_TITLE) + mockBundle.putString(PushTemplateConstants.PushPayloadKeys.BODY, MOCKED_BODY) + mockBundle.putString(PushTemplateConstants.PushPayloadKeys.VERSION, MOCKED_PAYLOAD_VERSION) + mockBundle.putString( + PushTemplateConstants.PushPayloadKeys.TEMPLATE_TYPE, + PushTemplateType.TIMER.value + ) + mockBundle.putString( + PushTemplateConstants.PushPayloadKeys.BODY_TEXT_COLOR, + MOCKED_BODY_TEXT_COLOR + ) + mockBundle.putString(PushTemplateConstants.PushPayloadKeys.SMALL_ICON, MOCKED_SMALL_ICON) + mockBundle.putString(PushTemplateConstants.PushPayloadKeys.LARGE_ICON, MOCKED_LARGE_ICON) + mockBundle.putString( + PushTemplateConstants.PushPayloadKeys.SMALL_ICON_COLOR, + MOCKED_SMALL_ICON_COLOR + ) + mockBundle.putString(PushTemplateConstants.PushPayloadKeys.VISIBILITY, MOCKED_VISIBILITY) + mockBundle.putString(PushTemplateConstants.PushPayloadKeys.PRIORITY, MOCKED_PRIORITY) + mockBundle.putString(PushTemplateConstants.PushPayloadKeys.TICKER, MOCKED_TICKER) + mockBundle.putString(PushTemplateConstants.PushPayloadKeys.TAG, MOCKED_TAG) + mockBundle.putString(PushTemplateConstants.PushPayloadKeys.STICKY, "true") + mockBundle.putString(PushTemplateConstants.PushPayloadKeys.ACTION_URI, MOCKED_URI) + mockBundle.putString( + PushTemplateConstants.PushPayloadKeys.TimerKeys.ALTERNATE_EXPANDED_BODY, + MOCKED_ALT_EXPANDED_BODY + ) + mockBundle.putString( + PushTemplateConstants.PushPayloadKeys.TimerKeys.ALTERNATE_TITLE, + MOCKED_ALT_TITLE + ) + mockBundle.putString( + PushTemplateConstants.PushPayloadKeys.TimerKeys.ALTERNATE_BODY, + MOCKED_ALT_BODY + ) + mockBundle.putString( + PushTemplateConstants.PushPayloadKeys.TimerKeys.ALTERNATE_IMAGE, + MOCKED_ALT_IMAGE_URI + ) + mockBundle.putString( + PushTemplateConstants.PushPayloadKeys.TimerKeys.TIMER_COLOR, + MOCKED_TIMER_COLOR + ) + if (isUsingDuration) { + mockBundle.putString( + PushTemplateConstants.PushPayloadKeys.TimerKeys.TIMER_DURATION, + duration + ) + } else { + mockBundle.putString( + PushTemplateConstants.PushPayloadKeys.TimerKeys.TIMER_END_TIME, + MOCKED_TIMER_EXPIRY_TIME + ) + } + return mockBundle + } +} From 4a86989823273a135db691d4bd797914d203e310 Mon Sep 17 00:00:00 2001 From: Ryan Morales Date: Tue, 18 Jun 2024 17:02:05 -0700 Subject: [PATCH 134/159] fix lint error: formatting --- .../builders/TimerNotificationBuilderTests.kt | 1 - .../internal/templates/MockDataUtils.kt | 42 +++++++++---------- 2 files changed, 21 insertions(+), 22 deletions(-) diff --git a/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/TimerNotificationBuilderTests.kt b/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/TimerNotificationBuilderTests.kt index 5b561de7..c12bff93 100644 --- a/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/TimerNotificationBuilderTests.kt +++ b/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/TimerNotificationBuilderTests.kt @@ -44,7 +44,6 @@ import org.robolectric.annotation.Config import org.robolectric.util.ReflectionHelpers import kotlin.test.assertNull - @RunWith(RobolectricTestRunner::class) @Config(sdk = [31]) class TimerNotificationBuilderTests { diff --git a/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/templates/MockDataUtils.kt b/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/templates/MockDataUtils.kt index 8a62b631..df66c05f 100644 --- a/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/templates/MockDataUtils.kt +++ b/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/templates/MockDataUtils.kt @@ -49,32 +49,32 @@ const val MOCK_REMIND_LATER_TEXT = "remind me" const val MOCK_REMIND_LATER_TIME = "1234567890" const val MOCK_REMIND_LATER_DURATION = "6000" const val MOCKED_MALFORMED_JSON_ACTION_BUTTON = "[" + - "{\"label\":\"\",\"uri\":\"https://chess.com/games/552\",\"type\":\"DEEPLINK\"}," + - "{}," + - "{\"label\":\"Open the app\",\"uri\":\"\",\"type\":\"GO_TO_WEB_PAGE\"}," + - "{\"label\":\"Go to chess.com\",\"uri\":\"https://chess.com/games/552\",\"type\":\"DEEPLINK\"}]" + "{\"label\":\"\",\"uri\":\"https://chess.com/games/552\",\"type\":\"DEEPLINK\"}," + + "{}," + + "{\"label\":\"Open the app\",\"uri\":\"\",\"type\":\"GO_TO_WEB_PAGE\"}," + + "{\"label\":\"Go to chess.com\",\"uri\":\"https://chess.com/games/552\",\"type\":\"DEEPLINK\"}]" const val MOCKED_CHANNEL_ID = "AEPSDKPushChannel1" const val MOCK_MULTI_ICON_ITEM_PAYLOAD = "[" + - "{\"img\":\"train\",\"uri\":\"myapp://chooseShoeType/shoe1\",\"type\":\"DEEPLINK\"}," + - "{\"img\":\"bus\",\"uri\":\"myapp://chooseShoeType/shoe2\",\"type\":\"DEEPLINK\"}," + - "{\"img\":\"car\",\"uri\":\"myapp://chooseShoeType/shoe3\",\"type\":\"DEEPLINK\"}," + - "{\"img\":\"tempo\",\"uri\":\"myapp://chooseShoeType/shoe4\",\"type\":\"DEEPLINK\"}," + - "{\"img\":\"airplane\",\"uri\":\"myapp://chooseShoeType/shoe5\",\"type\":\"DEEPLINK\"}" + - "]" + "{\"img\":\"train\",\"uri\":\"myapp://chooseShoeType/shoe1\",\"type\":\"DEEPLINK\"}," + + "{\"img\":\"bus\",\"uri\":\"myapp://chooseShoeType/shoe2\",\"type\":\"DEEPLINK\"}," + + "{\"img\":\"car\",\"uri\":\"myapp://chooseShoeType/shoe3\",\"type\":\"DEEPLINK\"}," + + "{\"img\":\"tempo\",\"uri\":\"myapp://chooseShoeType/shoe4\",\"type\":\"DEEPLINK\"}," + + "{\"img\":\"airplane\",\"uri\":\"myapp://chooseShoeType/shoe5\",\"type\":\"DEEPLINK\"}" + + "]" const val MOCK_MULTI_ICON_ITEM_PAYLOAD_INVALID_IMAGE = "[" + - "{\"img\":\"\",\"uri\":\"myapp://chooseShoeType/shoe1\",\"type\":\"DEEPLINK\"}," + - "{\"img\":\"bus\",\"uri\":\"myapp://chooseShoeType/shoe2\",\"type\":\"\"}," + - "{\"img\":\"\",\"uri\":\"myapp://chooseShoeType/shoe3\",\"type\":\"DEEPLINK\"}," + - "{\"img\":\"tempo\",\"uri\":\"myapp://chooseShoeType/shoe4\",\"type\":\"DEEPLINK\"}," + - "{\"img\":\"airplane\",\"uri\":\"myapp://chooseShoeType/shoe5\",\"type\":\"DEEPLINK\"}" + - "]" + "{\"img\":\"\",\"uri\":\"myapp://chooseShoeType/shoe1\",\"type\":\"DEEPLINK\"}," + + "{\"img\":\"bus\",\"uri\":\"myapp://chooseShoeType/shoe2\",\"type\":\"\"}," + + "{\"img\":\"\",\"uri\":\"myapp://chooseShoeType/shoe3\",\"type\":\"DEEPLINK\"}," + + "{\"img\":\"tempo\",\"uri\":\"myapp://chooseShoeType/shoe4\",\"type\":\"DEEPLINK\"}," + + "{\"img\":\"airplane\",\"uri\":\"myapp://chooseShoeType/shoe5\",\"type\":\"DEEPLINK\"}" + + "]" const val MOCK_MULTI_ICON_ITEM_PAYLOAD_INCOMPLETE_JSON = "[" + - "{\"img\":\"train\",\"uri\":\"\",\"type\":\"DEEPLINK\"}," + - "{\"img\":\"bus\",\"uri\":\"myapp://chooseShoeType/shoe2\",\"type\":\"DEEPLINK\"}," + - "{\"img\":\"tempo\",\"uri\":\"myapp://chooseShoeType/shoe4\",\"type\":\"DEEPLINK\"}," + - "{\"img\":\"airplane\",\"uri\":\"myapp://chooseShoeType/shoe5\",\"type\":\"DEEPLINK\"}" + - "]" + "{\"img\":\"train\",\"uri\":\"\",\"type\":\"DEEPLINK\"}," + + "{\"img\":\"bus\",\"uri\":\"myapp://chooseShoeType/shoe2\",\"type\":\"DEEPLINK\"}," + + "{\"img\":\"tempo\",\"uri\":\"myapp://chooseShoeType/shoe4\",\"type\":\"DEEPLINK\"}," + + "{\"img\":\"airplane\",\"uri\":\"myapp://chooseShoeType/shoe5\",\"type\":\"DEEPLINK\"}" + + "]" fun MutableMap.removeKeysFromMap(listOfKeys: List) { for (key in listOfKeys) { From 3d51b2e2ab24062832ae4980e03b11a22ab5a536 Mon Sep 17 00:00:00 2001 From: Prattham Arora <55047418+PratthamArora@users.noreply.github.com> Date: Wed, 19 Jun 2024 16:51:16 +0530 Subject: [PATCH 135/159] added unit tests for AEPPushNotificationBuilder (#39) * added unit tests for AEPPushNotificationBuilder * added test cases to verify channel ID, sound, visibility, badgeCount --- .../AEPPushNotificationBuilderTest.kt | 262 ++++++++++++++++++ .../builders/LegacyNotificationBuilderTest.kt | 4 - 2 files changed, 262 insertions(+), 4 deletions(-) create mode 100644 code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/AEPPushNotificationBuilderTest.kt diff --git a/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/AEPPushNotificationBuilderTest.kt b/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/AEPPushNotificationBuilderTest.kt new file mode 100644 index 00000000..68a47d82 --- /dev/null +++ b/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/AEPPushNotificationBuilderTest.kt @@ -0,0 +1,262 @@ +/* + Copyright 2024 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.notificationbuilder.internal.builders + +import android.app.Activity +import android.app.Notification +import android.content.Context +import android.content.Intent +import android.os.Build +import android.os.Bundle +import android.widget.RemoteViews +import androidx.core.app.NotificationCompat +import com.adobe.marketing.mobile.notificationbuilder.PushTemplateConstants +import com.adobe.marketing.mobile.notificationbuilder.PushTemplateConstants.IntentActions.TIMER_EXPIRED +import com.adobe.marketing.mobile.notificationbuilder.R +import com.adobe.marketing.mobile.notificationbuilder.internal.extensions.setNotificationBackgroundColor +import com.adobe.marketing.mobile.notificationbuilder.internal.extensions.setNotificationBodyTextColor +import com.adobe.marketing.mobile.notificationbuilder.internal.extensions.setNotificationTitleTextColor +import com.adobe.marketing.mobile.notificationbuilder.internal.templates.AEPPushTemplate +import com.adobe.marketing.mobile.notificationbuilder.internal.templates.BasicPushTemplate +import com.adobe.marketing.mobile.notificationbuilder.internal.templates.MockAEPPushTemplateDataProvider +import com.adobe.marketing.mobile.notificationbuilder.internal.templates.replaceValueInMap +import com.adobe.marketing.mobile.notificationbuilder.internal.util.IntentData +import com.adobe.marketing.mobile.notificationbuilder.internal.util.MapData +import junit.framework.TestCase.assertEquals +import junit.framework.TestCase.assertNotNull +import junit.framework.TestCase.assertNull +import org.junit.Assert.assertArrayEquals +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mock +import org.mockito.Mockito.verify +import org.mockito.MockitoAnnotations +import org.robolectric.RobolectricTestRunner +import org.robolectric.RuntimeEnvironment +import org.robolectric.Shadows +import org.robolectric.annotation.Config + +@RunWith(RobolectricTestRunner::class) +@Config(sdk = [31]) +class AEPPushNotificationBuilderTest { + + @Mock + private lateinit var smallLayout: RemoteViews + + @Mock + private lateinit var expandedLayout: RemoteViews + + private lateinit var context: Context + private lateinit var dataMap: MutableMap + private lateinit var trackerActivityClass: Class + private lateinit var mockBundle: Bundle + + @Before + fun setUp() { + MockitoAnnotations.openMocks(this) + context = RuntimeEnvironment.getApplication() + dataMap = MockAEPPushTemplateDataProvider.getMockedAEPDataMapWithAllKeys() + trackerActivityClass = DummyActivity::class.java + mockBundle = MockAEPPushTemplateDataProvider.getMockedAEPBundleWithAllKeys() + } + + @Test + fun `verify construct should map valid data fields to notification data`() { + val pushTemplate = BasicPushTemplate(MapData(dataMap)) + val notification = AEPPushNotificationBuilder.construct( + context, + pushTemplate, + CHANNEL_ID_TO_USE, + trackerActivityClass, + smallLayout, + expandedLayout, + CONTAINER_LAYOUT_VIEW_ID + ).build() + + verifyNotificationDataFields(notification, pushTemplate) + } + + @Test + fun `verify construct should map valid data fields and colors to notification view`() { + val pushTemplate = BasicPushTemplate(MapData(dataMap)) + AEPPushNotificationBuilder.construct( + context, + pushTemplate, + CHANNEL_ID_TO_USE, + trackerActivityClass, + smallLayout, + expandedLayout, + CONTAINER_LAYOUT_VIEW_ID + ) + + verifyNotificationViewDataAndColors(pushTemplate) + } + + @Test + fun `construct should map valid data to notification data and view for Intent Data`() { + val pushTemplate = BasicPushTemplate(IntentData(mockBundle, null)) + val notification = AEPPushNotificationBuilder.construct( + context, + pushTemplate, + CHANNEL_ID_TO_USE, + trackerActivityClass, + smallLayout, + expandedLayout, + CONTAINER_LAYOUT_VIEW_ID + ).build() + + verifyNotificationDataFields(notification, pushTemplate) + verifyNotificationViewDataAndColors(pushTemplate) + } + + @Test + fun `construct should not set notification sound if pushTemplate sound is invalid`() { + dataMap.replaceValueInMap( + Pair( + PushTemplateConstants.PushPayloadKeys.SOUND, + "invalid_sound" + ) + ) + val pushTemplate = BasicPushTemplate(MapData(dataMap)) + val notification = + AEPPushNotificationBuilder.construct( + context, + pushTemplate, + CHANNEL_ID_TO_USE, + trackerActivityClass, + smallLayout, + expandedLayout, + CONTAINER_LAYOUT_VIEW_ID + ).build() + + assertNull(notification.sound) + } + + @Test + fun `test createIntent returns intent with mapped properties`() { + val pushTemplate = BasicPushTemplate(MapData(dataMap)) + val action = TIMER_EXPIRED + val intent = AEPPushNotificationBuilder.createIntent(action, pushTemplate) + + assertEquals(action, intent.action) + assertEquals(Intent.FLAG_ACTIVITY_SINGLE_TOP, intent.flags) + assertNotNull(intent.extras) + } + + @Config(sdk = [25]) + @Test + fun `construct should set priority and vibration for API level below 26`() { + val pushTemplate = BasicPushTemplate(MapData(dataMap)) + val notification = + AEPPushNotificationBuilder.construct( + context, + pushTemplate, + CHANNEL_ID_TO_USE, + trackerActivityClass, + smallLayout, + expandedLayout, + CONTAINER_LAYOUT_VIEW_ID + ).build() + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) { + assertEquals(NotificationCompat.PRIORITY_HIGH, notification.priority) + assertArrayEquals(LongArray(0), notification.vibrate) + } + } + + @Test + @Config(sdk = [25]) + fun `construct should set sound on Notification Builder for API level 25 and below`() { + val pushTemplate = BasicPushTemplate(MapData(dataMap)) + val notification = + AEPPushNotificationBuilder.construct( + context, + pushTemplate, + CHANNEL_ID_TO_USE, + trackerActivityClass, + smallLayout, + expandedLayout, + CONTAINER_LAYOUT_VIEW_ID + ).build() + val soundUri = + "android.resource://com.adobe.marketing.mobile.notificationbuilder.test/raw/${pushTemplate.sound}" + assertEquals(soundUri, notification.sound.toString()) + } + + private fun verifyNotificationDataFields( + notification: Notification, + pushTemplate: AEPPushTemplate + ) { + val pendingIntent = notification.contentIntent + val shadowPendingIntent = Shadows.shadowOf(pendingIntent) + val intent = shadowPendingIntent.savedIntent + + assertEquals(Notification::class.java, notification.javaClass) + assertEquals(pushTemplate.ticker, notification.tickerText) + assertEquals(notification.channelId, CHANNEL_ID_TO_USE) + assertEquals(pushTemplate.badgeCount, notification.number) + assertEquals(pushTemplate.visibility.value, notification.visibility) + assertNotNull(notification.smallIcon) + assertNotNull(notification.deleteIntent) + assertEquals( + pushTemplate.actionUri, + intent.getStringExtra(PushTemplateConstants.TrackingKeys.ACTION_URI) + ) + assertEquals( + pushTemplate.tag, + intent.getStringExtra(PushTemplateConstants.PushPayloadKeys.TAG) + ) + assertEquals( + pushTemplate.isNotificationSticky.toString(), + intent.getStringExtra(PushTemplateConstants.PushPayloadKeys.STICKY) + ) + } + + private fun verifyNotificationViewDataAndColors(pushTemplate: AEPPushTemplate) { + verify(smallLayout).setTextViewText(R.id.notification_title, pushTemplate.title) + verify(smallLayout).setTextViewText(R.id.notification_body, pushTemplate.body) + verify(expandedLayout).setTextViewText(R.id.notification_title, pushTemplate.title) + verify(expandedLayout).setTextViewText( + R.id.notification_body_expanded, + pushTemplate.expandedBodyText + ) + verify(smallLayout).setNotificationBackgroundColor( + pushTemplate.backgroundColor, + R.id.basic_small_layout + ) + verify(expandedLayout).setNotificationBackgroundColor( + pushTemplate.backgroundColor, + CONTAINER_LAYOUT_VIEW_ID + ) + verify(smallLayout).setNotificationTitleTextColor( + pushTemplate.titleTextColor, + R.id.notification_title + ) + verify(expandedLayout).setNotificationTitleTextColor( + pushTemplate.titleTextColor, + R.id.notification_title + ) + verify(smallLayout).setNotificationBodyTextColor( + pushTemplate.bodyTextColor, + R.id.notification_body + ) + verify(expandedLayout).setNotificationBodyTextColor( + pushTemplate.bodyTextColor, + R.id.notification_body_expanded + ) + } + + companion object { + private const val CHANNEL_ID_TO_USE = "channel_id" + private const val CONTAINER_LAYOUT_VIEW_ID = 123 + } +} diff --git a/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/LegacyNotificationBuilderTest.kt b/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/LegacyNotificationBuilderTest.kt index 48ff1a70..416d4012 100644 --- a/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/LegacyNotificationBuilderTest.kt +++ b/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/LegacyNotificationBuilderTest.kt @@ -75,10 +75,6 @@ class LegacyNotificationBuilderTest { pushTemplate.body, notification.extras.getString(NotificationCompat.EXTRA_TEXT) ) - assertEquals( - pushTemplate.body, - notification.extras.getString(NotificationCompat.EXTRA_TEXT) - ) assertEquals(pushTemplate.channelId, notification.channelId) assertNotNull(notification.smallIcon) assertEquals( From ed77fc10e05266d3f8416d1e3546e8d24a5fc764 Mon Sep 17 00:00:00 2001 From: Ryan Morales Date: Wed, 19 Jun 2024 09:33:21 -0700 Subject: [PATCH 136/159] add more verification to TimerNotificationBuilderTests, fix a couple of tests - also clean kdoc in RemoteViewsExtensions --- .../extensions/RemoteViewsExtensions.kt | 4 +- .../builders/TimerNotificationBuilderTests.kt | 92 ++++++++++++++++--- .../internal/templates/MockDataUtils.kt | 1 + .../MockTimerTemplateDataProvider.kt | 6 +- 4 files changed, 85 insertions(+), 18 deletions(-) diff --git a/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/extensions/RemoteViewsExtensions.kt b/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/extensions/RemoteViewsExtensions.kt index 40ac66b9..61520901 100644 --- a/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/extensions/RemoteViewsExtensions.kt +++ b/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/extensions/RemoteViewsExtensions.kt @@ -94,7 +94,7 @@ internal fun RemoteViews.setTimerTextColor( timerTextColor: String?, containerViewId: Int ) { - // get custom color from hex string and set it the notification background + // get custom color from hex string and set it to the timer text color setElementColor( containerViewId, "#$timerTextColor", @@ -113,7 +113,7 @@ internal fun RemoteViews.setNotificationTitleTextColor( titleTextColor: String?, containerViewId: Int ) { - // get custom color from hex string and set it the notification title + // get custom color from hex string and set it to the notification title setElementColor( containerViewId, "#$titleTextColor", diff --git a/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/TimerNotificationBuilderTests.kt b/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/TimerNotificationBuilderTests.kt index c12bff93..6460fad1 100644 --- a/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/TimerNotificationBuilderTests.kt +++ b/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/TimerNotificationBuilderTests.kt @@ -22,9 +22,17 @@ import android.widget.RemoteViews import com.adobe.marketing.mobile.notificationbuilder.NotificationConstructionFailedException import com.adobe.marketing.mobile.notificationbuilder.internal.PendingIntentUtils import com.adobe.marketing.mobile.notificationbuilder.internal.PushTemplateImageUtils +import com.adobe.marketing.mobile.notificationbuilder.internal.extensions.setRemoteViewImage +import com.adobe.marketing.mobile.notificationbuilder.internal.extensions.setTimerTextColor import com.adobe.marketing.mobile.notificationbuilder.internal.templates.MOCKED_ALT_BODY import com.adobe.marketing.mobile.notificationbuilder.internal.templates.MOCKED_ALT_EXPANDED_BODY +import com.adobe.marketing.mobile.notificationbuilder.internal.templates.MOCKED_ALT_IMAGE_URI import com.adobe.marketing.mobile.notificationbuilder.internal.templates.MOCKED_ALT_TITLE +import com.adobe.marketing.mobile.notificationbuilder.internal.templates.MOCKED_BODY +import com.adobe.marketing.mobile.notificationbuilder.internal.templates.MOCKED_EXPANDED_BODY +import com.adobe.marketing.mobile.notificationbuilder.internal.templates.MOCKED_IMAGE_URI +import com.adobe.marketing.mobile.notificationbuilder.internal.templates.MOCKED_TIMER_COLOR +import com.adobe.marketing.mobile.notificationbuilder.internal.templates.MOCKED_TITLE import com.adobe.marketing.mobile.notificationbuilder.internal.templates.TimerPushTemplate import com.adobe.marketing.mobile.notificationbuilder.internal.templates.provideMockedTimerTemplate import io.mockk.Runs @@ -34,6 +42,7 @@ import io.mockk.mockk import io.mockk.mockkClass import io.mockk.mockkConstructor import io.mockk.mockkObject +import io.mockk.mockkStatic import io.mockk.verify import org.junit.Assert.assertNotNull import org.junit.Before @@ -70,12 +79,14 @@ class TimerNotificationBuilderTests { mockkObject(PushTemplateImageUtils) mockkObject(PendingIntentUtils) mockkConstructor(RemoteViews::class) + mockkStatic(RemoteViews::setTimerTextColor) + mockkStatic(RemoteViews::setRemoteViewImage) every { anyConstructed().setTextViewText(any(), any()) } just Runs every { anyConstructed().setImageViewBitmap(any(), any()) } just Runs } @Test(expected = NotificationConstructionFailedException::class) - fun `construct throws exception when schedule exact alarm permission false`() { + fun `construct throws exception when schedule exact alarm permission not given`() { every { alarmManager.canScheduleExactAlarms() } returns false val result = TimerNotificationBuilder.construct( context, @@ -107,18 +118,47 @@ class TimerNotificationBuilderTests { broadcastReceiverClass ) assertNotNull(result) - } - - @Test - fun `construct returns NotificationCompat Builder when using Intent for TimerTemplate`() { - pushTemplate = provideMockedTimerTemplate(true, false) - val result = TimerNotificationBuilder.construct( - context, - pushTemplate, - trackerActivityClass, - broadcastReceiverClass - ) - assertNotNull(result) + verify(exactly = 4) { // 4 calls expected, 2 from TimerNotificationBuilder and 2 from AEPPushNotificationBuilder + anyConstructed().setTextViewText( + any(), + MOCKED_TITLE + ) + } + verify(exactly = 2) { // 2 calls expected, 1 from TimerNotificationBuilder and 1 from AEPPushNotificationBuilder + anyConstructed().setTextViewText( + any(), + MOCKED_BODY + ) + } + verify(exactly = 2) { // 2 calls expected, 1 from TimerNotificationBuilder and 1 from AEPPushNotificationBuilder + anyConstructed().setTextViewText( + any(), + MOCKED_EXPANDED_BODY + ) + } + verify(exactly = 1) { + (RemoteViews::setRemoteViewImage)( + any(), + MOCKED_IMAGE_URI, + any() + ) + } + val expectedTime: Long = (60 * 1000) // using the duration present in the TimerTemplate + verify(exactly = 2) { + anyConstructed().setChronometer( + any(), + range(expectedTime - 5000, expectedTime, true, true), + any(), + true + ) + } + verify(exactly = 2) { + (RemoteViews::setTimerTextColor)( + any(), + MOCKED_TIMER_COLOR, + any() + ) + } } @Test @@ -154,6 +194,28 @@ class TimerNotificationBuilderTests { MOCKED_ALT_EXPANDED_BODY ) } + verify(exactly = 1) { + (RemoteViews::setRemoteViewImage)( + any(), + MOCKED_ALT_IMAGE_URI, + any() + ) + } + verify(exactly = 0) { + anyConstructed().setChronometer( + any(), + any(), + any(), + any() + ) + } + verify(exactly = 0) { + (RemoteViews::setTimerTextColor)( + any(), + any(), + any() + ) + } } @Test(expected = NotificationConstructionFailedException::class) @@ -189,7 +251,7 @@ class TimerNotificationBuilderTests { } @Test - fun `construct returns NotificationCompat Builder and alarm is set if duration present in TimerTemplate`() { + fun `construct returns NotificationCompat Builder and an alarm is set if duration present in TimerTemplate`() { val result = TimerNotificationBuilder.construct( context, pushTemplate, @@ -209,7 +271,7 @@ class TimerNotificationBuilderTests { } @Test - fun `construct returns NotificationCompat Builder and alarm is set using future timestamp if no duration in TimerTemplate`() { + fun `construct returns NotificationCompat Builder and an alarm is set using future timestamp if no duration in TimerTemplate`() { pushTemplate = provideMockedTimerTemplate(true, false) val result = TimerNotificationBuilder.construct( context, diff --git a/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/templates/MockDataUtils.kt b/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/templates/MockDataUtils.kt index df66c05f..46aa5b3f 100644 --- a/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/templates/MockDataUtils.kt +++ b/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/templates/MockDataUtils.kt @@ -20,6 +20,7 @@ const val MOCKED_ALT_TITLE = "Mocked Alternate Title" const val MOCKED_BODY = "Mocked Body" const val MOCKED_ALT_BODY = "Mocked Alternate Body" const val MOCKED_ALT_EXPANDED_BODY = "Mocked Alternate Expanded Body" +const val MOCKED_EXPANDED_BODY = "Mocked Expanded Body" const val MOCKED_PAYLOAD_VERSION = "1" const val MOCKED_CAROUSEL_LAYOUT = "default" const val MOCKED_BODY_TEXT_COLOR = "#FFFFFF" diff --git a/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/templates/MockTimerTemplateDataProvider.kt b/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/templates/MockTimerTemplateDataProvider.kt index 3ba26820..6929317c 100644 --- a/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/templates/MockTimerTemplateDataProvider.kt +++ b/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/templates/MockTimerTemplateDataProvider.kt @@ -23,6 +23,8 @@ object MockTimerTemplateDataProvider { val map = mutableMapOf( PushTemplateConstants.PushPayloadKeys.TITLE to MOCKED_TITLE, PushTemplateConstants.PushPayloadKeys.BODY to MOCKED_BODY, + PushTemplateConstants.PushPayloadKeys.EXPANDED_BODY_TEXT to MOCKED_EXPANDED_BODY, + PushTemplateConstants.PushPayloadKeys.IMAGE_URL to MOCKED_IMAGE_URI, PushTemplateConstants.PushPayloadKeys.VERSION to MOCKED_PAYLOAD_VERSION, PushTemplateConstants.PushPayloadKeys.TEMPLATE_TYPE to PushTemplateType.TIMER.value, PushTemplateConstants.PushPayloadKeys.BODY_TEXT_COLOR to MOCKED_BODY_TEXT_COLOR, @@ -53,7 +55,9 @@ object MockTimerTemplateDataProvider { internal fun getMockedBundleWithTimerData(isUsingDuration: Boolean, duration: String): Bundle { val mockBundle = Bundle() mockBundle.putString(PushTemplateConstants.PushPayloadKeys.TITLE, MOCKED_TITLE) - mockBundle.putString(PushTemplateConstants.PushPayloadKeys.BODY, MOCKED_BODY) + mockBundle.putString(PushTemplateConstants.PushPayloadKeys.BODY, MOCKED_EXPANDED_BODY) + mockBundle.putString(PushTemplateConstants.PushPayloadKeys.EXPANDED_BODY_TEXT, MOCKED_BODY) + mockBundle.putString(PushTemplateConstants.PushPayloadKeys.IMAGE_URL, MOCKED_IMAGE_URI) mockBundle.putString(PushTemplateConstants.PushPayloadKeys.VERSION, MOCKED_PAYLOAD_VERSION) mockBundle.putString( PushTemplateConstants.PushPayloadKeys.TEMPLATE_TYPE, From 073f3997bebed2cbd94a0c3816a4a755802bd0b7 Mon Sep 17 00:00:00 2001 From: Ryan Morales Date: Wed, 19 Jun 2024 09:39:25 -0700 Subject: [PATCH 137/159] relax time range verification for setChronometer verification --- .../internal/builders/TimerNotificationBuilderTests.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/TimerNotificationBuilderTests.kt b/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/TimerNotificationBuilderTests.kt index 6460fad1..6df12b2e 100644 --- a/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/TimerNotificationBuilderTests.kt +++ b/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/TimerNotificationBuilderTests.kt @@ -147,7 +147,7 @@ class TimerNotificationBuilderTests { verify(exactly = 2) { anyConstructed().setChronometer( any(), - range(expectedTime - 5000, expectedTime, true, true), + range(expectedTime - 5000, expectedTime + 5000, true, true), any(), true ) From b4f7b5b78835df0edb9cf019fa9868d1a0c9281c Mon Sep 17 00:00:00 2001 From: Ryan Morales Date: Wed, 19 Jun 2024 15:19:04 -0700 Subject: [PATCH 138/159] add NotificationBuilderTests for constructNotificationBuilder and handleRemindIntent --- .../internal/NotificationBuilderTests.java | 25 -- .../internal/NotificationBuilderTests.kt | 349 ++++++++++++++++++ .../builders/BasicNotificationBuilderTest.kt | 7 + .../builders/LegacyNotificationBuilderTest.kt | 7 + .../ManualCarouselNotificationBuilderTest.kt | 7 + .../internal/templates/MockDataUtils.kt | 4 +- 6 files changed, 372 insertions(+), 27 deletions(-) delete mode 100644 code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/NotificationBuilderTests.java create mode 100644 code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/NotificationBuilderTests.kt diff --git a/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/NotificationBuilderTests.java b/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/NotificationBuilderTests.java deleted file mode 100644 index 2b9a81cf..00000000 --- a/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/NotificationBuilderTests.java +++ /dev/null @@ -1,25 +0,0 @@ -/* - Copyright 2024 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.notificationbuilder.internal; - -import static org.junit.Assert.assertEquals; - -import com.adobe.marketing.mobile.notificationbuilder.NotificationBuilder; -import org.junit.Test; - -public class NotificationBuilderTests { - - @Test - public void test_extensionVersion() { - assertEquals("3.0.0", NotificationBuilder.version()); - } -} diff --git a/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/NotificationBuilderTests.kt b/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/NotificationBuilderTests.kt new file mode 100644 index 00000000..b07b41a4 --- /dev/null +++ b/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/NotificationBuilderTests.kt @@ -0,0 +1,349 @@ +/* + Copyright 2024 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.notificationbuilder.internal + +import android.app.Activity +import android.app.Application +import android.content.BroadcastReceiver +import android.content.Intent +import androidx.core.app.NotificationManagerCompat +import com.adobe.marketing.mobile.notificationbuilder.NotificationBuilder +import com.adobe.marketing.mobile.notificationbuilder.NotificationConstructionFailedException +import com.adobe.marketing.mobile.notificationbuilder.PushTemplateConstants +import com.adobe.marketing.mobile.notificationbuilder.internal.builders.DummyActivity +import com.adobe.marketing.mobile.notificationbuilder.internal.builders.DummyBroadcastReceiver +import com.adobe.marketing.mobile.notificationbuilder.internal.templates.MockAEPPushTemplateDataProvider +import com.adobe.marketing.mobile.services.AppContextService +import com.adobe.marketing.mobile.services.ServiceProvider +import io.mockk.clearAllMocks +import io.mockk.every +import io.mockk.mockk +import io.mockk.mockkClass +import io.mockk.mockkObject +import io.mockk.mockkStatic +import io.mockk.verify +import org.junit.After +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mock +import org.mockito.MockitoAnnotations +import org.robolectric.RobolectricTestRunner +import org.robolectric.RuntimeEnvironment +import org.robolectric.annotation.Config +import kotlin.test.assertEquals +import kotlin.test.assertNotNull +import kotlin.test.assertNull + +@RunWith(RobolectricTestRunner::class) +@Config(sdk = [31]) +class NotificationBuilderTests { + + @Mock + private lateinit var application: Application + private lateinit var trackerActivityClass: Class + private lateinit var broadcastReceiverClass: Class + private lateinit var mockNotificationManagerCompat: NotificationManagerCompat + + @Before + fun setUp() { + MockitoAnnotations.openMocks(this) + application = RuntimeEnvironment.getApplication() + ServiceProvider.getInstance().appContextService.setApplication(application) + trackerActivityClass = DummyActivity::class.java + broadcastReceiverClass = DummyBroadcastReceiver::class.java + mockkObject(PendingIntentUtils) + mockkStatic(NotificationManagerCompat::class) + mockNotificationManagerCompat = mockkClass(NotificationManagerCompat::class) + every { NotificationManagerCompat.from(any()) } returns mockNotificationManagerCompat + every { mockNotificationManagerCompat.cancel(any()) } returns Unit + } + + @After + fun cleanup() { + clearAllMocks() + } + + @Test + fun `NotificationBuilder version values matches the expected version`() { + val version = NotificationBuilder.version() + + assertEquals("3.0.0", version) + } + + @Test + fun `constructNotificationBuilder given a map with required data should return a NotificationCompat Builder`() { + val mapData = MockAEPPushTemplateDataProvider.getMockedDataMapWithRequiredData() + val notificationBuilder = NotificationBuilder.constructNotificationBuilder( + mapData, + trackerActivityClass, + broadcastReceiverClass + ) + + assertNotNull(notificationBuilder) + } + + @Test(expected = NotificationConstructionFailedException::class) + fun `constructNotificationBuilder given a map with required data but no context is available should throw an exception`() { + setNullContext() + val mapData = MockAEPPushTemplateDataProvider.getMockedDataMapWithRequiredData() + val notificationBuilder = NotificationBuilder.constructNotificationBuilder( + mapData, + trackerActivityClass, + broadcastReceiverClass + ) + + assertNull(notificationBuilder) + } + + @Test(expected = NotificationConstructionFailedException::class) + fun `constructNotificationBuilder given a map with empty data should thrown an exception`() { + val mapData = emptyMap() + val notificationBuilder = NotificationBuilder.constructNotificationBuilder( + mapData, + trackerActivityClass, + broadcastReceiverClass + ) + + assertNull(notificationBuilder) + } + + @Test + fun `constructNotificationBuilder given an intent with required data should return a NotificationCompat Builder`() { + val bundle = MockAEPPushTemplateDataProvider.getMockedBundleWithRequiredData() + val intent = Intent() + intent.action = "mockAction" + intent.putExtras(bundle) + val notificationBuilder = NotificationBuilder.constructNotificationBuilder( + intent, + trackerActivityClass, + broadcastReceiverClass + ) + + assertNotNull(notificationBuilder) + } + + @Test(expected = NotificationConstructionFailedException::class) + fun `constructNotificationBuilder given an intent with required data but no context is available should throw an exception`() { + setNullContext() + val bundle = MockAEPPushTemplateDataProvider.getMockedBundleWithRequiredData() + val intent = Intent() + intent.action = "mockAction" + intent.putExtras(bundle) + val notificationBuilder = NotificationBuilder.constructNotificationBuilder( + intent, + trackerActivityClass, + broadcastReceiverClass + ) + + assertNull(notificationBuilder) + } + + @Test(expected = NotificationConstructionFailedException::class) + fun `constructNotificationBuilder given an intent with empty data should thrown an exception`() { + val intent = Intent() + intent.action = "mockAction" + val notificationBuilder = NotificationBuilder.constructNotificationBuilder( + intent, + trackerActivityClass, + broadcastReceiverClass + ) + + assertNull(notificationBuilder) + } + + @Test + fun `handleRemindIntent given a valid intent should schedule a remind notification`() { + val bundle = MockAEPPushTemplateDataProvider.getMockedAEPBundleWithAllKeys() + val remindIntent = Intent() + remindIntent.putExtras(bundle) + remindIntent.action = PushTemplateConstants.IntentActions.REMIND_LATER_CLICKED + NotificationBuilder.handleRemindIntent( + remindIntent, + broadcastReceiverClass + ) + + verify(exactly = 1) { PendingIntentUtils.scheduleNotification(any(), any(), any(), any()) } + verify(exactly = 1) { mockNotificationManagerCompat.cancel(any()) } + } + + @Test + fun `handleRemindIntent given a valid intent with no broadcast receiver should not schedule a remind notification`() { + val bundle = MockAEPPushTemplateDataProvider.getMockedAEPBundleWithAllKeys() + val remindIntent = Intent() + remindIntent.putExtras(bundle) + remindIntent.action = PushTemplateConstants.IntentActions.REMIND_LATER_CLICKED + NotificationBuilder.handleRemindIntent( + remindIntent, + null + ) + + verify(exactly = 0) { PendingIntentUtils.scheduleNotification(any(), any(), any(), any()) } + verify(exactly = 1) { mockNotificationManagerCompat.cancel(any()) } + } + + @Test + fun `handleRemindIntent given a valid intent with no broadcast receiver and tag should not schedule a remind notification and should not cancel the current notification`() { + val bundle = MockAEPPushTemplateDataProvider.getMockedAEPBundleWithAllKeys() + bundle.remove(PushTemplateConstants.PushPayloadKeys.TAG) + val remindIntent = Intent() + remindIntent.putExtras(bundle) + remindIntent.action = PushTemplateConstants.IntentActions.REMIND_LATER_CLICKED + NotificationBuilder.handleRemindIntent( + remindIntent, + null + ) + + verify(exactly = 0) { PendingIntentUtils.scheduleNotification(any(), any(), any(), any()) } + verify(exactly = 0) { mockNotificationManagerCompat.cancel(any()) } + } + + @Test + fun `handleRemindIntent given a valid intent with a remind later timestamp should schedule a remind notification`() { + val bundle = MockAEPPushTemplateDataProvider.getMockedAEPBundleWithAllKeys() + bundle.remove(PushTemplateConstants.PushPayloadKeys.REMIND_LATER_DURATION) + val remindIntent = Intent() + remindIntent.putExtras(bundle) + remindIntent.action = PushTemplateConstants.IntentActions.REMIND_LATER_CLICKED + NotificationBuilder.handleRemindIntent( + remindIntent, + broadcastReceiverClass + ) + + verify(exactly = 1) { PendingIntentUtils.scheduleNotification(any(), any(), any(), any()) } + verify(exactly = 1) { mockNotificationManagerCompat.cancel(any()) } + } + + @Test + fun `handleRemindIntent given a valid intent with a remind later timestamp and no tag should schedule a remind notification but should not cancel the current notification`() { + val bundle = MockAEPPushTemplateDataProvider.getMockedAEPBundleWithAllKeys() + bundle.remove(PushTemplateConstants.PushPayloadKeys.REMIND_LATER_DURATION) + bundle.remove(PushTemplateConstants.PushPayloadKeys.TAG) + val remindIntent = Intent() + remindIntent.putExtras(bundle) + remindIntent.action = PushTemplateConstants.IntentActions.REMIND_LATER_CLICKED + NotificationBuilder.handleRemindIntent( + remindIntent, + broadcastReceiverClass + ) + + verify(exactly = 1) { PendingIntentUtils.scheduleNotification(any(), any(), any(), any()) } + verify(exactly = 0) { mockNotificationManagerCompat.cancel(any()) } + } + + @Test(expected = IllegalArgumentException::class) + fun `handleRemindIntent given an intent with no remind later timestamp or duration should throw an exception`() { + val bundle = MockAEPPushTemplateDataProvider.getMockedAEPBundleWithAllKeys() + bundle.remove(PushTemplateConstants.PushPayloadKeys.REMIND_LATER_DURATION) + bundle.remove(PushTemplateConstants.PushPayloadKeys.REMIND_LATER_TIMESTAMP) + val remindIntent = Intent() + remindIntent.putExtras(bundle) + remindIntent.action = PushTemplateConstants.IntentActions.REMIND_LATER_CLICKED + NotificationBuilder.handleRemindIntent( + remindIntent, + broadcastReceiverClass + ) + + verify(exactly = 0) { PendingIntentUtils.scheduleNotification(any(), any(), any(), any()) } + verify(exactly = 1) { mockNotificationManagerCompat.cancel(any()) } + } + + @Test(expected = IllegalArgumentException::class) + fun `handleRemindIntent given an intent with no remind later timestamp, duration, and tag should throw an exception but should not cancel the current notification`() { + val bundle = MockAEPPushTemplateDataProvider.getMockedAEPBundleWithAllKeys() + bundle.remove(PushTemplateConstants.PushPayloadKeys.REMIND_LATER_DURATION) + bundle.remove(PushTemplateConstants.PushPayloadKeys.REMIND_LATER_TIMESTAMP) + bundle.remove(PushTemplateConstants.PushPayloadKeys.TAG) + val remindIntent = Intent() + remindIntent.putExtras(bundle) + remindIntent.action = PushTemplateConstants.IntentActions.REMIND_LATER_CLICKED + NotificationBuilder.handleRemindIntent( + remindIntent, + broadcastReceiverClass + ) + + verify(exactly = 0) { PendingIntentUtils.scheduleNotification(any(), any(), any(), any()) } + verify(exactly = 0) { mockNotificationManagerCompat.cancel(any()) } + } + + @Test(expected = IllegalArgumentException::class) + fun `handleRemindIntent given an intent with an invalid remind later timestamp and duration should throw an exception`() { + val bundle = MockAEPPushTemplateDataProvider.getMockedAEPBundleWithAllKeys() + bundle.putString(PushTemplateConstants.PushPayloadKeys.REMIND_LATER_DURATION, "duration") + bundle.putString(PushTemplateConstants.PushPayloadKeys.REMIND_LATER_TIMESTAMP, "timestamp") + val remindIntent = Intent() + remindIntent.putExtras(bundle) + remindIntent.action = PushTemplateConstants.IntentActions.REMIND_LATER_CLICKED + NotificationBuilder.handleRemindIntent( + remindIntent, + broadcastReceiverClass + ) + + verify(exactly = 0) { PendingIntentUtils.scheduleNotification(any(), any(), any(), any()) } + verify(exactly = 0) { mockNotificationManagerCompat.cancel(any()) } + } + + @Test(expected = IllegalArgumentException::class) + fun `handleRemindIntent given a valid intent with a remind later timestamp before the current date should throw an exception`() { + val bundle = MockAEPPushTemplateDataProvider.getMockedAEPBundleWithAllKeys() + bundle.remove(PushTemplateConstants.PushPayloadKeys.REMIND_LATER_DURATION) + bundle.putString(PushTemplateConstants.PushPayloadKeys.REMIND_LATER_TIMESTAMP, "1234567890") + val remindIntent = Intent() + remindIntent.putExtras(bundle) + remindIntent.action = PushTemplateConstants.IntentActions.REMIND_LATER_CLICKED + NotificationBuilder.handleRemindIntent( + remindIntent, + broadcastReceiverClass + ) + + verify(exactly = 0) { PendingIntentUtils.scheduleNotification(any(), any(), any(), any()) } + verify(exactly = 0) { mockNotificationManagerCompat.cancel(any()) } + } + + @Test(expected = NotificationConstructionFailedException::class) + fun `handleRemindIntent given a valid intent but no context is available should throw an exception`() { + setNullContext() + val bundle = MockAEPPushTemplateDataProvider.getMockedAEPBundleWithAllKeys() + val remindIntent = Intent() + remindIntent.putExtras(bundle) + remindIntent.action = PushTemplateConstants.IntentActions.REMIND_LATER_CLICKED + NotificationBuilder.handleRemindIntent( + remindIntent, + broadcastReceiverClass + ) + + verify(exactly = 0) { PendingIntentUtils.scheduleNotification(any(), any(), any(), any()) } + verify(exactly = 0) { mockNotificationManagerCompat.cancel(any()) } + } + + @Test(expected = NotificationConstructionFailedException::class) + fun `handleRemindIntent given an intent with no bundle should throw an exception`() { + val remindIntent = Intent() + remindIntent.action = PushTemplateConstants.IntentActions.REMIND_LATER_CLICKED + NotificationBuilder.handleRemindIntent( + remindIntent, + broadcastReceiverClass + ) + + verify(exactly = 0) { PendingIntentUtils.scheduleNotification(any(), any(), any(), any()) } + verify(exactly = 0) { mockNotificationManagerCompat.cancel(any()) } + } + + private fun setNullContext() { + val mockAppContextService = mockk() + val mockServiceProvider = mockkClass(ServiceProvider::class) + mockkStatic(ServiceProvider::getInstance) + every { ServiceProvider.getInstance() } returns mockServiceProvider + every { mockServiceProvider.appContextService } returns mockAppContextService + every { mockAppContextService.applicationContext } returns null + } +} diff --git a/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/BasicNotificationBuilderTest.kt b/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/BasicNotificationBuilderTest.kt index 2c2f58de..bb16a7aa 100644 --- a/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/BasicNotificationBuilderTest.kt +++ b/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/BasicNotificationBuilderTest.kt @@ -28,7 +28,9 @@ import com.adobe.marketing.mobile.notificationbuilder.internal.templates.MockAEP import com.adobe.marketing.mobile.notificationbuilder.internal.templates.provideMockedBasicPushTemplateWithAllKeys import com.adobe.marketing.mobile.notificationbuilder.internal.templates.provideMockedBasicPushTemplateWithRequiredData import com.adobe.marketing.mobile.notificationbuilder.internal.util.MapData +import io.mockk.clearAllMocks import junit.framework.TestCase.assertEquals +import org.junit.After import org.junit.Assert.assertNull import org.junit.Before import org.junit.Test @@ -59,6 +61,11 @@ class BasicNotificationBuilderTest { broadcastReceiverClass = DummyBroadcastReceiver::class.java } + @After + fun cleanup() { + clearAllMocks() + } + @Test fun `construct should return a NotificationCompat Builder`() { val pushTemplate = provideMockedBasicPushTemplateWithAllKeys() diff --git a/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/LegacyNotificationBuilderTest.kt b/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/LegacyNotificationBuilderTest.kt index 48ff1a70..ee6f5061 100644 --- a/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/LegacyNotificationBuilderTest.kt +++ b/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/LegacyNotificationBuilderTest.kt @@ -26,9 +26,11 @@ import com.adobe.marketing.mobile.notificationbuilder.internal.templates.removeK import com.adobe.marketing.mobile.notificationbuilder.internal.templates.replaceValueInMap import com.adobe.marketing.mobile.notificationbuilder.internal.util.IntentData import com.adobe.marketing.mobile.notificationbuilder.internal.util.MapData +import io.mockk.clearAllMocks import junit.framework.TestCase.assertEquals import junit.framework.TestCase.assertNotNull import junit.framework.TestCase.assertNull +import org.junit.After import org.junit.Assert.assertArrayEquals import org.junit.Before import org.junit.Test @@ -55,6 +57,11 @@ class LegacyNotificationBuilderTest { mockBundle = MockAEPPushTemplateDataProvider.getMockedAEPBundleWithAllKeys() } + @After + fun cleanup() { + clearAllMocks() + } + @Test fun `verify construct should map valid BasicPushTemplate data to notification data`() { val pushTemplate = BasicPushTemplate(MapData(dataMap)) diff --git a/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/ManualCarouselNotificationBuilderTest.kt b/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/ManualCarouselNotificationBuilderTest.kt index a1e9ef02..4443c19a 100644 --- a/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/ManualCarouselNotificationBuilderTest.kt +++ b/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/ManualCarouselNotificationBuilderTest.kt @@ -32,12 +32,14 @@ import com.adobe.marketing.mobile.notificationbuilder.internal.templates.MockCar import com.adobe.marketing.mobile.notificationbuilder.internal.templates.provideMockedManualCarousalTemplate import com.adobe.marketing.mobile.notificationbuilder.internal.util.IntentData import io.mockk.Runs +import io.mockk.clearAllMocks import io.mockk.every import io.mockk.just import io.mockk.mockkClass import io.mockk.mockkConstructor import io.mockk.mockkObject import io.mockk.verify +import org.junit.After import org.junit.Assert.assertEquals import org.junit.Assert.assertNotNull import org.junit.Assert.assertTrue @@ -71,6 +73,11 @@ class ManualCarouselNotificationBuilderTest { expandedLayout = mockkClass(RemoteViews::class) } + @After + fun cleanup() { + clearAllMocks() + } + @Test fun `construct returns NotificationCompat Builder for valid inputs`() { val result = ManualCarouselNotificationBuilder.construct( diff --git a/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/templates/MockDataUtils.kt b/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/templates/MockDataUtils.kt index 21a45ac7..385c6e3c 100644 --- a/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/templates/MockDataUtils.kt +++ b/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/templates/MockDataUtils.kt @@ -38,8 +38,8 @@ const val MOCKED_ACTION_BUTTON_DATA = "[{\"label\":\"Go to chess.com\",\"uri\":\"https://chess.com/games/552\",\"type\":\"DEEPLINK\"},{\"label\":\"Open the app\",\"uri\":\"\",\"type\":\"OPENAPP\"}]" const val MOCKED_BASIC_TEMPLATE_BODY = "Shall we play a game?" const val MOCK_REMIND_LATER_TEXT = "remind me" -const val MOCK_REMIND_LATER_TIME = "1234567890" -const val MOCK_REMIND_LATER_DURATION = "6000" +const val MOCK_REMIND_LATER_TIME = "4085583547" +const val MOCK_REMIND_LATER_DURATION = "60" const val MOCKED_MALFORMED_JSON_ACTION_BUTTON = "[" + "{\"label\":\"\",\"uri\":\"https://chess.com/games/552\",\"type\":\"DEEPLINK\"}," + "{}," + From 1be074de98044360288cdccc3fdf5a3e29b84143 Mon Sep 17 00:00:00 2001 From: Ishita Gambhir Date: Thu, 20 Jun 2024 22:59:04 +0530 Subject: [PATCH 139/159] MOB-20954: Add unit tests for InputBoxNotificationBuilder (#36) * MOB-20954: Add unit tests for InputBoxNotificationBuilder * address PR comments for InputBoxNotificationBuilder unit tests * add more tests --- .../InputBoxNotificationBuilderTest.kt | 267 ++++++++++++++++++ .../internal/templates/MockDataUtils.kt | 26 ++ .../MockInputBoxPushTemplateDataProvider.kt | 101 +++++++ 3 files changed, 394 insertions(+) create mode 100644 code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/InputBoxNotificationBuilderTest.kt create mode 100644 code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/templates/MockInputBoxPushTemplateDataProvider.kt diff --git a/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/InputBoxNotificationBuilderTest.kt b/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/InputBoxNotificationBuilderTest.kt new file mode 100644 index 00000000..a11a51d9 --- /dev/null +++ b/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/InputBoxNotificationBuilderTest.kt @@ -0,0 +1,267 @@ +/* + Copyright 2024 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.notificationbuilder.internal.builders + +import android.app.Activity +import android.content.BroadcastReceiver +import android.content.Context +import android.widget.RemoteViews +import androidx.core.app.NotificationCompat +import com.adobe.marketing.mobile.notificationbuilder.PushTemplateConstants +import com.adobe.marketing.mobile.notificationbuilder.R +import com.adobe.marketing.mobile.notificationbuilder.internal.extensions.setRemoteImage +import com.adobe.marketing.mobile.notificationbuilder.internal.templates.InputBoxPushTemplate +import com.adobe.marketing.mobile.notificationbuilder.internal.templates.MOCKED_BASIC_TEMPLATE_BODY +import com.adobe.marketing.mobile.notificationbuilder.internal.templates.MOCKED_BASIC_TEMPLATE_BODY_EXPANDED +import com.adobe.marketing.mobile.notificationbuilder.internal.templates.MOCKED_BODY +import com.adobe.marketing.mobile.notificationbuilder.internal.templates.MOCKED_FEEDBACK_IMAGE +import com.adobe.marketing.mobile.notificationbuilder.internal.templates.MOCKED_FEEDBACK_TEXT +import com.adobe.marketing.mobile.notificationbuilder.internal.templates.MOCKED_HINT +import com.adobe.marketing.mobile.notificationbuilder.internal.templates.MOCKED_IMAGE_URI +import com.adobe.marketing.mobile.notificationbuilder.internal.templates.MOCKED_RECEIVER_NAME +import com.adobe.marketing.mobile.notificationbuilder.internal.templates.MOCKED_TITLE +import com.adobe.marketing.mobile.notificationbuilder.internal.templates.MockInputBoxPushTemplateDataProvider +import com.adobe.marketing.mobile.notificationbuilder.internal.templates.provideMockedInputBoxPushTemplateWithAllKeys +import com.adobe.marketing.mobile.notificationbuilder.internal.templates.provideMockedInputBoxPushTemplateWithRequiredData +import com.adobe.marketing.mobile.notificationbuilder.internal.templates.removeKeysFromMap +import com.adobe.marketing.mobile.notificationbuilder.internal.templates.replaceValueInMap +import com.adobe.marketing.mobile.notificationbuilder.internal.util.MapData +import com.google.common.base.Verify.verify +import io.mockk.Runs +import io.mockk.every +import io.mockk.just +import io.mockk.mockkConstructor +import io.mockk.verify +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mock +import org.mockito.MockitoAnnotations +import org.robolectric.RobolectricTestRunner +import org.robolectric.RuntimeEnvironment +import org.robolectric.Shadows +import org.robolectric.annotation.Config +import kotlin.test.assertEquals +import kotlin.test.assertNotNull +import kotlin.test.assertNull + +@RunWith(RobolectricTestRunner::class) +@Config(sdk = [31]) +class InputBoxNotificationBuilderTest { + @Mock + private lateinit var context: Context + private lateinit var trackerActivityClass: Class + private lateinit var broadcastReceiverClass: Class + + @Before + fun setUp() { + MockitoAnnotations.openMocks(this) + + context = RuntimeEnvironment.getApplication() + trackerActivityClass = DummyActivity::class.java + broadcastReceiverClass = DummyBroadcastReceiver::class.java + mockkConstructor(RemoteViews::class) + } + + @Test + fun `construct should return a NotificationCompat Builder`() { + val pushTemplate = provideMockedInputBoxPushTemplateWithRequiredData() + val notificationBuilder = InputBoxNotificationBuilder.construct( + context, + pushTemplate, + trackerActivityClass, + broadcastReceiverClass + ) + + assertNotNull(notificationBuilder) + assertEquals(NotificationCompat.Builder::class.java, notificationBuilder.javaClass) + } + + @Test + fun `construct should not have any inputText action if the template is created from intent`() { + val pushTemplate = provideMockedInputBoxPushTemplateWithRequiredData(true) + val notificationBuilder = InputBoxNotificationBuilder.construct( + context, + pushTemplate, + trackerActivityClass, + broadcastReceiverClass + ) + + assertNotNull(notificationBuilder) + assertEquals(0, notificationBuilder.mActions.size) + } + + @Test + fun `construct should have an InputText action if the template is not created from intent`() { + val pushTemplate = provideMockedInputBoxPushTemplateWithRequiredData() + val notificationBuilder = InputBoxNotificationBuilder.construct( + context, + pushTemplate, + trackerActivityClass, + broadcastReceiverClass + ) + + assertNotNull(notificationBuilder) + assertEquals(1, notificationBuilder.mActions.size) + assertNotNull(notificationBuilder.mActions.find { it.title == MOCKED_HINT }) + } + + @Test + fun `createInputReceivedPendingIntent should not set class when trackerActivityClass parameter is null`() { + val pushTemplate = provideMockedInputBoxPushTemplateWithRequiredData() + val notificationBuilder = InputBoxNotificationBuilder.construct( + context, + pushTemplate, + null, + broadcastReceiverClass + ) + + val notification = notificationBuilder.build() + val pendingIntent = notification.contentIntent + val shadowPendingIntent = Shadows.shadowOf(pendingIntent) + val intent = shadowPendingIntent.savedIntent + + assertNotNull(intent) + assertNull(intent.resolveActivity(context.packageManager)) + } + + @Test + fun `createInputReceivedPendingIntent should set class when trackerActivityClass parameter is not null`() { + val pushTemplate = provideMockedInputBoxPushTemplateWithRequiredData() + val notificationBuilder = InputBoxNotificationBuilder.construct( + context, + pushTemplate, + trackerActivityClass, + broadcastReceiverClass + ) + + val notification = notificationBuilder.build() + val pendingIntent = notification.contentIntent + val shadowPendingIntent = Shadows.shadowOf(pendingIntent) + val intent = shadowPendingIntent.savedIntent + + assertNotNull(intent) + assertEquals("com.adobe.marketing.mobile.notificationbuilder.internal.builders.DummyActivity", intent.resolveActivity(context.packageManager).className) + } + + @Test + fun `createInputReceivedPendingIntent should set intent extras correctly`() { + val pushTemplate = provideMockedInputBoxPushTemplateWithRequiredData() + val notificationBuilder = InputBoxNotificationBuilder.construct( + context, + pushTemplate, + trackerActivityClass, + broadcastReceiverClass + ) + + val notification = notificationBuilder.build() + val pendingIntent = notification.contentIntent + val shadowPendingIntent = Shadows.shadowOf(pendingIntent) + val intent = shadowPendingIntent.savedIntent + + assertNotNull(intent) + assertEquals(MOCKED_TITLE, intent.getStringExtra(PushTemplateConstants.PushPayloadKeys.TITLE)) + assertEquals(MOCKED_BODY, intent.getStringExtra(PushTemplateConstants.PushPayloadKeys.BODY)) + assertEquals(MOCKED_RECEIVER_NAME, intent.getStringExtra(PushTemplateConstants.PushPayloadKeys.INPUT_BOX_RECEIVER_NAME)) + assertEquals(MOCKED_FEEDBACK_IMAGE, intent.getStringExtra(PushTemplateConstants.PushPayloadKeys.INPUT_BOX_FEEDBACK_IMAGE)) + assertEquals(MOCKED_FEEDBACK_TEXT, intent.getStringExtra(PushTemplateConstants.PushPayloadKeys.INPUT_BOX_FEEDBACK_TEXT)) + assertEquals(MOCKED_HINT, intent.getStringExtra(PushTemplateConstants.PushPayloadKeys.INPUT_BOX_HINT)) + } + + @Test + fun `Action with default hint text should be created when inputTextHint field is empty`() { + val dataMap = MockInputBoxPushTemplateDataProvider.getMockedInputBoxDataMapWithRequiredData() + dataMap.replaceValueInMap(PushTemplateConstants.PushPayloadKeys.INPUT_BOX_HINT, "") + + val pushTemplate = InputBoxPushTemplate(MapData(dataMap)) + val notificationBuilder = InputBoxNotificationBuilder.construct( + context, + pushTemplate, + trackerActivityClass, + broadcastReceiverClass + ) + + assertNotNull(notificationBuilder) + + val actions = notificationBuilder.mActions + assertEquals(PushTemplateConstants.DefaultValues.INPUT_BOX_DEFAULT_REPLY_TEXT, actions[0].title) + } + + @Test + fun `Action with default hint text should be created when inputTextHint field is null`() { + val dataMap = MockInputBoxPushTemplateDataProvider.getMockedInputBoxDataMapWithRequiredData() + dataMap.removeKeysFromMap(PushTemplateConstants.PushPayloadKeys.INPUT_BOX_HINT) + + val pushTemplate = InputBoxPushTemplate(MapData(dataMap)) + val notificationBuilder = InputBoxNotificationBuilder.construct( + context, + pushTemplate, + trackerActivityClass, + broadcastReceiverClass + ) + + assertNotNull(notificationBuilder) + + val actions = notificationBuilder.mActions + assertEquals(PushTemplateConstants.DefaultValues.INPUT_BOX_DEFAULT_REPLY_TEXT, actions[0].title) + } + + @Test + fun `Action with provided hint text should be created when inputTextHint field is present`() { + val pushTemplate = provideMockedInputBoxPushTemplateWithRequiredData() + val notificationBuilder = InputBoxNotificationBuilder.construct( + context, + pushTemplate, + trackerActivityClass, + broadcastReceiverClass + ) + + assertNotNull(notificationBuilder) + + val actions = notificationBuilder.mActions + assertEquals(MOCKED_HINT, actions[0].title) + } + + @Test + fun `Validate notification content when push template is created from intent`() { + val pushTemplate = provideMockedInputBoxPushTemplateWithAllKeys(true) + every { anyConstructed().setTextViewText(any(), any()) } just Runs + + val notificationBuilder = InputBoxNotificationBuilder.construct( + context, + pushTemplate, + trackerActivityClass, + broadcastReceiverClass + ) + + verify { anyConstructed().setRemoteImage(MOCKED_FEEDBACK_IMAGE, R.id.expanded_template_image) } + verify { anyConstructed().setTextViewText(R.id.notification_body, MOCKED_FEEDBACK_TEXT) } + verify { anyConstructed().setTextViewText(R.id.notification_body_expanded, MOCKED_FEEDBACK_TEXT) } + } + + @Test + fun `Validate notification content when push template is not created from intent`() { + val pushTemplate = provideMockedInputBoxPushTemplateWithAllKeys() + every { anyConstructed().setTextViewText(any(), any()) } just Runs + + val notificationBuilder = InputBoxNotificationBuilder.construct( + context, + pushTemplate, + trackerActivityClass, + broadcastReceiverClass + ) + + verify { anyConstructed().setRemoteImage(MOCKED_IMAGE_URI, R.id.expanded_template_image) } + verify { anyConstructed().setTextViewText(R.id.notification_body, MOCKED_BASIC_TEMPLATE_BODY) } + verify { anyConstructed().setTextViewText(R.id.notification_body_expanded, MOCKED_BASIC_TEMPLATE_BODY_EXPANDED) } + } +} diff --git a/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/templates/MockDataUtils.kt b/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/templates/MockDataUtils.kt index 21a45ac7..c4f42a81 100644 --- a/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/templates/MockDataUtils.kt +++ b/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/templates/MockDataUtils.kt @@ -46,6 +46,10 @@ const val MOCKED_MALFORMED_JSON_ACTION_BUTTON = "[" + "{\"label\":\"Open the app\",\"uri\":\"\",\"type\":\"GO_TO_WEB_PAGE\"}," + "{\"label\":\"Go to chess.com\",\"uri\":\"https://chess.com/games/552\",\"type\":\"DEEPLINK\"}]" const val MOCKED_CHANNEL_ID = "AEPSDKPushChannel1" +const val MOCKED_RECEIVER_NAME = "receiverName" +const val MOCKED_HINT = "hint" +const val MOCKED_FEEDBACK_TEXT = "feedbackText" +const val MOCKED_FEEDBACK_IMAGE = "https://i.imgur.com/7ZolaOv.jpeg" const val MOCK_MULTI_ICON_ITEM_PAYLOAD = "[" + "{\"img\":\"train\",\"uri\":\"myapp://chooseShoeType/shoe1\",\"type\":\"DEEPLINK\"}," + @@ -144,6 +148,28 @@ internal fun provideMockedAutoCarousalTemplate(isFromIntent: Boolean = false): A return CarouselPushTemplate(data) as AutoCarouselPushTemplate } +internal fun provideMockedInputBoxPushTemplateWithAllKeys(isFromIntent: Boolean = false): InputBoxPushTemplate { + val data: NotificationData = if (isFromIntent) { + val mockBundle = MockInputBoxPushTemplateDataProvider.getMockedInputBoxBundleWithAllKeys() + IntentData(mockBundle, null) + } else { + val dataMap = MockInputBoxPushTemplateDataProvider.getMockedInputBoxDataMapWithAllKeys() + MapData(dataMap) + } + return InputBoxPushTemplate(data) +} + +internal fun provideMockedInputBoxPushTemplateWithRequiredData(isFromIntent: Boolean = false): InputBoxPushTemplate { + val data: NotificationData = if (isFromIntent) { + val mockBundle = MockInputBoxPushTemplateDataProvider.getMockedInputBoxBundleWithRequiredData() + IntentData(mockBundle, null) + } else { + val dataMap = MockInputBoxPushTemplateDataProvider.getMockedInputBoxDataMapWithRequiredData() + MapData(dataMap) + } + return InputBoxPushTemplate(data) +} + internal fun provideMockedMultiIconTemplateWithAllKeys(): MultiIconPushTemplate { val data: NotificationData val dataMap = MockMultiIconTemplateDataProvider.getMockedDataMapWithForMultiIcon() diff --git a/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/templates/MockInputBoxPushTemplateDataProvider.kt b/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/templates/MockInputBoxPushTemplateDataProvider.kt new file mode 100644 index 00000000..2d1684b3 --- /dev/null +++ b/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/templates/MockInputBoxPushTemplateDataProvider.kt @@ -0,0 +1,101 @@ +/* + Copyright 2024 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.notificationbuilder.internal.templates + +import android.os.Bundle +import com.adobe.marketing.mobile.notificationbuilder.PushTemplateConstants +import com.adobe.marketing.mobile.notificationbuilder.internal.PushTemplateType + +object MockInputBoxPushTemplateDataProvider { + fun getMockedInputBoxDataMapWithRequiredData(): MutableMap { + return mutableMapOf( + PushTemplateConstants.PushPayloadKeys.TITLE to MOCKED_TITLE, + PushTemplateConstants.PushPayloadKeys.BODY to MOCKED_BODY, + PushTemplateConstants.PushPayloadKeys.VERSION to MOCKED_PAYLOAD_VERSION, + PushTemplateConstants.PushPayloadKeys.INPUT_BOX_RECEIVER_NAME to MOCKED_RECEIVER_NAME, + PushTemplateConstants.PushPayloadKeys.INPUT_BOX_HINT to MOCKED_HINT, + PushTemplateConstants.PushPayloadKeys.INPUT_BOX_FEEDBACK_TEXT to MOCKED_FEEDBACK_TEXT, + PushTemplateConstants.PushPayloadKeys.INPUT_BOX_FEEDBACK_IMAGE to MOCKED_FEEDBACK_IMAGE + ) + } + + fun getMockedInputBoxBundleWithRequiredData(): Bundle { + val mockBundle = Bundle() + mockBundle.putString(PushTemplateConstants.PushPayloadKeys.TITLE, MOCKED_TITLE) + mockBundle.putString(PushTemplateConstants.PushPayloadKeys.BODY, MOCKED_BODY) + mockBundle.putString(PushTemplateConstants.PushPayloadKeys.VERSION, MOCKED_PAYLOAD_VERSION) + mockBundle.putString(PushTemplateConstants.PushPayloadKeys.INPUT_BOX_RECEIVER_NAME, MOCKED_RECEIVER_NAME) + mockBundle.putString(PushTemplateConstants.PushPayloadKeys.INPUT_BOX_HINT, MOCKED_HINT) + mockBundle.putString(PushTemplateConstants.PushPayloadKeys.INPUT_BOX_FEEDBACK_TEXT, MOCKED_FEEDBACK_TEXT) + mockBundle.putString(PushTemplateConstants.PushPayloadKeys.INPUT_BOX_FEEDBACK_IMAGE, MOCKED_FEEDBACK_IMAGE) + return mockBundle + } + + fun getMockedInputBoxDataMapWithAllKeys(): MutableMap { + return mutableMapOf( + PushTemplateConstants.PushPayloadKeys.TAG to MOCKED_TAG, + PushTemplateConstants.PushPayloadKeys.TITLE to MOCKED_TITLE, + PushTemplateConstants.PushPayloadKeys.TEMPLATE_TYPE to PushTemplateType.BASIC.value, + PushTemplateConstants.PushPayloadKeys.BADGE_COUNT to "5", + PushTemplateConstants.PushPayloadKeys.BODY to MOCKED_BASIC_TEMPLATE_BODY, + PushTemplateConstants.PushPayloadKeys.CHANNEL_ID to MOCKED_CHANNEL_ID, + PushTemplateConstants.PushPayloadKeys.EXPANDED_BODY_TEXT to MOCKED_BASIC_TEMPLATE_BODY_EXPANDED, + PushTemplateConstants.PushPayloadKeys.BODY_TEXT_COLOR to "FFD966", + PushTemplateConstants.PushPayloadKeys.IMAGE_URL to MOCKED_IMAGE_URI, + PushTemplateConstants.PushPayloadKeys.LARGE_ICON to MOCKED_LARGE_ICON, + PushTemplateConstants.PushPayloadKeys.BACKGROUND_COLOR to "FFD966", + PushTemplateConstants.PushPayloadKeys.PRIORITY to MOCKED_PRIORITY, + PushTemplateConstants.PushPayloadKeys.VISIBILITY to MOCKED_VISIBILITY, + PushTemplateConstants.PushPayloadKeys.SOUND to "bell", + PushTemplateConstants.PushPayloadKeys.SMALL_ICON to MOCKED_SMALL_ICON, + PushTemplateConstants.PushPayloadKeys.TITLE_TEXT_COLOR to "FFD966", + PushTemplateConstants.PushPayloadKeys.TICKER to MOCKED_TICKER, + PushTemplateConstants.PushPayloadKeys.VERSION to MOCKED_PAYLOAD_VERSION, + PushTemplateConstants.PushPayloadKeys.STICKY to "true", + PushTemplateConstants.PushPayloadKeys.INPUT_BOX_RECEIVER_NAME to MOCKED_RECEIVER_NAME, + PushTemplateConstants.PushPayloadKeys.INPUT_BOX_HINT to MOCKED_HINT, + PushTemplateConstants.PushPayloadKeys.INPUT_BOX_FEEDBACK_TEXT to MOCKED_FEEDBACK_TEXT, + PushTemplateConstants.PushPayloadKeys.INPUT_BOX_FEEDBACK_IMAGE to MOCKED_FEEDBACK_IMAGE + ) + } + + fun getMockedInputBoxBundleWithAllKeys(): Bundle { + val mockBundle = Bundle() + mockBundle.putString(PushTemplateConstants.PushPayloadKeys.VERSION, MOCKED_PAYLOAD_VERSION) + mockBundle.putString(PushTemplateConstants.PushPayloadKeys.TITLE, MOCKED_TITLE) + mockBundle.putString(PushTemplateConstants.PushPayloadKeys.BODY, MOCKED_BODY) + mockBundle.putString(PushTemplateConstants.PushPayloadKeys.TAG, MOCKED_TAG) + mockBundle.putString(PushTemplateConstants.PushPayloadKeys.TITLE, MOCKED_TITLE) + mockBundle.putString(PushTemplateConstants.PushPayloadKeys.TEMPLATE_TYPE, PushTemplateType.BASIC.value) + mockBundle.putString(PushTemplateConstants.PushPayloadKeys.BADGE_COUNT, "5") + mockBundle.putString(PushTemplateConstants.PushPayloadKeys.BODY, MOCKED_BASIC_TEMPLATE_BODY) + mockBundle.putString(PushTemplateConstants.PushPayloadKeys.CHANNEL_ID, "2024") + mockBundle.putString(PushTemplateConstants.PushPayloadKeys.EXPANDED_BODY_TEXT, MOCKED_BASIC_TEMPLATE_BODY_EXPANDED) + mockBundle.putString(PushTemplateConstants.PushPayloadKeys.BODY_TEXT_COLOR, "FFD966") + mockBundle.putString(PushTemplateConstants.PushPayloadKeys.IMAGE_URL, MOCKED_IMAGE_URI) + mockBundle.putString(PushTemplateConstants.PushPayloadKeys.LARGE_ICON, MOCKED_LARGE_ICON) + mockBundle.putString(PushTemplateConstants.PushPayloadKeys.BACKGROUND_COLOR, "FFD966") + mockBundle.putString(PushTemplateConstants.PushPayloadKeys.PRIORITY, (MOCKED_PRIORITY)) + mockBundle.putString(PushTemplateConstants.PushPayloadKeys.VISIBILITY, MOCKED_VISIBILITY) + mockBundle.putString(PushTemplateConstants.PushPayloadKeys.SOUND, "bell") + mockBundle.putString(PushTemplateConstants.PushPayloadKeys.SMALL_ICON, MOCKED_SMALL_ICON) + mockBundle.putString(PushTemplateConstants.PushPayloadKeys.TITLE_TEXT_COLOR, "FFD966") + mockBundle.putString(PushTemplateConstants.PushPayloadKeys.TICKER, MOCKED_TICKER) + mockBundle.putString(PushTemplateConstants.PushPayloadKeys.VERSION, MOCKED_PAYLOAD_VERSION) + mockBundle.putString(PushTemplateConstants.PushPayloadKeys.STICKY, "true") + mockBundle.putString(PushTemplateConstants.PushPayloadKeys.INPUT_BOX_RECEIVER_NAME, MOCKED_RECEIVER_NAME) + mockBundle.putString(PushTemplateConstants.PushPayloadKeys.INPUT_BOX_HINT, MOCKED_HINT) + mockBundle.putString(PushTemplateConstants.PushPayloadKeys.INPUT_BOX_FEEDBACK_TEXT, MOCKED_FEEDBACK_TEXT) + mockBundle.putString(PushTemplateConstants.PushPayloadKeys.INPUT_BOX_FEEDBACK_IMAGE, MOCKED_FEEDBACK_IMAGE) + return mockBundle + } +} From bf8d1a0d356c44dce7bbcd3185f07f4986241582 Mon Sep 17 00:00:00 2001 From: Ishita Gambhir Date: Fri, 21 Jun 2024 00:09:56 +0530 Subject: [PATCH 140/159] MOB-21188: [Android] Add unit tests for ProductCatalogNotificationBuilder (#45) * add unit tests for ProductCatalogNotificationBuilderTest * address PR comments --- .../ProductCatalogNotificationBuilderTest.kt | 208 ++++++++++++++++++ .../internal/templates/MockDataUtils.kt | 11 + .../MockProductCatalogTemplateDataProvider.kt | 56 +++++ 3 files changed, 275 insertions(+) create mode 100644 code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/ProductCatalogNotificationBuilderTest.kt create mode 100644 code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/templates/MockProductCatalogTemplateDataProvider.kt diff --git a/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/ProductCatalogNotificationBuilderTest.kt b/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/ProductCatalogNotificationBuilderTest.kt new file mode 100644 index 00000000..3921a5fe --- /dev/null +++ b/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/ProductCatalogNotificationBuilderTest.kt @@ -0,0 +1,208 @@ +/* + Copyright 2024 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.notificationbuilder.internal.builders + +import android.app.Activity +import android.content.BroadcastReceiver +import android.content.Context +import android.graphics.Bitmap +import androidx.core.app.NotificationCompat +import com.adobe.marketing.mobile.notificationbuilder.NotificationConstructionFailedException +import com.adobe.marketing.mobile.notificationbuilder.PushTemplateConstants +import com.adobe.marketing.mobile.notificationbuilder.PushTemplateConstants.DefaultValues.PRODUCT_CATALOG_VERTICAL_LAYOUT +import com.adobe.marketing.mobile.notificationbuilder.R +import com.adobe.marketing.mobile.notificationbuilder.internal.PushTemplateImageUtils +import com.adobe.marketing.mobile.notificationbuilder.internal.PushTemplateImageUtils.cacheImages +import com.adobe.marketing.mobile.notificationbuilder.internal.PushTemplateImageUtils.getCachedImage +import com.adobe.marketing.mobile.notificationbuilder.internal.templates.MockProductCatalogTemplateDataProvider.getMockedMapWithProductCatalogData +import com.adobe.marketing.mobile.notificationbuilder.internal.templates.ProductCatalogPushTemplate +import com.adobe.marketing.mobile.notificationbuilder.internal.templates.provideMockedProductCatalogTemplate +import com.adobe.marketing.mobile.notificationbuilder.internal.templates.replaceValueInMap +import com.adobe.marketing.mobile.notificationbuilder.internal.util.MapData +import io.mockk.clearAllMocks +import io.mockk.every +import io.mockk.mockkClass +import io.mockk.mockkObject +import org.junit.After +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mock +import org.mockito.MockitoAnnotations +import org.robolectric.RobolectricTestRunner +import org.robolectric.RuntimeEnvironment +import org.robolectric.annotation.Config +import kotlin.test.assertEquals +import kotlin.test.assertFailsWith + +@RunWith(RobolectricTestRunner::class) +@Config(sdk = [31]) +class ProductCatalogNotificationBuilderTest { + @Mock + private lateinit var context: Context + private lateinit var trackerActivityClass: Class + private lateinit var broadcastReceiverClass: Class + + @Before + fun setUp() { + MockitoAnnotations.openMocks(this) + + context = RuntimeEnvironment.getApplication() + trackerActivityClass = mockkClass(Activity::class, relaxed = true).javaClass + broadcastReceiverClass = mockkClass(BroadcastReceiver::class, relaxed = true).javaClass + mockkObject(PushTemplateImageUtils) + } + + @After + fun cleanup() { + clearAllMocks() + } + + @Test + fun `construct should throw NotificationConstructionFailedException if downloaded image count does not match catalog image count`() { + val pushTemplate = provideMockedProductCatalogTemplate() + + assertFailsWith( + exceptionClass = NotificationConstructionFailedException::class, + message = "Failed to download all images for the product catalog notification.", + block = { + ProductCatalogNotificationBuilder.construct(context, pushTemplate, trackerActivityClass, broadcastReceiverClass) + } + ) + } + + @Test + fun `construct should throw IllegalArgumentException if downloaded image count is more than 3`() { + val dataMap = getMockedMapWithProductCatalogData() + dataMap.replaceValueInMap( + PushTemplateConstants.PushPayloadKeys.CATALOG_ITEMS, + "[" + + "{\"title\":\"title1\",\"body\":\"body1\",\"img\":\"img1\",\"price\":\"price1\",\"uri\":\"uri1\"}," + + "{\"title\":\"title2\",\"body\":\"body2\",\"img\":\"img2\",\"price\":\"price2\",\"uri\":\"uri2\"}," + + "{\"title\":\"title3\",\"body\":\"body3\",\"img\":\"img3\",\"price\":\"price3\",\"uri\":\"uri3\"}," + + "{\"title\":\"title4\",\"body\":\"body4\",\"img\":\"img4\",\"price\":\"price4\",\"uri\":\"uri4\"}" + + "]" + ) // 4 items in the catalog + + assertFailsWith( + exceptionClass = IllegalArgumentException::class, + message = "3 catalog items are required for a Product Catalog notification.", + block = { + val pushTemplate = ProductCatalogPushTemplate(MapData(dataMap)) + val cachedItem = mockkClass(Bitmap::class) + + // product catalog template requires 3 catalog items + every { cacheImages(any()) } answers { 4 } + every { getCachedImage(any()) } answers { cachedItem } + ProductCatalogNotificationBuilder.construct(context, pushTemplate, trackerActivityClass, broadcastReceiverClass) + } + ) + } + + @Test + fun `construct should throw IllegalArgumentException if downloaded image count is less than 3`() { + val dataMap = getMockedMapWithProductCatalogData() + dataMap.replaceValueInMap( + PushTemplateConstants.PushPayloadKeys.CATALOG_ITEMS, + "[" + + "{\"title\":\"title1\",\"body\":\"body1\",\"img\":\"img1\",\"price\":\"price1\",\"uri\":\"uri1\"}," + + "{\"title\":\"title2\",\"body\":\"body2\",\"img\":\"img2\",\"price\":\"price2\",\"uri\":\"uri2\"}," + + "]" + ) // 2 items in the catalog + + assertFailsWith( + exceptionClass = IllegalArgumentException::class, + message = "3 catalog items are required for a Product Catalog notification.", + block = { + val pushTemplate = ProductCatalogPushTemplate(MapData(dataMap)) + val cachedItem = mockkClass(Bitmap::class) + + // product catalog template requires 3 catalog items + every { cacheImages(any()) } answers { 2 } + every { getCachedImage(any()) } answers { cachedItem } + ProductCatalogNotificationBuilder.construct(context, pushTemplate, trackerActivityClass, broadcastReceiverClass) + } + ) + } + + @Test + fun `construct should return a NotificationCompat Builder if download image count matches catalog image count`() { + val pushTemplate = provideMockedProductCatalogTemplate() + val cachedItem = mockkClass(Bitmap::class) + + // product catalog template requires 3 catalog items + every { cacheImages(any()) } answers { 3 } + every { getCachedImage(any()) } answers { cachedItem } + val notificationBuilder = ProductCatalogNotificationBuilder.construct(context, pushTemplate, trackerActivityClass, broadcastReceiverClass) + + assertEquals(R.layout.push_template_horizontal_catalog, notificationBuilder.bigContentView.layoutId) + assertEquals(NotificationCompat.Builder::class.java, notificationBuilder::class.java) + } + + @Test + fun `construct should build a notification with a vertical layout if vertical layout is provided`() { + val dataMap = getMockedMapWithProductCatalogData() + dataMap.replaceValueInMap(PushTemplateConstants.PushPayloadKeys.CATALOG_LAYOUT, PRODUCT_CATALOG_VERTICAL_LAYOUT) + val pushTemplate = ProductCatalogPushTemplate(MapData(dataMap)) + val cachedItem = mockkClass(Bitmap::class) + + // product catalog template requires 3 catalog items + every { cacheImages(any()) } answers { 3 } + every { getCachedImage(any()) } answers { cachedItem } + + val notificationBuilder = ProductCatalogNotificationBuilder.construct(context, pushTemplate, trackerActivityClass, broadcastReceiverClass) + assertEquals(R.layout.push_tempate_vertical_catalog, notificationBuilder.bigContentView.layoutId) + assertEquals(NotificationCompat.Builder::class.java, notificationBuilder::class.java) + } + + @Test + fun `construct should return a valid NotificationCompat Builder if Broadcast Receiver is not provided`() { + val pushTemplate = provideMockedProductCatalogTemplate() + val cachedItem = mockkClass(Bitmap::class) + + // product catalog template requires 3 catalog items + every { cacheImages(any()) } answers { 3 } + every { getCachedImage(any()) } answers { cachedItem } + + val notificationBuilder = ProductCatalogNotificationBuilder.construct(context, pushTemplate, trackerActivityClass, null) + assertEquals(NotificationCompat.Builder::class.java, notificationBuilder::class.java) + } + + @Test + fun `construct should throw NotificationConstructionFailedException if catalog thumbnail is not found`() { + val pushTemplate = provideMockedProductCatalogTemplate() + every { cacheImages(any()) } answers { 3 } + + assertFailsWith( + exceptionClass = NotificationConstructionFailedException::class, + message = "No image found for catalog item thumbnail.", + block = { + ProductCatalogNotificationBuilder.construct(context, pushTemplate, trackerActivityClass, broadcastReceiverClass) + } + ) + } + + @Test + fun `construct should return a NotificationCompat Builder if Tag is provided in Push Template`() { + val dataMap = getMockedMapWithProductCatalogData() + dataMap[PushTemplateConstants.PushPayloadKeys.TAG] = "tag" + val pushTemplate = ProductCatalogPushTemplate(MapData(dataMap)) + val cachedItem = mockkClass(Bitmap::class) + + // product catalog template requires 3 catalog items + every { cacheImages(any()) } answers { 3 } + every { getCachedImage(any()) } answers { cachedItem } + + val notificationBuilder = ProductCatalogNotificationBuilder.construct(context, pushTemplate, trackerActivityClass, broadcastReceiverClass) + assertEquals(NotificationCompat.Builder::class.java, notificationBuilder::class.java) + } +} diff --git a/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/templates/MockDataUtils.kt b/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/templates/MockDataUtils.kt index c4f42a81..ef276200 100644 --- a/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/templates/MockDataUtils.kt +++ b/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/templates/MockDataUtils.kt @@ -176,3 +176,14 @@ internal fun provideMockedMultiIconTemplateWithAllKeys(): MultiIconPushTemplate data = MapData(dataMap) return MultiIconPushTemplate(data) } + +internal fun provideMockedProductCatalogTemplate(isFromIntent: Boolean = false): ProductCatalogPushTemplate { + val data: NotificationData + if (isFromIntent) { + val mockBundle = MockProductCatalogTemplateDataProvider.getMockedBundleWithProductCatalogData() + data = IntentData(mockBundle, null) + } else { + data = MapData(MockProductCatalogTemplateDataProvider.getMockedMapWithProductCatalogData()) + } + return ProductCatalogPushTemplate(data) +} diff --git a/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/templates/MockProductCatalogTemplateDataProvider.kt b/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/templates/MockProductCatalogTemplateDataProvider.kt new file mode 100644 index 00000000..b67e057b --- /dev/null +++ b/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/templates/MockProductCatalogTemplateDataProvider.kt @@ -0,0 +1,56 @@ +/* + Copyright 2024 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.notificationbuilder.internal.templates + +import android.os.Bundle +import com.adobe.marketing.mobile.notificationbuilder.PushTemplateConstants + +object MockProductCatalogTemplateDataProvider { + fun getMockedMapWithProductCatalogData(): MutableMap { + return mutableMapOf( + PushTemplateConstants.PushPayloadKeys.TITLE to MOCKED_TITLE, + PushTemplateConstants.PushPayloadKeys.BODY to MOCKED_BODY, + PushTemplateConstants.PushPayloadKeys.VERSION to MOCKED_PAYLOAD_VERSION, + PushTemplateConstants.PushPayloadKeys.CATALOG_CTA_BUTTON_TEXT to "ctaButtonText", + PushTemplateConstants.PushPayloadKeys.CATALOG_CTA_BUTTON_COLOR to "ctaButtonColor", + PushTemplateConstants.PushPayloadKeys.CATALOG_CTA_BUTTON_TEXT_COLOR to "ctaButtonTextColor", + PushTemplateConstants.PushPayloadKeys.CATALOG_CTA_BUTTON_URI to "ctaButtonUri", + PushTemplateConstants.PushPayloadKeys.CATALOG_LAYOUT to "horizontal", + PushTemplateConstants.PushPayloadKeys.CATALOG_ITEMS to "[" + + "{\"title\":\"title1\",\"body\":\"body1\",\"img\":\"https://images.pexels.com/photos/260024/pexels-photo-260024.jpeg?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=2\",\"price\":\"price1\",\"uri\":\"uri1\"}," + + "{\"title\":\"title2\",\"body\":\"body2\",\"img\":\"https://images.pexels.com/photos/260024/pexels-photo-260024.jpeg?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=2\",\"price\":\"price2\",\"uri\":\"uri2\"}," + + "{\"title\":\"title3\",\"body\":\"body3\",\"img\":\"https://images.pexels.com/photos/260024/pexels-photo-260024.jpeg?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=2\",\"price\":\"price3\",\"uri\":\"uri3\"}" + + "]" + ) + } + + fun getMockedBundleWithProductCatalogData(): Bundle { + val mockBundle = Bundle() + mockBundle.putString(PushTemplateConstants.PushPayloadKeys.TITLE, MOCKED_TITLE) + mockBundle.putString(PushTemplateConstants.PushPayloadKeys.BODY, MOCKED_BODY) + mockBundle.putString(PushTemplateConstants.PushPayloadKeys.VERSION, MOCKED_PAYLOAD_VERSION) + mockBundle.putString(PushTemplateConstants.PushPayloadKeys.CATALOG_CTA_BUTTON_TEXT, "ctaButtonText") + mockBundle.putString(PushTemplateConstants.PushPayloadKeys.CATALOG_CTA_BUTTON_COLOR, "ctaButtonColor") + mockBundle.putString(PushTemplateConstants.PushPayloadKeys.CATALOG_CTA_BUTTON_TEXT_COLOR, "ctaButtonTextColor") + mockBundle.putString(PushTemplateConstants.PushPayloadKeys.CATALOG_CTA_BUTTON_URI, "ctaButtonUri") + mockBundle.putString(PushTemplateConstants.PushPayloadKeys.CATALOG_LAYOUT, "horizontal") + mockBundle.putString( + PushTemplateConstants.PushPayloadKeys.CATALOG_ITEMS, + "[" + + "{\"title\":\"title1\",\"body\":\"body1\",\"img\":\"https://images.pexels.com/photos/260024/pexels-photo-260024.jpeg?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=2\",\"price\":\"price1\",\"uri\":\"uri1\"}," + + "{\"title\":\"title2\",\"body\":\"body2\",\"img\":\"https://images.pexels.com/photos/260024/pexels-photo-260024.jpeg?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=2\",\"price\":\"price2\",\"uri\":\"uri2\"}," + + "{\"title\":\"title3\",\"body\":\"body3\",\"img\":\"https://images.pexels.com/photos/260024/pexels-photo-260024.jpeg?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=2\",\"price\":\"price3\",\"uri\":\"uri3\"}" + + "]" + ) + return mockBundle + } +} From 3dfe28fbe1dcf22b6fb890de573824febd135923 Mon Sep 17 00:00:00 2001 From: siddique-adobe Date: Fri, 21 Jun 2024 00:13:18 +0530 Subject: [PATCH 141/159] [MOB-20956] Adding unit tests for AutoCarouselNotificationBuilder (#41) * [MOB-20956] Initial tests for AutoCarouselNotificationBuilder class [MOB-20956] Initial tests for AutoCarouselNotificationBuilder class * [MOB-20956] PR comments resolved --- .../AutoCarouselNotificationBuilder.kt | 4 +- .../AutoCarouselNotificationBuilderTest.kt | 184 ++++++++++++++++++ 2 files changed, 187 insertions(+), 1 deletion(-) create mode 100644 code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/AutoCarouselNotificationBuilderTest.kt diff --git a/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/AutoCarouselNotificationBuilder.kt b/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/AutoCarouselNotificationBuilder.kt index a3ec9a50..2b6aabf1 100644 --- a/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/AutoCarouselNotificationBuilder.kt +++ b/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/AutoCarouselNotificationBuilder.kt @@ -17,6 +17,7 @@ import android.content.BroadcastReceiver import android.content.Context import android.graphics.Bitmap import android.widget.RemoteViews +import androidx.annotation.VisibleForTesting import androidx.core.app.NotificationCompat import com.adobe.marketing.mobile.notificationbuilder.PushTemplateConstants import com.adobe.marketing.mobile.notificationbuilder.PushTemplateConstants.LOG_TAG @@ -102,7 +103,8 @@ internal object AutoCarouselNotificationBuilder { * @param packageName the `String` name of the application package used to locate the layout resources * @return a [List] of downloaded image URIs */ - private fun populateAutoCarouselImages( + @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) + internal fun populateAutoCarouselImages( context: Context, trackerActivityClass: Class?, expandedLayout: RemoteViews, diff --git a/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/AutoCarouselNotificationBuilderTest.kt b/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/AutoCarouselNotificationBuilderTest.kt new file mode 100644 index 00000000..c611eb49 --- /dev/null +++ b/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/AutoCarouselNotificationBuilderTest.kt @@ -0,0 +1,184 @@ +/* + Copyright 2024 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.notificationbuilder.internal.builders + +import android.app.Activity +import android.content.BroadcastReceiver +import android.content.Context +import android.graphics.Bitmap +import android.widget.RemoteViews +import com.adobe.marketing.mobile.notificationbuilder.internal.PendingIntentUtils +import com.adobe.marketing.mobile.notificationbuilder.internal.PushTemplateImageUtils +import com.adobe.marketing.mobile.notificationbuilder.internal.PushTemplateImageUtils.cacheImages +import com.adobe.marketing.mobile.notificationbuilder.internal.PushTemplateImageUtils.getCachedImage +import com.adobe.marketing.mobile.notificationbuilder.internal.extensions.setRemoteViewClickAction +import com.adobe.marketing.mobile.notificationbuilder.internal.templates.AutoCarouselPushTemplate +import com.adobe.marketing.mobile.notificationbuilder.internal.templates.provideMockedAutoCarousalTemplate +import io.mockk.Runs +import io.mockk.every +import io.mockk.just +import io.mockk.mockkClass +import io.mockk.mockkConstructor +import io.mockk.mockkObject +import io.mockk.unmockkAll +import io.mockk.verify +import org.junit.After +import org.junit.Assert.assertFalse +import org.junit.Assert.assertNotNull +import org.junit.Assert.assertTrue +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.robolectric.RobolectricTestRunner +import org.robolectric.RuntimeEnvironment +import org.robolectric.annotation.Config + +@RunWith(RobolectricTestRunner::class) +@Config(sdk = [31]) +class AutoCarouselNotificationBuilderTest { + + private lateinit var context: Context + private lateinit var expandedLayout: RemoteViews + private lateinit var trackerActivityClass: Class + private lateinit var broadcastReceiverClass: Class + private lateinit var autoCarouselPushTemplate: AutoCarouselPushTemplate + + @Before + fun setUp() { + context = RuntimeEnvironment.getApplication() + expandedLayout = mockkClass(RemoteViews::class) + trackerActivityClass = mockkClass(Activity::class, relaxed = true).javaClass + broadcastReceiverClass = mockkClass(BroadcastReceiver::class, relaxed = true).javaClass + autoCarouselPushTemplate = provideMockedAutoCarousalTemplate(false) + mockkObject(BasicNotificationBuilder) + mockkObject(PendingIntentUtils) + mockkObject(PushTemplateImageUtils) + mockkConstructor(RemoteViews::class) + } + + @Test + fun `construct returns NotificationCompat Builder for valid inputs`() { + val result = AutoCarouselNotificationBuilder.construct( + context, + autoCarouselPushTemplate, + trackerActivityClass, + broadcastReceiverClass + ) + assertNotNull(result) + } + + @Test + fun `construct returns BasicNotificationBuilder if less than 3 images were downloaded`() { + every { cacheImages(any()) } answers { 2 } + AutoCarouselNotificationBuilder.construct( + context, + autoCarouselPushTemplate, + trackerActivityClass, + broadcastReceiverClass + ) + verify(exactly = 1) { + BasicNotificationBuilder.fallbackToBasicNotification( + context, + trackerActivityClass, + broadcastReceiverClass, + autoCarouselPushTemplate.data + ) + } + } + + @Test + fun `construct does not fallback to BasicNotificationBuilder if greater than or equal to 3 images were downloaded`() { + every { cacheImages(any()) } answers { 3 } + AutoCarouselNotificationBuilder.construct( + context, + autoCarouselPushTemplate, + trackerActivityClass, + broadcastReceiverClass + ) + verify(exactly = 0) { + BasicNotificationBuilder.fallbackToBasicNotification( + context, + trackerActivityClass, + broadcastReceiverClass, + autoCarouselPushTemplate.data + ) + } + } + + @Test + fun `populateAutoCarouselImages returns list with only cached images with tracker activity`() { + val cachedItem = mockkClass(Bitmap::class) + every { getCachedImage(any()) } answers { cachedItem } + every { anyConstructed().setImageViewBitmap(any(), any()) } just Runs + every { anyConstructed().setTextViewText(any(), any()) } just Runs + every { anyConstructed().setRemoteViewClickAction(context, trackerActivityClass, any(), any(), null, any()) } just Runs + every { expandedLayout.addView(any(), any()) } just Runs + val imagesList = AutoCarouselNotificationBuilder.populateAutoCarouselImages( + context, + trackerActivityClass, + expandedLayout, + autoCarouselPushTemplate, + autoCarouselPushTemplate.carouselItems, + context.packageName + ) + assertFalse(imagesList.isEmpty()) + verify(exactly = imagesList.size) { getCachedImage(any()) } + verify(exactly = imagesList.size) { anyConstructed().setImageViewBitmap(any(), any()) } + verify(exactly = imagesList.size) { anyConstructed().setRemoteViewClickAction(context, trackerActivityClass, any(), any(), null, any()) } + verify(exactly = imagesList.size) { anyConstructed().setTextViewText(any(), any()) } + } + + @Test + fun `populateAutoCarouselImages returns list with only cached images with no tracker activity`() { + val cachedItem = mockkClass(Bitmap::class) + every { getCachedImage(any()) } answers { cachedItem } + every { anyConstructed().setImageViewBitmap(any(), any()) } just Runs + every { anyConstructed().setTextViewText(any(), any()) } just Runs + every { anyConstructed().setRemoteViewClickAction(context, trackerActivityClass, any(), any(), null, any()) } just Runs + every { expandedLayout.addView(any(), any()) } just Runs + val imagesList = AutoCarouselNotificationBuilder.populateAutoCarouselImages( + context, + null, + expandedLayout, + autoCarouselPushTemplate, + autoCarouselPushTemplate.carouselItems, + context.packageName + ) + assertFalse(imagesList.isEmpty()) + verify(exactly = imagesList.size) { getCachedImage(any()) } + verify(exactly = imagesList.size) { anyConstructed().setImageViewBitmap(any(), any()) } + verify(exactly = 0) { anyConstructed().setRemoteViewClickAction(context, trackerActivityClass, any(), any(), null, any()) } + verify(exactly = imagesList.size) { anyConstructed().setTextViewText(any(), any()) } + } + + @Test + fun `populateAutoCarouselImages returns empty list when no images are cached`() { + every { getCachedImage(any()) } answers { null } + val imagesList = AutoCarouselNotificationBuilder.populateAutoCarouselImages( + context, + trackerActivityClass, + expandedLayout, + autoCarouselPushTemplate, + autoCarouselPushTemplate.carouselItems, + context.packageName + ) + assertTrue(imagesList.isEmpty()) + verify(exactly = 0) { anyConstructed().setImageViewBitmap(any(), any()) } + verify(exactly = 0) { anyConstructed().setRemoteViewClickAction(context, trackerActivityClass, any(), any(), null, any()) } + verify(exactly = 0) { anyConstructed().setTextViewText(any(), any()) } + } + + @After + fun teardown() { + unmockkAll() + } +} From 22922d98ad85bb4b65fa9fb455ed304624a66f3a Mon Sep 17 00:00:00 2001 From: Prattham Arora <55047418+PratthamArora@users.noreply.github.com> Date: Fri, 21 Jun 2024 00:56:14 +0530 Subject: [PATCH 142/159] Added unit tests for Zero bezel Notification Builder (#46) * setup test class for ZeroBezelNotificationBuilder * added test cases for ZeroBezelNotificationBuilder * added test cases to verify notifcation data * code clean up --- .../ZeroBezelNotificationBuilderTest.kt | 234 ++++++++++++++++++ .../MockAEPPushTemplateDataProvider.kt | 4 +- 2 files changed, 237 insertions(+), 1 deletion(-) create mode 100644 code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/ZeroBezelNotificationBuilderTest.kt diff --git a/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/ZeroBezelNotificationBuilderTest.kt b/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/ZeroBezelNotificationBuilderTest.kt new file mode 100644 index 00000000..f110eea0 --- /dev/null +++ b/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/ZeroBezelNotificationBuilderTest.kt @@ -0,0 +1,234 @@ +/* + Copyright 2024 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.notificationbuilder.internal.builders + +import android.app.Activity +import android.app.Notification +import android.content.Context +import android.graphics.Bitmap +import android.os.Bundle +import android.view.View +import android.widget.RemoteViews +import com.adobe.marketing.mobile.notificationbuilder.PushTemplateConstants +import com.adobe.marketing.mobile.notificationbuilder.internal.PushTemplateImageUtils +import com.adobe.marketing.mobile.notificationbuilder.internal.PushTemplateImageUtils.cacheImages +import com.adobe.marketing.mobile.notificationbuilder.internal.PushTemplateImageUtils.getCachedImage +import com.adobe.marketing.mobile.notificationbuilder.internal.templates.MockAEPPushTemplateDataProvider +import com.adobe.marketing.mobile.notificationbuilder.internal.templates.ZeroBezelPushTemplate +import com.adobe.marketing.mobile.notificationbuilder.internal.templates.replaceValueInMap +import com.adobe.marketing.mobile.notificationbuilder.internal.util.IntentData +import com.adobe.marketing.mobile.notificationbuilder.internal.util.MapData +import io.mockk.Runs +import io.mockk.every +import io.mockk.just +import io.mockk.mockk +import io.mockk.mockkConstructor +import io.mockk.mockkObject +import io.mockk.verify +import junit.framework.TestCase.assertEquals +import junit.framework.TestCase.assertNotNull +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.robolectric.RobolectricTestRunner +import org.robolectric.RuntimeEnvironment +import org.robolectric.Shadows +import org.robolectric.annotation.Config + +@RunWith(RobolectricTestRunner::class) +@Config(sdk = [31]) +class ZeroBezelNotificationBuilderTest { + + private lateinit var context: Context + private lateinit var dataMap: MutableMap + private lateinit var mockBitmap: Bitmap + private lateinit var trackerActivityClass: Class + private lateinit var mockBundle: Bundle + + @Before + fun setup() { + context = RuntimeEnvironment.getApplication() + dataMap = MockAEPPushTemplateDataProvider.getMockedAEPDataMapWithAllKeys() + mockkObject(PushTemplateImageUtils) + mockkConstructor(RemoteViews::class) + mockBitmap = mockk() + trackerActivityClass = DummyActivity::class.java + mockBundle = MockAEPPushTemplateDataProvider.getMockedAEPBundleWithAllKeys() + every { getCachedImage(any()) } answers { mockBitmap } + } + + @Test + fun `verify construct with image downloaded and collapsedStyle is img`() { + val pushTemplate = ZeroBezelPushTemplate(MapData(dataMap)) + every { cacheImages(any()) } answers { 2 } + every { anyConstructed().setImageViewBitmap(any(), mockBitmap) } just Runs + + val notification = ZeroBezelNotificationBuilder.construct( + context, + pushTemplate, + trackerActivityClass + ).build() + + assertEquals(pushTemplate.channelId, notification.channelId) + verifyNotificationDataFields(notification, pushTemplate) + verify { cacheImages(listOf(pushTemplate.imageUrl)) } + verify { getCachedImage(pushTemplate.imageUrl) } + verify { anyConstructed().setImageViewBitmap(any(), mockBitmap) } + } + + @Test + fun `verify construct with image downloaded and collapsedStyle is txt`() { + dataMap.replaceValueInMap( + Pair( + PushTemplateConstants.PushPayloadKeys.ZERO_BEZEL_COLLAPSED_STYLE, + "txt" + ) + ) + val pushTemplate = ZeroBezelPushTemplate(MapData(dataMap)) + + every { cacheImages(any()) } answers { 2 } + every { anyConstructed().setImageViewBitmap(any(), mockBitmap) } just Runs + every { anyConstructed().setViewVisibility(any(), View.GONE) } just Runs + + val notification = ZeroBezelNotificationBuilder.construct( + context, + pushTemplate, + trackerActivityClass + ).build() + + assertEquals(pushTemplate.channelId, notification.channelId) + verifyNotificationDataFields(notification, pushTemplate) + verify { cacheImages(listOf(pushTemplate.imageUrl)) } + verify { getCachedImage(pushTemplate.imageUrl) } + verify { anyConstructed().setImageViewBitmap(any(), mockBitmap) } + verify { anyConstructed().setViewVisibility(any(), View.GONE) } + } + + @Test + fun `verify construct with no image downloaded`() { + val pushTemplate = ZeroBezelPushTemplate(MapData(dataMap)) + + every { cacheImages(listOf(pushTemplate.imageUrl)) } returns 0 + every { anyConstructed().setViewVisibility(any(), View.GONE) } just Runs + + val notification = ZeroBezelNotificationBuilder.construct( + context, + pushTemplate, + trackerActivityClass + ).build() + + assertEquals(pushTemplate.channelId, notification.channelId) + verifyNotificationDataFields(notification, pushTemplate) + verify { cacheImages(listOf(pushTemplate.imageUrl)) } + verify { anyConstructed().setViewVisibility(any(), View.GONE) } + } + + @Test + fun `verify construct with image downloaded and collapsedStyle is img for IntentData`() { + val pushTemplate = ZeroBezelPushTemplate(IntentData(mockBundle, null)) + every { cacheImages(any()) } answers { 2 } + every { anyConstructed().setImageViewBitmap(any(), mockBitmap) } just Runs + + val notification = ZeroBezelNotificationBuilder.construct( + context, + pushTemplate, + trackerActivityClass + ).build() + + assertEquals( + PushTemplateConstants.DefaultValues.SILENT_NOTIFICATION_CHANNEL_ID, + notification.channelId + ) + verifyNotificationDataFields(notification, pushTemplate) + verify { cacheImages(listOf(pushTemplate.imageUrl)) } + verify { getCachedImage(pushTemplate.imageUrl) } + verify { anyConstructed().setImageViewBitmap(any(), mockBitmap) } + } + + @Test + fun `verify construct with image downloaded and collapsedStyle is txt for IntentData`() { + mockBundle.putString( + PushTemplateConstants.PushPayloadKeys.ZERO_BEZEL_COLLAPSED_STYLE, + "txt" + ) + + val pushTemplate = ZeroBezelPushTemplate(IntentData(mockBundle, null)) + every { cacheImages(any()) } answers { 2 } + every { anyConstructed().setImageViewBitmap(any(), mockBitmap) } just Runs + every { anyConstructed().setViewVisibility(any(), View.GONE) } just Runs + + val notification = ZeroBezelNotificationBuilder.construct( + context, + pushTemplate, + trackerActivityClass + ).build() + + assertEquals( + PushTemplateConstants.DefaultValues.SILENT_NOTIFICATION_CHANNEL_ID, + notification.channelId + ) + verifyNotificationDataFields(notification, pushTemplate) + verify { cacheImages(listOf(pushTemplate.imageUrl)) } + verify { getCachedImage(pushTemplate.imageUrl) } + verify { anyConstructed().setImageViewBitmap(any(), mockBitmap) } + verify { anyConstructed().setViewVisibility(any(), View.GONE) } + } + + @Test + fun `verify construct with no image downloaded for IntentData`() { + val pushTemplate = ZeroBezelPushTemplate(IntentData(mockBundle, null)) + every { cacheImages(listOf(pushTemplate.imageUrl)) } returns 0 + every { anyConstructed().setViewVisibility(any(), View.GONE) } just Runs + + val notification = ZeroBezelNotificationBuilder.construct( + context, + pushTemplate, + trackerActivityClass + ).build() + + assertEquals( + PushTemplateConstants.DefaultValues.SILENT_NOTIFICATION_CHANNEL_ID, + notification.channelId + ) + verifyNotificationDataFields(notification, pushTemplate) + verify { cacheImages(listOf(pushTemplate.imageUrl)) } + verify { anyConstructed().setViewVisibility(any(), View.GONE) } + } + + private fun verifyNotificationDataFields( + notification: Notification, + pushTemplate: ZeroBezelPushTemplate + ) { + val pendingIntent = notification.contentIntent + val shadowPendingIntent = Shadows.shadowOf(pendingIntent) + val intent = shadowPendingIntent.savedIntent + + assertEquals(Notification::class.java, notification.javaClass) + assertEquals(pushTemplate.ticker, notification.tickerText) + assertEquals(pushTemplate.badgeCount, notification.number) + assertEquals(pushTemplate.visibility.value, notification.visibility) + assertNotNull(notification.smallIcon) + assertNotNull(notification.deleteIntent) + assertEquals( + pushTemplate.actionUri, + intent.getStringExtra(PushTemplateConstants.TrackingKeys.ACTION_URI) + ) + assertEquals( + pushTemplate.tag, + intent.getStringExtra(PushTemplateConstants.PushPayloadKeys.TAG) + ) + assertEquals( + pushTemplate.isNotificationSticky.toString(), + intent.getStringExtra(PushTemplateConstants.PushPayloadKeys.STICKY) + ) + } +} diff --git a/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/templates/MockAEPPushTemplateDataProvider.kt b/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/templates/MockAEPPushTemplateDataProvider.kt index e1c3f212..8b402809 100644 --- a/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/templates/MockAEPPushTemplateDataProvider.kt +++ b/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/templates/MockAEPPushTemplateDataProvider.kt @@ -60,7 +60,8 @@ object MockAEPPushTemplateDataProvider { PushTemplateConstants.PushPayloadKeys.TITLE_TEXT_COLOR to "FFD966", PushTemplateConstants.PushPayloadKeys.TICKER to MOCKED_TICKER, PushTemplateConstants.PushPayloadKeys.VERSION to MOCKED_PAYLOAD_VERSION, - PushTemplateConstants.PushPayloadKeys.STICKY to "true" + PushTemplateConstants.PushPayloadKeys.STICKY to "true", + PushTemplateConstants.PushPayloadKeys.ZERO_BEZEL_COLLAPSED_STYLE to "img" ) } @@ -94,6 +95,7 @@ object MockAEPPushTemplateDataProvider { mockBundle.putString(PushTemplateConstants.PushPayloadKeys.TICKER, MOCKED_TICKER) mockBundle.putString(PushTemplateConstants.PushPayloadKeys.VERSION, MOCKED_PAYLOAD_VERSION) mockBundle.putString(PushTemplateConstants.PushPayloadKeys.STICKY, "true") + mockBundle.putString(PushTemplateConstants.PushPayloadKeys.ZERO_BEZEL_COLLAPSED_STYLE, "img") return mockBundle } } From 336d1cb3546f10857e1ff694212e9dfb853d143a Mon Sep 17 00:00:00 2001 From: Ryan Morales Date: Thu, 20 Jun 2024 13:35:51 -0700 Subject: [PATCH 143/159] add private NotificationBuilder tests, more cleanup --- .../internal/NotificationBuilderTests.kt | 184 +++++++++++++++--- .../builders/BasicNotificationBuilderTest.kt | 25 ++- .../builders/LegacyNotificationBuilderTest.kt | 6 +- .../ManualCarouselNotificationBuilderTest.kt | 72 +++++-- .../MockAEPPushTemplateDataProvider.kt | 43 +++- .../internal/templates/MockDataUtils.kt | 26 +++ .../MockProductCatalogTemplateDataProvider.kt | 68 +++++++ .../MockTimerTemplateDataProvider.kt | 115 +++++++++++ 8 files changed, 479 insertions(+), 60 deletions(-) create mode 100644 code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/templates/MockProductCatalogTemplateDataProvider.kt create mode 100644 code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/templates/MockTimerTemplateDataProvider.kt diff --git a/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/NotificationBuilderTests.kt b/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/NotificationBuilderTests.kt index b07b41a4..fa6cb2db 100644 --- a/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/NotificationBuilderTests.kt +++ b/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/NotificationBuilderTests.kt @@ -1,6 +1,6 @@ /* Copyright 2024 Adobe. All rights reserved. - This file is licensed to you under the Apache License, Version 2.0 (the "License") + 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 @@ -12,33 +12,45 @@ package com.adobe.marketing.mobile.notificationbuilder.internal import android.app.Activity +import android.app.AlarmManager import android.app.Application +import android.app.NotificationManager import android.content.BroadcastReceiver +import android.content.Context import android.content.Intent import androidx.core.app.NotificationManagerCompat import com.adobe.marketing.mobile.notificationbuilder.NotificationBuilder import com.adobe.marketing.mobile.notificationbuilder.NotificationConstructionFailedException import com.adobe.marketing.mobile.notificationbuilder.PushTemplateConstants -import com.adobe.marketing.mobile.notificationbuilder.internal.builders.DummyActivity -import com.adobe.marketing.mobile.notificationbuilder.internal.builders.DummyBroadcastReceiver +import com.adobe.marketing.mobile.notificationbuilder.internal.builders.AutoCarouselNotificationBuilder +import com.adobe.marketing.mobile.notificationbuilder.internal.builders.BasicNotificationBuilder +import com.adobe.marketing.mobile.notificationbuilder.internal.builders.InputBoxNotificationBuilder +import com.adobe.marketing.mobile.notificationbuilder.internal.builders.LegacyNotificationBuilder +import com.adobe.marketing.mobile.notificationbuilder.internal.builders.ManualCarouselNotificationBuilder +import com.adobe.marketing.mobile.notificationbuilder.internal.builders.MultiIconNotificationBuilder +import com.adobe.marketing.mobile.notificationbuilder.internal.builders.ProductCatalogNotificationBuilder +import com.adobe.marketing.mobile.notificationbuilder.internal.builders.ProductRatingNotificationBuilder +import com.adobe.marketing.mobile.notificationbuilder.internal.builders.TimerNotificationBuilder +import com.adobe.marketing.mobile.notificationbuilder.internal.builders.ZeroBezelNotificationBuilder import com.adobe.marketing.mobile.notificationbuilder.internal.templates.MockAEPPushTemplateDataProvider +import com.adobe.marketing.mobile.notificationbuilder.internal.templates.MockCarousalTemplateDataProvider +import com.adobe.marketing.mobile.notificationbuilder.internal.templates.MockProductCatalogTemplateDataProvider +import com.adobe.marketing.mobile.notificationbuilder.internal.templates.MockTimerTemplateDataProvider import com.adobe.marketing.mobile.services.AppContextService import com.adobe.marketing.mobile.services.ServiceProvider -import io.mockk.clearAllMocks import io.mockk.every import io.mockk.mockk import io.mockk.mockkClass import io.mockk.mockkObject import io.mockk.mockkStatic +import io.mockk.unmockkAll import io.mockk.verify import org.junit.After import org.junit.Before import org.junit.Test import org.junit.runner.RunWith import org.mockito.Mock -import org.mockito.MockitoAnnotations import org.robolectric.RobolectricTestRunner -import org.robolectric.RuntimeEnvironment import org.robolectric.annotation.Config import kotlin.test.assertEquals import kotlin.test.assertNotNull @@ -50,27 +62,56 @@ class NotificationBuilderTests { @Mock private lateinit var application: Application + private lateinit var context: Context + private lateinit var alarmManager: AlarmManager + private lateinit var notificationManager: NotificationManager private lateinit var trackerActivityClass: Class private lateinit var broadcastReceiverClass: Class - private lateinit var mockNotificationManagerCompat: NotificationManagerCompat + private lateinit var notificationManagerCompat: NotificationManagerCompat @Before fun setUp() { - MockitoAnnotations.openMocks(this) - application = RuntimeEnvironment.getApplication() - ServiceProvider.getInstance().appContextService.setApplication(application) - trackerActivityClass = DummyActivity::class.java - broadcastReceiverClass = DummyBroadcastReceiver::class.java + setupNotificationBuilderMocks() + setupApplicationMocks() + trackerActivityClass = mockkClass(Activity::class, relaxed = true).javaClass + broadcastReceiverClass = mockkClass(BroadcastReceiver::class, relaxed = true).javaClass mockkObject(PendingIntentUtils) + mockkObject(PushTemplateImageUtils) + } + + private fun setupNotificationBuilderMocks() { + mockkObject(BasicNotificationBuilder) + mockkObject(ManualCarouselNotificationBuilder) + mockkObject(AutoCarouselNotificationBuilder) + mockkObject(InputBoxNotificationBuilder) + mockkObject(ZeroBezelNotificationBuilder) + mockkObject(ProductRatingNotificationBuilder) + mockkObject(ProductCatalogNotificationBuilder) + mockkObject(MultiIconNotificationBuilder) + mockkObject(TimerNotificationBuilder) + mockkObject(LegacyNotificationBuilder) + } + + private fun setupApplicationMocks() { mockkStatic(NotificationManagerCompat::class) - mockNotificationManagerCompat = mockkClass(NotificationManagerCompat::class) - every { NotificationManagerCompat.from(any()) } returns mockNotificationManagerCompat - every { mockNotificationManagerCompat.cancel(any()) } returns Unit + application = mockk(relaxed = true) + context = mockk(relaxed = true) + alarmManager = mockk(relaxed = true) + notificationManager = mockk(relaxed = true) + notificationManagerCompat = mockkClass(NotificationManagerCompat::class) + every { NotificationManagerCompat.from(any()) } returns notificationManagerCompat + every { notificationManagerCompat.cancel(any()) } returns Unit + every { alarmManager.canScheduleExactAlarms() } returns true + every { context.getSystemService(Context.NOTIFICATION_SERVICE) } returns notificationManager + every { context.getSystemService(Context.ALARM_SERVICE) } returns alarmManager + every { context.packageName } answers { callOriginal() } + every { application.applicationContext } returns context + ServiceProvider.getInstance().appContextService.setApplication(application) } @After - fun cleanup() { - clearAllMocks() + fun tearDown() { + unmockkAll() } @Test @@ -173,7 +214,7 @@ class NotificationBuilderTests { ) verify(exactly = 1) { PendingIntentUtils.scheduleNotification(any(), any(), any(), any()) } - verify(exactly = 1) { mockNotificationManagerCompat.cancel(any()) } + verify(exactly = 1) { notificationManagerCompat.cancel(any()) } } @Test @@ -188,7 +229,7 @@ class NotificationBuilderTests { ) verify(exactly = 0) { PendingIntentUtils.scheduleNotification(any(), any(), any(), any()) } - verify(exactly = 1) { mockNotificationManagerCompat.cancel(any()) } + verify(exactly = 1) { notificationManagerCompat.cancel(any()) } } @Test @@ -204,7 +245,7 @@ class NotificationBuilderTests { ) verify(exactly = 0) { PendingIntentUtils.scheduleNotification(any(), any(), any(), any()) } - verify(exactly = 0) { mockNotificationManagerCompat.cancel(any()) } + verify(exactly = 0) { notificationManagerCompat.cancel(any()) } } @Test @@ -220,7 +261,7 @@ class NotificationBuilderTests { ) verify(exactly = 1) { PendingIntentUtils.scheduleNotification(any(), any(), any(), any()) } - verify(exactly = 1) { mockNotificationManagerCompat.cancel(any()) } + verify(exactly = 1) { notificationManagerCompat.cancel(any()) } } @Test @@ -237,7 +278,7 @@ class NotificationBuilderTests { ) verify(exactly = 1) { PendingIntentUtils.scheduleNotification(any(), any(), any(), any()) } - verify(exactly = 0) { mockNotificationManagerCompat.cancel(any()) } + verify(exactly = 0) { notificationManagerCompat.cancel(any()) } } @Test(expected = IllegalArgumentException::class) @@ -254,7 +295,7 @@ class NotificationBuilderTests { ) verify(exactly = 0) { PendingIntentUtils.scheduleNotification(any(), any(), any(), any()) } - verify(exactly = 1) { mockNotificationManagerCompat.cancel(any()) } + verify(exactly = 1) { notificationManagerCompat.cancel(any()) } } @Test(expected = IllegalArgumentException::class) @@ -272,7 +313,7 @@ class NotificationBuilderTests { ) verify(exactly = 0) { PendingIntentUtils.scheduleNotification(any(), any(), any(), any()) } - verify(exactly = 0) { mockNotificationManagerCompat.cancel(any()) } + verify(exactly = 0) { notificationManagerCompat.cancel(any()) } } @Test(expected = IllegalArgumentException::class) @@ -289,7 +330,7 @@ class NotificationBuilderTests { ) verify(exactly = 0) { PendingIntentUtils.scheduleNotification(any(), any(), any(), any()) } - verify(exactly = 0) { mockNotificationManagerCompat.cancel(any()) } + verify(exactly = 0) { notificationManagerCompat.cancel(any()) } } @Test(expected = IllegalArgumentException::class) @@ -306,7 +347,7 @@ class NotificationBuilderTests { ) verify(exactly = 0) { PendingIntentUtils.scheduleNotification(any(), any(), any(), any()) } - verify(exactly = 0) { mockNotificationManagerCompat.cancel(any()) } + verify(exactly = 0) { notificationManagerCompat.cancel(any()) } } @Test(expected = NotificationConstructionFailedException::class) @@ -322,7 +363,7 @@ class NotificationBuilderTests { ) verify(exactly = 0) { PendingIntentUtils.scheduleNotification(any(), any(), any(), any()) } - verify(exactly = 0) { mockNotificationManagerCompat.cancel(any()) } + verify(exactly = 0) { notificationManagerCompat.cancel(any()) } } @Test(expected = NotificationConstructionFailedException::class) @@ -335,7 +376,94 @@ class NotificationBuilderTests { ) verify(exactly = 0) { PendingIntentUtils.scheduleNotification(any(), any(), any(), any()) } - verify(exactly = 0) { mockNotificationManagerCompat.cancel(any()) } + verify(exactly = 0) { notificationManagerCompat.cancel(any()) } + } + + @Test + fun `verify private createNotificationBuilder calls LegacyNotificationBuilder construct`() { + val mapData = MockAEPPushTemplateDataProvider.getMockedAEPDataMapWithAllKeys() + mapData[PushTemplateConstants.PushPayloadKeys.TEMPLATE_TYPE] = "some unknown type" + NotificationBuilder.constructNotificationBuilder(mapData, trackerActivityClass, broadcastReceiverClass) + verify(exactly = 1) { LegacyNotificationBuilder.construct(any(Context::class), any(), trackerActivityClass) } + } + + @Test + fun `verify private createNotificationBuilder calls BasicNotificationBuilder construct`() { + val mapData = MockAEPPushTemplateDataProvider.getMockedAEPDataMapWithAllKeys() + NotificationBuilder.constructNotificationBuilder(mapData, trackerActivityClass, broadcastReceiverClass) + verify(exactly = 1) { BasicNotificationBuilder.construct(any(Context::class), any(), trackerActivityClass, broadcastReceiverClass) } + } + + @Test + fun `verify private createNotificationBuilder calls ManualCarouselNotificationBuilder construct`() { + val mapData = MockCarousalTemplateDataProvider.getMockedMapWithManualCarouselData() + NotificationBuilder.constructNotificationBuilder(mapData, trackerActivityClass, broadcastReceiverClass) + verify(exactly = 1) { ManualCarouselNotificationBuilder.construct(any(Context::class), any(), trackerActivityClass, broadcastReceiverClass) } + } + + @Test + fun `verify private createNotificationBuilder calls AutoCarouselNotificationBuilder construct`() { + val mapData = MockCarousalTemplateDataProvider.getMockedMapWithAutoCarouselData() + NotificationBuilder.constructNotificationBuilder(mapData, trackerActivityClass, broadcastReceiverClass) + verify(exactly = 1) { AutoCarouselNotificationBuilder.construct(any(Context::class), any(), trackerActivityClass, broadcastReceiverClass) } + } + + @Test + fun `verify private createNotificationBuilder calls InputBoxNotificationBuilder construct`() { + val mapData = MockAEPPushTemplateDataProvider.getMockedAEPDataMapWithAllKeys() + mapData[PushTemplateConstants.PushPayloadKeys.TEMPLATE_TYPE] = PushTemplateType.INPUT_BOX.value + mapData[PushTemplateConstants.PushPayloadKeys.INPUT_BOX_RECEIVER_NAME] = "receiverName" + NotificationBuilder.constructNotificationBuilder(mapData, trackerActivityClass, broadcastReceiverClass) + verify(exactly = 1) { InputBoxNotificationBuilder.construct(any(Context::class), any(), trackerActivityClass, broadcastReceiverClass) } + } + + @Test + fun `verify private createNotificationBuilder calls ZeroBezelNotificationBuilder construct`() { + val mapData = MockAEPPushTemplateDataProvider.getMockedAEPDataMapWithAllKeys() + mapData[PushTemplateConstants.PushPayloadKeys.TEMPLATE_TYPE] = PushTemplateType.ZERO_BEZEL.value + NotificationBuilder.constructNotificationBuilder(mapData, trackerActivityClass, broadcastReceiverClass) + verify(exactly = 1) { ZeroBezelNotificationBuilder.construct(any(Context::class), any(), trackerActivityClass) } + } + + @Test + fun `verify private createNotificationBuilder calls ProductRatingNotificationBuilder construct`() { + every { PushTemplateImageUtils.cacheImages(any()) } answers { 1 } + val mapData = MockAEPPushTemplateDataProvider.getMockedAEPDataMapWithAllKeys() + mapData[PushTemplateConstants.PushPayloadKeys.TEMPLATE_TYPE] = PushTemplateType.PRODUCT_RATING.value + mapData[PushTemplateConstants.PushPayloadKeys.RATING_UNSELECTED_ICON] = "https://i.imgur.com/unselected.png" + mapData[PushTemplateConstants.PushPayloadKeys.RATING_SELECTED_ICON] = "https://i.imgur.com/selected.png" + mapData[PushTemplateConstants.PushPayloadKeys.RATING_ACTIONS] = "[{\"uri\":\"https://www.adobe.com\", \"type\":\"WEBURL\"},{\"type\":\"OPENAPP\"},{\"type\":\"DISMISS\"},{\"uri\": \"https://www.adobe.com\", \"type\":\"WEBURL\"},{\"uri\":\"instabiz://opensecond\", \"type\":\"DEEPLINK\"}]" + NotificationBuilder.constructNotificationBuilder(mapData, trackerActivityClass, broadcastReceiverClass) + verify(exactly = 1) { ProductRatingNotificationBuilder.construct(any(Context::class), any(), trackerActivityClass, broadcastReceiverClass) } + } + + @Test + fun `verify private createNotificationBuilder calls ProductCatalogNotificationBuilder construct`() { + every { PushTemplateImageUtils.cacheImages(any()) } answers { 3 } + every { PushTemplateImageUtils.getCachedImage(any()) } answers { mockk() } + val mapData = MockProductCatalogTemplateDataProvider.getMockedMapWithProductCatalogData() + mapData[PushTemplateConstants.PushPayloadKeys.TEMPLATE_TYPE] = PushTemplateType.PRODUCT_CATALOG.value + NotificationBuilder.constructNotificationBuilder(mapData, trackerActivityClass, broadcastReceiverClass) + verify(exactly = 1) { ProductCatalogNotificationBuilder.construct(any(Context::class), any(), trackerActivityClass, broadcastReceiverClass) } + } + + @Test + fun `verify private createNotificationBuilder calls MultiIconNotificationBuilder construct`() { + every { PushTemplateImageUtils.cacheImages(any()) } answers { 3 } + every { PushTemplateImageUtils.getCachedImage(any()) } answers { mockk() } + val mapData = MockAEPPushTemplateDataProvider.getMockedAEPDataMapWithAllKeys() + mapData[PushTemplateConstants.PushPayloadKeys.TEMPLATE_TYPE] = PushTemplateType.MULTI_ICON.value + mapData[PushTemplateConstants.PushPayloadKeys.MULTI_ICON_ITEMS] = "[{\"img\":\"https://sneakerland.com/products/assets/shoe1.png\",\"uri\":\"myapp://chooseShoeType/shoe1\",\"type\":\"DEEPLINK\"},{\"img\":\"https://sneakerland.com/products/assets/shoe2.png\",\"uri\":\"myapp://chooseShoeType/shoe2\",\"type\":\"DEEPLINK\"},{\"img\":\"https://sneakerland.com/products/assets/shoe3.png\",\"uri\":\"myapp://chooseShoeType/shoe3\",\"type\":\"DEEPLINK\"},{\"img\":\"https://sneakerland.com/products/assets/shoe4.png\",\"uri\":\"myapp://chooseShoeType/shoe4\",\"type\":\"DEEPLINK\"},{\"img\":\"https://sneakerland.com/products/assets/shoe5.png\",\"uri\":\"myapp://chooseShoeType/shoe5\",\"type\":\"DEEPLINK\"}]" + NotificationBuilder.constructNotificationBuilder(mapData, trackerActivityClass, broadcastReceiverClass) + verify(exactly = 1) { MultiIconNotificationBuilder.construct(any(Context::class), any(), trackerActivityClass) } + } + + @Test + fun `verify private createNotificationBuilder calls TimerNotificationBuilder construct`() { + val mapData = MockTimerTemplateDataProvider.getMockedMapWithTimerData(true, "10") + mapData[PushTemplateConstants.PushPayloadKeys.TEMPLATE_TYPE] = PushTemplateType.TIMER.value + NotificationBuilder.constructNotificationBuilder(mapData, trackerActivityClass, broadcastReceiverClass) + verify(exactly = 1) { TimerNotificationBuilder.construct(any(Context::class), any(), trackerActivityClass, broadcastReceiverClass) } } private fun setNullContext() { diff --git a/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/BasicNotificationBuilderTest.kt b/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/BasicNotificationBuilderTest.kt index bb16a7aa..371918f2 100644 --- a/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/BasicNotificationBuilderTest.kt +++ b/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/BasicNotificationBuilderTest.kt @@ -28,7 +28,7 @@ import com.adobe.marketing.mobile.notificationbuilder.internal.templates.MockAEP import com.adobe.marketing.mobile.notificationbuilder.internal.templates.provideMockedBasicPushTemplateWithAllKeys import com.adobe.marketing.mobile.notificationbuilder.internal.templates.provideMockedBasicPushTemplateWithRequiredData import com.adobe.marketing.mobile.notificationbuilder.internal.util.MapData -import io.mockk.clearAllMocks +import io.mockk.unmockkAll import junit.framework.TestCase.assertEquals import org.junit.After import org.junit.Assert.assertNull @@ -62,8 +62,8 @@ class BasicNotificationBuilderTest { } @After - fun cleanup() { - clearAllMocks() + fun tearDown() { + unmockkAll() } @Test @@ -108,8 +108,14 @@ class BasicNotificationBuilderTest { val shadowPendingIntent = Shadows.shadowOf(pendingIntent) val intent = shadowPendingIntent.savedIntent - assertEquals(pushTemplate.tag, intent.getStringExtra(PushTemplateConstants.PushPayloadKeys.TAG)) - assertEquals(pushTemplate.isNotificationSticky.toString(), intent.getStringExtra(PushTemplateConstants.PushPayloadKeys.STICKY)) + assertEquals( + pushTemplate.tag, + intent.getStringExtra(PushTemplateConstants.PushPayloadKeys.TAG) + ) + assertEquals( + pushTemplate.isNotificationSticky.toString(), + intent.getStringExtra(PushTemplateConstants.PushPayloadKeys.STICKY) + ) assertEquals(pushTemplate.tag, MOCKED_TAG) assertEquals(build.channelId, MOCKED_CHANNEL_ID) assertEquals(MOCKED_TICKER, build.tickerText) @@ -120,7 +126,8 @@ class BasicNotificationBuilderTest { val dataMap = MockAEPPushTemplateDataProvider.getMockedDataMapWithRequiredData() dataMap[PushTemplateConstants.PushPayloadKeys.REMIND_LATER_TEXT] = MOCK_REMIND_LATER_TEXT - dataMap[PushTemplateConstants.PushPayloadKeys.REMIND_LATER_DURATION] = MOCK_REMIND_LATER_DURATION + dataMap[PushTemplateConstants.PushPayloadKeys.REMIND_LATER_DURATION] = + MOCK_REMIND_LATER_DURATION dataMap[PushTemplateConstants.PushPayloadKeys.STICKY] = "false" val pushTemplate = BasicPushTemplate(MapData(dataMap)) @@ -223,7 +230,8 @@ class BasicNotificationBuilderTest { fun `remindLaterButton is added when remindLaterText is not null, remindLaterTimestamp is not null, remindLaterDuration is null`() { val dataMap = MockAEPPushTemplateDataProvider.getMockedDataMapWithRequiredData() dataMap[PushTemplateConstants.PushPayloadKeys.REMIND_LATER_TEXT] = MOCK_REMIND_LATER_TEXT - dataMap[PushTemplateConstants.PushPayloadKeys.REMIND_LATER_TIMESTAMP] = MOCK_REMIND_LATER_TIME + dataMap[PushTemplateConstants.PushPayloadKeys.REMIND_LATER_TIMESTAMP] = + MOCK_REMIND_LATER_TIME val pushTemplate = BasicPushTemplate(MapData(dataMap)) val notificationBuilder = BasicNotificationBuilder.construct( @@ -244,7 +252,8 @@ class BasicNotificationBuilderTest { fun `remindLaterButton is added when remindLaterText is not null, remindLaterTimestamp is null, remindLaterDuration is not null`() { val dataMap = MockAEPPushTemplateDataProvider.getMockedDataMapWithRequiredData() dataMap[PushTemplateConstants.PushPayloadKeys.REMIND_LATER_TEXT] = MOCK_REMIND_LATER_TEXT - dataMap[PushTemplateConstants.PushPayloadKeys.REMIND_LATER_DURATION] = MOCK_REMIND_LATER_DURATION + dataMap[PushTemplateConstants.PushPayloadKeys.REMIND_LATER_DURATION] = + MOCK_REMIND_LATER_DURATION val pushTemplate = BasicPushTemplate(MapData(dataMap)) val notificationBuilder = BasicNotificationBuilder.construct( diff --git a/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/LegacyNotificationBuilderTest.kt b/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/LegacyNotificationBuilderTest.kt index ee6f5061..95307ff3 100644 --- a/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/LegacyNotificationBuilderTest.kt +++ b/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/LegacyNotificationBuilderTest.kt @@ -26,7 +26,7 @@ import com.adobe.marketing.mobile.notificationbuilder.internal.templates.removeK import com.adobe.marketing.mobile.notificationbuilder.internal.templates.replaceValueInMap import com.adobe.marketing.mobile.notificationbuilder.internal.util.IntentData import com.adobe.marketing.mobile.notificationbuilder.internal.util.MapData -import io.mockk.clearAllMocks +import io.mockk.unmockkAll import junit.framework.TestCase.assertEquals import junit.framework.TestCase.assertNotNull import junit.framework.TestCase.assertNull @@ -58,8 +58,8 @@ class LegacyNotificationBuilderTest { } @After - fun cleanup() { - clearAllMocks() + fun tearDown() { + unmockkAll() } @Test diff --git a/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/ManualCarouselNotificationBuilderTest.kt b/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/ManualCarouselNotificationBuilderTest.kt index 4443c19a..073a997a 100644 --- a/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/ManualCarouselNotificationBuilderTest.kt +++ b/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/ManualCarouselNotificationBuilderTest.kt @@ -32,12 +32,12 @@ import com.adobe.marketing.mobile.notificationbuilder.internal.templates.MockCar import com.adobe.marketing.mobile.notificationbuilder.internal.templates.provideMockedManualCarousalTemplate import com.adobe.marketing.mobile.notificationbuilder.internal.util.IntentData import io.mockk.Runs -import io.mockk.clearAllMocks import io.mockk.every import io.mockk.just import io.mockk.mockkClass import io.mockk.mockkConstructor import io.mockk.mockkObject +import io.mockk.unmockkAll import io.mockk.verify import org.junit.After import org.junit.Assert.assertEquals @@ -74,8 +74,8 @@ class ManualCarouselNotificationBuilderTest { } @After - fun cleanup() { - clearAllMocks() + fun tearDown() { + unmockkAll() } @Test @@ -130,14 +130,16 @@ class ManualCarouselNotificationBuilderTest { @Test fun `test downloadCarouselItems returns non empty list when image bitmap is present in the cache`() { every { getCachedImage(any()) } answers { mockkClass(Bitmap::class) } - val imagesList = ManualCarouselNotificationBuilder.downloadCarouselItems(pushTemplate.carouselItems) + val imagesList = + ManualCarouselNotificationBuilder.downloadCarouselItems(pushTemplate.carouselItems) assertTrue(imagesList == pushTemplate.carouselItems) } @Test fun `test getCarouselIndices with left click intent action`() { val mockBundle = MockCarousalTemplateDataProvider.getMockedBundleWithManualCarouselData() - val data = IntentData(mockBundle, PushTemplateConstants.IntentActions.MANUAL_CAROUSEL_LEFT_CLICKED) + val data = + IntentData(mockBundle, PushTemplateConstants.IntentActions.MANUAL_CAROUSEL_LEFT_CLICKED) val mcPushTemplate = CarouselPushTemplate(data) as ManualCarouselPushTemplate val result = getCarouselIndices(mcPushTemplate, 3) assertEquals(Triple(1, 2, 0), result) @@ -146,7 +148,8 @@ class ManualCarouselNotificationBuilderTest { @Test fun `test getCarouselIndices with filmstrip left click intent action`() { val mockBundle = MockCarousalTemplateDataProvider.getMockedBundleWithManualCarouselData() - val data = IntentData(mockBundle, PushTemplateConstants.IntentActions.FILMSTRIP_LEFT_CLICKED) + val data = + IntentData(mockBundle, PushTemplateConstants.IntentActions.FILMSTRIP_LEFT_CLICKED) val mcPushTemplate = CarouselPushTemplate(data) as ManualCarouselPushTemplate val result = getCarouselIndices(mcPushTemplate, 3) assertEquals(Triple(1, 2, 0), result) @@ -155,7 +158,8 @@ class ManualCarouselNotificationBuilderTest { @Test fun `test getCarouselIndices with no intent action and filmstrip layout`() { val mockBundle = MockCarousalTemplateDataProvider.getMockedBundleWithManualCarouselData() - val data = IntentData(mockBundle, PushTemplateConstants.DefaultValues.FILMSTRIP_CAROUSEL_MODE) + val data = + IntentData(mockBundle, PushTemplateConstants.DefaultValues.FILMSTRIP_CAROUSEL_MODE) val mcPushTemplate = CarouselPushTemplate(data) as ManualCarouselPushTemplate val result = getCarouselIndices(mcPushTemplate, 3) assertEquals( @@ -188,7 +192,16 @@ class ManualCarouselNotificationBuilderTest { every { getCachedImage(any()) } answers { mockkClass(Bitmap::class) } every { anyConstructed().setTextViewText(any(), any()) } just Runs every { anyConstructed().setImageViewBitmap(any(), any()) } just Runs - every { anyConstructed().setRemoteViewClickAction(context, trackerActivityClass, any(), any(), null, any()) } just Runs + every { + anyConstructed().setRemoteViewClickAction( + context, + trackerActivityClass, + any(), + any(), + null, + any() + ) + } just Runs every { expandedLayout.addView(any(), any()) } just Runs every { expandedLayout.setDisplayedChild(any(), any()) } just Runs populateManualCarouselImages( @@ -201,11 +214,35 @@ class ManualCarouselNotificationBuilderTest { expandedLayout ) val carouselItemsCount = pushTemplate.carouselItems.size - verify(exactly = carouselItemsCount) { anyConstructed().setTextViewText(any(), any()) } - verify(exactly = carouselItemsCount) { anyConstructed().setImageViewBitmap(any(), any()) } - verify(exactly = carouselItemsCount) { anyConstructed().setRemoteViewClickAction(context, trackerActivityClass, any(), any(), null, any()) } + verify(exactly = carouselItemsCount) { + anyConstructed().setTextViewText( + any(), + any() + ) + } + verify(exactly = carouselItemsCount) { + anyConstructed().setImageViewBitmap( + any(), + any() + ) + } + verify(exactly = carouselItemsCount) { + anyConstructed().setRemoteViewClickAction( + context, + trackerActivityClass, + any(), + any(), + null, + any() + ) + } verify(exactly = carouselItemsCount) { expandedLayout.addView(any(), any()) } - verify(exactly = carouselItemsCount) { expandedLayout.setDisplayedChild(any(), centerIndex) } + verify(exactly = carouselItemsCount) { + expandedLayout.setDisplayedChild( + any(), + centerIndex + ) + } } @Test @@ -224,7 +261,16 @@ class ManualCarouselNotificationBuilderTest { verify(exactly = 0) { anyConstructed().setTextViewText(any(), any()) } verify(exactly = 0) { anyConstructed().setImageViewBitmap(any(), any()) } - verify(exactly = 0) { anyConstructed().setRemoteViewClickAction(context, trackerActivityClass, any(), any(), null, any()) } + verify(exactly = 0) { + anyConstructed().setRemoteViewClickAction( + context, + trackerActivityClass, + any(), + any(), + null, + any() + ) + } verify(exactly = 0) { expandedLayout.addView(any(), any()) } verify(exactly = 0) { expandedLayout.setDisplayedChild(any(), centerIndex) } } diff --git a/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/templates/MockAEPPushTemplateDataProvider.kt b/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/templates/MockAEPPushTemplateDataProvider.kt index e1c3f212..c6cbca9d 100644 --- a/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/templates/MockAEPPushTemplateDataProvider.kt +++ b/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/templates/MockAEPPushTemplateDataProvider.kt @@ -23,6 +23,7 @@ object MockAEPPushTemplateDataProvider { PushTemplateConstants.PushPayloadKeys.VERSION to MOCKED_PAYLOAD_VERSION ) } + /** * Returns a mocked data bundle with basic data. */ @@ -60,7 +61,8 @@ object MockAEPPushTemplateDataProvider { PushTemplateConstants.PushPayloadKeys.TITLE_TEXT_COLOR to "FFD966", PushTemplateConstants.PushPayloadKeys.TICKER to MOCKED_TICKER, PushTemplateConstants.PushPayloadKeys.VERSION to MOCKED_PAYLOAD_VERSION, - PushTemplateConstants.PushPayloadKeys.STICKY to "true" + PushTemplateConstants.PushPayloadKeys.STICKY to "true", + PushTemplateConstants.PushPayloadKeys.ZERO_BEZEL_COLLAPSED_STYLE to "img" ) } @@ -71,29 +73,54 @@ object MockAEPPushTemplateDataProvider { mockBundle.putString(PushTemplateConstants.PushPayloadKeys.BODY, MOCKED_BODY) mockBundle.putString(PushTemplateConstants.PushPayloadKeys.TAG, MOCKED_TAG) mockBundle.putString(PushTemplateConstants.PushPayloadKeys.TITLE, MOCKED_TITLE) - mockBundle.putString(PushTemplateConstants.PushPayloadKeys.TEMPLATE_TYPE, PushTemplateType.BASIC.value) + mockBundle.putString( + PushTemplateConstants.PushPayloadKeys.TEMPLATE_TYPE, + PushTemplateType.BASIC.value + ) mockBundle.putString(PushTemplateConstants.PushPayloadKeys.ACTION_URI, MOCKED_ACTION_URI) - mockBundle.putString(PushTemplateConstants.PushPayloadKeys.ACTION_TYPE, PushTemplateConstants.ActionType.NONE.name) - mockBundle.putString(PushTemplateConstants.PushPayloadKeys.ACTION_BUTTONS, MOCKED_ACTION_BUTTON_DATA) + mockBundle.putString( + PushTemplateConstants.PushPayloadKeys.ACTION_TYPE, + PushTemplateConstants.ActionType.NONE.name + ) + mockBundle.putString( + PushTemplateConstants.PushPayloadKeys.ACTION_BUTTONS, + MOCKED_ACTION_BUTTON_DATA + ) mockBundle.putString(PushTemplateConstants.PushPayloadKeys.BADGE_COUNT, "5") mockBundle.putString(PushTemplateConstants.PushPayloadKeys.BODY, MOCKED_BASIC_TEMPLATE_BODY) mockBundle.putString(PushTemplateConstants.PushPayloadKeys.CHANNEL_ID, "2024") - mockBundle.putString(PushTemplateConstants.PushPayloadKeys.EXPANDED_BODY_TEXT, MOCKED_BASIC_TEMPLATE_BODY_EXPANDED) + mockBundle.putString( + PushTemplateConstants.PushPayloadKeys.EXPANDED_BODY_TEXT, + MOCKED_BASIC_TEMPLATE_BODY_EXPANDED + ) mockBundle.putString(PushTemplateConstants.PushPayloadKeys.BODY_TEXT_COLOR, "FFD966") mockBundle.putString(PushTemplateConstants.PushPayloadKeys.IMAGE_URL, MOCKED_IMAGE_URI) mockBundle.putString(PushTemplateConstants.PushPayloadKeys.LARGE_ICON, MOCKED_LARGE_ICON) mockBundle.putString(PushTemplateConstants.PushPayloadKeys.BACKGROUND_COLOR, "FFD966") mockBundle.putString(PushTemplateConstants.PushPayloadKeys.PRIORITY, (MOCKED_PRIORITY)) mockBundle.putString(PushTemplateConstants.PushPayloadKeys.VISIBILITY, MOCKED_VISIBILITY) - mockBundle.putString(PushTemplateConstants.PushPayloadKeys.REMIND_LATER_TEXT, MOCK_REMIND_LATER_TEXT) - mockBundle.putString(PushTemplateConstants.PushPayloadKeys.REMIND_LATER_TIMESTAMP, MOCK_REMIND_LATER_TIME) - mockBundle.putString(PushTemplateConstants.PushPayloadKeys.REMIND_LATER_DURATION, MOCK_REMIND_LATER_DURATION) + mockBundle.putString( + PushTemplateConstants.PushPayloadKeys.REMIND_LATER_TEXT, + MOCK_REMIND_LATER_TEXT + ) + mockBundle.putString( + PushTemplateConstants.PushPayloadKeys.REMIND_LATER_TIMESTAMP, + MOCK_REMIND_LATER_TIME + ) + mockBundle.putString( + PushTemplateConstants.PushPayloadKeys.REMIND_LATER_DURATION, + MOCK_REMIND_LATER_DURATION + ) mockBundle.putString(PushTemplateConstants.PushPayloadKeys.SOUND, "bell") mockBundle.putString(PushTemplateConstants.PushPayloadKeys.SMALL_ICON, MOCKED_SMALL_ICON) mockBundle.putString(PushTemplateConstants.PushPayloadKeys.TITLE_TEXT_COLOR, "FFD966") mockBundle.putString(PushTemplateConstants.PushPayloadKeys.TICKER, MOCKED_TICKER) mockBundle.putString(PushTemplateConstants.PushPayloadKeys.VERSION, MOCKED_PAYLOAD_VERSION) mockBundle.putString(PushTemplateConstants.PushPayloadKeys.STICKY, "true") + mockBundle.putString( + PushTemplateConstants.PushPayloadKeys.ZERO_BEZEL_COLLAPSED_STYLE, + "img" + ) return mockBundle } } diff --git a/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/templates/MockDataUtils.kt b/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/templates/MockDataUtils.kt index 385c6e3c..222df097 100644 --- a/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/templates/MockDataUtils.kt +++ b/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/templates/MockDataUtils.kt @@ -16,13 +16,20 @@ import com.adobe.marketing.mobile.notificationbuilder.internal.util.MapData import com.adobe.marketing.mobile.notificationbuilder.internal.util.NotificationData const val MOCKED_TITLE = "Mocked Title" +const val MOCKED_ALT_TITLE = "Mocked Alternate Title" const val MOCKED_BODY = "Mocked Body" +const val MOCKED_ALT_BODY = "Mocked Alternate Body" +const val MOCKED_ALT_EXPANDED_BODY = "Mocked Alternate Expanded Body" +const val MOCKED_EXPANDED_BODY = "Mocked Expanded Body" const val MOCKED_PAYLOAD_VERSION = "1" const val MOCKED_CAROUSEL_LAYOUT = "default" const val MOCKED_BODY_TEXT_COLOR = "#FFFFFF" const val MOCKED_SMALL_ICON = "skipleft" const val MOCKED_LARGE_ICON = "https://cdn-icons-png.flaticon.com/128/864/864639.png" const val MOCKED_SMALL_ICON_COLOR = "#000000" +const val MOCKED_TIMER_COLOR = "#FFFF00" +const val MOCKED_TIMER_DURATION = "60" +const val MOCKED_TIMER_EXPIRY_TIME = "2665428926" // Thursday, June 18, 2054 8:55:26 PM GMT const val MOCKED_VISIBILITY = "PUBLIC" const val MOCKED_PRIORITY = "PRIORITY_HIGH" const val MOCKED_TICKER = "ticker" @@ -32,6 +39,8 @@ const val MOCKED_CAROUSEL_LAYOUT_DATA = "[{\"img\":\"https://i.imgur.com/7ZolaOv.jpeg\",\"txt\":\"Basketball Shoes\"},{\"img\":\"https://i.imgur.com/mZvLuzU.jpg\",\"txt\":\"Red Jersey\",\"uri\":\"https://firefly.adobe.com/red_jersey\"},{\"img\":\"https://i.imgur.com/X5yjy09.jpg\",\"txt\":\"Volleyball\", \"uri\":\"https://firefly.adobe.com/volleyball\"},{\"img\":\"https://i.imgur.com/35B0mkh.jpg\",\"txt\":\"Basketball\",\"uri\":\"https://firefly.adobe.com/basketball\"},{\"img\":\"https://i.imgur.com/Cs5hmfb.jpg\",\"txt\":\"Black Batting Helmet\",\"uri\":\"https://firefly.adobe.com/black_helmet\"}]" const val MOCKED_IMAGE_URI = "https://images.pexels.com/photos/260024/pexels-photo-260024.jpeg?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=2" +const val MOCKED_ALT_IMAGE_URI = + "https://images2.pexels.com/photos/260024/pexels-photo-260024.jpeg?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=2" const val MOCKED_ACTION_URI = "https://chess.com/games" const val MOCKED_BASIC_TEMPLATE_BODY_EXPANDED = "Basic push template with action buttons." const val MOCKED_ACTION_BUTTON_DATA = @@ -150,3 +159,20 @@ internal fun provideMockedMultiIconTemplateWithAllKeys(): MultiIconPushTemplate data = MapData(dataMap) return MultiIconPushTemplate(data) } + +internal fun provideMockedTimerTemplate( + isFromIntent: Boolean = false, + isUsingDuration: Boolean = false, + duration: String = MOCKED_TIMER_DURATION +): TimerPushTemplate { + val data: NotificationData = if (isFromIntent) { + val mockBundle = + MockTimerTemplateDataProvider.getMockedBundleWithTimerData(isUsingDuration, duration) + IntentData(mockBundle, null) + } else { + val dataMap = + MockTimerTemplateDataProvider.getMockedMapWithTimerData(isUsingDuration, duration) + MapData(dataMap) + } + return TimerPushTemplate(data) +} diff --git a/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/templates/MockProductCatalogTemplateDataProvider.kt b/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/templates/MockProductCatalogTemplateDataProvider.kt new file mode 100644 index 00000000..a21f9ae6 --- /dev/null +++ b/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/templates/MockProductCatalogTemplateDataProvider.kt @@ -0,0 +1,68 @@ +/* + Copyright 2024 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.notificationbuilder.internal.templates + +import android.os.Bundle +import com.adobe.marketing.mobile.notificationbuilder.PushTemplateConstants + +object MockProductCatalogTemplateDataProvider { + fun getMockedMapWithProductCatalogData(): MutableMap { + return mutableMapOf( + PushTemplateConstants.PushPayloadKeys.TITLE to MOCKED_TITLE, + PushTemplateConstants.PushPayloadKeys.BODY to MOCKED_BODY, + PushTemplateConstants.PushPayloadKeys.VERSION to MOCKED_PAYLOAD_VERSION, + PushTemplateConstants.PushPayloadKeys.CATALOG_CTA_BUTTON_TEXT to "ctaButtonText", + PushTemplateConstants.PushPayloadKeys.CATALOG_CTA_BUTTON_COLOR to "ctaButtonColor", + PushTemplateConstants.PushPayloadKeys.CATALOG_CTA_BUTTON_TEXT_COLOR to "ctaButtonTextColor", + PushTemplateConstants.PushPayloadKeys.CATALOG_CTA_BUTTON_URI to "ctaButtonUri", + PushTemplateConstants.PushPayloadKeys.CATALOG_LAYOUT to "horizontal", + PushTemplateConstants.PushPayloadKeys.CATALOG_ITEMS to "[" + + "{\"title\":\"title1\",\"body\":\"body1\",\"img\":\"https://images.pexels.com/photos/260024/pexels-photo-260024.jpeg?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=2\",\"price\":\"price1\",\"uri\":\"uri1\"}," + + "{\"title\":\"title2\",\"body\":\"body2\",\"img\":\"https://images.pexels.com/photos/260024/pexels-photo-260024.jpeg?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=2\",\"price\":\"price2\",\"uri\":\"uri2\"}," + + "{\"title\":\"title3\",\"body\":\"body3\",\"img\":\"https://images.pexels.com/photos/260024/pexels-photo-260024.jpeg?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=2\",\"price\":\"price3\",\"uri\":\"uri3\"}" + + "]" + ) + } + + fun getMockedBundleWithProductCatalogData(): Bundle { + val mockBundle = Bundle() + mockBundle.putString(PushTemplateConstants.PushPayloadKeys.TITLE, MOCKED_TITLE) + mockBundle.putString(PushTemplateConstants.PushPayloadKeys.BODY, MOCKED_BODY) + mockBundle.putString(PushTemplateConstants.PushPayloadKeys.VERSION, MOCKED_PAYLOAD_VERSION) + mockBundle.putString( + PushTemplateConstants.PushPayloadKeys.CATALOG_CTA_BUTTON_TEXT, + "ctaButtonText" + ) + mockBundle.putString( + PushTemplateConstants.PushPayloadKeys.CATALOG_CTA_BUTTON_COLOR, + "ctaButtonColor" + ) + mockBundle.putString( + PushTemplateConstants.PushPayloadKeys.CATALOG_CTA_BUTTON_TEXT_COLOR, + "ctaButtonTextColor" + ) + mockBundle.putString( + PushTemplateConstants.PushPayloadKeys.CATALOG_CTA_BUTTON_URI, + "ctaButtonUri" + ) + mockBundle.putString(PushTemplateConstants.PushPayloadKeys.CATALOG_LAYOUT, "horizontal") + mockBundle.putString( + PushTemplateConstants.PushPayloadKeys.CATALOG_ITEMS, + "[" + + "{\"title\":\"title1\",\"body\":\"body1\",\"img\":\"https://images.pexels.com/photos/260024/pexels-photo-260024.jpeg?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=2\",\"price\":\"price1\",\"uri\":\"uri1\"}," + + "{\"title\":\"title2\",\"body\":\"body2\",\"img\":\"https://images.pexels.com/photos/260024/pexels-photo-260024.jpeg?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=2\",\"price\":\"price2\",\"uri\":\"uri2\"}," + + "{\"title\":\"title3\",\"body\":\"body3\",\"img\":\"https://images.pexels.com/photos/260024/pexels-photo-260024.jpeg?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=2\",\"price\":\"price3\",\"uri\":\"uri3\"}" + + "]" + ) + return mockBundle + } +} diff --git a/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/templates/MockTimerTemplateDataProvider.kt b/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/templates/MockTimerTemplateDataProvider.kt new file mode 100644 index 00000000..6929317c --- /dev/null +++ b/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/templates/MockTimerTemplateDataProvider.kt @@ -0,0 +1,115 @@ +/* + Copyright 2024 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.notificationbuilder.internal.templates + +import android.os.Bundle +import com.adobe.marketing.mobile.notificationbuilder.PushTemplateConstants +import com.adobe.marketing.mobile.notificationbuilder.internal.PushTemplateType + +object MockTimerTemplateDataProvider { + internal fun getMockedMapWithTimerData( + isUsingDuration: Boolean, + duration: String + ): MutableMap { + val map = mutableMapOf( + PushTemplateConstants.PushPayloadKeys.TITLE to MOCKED_TITLE, + PushTemplateConstants.PushPayloadKeys.BODY to MOCKED_BODY, + PushTemplateConstants.PushPayloadKeys.EXPANDED_BODY_TEXT to MOCKED_EXPANDED_BODY, + PushTemplateConstants.PushPayloadKeys.IMAGE_URL to MOCKED_IMAGE_URI, + PushTemplateConstants.PushPayloadKeys.VERSION to MOCKED_PAYLOAD_VERSION, + PushTemplateConstants.PushPayloadKeys.TEMPLATE_TYPE to PushTemplateType.TIMER.value, + PushTemplateConstants.PushPayloadKeys.BODY_TEXT_COLOR to MOCKED_BODY_TEXT_COLOR, + PushTemplateConstants.PushPayloadKeys.SMALL_ICON to MOCKED_SMALL_ICON, + PushTemplateConstants.PushPayloadKeys.LARGE_ICON to MOCKED_LARGE_ICON, + PushTemplateConstants.PushPayloadKeys.SMALL_ICON_COLOR to MOCKED_SMALL_ICON_COLOR, + PushTemplateConstants.PushPayloadKeys.VISIBILITY to MOCKED_VISIBILITY, + PushTemplateConstants.PushPayloadKeys.PRIORITY to MOCKED_PRIORITY, + PushTemplateConstants.PushPayloadKeys.TICKER to MOCKED_TICKER, + PushTemplateConstants.PushPayloadKeys.STICKY to "true", + PushTemplateConstants.PushPayloadKeys.TAG to MOCKED_TAG, + PushTemplateConstants.PushPayloadKeys.ACTION_URI to MOCKED_URI, + PushTemplateConstants.PushPayloadKeys.TimerKeys.ALTERNATE_EXPANDED_BODY to MOCKED_ALT_EXPANDED_BODY, + PushTemplateConstants.PushPayloadKeys.TimerKeys.ALTERNATE_TITLE to MOCKED_ALT_TITLE, + PushTemplateConstants.PushPayloadKeys.TimerKeys.ALTERNATE_BODY to MOCKED_ALT_BODY, + PushTemplateConstants.PushPayloadKeys.TimerKeys.ALTERNATE_IMAGE to MOCKED_ALT_IMAGE_URI, + PushTemplateConstants.PushPayloadKeys.TimerKeys.TIMER_COLOR to MOCKED_TIMER_COLOR, + ) + if (isUsingDuration) { + map[PushTemplateConstants.PushPayloadKeys.TimerKeys.TIMER_DURATION] = duration + } else { + map[PushTemplateConstants.PushPayloadKeys.TimerKeys.TIMER_END_TIME] = + MOCKED_TIMER_EXPIRY_TIME + } + return map + } + + internal fun getMockedBundleWithTimerData(isUsingDuration: Boolean, duration: String): Bundle { + val mockBundle = Bundle() + mockBundle.putString(PushTemplateConstants.PushPayloadKeys.TITLE, MOCKED_TITLE) + mockBundle.putString(PushTemplateConstants.PushPayloadKeys.BODY, MOCKED_EXPANDED_BODY) + mockBundle.putString(PushTemplateConstants.PushPayloadKeys.EXPANDED_BODY_TEXT, MOCKED_BODY) + mockBundle.putString(PushTemplateConstants.PushPayloadKeys.IMAGE_URL, MOCKED_IMAGE_URI) + mockBundle.putString(PushTemplateConstants.PushPayloadKeys.VERSION, MOCKED_PAYLOAD_VERSION) + mockBundle.putString( + PushTemplateConstants.PushPayloadKeys.TEMPLATE_TYPE, + PushTemplateType.TIMER.value + ) + mockBundle.putString( + PushTemplateConstants.PushPayloadKeys.BODY_TEXT_COLOR, + MOCKED_BODY_TEXT_COLOR + ) + mockBundle.putString(PushTemplateConstants.PushPayloadKeys.SMALL_ICON, MOCKED_SMALL_ICON) + mockBundle.putString(PushTemplateConstants.PushPayloadKeys.LARGE_ICON, MOCKED_LARGE_ICON) + mockBundle.putString( + PushTemplateConstants.PushPayloadKeys.SMALL_ICON_COLOR, + MOCKED_SMALL_ICON_COLOR + ) + mockBundle.putString(PushTemplateConstants.PushPayloadKeys.VISIBILITY, MOCKED_VISIBILITY) + mockBundle.putString(PushTemplateConstants.PushPayloadKeys.PRIORITY, MOCKED_PRIORITY) + mockBundle.putString(PushTemplateConstants.PushPayloadKeys.TICKER, MOCKED_TICKER) + mockBundle.putString(PushTemplateConstants.PushPayloadKeys.TAG, MOCKED_TAG) + mockBundle.putString(PushTemplateConstants.PushPayloadKeys.STICKY, "true") + mockBundle.putString(PushTemplateConstants.PushPayloadKeys.ACTION_URI, MOCKED_URI) + mockBundle.putString( + PushTemplateConstants.PushPayloadKeys.TimerKeys.ALTERNATE_EXPANDED_BODY, + MOCKED_ALT_EXPANDED_BODY + ) + mockBundle.putString( + PushTemplateConstants.PushPayloadKeys.TimerKeys.ALTERNATE_TITLE, + MOCKED_ALT_TITLE + ) + mockBundle.putString( + PushTemplateConstants.PushPayloadKeys.TimerKeys.ALTERNATE_BODY, + MOCKED_ALT_BODY + ) + mockBundle.putString( + PushTemplateConstants.PushPayloadKeys.TimerKeys.ALTERNATE_IMAGE, + MOCKED_ALT_IMAGE_URI + ) + mockBundle.putString( + PushTemplateConstants.PushPayloadKeys.TimerKeys.TIMER_COLOR, + MOCKED_TIMER_COLOR + ) + if (isUsingDuration) { + mockBundle.putString( + PushTemplateConstants.PushPayloadKeys.TimerKeys.TIMER_DURATION, + duration + ) + } else { + mockBundle.putString( + PushTemplateConstants.PushPayloadKeys.TimerKeys.TIMER_END_TIME, + MOCKED_TIMER_EXPIRY_TIME + ) + } + return mockBundle + } +} From 66b7235ee9a5a87930e74505439bdb6df2e9f816 Mon Sep 17 00:00:00 2001 From: Ryan Morales Date: Thu, 20 Jun 2024 13:38:28 -0700 Subject: [PATCH 144/159] restore accidentally removed curly brace --- .../notificationbuilder/internal/templates/MockDataUtils.kt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/templates/MockDataUtils.kt b/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/templates/MockDataUtils.kt index 3553b4cb..51ee5d5d 100644 --- a/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/templates/MockDataUtils.kt +++ b/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/templates/MockDataUtils.kt @@ -201,6 +201,8 @@ internal fun provideMockedTimerTemplate( MapData(dataMap) } return TimerPushTemplate(data) +} + internal fun provideMockedProductCatalogTemplate(isFromIntent: Boolean = false): ProductCatalogPushTemplate { val data: NotificationData if (isFromIntent) { From 7371e099a2c6fc6d47d444757f91dbd5cbc01d2f Mon Sep 17 00:00:00 2001 From: Ryan Morales Date: Thu, 20 Jun 2024 13:45:53 -0700 Subject: [PATCH 145/159] fix breaks after merging latest main --- .../internal/NotificationBuilderTests.kt | 33 ++++++++++--------- .../AEPPushNotificationBuilderTest.kt | 7 ++++ .../AutoCarouselNotificationBuilderTest.kt | 5 +++ .../InputBoxNotificationBuilderTest.kt | 7 ++++ .../ProductCatalogNotificationBuilderTest.kt | 6 ++-- .../ZeroBezelNotificationBuilderTest.kt | 2 ++ 6 files changed, 41 insertions(+), 19 deletions(-) diff --git a/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/NotificationBuilderTests.kt b/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/NotificationBuilderTests.kt index fa6cb2db..7fb635ac 100644 --- a/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/NotificationBuilderTests.kt +++ b/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/NotificationBuilderTests.kt @@ -22,6 +22,7 @@ import androidx.core.app.NotificationManagerCompat import com.adobe.marketing.mobile.notificationbuilder.NotificationBuilder import com.adobe.marketing.mobile.notificationbuilder.NotificationConstructionFailedException import com.adobe.marketing.mobile.notificationbuilder.PushTemplateConstants +import com.adobe.marketing.mobile.notificationbuilder.RemindLaterHandler import com.adobe.marketing.mobile.notificationbuilder.internal.builders.AutoCarouselNotificationBuilder import com.adobe.marketing.mobile.notificationbuilder.internal.builders.BasicNotificationBuilder import com.adobe.marketing.mobile.notificationbuilder.internal.builders.InputBoxNotificationBuilder @@ -79,6 +80,11 @@ class NotificationBuilderTests { mockkObject(PushTemplateImageUtils) } + @After + fun tearDown() { + unmockkAll() + } + private fun setupNotificationBuilderMocks() { mockkObject(BasicNotificationBuilder) mockkObject(ManualCarouselNotificationBuilder) @@ -109,11 +115,6 @@ class NotificationBuilderTests { ServiceProvider.getInstance().appContextService.setApplication(application) } - @After - fun tearDown() { - unmockkAll() - } - @Test fun `NotificationBuilder version values matches the expected version`() { val version = NotificationBuilder.version() @@ -208,7 +209,7 @@ class NotificationBuilderTests { val remindIntent = Intent() remindIntent.putExtras(bundle) remindIntent.action = PushTemplateConstants.IntentActions.REMIND_LATER_CLICKED - NotificationBuilder.handleRemindIntent( + RemindLaterHandler.handleRemindIntent( remindIntent, broadcastReceiverClass ) @@ -223,7 +224,7 @@ class NotificationBuilderTests { val remindIntent = Intent() remindIntent.putExtras(bundle) remindIntent.action = PushTemplateConstants.IntentActions.REMIND_LATER_CLICKED - NotificationBuilder.handleRemindIntent( + RemindLaterHandler.handleRemindIntent( remindIntent, null ) @@ -239,7 +240,7 @@ class NotificationBuilderTests { val remindIntent = Intent() remindIntent.putExtras(bundle) remindIntent.action = PushTemplateConstants.IntentActions.REMIND_LATER_CLICKED - NotificationBuilder.handleRemindIntent( + RemindLaterHandler.handleRemindIntent( remindIntent, null ) @@ -255,7 +256,7 @@ class NotificationBuilderTests { val remindIntent = Intent() remindIntent.putExtras(bundle) remindIntent.action = PushTemplateConstants.IntentActions.REMIND_LATER_CLICKED - NotificationBuilder.handleRemindIntent( + RemindLaterHandler.handleRemindIntent( remindIntent, broadcastReceiverClass ) @@ -272,7 +273,7 @@ class NotificationBuilderTests { val remindIntent = Intent() remindIntent.putExtras(bundle) remindIntent.action = PushTemplateConstants.IntentActions.REMIND_LATER_CLICKED - NotificationBuilder.handleRemindIntent( + RemindLaterHandler.handleRemindIntent( remindIntent, broadcastReceiverClass ) @@ -289,7 +290,7 @@ class NotificationBuilderTests { val remindIntent = Intent() remindIntent.putExtras(bundle) remindIntent.action = PushTemplateConstants.IntentActions.REMIND_LATER_CLICKED - NotificationBuilder.handleRemindIntent( + RemindLaterHandler.handleRemindIntent( remindIntent, broadcastReceiverClass ) @@ -307,7 +308,7 @@ class NotificationBuilderTests { val remindIntent = Intent() remindIntent.putExtras(bundle) remindIntent.action = PushTemplateConstants.IntentActions.REMIND_LATER_CLICKED - NotificationBuilder.handleRemindIntent( + RemindLaterHandler.handleRemindIntent( remindIntent, broadcastReceiverClass ) @@ -324,7 +325,7 @@ class NotificationBuilderTests { val remindIntent = Intent() remindIntent.putExtras(bundle) remindIntent.action = PushTemplateConstants.IntentActions.REMIND_LATER_CLICKED - NotificationBuilder.handleRemindIntent( + RemindLaterHandler.handleRemindIntent( remindIntent, broadcastReceiverClass ) @@ -341,7 +342,7 @@ class NotificationBuilderTests { val remindIntent = Intent() remindIntent.putExtras(bundle) remindIntent.action = PushTemplateConstants.IntentActions.REMIND_LATER_CLICKED - NotificationBuilder.handleRemindIntent( + RemindLaterHandler.handleRemindIntent( remindIntent, broadcastReceiverClass ) @@ -357,7 +358,7 @@ class NotificationBuilderTests { val remindIntent = Intent() remindIntent.putExtras(bundle) remindIntent.action = PushTemplateConstants.IntentActions.REMIND_LATER_CLICKED - NotificationBuilder.handleRemindIntent( + RemindLaterHandler.handleRemindIntent( remindIntent, broadcastReceiverClass ) @@ -370,7 +371,7 @@ class NotificationBuilderTests { fun `handleRemindIntent given an intent with no bundle should throw an exception`() { val remindIntent = Intent() remindIntent.action = PushTemplateConstants.IntentActions.REMIND_LATER_CLICKED - NotificationBuilder.handleRemindIntent( + RemindLaterHandler.handleRemindIntent( remindIntent, broadcastReceiverClass ) diff --git a/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/AEPPushNotificationBuilderTest.kt b/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/AEPPushNotificationBuilderTest.kt index 68a47d82..4680d60b 100644 --- a/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/AEPPushNotificationBuilderTest.kt +++ b/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/AEPPushNotificationBuilderTest.kt @@ -31,9 +31,11 @@ import com.adobe.marketing.mobile.notificationbuilder.internal.templates.MockAEP import com.adobe.marketing.mobile.notificationbuilder.internal.templates.replaceValueInMap import com.adobe.marketing.mobile.notificationbuilder.internal.util.IntentData import com.adobe.marketing.mobile.notificationbuilder.internal.util.MapData +import io.mockk.unmockkAll import junit.framework.TestCase.assertEquals import junit.framework.TestCase.assertNotNull import junit.framework.TestCase.assertNull +import org.junit.After import org.junit.Assert.assertArrayEquals import org.junit.Before import org.junit.Test @@ -70,6 +72,11 @@ class AEPPushNotificationBuilderTest { mockBundle = MockAEPPushTemplateDataProvider.getMockedAEPBundleWithAllKeys() } + @After + fun tearDown() { + unmockkAll() + } + @Test fun `verify construct should map valid data fields to notification data`() { val pushTemplate = BasicPushTemplate(MapData(dataMap)) diff --git a/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/AutoCarouselNotificationBuilderTest.kt b/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/AutoCarouselNotificationBuilderTest.kt index c611eb49..25c6e0ae 100644 --- a/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/AutoCarouselNotificationBuilderTest.kt +++ b/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/AutoCarouselNotificationBuilderTest.kt @@ -65,6 +65,11 @@ class AutoCarouselNotificationBuilderTest { mockkConstructor(RemoteViews::class) } + @After + fun tearDown() { + unmockkAll() + } + @Test fun `construct returns NotificationCompat Builder for valid inputs`() { val result = AutoCarouselNotificationBuilder.construct( diff --git a/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/InputBoxNotificationBuilderTest.kt b/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/InputBoxNotificationBuilderTest.kt index a11a51d9..d362bc8b 100644 --- a/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/InputBoxNotificationBuilderTest.kt +++ b/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/InputBoxNotificationBuilderTest.kt @@ -40,7 +40,9 @@ import io.mockk.Runs import io.mockk.every import io.mockk.just import io.mockk.mockkConstructor +import io.mockk.unmockkAll import io.mockk.verify +import org.junit.After import org.junit.Before import org.junit.Test import org.junit.runner.RunWith @@ -72,6 +74,11 @@ class InputBoxNotificationBuilderTest { mockkConstructor(RemoteViews::class) } + @After + fun tearDown() { + unmockkAll() + } + @Test fun `construct should return a NotificationCompat Builder`() { val pushTemplate = provideMockedInputBoxPushTemplateWithRequiredData() diff --git a/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/ProductCatalogNotificationBuilderTest.kt b/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/ProductCatalogNotificationBuilderTest.kt index 3921a5fe..9e0bc29f 100644 --- a/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/ProductCatalogNotificationBuilderTest.kt +++ b/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/ProductCatalogNotificationBuilderTest.kt @@ -28,10 +28,10 @@ import com.adobe.marketing.mobile.notificationbuilder.internal.templates.Product import com.adobe.marketing.mobile.notificationbuilder.internal.templates.provideMockedProductCatalogTemplate import com.adobe.marketing.mobile.notificationbuilder.internal.templates.replaceValueInMap import com.adobe.marketing.mobile.notificationbuilder.internal.util.MapData -import io.mockk.clearAllMocks import io.mockk.every import io.mockk.mockkClass import io.mockk.mockkObject +import io.mockk.unmockkAll import org.junit.After import org.junit.Before import org.junit.Test @@ -63,8 +63,8 @@ class ProductCatalogNotificationBuilderTest { } @After - fun cleanup() { - clearAllMocks() + fun tearDown() { + unmockkAll() } @Test diff --git a/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/ZeroBezelNotificationBuilderTest.kt b/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/ZeroBezelNotificationBuilderTest.kt index f110eea0..559c0e5e 100644 --- a/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/ZeroBezelNotificationBuilderTest.kt +++ b/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/ZeroBezelNotificationBuilderTest.kt @@ -33,9 +33,11 @@ import io.mockk.just import io.mockk.mockk import io.mockk.mockkConstructor import io.mockk.mockkObject +import io.mockk.unmockkAll import io.mockk.verify import junit.framework.TestCase.assertEquals import junit.framework.TestCase.assertNotNull +import org.junit.After import org.junit.Before import org.junit.Test import org.junit.runner.RunWith From 1eaa145a2fe9618373ab1152b81a75dece9de553 Mon Sep 17 00:00:00 2001 From: Ryan Morales Date: Thu, 20 Jun 2024 13:57:58 -0700 Subject: [PATCH 146/159] fix lint error --- .../internal/builders/ZeroBezelNotificationBuilderTest.kt | 2 -- 1 file changed, 2 deletions(-) diff --git a/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/ZeroBezelNotificationBuilderTest.kt b/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/ZeroBezelNotificationBuilderTest.kt index 559c0e5e..f110eea0 100644 --- a/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/ZeroBezelNotificationBuilderTest.kt +++ b/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/ZeroBezelNotificationBuilderTest.kt @@ -33,11 +33,9 @@ import io.mockk.just import io.mockk.mockk import io.mockk.mockkConstructor import io.mockk.mockkObject -import io.mockk.unmockkAll import io.mockk.verify import junit.framework.TestCase.assertEquals import junit.framework.TestCase.assertNotNull -import org.junit.After import org.junit.Before import org.junit.Test import org.junit.runner.RunWith From 0fa16c78e0648978ea29994727e492def1e90ab6 Mon Sep 17 00:00:00 2001 From: Ryan Morales Date: Thu, 20 Jun 2024 15:52:32 -0700 Subject: [PATCH 147/159] remove handleRemindIntent tests from NotificationBuilderTests - also make bitmap mockk relaxed in ZeroBezelNotificationBuilderTest --- .../internal/NotificationBuilderTests.kt | 178 ------------------ .../ZeroBezelNotificationBuilderTest.kt | 9 +- 2 files changed, 8 insertions(+), 179 deletions(-) diff --git a/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/NotificationBuilderTests.kt b/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/NotificationBuilderTests.kt index 7fb635ac..d51dd103 100644 --- a/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/NotificationBuilderTests.kt +++ b/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/NotificationBuilderTests.kt @@ -22,7 +22,6 @@ import androidx.core.app.NotificationManagerCompat import com.adobe.marketing.mobile.notificationbuilder.NotificationBuilder import com.adobe.marketing.mobile.notificationbuilder.NotificationConstructionFailedException import com.adobe.marketing.mobile.notificationbuilder.PushTemplateConstants -import com.adobe.marketing.mobile.notificationbuilder.RemindLaterHandler import com.adobe.marketing.mobile.notificationbuilder.internal.builders.AutoCarouselNotificationBuilder import com.adobe.marketing.mobile.notificationbuilder.internal.builders.BasicNotificationBuilder import com.adobe.marketing.mobile.notificationbuilder.internal.builders.InputBoxNotificationBuilder @@ -203,183 +202,6 @@ class NotificationBuilderTests { assertNull(notificationBuilder) } - @Test - fun `handleRemindIntent given a valid intent should schedule a remind notification`() { - val bundle = MockAEPPushTemplateDataProvider.getMockedAEPBundleWithAllKeys() - val remindIntent = Intent() - remindIntent.putExtras(bundle) - remindIntent.action = PushTemplateConstants.IntentActions.REMIND_LATER_CLICKED - RemindLaterHandler.handleRemindIntent( - remindIntent, - broadcastReceiverClass - ) - - verify(exactly = 1) { PendingIntentUtils.scheduleNotification(any(), any(), any(), any()) } - verify(exactly = 1) { notificationManagerCompat.cancel(any()) } - } - - @Test - fun `handleRemindIntent given a valid intent with no broadcast receiver should not schedule a remind notification`() { - val bundle = MockAEPPushTemplateDataProvider.getMockedAEPBundleWithAllKeys() - val remindIntent = Intent() - remindIntent.putExtras(bundle) - remindIntent.action = PushTemplateConstants.IntentActions.REMIND_LATER_CLICKED - RemindLaterHandler.handleRemindIntent( - remindIntent, - null - ) - - verify(exactly = 0) { PendingIntentUtils.scheduleNotification(any(), any(), any(), any()) } - verify(exactly = 1) { notificationManagerCompat.cancel(any()) } - } - - @Test - fun `handleRemindIntent given a valid intent with no broadcast receiver and tag should not schedule a remind notification and should not cancel the current notification`() { - val bundle = MockAEPPushTemplateDataProvider.getMockedAEPBundleWithAllKeys() - bundle.remove(PushTemplateConstants.PushPayloadKeys.TAG) - val remindIntent = Intent() - remindIntent.putExtras(bundle) - remindIntent.action = PushTemplateConstants.IntentActions.REMIND_LATER_CLICKED - RemindLaterHandler.handleRemindIntent( - remindIntent, - null - ) - - verify(exactly = 0) { PendingIntentUtils.scheduleNotification(any(), any(), any(), any()) } - verify(exactly = 0) { notificationManagerCompat.cancel(any()) } - } - - @Test - fun `handleRemindIntent given a valid intent with a remind later timestamp should schedule a remind notification`() { - val bundle = MockAEPPushTemplateDataProvider.getMockedAEPBundleWithAllKeys() - bundle.remove(PushTemplateConstants.PushPayloadKeys.REMIND_LATER_DURATION) - val remindIntent = Intent() - remindIntent.putExtras(bundle) - remindIntent.action = PushTemplateConstants.IntentActions.REMIND_LATER_CLICKED - RemindLaterHandler.handleRemindIntent( - remindIntent, - broadcastReceiverClass - ) - - verify(exactly = 1) { PendingIntentUtils.scheduleNotification(any(), any(), any(), any()) } - verify(exactly = 1) { notificationManagerCompat.cancel(any()) } - } - - @Test - fun `handleRemindIntent given a valid intent with a remind later timestamp and no tag should schedule a remind notification but should not cancel the current notification`() { - val bundle = MockAEPPushTemplateDataProvider.getMockedAEPBundleWithAllKeys() - bundle.remove(PushTemplateConstants.PushPayloadKeys.REMIND_LATER_DURATION) - bundle.remove(PushTemplateConstants.PushPayloadKeys.TAG) - val remindIntent = Intent() - remindIntent.putExtras(bundle) - remindIntent.action = PushTemplateConstants.IntentActions.REMIND_LATER_CLICKED - RemindLaterHandler.handleRemindIntent( - remindIntent, - broadcastReceiverClass - ) - - verify(exactly = 1) { PendingIntentUtils.scheduleNotification(any(), any(), any(), any()) } - verify(exactly = 0) { notificationManagerCompat.cancel(any()) } - } - - @Test(expected = IllegalArgumentException::class) - fun `handleRemindIntent given an intent with no remind later timestamp or duration should throw an exception`() { - val bundle = MockAEPPushTemplateDataProvider.getMockedAEPBundleWithAllKeys() - bundle.remove(PushTemplateConstants.PushPayloadKeys.REMIND_LATER_DURATION) - bundle.remove(PushTemplateConstants.PushPayloadKeys.REMIND_LATER_TIMESTAMP) - val remindIntent = Intent() - remindIntent.putExtras(bundle) - remindIntent.action = PushTemplateConstants.IntentActions.REMIND_LATER_CLICKED - RemindLaterHandler.handleRemindIntent( - remindIntent, - broadcastReceiverClass - ) - - verify(exactly = 0) { PendingIntentUtils.scheduleNotification(any(), any(), any(), any()) } - verify(exactly = 1) { notificationManagerCompat.cancel(any()) } - } - - @Test(expected = IllegalArgumentException::class) - fun `handleRemindIntent given an intent with no remind later timestamp, duration, and tag should throw an exception but should not cancel the current notification`() { - val bundle = MockAEPPushTemplateDataProvider.getMockedAEPBundleWithAllKeys() - bundle.remove(PushTemplateConstants.PushPayloadKeys.REMIND_LATER_DURATION) - bundle.remove(PushTemplateConstants.PushPayloadKeys.REMIND_LATER_TIMESTAMP) - bundle.remove(PushTemplateConstants.PushPayloadKeys.TAG) - val remindIntent = Intent() - remindIntent.putExtras(bundle) - remindIntent.action = PushTemplateConstants.IntentActions.REMIND_LATER_CLICKED - RemindLaterHandler.handleRemindIntent( - remindIntent, - broadcastReceiverClass - ) - - verify(exactly = 0) { PendingIntentUtils.scheduleNotification(any(), any(), any(), any()) } - verify(exactly = 0) { notificationManagerCompat.cancel(any()) } - } - - @Test(expected = IllegalArgumentException::class) - fun `handleRemindIntent given an intent with an invalid remind later timestamp and duration should throw an exception`() { - val bundle = MockAEPPushTemplateDataProvider.getMockedAEPBundleWithAllKeys() - bundle.putString(PushTemplateConstants.PushPayloadKeys.REMIND_LATER_DURATION, "duration") - bundle.putString(PushTemplateConstants.PushPayloadKeys.REMIND_LATER_TIMESTAMP, "timestamp") - val remindIntent = Intent() - remindIntent.putExtras(bundle) - remindIntent.action = PushTemplateConstants.IntentActions.REMIND_LATER_CLICKED - RemindLaterHandler.handleRemindIntent( - remindIntent, - broadcastReceiverClass - ) - - verify(exactly = 0) { PendingIntentUtils.scheduleNotification(any(), any(), any(), any()) } - verify(exactly = 0) { notificationManagerCompat.cancel(any()) } - } - - @Test(expected = IllegalArgumentException::class) - fun `handleRemindIntent given a valid intent with a remind later timestamp before the current date should throw an exception`() { - val bundle = MockAEPPushTemplateDataProvider.getMockedAEPBundleWithAllKeys() - bundle.remove(PushTemplateConstants.PushPayloadKeys.REMIND_LATER_DURATION) - bundle.putString(PushTemplateConstants.PushPayloadKeys.REMIND_LATER_TIMESTAMP, "1234567890") - val remindIntent = Intent() - remindIntent.putExtras(bundle) - remindIntent.action = PushTemplateConstants.IntentActions.REMIND_LATER_CLICKED - RemindLaterHandler.handleRemindIntent( - remindIntent, - broadcastReceiverClass - ) - - verify(exactly = 0) { PendingIntentUtils.scheduleNotification(any(), any(), any(), any()) } - verify(exactly = 0) { notificationManagerCompat.cancel(any()) } - } - - @Test(expected = NotificationConstructionFailedException::class) - fun `handleRemindIntent given a valid intent but no context is available should throw an exception`() { - setNullContext() - val bundle = MockAEPPushTemplateDataProvider.getMockedAEPBundleWithAllKeys() - val remindIntent = Intent() - remindIntent.putExtras(bundle) - remindIntent.action = PushTemplateConstants.IntentActions.REMIND_LATER_CLICKED - RemindLaterHandler.handleRemindIntent( - remindIntent, - broadcastReceiverClass - ) - - verify(exactly = 0) { PendingIntentUtils.scheduleNotification(any(), any(), any(), any()) } - verify(exactly = 0) { notificationManagerCompat.cancel(any()) } - } - - @Test(expected = NotificationConstructionFailedException::class) - fun `handleRemindIntent given an intent with no bundle should throw an exception`() { - val remindIntent = Intent() - remindIntent.action = PushTemplateConstants.IntentActions.REMIND_LATER_CLICKED - RemindLaterHandler.handleRemindIntent( - remindIntent, - broadcastReceiverClass - ) - - verify(exactly = 0) { PendingIntentUtils.scheduleNotification(any(), any(), any(), any()) } - verify(exactly = 0) { notificationManagerCompat.cancel(any()) } - } - @Test fun `verify private createNotificationBuilder calls LegacyNotificationBuilder construct`() { val mapData = MockAEPPushTemplateDataProvider.getMockedAEPDataMapWithAllKeys() diff --git a/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/ZeroBezelNotificationBuilderTest.kt b/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/ZeroBezelNotificationBuilderTest.kt index f110eea0..66f2796e 100644 --- a/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/ZeroBezelNotificationBuilderTest.kt +++ b/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/ZeroBezelNotificationBuilderTest.kt @@ -33,9 +33,11 @@ import io.mockk.just import io.mockk.mockk import io.mockk.mockkConstructor import io.mockk.mockkObject +import io.mockk.unmockkAll import io.mockk.verify import junit.framework.TestCase.assertEquals import junit.framework.TestCase.assertNotNull +import org.junit.After import org.junit.Before import org.junit.Test import org.junit.runner.RunWith @@ -60,12 +62,17 @@ class ZeroBezelNotificationBuilderTest { dataMap = MockAEPPushTemplateDataProvider.getMockedAEPDataMapWithAllKeys() mockkObject(PushTemplateImageUtils) mockkConstructor(RemoteViews::class) - mockBitmap = mockk() + mockBitmap = mockk(relaxed = true) trackerActivityClass = DummyActivity::class.java mockBundle = MockAEPPushTemplateDataProvider.getMockedAEPBundleWithAllKeys() every { getCachedImage(any()) } answers { mockBitmap } } + @After + fun tearDown() { + unmockkAll() + } + @Test fun `verify construct with image downloaded and collapsedStyle is img`() { val pushTemplate = ZeroBezelPushTemplate(MapData(dataMap)) From 53c736e9e56b898041e354a6e9e0bceb259a8d2f Mon Sep 17 00:00:00 2001 From: Ishita Gambhir Date: Fri, 21 Jun 2024 20:24:18 +0530 Subject: [PATCH 148/159] MOB-20866: Add additional tests for BasicNotificationBuilderTest (#51) --- .../builders/BasicNotificationBuilderTest.kt | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/BasicNotificationBuilderTest.kt b/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/BasicNotificationBuilderTest.kt index 2c2f58de..8dfe9a00 100644 --- a/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/BasicNotificationBuilderTest.kt +++ b/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/BasicNotificationBuilderTest.kt @@ -15,10 +15,15 @@ import android.app.Activity import android.content.BroadcastReceiver import android.content.Context import android.content.Intent +import android.widget.RemoteViews import androidx.core.app.NotificationCompat import com.adobe.marketing.mobile.notificationbuilder.PushTemplateConstants +import com.adobe.marketing.mobile.notificationbuilder.R +import com.adobe.marketing.mobile.notificationbuilder.internal.PushTemplateImageUtils +import com.adobe.marketing.mobile.notificationbuilder.internal.extensions.setRemoteViewImage import com.adobe.marketing.mobile.notificationbuilder.internal.templates.BasicPushTemplate import com.adobe.marketing.mobile.notificationbuilder.internal.templates.MOCKED_CHANNEL_ID +import com.adobe.marketing.mobile.notificationbuilder.internal.templates.MOCKED_IMAGE_URI import com.adobe.marketing.mobile.notificationbuilder.internal.templates.MOCKED_TAG import com.adobe.marketing.mobile.notificationbuilder.internal.templates.MOCKED_TICKER import com.adobe.marketing.mobile.notificationbuilder.internal.templates.MOCK_REMIND_LATER_DURATION @@ -28,7 +33,15 @@ import com.adobe.marketing.mobile.notificationbuilder.internal.templates.MockAEP import com.adobe.marketing.mobile.notificationbuilder.internal.templates.provideMockedBasicPushTemplateWithAllKeys import com.adobe.marketing.mobile.notificationbuilder.internal.templates.provideMockedBasicPushTemplateWithRequiredData import com.adobe.marketing.mobile.notificationbuilder.internal.util.MapData +import com.google.common.base.Verify.verify +import io.mockk.every +import io.mockk.mockkConstructor +import io.mockk.mockkObject +import io.mockk.mockkStatic +import io.mockk.unmockkAll +import io.mockk.verify import junit.framework.TestCase.assertEquals +import org.junit.After import org.junit.Assert.assertNull import org.junit.Before import org.junit.Test @@ -57,10 +70,21 @@ class BasicNotificationBuilderTest { context = RuntimeEnvironment.getApplication() trackerActivityClass = DummyActivity::class.java broadcastReceiverClass = DummyBroadcastReceiver::class.java + + mockkConstructor(RemoteViews::class) + mockkStatic(RemoteViews::setRemoteViewImage) + mockkObject(PushTemplateImageUtils) + } + + @After + fun teardown() { + unmockkAll() } @Test fun `construct should return a NotificationCompat Builder`() { + every { any().setRemoteViewImage(any(), any()) } returns true + val pushTemplate = provideMockedBasicPushTemplateWithAllKeys() val notificationBuilder = BasicNotificationBuilder.construct( context, @@ -70,6 +94,7 @@ class BasicNotificationBuilderTest { ) assertEquals(NotificationCompat.Builder::class.java, notificationBuilder.javaClass) + verify(exactly = 1) { any().setRemoteViewImage(MOCKED_IMAGE_URI, R.id.expanded_template_image) } } @Test From f216324c0b99463ad215dd6d92322956b68269aa Mon Sep 17 00:00:00 2001 From: Ishita Gambhir Date: Fri, 21 Jun 2024 20:33:08 +0530 Subject: [PATCH 149/159] MOB-21194: Add unit tests for ProductRatingNotificationBuilder (#47) * MOB-21194: Add unit tests for ProductRatingNotificationBuilder * Address PR comments * address PR comments --- .../ProductRatingNotificationBuilderTest.kt | 197 ++++++++++++++++++ .../internal/templates/MockDataUtils.kt | 12 ++ .../MockProductRatingTemplateDataProvider.kt | 52 +++++ 3 files changed, 261 insertions(+) create mode 100644 code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/ProductRatingNotificationBuilderTest.kt create mode 100644 code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/templates/MockProductRatingTemplateDataProvider.kt diff --git a/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/ProductRatingNotificationBuilderTest.kt b/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/ProductRatingNotificationBuilderTest.kt new file mode 100644 index 00000000..0cf9da7e --- /dev/null +++ b/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/builders/ProductRatingNotificationBuilderTest.kt @@ -0,0 +1,197 @@ +/* + Copyright 2024 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.notificationbuilder.internal.builders + +import android.app.Activity +import android.content.BroadcastReceiver +import android.content.Context +import android.graphics.Bitmap +import android.view.View +import android.widget.RemoteViews +import androidx.core.app.NotificationCompat +import com.adobe.marketing.mobile.notificationbuilder.NotificationConstructionFailedException +import com.adobe.marketing.mobile.notificationbuilder.PushTemplateConstants +import com.adobe.marketing.mobile.notificationbuilder.R +import com.adobe.marketing.mobile.notificationbuilder.internal.PushTemplateImageUtils +import com.adobe.marketing.mobile.notificationbuilder.internal.PushTemplateImageUtils.cacheImages +import com.adobe.marketing.mobile.notificationbuilder.internal.PushTemplateImageUtils.getCachedImage +import com.adobe.marketing.mobile.notificationbuilder.internal.extensions.setRemoteViewClickAction +import com.adobe.marketing.mobile.notificationbuilder.internal.extensions.setRemoteViewImage +import com.adobe.marketing.mobile.notificationbuilder.internal.templates.MockProductRatingTemplateDataProvider.getMockedDataMapForRatingTemplate +import com.adobe.marketing.mobile.notificationbuilder.internal.templates.ProductRatingPushTemplate +import com.adobe.marketing.mobile.notificationbuilder.internal.templates.provideMockedProductRatingTemplate +import com.adobe.marketing.mobile.notificationbuilder.internal.util.MapData +import io.mockk.Runs +import io.mockk.every +import io.mockk.just +import io.mockk.mockkClass +import io.mockk.mockkConstructor +import io.mockk.mockkObject +import io.mockk.mockkStatic +import io.mockk.unmockkAll +import io.mockk.verify +import org.junit.After +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.robolectric.RobolectricTestRunner +import org.robolectric.RuntimeEnvironment +import org.robolectric.annotation.Config +import kotlin.test.assertEquals +import kotlin.test.assertFailsWith + +@RunWith(RobolectricTestRunner::class) +@Config(sdk = [31]) +class ProductRatingNotificationBuilderTest { + private lateinit var context: Context + private lateinit var trackerActivityClass: Class + private lateinit var broadcastReceiverClass: Class + + @Before + fun setUp() { + context = RuntimeEnvironment.getApplication() + trackerActivityClass = DummyActivity::class.java + broadcastReceiverClass = DummyBroadcastReceiver::class.java + mockkConstructor(RemoteViews::class) + mockkStatic(RemoteViews::setRemoteViewImage) + mockkStatic(RemoteViews::setRemoteViewClickAction) + mockkObject(PushTemplateImageUtils) + } + + @After + fun teardown() { + unmockkAll() + } + + @Test + fun `construct should return a NotificationCompat Builder`() { + every { any().setRemoteViewImage(any(), any()) } returns true + every { anyConstructed().setOnClickPendingIntent(any(), any()) } just Runs + + val pushTemplate = provideMockedProductRatingTemplate() + val notificationBuilder = ProductRatingNotificationBuilder.construct(context, pushTemplate, trackerActivityClass, broadcastReceiverClass) + + // 4 rating icons + 1 setting view visibility for rating confirmation + verify(exactly = 5) { anyConstructed().setOnClickPendingIntent(R.id.rating_icon_image, any()) } + assertEquals(NotificationCompat.Builder::class.java, notificationBuilder::class.java) + } + + @Test + fun `construct should set visibility of expanded layout image view as GONE if no images are downloaded`() { + every { cacheImages(any()) } answers { 0 } + every { any().setRemoteViewImage(any(), any()) } returns true + every { anyConstructed().setViewVisibility(any(), View.GONE) } just Runs + + val pushTemplate = provideMockedProductRatingTemplate() + val notificationBuilder = ProductRatingNotificationBuilder.construct(context, pushTemplate, trackerActivityClass, broadcastReceiverClass) + + verify(exactly = 1) { anyConstructed().setViewVisibility(R.id.expanded_template_image, View.GONE) } + } + + @Test + fun `construct should set image for expanded layout if image is downloaded successfully`() { + val cachedItem = mockkClass(Bitmap::class) + + every { cacheImages(any()) } answers { 1 } + every { getCachedImage(any()) } answers { cachedItem } + every { any().setRemoteViewImage(any(), any()) } returns true + every { anyConstructed().setViewVisibility(any(), View.GONE) } just Runs + + val pushTemplate = provideMockedProductRatingTemplate() + val notificationBuilder = ProductRatingNotificationBuilder.construct(context, pushTemplate, trackerActivityClass, broadcastReceiverClass) + + verify(exactly = 1) { anyConstructed().setImageViewBitmap(R.id.expanded_template_image, cachedItem) } + } + + @Test + fun `Rating Confirmation action should be hidden if no rating has been selected`() { + every { any().setRemoteViewImage(any(), any()) } returns true + every { anyConstructed().setViewVisibility(any(), any()) } just Runs + + val pushTemplate = provideMockedProductRatingTemplate() + val notificationBuilder = ProductRatingNotificationBuilder.construct(context, pushTemplate, trackerActivityClass, broadcastReceiverClass) + + verify(exactly = 1) { anyConstructed().setViewVisibility(R.id.rating_confirm, View.INVISIBLE) } + } + + @Test + fun `Rating confirmation action should be visible when rating is selected`() { + every { any().setRemoteViewImage(any(), any()) } returns true + every { anyConstructed().setViewVisibility(any(), any()) } just Runs + + val dataMap = getMockedDataMapForRatingTemplate() + dataMap[PushTemplateConstants.IntentKeys.RATING_SELECTED] = "3" + val pushTemplate = ProductRatingPushTemplate(MapData(dataMap)) + val notificationBuilder = ProductRatingNotificationBuilder.construct(context, pushTemplate, trackerActivityClass, broadcastReceiverClass) + + verify(exactly = 1) { anyConstructed().setViewVisibility(R.id.rating_confirm, View.VISIBLE) } + verify(exactly = 1) { any().setRemoteViewClickAction(any(), trackerActivityClass, R.id.rating_confirm, any(), "3", any()) } + } + + @Test + fun `construct should throw NotificationConstructionFailedException if image for unselected rating icon is invalid`() { + every { any().setRemoteViewImage("rating_star_outline", any()) } returns false + every { any().setRemoteViewImage("rating_star_filled", any()) } returns true + + val dataMap = getMockedDataMapForRatingTemplate() + dataMap[PushTemplateConstants.IntentKeys.RATING_SELECTED] = "3" + val pushTemplate = ProductRatingPushTemplate(MapData(dataMap)) + + assertFailsWith( + exceptionClass = NotificationConstructionFailedException::class, + message = "Image for unselected rating icon is invalid.", + block = { + ProductRatingNotificationBuilder.construct(context, pushTemplate, trackerActivityClass, broadcastReceiverClass) + } + ) + } + + @Test + fun `construct should throw NotificationConstructionFailedException if image for selected rating icon is invalid`() { + every { any().setRemoteViewImage("rating_star_outline", any()) } returns true + every { any().setRemoteViewImage("rating_star_filled", any()) } returns false + + val dataMap = getMockedDataMapForRatingTemplate() + dataMap[PushTemplateConstants.IntentKeys.RATING_SELECTED] = "3" + val pushTemplate = ProductRatingPushTemplate(MapData(dataMap)) + + assertFailsWith( + exceptionClass = NotificationConstructionFailedException::class, + message = "Image for selected rating icon is invalid.", + block = { + ProductRatingNotificationBuilder.construct(context, pushTemplate, trackerActivityClass, broadcastReceiverClass) + } + ) + } + + @Test + fun `construct should return a valid NotificationCompat Builder if Broadcast Receiver is not provided`() { + every { any().setRemoteViewImage(any(), any()) } returns true + + val pushTemplate = provideMockedProductRatingTemplate() + val notificationBuilder = ProductRatingNotificationBuilder.construct(context, pushTemplate, trackerActivityClass, null) + + assertEquals(NotificationCompat.Builder::class.java, notificationBuilder::class.java) + } + + @Test + fun `construct should return a NotificationCompat Builder if Tag is provided in Push Template`() { + every { any().setRemoteViewImage(any(), any()) } returns true + + val dataMap = getMockedDataMapForRatingTemplate() + dataMap[PushTemplateConstants.PushPayloadKeys.TAG] = "tag" + val pushTemplate = ProductRatingPushTemplate(MapData(dataMap)) + val notificationBuilder = ProductRatingNotificationBuilder.construct(context, pushTemplate, trackerActivityClass, broadcastReceiverClass) + + assertEquals(NotificationCompat.Builder::class.java, notificationBuilder::class.java) + } +} diff --git a/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/templates/MockDataUtils.kt b/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/templates/MockDataUtils.kt index ef276200..8cd595fa 100644 --- a/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/templates/MockDataUtils.kt +++ b/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/templates/MockDataUtils.kt @@ -187,3 +187,15 @@ internal fun provideMockedProductCatalogTemplate(isFromIntent: Boolean = false): } return ProductCatalogPushTemplate(data) } + +internal fun provideMockedProductRatingTemplate(isFromIntent: Boolean = false): ProductRatingPushTemplate { + val data: NotificationData + if (isFromIntent) { + val mockBundle = MockProductRatingTemplateDataProvider.getMockedBundleForRatingTemplate() + data = IntentData(mockBundle, null) + } else { + val dataMap = MockProductRatingTemplateDataProvider.getMockedDataMapForRatingTemplate() + data = MapData(dataMap) + } + return ProductRatingPushTemplate(data) +} diff --git a/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/templates/MockProductRatingTemplateDataProvider.kt b/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/templates/MockProductRatingTemplateDataProvider.kt new file mode 100644 index 00000000..4e6a39fe --- /dev/null +++ b/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/templates/MockProductRatingTemplateDataProvider.kt @@ -0,0 +1,52 @@ +/* + Copyright 2024 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.notificationbuilder.internal.templates + +import android.os.Bundle +import com.adobe.marketing.mobile.notificationbuilder.PushTemplateConstants +import com.adobe.marketing.mobile.notificationbuilder.internal.PushTemplateType + +object MockProductRatingTemplateDataProvider { + fun getMockedDataMapForRatingTemplate(): MutableMap { + return mutableMapOf( + PushTemplateConstants.PushPayloadKeys.VERSION to MOCKED_PAYLOAD_VERSION, + PushTemplateConstants.PushPayloadKeys.TEMPLATE_TYPE to PushTemplateType.PRODUCT_RATING.value, + PushTemplateConstants.PushPayloadKeys.TITLE to MOCKED_TITLE, + PushTemplateConstants.PushPayloadKeys.BODY to MOCKED_BODY, + PushTemplateConstants.PushPayloadKeys.EXPANDED_BODY_TEXT to MOCKED_BASIC_TEMPLATE_BODY_EXPANDED, + PushTemplateConstants.PushPayloadKeys.IMAGE_URL to MOCKED_IMAGE_URI, + PushTemplateConstants.PushPayloadKeys.ACTION_TYPE to "WEBURL", + PushTemplateConstants.PushPayloadKeys.ACTION_URI to MOCKED_ACTION_URI, + PushTemplateConstants.PushPayloadKeys.VERSION to MOCKED_PAYLOAD_VERSION, + PushTemplateConstants.PushPayloadKeys.RATING_UNSELECTED_ICON to "rating_star_outline", + PushTemplateConstants.PushPayloadKeys.RATING_SELECTED_ICON to "rating_star_filled", + PushTemplateConstants.PushPayloadKeys.RATING_ACTIONS to "[{\"uri\":\"https://www.adobe.com\", \"type\":\"WEBURL\"},{\"type\":\"OPENAPP\"},{\"uri\":\"https://www.adobe.com\", \"type\":\"WEBURL\"},{\"uri\": \"https://www.adobe.com\", \"type\":\"WEBURL\"},{\"uri\":\"https://www.adobe.com\", \"type\":\"WEBURL\"}]" + ) + } + + fun getMockedBundleForRatingTemplate(): Bundle { + val mockBundle = Bundle() + mockBundle.putString(PushTemplateConstants.PushPayloadKeys.VERSION, MOCKED_PAYLOAD_VERSION) + mockBundle.putString(PushTemplateConstants.PushPayloadKeys.TEMPLATE_TYPE, PushTemplateType.PRODUCT_RATING.value) + mockBundle.putString(PushTemplateConstants.PushPayloadKeys.TITLE, MOCKED_TITLE) + mockBundle.putString(PushTemplateConstants.PushPayloadKeys.BODY, MOCKED_BODY) + mockBundle.putString(PushTemplateConstants.PushPayloadKeys.EXPANDED_BODY_TEXT, MOCKED_BASIC_TEMPLATE_BODY_EXPANDED) + mockBundle.putString(PushTemplateConstants.PushPayloadKeys.IMAGE_URL, MOCKED_IMAGE_URI) + mockBundle.putString(PushTemplateConstants.PushPayloadKeys.ACTION_TYPE, "WEBURL") + mockBundle.putString(PushTemplateConstants.PushPayloadKeys.ACTION_URI, MOCKED_ACTION_URI) + mockBundle.putString(PushTemplateConstants.PushPayloadKeys.VERSION, MOCKED_PAYLOAD_VERSION) + mockBundle.putString(PushTemplateConstants.PushPayloadKeys.RATING_UNSELECTED_ICON, "rating_star_outline") + mockBundle.putString(PushTemplateConstants.PushPayloadKeys.RATING_SELECTED_ICON, "rating_star_filled") + mockBundle.putString(PushTemplateConstants.PushPayloadKeys.RATING_ACTIONS, "[{\"uri\":\"https://www.adobe.com\", \"type\":\"WEBURL\"},{\"type\":\"OPENAPP\"},{\"uri\":\"https://www.adobe.com\", \"type\":\"WEBURL\"},{\"uri\": \"https://www.adobe.com\", \"type\":\"WEBURL\"},{\"uri\":\"https://www.adobe.com\", \"type\":\"WEBURL\"}]") + return mockBundle + } +} From 620cf3f89808721bbffe6bb34c8161afd9faed16 Mon Sep 17 00:00:00 2001 From: Navratan Soni Date: Fri, 21 Jun 2024 20:34:17 +0530 Subject: [PATCH 150/159] Added Carousel notification test class (#42) * Added boilerplate class for MultiiconBuilderClass * Added carousel push template class * Removed wrong file added * Spotless apply --- .../templates/CarousalPushTemplateTests.kt | 109 ++++++++++++++++++ 1 file changed, 109 insertions(+) create mode 100644 code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/templates/CarousalPushTemplateTests.kt diff --git a/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/templates/CarousalPushTemplateTests.kt b/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/templates/CarousalPushTemplateTests.kt new file mode 100644 index 00000000..0223d99f --- /dev/null +++ b/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/templates/CarousalPushTemplateTests.kt @@ -0,0 +1,109 @@ +/* + Copyright 2024 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.notificationbuilder.internal.templates + +import com.adobe.marketing.mobile.notificationbuilder.PushTemplateConstants +import com.adobe.marketing.mobile.notificationbuilder.internal.PushTemplateType +import com.adobe.marketing.mobile.notificationbuilder.internal.templates.MockCarousalTemplateDataProvider.getMockedBundleWithAutoCarouselData +import com.adobe.marketing.mobile.notificationbuilder.internal.templates.MockCarousalTemplateDataProvider.getMockedBundleWithManualCarouselData +import com.adobe.marketing.mobile.notificationbuilder.internal.templates.MockCarousalTemplateDataProvider.getMockedMapWithAutoCarouselData +import com.adobe.marketing.mobile.notificationbuilder.internal.templates.MockCarousalTemplateDataProvider.getMockedMapWithManualCarouselData +import com.adobe.marketing.mobile.notificationbuilder.internal.util.IntentData +import com.adobe.marketing.mobile.notificationbuilder.internal.util.MapData +import org.junit.Assert.assertEquals +import org.junit.Assert.assertTrue +import org.junit.runner.RunWith +import org.robolectric.RobolectricTestRunner +import org.robolectric.annotation.Config +import kotlin.test.Test +import kotlin.test.assertFailsWith + +@RunWith(RobolectricTestRunner::class) +@Config(sdk = [31]) +class CarousalPushTemplateTests { + @Test + fun `Test AutoCarouselPushTemplate initialization with Intent`() { + val mockBundle = getMockedBundleWithAutoCarouselData() + val template = CarouselPushTemplate(IntentData(mockBundle, null)) + assertEquals(PushTemplateType.CAROUSEL, template.templateType) + assertTrue(template is AutoCarouselPushTemplate) + assertEquals(MOCKED_CAROUSEL_LAYOUT_DATA, template.rawCarouselItems) + } + + @Test + fun `Test ManualCarouselPushTemplate initialization with Intent`() { + val mockBundle = getMockedBundleWithManualCarouselData() + val template = CarouselPushTemplate(IntentData(mockBundle, null)) + assertEquals(PushTemplateType.CAROUSEL, template.templateType) + assertTrue(template is ManualCarouselPushTemplate) + assertEquals(MOCKED_CAROUSEL_LAYOUT_DATA, template.rawCarouselItems) + } + + @Test + fun `Test AutoCarouselPushTemplate initialization with MapData`() { + val mockedMap = getMockedMapWithAutoCarouselData() + val template = CarouselPushTemplate(MapData(mockedMap)) + assertEquals(PushTemplateType.CAROUSEL, template.templateType) + assertTrue(template is AutoCarouselPushTemplate) + assertEquals(MOCKED_CAROUSEL_LAYOUT_DATA, template.rawCarouselItems) + } + + @Test + fun `Test ManualCarouselPushTemplate initialization with MapData`() { + val mockedMap = getMockedMapWithManualCarouselData() + val template = CarouselPushTemplate(MapData(mockedMap)) + assertEquals(PushTemplateType.CAROUSEL, template.templateType) + assertTrue(template is ManualCarouselPushTemplate) + assertEquals(MOCKED_CAROUSEL_LAYOUT_DATA, template.rawCarouselItems) + } + @Test + fun `Test CarouselPushTemplate initialization with missing carouselLayout`() { + val mockedMap = getMockedMapWithAutoCarouselData().apply { + remove(PushTemplateConstants.PushPayloadKeys.CAROUSEL_LAYOUT) + } + val exception = assertFailsWith { + CarouselPushTemplate(MapData(mockedMap)) + } + assertEquals("Required push template key ${PushTemplateConstants.PushPayloadKeys.CAROUSEL_LAYOUT} not found or null", exception.message) + } + + @Test + fun `Test CarouselPushTemplate initialization with missing rawCarouselItems`() { + val mockedMap = getMockedMapWithAutoCarouselData().apply { + remove(PushTemplateConstants.PushPayloadKeys.CAROUSEL_ITEMS) + } + val exception = assertFailsWith { + CarouselPushTemplate(MapData(mockedMap)) + } + assertEquals("Required push template key ${PushTemplateConstants.PushPayloadKeys.CAROUSEL_ITEMS} not found or null", exception.message) + } + + @Test + fun `Test CarouselPushTemplate initialization with empty rawCarouselItems`() { + val mockedMap = getMockedMapWithAutoCarouselData().apply { + put(PushTemplateConstants.PushPayloadKeys.CAROUSEL_ITEMS, "") + } + val template = CarouselPushTemplate(MapData(mockedMap)) + assertEquals(PushTemplateType.CAROUSEL, template.templateType) + assertTrue(template.carouselItems.isEmpty()) + } + + @Test + fun `Test CarouselPushTemplate initialization with malformed rawCarouselItems`() { + val mockedMap = getMockedMapWithAutoCarouselData().apply { + put(PushTemplateConstants.PushPayloadKeys.CAROUSEL_ITEMS, "malformed_json_string") + } + val template = CarouselPushTemplate(MapData(mockedMap)) + assertEquals(PushTemplateType.CAROUSEL, template.templateType) + assertTrue(template.carouselItems.isEmpty()) + } +} From 1df497840bbd616d8b17aaf1835e61736c141619 Mon Sep 17 00:00:00 2001 From: Spoorthi Pujari <63024083+spoorthipujariadobe@users.noreply.github.com> Date: Fri, 21 Jun 2024 08:07:53 -0700 Subject: [PATCH 151/159] NotificationCompatBuilderExtensions, RemoteViewsExtensionsTest, PendingIntentUtils unit tests (#44) * move remind later handling to different public class * NotificationCompatBuilderExtensions tests * improve readability * unmockAll in teardown for each test * RemoteViewsExtensionsTest tests * PendingIntentUtils tests * code review comments * code review comments 2 * assert import improvements --- .../internal/PendingIntentUtils.kt | 21 +- .../NotificationCompatBuilderExtensions.kt | 38 +- .../internal/PendingIntentUtilsTest.kt | 262 +++++++++ ...NotificationCompatBuilderExtensionsTest.kt | 527 ++++++++++++++++++ .../extensions/RemoteViewsExtensionsTest.kt | 354 ++++++++++++ 5 files changed, 1159 insertions(+), 43 deletions(-) create mode 100644 code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/PendingIntentUtilsTest.kt create mode 100644 code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/extensions/NotificationCompatBuilderExtensionsTest.kt create mode 100644 code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/extensions/RemoteViewsExtensionsTest.kt diff --git a/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/PendingIntentUtils.kt b/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/PendingIntentUtils.kt index e19b64b0..6b6e6c55 100644 --- a/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/PendingIntentUtils.kt +++ b/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/PendingIntentUtils.kt @@ -47,17 +47,21 @@ internal object PendingIntentUtils { val alarmManager = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager? ?: return - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S && - isExactAlarmsAllowed(alarmManager) - ) { + if (isExactAlarmsAllowed(alarmManager)) { Log.trace( PushTemplateConstants.LOG_TAG, SELF_TAG, "Exact alarms are permitted, scheduling an exact alarm for the current notification." ) - alarmManager.setExactAndAllowWhileIdle( - AlarmManager.RTC_WAKEUP, triggerAtSeconds * 1000, pendingIntent - ) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + alarmManager.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, triggerAtSeconds * 1000, pendingIntent) + } else { + alarmManager.setExact( + AlarmManager.RTC_WAKEUP, + triggerAtSeconds * 1000, + pendingIntent + ) + } } else { // schedule an inexact alarm for the current notification Log.trace( @@ -65,8 +69,11 @@ internal object PendingIntentUtils { SELF_TAG, "Exact alarms are not permitted, scheduling an inexact alarm for the current notification." ) - alarmManager[AlarmManager.RTC_WAKEUP, triggerAtSeconds * 1000] = + alarmManager.setAndAllowWhileIdle( + AlarmManager.RTC_WAKEUP, + triggerAtSeconds * 1000, pendingIntent + ) } } diff --git a/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/extensions/NotificationCompatBuilderExtensions.kt b/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/extensions/NotificationCompatBuilderExtensions.kt index 15d4fdf9..3d82a06d 100644 --- a/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/extensions/NotificationCompatBuilderExtensions.kt +++ b/code/notificationbuilder/src/main/java/com/adobe/marketing/mobile/notificationbuilder/internal/extensions/NotificationCompatBuilderExtensions.kt @@ -24,7 +24,6 @@ import com.adobe.marketing.mobile.notificationbuilder.PushTemplateConstants import com.adobe.marketing.mobile.notificationbuilder.PushTemplateConstants.LOG_TAG import com.adobe.marketing.mobile.notificationbuilder.internal.PendingIntentUtils import com.adobe.marketing.mobile.notificationbuilder.internal.PushTemplateImageUtils -import com.adobe.marketing.mobile.notificationbuilder.internal.templates.AEPPushTemplate import com.adobe.marketing.mobile.notificationbuilder.internal.templates.BasicPushTemplate import com.adobe.marketing.mobile.services.Log import java.util.Random @@ -91,7 +90,8 @@ private fun NotificationCompat.Builder.setSmallIconColor( try { // sets the icon color if provided - setColorized(true).color = Color.parseColor("#$iconColorHex") + val color = Color.parseColor("#$iconColorHex") + setColorized(true).color = color } catch (exception: IllegalArgumentException) { Log.warning( LOG_TAG, @@ -102,40 +102,6 @@ private fun NotificationCompat.Builder.setSmallIconColor( return } -/** - * Sets the visibility of the notification. If a visibility is received from the payload, the - * same is used. If a visibility is not received from the payload, the default visibility is used. - * - * @param pushTemplate the [AEPPushTemplate] containing the visibility value - */ -internal fun NotificationCompat.Builder.setVisibility( - pushTemplate: AEPPushTemplate -): NotificationCompat.Builder { - when (pushTemplate.visibility.value) { - NotificationCompat.VISIBILITY_PUBLIC -> setVisibility( - NotificationCompat.VISIBILITY_PUBLIC - ) - - NotificationCompat.VISIBILITY_PRIVATE -> setVisibility( - NotificationCompat.VISIBILITY_PRIVATE - ) - - NotificationCompat.VISIBILITY_SECRET -> setVisibility( - NotificationCompat.VISIBILITY_SECRET - ) - - else -> { - setVisibility(NotificationCompat.VISIBILITY_PRIVATE) - Log.debug( - LOG_TAG, - SELF_TAG, - "Invalid visibility value received from the payload. Using the default visibility value." - ) - } - } - return this -} - /** * Sets the sound for the legacy style notification or notification on a device less than API 25. * If a sound is received from the payload, the same is used. diff --git a/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/PendingIntentUtilsTest.kt b/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/PendingIntentUtilsTest.kt new file mode 100644 index 00000000..f3d432aa --- /dev/null +++ b/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/PendingIntentUtilsTest.kt @@ -0,0 +1,262 @@ +/* + Copyright 2024 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.notificationbuilder.internal + +import android.app.AlarmManager +import android.app.PendingIntent +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import io.mockk.every +import io.mockk.mockk +import io.mockk.slot +import io.mockk.unmockkAll +import io.mockk.verify +import org.junit.After +import org.junit.Assert.assertEquals +import org.junit.Assert.assertFalse +import org.junit.Assert.assertNotNull +import org.junit.Assert.assertTrue +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.robolectric.RobolectricTestRunner +import org.robolectric.RuntimeEnvironment +import org.robolectric.Shadows +import org.robolectric.annotation.Config + +@RunWith(RobolectricTestRunner::class) +class PendingIntentUtilsTest { + + private lateinit var mockContext: Context + private lateinit var scheduledIntent: Intent + private lateinit var broadcastReceiverClass: Class + private lateinit var mockAlarmManager: AlarmManager + private val triggerAtSeconds = 1000L + + @Before + fun setup() { + mockContext = mockk(relaxed = true) + scheduledIntent = Intent() + broadcastReceiverClass = BroadcastReceiver::class.java + mockAlarmManager = mockk(relaxed = true) + every { mockContext.getSystemService(Context.ALARM_SERVICE) } returns mockAlarmManager + } + + @After + fun teardown() { + // reset the mock + unmockkAll() + } + + @Test + @Config(sdk = [31]) + fun `scheduleNotification schedules exact alarm when exact alarms are allowed`() { + every { mockAlarmManager.canScheduleExactAlarms() } returns true + + PendingIntentUtils.scheduleNotification( + mockContext, + scheduledIntent, + broadcastReceiverClass, + triggerAtSeconds + ) + + val pendingIntentCapture = slot() + verify(exactly = 1) { + mockAlarmManager.setExactAndAllowWhileIdle( + AlarmManager.RTC_WAKEUP, + triggerAtSeconds * 1000, + capture(pendingIntentCapture) + ) + } + verifyScheduledPendingIntent(pendingIntentCapture.captured) + } + + @Test + @Config(sdk = [31]) + fun `scheduleNotification schedules inexact alarm when exact alarms are not allowed`() { + every { mockAlarmManager.canScheduleExactAlarms() } returns false + + PendingIntentUtils.scheduleNotification( + mockContext, + scheduledIntent, + broadcastReceiverClass, + triggerAtSeconds + ) + + val pendingIntentCapture = slot() + verify(exactly = 1) { + mockAlarmManager.setAndAllowWhileIdle( + AlarmManager.RTC_WAKEUP, + triggerAtSeconds * 1000, + capture(pendingIntentCapture) + ) + } + + verifyScheduledPendingIntent(pendingIntentCapture.captured) + } + + @Test + @Config(sdk = [30]) + fun `scheduleNotification schedules exact alarm when version is less than 31`() { + PendingIntentUtils.scheduleNotification( + mockContext, + scheduledIntent, + broadcastReceiverClass, + triggerAtSeconds + ) + + val pendingIntentCapture = slot() + verify(exactly = 1) { + mockAlarmManager.setExactAndAllowWhileIdle( + AlarmManager.RTC_WAKEUP, + triggerAtSeconds * 1000, + capture(pendingIntentCapture) + ) + } + verifyScheduledPendingIntent(pendingIntentCapture.captured) + } + + @Test + @Config(sdk = [22]) + fun `scheduleNotification schedules exact alarm when version is less than 23`() { + val triggerAtSeconds = 1000L + + PendingIntentUtils.scheduleNotification( + mockContext, + scheduledIntent, + broadcastReceiverClass, + triggerAtSeconds + ) + + val pendingIntentCapture = slot() + verify(exactly = 1) { + mockAlarmManager.setExact( + AlarmManager.RTC_WAKEUP, + triggerAtSeconds * 1000, + capture(pendingIntentCapture) + ) + } + verifyScheduledPendingIntent(pendingIntentCapture.captured) + } + + @Test + @Config(sdk = [31]) + fun `scheduleNotification schedules exact alarm when broadcastReceiverClass is null`() { + val triggerAtSeconds = 1000L + every { mockAlarmManager.canScheduleExactAlarms() } returns true + + PendingIntentUtils.scheduleNotification( + mockContext, + scheduledIntent, + null, + triggerAtSeconds + ) + + val pendingIntentCapture = slot() + verify(exactly = 1) { + mockAlarmManager.setExactAndAllowWhileIdle( + AlarmManager.RTC_WAKEUP, + triggerAtSeconds * 1000, + capture(pendingIntentCapture) + ) + } + val pendingIntent = pendingIntentCapture.captured + assertNotNull(pendingIntent) + val shadowPendingIntent = Shadows.shadowOf(pendingIntent) + assertTrue(shadowPendingIntent.isBroadcastIntent) + assertEquals(mockContext, shadowPendingIntent.savedContext) + assertEquals( + PendingIntent.FLAG_MUTABLE or PendingIntent.FLAG_UPDATE_CURRENT, + shadowPendingIntent.flags + ) + + val intent = shadowPendingIntent.savedIntent + assertEquals(null, intent.component?.className) + assertEquals(scheduledIntent, intent) + } + + @Test + @Config(sdk = [22]) + fun `scheduleNotification does not schedule alarm when AlarmManager is null`() { + val triggerAtSeconds = 1000L + every { mockContext.getSystemService(Context.ALARM_SERVICE) } returns null + + PendingIntentUtils.scheduleNotification( + mockContext, + scheduledIntent, + broadcastReceiverClass, + triggerAtSeconds + ) + + verify(exactly = 0) { + mockAlarmManager.setExact( + AlarmManager.RTC_WAKEUP, + triggerAtSeconds * 1000, + any() + ) + } + } + + @Test + @Config(sdk = [30]) + fun `isExactAlarmsAllowed returns true when SDK version is less than S`() { + val context = RuntimeEnvironment.getApplication() + val alarmManager = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager? + val result = PendingIntentUtils.isExactAlarmsAllowed(alarmManager) + + assertTrue(result) + } + + @Test + @Config(sdk = [31]) + fun `isExactAlarmsAllowed returns true when canScheduleExactAlarms is true`() { + every { mockAlarmManager.canScheduleExactAlarms() } returns true + + val result = PendingIntentUtils.isExactAlarmsAllowed(mockAlarmManager) + + assertTrue(result) + } + + @Test + @Config(sdk = [31]) + fun `isExactAlarmsAllowed returns false when canScheduleExactAlarms is false`() { + every { mockAlarmManager.canScheduleExactAlarms() } returns false + + val result = PendingIntentUtils.isExactAlarmsAllowed(mockAlarmManager) + + assertFalse(result) + } + + @Test + @Config(sdk = [31]) + fun `isExactAlarmsAllowed returns false when alarmManager is null and SDK version is S or higher`() { + val result = PendingIntentUtils.isExactAlarmsAllowed(null) + + assertFalse(result) + } + + private fun verifyScheduledPendingIntent(pendingIntent: PendingIntent) { + assertNotNull(pendingIntent) + val shadowPendingIntent = Shadows.shadowOf(pendingIntent) + assertTrue(shadowPendingIntent.isBroadcastIntent) + assertEquals(mockContext, shadowPendingIntent.savedContext) + assertEquals( + PendingIntent.FLAG_MUTABLE or PendingIntent.FLAG_UPDATE_CURRENT, + shadowPendingIntent.flags + ) + + val intent = shadowPendingIntent.savedIntent + assertEquals(broadcastReceiverClass.name, intent.component?.className) + assertEquals(scheduledIntent, intent) + } +} diff --git a/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/extensions/NotificationCompatBuilderExtensionsTest.kt b/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/extensions/NotificationCompatBuilderExtensionsTest.kt new file mode 100644 index 00000000..82a5cb15 --- /dev/null +++ b/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/extensions/NotificationCompatBuilderExtensionsTest.kt @@ -0,0 +1,527 @@ +/* + Copyright 2024 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.notificationbuilder.internal.extensions + +import android.app.Activity +import android.app.PendingIntent +import android.content.Context +import android.content.Intent +import android.graphics.Bitmap +import android.media.RingtoneManager +import android.net.Uri +import android.os.Bundle +import androidx.core.app.NotificationCompat +import com.adobe.marketing.mobile.MobileCore +import com.adobe.marketing.mobile.notificationbuilder.PushTemplateConstants +import com.adobe.marketing.mobile.notificationbuilder.internal.PushTemplateImageUtils +import com.adobe.marketing.mobile.notificationbuilder.internal.builders.DummyActivity +import com.adobe.marketing.mobile.notificationbuilder.internal.templates.BasicPushTemplate +import io.mockk.every +import io.mockk.mockk +import io.mockk.mockkObject +import io.mockk.mockkStatic +import io.mockk.slot +import io.mockk.spyk +import io.mockk.unmockkAll +import io.mockk.verify +import org.junit.After +import org.junit.Assert.assertEquals +import org.junit.Assert.assertNotNull +import org.junit.Assert.assertNull +import org.junit.Assert.assertTrue +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.robolectric.RobolectricTestRunner +import org.robolectric.Shadows +import org.robolectric.annotation.Config + +// this class also has tests for PendingIntentUtils.createPendingIntentForTrackerActivity +@RunWith(RobolectricTestRunner::class) +@Config(sdk = [31]) +class NotificationCompatBuilderExtensionsTest { + + private lateinit var mockContext: Context + private lateinit var trackerActivityClass: Class + private lateinit var mockBitmap: Bitmap + + @Before + fun setup() { + mockContext = mockk(relaxed = true) + mockkStatic(Context::getIconWithResourceName) + mockkStatic(MobileCore::class) + mockkObject(PushTemplateImageUtils) + mockBitmap = mockk(relaxed = true) + trackerActivityClass = DummyActivity::class.java + } + + @After + fun tearDown() { + unmockkAll() + } + + @Test + fun `setSmallIcon uses icon from payload when icon and color are valid`() { + every { mockContext.getIconWithResourceName("valid_icon") } returns 1234 + every { MobileCore.getSmallIconResourceID() } returns 0 + + val spyBuilder = spyk(NotificationCompat.Builder(mockContext, "mockChannelId")) + + spyBuilder.setSmallIcon(mockContext, "valid_icon", "000000") + verify(exactly = 1) { spyBuilder.setSmallIcon(1234) } + verify(exactly = 1) { spyBuilder.setColorized(true) } + assertEquals(-16777216, spyBuilder.color) + } + + @Test + fun `setSmallIcon uses icon from mobile core when icon from payload is null`() { + every { mockContext.getIconWithResourceName("invalid_icon") } returns 0 + every { MobileCore.getSmallIconResourceID() } returns 1234 + every { mockContext.getDefaultAppIcon() } returns 0 + + val spyBuilder = spyk(NotificationCompat.Builder(mockContext, "mockChannelId")) + + spyBuilder.setSmallIcon(mockContext, "invalid_icon", null) + verify(exactly = 1) { spyBuilder.setSmallIcon(1234) } + } + + @Test + fun `setSmallIcon uses default app icon when icon from payload is invalid`() { + every { mockContext.getIconWithResourceName("invalid_icon") } returns 0 + every { MobileCore.getSmallIconResourceID() } returns 0 + every { mockContext.getDefaultAppIcon() } returns 1234 + + val spyBuilder = spyk(NotificationCompat.Builder(mockContext, "mockChannelId")) + + spyBuilder.setSmallIcon(mockContext, "invalid_icon", null) + verify(exactly = 1) { spyBuilder.setSmallIcon(1234) } + } + + @Test + fun `setSmallIcon does not set icon and color when all icon sources are invalid`() { + every { mockContext.getIconWithResourceName("invalid_icon") } returns 0 + every { MobileCore.getSmallIconResourceID() } returns 0 + every { mockContext.getDefaultAppIcon() } returns 0 + + val spyBuilder = spyk(NotificationCompat.Builder(mockContext, "mockChannelId")) + + spyBuilder.setSmallIcon(mockContext, "invalid_icon", "000000") + verify(exactly = 0) { spyBuilder.setSmallIcon(any()) } + verify(exactly = 0) { spyBuilder.setColorized(true) } + assertEquals(0, spyBuilder.color) + } + + @Test + fun `setSmallIcon does not set color when icon is valid but color is null`() { + every { mockContext.getIconWithResourceName("valid_icon") } returns 1234 + every { MobileCore.getSmallIconResourceID() } returns 0 + every { mockContext.getDefaultAppIcon() } returns 0 + + val spyBuilder = spyk(NotificationCompat.Builder(mockContext, "mockChannelId")) + + spyBuilder.setSmallIcon(mockContext, "valid_icon", null) + verify(exactly = 1) { spyBuilder.setSmallIcon(1234) } + verify(exactly = 0) { spyBuilder.setColorized(true) } + assertEquals(0, spyBuilder.color) + } + + @Test + fun `setSmallIcon does not set color when icon is valid but color is empty`() { + every { mockContext.getIconWithResourceName("valid_icon") } returns 1234 + every { MobileCore.getSmallIconResourceID() } returns 0 + every { mockContext.getDefaultAppIcon() } returns 0 + + val spyBuilder = spyk(NotificationCompat.Builder(mockContext, "mockChannelId")) + + spyBuilder.setSmallIcon(mockContext, "valid_icon", "") + verify(exactly = 1) { spyBuilder.setSmallIcon(1234) } + verify(exactly = 0) { spyBuilder.setColorized(true) } + assertEquals(0, spyBuilder.color) + } + + @Test + fun `setSmallIcon does not set color when icon is valid but color is invalid`() { + every { mockContext.getIconWithResourceName("valid_icon") } returns 1234 + every { MobileCore.getSmallIconResourceID() } returns 0 + every { mockContext.getDefaultAppIcon() } returns 0 + + val spyBuilder = spyk(NotificationCompat.Builder(mockContext, "mockChannelId")) + + spyBuilder.setSmallIcon(mockContext, "valid_icon", "invalid_color") + verify(exactly = 1) { spyBuilder.setSmallIcon(1234) } + verify(exactly = 0) { spyBuilder.setColorized(true) } + assertEquals(0, spyBuilder.color) + } + + @Test + fun `test sets default sound setSound with null customSound`() { + val spyBuilder = spyk(NotificationCompat.Builder(mockContext, "mockChannelId")) + + spyBuilder.setSound(mockContext, null) + verify(exactly = 1) { + spyBuilder.setSound(RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION)) + } + } + + @Test + fun `test sets default sound setSound with empty customSound`() { + val spyBuilder = spyk(NotificationCompat.Builder(mockContext, "mockChannelId")) + + spyBuilder.setSound(mockContext, "") + verify(exactly = 1) { + spyBuilder.setSound(RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION)) + } + } + + @Test + fun `test provided sound setSound with non null customSound`() { + val testUri = Uri.parse("test_uri") + every { mockContext.getSoundUriForResourceName("valid_sound") } returns testUri + + val spyBuilder = spyk(NotificationCompat.Builder(mockContext, "mockChannelId")) + + spyBuilder.setSound(mockContext, "valid_sound") + verify(exactly = 1) { + spyBuilder.setSound(testUri) + } + } + + @Test + fun `setLargeIcon with valid imageUrl`() { + every { PushTemplateImageUtils.cacheImages(listOf("valid_image_url")) } returns 1 + every { PushTemplateImageUtils.getCachedImage("valid_image_url") } returns mockBitmap + + val spyBuilder = spyk(NotificationCompat.Builder(mockContext, "mockChannelId")) + + spyBuilder.setLargeIcon("valid_image_url", "title", "bodyText") + + verify(exactly = 1) { spyBuilder.setLargeIcon(mockBitmap) } + verify(exactly = 1) { spyBuilder.setStyle(any()) } + } + + @Test + fun `setLargeIcon with imageUrl that cannot be downloaded`() { + every { PushTemplateImageUtils.cacheImages(listOf("invalid_image_url")) } returns 0 + + val spyBuilder = spyk(NotificationCompat.Builder(mockContext, "mockChannelId")) + + spyBuilder.setLargeIcon("invalid_image_url", "title", "bodyText") + + verify(exactly = 0) { spyBuilder.setLargeIcon(any()) } + verify(exactly = 0) { spyBuilder.setStyle(any()) } + } + + @Test + fun `setLargeIcon with null imageUrl`() { + val spyBuilder = spyk(NotificationCompat.Builder(mockContext, "mockChannelId")) + + spyBuilder.setLargeIcon(null, "title", "bodyText") + + verify(exactly = 0) { spyBuilder.setLargeIcon(any()) } + verify(exactly = 0) { spyBuilder.setStyle(any()) } + } + + @Test + fun `setLargeIcon with empty imageUrl`() { + val spyBuilder = spyk(NotificationCompat.Builder(mockContext, "mockChannelId")) + + spyBuilder.setLargeIcon("", "title", "bodyText") + + verify(exactly = 0) { spyBuilder.setLargeIcon(any()) } + verify(exactly = 0) { spyBuilder.setStyle(any()) } + } + + @Test + fun `setNotificationClickAction sets content intent when actionUri is not null`() { + val testActionUri = "testActionUri" + val testIntentExtras = Bundle() + testIntentExtras.putString("testKey", "testValue") + every { mockContext.applicationContext } returns mockContext + val mockBuilder: NotificationCompat.Builder = mockk(relaxed = true) + every { mockBuilder.setContentIntent(any()) } returns mockBuilder + + mockBuilder.setNotificationClickAction(mockContext, trackerActivityClass, testActionUri, testIntentExtras) + + val pendingIntentCapture = slot() + verify(exactly = 1) { mockBuilder.setContentIntent(capture(pendingIntentCapture)) } + val pendingIntent = pendingIntentCapture.captured + assertNotNull(pendingIntent) + val shadowPendingIntent = Shadows.shadowOf(pendingIntent) + assertTrue(shadowPendingIntent.isActivityIntent) + assertEquals(mockContext, shadowPendingIntent.savedContext) + assertEquals(PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE, shadowPendingIntent.flags) + + val intent = shadowPendingIntent.savedIntent + assertNotNull(intent) + assertEquals(PushTemplateConstants.NotificationAction.CLICKED, intent.action) + assertEquals(trackerActivityClass.name, intent.component?.className) + assertEquals(Intent.FLAG_ACTIVITY_SINGLE_TOP or Intent.FLAG_ACTIVITY_CLEAR_TOP, intent.flags) + assertEquals("testValue", intent.getStringExtra("testKey")) + assertEquals(testActionUri, intent.getStringExtra(PushTemplateConstants.TrackingKeys.ACTION_URI)) + assertEquals(null, intent.getStringExtra(PushTemplateConstants.TrackingKeys.ACTION_ID)) + } + + @Test + fun `setNotificationClickAction sets content intent when trackerActivityClass, actionUri and intentExtras are null`() { + every { mockContext.applicationContext } returns mockContext + val mockBuilder: NotificationCompat.Builder = mockk(relaxed = true) + every { mockBuilder.setContentIntent(any()) } returns mockBuilder + + mockBuilder.setNotificationClickAction(mockContext, null, null, null) + + val pendingIntentCapture = slot() + verify(exactly = 1) { mockBuilder.setContentIntent(capture(pendingIntentCapture)) } + val pendingIntent = pendingIntentCapture.captured + assertNotNull(pendingIntent) + val shadowPendingIntent = Shadows.shadowOf(pendingIntent) + assertTrue(shadowPendingIntent.isActivityIntent) + assertEquals(mockContext, shadowPendingIntent.savedContext) + assertEquals(PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE, shadowPendingIntent.flags) + + val intent = shadowPendingIntent.savedIntent + assertNotNull(intent) + assertEquals(PushTemplateConstants.NotificationAction.CLICKED, intent.action) + assertEquals(null, intent.component?.className) + assertEquals(Intent.FLAG_ACTIVITY_SINGLE_TOP or Intent.FLAG_ACTIVITY_CLEAR_TOP, intent.flags) + assertEquals(null, intent.extras) + } + + @Test + fun `setNotificationDeleteAction sets delete intent when trackerActivityClass is not null`() { + every { mockContext.applicationContext } returns mockContext + val mockBuilder: NotificationCompat.Builder = mockk(relaxed = true) + every { mockBuilder.setDeleteIntent(any()) } returns mockBuilder + + mockBuilder.setNotificationDeleteAction(mockContext, trackerActivityClass) + + val pendingIntentCapture = slot() + verify(exactly = 1) { mockBuilder.setDeleteIntent(capture(pendingIntentCapture)) } + val pendingIntent = pendingIntentCapture.captured + assertNotNull(pendingIntent) + val shadowPendingIntent = Shadows.shadowOf(pendingIntent) + assertTrue(shadowPendingIntent.isActivityIntent) + assertEquals(mockContext, shadowPendingIntent.savedContext) + assertEquals(PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE, shadowPendingIntent.flags) + + val intent = shadowPendingIntent.savedIntent + assertNotNull(intent) + assertEquals(PushTemplateConstants.NotificationAction.DISMISSED, intent.action) + assertEquals(trackerActivityClass.name, intent.component?.className) + assertEquals(Intent.FLAG_ACTIVITY_SINGLE_TOP, intent.flags) + assertNull(intent.extras) + } + + @Test + fun `setNotificationDeleteAction sets delete intent when trackerActivityClass is null`() { + every { mockContext.applicationContext } returns mockContext + val mockBuilder: NotificationCompat.Builder = mockk(relaxed = true) + every { mockBuilder.setDeleteIntent(any()) } returns mockBuilder + + mockBuilder.setNotificationDeleteAction(mockContext, null) + + val pendingIntentCapture = slot() + verify(exactly = 1) { mockBuilder.setDeleteIntent(capture(pendingIntentCapture)) } + val pendingIntent = pendingIntentCapture.captured + assertNotNull(pendingIntent) + val shadowPendingIntent = Shadows.shadowOf(pendingIntent) + assertTrue(shadowPendingIntent.isActivityIntent) + assertEquals(mockContext, shadowPendingIntent.savedContext) + assertEquals(PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE, shadowPendingIntent.flags) + + val intent = shadowPendingIntent.savedIntent + assertNotNull(intent) + assertEquals(PushTemplateConstants.NotificationAction.DISMISSED, intent.action) + assertNull(intent.component?.className) + assertEquals(Intent.FLAG_ACTIVITY_SINGLE_TOP, intent.flags) + assertNull(intent.extras) + } + + @Test + fun `addActionButtons adds actions when actionButtons is valid and type is deeplink`() { + val testIntentExtras = Bundle() + testIntentExtras.putString("testKey", "testValue") + every { mockContext.applicationContext } returns mockContext + val mockBuilder: NotificationCompat.Builder = mockk(relaxed = true) + every { mockBuilder.addAction(any(), any(), any()) } returns mockBuilder + + mockBuilder.addActionButtons( + mockContext, trackerActivityClass, + listOf( + BasicPushTemplate.ActionButton("testLabel", "testLink", PushTemplateConstants.ActionType.DEEPLINK.name) + ), + testIntentExtras + ) + + val iconCapture = slot() + val labelCapture = slot() + val pendingIntentCapture = slot() + verify(exactly = 1) { mockBuilder.addAction(capture(iconCapture), capture(labelCapture), capture(pendingIntentCapture)) } + val pendingIntent = pendingIntentCapture.captured + assertNotNull(pendingIntent) + val shadowPendingIntent = Shadows.shadowOf(pendingIntent) + assertTrue(shadowPendingIntent.isActivityIntent) + assertEquals(mockContext, shadowPendingIntent.savedContext) + assertEquals(PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE, shadowPendingIntent.flags) + + val intent = shadowPendingIntent.savedIntent + assertNotNull(intent) + assertEquals(PushTemplateConstants.NotificationAction.CLICKED, intent.action) + assertEquals(trackerActivityClass.name, intent.component?.className) + assertEquals(Intent.FLAG_ACTIVITY_SINGLE_TOP or Intent.FLAG_ACTIVITY_CLEAR_TOP, intent.flags) + assertEquals("testValue", intent.getStringExtra("testKey")) + assertEquals("testLink", intent.getStringExtra(PushTemplateConstants.TrackingKeys.ACTION_URI)) + assertEquals("testLabel", intent.getStringExtra(PushTemplateConstants.TrackingKeys.ACTION_ID)) + } + + @Test + fun `addActionButtons adds actions when actionButtons is valid and type is weburl`() { + val testIntentExtras = Bundle() + testIntentExtras.putString("testKey", "testValue") + every { mockContext.applicationContext } returns mockContext + val mockBuilder: NotificationCompat.Builder = mockk(relaxed = true) + every { mockBuilder.addAction(any(), any(), any()) } returns mockBuilder + + mockBuilder.addActionButtons( + mockContext, trackerActivityClass, + listOf( + BasicPushTemplate.ActionButton("testLabel", "testLink", PushTemplateConstants.ActionType.WEBURL.name) + ), + testIntentExtras + ) + + val iconCapture = slot() + val labelCapture = slot() + val pendingIntentCapture = slot() + verify(exactly = 1) { mockBuilder.addAction(capture(iconCapture), capture(labelCapture), capture(pendingIntentCapture)) } + val pendingIntent = pendingIntentCapture.captured + assertNotNull(pendingIntent) + val shadowPendingIntent = Shadows.shadowOf(pendingIntent) + assertTrue(shadowPendingIntent.isActivityIntent) + assertEquals(mockContext, shadowPendingIntent.savedContext) + assertEquals(PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE, shadowPendingIntent.flags) + + val intent = shadowPendingIntent.savedIntent + assertNotNull(intent) + assertEquals(PushTemplateConstants.NotificationAction.CLICKED, intent.action) + assertEquals(trackerActivityClass.name, intent.component?.className) + assertEquals(Intent.FLAG_ACTIVITY_SINGLE_TOP or Intent.FLAG_ACTIVITY_CLEAR_TOP, intent.flags) + assertEquals("testValue", intent.getStringExtra("testKey")) + assertEquals("testLink", intent.getStringExtra(PushTemplateConstants.TrackingKeys.ACTION_URI)) + assertEquals("testLabel", intent.getStringExtra(PushTemplateConstants.TrackingKeys.ACTION_ID)) + } + + @Test + fun `addActionButtons adds actions when actionButtons is valid and type is openapp`() { + val testIntentExtras = Bundle() + testIntentExtras.putString("testKey", "testValue") + every { mockContext.applicationContext } returns mockContext + val mockBuilder: NotificationCompat.Builder = mockk(relaxed = true) + every { mockBuilder.addAction(any(), any(), any()) } returns mockBuilder + + mockBuilder.addActionButtons( + mockContext, trackerActivityClass, + listOf( + BasicPushTemplate.ActionButton("testLabel", null, PushTemplateConstants.ActionType.OPENAPP.name) + ), + testIntentExtras + ) + + val iconCapture = slot() + val labelCapture = slot() + val pendingIntentCapture = slot() + verify(exactly = 1) { mockBuilder.addAction(capture(iconCapture), capture(labelCapture), capture(pendingIntentCapture)) } + val pendingIntent = pendingIntentCapture.captured + assertNotNull(pendingIntent) + val shadowPendingIntent = Shadows.shadowOf(pendingIntent) + assertTrue(shadowPendingIntent.isActivityIntent) + assertEquals(mockContext, shadowPendingIntent.savedContext) + assertEquals(PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE, shadowPendingIntent.flags) + + val intent = shadowPendingIntent.savedIntent + assertNotNull(intent) + assertEquals(PushTemplateConstants.NotificationAction.CLICKED, intent.action) + assertEquals(trackerActivityClass.name, intent.component?.className) + assertEquals(Intent.FLAG_ACTIVITY_SINGLE_TOP or Intent.FLAG_ACTIVITY_CLEAR_TOP, intent.flags) + assertEquals("testValue", intent.getStringExtra("testKey")) + assertEquals(null, intent.getStringExtra(PushTemplateConstants.TrackingKeys.ACTION_URI)) + assertEquals("testLabel", intent.getStringExtra(PushTemplateConstants.TrackingKeys.ACTION_ID)) + } + + @Test + fun `addActionButtons adds actions when actionButtons is valid and type is not valid`() { + val testIntentExtras = Bundle() + testIntentExtras.putString("testKey", "testValue") + every { mockContext.applicationContext } returns mockContext + val mockBuilder: NotificationCompat.Builder = mockk(relaxed = true) + every { mockBuilder.addAction(any(), any(), any()) } returns mockBuilder + + mockBuilder.addActionButtons( + mockContext, trackerActivityClass, + listOf( + BasicPushTemplate.ActionButton("testLabel", "testLink", PushTemplateConstants.ActionType.NONE.name) + ), + testIntentExtras + ) + + val iconCapture = slot() + val labelCapture = slot() + val pendingIntentCapture = slot() + verify(exactly = 1) { mockBuilder.addAction(capture(iconCapture), capture(labelCapture), capture(pendingIntentCapture)) } + val pendingIntent = pendingIntentCapture.captured + assertNotNull(pendingIntent) + val shadowPendingIntent = Shadows.shadowOf(pendingIntent) + assertTrue(shadowPendingIntent.isActivityIntent) + assertEquals(mockContext, shadowPendingIntent.savedContext) + assertEquals(PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE, shadowPendingIntent.flags) + + val intent = shadowPendingIntent.savedIntent + assertNotNull(intent) + assertEquals(PushTemplateConstants.NotificationAction.CLICKED, intent.action) + assertEquals(trackerActivityClass.name, intent.component?.className) + assertEquals(Intent.FLAG_ACTIVITY_SINGLE_TOP or Intent.FLAG_ACTIVITY_CLEAR_TOP, intent.flags) + assertEquals("testValue", intent.getStringExtra("testKey")) + assertEquals(null, intent.getStringExtra(PushTemplateConstants.TrackingKeys.ACTION_URI)) + assertEquals("testLabel", intent.getStringExtra(PushTemplateConstants.TrackingKeys.ACTION_ID)) + } + + @Test + fun `addActionButtons adds actions when actionButtons is null`() { + every { mockContext.applicationContext } returns mockContext + val mockBuilder: NotificationCompat.Builder = mockk(relaxed = true) + every { mockBuilder.addAction(any(), any(), any()) } returns mockBuilder + + mockBuilder.addActionButtons( + mockContext, + trackerActivityClass, + null, + null + ) + + verify(exactly = 0) { mockBuilder.addAction(any(), any(), any()) } + } + + @Test + fun `addActionButtons adds actions when actionButtons is empty`() { + every { mockContext.applicationContext } returns mockContext + val mockBuilder: NotificationCompat.Builder = mockk(relaxed = true) + every { mockBuilder.addAction(any(), any(), any()) } returns mockBuilder + + mockBuilder.addActionButtons( + mockContext, + trackerActivityClass, + emptyList(), + null + ) + + verify(exactly = 0) { mockBuilder.addAction(any(), any(), any()) } + } +} diff --git a/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/extensions/RemoteViewsExtensionsTest.kt b/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/extensions/RemoteViewsExtensionsTest.kt new file mode 100644 index 00000000..c836e527 --- /dev/null +++ b/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/extensions/RemoteViewsExtensionsTest.kt @@ -0,0 +1,354 @@ +/* + Copyright 2024 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.notificationbuilder.internal.extensions + +import android.app.Activity +import android.app.PendingIntent +import android.content.Context +import android.content.Intent +import android.graphics.Bitmap +import android.graphics.Color +import android.os.Bundle +import android.view.View +import android.widget.RemoteViews +import com.adobe.marketing.mobile.notificationbuilder.PushTemplateConstants +import com.adobe.marketing.mobile.notificationbuilder.internal.PushTemplateImageUtils +import com.adobe.marketing.mobile.notificationbuilder.internal.builders.DummyActivity +import com.adobe.marketing.mobile.services.Logging +import com.adobe.marketing.mobile.services.ServiceProvider +import io.mockk.Runs +import io.mockk.every +import io.mockk.just +import io.mockk.mockk +import io.mockk.mockkObject +import io.mockk.mockkStatic +import io.mockk.slot +import io.mockk.unmockkAll +import io.mockk.verify +import org.junit.After +import org.junit.Assert.assertFalse +import org.junit.Assert.assertNotNull +import org.junit.Assert.assertTrue +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.robolectric.RobolectricTestRunner +import org.robolectric.RuntimeEnvironment +import org.robolectric.Shadows +import org.robolectric.annotation.Config +import kotlin.test.assertEquals + +// this class also tests PendingIntentUtils.createPendingIntentForTrackerActivity +@RunWith(RobolectricTestRunner::class) +@Config(sdk = [31]) +class RemoteViewsExtensionsTest { + + private lateinit var remoteViews: RemoteViews + private lateinit var mockBitmap: Bitmap + private lateinit var trackerActivityClass: Class + + @Before + fun setup() { + remoteViews = mockk(relaxed = true) + mockkObject(PushTemplateImageUtils) + mockBitmap = mockk(relaxed = true) + trackerActivityClass = DummyActivity::class.java + } + + @After + fun teardown() { + unmockkAll() + } + + @Test + fun `setElementColor applies color when valid hex string provided`() { + val colorHex = "#FFFFFF" + val elementIdCapture = slot() + val methodNameCapture = slot() + val colorCapture = slot() + every { + remoteViews.setInt( + capture(elementIdCapture), + capture(methodNameCapture), + capture(colorCapture) + ) + } just Runs + + remoteViews.setElementColor(1, colorHex, "setBackgroundColor", "testFriendlyName") + + verify(exactly = 1) { remoteViews.setInt(any(), any(), any()) } + assertEquals(1, elementIdCapture.captured) + assertEquals("setBackgroundColor", methodNameCapture.captured) + assertEquals(Color.parseColor(colorHex), colorCapture.captured) + } + + @Test + fun `setElementColor does not apply color when hex string is null`() { + remoteViews.setElementColor(1, null, "setBackgroundColor", "testFriendlyName") + + verify(exactly = 0) { remoteViews.setInt(any(), any(), any()) } + } + + @Test + fun `setElementColor does not apply color when hex string is empty`() { + + remoteViews.setElementColor(1, "", "setBackgroundColor", "testFriendlyName") + + verify(exactly = 0) { remoteViews.setInt(any(), any(), any()) } + } + + @Test + fun `setElementColor does not apply color when hex string is invalid`() { + val colorHex = "invalid" + + remoteViews.setElementColor(1, colorHex, "setBackgroundColor", "testFriendlyName") + + verify(exactly = 0) { remoteViews.setInt(any(), any(), any()) } + } + + @Test + fun `setNotificationBackgroundColor applies color when valid hex string provided`() { + val colorHex = "FFFFFF" + + remoteViews.setNotificationBackgroundColor(colorHex, 1) + + verify { + remoteViews.setElementColor( + 1, + "#$colorHex", + PushTemplateConstants.MethodNames.SET_BACKGROUND_COLOR, + PushTemplateConstants.FriendlyViewNames.NOTIFICATION_BACKGROUND + ) + } + } + + @Test + fun `setTimerTextColor applies color when valid hex string provided`() { + val colorHex = "FFFFFF" + + remoteViews.setTimerTextColor(colorHex, 1) + + verify { + remoteViews.setElementColor( + 1, + "#$colorHex", + PushTemplateConstants.MethodNames.SET_TEXT_COLOR, + PushTemplateConstants.FriendlyViewNames.TIMER_TEXT + ) + } + } + + @Test + fun `setNotificationTitleTextColor applies color when valid hex string provided`() { + val colorHex = "FFFFFF" + + remoteViews.setNotificationTitleTextColor(colorHex, 1) + + verify { + remoteViews.setElementColor( + 1, + "#$colorHex", + PushTemplateConstants.MethodNames.SET_TEXT_COLOR, + PushTemplateConstants.FriendlyViewNames.NOTIFICATION_TITLE + ) + } + } + + @Test + fun `setNotificationBodyTextColor applies color when valid hex string provided`() { + val colorHex = "FFFFFF" + + remoteViews.setNotificationBodyTextColor(colorHex, 1) + + verify { + remoteViews.setElementColor( + 1, + "#$colorHex", + PushTemplateConstants.MethodNames.SET_TEXT_COLOR, + PushTemplateConstants.FriendlyViewNames.NOTIFICATION_BODY_TEXT + ) + } + } + + @Test + fun `setRemoteViewImage applies image when valid URL provided`() { + val imageUrl = "http://example.com/image.png" + every { PushTemplateImageUtils.cacheImages(listOf(imageUrl)) } returns 1 + every { PushTemplateImageUtils.getCachedImage(imageUrl) } returns mockBitmap + + val result = remoteViews.setRemoteViewImage(imageUrl, 1) + + assertTrue(result) + verify { remoteViews.setImageViewBitmap(1, mockBitmap) } + verify(exactly = 0) { remoteViews.setViewVisibility(1, View.GONE) } + } + + @Test + fun `setRemoteViewImage applies image when valid resource name provided`() { + val imageName = "valid_image" + mockkStatic(ServiceProvider::class) + mockkStatic(Context::getIconWithResourceName) + every { + ServiceProvider.getInstance().appContextService.applicationContext?.getIconWithResourceName( + imageName + ) + } returns 1234 + + val result = remoteViews.setRemoteViewImage(imageName, 1) + + assertTrue(result) + verify { remoteViews.setImageViewResource(1, 1234) } + verify(exactly = 0) { remoteViews.setViewVisibility(1, View.GONE) } + } + + @Test + fun `setRemoteViewImage does not apply image when image string is null`() { + every { remoteViews.setViewVisibility(any(), any()) } just Runs + val result = remoteViews.setRemoteViewImage(null, 1) + + assertFalse(result) + verify(exactly = 0) { remoteViews.setImageViewBitmap(any(), any()) } + verify(exactly = 0) { remoteViews.setImageViewResource(any(), any()) } + verify(exactly = 1) { remoteViews.setViewVisibility(1, View.GONE) } + } + + @Test + fun `setRemoteViewImage does not apply image when image string is empty`() { + every { remoteViews.setViewVisibility(any(), any()) } just Runs + val result = remoteViews.setRemoteViewImage("", 1) + + assertFalse(result) + verify(exactly = 0) { remoteViews.setImageViewBitmap(any(), any()) } + verify(exactly = 0) { remoteViews.setImageViewResource(any(), any()) } + verify(exactly = 1) { remoteViews.setViewVisibility(1, View.GONE) } + } + + @Test + fun `setRemoteViewImage does not apply image when URL is invalid`() { + val imageUrl = "invalid_url" + every { remoteViews.setViewVisibility(any(), any()) } just Runs + every { PushTemplateImageUtils.cacheImages(listOf(imageUrl)) } returns 0 + + val result = remoteViews.setRemoteViewImage(imageUrl, 1) + + assertFalse(result) + verify(exactly = 0) { remoteViews.setImageViewBitmap(any(), any()) } + verify(exactly = 0) { remoteViews.setImageViewResource(any(), any()) } + verify(exactly = 1) { remoteViews.setViewVisibility(1, View.GONE) } + } + + @Test + fun `setRemoteViewImage does not apply image URL could not be downloaded`() { + val imageUrl = "http://example.com/image.png" + every { remoteViews.setViewVisibility(any(), any()) } just Runs + every { PushTemplateImageUtils.cacheImages(listOf(imageUrl)) } returns 0 + + val result = remoteViews.setRemoteViewImage(imageUrl, 1) + + assertFalse(result) + verify(exactly = 0) { remoteViews.setImageViewBitmap(any(), any()) } + verify(exactly = 0) { remoteViews.setImageViewResource(any(), any()) } + verify(atLeast = 1) { remoteViews.setViewVisibility(1, View.GONE) } + } + + @Test + fun `setRemoteViewImage does not apply image when resource name is invalid`() { + val imageName = "invalid_image" + every { remoteViews.setViewVisibility(any(), any()) } just Runs + mockkStatic(ServiceProvider::class) + mockkStatic(Context::getIconWithResourceName) + every { + ServiceProvider.getInstance().appContextService.applicationContext?.getIconWithResourceName( + imageName + ) + } returns 0 + every { ServiceProvider.getInstance().loggingService } returns mockk(relaxed = true) + + val result = remoteViews.setRemoteViewImage(imageName, 1) + + assertFalse(result) + verify(exactly = 0) { remoteViews.setImageViewResource(any(), any()) } + verify(exactly = 0) { remoteViews.setImageViewBitmap(any(), any()) } + verify(exactly = 1) { remoteViews.setViewVisibility(1, View.GONE) } + } + + @Test + fun `setRemoteViewClickAction sets click action when all parameter values are provided`() { + val testActionUri = "testActionUri" + val testActionID = "testActionID" + val testIntentExtras = Bundle() + testIntentExtras.putString("testKey", "testValue") + val context = RuntimeEnvironment.getApplication() + every { remoteViews.setOnClickPendingIntent(any(), any()) } just Runs + + remoteViews.setRemoteViewClickAction(context, trackerActivityClass, 1, testActionUri, testActionID, testIntentExtras) + + val pendingIntentCapture = slot() + verify(exactly = 1) { remoteViews.setOnClickPendingIntent(1, capture(pendingIntentCapture)) } + val pendingIntent = pendingIntentCapture.captured + assertNotNull(pendingIntent) + val shadowPendingIntent = Shadows.shadowOf(pendingIntent) + assertTrue(shadowPendingIntent.isActivityIntent) + assertEquals(context, shadowPendingIntent.savedContext) + assertEquals( + PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE, + shadowPendingIntent.flags + ) + + val intent = shadowPendingIntent.savedIntent + assertNotNull(intent) + assertEquals(PushTemplateConstants.NotificationAction.CLICKED, intent.action) + assertEquals(trackerActivityClass.name, intent.component?.className) + assertEquals( + Intent.FLAG_ACTIVITY_SINGLE_TOP or Intent.FLAG_ACTIVITY_CLEAR_TOP, + intent.flags + ) + assertEquals("testValue", intent.getStringExtra("testKey")) + assertEquals( + testActionUri, + intent.getStringExtra(PushTemplateConstants.TrackingKeys.ACTION_URI) + ) + assertEquals( + testActionID, + intent.getStringExtra(PushTemplateConstants.TrackingKeys.ACTION_ID) + ) + } + + @Test + fun `setRemoteViewClickAction sets click action when when trackerActivityClass, actionUri, actionID and intentExtras are null`() { + val context = RuntimeEnvironment.getApplication() + + remoteViews.setRemoteViewClickAction(context, null, 1, null, null, null) + + val pendingIntentCapture = slot() + verify(exactly = 1) { remoteViews.setOnClickPendingIntent(1, capture(pendingIntentCapture)) } + val pendingIntent = pendingIntentCapture.captured + assertNotNull(pendingIntent) + val shadowPendingIntent = Shadows.shadowOf(pendingIntent) + assertTrue(shadowPendingIntent.isActivityIntent) + assertEquals(context, shadowPendingIntent.savedContext) + assertEquals( + PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE, + shadowPendingIntent.flags + ) + + val intent = shadowPendingIntent.savedIntent + assertNotNull(intent) + assertEquals(PushTemplateConstants.NotificationAction.CLICKED, intent.action) + assertEquals(null, intent.component?.className) + assertEquals( + Intent.FLAG_ACTIVITY_SINGLE_TOP or Intent.FLAG_ACTIVITY_CLEAR_TOP, + intent.flags + ) + assertEquals(null, intent.extras) + } +} From de3277f9b4f673be8faf79e72aea6cdaf22b0da3 Mon Sep 17 00:00:00 2001 From: Spoorthi Pujari <63024083+spoorthipujariadobe@users.noreply.github.com> Date: Fri, 21 Jun 2024 08:10:05 -0700 Subject: [PATCH 152/159] RemindLaterHandler tests (#50) --- .../NotificationBuilderTests.java | 3 +- .../NotificationPriorityTests.kt | 3 +- .../NotificationVisibilityTests.kt | 3 +- .../{internal => }/PushTemplateTypeTest.kt | 3 +- .../RemindLaterHandlerTests.kt | 169 ++++++++++++++++++ 5 files changed, 174 insertions(+), 7 deletions(-) rename code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/{internal => }/NotificationBuilderTests.java (85%) rename code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/{internal => }/NotificationPriorityTests.kt (96%) rename code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/{internal => }/NotificationVisibilityTests.kt (95%) rename code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/{internal => }/PushTemplateTypeTest.kt (89%) create mode 100644 code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/RemindLaterHandlerTests.kt diff --git a/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/NotificationBuilderTests.java b/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/NotificationBuilderTests.java similarity index 85% rename from code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/NotificationBuilderTests.java rename to code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/NotificationBuilderTests.java index 2b9a81cf..a0341a66 100644 --- a/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/NotificationBuilderTests.java +++ b/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/NotificationBuilderTests.java @@ -9,11 +9,10 @@ governing permissions and limitations under the License. */ -package com.adobe.marketing.mobile.notificationbuilder.internal; +package com.adobe.marketing.mobile.notificationbuilder; import static org.junit.Assert.assertEquals; -import com.adobe.marketing.mobile.notificationbuilder.NotificationBuilder; import org.junit.Test; public class NotificationBuilderTests { diff --git a/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/NotificationPriorityTests.kt b/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/NotificationPriorityTests.kt similarity index 96% rename from code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/NotificationPriorityTests.kt rename to code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/NotificationPriorityTests.kt index 43b6086c..90a7d10d 100644 --- a/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/NotificationPriorityTests.kt +++ b/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/NotificationPriorityTests.kt @@ -9,10 +9,9 @@ governing permissions and limitations under the License. */ -package com.adobe.marketing.mobile.notificationbuilder.internal +package com.adobe.marketing.mobile.notificationbuilder import androidx.core.app.NotificationCompat -import com.adobe.marketing.mobile.notificationbuilder.NotificationPriority import org.junit.Assert.assertEquals import org.junit.Test diff --git a/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/NotificationVisibilityTests.kt b/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/NotificationVisibilityTests.kt similarity index 95% rename from code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/NotificationVisibilityTests.kt rename to code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/NotificationVisibilityTests.kt index a76b507f..7f291243 100644 --- a/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/NotificationVisibilityTests.kt +++ b/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/NotificationVisibilityTests.kt @@ -9,10 +9,9 @@ governing permissions and limitations under the License. */ -package com.adobe.marketing.mobile.notificationbuilder.internal +package com.adobe.marketing.mobile.notificationbuilder import androidx.core.app.NotificationCompat -import com.adobe.marketing.mobile.notificationbuilder.NotificationVisibility import org.junit.Assert.assertEquals import org.junit.Test class NotificationVisibilityTests { diff --git a/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/PushTemplateTypeTest.kt b/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/PushTemplateTypeTest.kt similarity index 89% rename from code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/PushTemplateTypeTest.kt rename to code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/PushTemplateTypeTest.kt index 2acf2f18..0643a048 100644 --- a/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/PushTemplateTypeTest.kt +++ b/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/PushTemplateTypeTest.kt @@ -9,8 +9,9 @@ governing permissions and limitations under the License. */ -package com.adobe.marketing.mobile.notificationbuilder.internal +package com.adobe.marketing.mobile.notificationbuilder +import com.adobe.marketing.mobile.notificationbuilder.internal.PushTemplateType import org.junit.Test import kotlin.test.assertEquals diff --git a/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/RemindLaterHandlerTests.kt b/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/RemindLaterHandlerTests.kt new file mode 100644 index 00000000..45266a4b --- /dev/null +++ b/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/RemindLaterHandlerTests.kt @@ -0,0 +1,169 @@ +/* + Copyright 2024 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.notificationbuilder + +import android.content.BroadcastReceiver +import android.content.Intent +import androidx.core.app.NotificationManagerCompat +import com.adobe.marketing.mobile.notificationbuilder.internal.PendingIntentUtils +import com.adobe.marketing.mobile.services.ServiceProvider +import io.mockk.every +import io.mockk.mockk +import io.mockk.mockkObject +import io.mockk.mockkStatic +import io.mockk.unmockkAll +import io.mockk.verify +import org.junit.After +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.robolectric.RobolectricTestRunner +import org.robolectric.RuntimeEnvironment +import org.robolectric.annotation.Config + +@RunWith(RobolectricTestRunner::class) +@Config(sdk = [31]) +class RemindLaterHandlerTests { + + private lateinit var mockBroadcastReceiverClass: Class + private lateinit var mockedNotificationManagerCompat: NotificationManagerCompat + + @Before + fun setup() { + mockBroadcastReceiverClass = BroadcastReceiver::class.java + mockkStatic(ServiceProvider::class) + mockkObject(PendingIntentUtils) + val context = RuntimeEnvironment.getApplication() + every { ServiceProvider.getInstance().appContextService.applicationContext } returns context + every { ServiceProvider.getInstance().loggingService } returns mockk(relaxed = true) + mockedNotificationManagerCompat = mockk(relaxed = true) + mockkStatic(NotificationManagerCompat::class) + every { NotificationManagerCompat.from(context) } returns mockedNotificationManagerCompat + every { PendingIntentUtils.scheduleNotification(any(), any(), any(), any()) } returns Unit + } + + @After + fun tearDown() { + unmockkAll() + } + + @Test + fun `handleRemindIntent should schedule notification when valid remindLaterDuration and broadcastReceiverClass are provided`() { + RemindLaterHandler.handleRemindIntent(getRemindLaterIntentDuration(), mockBroadcastReceiverClass) + + verify { PendingIntentUtils.scheduleNotification(any(), any(), any(), any()) } + verify(atLeast = 1) { mockedNotificationManagerCompat.cancel(any()) } + } + + @Test + fun `handleRemindIntent should schedule notification when valid remindLaterTimestamp and broadcastReceiverClass are provided`() { + RemindLaterHandler.handleRemindIntent(getRemindLaterIntentTimestamp(), mockBroadcastReceiverClass) + + verify { PendingIntentUtils.scheduleNotification(any(), any(), any(), any()) } + verify(atLeast = 1) { mockedNotificationManagerCompat.cancel(any()) } + } + + @Test(expected = NotificationConstructionFailedException::class) + fun `handleRemindIntent should throw NotificationConstructionFailedException when applicationContext is null`() { + every { ServiceProvider.getInstance().appContextService.applicationContext } returns null + + RemindLaterHandler.handleRemindIntent(getRemindLaterIntentDuration(), mockBroadcastReceiverClass) + + verify(exactly = 0) { PendingIntentUtils.scheduleNotification(any(), any(), any(), any()) } + verify(exactly = 0) { mockedNotificationManagerCompat.cancel(any()) } + } + + @Test(expected = NotificationConstructionFailedException::class) + fun `handleRemindIntent should throw NotificationConstructionFailedException when intent extras are null`() { + + RemindLaterHandler.handleRemindIntent(Intent(), mockBroadcastReceiverClass) + + verify(exactly = 0) { PendingIntentUtils.scheduleNotification(any(), any(), any(), any()) } + verify(exactly = 0) { mockedNotificationManagerCompat.cancel(any()) } + } + + @Test(expected = IllegalArgumentException::class) + fun `handleRemindIntent should throw IllegalArgumentException when remindLaterDuration is less than or equal to current timestamp`() { + val remindLaterIntent = getRemindLaterIntentDuration() + remindLaterIntent.putExtra(PushTemplateConstants.PushPayloadKeys.REMIND_LATER_DURATION, "-20") + + RemindLaterHandler.handleRemindIntent(remindLaterIntent, mockBroadcastReceiverClass) + + verify(exactly = 0) { PendingIntentUtils.scheduleNotification(any(), any(), any(), any()) } + verify(atLeast = 1) { mockedNotificationManagerCompat.cancel(any()) } + } + + @Test(expected = IllegalArgumentException::class) + fun `handleRemindIntent should throw IllegalArgumentException when remindLaterDuration is invalid`() { + val remindLaterIntent = getRemindLaterIntentDuration() + remindLaterIntent.putExtra(PushTemplateConstants.PushPayloadKeys.REMIND_LATER_DURATION, "invalid") + + RemindLaterHandler.handleRemindIntent(remindLaterIntent, mockBroadcastReceiverClass) + + verify(exactly = 0) { PendingIntentUtils.scheduleNotification(any(), any(), any(), any()) } + verify(atLeast = 1) { mockedNotificationManagerCompat.cancel(any()) } + } + + @Test(expected = IllegalArgumentException::class) + fun `handleRemindIntent should schedule notification when valid remindLaterTimestamp is less than or equal to current timestamp`() { + val remindLaterIntent = getRemindLaterIntentTimestamp() + remindLaterIntent.putExtra(PushTemplateConstants.PushPayloadKeys.REMIND_LATER_TIMESTAMP, (System.currentTimeMillis() / 1000L - 10).toString()) + + RemindLaterHandler.handleRemindIntent(remindLaterIntent, mockBroadcastReceiverClass) + + verify(exactly = 0) { PendingIntentUtils.scheduleNotification(any(), any(), any(), any()) } + verify(atLeast = 1) { mockedNotificationManagerCompat.cancel(any()) } + } + + @Test(expected = IllegalArgumentException::class) + fun `handleRemindIntent should schedule notification when remindLaterTimestamp is invalid`() { + val remindLaterIntent = getRemindLaterIntentTimestamp() + remindLaterIntent.putExtra(PushTemplateConstants.PushPayloadKeys.REMIND_LATER_TIMESTAMP, "invalid") + + RemindLaterHandler.handleRemindIntent(remindLaterIntent, mockBroadcastReceiverClass) + + verify(exactly = 0) { PendingIntentUtils.scheduleNotification(any(), any(), any(), any()) } + verify(atLeast = 1) { mockedNotificationManagerCompat.cancel(any()) } + } + + @Test + fun `handleRemindIntent should not schedule notification when broadcastReceiverClass is null`() { + RemindLaterHandler.handleRemindIntent(getRemindLaterIntentDuration(), null) + + verify(exactly = 0) { PendingIntentUtils.scheduleNotification(any(), any(), any(), any()) } + verify(atLeast = 1) { mockedNotificationManagerCompat.cancel(any()) } + } + + @Test + fun `handleRemindIntent should not schedule notification when tag is null`() { + val remindLaterIntent = getRemindLaterIntentDuration() + remindLaterIntent.removeExtra(PushTemplateConstants.PushPayloadKeys.TAG) + RemindLaterHandler.handleRemindIntent(remindLaterIntent, mockBroadcastReceiverClass) + + verify(atLeast = 1) { PendingIntentUtils.scheduleNotification(any(), any(), any(), any()) } + verify(exactly = 0) { mockedNotificationManagerCompat.cancel(any()) } + } + + private fun getRemindLaterIntentDuration(): Intent { + val remindLaterIntent = Intent() + remindLaterIntent.putExtra(PushTemplateConstants.PushPayloadKeys.REMIND_LATER_DURATION, "20") + remindLaterIntent.putExtra(PushTemplateConstants.PushPayloadKeys.TAG, "testTag") + return remindLaterIntent + } + + private fun getRemindLaterIntentTimestamp(): Intent { + val remindLaterIntent = Intent() + remindLaterIntent.putExtra(PushTemplateConstants.PushPayloadKeys.REMIND_LATER_TIMESTAMP, (System.currentTimeMillis() / 1000L + 10).toString()) + remindLaterIntent.putExtra(PushTemplateConstants.PushPayloadKeys.TAG, "testTag") + return remindLaterIntent + } +} From 0ada45c0d5ac848488a75fc4f7b044ec01817287 Mon Sep 17 00:00:00 2001 From: Ryan Morales Date: Fri, 21 Jun 2024 08:20:50 -0700 Subject: [PATCH 153/159] fix NotificationBuilderTests package --- .../{internal => }/NotificationBuilderTests.kt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) rename code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/{internal => }/NotificationBuilderTests.kt (98%) diff --git a/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/NotificationBuilderTests.kt b/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/NotificationBuilderTests.kt similarity index 98% rename from code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/NotificationBuilderTests.kt rename to code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/NotificationBuilderTests.kt index d51dd103..4362c756 100644 --- a/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/NotificationBuilderTests.kt +++ b/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/NotificationBuilderTests.kt @@ -9,7 +9,7 @@ governing permissions and limitations under the License. */ -package com.adobe.marketing.mobile.notificationbuilder.internal +package com.adobe.marketing.mobile.notificationbuilder import android.app.Activity import android.app.AlarmManager @@ -19,9 +19,9 @@ import android.content.BroadcastReceiver import android.content.Context import android.content.Intent import androidx.core.app.NotificationManagerCompat -import com.adobe.marketing.mobile.notificationbuilder.NotificationBuilder -import com.adobe.marketing.mobile.notificationbuilder.NotificationConstructionFailedException -import com.adobe.marketing.mobile.notificationbuilder.PushTemplateConstants +import com.adobe.marketing.mobile.notificationbuilder.internal.PendingIntentUtils +import com.adobe.marketing.mobile.notificationbuilder.internal.PushTemplateImageUtils +import com.adobe.marketing.mobile.notificationbuilder.internal.PushTemplateType import com.adobe.marketing.mobile.notificationbuilder.internal.builders.AutoCarouselNotificationBuilder import com.adobe.marketing.mobile.notificationbuilder.internal.builders.BasicNotificationBuilder import com.adobe.marketing.mobile.notificationbuilder.internal.builders.InputBoxNotificationBuilder From 2e220607592c5225b696c427e710609ac65b708d Mon Sep 17 00:00:00 2001 From: Ryan Morales Date: Fri, 21 Jun 2024 08:22:06 -0700 Subject: [PATCH 154/159] fix format error --- .../internal/templates/MockDataUtils.kt | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/templates/MockDataUtils.kt b/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/templates/MockDataUtils.kt index 28389d03..54dc0189 100644 --- a/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/templates/MockDataUtils.kt +++ b/code/notificationbuilder/src/test/java/com/adobe/marketing/mobile/notificationbuilder/internal/templates/MockDataUtils.kt @@ -201,14 +201,17 @@ internal fun provideMockedTimerTemplate( MapData(dataMap) } return TimerPushTemplate(data) +} internal fun provideMockedProductCatalogTemplate(isFromIntent: Boolean = false): ProductCatalogPushTemplate { val data: NotificationData if (isFromIntent) { - val mockBundle = MockProductCatalogTemplateDataProvider.getMockedBundleWithProductCatalogData() + val mockBundle = + MockProductCatalogTemplateDataProvider.getMockedBundleWithProductCatalogData() data = IntentData(mockBundle, null) } else { - data = MapData(MockProductCatalogTemplateDataProvider.getMockedMapWithProductCatalogData()) + data = + MapData(MockProductCatalogTemplateDataProvider.getMockedMapWithProductCatalogData()) } return ProductCatalogPushTemplate(data) } @@ -216,7 +219,8 @@ internal fun provideMockedProductCatalogTemplate(isFromIntent: Boolean = false): internal fun provideMockedProductRatingTemplate(isFromIntent: Boolean = false): ProductRatingPushTemplate { val data: NotificationData if (isFromIntent) { - val mockBundle = MockProductRatingTemplateDataProvider.getMockedBundleForRatingTemplate() + val mockBundle = + MockProductRatingTemplateDataProvider.getMockedBundleForRatingTemplate() data = IntentData(mockBundle, null) } else { val dataMap = MockProductRatingTemplateDataProvider.getMockedDataMapForRatingTemplate() From bfee9581f2921f16be3dd15bfbdc66795bb877ef Mon Sep 17 00:00:00 2001 From: Navratan Soni Date: Fri, 21 Jun 2024 22:46:52 +0530 Subject: [PATCH 155/159] Fixed UI issue when less then 5 images are present --- .../src/main/res/layout/push_template_multi_icon.xml | 1 - 1 file changed, 1 deletion(-) diff --git a/code/notificationbuilder/src/main/res/layout/push_template_multi_icon.xml b/code/notificationbuilder/src/main/res/layout/push_template_multi_icon.xml index 2c8bd71a..4477bc37 100644 --- a/code/notificationbuilder/src/main/res/layout/push_template_multi_icon.xml +++ b/code/notificationbuilder/src/main/res/layout/push_template_multi_icon.xml @@ -23,7 +23,6 @@ android:orientation="horizontal" android:layout_width="match_parent" android:layout_height="match_parent" - android:weightSum="5" android:layout_centerVertical="true" android:gravity="center_vertical" android:layout_toStartOf="@+id/five_icon_close_button"/> From 938fecdc9cd8b9942fcd5ea91e04222b971154f9 Mon Sep 17 00:00:00 2001 From: Navratan Soni Date: Sat, 22 Jun 2024 00:30:48 +0530 Subject: [PATCH 156/159] Made space between title and body, added zero bazel notif fix --- .../src/main/res/layout/push_template_zero_bezel_expanded.xml | 1 + code/notificationbuilder/src/main/res/values/dimens.xml | 1 + code/notificationbuilder/src/main/res/values/styles.xml | 1 + 3 files changed, 3 insertions(+) diff --git a/code/notificationbuilder/src/main/res/layout/push_template_zero_bezel_expanded.xml b/code/notificationbuilder/src/main/res/layout/push_template_zero_bezel_expanded.xml index f0f02ab4..451abc7b 100644 --- a/code/notificationbuilder/src/main/res/layout/push_template_zero_bezel_expanded.xml +++ b/code/notificationbuilder/src/main/res/layout/push_template_zero_bezel_expanded.xml @@ -40,6 +40,7 @@ 48dp 56dp 4dp + 4dp \ No newline at end of file diff --git a/code/notificationbuilder/src/main/res/values/styles.xml b/code/notificationbuilder/src/main/res/values/styles.xml index aec29723..2626dcbe 100644 --- a/code/notificationbuilder/src/main/res/values/styles.xml +++ b/code/notificationbuilder/src/main/res/values/styles.xml @@ -18,6 +18,7 @@ bold 12sp 1 + @dimen/standard_title_bottom_margin end