├── .crux_template.md ├── .editorconfig ├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md └── PULL_REQUEST_TEMPLATE.md ├── .gitignore ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE.txt ├── NOTICE.txt ├── README.md ├── SECURITY.md ├── build.gradle ├── buildspec.yml ├── clickstream ├── .gitignore ├── build.gradle ├── consumer-rules.pro ├── gradle.properties ├── proguard-rules.pro └── src │ ├── main │ ├── AndroidManifest.xml │ └── java │ │ └── software │ │ └── aws │ │ └── solution │ │ └── clickstream │ │ ├── AWSClickstreamPlugin.java │ │ ├── ActivityLifecycleManager.java │ │ ├── AnalyticsLongProperty.java │ │ ├── AutoEventSubmitter.java │ │ ├── ClickstreamAnalytics.java │ │ ├── ClickstreamAttribute.java │ │ ├── ClickstreamConfiguration.java │ │ ├── ClickstreamEvent.java │ │ ├── ClickstreamItem.java │ │ ├── ClickstreamUserAttribute.java │ │ └── client │ │ ├── AnalyticsClient.java │ │ ├── AnalyticsEvent.java │ │ ├── AutoRecordEventClient.java │ │ ├── ClickstreamContext.java │ │ ├── ClickstreamExceptionHandler.java │ │ ├── ClickstreamManager.java │ │ ├── Event.java │ │ ├── EventChecker.java │ │ ├── EventRecorder.java │ │ ├── SDKInfo.java │ │ ├── ScreenRefererTool.java │ │ ├── Session.java │ │ ├── SessionClient.java │ │ ├── db │ │ ├── ClickstreamDBBase.java │ │ ├── ClickstreamDBUtil.java │ │ ├── ClickstreamDatabaseHelper.java │ │ └── EventTable.java │ │ ├── network │ │ ├── NetRequest.java │ │ ├── NetUtil.java │ │ └── UserAgentInterceptor.java │ │ ├── system │ │ ├── AndroidAppDetails.java │ │ ├── AndroidConnectivity.java │ │ ├── AndroidDeviceDetails.java │ │ ├── AndroidPreferences.java │ │ └── AndroidSystem.java │ │ ├── uniqueid │ │ └── SharedPrefsDeviceIdService.java │ │ └── util │ │ ├── JSONBuilder.java │ │ ├── JSONSerializable.java │ │ ├── PreferencesUtil.java │ │ ├── StringUtil.java │ │ └── ThreadUtil.java │ └── test │ ├── java │ └── software │ │ └── aws │ │ └── solution │ │ └── clickstream │ │ ├── ActivityLifecycleManagerUnitTest.java │ │ ├── AnalyticsClientTest.java │ │ ├── AnalyticsEventTest.java │ │ ├── AutoRecordEventClientTest.java │ │ ├── EventRecorderTest.java │ │ ├── ExceptionHandlerTest.java │ │ ├── InitClientTest.java │ │ ├── IntegrationTest.java │ │ ├── SessionClientTest.java │ │ ├── db │ │ └── DBUtilTest.java │ │ ├── event │ │ └── EventCheckerTest.java │ │ ├── system │ │ └── AndroidPreferencesTest.java │ │ ├── uniqueid │ │ └── SharedPrefsDeviceIdServiceTest.java │ │ └── util │ │ ├── CustomOkhttpDns.java │ │ ├── PreferencesUtilTest.java │ │ ├── ReflectUtil.java │ │ └── StringUtilTest.java │ ├── res │ └── raw │ │ └── amplifyconfiguration.json │ └── resources │ └── robolectric.properties ├── configuration ├── checkstyle-rules.xml ├── checkstyle-suppressions.xml ├── checkstyle.gradle ├── consumer-rules.pro └── java.header ├── deployment ├── build-open-source-dist.sh └── build-s3-dist.sh ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── images └── raw_folder.png ├── integrationtest ├── appium │ └── shopping_test.py ├── devicefarm │ ├── automate_device_farm.py │ └── logcat_test.py └── requirements.txt ├── jacoco.gradle ├── publishing.gradle ├── release.sh ├── settings.gradle ├── solution-manifest.yaml └── sonar-project.properties /.crux_template.md: -------------------------------------------------------------------------------- 1 | ## Context 2 | ** add your description here ** 3 | 4 | Link to Asana task: 5 | 6 | 7 | ## Checklist 8 | Select all applicable fields with (+, x, y) and mark all non-applicable fields with (n/a or -) in the list 9 | 10 | ### Testing 11 | - [] All unit tests pass (locally and/or on the feature pipeline). 12 | - [] Functional tests pass, if applicable. 13 | - Refer to the [Integration Test section](https://quip-amazon.com/4atAAHVokRYT/Test-Clickstream-Android-SDK) for step-by-step instructions. 14 | - [] I have added new unit tests, if necessary for the changes made. 15 | 16 | ### Build, deployment & pipelines 17 | - [] Local build (`./gradlew build -p clickstream`) completes successfully. 18 | - [] Feature pipeline completes successfully for my changes. 19 | - [] NightsWatch Deployment tests pass (verify for significant changes). 20 | 21 | ### Miscellaneous 22 | - [] I have reviewed the *New Code* and *Overall Code* tab in SonarQube for my branch. 23 | - [] I have removed any commented-out code or debugging statements. 24 | - [] I have updated the README or relevant documentation if necessary. 25 | - [] I have included any additional information or diagrams for other developers to understand my changes. 26 | 27 | ## Additional Notes including Supplementary Testing/Screenshots 28 | * Add your notes and screenshots here 29 | * If any items in the checklist are not applicable to your changes, please explain why. 30 | * If there are any known issues or limitations with the changes, please document them. -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | [*.{kt,kts}] 2 | #this is to match java checkstyle 3 | max_line_length=120 4 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: bug 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior. 15 | 16 | **Expected behavior** 17 | A clear and concise description of what you expected to happen. 18 | 19 | **Please complete the following information about the solution:** 20 | - [ ] Version: [e.g. v1.0.0] 21 | 22 | To get the version of the solution, you can look at the description of the created CloudFormation stack. For example, "_(SO0021) - Video On Demand workflow with AWS Step Functions, MediaConvert, MediaPackage, S3, CloudFront and DynamoDB. Version **v5.0.0**_". If you have not yet installed the stack, or are unable to install due to a problem, you can find the version and solution ID in the template with a text editor. Open the .template file and search for `SOLUTION_VERSION` in the content. You will find several matches and they will all be the same value: 23 | 24 | ```json 25 | "Environment": { 26 | "Variables": { 27 | "SOLUTION_ID": "SO0248", 28 | "SOLUTION_VERSION": "v1.0.2" 29 | } 30 | }, 31 | ``` 32 | 33 | This information is also provided in `source/infrastructure/cdk.json`: 34 | 35 | ```json 36 | "SOLUTION_ID": "SO0248", 37 | "SOLUTION_VERSION": "v1.0.2", 38 | ``` 39 | 40 | 41 | 42 | - [ ] Region: [e.g. us-east-1] 43 | - [ ] Was the solution modified from the version published on this repository? 44 | - [ ] If the answer to the previous question was yes, are the changes available on GitHub? 45 | - [ ] Have you checked your [service quotas](https://docs.aws.amazon.com/general/latest/gr/aws_service_limits.html) for the sevices this solution uses? 46 | - [ ] Were there any errors in the CloudWatch Logs? 47 | 48 | **Screenshots** 49 | If applicable, add screenshots to help explain your problem (please **DO NOT include sensitive information**). 50 | 51 | **Additional context** 52 | Add any other context about the problem here. 53 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this solution 4 | title: '' 5 | labels: enhancement 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the feature you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Additional context** 17 | Add any other context or screenshots about the feature request here. -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | *Issue #, if available:* 2 | 3 | *Description of changes:* 4 | 5 | By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice. -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | 3 | # Built application files 4 | *.apk 5 | *.ap_ 6 | 7 | # Files for the ART/Dalvik VM 8 | *.dex 9 | 10 | # Java class files 11 | *.class 12 | 13 | # Generated files 14 | bin/ 15 | gen/ 16 | out/ 17 | 18 | # Gradle files 19 | .gradle/ 20 | build/ 21 | .gradle/** 22 | 23 | # Local configuration file (sdk path, etc) 24 | local.properties 25 | key.properties 26 | 27 | # Proguard folder generated by Eclipse 28 | proguard/ 29 | 30 | # Log Files 31 | *.log 32 | 33 | # Android Studio Navigation editor temp files 34 | .navigation/ 35 | 36 | # Android Studio captures folder 37 | captures/ 38 | 39 | # IntelliJ 40 | *.iml 41 | **/*.iml 42 | .idea/workspace.xml 43 | .idea/tasks.xml 44 | .idea/gradle.xml 45 | .idea/assetWizardSettings.xml 46 | .idea/dictionaries 47 | .idea/libraries 48 | .idea/caches 49 | .idea/misc.xml 50 | .idea/** 51 | 52 | # VIM 53 | *.swp 54 | *.swo 55 | 56 | # Keystore files 57 | # Uncomment the following line if you do not want to check your keystore files in. 58 | *.jks 59 | 60 | # External native build folder generated in Android Studio 2.2 and later 61 | .externalNativeBuild 62 | 63 | # Google Services (e.g. APIs or Firebase) 64 | google-services.json 65 | 66 | # Freeline 67 | freeline.py 68 | freeline/ 69 | freeline_project_description.json 70 | 71 | # fastlane 72 | scripts/fastlane/report.xml 73 | scripts/fastlane/Preview.html 74 | scripts/fastlane/screenshots 75 | scripts/fastlane/test_output 76 | scripts/fastlane/readme.md 77 | 78 | # maven file 79 | target/ 80 | 81 | #python comile 82 | __pycache__/ 83 | 84 | # Credentials 85 | **/awsconfiguration.json 86 | **/amplifyconfiguration.json 87 | **/amplifyconfiguration_v2.json 88 | **/credentials.json 89 | **/google_client_creds.json 90 | 91 | #amplify 92 | amplify/ 93 | dist/ 94 | node_modules/ 95 | aws-exports.js 96 | awsconfiguration.json 97 | 98 | test-results/ 99 | build-artifacts/ 100 | build-artifacts.tar.gz 101 | 102 | # add ignore for jenv 103 | .java-version 104 | 105 | # python 106 | venv 107 | *.idea 108 | 109 | deployment/open-source/ 110 | deployment/regional-s3-assets 111 | deployment/global-s3-assets -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 6 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 7 | 8 | ## [1.0.0] - 2025-02-14 9 | 10 | ### Changed 11 | 12 | - Upgrade to JDK 21, Gradle 8.11, Android Gradle plugin 8.7.3, and other dependencies 13 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | ## Code of Conduct 2 | This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct). 3 | For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact 4 | opensource-codeofconduct@amazon.com with any additional questions or comments. 5 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing Guidelines 2 | 3 | Thank you for your interest in contributing to our project. Whether it's a bug report, new feature, correction, or additional 4 | documentation, we greatly value feedback and contributions from our community. 5 | 6 | Please read through this document before submitting any issues or pull requests to ensure we have all the necessary 7 | information to effectively respond to your bug report or contribution. 8 | 9 | ## Reporting Bugs/Feature Requests 10 | 11 | We welcome you to use the GitHub issue tracker to report bugs or suggest features. 12 | 13 | When filing an issue, please check [existing open](https://github.com/aws-solutions/clickstream-analytics-on-aws-android-sdk/issues), or [recently closed](https://github.com/aws-solutions/clickstream-analytics-on-aws-android-sdk/issues?q=is%3Aissue%20state%3Aclosed), issues to make sure somebody else hasn't already 14 | reported the issue. Please try to include as much information as you can. Details like these are incredibly useful: 15 | 16 | - A reproducible test case or series of steps 17 | - The version of our code being used 18 | - Any modifications you've made relevant to the bug 19 | - Anything unusual about your environment or deployment 20 | 21 | ## Contributing via Pull Requests 22 | 23 | Contributions via pull requests are much appreciated. Before sending us a pull request, please ensure that: 24 | 25 | 1. You are working against the latest source on the _main_ branch. 26 | 2. You check existing open, and recently merged, pull requests to make sure someone else hasn't addressed the problem already. 27 | 3. You open an issue to discuss any significant work - we would hate for your time to be wasted. 28 | 29 | To send us a pull request, please: 30 | 31 | 1. Fork the repository. 32 | 2. Modify the source; please focus on the specific change you are contributing. If you also reformat all the code, it will be hard for us to focus on your change. 33 | 3. Ensure local tests pass. 34 | 4. Commit to your fork using clear commit messages. 35 | 5. Send us a pull request, answering any default questions in the pull request interface. 36 | 6. Pay attention to any automated CI failures reported in the pull request, and stay involved in the conversation. 37 | 38 | GitHub provides additional document on [forking a repository](https://help.github.com/articles/fork-a-repo/) and 39 | [creating a pull request](https://help.github.com/articles/creating-a-pull-request/). 40 | 41 | ## Finding contributions to work on 42 | 43 | Looking at the existing issues is a great way to find something to contribute on. As our projects, by default, use the default GitHub issue labels ((enhancement/bug/duplicate/help wanted/invalid/question/wontfix), looking at any ['help wanted'](https://github.com/aws-solutions/clickstream-analytics-on-aws-android-sdk/labels/help%20wanted) issues is a great place to start. 44 | 45 | ## Code of Conduct 46 | 47 | This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct). 48 | For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact 49 | opensource-codeofconduct@amazon.com with any additional questions or comments. 50 | 51 | ## Security issue notifications 52 | 53 | If you discover a potential security issue in this project we ask that you notify AWS/Amazon Security via our [vulnerability reporting page](http://aws.amazon.com/security/vulnerability-reporting/). Please do **not** create a public github issue. 54 | 55 | ## Licensing 56 | 57 | See the [LICENSE](https://github.com/aws-solutions/clickstream-analytics-on-aws-android-sdk/blob/main/LICENSE) file for our project's licensing. We will ask you to confirm the licensing of your contribution. 58 | 59 | We may ask you to sign a [Contributor License Agreement (CLA)](https://en.wikipedia.org/wiki/Contributor_License_Agreement) for larger changes. -------------------------------------------------------------------------------- /NOTICE.txt: -------------------------------------------------------------------------------- 1 | Clickstream Analytics on AWS Swift SDK 2 | 3 | Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 4 | Licensed under the Apache License Version 2.0 (the "License"). You may not use this file except 5 | in compliance with the License. A copy of the License is located at http://www.apache.org/licenses/ 6 | or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, 7 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, express or implied. See the License for the 8 | specific language governing permissions and limitations under the License. 9 | 10 | ********************** 11 | THIRD PARTY COMPONENTS 12 | ********************** 13 | 14 | This software includes third party software subject to the following copyrights: 15 | 16 | Appium-Python-Client under the Apache-2.0 license. 17 | pytest under the MIT license. 18 | boto3 under the Apache-2.0 license. 19 | requests under the Apache-2.0 license. 20 | PyYAML under the MIT license. 21 | pytest-html under the MIT license. 22 | selenium under the Apache-2.0 license. 23 | com.amazonaws:aws-android-sdk-mobile-client under the Apache-2.0 license. 24 | com.amplifyframework:core under the Apache-2.0 license. 25 | com.squareup.okhttp3:okhttp under the Apache-2.0 license. 26 | androidx.lifecycle:lifecycle-common-java8 under the Apache-2.0 license. 27 | androidx.lifecycle:lifecycle-process under the Apache-2.0 license. 28 | junit:junit under the EPL-1.0 license. 29 | org.mockito:mockito-core under MIT license. 30 | org.mockito:mockito-inline under MIT license. 31 | org.robolectric:robolectric under MIT license. 32 | androidx.test:core under the Apache-2.0 license. 33 | com.github.dreamhead:moco-core under MIT license. 34 | org.jacoco:org.jacoco.agent under the EPL-2.0 license. 35 | org.jacoco:org.jacoco.ant under the EPL-2.0 license. 36 | 37 | 38 | ******************** 39 | OPEN SOURCE LICENSES 40 | ******************** 41 | 42 | Apache-2.0 - https://spdx.org/licenses/Apache-2.0.html 43 | MIT - https://spdx.org/licenses/MIT.html 44 | EPL-1.0 - https://spdx.org/licenses/EPL-1.0.html 45 | EPL-2.0 - https://spdx.org/licenses/EPL-2.0.html 46 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Reporting Security Issues 2 | 3 | We take all security reports seriously. 4 | When we receive such reports, 5 | we will investigate and subsequently address 6 | any potential vulnerabilities as quickly as possible. 7 | If you discover a potential security issue in this project, 8 | please notify AWS/Amazon Security via our 9 | [vulnerability reporting page](http://aws.amazon.com/security/vulnerability-reporting/) 10 | or directly via email to [AWS Security](mailto:aws-security@amazon.com). 11 | Please do *not* create a public GitHub issue in this project. 12 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). 5 | * You may not use this file except in compliance with the License. 6 | * A copy of the License is located at 7 | * 8 | * http://aws.amazon.com/apache2.0 9 | * 10 | * or in the "license" file accompanying this file. This file is distributed 11 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 12 | * express or implied. See the License for the specific language governing 13 | * permissions and limitations under the License. 14 | */ 15 | 16 | plugins { 17 | id 'java' 18 | id 'jacoco' 19 | } 20 | 21 | allprojects { 22 | repositories { 23 | google() 24 | mavenCentral() 25 | gradlePluginPortal() 26 | } 27 | } 28 | 29 | clean { 30 | delete rootProject.buildDir 31 | delete "clickstream/build/" 32 | } 33 | 34 | 35 | ext { 36 | compileSdkVersion = 34 37 | minSdkVersion = 19 38 | targetSdkVersion = 34 39 | awsSdkVersion = '2.59.1' 40 | amplifySdkVersion = '1.38.8' 41 | lifecycleVersion = "2.8.7" 42 | dependency = [ 43 | android: [ 44 | desugartools: 'com.android.tools:desugar_jdk_libs:1.0.9', 45 | ], 46 | androidx: [ 47 | test: 'androidx.test:core:1.6.1', 48 | lifecycle_common: "androidx.lifecycle:lifecycle-common-java8:$lifecycleVersion", 49 | lifecycle_process: "androidx.lifecycle:lifecycle-process:$lifecycleVersion", 50 | ], 51 | amplifyframework: [ 52 | core: "com.amplifyframework:core:$amplifySdkVersion" 53 | ], 54 | aws: [ 55 | mobileclient: "com.amazonaws:aws-android-sdk-mobile-client:$awsSdkVersion" 56 | ], 57 | okhttp: 'com.squareup.okhttp3:okhttp:4.12.0', 58 | junit: 'junit:junit:4.13.2', 59 | mockito: 'org.mockito:mockito-core:5.14.2', 60 | mockitoinline: 'org.mockito:mockito-inline:4.11.0', 61 | moco: 'com.github.dreamhead:moco-core:1.4.0', 62 | robolectric: 'org.robolectric:robolectric:4.14', 63 | ] 64 | } 65 | 66 | subprojects { project -> 67 | afterEvaluate { 68 | configAndroidLibrary(project) 69 | project.apply from: '../jacoco.gradle' 70 | project.apply from: '../publishing.gradle' 71 | } 72 | } 73 | 74 | private void configAndroidLibrary(Project project) { 75 | project.android { 76 | compileSdkVersion rootProject.ext.compileSdkVersion 77 | 78 | defaultConfig { 79 | multiDexEnabled true 80 | minSdkVersion project.findProperty('minSdkVersion') 81 | targetSdkVersion rootProject.ext.targetSdkVersion 82 | versionName project.ext.VERSION_NAME 83 | consumerProguardFiles rootProject.file('configuration/consumer-rules.pro') 84 | testOptions { 85 | animationsDisabled = true 86 | unitTests { 87 | includeAndroidResources = true 88 | } 89 | } 90 | } 91 | 92 | lintOptions { 93 | warningsAsErrors true 94 | abortOnError true 95 | enable 'UnusedResources' 96 | enable 'NewerVersionAvailable' 97 | } 98 | 99 | compileOptions { 100 | coreLibraryDesugaringEnabled true 101 | sourceCompatibility JavaVersion.VERSION_21 102 | targetCompatibility JavaVersion.VERSION_21 103 | } 104 | } 105 | project.dependencies { 106 | coreLibraryDesugaring dependency.android.desugartools 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /buildspec.yml: -------------------------------------------------------------------------------- 1 | version: 0.2 2 | 3 | env: 4 | shell: bash 5 | variables: 6 | PACKAGE_VERSION: "1.0.0" 7 | PACKAGE_NAME: "aws-clickstream-android" 8 | phases: 9 | install: 10 | runtime-versions: 11 | java: corretto21 12 | pre_build: 13 | commands: 14 | - |- 15 | set -euxo pipefail 16 | echo "=== Download Android command line tools ===" 17 | wget https://dl.google.com/android/repository/commandlinetools-linux-11076708_latest.zip 18 | unzip commandlinetools-linux-11076708_latest.zip 19 | mkdir android_sdk 20 | mv cmdline-tools android_sdk/ 21 | echo "=== Install Android SDK platform and build tools" 22 | echo "y" | android_sdk/cmdline-tools/bin/sdkmanager --sdk_root=$HOME/Android/Sdk --licenses 23 | echo "y" | android_sdk/cmdline-tools/bin/sdkmanager --sdk_root=$HOME/Android/Sdk "platforms;android-33" "platform-tools" "build-tools;33.0.0" 24 | export ANDROID_SDK_ROOT=$HOME/Android/Sdk 25 | rm -rf android_sdk/ 26 | rm -f commandlinetools-linux-11076708_latest.zip 27 | build: 28 | commands: 29 | - BUILD_DIR=$(pwd) 30 | - echo "=== Run Open Source Dist & S3 Dist @ $(date) in $(pwd) ===" 31 | - cd deployment 32 | - chmod +x ./build-s3-dist.sh && ./build-s3-dist.sh --template-bucket ${TEMPLATE_OUTPUT_BUCKET} --version ${VERSION} --region ${AWS_REGION} 33 | - echo "Starting open-source-dist `date` in `pwd`" 34 | - chmod +x ./build-open-source-dist.sh && ./build-open-source-dist.sh $SOLUTION_NAME 35 | - cd $BUILD_DIR 36 | - chmod +x gradlew 37 | - echo "=== Run unit tests ===" 38 | - ./gradlew clean && rm -rf .gradle 39 | - ./gradlew jacocoTestReport 40 | - echo "=== Build Clickstream Android SDK with Gradle ===" 41 | - ./gradlew clean && rm -rf .gradle 42 | - ./gradlew build -p clickstream 43 | post_build: 44 | commands: 45 | - echo "=== Starting Post-Build Phase ===" 46 | 47 | artifacts: 48 | exclude-paths: 49 | - .nightswatch/**/* 50 | files: 51 | - '**/*' 52 | -------------------------------------------------------------------------------- /clickstream/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /clickstream/build.gradle: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). 5 | * You may not use this file except in compliance with the License. 6 | * A copy of the License is located at 7 | * 8 | * http://aws.amazon.com/apache2.0 9 | * 10 | * or in the "license" file accompanying this file. This file is distributed 11 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 12 | * express or implied. See the License for the specific language governing 13 | * permissions and limitations under the License. 14 | */ 15 | 16 | apply plugin: 'com.android.library' 17 | apply from: rootProject.file("configuration/checkstyle.gradle") 18 | 19 | 20 | android { 21 | namespace "software.aws.solution.clickstream" 22 | buildFeatures { 23 | buildConfig = true 24 | } 25 | buildTypes { 26 | release { 27 | // These values are defined only for the release build, which 28 | // is typically used for full builds and continuous builds. 29 | buildConfigField("String", "VERSION_NAME", "\"${VERSION_NAME}\"") 30 | resValue("string", "version_name", "${VERSION_NAME}") 31 | } 32 | debug { 33 | // Use static values for incremental builds to ensure that 34 | // resource files and BuildConfig aren't rebuilt with each run. 35 | // If these rebuild dynamically, they can interfere with 36 | // Apply Changes as well as Gradle UP-TO-DATE checks. 37 | buildConfigField("String", "VERSION_NAME", "\"${VERSION_NAME}-debug\"") 38 | resValue("string", "version_name", "${VERSION_NAME}-debug") 39 | } 40 | } 41 | } 42 | 43 | dependencies { 44 | implementation dependency.aws.mobileclient 45 | implementation dependency.amplifyframework.core 46 | implementation dependency.okhttp 47 | implementation dependency.androidx.lifecycle_common 48 | implementation dependency.androidx.lifecycle_process 49 | 50 | testImplementation dependency.junit 51 | testImplementation dependency.mockito 52 | testImplementation dependency.mockitoinline 53 | testImplementation dependency.robolectric 54 | testImplementation dependency.androidx.test 55 | testImplementation dependency.moco 56 | testImplementation project(path: ':clickstream') 57 | } 58 | -------------------------------------------------------------------------------- /clickstream/consumer-rules.pro: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-solutions/clickstream-analytics-on-aws-android-sdk/53ed68f7e1d49cdc0015940541dc87724c9cc7b0/clickstream/consumer-rules.pro -------------------------------------------------------------------------------- /clickstream/gradle.properties: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-solutions/clickstream-analytics-on-aws-android-sdk/53ed68f7e1d49cdc0015940541dc87724c9cc7b0/clickstream/gradle.properties -------------------------------------------------------------------------------- /clickstream/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile -------------------------------------------------------------------------------- /clickstream/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 15 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /clickstream/src/main/java/software/aws/solution/clickstream/AnalyticsLongProperty.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). 5 | * You may not use this file except in compliance with the License. 6 | * A copy of the License is located at 7 | * 8 | * http://aws.amazon.com/apache2.0 9 | * 10 | * or in the "license" file accompanying this file. This file is distributed 11 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 12 | * express or implied. See the License for the specific language governing 13 | * permissions and limitations under the License. 14 | */ 15 | 16 | package software.aws.solution.clickstream; 17 | 18 | import androidx.annotation.NonNull; 19 | 20 | import com.amplifyframework.analytics.AnalyticsProperties; 21 | import com.amplifyframework.analytics.AnalyticsPropertyBehavior; 22 | 23 | import java.util.Objects; 24 | 25 | /** 26 | * AnalyticsLongProperty wraps an Long value to store in {@link AnalyticsProperties}. 27 | */ 28 | public final class AnalyticsLongProperty implements AnalyticsPropertyBehavior { 29 | private final Long value; 30 | 31 | private AnalyticsLongProperty(Long value) { 32 | this.value = value; 33 | } 34 | 35 | /** 36 | * getValue returns the wrapped Long value stored in the property. 37 | * 38 | * @return The wrapped Boolean value 39 | */ 40 | @NonNull 41 | @Override 42 | public Long getValue() { 43 | return value; 44 | } 45 | 46 | /** 47 | * Factory method to instantiate an {@link AnalyticsLongProperty} from a {@link Long} value. 48 | * 49 | * @param value an Long value 50 | * @return an instance of {@link AnalyticsLongProperty} 51 | */ 52 | @NonNull 53 | public static AnalyticsLongProperty from(@NonNull Long value) { 54 | return new AnalyticsLongProperty(Objects.requireNonNull(value)); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /clickstream/src/main/java/software/aws/solution/clickstream/AutoEventSubmitter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). 5 | * You may not use this file except in compliance with the License. 6 | * A copy of the License is located at 7 | * 8 | * http://aws.amazon.com/apache2.0 9 | * 10 | * or in the "license" file accompanying this file. This file is distributed 11 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 12 | * express or implied. See the License for the specific language governing 13 | * permissions and limitations under the License. 14 | */ 15 | 16 | package software.aws.solution.clickstream; 17 | 18 | import android.os.Handler; 19 | import android.os.HandlerThread; 20 | 21 | import com.amplifyframework.core.Amplify; 22 | 23 | import com.amazonaws.logging.Log; 24 | import com.amazonaws.logging.LogFactory; 25 | 26 | /** 27 | * Submits all the recorded event periodically. 28 | */ 29 | final class AutoEventSubmitter { 30 | private static final Log LOG = LogFactory.getLog(AutoEventSubmitter.class); 31 | private final Handler handler; 32 | private Runnable submitRunnable; 33 | private final long autoFlushInterval; 34 | 35 | AutoEventSubmitter(final long autoFlushInterval) { 36 | HandlerThread handlerThread = new HandlerThread("AutoEventSubmitter"); 37 | handlerThread.start(); 38 | this.handler = new Handler(handlerThread.getLooper()); 39 | this.autoFlushInterval = autoFlushInterval; 40 | this.submitRunnable = () -> { 41 | Amplify.Analytics.flushEvents(); 42 | handler.postDelayed(this.submitRunnable, autoFlushInterval); 43 | }; 44 | LOG.debug("Auto submitting init"); 45 | } 46 | 47 | synchronized void start() { 48 | handler.postDelayed(submitRunnable, autoFlushInterval); 49 | LOG.debug("Auto submitting start"); 50 | } 51 | 52 | synchronized void stop() { 53 | handler.removeCallbacksAndMessages(null); 54 | LOG.debug("Auto submitting stop"); 55 | } 56 | } 57 | 58 | -------------------------------------------------------------------------------- /clickstream/src/main/java/software/aws/solution/clickstream/ClickstreamAttribute.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). 5 | * You may not use this file except in compliance with the License. 6 | * A copy of the License is located at 7 | * 8 | * http://aws.amazon.com/apache2.0 9 | * 10 | * or in the "license" file accompanying this file. This file is distributed 11 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 12 | * express or implied. See the License for the specific language governing 13 | * permissions and limitations under the License. 14 | */ 15 | 16 | package software.aws.solution.clickstream; 17 | 18 | import androidx.annotation.NonNull; 19 | import androidx.annotation.Size; 20 | 21 | import com.amplifyframework.analytics.AnalyticsBooleanProperty; 22 | import com.amplifyframework.analytics.AnalyticsDoubleProperty; 23 | import com.amplifyframework.analytics.AnalyticsIntegerProperty; 24 | import com.amplifyframework.analytics.AnalyticsProperties; 25 | import com.amplifyframework.analytics.AnalyticsStringProperty; 26 | 27 | import software.aws.solution.clickstream.client.Event; 28 | 29 | import java.io.Serializable; 30 | 31 | /** 32 | * Clickstream attribute setter. 33 | */ 34 | public class ClickstreamAttribute implements Serializable { 35 | private final AnalyticsProperties attributes; 36 | 37 | /** 38 | * Constructor for init the userAttribute and userId. 39 | * 40 | * @param builder An instance of the builder with the desired properties set. 41 | */ 42 | protected ClickstreamAttribute(@NonNull Builder builder) { 43 | this.attributes = builder.analyticsPropertiesBuilder.build(); 44 | } 45 | 46 | /** 47 | * getter for userAttributes. 48 | * 49 | * @return userAttributes 50 | */ 51 | public AnalyticsProperties getAttributes() { 52 | return attributes; 53 | } 54 | 55 | /** 56 | * Begins construction of an {@link ClickstreamAttribute} using a builder pattern. 57 | * 58 | * @return An {@link ClickstreamAttribute.Builder} instance 59 | */ 60 | @NonNull 61 | public static Builder builder() { 62 | return new ClickstreamAttribute.Builder(); 63 | } 64 | 65 | /** 66 | * Builder for the {@link ClickstreamAttribute} class. 67 | */ 68 | public static class Builder { 69 | private final AnalyticsProperties.Builder analyticsPropertiesBuilder = AnalyticsProperties.builder(); 70 | 71 | /** 72 | * Adds a {@link AnalyticsStringProperty} to the {@link AnalyticsProperties} under 73 | * construction. 74 | * 75 | * @param key A name for the property 76 | * @param value A String to store in the property 77 | * @return Current Builder instance, for fluent method chaining 78 | */ 79 | @NonNull 80 | public Builder add(@NonNull @Size(min = 1L, max = Event.Limit.MAX_LENGTH_OF_NAME) String key, 81 | @NonNull @Size(min = 0L, max = Event.Limit.MAX_LENGTH_OF_VALUE) String value) { 82 | analyticsPropertiesBuilder.add(key, value); 83 | return this; 84 | } 85 | 86 | /** 87 | * Adds a {@link AnalyticsDoubleProperty} to the {@link AnalyticsProperties} under 88 | * construction. 89 | * 90 | * @param key A name for the property 91 | * @param value A Double to store in the property 92 | * @return Current Builder instance, for fluent method chaining 93 | */ 94 | @NonNull 95 | public Builder add(@NonNull @Size(min = 1L, max = Event.Limit.MAX_LENGTH_OF_NAME) String key, 96 | @NonNull Double value) { 97 | analyticsPropertiesBuilder.add(key, value); 98 | return this; 99 | } 100 | 101 | /** 102 | * Adds a {@link AnalyticsBooleanProperty} to the {@link AnalyticsProperties} under 103 | * construction. 104 | * 105 | * @param key A name for the property 106 | * @param value A Boolean to store in the property 107 | * @return Current Builder instance, for fluent method chaining 108 | */ 109 | @NonNull 110 | public Builder add(@NonNull @Size(min = 1L, max = Event.Limit.MAX_LENGTH_OF_NAME) String key, 111 | @NonNull Boolean value) { 112 | analyticsPropertiesBuilder.add(key, value); 113 | return this; 114 | } 115 | 116 | /** 117 | * Adds an {@link AnalyticsIntegerProperty} to the {@link AnalyticsProperties} under 118 | * construction. 119 | * 120 | * @param key A name for the property 121 | * @param value An Integer to store in the property 122 | * @return Current Builder instance, for fluent method chaining 123 | */ 124 | @NonNull 125 | public Builder add(@NonNull @Size(min = 1L, max = Event.Limit.MAX_LENGTH_OF_NAME) String key, 126 | @NonNull Integer value) { 127 | analyticsPropertiesBuilder.add(key, value); 128 | return this; 129 | } 130 | 131 | /** 132 | * Adds an {@link AnalyticsLongProperty} to the {@link AnalyticsProperties} under 133 | * construction. 134 | * 135 | * @param key A name for the property 136 | * @param value An Long to store in the property 137 | * @return Current Builder instance, for fluent method chaining 138 | */ 139 | @NonNull 140 | public Builder add(@NonNull @Size(min = 1L, max = Event.Limit.MAX_LENGTH_OF_NAME) String key, 141 | @NonNull Long value) { 142 | analyticsPropertiesBuilder.add(key, AnalyticsLongProperty.from(value)); 143 | return this; 144 | } 145 | 146 | /** 147 | * Builds an instance of {@link ClickstreamAttribute}, using the provided values. 148 | * 149 | * @return An {@link ClickstreamAttribute} 150 | */ 151 | @NonNull 152 | public ClickstreamAttribute build() { 153 | return new ClickstreamAttribute(this); 154 | } 155 | } 156 | } 157 | -------------------------------------------------------------------------------- /clickstream/src/main/java/software/aws/solution/clickstream/ClickstreamItem.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). 5 | * You may not use this file except in compliance with the License. 6 | * A copy of the License is located at 7 | * 8 | * http://aws.amazon.com/apache2.0 9 | * 10 | * or in the "license" file accompanying this file. This file is distributed 11 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 12 | * express or implied. See the License for the specific language governing 13 | * permissions and limitations under the License. 14 | */ 15 | 16 | package software.aws.solution.clickstream; 17 | 18 | import androidx.annotation.NonNull; 19 | import androidx.annotation.Size; 20 | 21 | import com.amazonaws.logging.Log; 22 | import com.amazonaws.logging.LogFactory; 23 | import org.json.JSONException; 24 | import org.json.JSONObject; 25 | import software.aws.solution.clickstream.client.Event; 26 | 27 | /** 28 | * ClickstreamItem for item record. 29 | */ 30 | public final class ClickstreamItem { 31 | private static final Log LOG = LogFactory.getLog(ClickstreamItem.class); 32 | private final JSONObject attributes; 33 | 34 | /** 35 | * Constructor for init the ClickstreamItem. 36 | * 37 | * @param attributes An instance of the builder with the desired attributes set. 38 | */ 39 | private ClickstreamItem(@NonNull JSONObject attributes) { 40 | this.attributes = attributes; 41 | } 42 | 43 | /** 44 | * the getter for attributes. 45 | * 46 | * @return the attributes json object. 47 | */ 48 | public JSONObject getAttributes() { 49 | return attributes; 50 | } 51 | 52 | 53 | /** 54 | * Begins construction of an {@link ClickstreamItem} using a builder pattern. 55 | * 56 | * @return An {@link ClickstreamItem.Builder} instance 57 | */ 58 | @NonNull 59 | public static Builder builder() { 60 | return new ClickstreamItem.Builder(); 61 | } 62 | 63 | /** 64 | * Builder for the {@link ClickstreamItem} class. 65 | */ 66 | public static class Builder { 67 | private final JSONObject jsonObjectBuilder = new JSONObject(); 68 | 69 | /** 70 | * constructor for Builder. 71 | * 72 | * @param key A name for the property 73 | * @param value A String to store in the property 74 | * @return Current Builder instance, for fluent method chaining 75 | */ 76 | @NonNull 77 | public ClickstreamItem.Builder add(@NonNull @Size(min = 1L, max = Event.Limit.MAX_LENGTH_OF_NAME) String key, 78 | @NonNull @Size(min = 0L, max = Event.Limit.MAX_LENGTH_OF_ITEM_VALUE) 79 | String value) { 80 | setAttribute(key, value); 81 | return this; 82 | } 83 | 84 | /** 85 | * Adds double value. 86 | * 87 | * @param key A name for the property 88 | * @param value A Double to store in the property 89 | * @return Current Builder instance, for fluent method chaining 90 | */ 91 | @NonNull 92 | public ClickstreamItem.Builder add(@NonNull @Size(min = 1L, max = Event.Limit.MAX_LENGTH_OF_NAME) String key, 93 | @NonNull Double value) { 94 | setAttribute(key, value); 95 | return this; 96 | } 97 | 98 | /** 99 | * Adds boolean value. 100 | * 101 | * @param key A name for the property 102 | * @param value A Boolean to store in the property 103 | * @return Current Builder instance, for fluent method chaining 104 | */ 105 | @NonNull 106 | public ClickstreamItem.Builder add(@NonNull @Size(min = 1L, max = Event.Limit.MAX_LENGTH_OF_NAME) String key, 107 | @NonNull Boolean value) { 108 | setAttribute(key, value); 109 | return this; 110 | } 111 | 112 | /** 113 | * Adds int value. 114 | * 115 | * @param key A name for the property 116 | * @param value An Integer to store in the property 117 | * @return Current Builder instance, for fluent method chaining 118 | */ 119 | @NonNull 120 | public ClickstreamItem.Builder add(@NonNull @Size(min = 1L, max = Event.Limit.MAX_LENGTH_OF_NAME) String key, 121 | @NonNull Integer value) { 122 | setAttribute(key, value); 123 | return this; 124 | } 125 | 126 | /** 127 | * Adds long value. 128 | * 129 | * @param key A name for the property 130 | * @param value An Long to store in the property 131 | * @return Current Builder instance, for fluent method chaining 132 | */ 133 | @NonNull 134 | public ClickstreamItem.Builder add(@NonNull @Size(min = 1L, max = Event.Limit.MAX_LENGTH_OF_NAME) String key, 135 | @NonNull Long value) { 136 | setAttribute(key, value); 137 | return this; 138 | } 139 | 140 | private void setAttribute(String key, Object value) { 141 | try { 142 | jsonObjectBuilder.putOpt(key, value); 143 | } catch (JSONException exception) { 144 | LOG.warn("error parsing json, error message:" + exception.getMessage()); 145 | } 146 | } 147 | 148 | /** 149 | * Builds an instance of {@link ClickstreamItem}, using the provided values. 150 | * 151 | * @return An {@link ClickstreamItem} 152 | */ 153 | @NonNull 154 | public ClickstreamItem build() { 155 | return new ClickstreamItem(jsonObjectBuilder); 156 | } 157 | } 158 | } 159 | -------------------------------------------------------------------------------- /clickstream/src/main/java/software/aws/solution/clickstream/ClickstreamUserAttribute.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). 5 | * You may not use this file except in compliance with the License. 6 | * A copy of the License is located at 7 | * 8 | * http://aws.amazon.com/apache2.0 9 | * 10 | * or in the "license" file accompanying this file. This file is distributed 11 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 12 | * express or implied. See the License for the specific language governing 13 | * permissions and limitations under the License. 14 | */ 15 | 16 | package software.aws.solution.clickstream; 17 | 18 | import androidx.annotation.NonNull; 19 | import androidx.annotation.Size; 20 | 21 | import com.amplifyframework.analytics.AnalyticsBooleanProperty; 22 | import com.amplifyframework.analytics.AnalyticsDoubleProperty; 23 | import com.amplifyframework.analytics.AnalyticsIntegerProperty; 24 | import com.amplifyframework.analytics.AnalyticsProperties; 25 | import com.amplifyframework.analytics.AnalyticsStringProperty; 26 | import com.amplifyframework.analytics.UserProfile; 27 | 28 | /** 29 | * Clickstream UserProfile with userAttributes and userId. 30 | */ 31 | public class ClickstreamUserAttribute extends UserProfile { 32 | private static final int MAX_NAME_LENGTH = 50; 33 | private static final int MAX_VALUE_LENGTH = 256; 34 | private final AnalyticsProperties userAttributes; 35 | 36 | /** 37 | * Constructor for init the userAttribute and userId. 38 | * 39 | * @param builder An instance of the builder with the desired properties set. 40 | */ 41 | protected ClickstreamUserAttribute(@NonNull Builder builder) { 42 | super(builder); 43 | this.userAttributes = builder.analyticsPropertiesBuilder.build(); 44 | } 45 | 46 | /** 47 | * getter for userAttributes. 48 | * 49 | * @return userAttributes 50 | */ 51 | public AnalyticsProperties getUserAttributes() { 52 | return userAttributes; 53 | } 54 | 55 | /** 56 | * Begins construction of an {@link ClickstreamUserAttribute} using a builder pattern. 57 | * 58 | * @return An {@link ClickstreamUserAttribute.Builder} instance 59 | */ 60 | @NonNull 61 | public static Builder builder() { 62 | return new ClickstreamUserAttribute.Builder(); 63 | } 64 | 65 | /** 66 | * Builder for the {@link ClickstreamUserAttribute} class. 67 | */ 68 | public static final class Builder extends UserProfile.Builder { 69 | private final AnalyticsProperties.Builder analyticsPropertiesBuilder = AnalyticsProperties.builder(); 70 | 71 | /** 72 | * Adds a {@link AnalyticsStringProperty} to the {@link AnalyticsProperties} under 73 | * construction. 74 | * 75 | * @param key A name for the property 76 | * @param value A String to store in the property 77 | * @return Current Builder instance, for fluent method chaining 78 | */ 79 | @NonNull 80 | public Builder add(@NonNull @Size(min = 1L, max = MAX_NAME_LENGTH) String key, 81 | @NonNull @Size(min = 1L, max = MAX_VALUE_LENGTH) String value) { 82 | analyticsPropertiesBuilder.add(key, value); 83 | return this; 84 | } 85 | 86 | /** 87 | * Adds a {@link AnalyticsDoubleProperty} to the {@link AnalyticsProperties} under 88 | * construction. 89 | * 90 | * @param key A name for the property 91 | * @param value A Double to store in the property 92 | * @return Current Builder instance, for fluent method chaining 93 | */ 94 | @NonNull 95 | public Builder add(@NonNull @Size(min = 1L, max = MAX_NAME_LENGTH) String key, @NonNull Double value) { 96 | analyticsPropertiesBuilder.add(key, value); 97 | return this; 98 | } 99 | 100 | /** 101 | * Adds a {@link AnalyticsBooleanProperty} to the {@link AnalyticsProperties} under 102 | * construction. 103 | * 104 | * @param key A name for the property 105 | * @param value A Boolean to store in the property 106 | * @return Current Builder instance, for fluent method chaining 107 | */ 108 | @NonNull 109 | public Builder add(@NonNull @Size(min = 1L, max = MAX_NAME_LENGTH) String key, @NonNull Boolean value) { 110 | analyticsPropertiesBuilder.add(key, value); 111 | return this; 112 | } 113 | 114 | /** 115 | * Adds an {@link AnalyticsIntegerProperty} to the {@link AnalyticsProperties} under 116 | * construction. 117 | * 118 | * @param key A name for the property 119 | * @param value An Integer to store in the property 120 | * @return Current Builder instance, for fluent method chaining 121 | */ 122 | @NonNull 123 | public Builder add(@NonNull @Size(min = 1L, max = MAX_NAME_LENGTH) String key, @NonNull Integer value) { 124 | analyticsPropertiesBuilder.add(key, value); 125 | return this; 126 | } 127 | 128 | /** 129 | * Adds an {@link AnalyticsLongProperty} to the {@link AnalyticsProperties} under 130 | * construction. 131 | * 132 | * @param key A name for the property 133 | * @param value An Long to store in the property 134 | * @return Current Builder instance, for fluent method chaining 135 | */ 136 | @NonNull 137 | public ClickstreamUserAttribute.Builder add(@NonNull @Size(min = 1L, max = MAX_NAME_LENGTH) String key, 138 | @NonNull Long value) { 139 | analyticsPropertiesBuilder.add(key, AnalyticsLongProperty.from(value)); 140 | return this; 141 | } 142 | 143 | /** 144 | * Builds an instance of {@link ClickstreamUserAttribute}, using the provided values. 145 | * 146 | * @return An {@link ClickstreamUserAttribute} 147 | */ 148 | @NonNull 149 | public ClickstreamUserAttribute build() { 150 | return new ClickstreamUserAttribute(this); 151 | } 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /clickstream/src/main/java/software/aws/solution/clickstream/client/ClickstreamContext.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). 5 | * You may not use this file except in compliance with the License. 6 | * A copy of the License is located at 7 | * 8 | * http://aws.amazon.com/apache2.0 9 | * 10 | * or in the "license" file accompanying this file. This file is distributed 11 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 12 | * express or implied. See the License for the specific language governing 13 | * permissions and limitations under the License. 14 | */ 15 | 16 | package software.aws.solution.clickstream.client; 17 | 18 | import android.content.Context; 19 | 20 | import software.aws.solution.clickstream.ClickstreamConfiguration; 21 | import software.aws.solution.clickstream.client.system.AndroidSystem; 22 | import software.aws.solution.clickstream.client.uniqueid.SharedPrefsDeviceIdService; 23 | 24 | import java.io.Serializable; 25 | 26 | /** 27 | * Clickstream Context. 28 | */ 29 | @SuppressWarnings("serial") 30 | public class ClickstreamContext implements Serializable { 31 | /** 32 | * The configuration of Clickstream. 33 | */ 34 | private final ClickstreamConfiguration clickstreamConfiguration; 35 | /** 36 | * The info of SDK. 37 | */ 38 | private final SDKInfo sdkInfo; 39 | /** 40 | * The context of application. 41 | */ 42 | private final Context applicationContext; 43 | /** 44 | * The instance of AnalyticsClient. 45 | */ 46 | private AnalyticsClient analyticsClient; 47 | /** 48 | * The instance of SessionClient. 49 | */ 50 | private SessionClient sessionClient; 51 | /** 52 | * The system of Android. 53 | */ 54 | private final AndroidSystem system; 55 | /** 56 | * The device unique ID. 57 | */ 58 | private final String deviceId; 59 | 60 | /** 61 | * The constructor with parameters. 62 | * 63 | * @param applicationContext The context of Android. 64 | * @param sdkInfo The SDK info. 65 | * @param clickstreamConfiguration The configuration of Clickstream. 66 | */ 67 | public ClickstreamContext(final Context applicationContext, 68 | final SDKInfo sdkInfo, 69 | final ClickstreamConfiguration clickstreamConfiguration) { 70 | this.sdkInfo = sdkInfo; 71 | this.clickstreamConfiguration = clickstreamConfiguration; 72 | this.applicationContext = applicationContext; 73 | this.system = new AndroidSystem(applicationContext); 74 | this.deviceId = new SharedPrefsDeviceIdService().getDeviceId(this); 75 | } 76 | 77 | /** 78 | * Get the AnalyticsClient. 79 | * 80 | * @return The instance of AnalyticsClient. 81 | */ 82 | public AnalyticsClient getAnalyticsClient() { 83 | return analyticsClient; 84 | } 85 | 86 | /** 87 | * Set the AnalyticsClient. 88 | * 89 | * @param analyticsClient The instance of AnalyticsClient. 90 | */ 91 | public void setAnalyticsClient(AnalyticsClient analyticsClient) { 92 | this.analyticsClient = analyticsClient; 93 | } 94 | 95 | /** 96 | * Get the SessionClient. 97 | * 98 | * @return The instance of SessionClient. 99 | */ 100 | public SessionClient getSessionClient() { 101 | return sessionClient; 102 | } 103 | 104 | /** 105 | * Set the SessionClient. 106 | * 107 | * @param sessionClient The instance of SessionClient. 108 | */ 109 | public void setSessionClient(SessionClient sessionClient) { 110 | this.sessionClient = sessionClient; 111 | } 112 | 113 | /** 114 | * Get the configuration of Clickstream. 115 | * 116 | * @return The configuration of Clickstream. 117 | */ 118 | public ClickstreamConfiguration getClickstreamConfiguration() { 119 | return clickstreamConfiguration; 120 | } 121 | 122 | /** 123 | * Get the info of SDK. 124 | * 125 | * @return The info of SDK. 126 | */ 127 | public SDKInfo getSDKInfo() { 128 | return sdkInfo; 129 | } 130 | 131 | /** 132 | * Get the context of application. 133 | * 134 | * @return The context of application. 135 | */ 136 | public Context getApplicationContext() { 137 | return applicationContext; 138 | } 139 | 140 | /** 141 | * Get the system of Android. 142 | * 143 | * @return The system of Android. 144 | */ 145 | public AndroidSystem getSystem() { 146 | return system; 147 | } 148 | 149 | /** 150 | * Get the unique ID. 151 | * 152 | * @return The unique ID. 153 | */ 154 | public String getDeviceId() { 155 | return deviceId; 156 | } 157 | 158 | } 159 | 160 | -------------------------------------------------------------------------------- /clickstream/src/main/java/software/aws/solution/clickstream/client/ClickstreamExceptionHandler.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). 5 | * You may not use this file except in compliance with the License. 6 | * A copy of the License is located at 7 | * 8 | * http://aws.amazon.com/apache2.0 9 | * 10 | * or in the "license" file accompanying this file. This file is distributed 11 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 12 | * express or implied. See the License for the specific language governing 13 | * permissions and limitations under the License. 14 | */ 15 | 16 | package software.aws.solution.clickstream.client; 17 | 18 | import androidx.annotation.NonNull; 19 | 20 | import com.amazonaws.logging.Log; 21 | import com.amazonaws.logging.LogFactory; 22 | 23 | import java.io.PrintWriter; 24 | import java.io.StringWriter; 25 | import java.io.Writer; 26 | 27 | /** 28 | * Exception handler for record app exception event. 29 | */ 30 | public final class ClickstreamExceptionHandler implements Thread.UncaughtExceptionHandler { 31 | private static final Log LOG = LogFactory.getLog(ClickstreamExceptionHandler.class); 32 | private static ClickstreamExceptionHandler handlerInstance; 33 | private static final int SLEEP_TIMEOUT_MS = 500; 34 | private Thread.UncaughtExceptionHandler defaultExceptionHandler; 35 | private final ClickstreamContext clickstreamContext; 36 | 37 | private ClickstreamExceptionHandler(ClickstreamContext context) { 38 | this.clickstreamContext = context; 39 | } 40 | 41 | /** 42 | * start listening the exception events. 43 | */ 44 | public void startTrackException() { 45 | defaultExceptionHandler = Thread.getDefaultUncaughtExceptionHandler(); 46 | Thread.setDefaultUncaughtExceptionHandler(this); 47 | } 48 | 49 | /** 50 | * stop listening the exception events. 51 | */ 52 | public void stopTackException() { 53 | Thread.setDefaultUncaughtExceptionHandler(null); 54 | } 55 | 56 | /** 57 | * init static method for ClickstreamExceptionHandler. 58 | * 59 | * @param context the clickstream context for initial the ClickstreamExceptionHandler 60 | * @return ClickstreamExceptionHandler the instance. 61 | */ 62 | public static synchronized ClickstreamExceptionHandler init(ClickstreamContext context) { 63 | if (handlerInstance == null) { 64 | handlerInstance = new ClickstreamExceptionHandler(context); 65 | } 66 | return handlerInstance; 67 | } 68 | 69 | /** 70 | * fetch uncaught exception and record crash event. 71 | * 72 | * @param thread the thread 73 | * @param throwable the exception 74 | */ 75 | @Override 76 | public void uncaughtException(@NonNull Thread thread, @NonNull Throwable throwable) { 77 | try { 78 | Boolean isTrackAppExceptionEvents = clickstreamContext 79 | .getClickstreamConfiguration().isTrackAppExceptionEvents(); 80 | if (Boolean.TRUE.equals(isTrackAppExceptionEvents)) { 81 | trackExceptionEvent(throwable); 82 | } 83 | 84 | this.clickstreamContext.getAnalyticsClient().submitEvents(); 85 | 86 | sleepWithInterruptHandling(); 87 | 88 | if (defaultExceptionHandler != null) { 89 | defaultExceptionHandler.uncaughtException(thread, throwable); 90 | } else { 91 | killProcessAndExit(); 92 | } 93 | } catch (Exception exception) { 94 | LOG.error("uncaughtException:", exception); 95 | Thread.currentThread().interrupt(); 96 | } 97 | } 98 | 99 | /** 100 | * Tracks application exceptions by creating and recording an analytics event with exception details. 101 | * 102 | * @param throwable The throwable object containing exception details to be tracked 103 | */ 104 | private void trackExceptionEvent(Throwable throwable) { 105 | String exceptionMessage = ""; 106 | String exceptionStack = ""; 107 | try { 108 | if (throwable.getMessage() != null) { 109 | exceptionMessage = throwable.getMessage(); 110 | } 111 | final Writer writer = new StringWriter(); 112 | PrintWriter printWriter = new PrintWriter(writer); 113 | throwable.printStackTrace(printWriter); 114 | Throwable cause = throwable.getCause(); 115 | while (cause != null) { 116 | cause.printStackTrace(printWriter); 117 | cause = cause.getCause(); 118 | } 119 | 120 | printWriter.close(); 121 | exceptionStack = writer.toString(); 122 | } catch (Exception exception) { 123 | LOG.error("exception for get exception stack:", exception); 124 | } 125 | 126 | final AnalyticsEvent event = 127 | this.clickstreamContext.getAnalyticsClient().createEvent(Event.PresetEvent.APP_EXCEPTION); 128 | event.addInternalAttribute("exception_message", exceptionMessage); 129 | event.addInternalAttribute("exception_stack", exceptionStack); 130 | 131 | this.clickstreamContext.getAnalyticsClient().recordEvent(event); 132 | } 133 | 134 | /** 135 | * Pauses the current thread execution for a specified timeout period. 136 | * If interrupted while sleeping, logs the error and preserves the interrupt status. 137 | * 138 | * @throws RuntimeException if any other unexpected error occurs during sleep 139 | */ 140 | private void sleepWithInterruptHandling() { 141 | try { 142 | Thread.sleep(SLEEP_TIMEOUT_MS); 143 | } catch (InterruptedException exception) { 144 | LOG.error("interrupted exception for sleep:", exception); 145 | Thread.currentThread().interrupt(); 146 | } 147 | } 148 | 149 | /** 150 | * exit app. 151 | */ 152 | private void killProcessAndExit() { 153 | try { 154 | android.os.Process.killProcess(android.os.Process.myPid()); 155 | System.exit(1); 156 | } catch (Exception exception) { 157 | LOG.error("exit app exception:", exception); 158 | } 159 | } 160 | } 161 | -------------------------------------------------------------------------------- /clickstream/src/main/java/software/aws/solution/clickstream/client/ClickstreamManager.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). 5 | * You may not use this file except in compliance with the License. 6 | * A copy of the License is located at 7 | * 8 | * http://aws.amazon.com/apache2.0 9 | * 10 | * or in the "license" file accompanying this file. This file is distributed 11 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 12 | * express or implied. See the License for the specific language governing 13 | * permissions and limitations under the License. 14 | */ 15 | 16 | package software.aws.solution.clickstream.client; 17 | 18 | import android.content.Context; 19 | import androidx.annotation.NonNull; 20 | 21 | import com.amplifyframework.analytics.AnalyticsPropertyBehavior; 22 | 23 | import com.amazonaws.AmazonClientException; 24 | import com.amazonaws.logging.Log; 25 | import com.amazonaws.logging.LogFactory; 26 | import software.aws.solution.clickstream.AWSClickstreamPlugin; 27 | import software.aws.solution.clickstream.BuildConfig; 28 | import software.aws.solution.clickstream.ClickstreamConfiguration; 29 | 30 | import java.util.Locale; 31 | import java.util.Map; 32 | 33 | /** 34 | * Clickstream Manager. 35 | */ 36 | public class ClickstreamManager { 37 | // This value is decided by the Clickstream 38 | private static final String SDK_NAME = "aws-solution-clickstream-sdk"; 39 | private static final SDKInfo SDK_INFO = new SDKInfo(SDK_NAME, BuildConfig.VERSION_NAME); 40 | private static final Log LOG = LogFactory.getLog(AWSClickstreamPlugin.class); 41 | 42 | private final ClickstreamContext clickstreamContext; 43 | private final AnalyticsClient analyticsClient; 44 | private final SessionClient sessionClient; 45 | private final AutoRecordEventClient autoRecordEventClient; 46 | private ClickstreamExceptionHandler exceptionHandler; 47 | 48 | /** 49 | * Constructor. 50 | * 51 | * @param config {@link Context} object. 52 | * @param appContext {@link ClickstreamConfiguration} object. 53 | * @throws AmazonClientException When RuntimeException occur. 54 | */ 55 | public ClickstreamManager(@NonNull Context appContext, @NonNull final ClickstreamConfiguration config) { 56 | try { 57 | this.clickstreamContext = new ClickstreamContext(appContext, SDK_INFO, config); 58 | this.analyticsClient = new AnalyticsClient(this.clickstreamContext); 59 | this.clickstreamContext.setAnalyticsClient(this.analyticsClient); 60 | this.sessionClient = new SessionClient(this.clickstreamContext); 61 | this.autoRecordEventClient = new AutoRecordEventClient(this.clickstreamContext); 62 | this.clickstreamContext.setSessionClient(this.sessionClient); 63 | Boolean isTrackAppExceptionEvents = config.isTrackAppExceptionEvents(); 64 | if (Boolean.TRUE.equals(isTrackAppExceptionEvents)) { 65 | exceptionHandler = ClickstreamExceptionHandler.init(this.clickstreamContext); 66 | enableTrackAppException(); 67 | } 68 | setInitialGlobalAttributes(this.analyticsClient, config); 69 | LOG.debug(String.format(Locale.US, 70 | "Clickstream SDK(%s) initialization successfully completed", BuildConfig.VERSION_NAME)); 71 | this.autoRecordEventClient.handleAppStart(); 72 | handleSessionStart(); 73 | } catch (final RuntimeException runtimeException) { 74 | LOG.error(String.format(Locale.US, 75 | "Cannot initialize Clickstream SDK %s", runtimeException.getMessage())); 76 | throw new AmazonClientException(runtimeException.getLocalizedMessage()); 77 | } 78 | } 79 | 80 | private void setInitialGlobalAttributes(AnalyticsClient analyticsClient, ClickstreamConfiguration config) { 81 | if (config.getInitialGlobalAttributes() != null) { 82 | for (Map.Entry> entry : config.getInitialGlobalAttributes() 83 | .getAttributes()) { 84 | AnalyticsPropertyBehavior property = entry.getValue(); 85 | analyticsClient.addGlobalAttribute(entry.getKey(), property.getValue()); 86 | } 87 | config.withInitialGlobalAttributes(null); 88 | } 89 | } 90 | 91 | /** 92 | * handle session start after SDK initialize. 93 | */ 94 | private void handleSessionStart() { 95 | boolean isNewSession = sessionClient.initialSession(); 96 | if (isNewSession) { 97 | autoRecordEventClient.handleSessionStart(); 98 | autoRecordEventClient.setIsEntrances(); 99 | sessionClient.startSession(); 100 | } 101 | } 102 | 103 | /** 104 | * Enable track app exception. 105 | */ 106 | public void enableTrackAppException() { 107 | if (exceptionHandler != null) { 108 | exceptionHandler.startTrackException(); 109 | } 110 | } 111 | 112 | /** 113 | * Disable track app exception. 114 | */ 115 | public void disableTrackAppException() { 116 | if (exceptionHandler != null) { 117 | exceptionHandler.stopTackException(); 118 | } 119 | } 120 | 121 | /** 122 | * Get the Clickstream context. 123 | * 124 | * @return The Clickstream context. 125 | */ 126 | public ClickstreamContext getClickstreamContext() { 127 | return clickstreamContext; 128 | } 129 | 130 | /** 131 | * The {@link AnalyticsClient} is the primary class used to create, store, and 132 | * submit events from your application. 133 | * 134 | * @return an {@link AnalyticsClient} 135 | */ 136 | public AnalyticsClient getAnalyticsClient() { 137 | return analyticsClient; 138 | } 139 | 140 | /** 141 | * The {@link SessionClient} is the primary class used to create, store session from your application. 142 | * 143 | * @return a {@link SessionClient} 144 | */ 145 | public SessionClient getSessionClient() { 146 | return sessionClient; 147 | } 148 | 149 | /** 150 | * The {@link AutoRecordEventClient} is aim to auto record lifecycle relates event. 151 | * 152 | * @return a {@link AutoRecordEventClient} 153 | */ 154 | public AutoRecordEventClient getAutoRecordEventClient() { 155 | return autoRecordEventClient; 156 | } 157 | } 158 | 159 | -------------------------------------------------------------------------------- /clickstream/src/main/java/software/aws/solution/clickstream/client/SDKInfo.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). 5 | * You may not use this file except in compliance with the License. 6 | * A copy of the License is located at 7 | * 8 | * http://aws.amazon.com/apache2.0 9 | * 10 | * or in the "license" file accompanying this file. This file is distributed 11 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 12 | * express or implied. See the License for the specific language governing 13 | * permissions and limitations under the License. 14 | */ 15 | 16 | package software.aws.solution.clickstream.client; 17 | 18 | import java.io.Serializable; 19 | 20 | /** 21 | * Entity for SDKInfo. 22 | */ 23 | public class SDKInfo implements Serializable { 24 | private final String name; 25 | private final String version; 26 | 27 | /** 28 | * The constructor of SDKInfo. 29 | * 30 | * @param name The name of SDKInfo. 31 | * @param version The version of SDKInfo. 32 | */ 33 | public SDKInfo(final String name, final String version) { 34 | this.name = name; 35 | this.version = version; 36 | } 37 | 38 | /** 39 | * Get the name of SDK. 40 | * 41 | * @return The name of SDK. 42 | */ 43 | public String getName() { 44 | return name; 45 | } 46 | 47 | /** 48 | * Get the version of SDK. 49 | * 50 | * @return The version of SDK. 51 | */ 52 | public String getVersion() { 53 | return version; 54 | } 55 | } 56 | 57 | -------------------------------------------------------------------------------- /clickstream/src/main/java/software/aws/solution/clickstream/client/ScreenRefererTool.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). 5 | * You may not use this file except in compliance with the License. 6 | * A copy of the License is located at 7 | * 8 | * http://aws.amazon.com/apache2.0 9 | * 10 | * or in the "license" file accompanying this file. This file is distributed 11 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 12 | * express or implied. See the License for the specific language governing 13 | * permissions and limitations under the License. 14 | */ 15 | 16 | package software.aws.solution.clickstream.client; 17 | 18 | /** 19 | * ScreenRefererTool for save previous screen info. 20 | */ 21 | public final class ScreenRefererTool { 22 | 23 | private static String mPreviousScreenId; 24 | private static String mCurrentScreenId; 25 | private static String mPreviousScreenName; 26 | private static String mCurrentScreenName; 27 | private static String mPreviousScreenUniqueId; 28 | private static String mCurrentScreenUniqueId; 29 | 30 | private ScreenRefererTool() { 31 | } 32 | 33 | /** 34 | * set current screen name. 35 | * 36 | * @param screenName current screen name to set. 37 | */ 38 | public static void setCurrentScreenName(String screenName) { 39 | mPreviousScreenName = mCurrentScreenName; 40 | mCurrentScreenName = screenName; 41 | } 42 | 43 | /** 44 | * set current screen id. 45 | * 46 | * @param screenId current screen id to set. 47 | */ 48 | public static void setCurrentScreenId(String screenId) { 49 | mPreviousScreenId = mCurrentScreenId; 50 | mCurrentScreenId = screenId; 51 | } 52 | 53 | /** 54 | * set current screen unique id. 55 | * 56 | * @param screenUniqueId current screen unique id to set. 57 | */ 58 | public static void setCurrentScreenUniqueId(String screenUniqueId) { 59 | mPreviousScreenUniqueId = mCurrentScreenUniqueId; 60 | mCurrentScreenUniqueId = screenUniqueId; 61 | } 62 | 63 | /** 64 | * get current ScreenName. 65 | * 66 | * @return mCurrentScreenName 67 | */ 68 | public static String getCurrentScreenName() { 69 | return mCurrentScreenName; 70 | } 71 | 72 | /** 73 | * get current screenId. 74 | * 75 | * @return mCurrentScreenId 76 | */ 77 | public static String getCurrentScreenId() { 78 | return mCurrentScreenId; 79 | } 80 | 81 | /** 82 | * get current screen unique id. 83 | * 84 | * @return mCurrentScreenUniqueId 85 | */ 86 | public static String getCurrentScreenUniqueId() { 87 | return mCurrentScreenUniqueId; 88 | } 89 | 90 | /** 91 | * get previous ScreenName. 92 | * 93 | * @return mPreviousScreenName 94 | */ 95 | public static String getPreviousScreenName() { 96 | return mPreviousScreenName; 97 | } 98 | 99 | /** 100 | * get previous screenId. 101 | * 102 | * @return mPreviousScreenId 103 | */ 104 | public static String getPreviousScreenId() { 105 | return mPreviousScreenId; 106 | } 107 | 108 | /** 109 | * get previous screen unique id. 110 | * 111 | * @return mPreviousScreenUniqueId 112 | */ 113 | public static String getPreviousScreenUniqueId() { 114 | return mPreviousScreenUniqueId; 115 | } 116 | 117 | /** 118 | * Judging that the current screen is the same as the previous screen. 119 | * 120 | * @param screenName current screen name 121 | * @param screenUniqueId current screen unique id 122 | * @return the boolean value for is the same screen 123 | */ 124 | public static boolean isSameScreen(String screenName, String screenUniqueId) { 125 | return mCurrentScreenName != null && 126 | mCurrentScreenName.equals(screenName) && 127 | (mCurrentScreenUniqueId == null || mCurrentScreenUniqueId.equals(screenUniqueId)); 128 | } 129 | 130 | /** 131 | * method for clear cached screen information. 132 | */ 133 | public static void clear() { 134 | mCurrentScreenId = null; 135 | mCurrentScreenName = null; 136 | mCurrentScreenUniqueId = null; 137 | mPreviousScreenId = null; 138 | mPreviousScreenName = null; 139 | mPreviousScreenUniqueId = null; 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /clickstream/src/main/java/software/aws/solution/clickstream/client/Session.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). 5 | * You may not use this file except in compliance with the License. 6 | * A copy of the License is located at 7 | * 8 | * http://aws.amazon.com/apache2.0 9 | * 10 | * or in the "license" file accompanying this file. This file is distributed 11 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 12 | * express or implied. See the License for the specific language governing 13 | * permissions and limitations under the License. 14 | */ 15 | 16 | package software.aws.solution.clickstream.client; 17 | 18 | import androidx.annotation.NonNull; 19 | 20 | import software.aws.solution.clickstream.client.util.PreferencesUtil; 21 | import software.aws.solution.clickstream.client.util.StringUtil; 22 | 23 | import java.io.Serializable; 24 | import java.text.DateFormat; 25 | import java.text.SimpleDateFormat; 26 | import java.util.Locale; 27 | import java.util.TimeZone; 28 | 29 | /** 30 | * A Session Object Mostly immutable, with the exception of its stop time. This 31 | * session object tracks whether or not it has been paused by checking the 32 | * status of it's stop time. A session's stop time is only set when the session 33 | * has been paused. 34 | */ 35 | public final class Session implements Serializable { 36 | /** 37 | * The session ID date format. 38 | */ 39 | private static final String SESSION_ID_DATE_FORMAT = "yyyyMMdd"; 40 | /** 41 | * The session ID time format. 42 | */ 43 | private static final String SESSION_ID_TIME_FORMAT = "HHmmssSSS"; 44 | /** 45 | * The session ID delimiter. 46 | */ 47 | private static final char SESSION_ID_DELIMITER = '-'; 48 | /** 49 | * The session ID pad char. 50 | */ 51 | private static final char SESSION_ID_PAD_CHAR = '_'; 52 | /** 53 | * The session ID unique ID length. 54 | */ 55 | private static final int SESSION_ID_UNIQUE_ID_LENGTH = 8; 56 | private final DateFormat sessionIdTimeFormat; 57 | private final String sessionID; 58 | private final Long startTime; 59 | private Long pauseTime; 60 | private final int sessionIndex; 61 | 62 | private boolean isStarted; 63 | 64 | /** 65 | * CONSTRUCTOR - ACTUAL Used by SessionClient. 66 | * 67 | * @param context The context of ClickstreamContext. 68 | * @throws NullPointerException When the context is null. 69 | */ 70 | Session(@NonNull final ClickstreamContext context, final int sessionIndex) { 71 | this.sessionIdTimeFormat = new SimpleDateFormat(SESSION_ID_DATE_FORMAT + 72 | SESSION_ID_DELIMITER + SESSION_ID_TIME_FORMAT, Locale.US); 73 | this.sessionIdTimeFormat.setTimeZone(TimeZone.getTimeZone("UTC")); 74 | this.startTime = System.currentTimeMillis(); 75 | this.pauseTime = null; 76 | this.sessionID = this.generateSessionID(context); 77 | this.sessionIndex = sessionIndex; 78 | } 79 | 80 | /** 81 | * Used by deserializer. 82 | * 83 | * @param sessionID The session ID. 84 | * @param startTime The start session timestamp. 85 | * @param pauseTime The stop session timestamp. 86 | * @param sessionIndex the index of session. 87 | */ 88 | public Session(final String sessionID, final Long startTime, final Long pauseTime, final int sessionIndex) { 89 | this.sessionIdTimeFormat = new SimpleDateFormat(SESSION_ID_DATE_FORMAT + 90 | SESSION_ID_DELIMITER + SESSION_ID_TIME_FORMAT, Locale.US); 91 | this.sessionIdTimeFormat.setTimeZone(TimeZone.getTimeZone("UTC")); 92 | this.startTime = startTime; 93 | this.pauseTime = pauseTime; 94 | this.sessionID = sessionID; 95 | this.sessionIndex = sessionIndex; 96 | } 97 | 98 | /** 99 | * Get the session object from preferences, if exists and not expired return it. 100 | * otherwise create a new session. 101 | * 102 | * @param context The {@link ClickstreamContext}. 103 | * @param previousSession The previous session for check whether is new session. 104 | * @return new Session object. 105 | */ 106 | public static Session getInstance(final ClickstreamContext context, Session previousSession) { 107 | Session session = previousSession; 108 | if (session == null) { 109 | session = PreferencesUtil.getSession(context.getSystem().getPreferences()); 110 | } 111 | if (session != null) { 112 | if (session.getPauseTime() == null || 113 | System.currentTimeMillis() - session.getPauseTime() < 114 | context.getClickstreamConfiguration().getSessionTimeoutDuration()) { 115 | return session; 116 | } else { 117 | return new Session(context, session.getSessionIndex() + 1); 118 | } 119 | } else { 120 | return new Session(context, 1); 121 | } 122 | } 123 | 124 | /** 125 | * function for get session whether is started. 126 | * 127 | * @return session whether is started. 128 | */ 129 | public boolean isStarted() { 130 | return this.isStarted; 131 | } 132 | 133 | /** 134 | * function for flag the session is started. 135 | */ 136 | public void start() { 137 | this.isStarted = true; 138 | } 139 | 140 | /** 141 | * Pauses the session object. Generates a stop time. 142 | */ 143 | public void pause() { 144 | this.pauseTime = System.currentTimeMillis(); 145 | } 146 | 147 | /** 148 | * function for is new session. 149 | * 150 | * @return is new session 151 | */ 152 | public boolean isNewSession() { 153 | return this.pauseTime == null; 154 | } 155 | 156 | /** 157 | * Calculates and returns the session's duration Returns a duration of 0 if 158 | * session is not paused or the system clock has been tampered with. 159 | * 160 | * @return session duration in milliseconds. 161 | */ 162 | public Long getSessionDuration() { 163 | Long time = this.pauseTime; 164 | if (time == null) { 165 | time = System.currentTimeMillis(); 166 | } 167 | 168 | if (time < this.startTime) { 169 | return 0L; 170 | } 171 | return time - this.startTime; 172 | } 173 | 174 | /** 175 | * Generates Session ID by concatenating present AppKey, UniqueID, and 176 | * AppKey-UniqueID-yyyyMMdd-HHmmssSSS. 177 | * 178 | * @param context The {@link ClickstreamContext}. 179 | * @return [String] SessionID. 180 | */ 181 | public String generateSessionID(final ClickstreamContext context) { 182 | final String uniqueId = context.getDeviceId(); 183 | final String time = this.sessionIdTimeFormat.format(this.startTime); 184 | return StringUtil.trimOrPadString(uniqueId, SESSION_ID_UNIQUE_ID_LENGTH, SESSION_ID_PAD_CHAR) + 185 | SESSION_ID_DELIMITER + time; 186 | } 187 | 188 | /** 189 | * Get session ID. 190 | * 191 | * @return The string of session ID. 192 | */ 193 | public String getSessionID() { 194 | return this.sessionID; 195 | } 196 | 197 | /** 198 | * Get the start session timestamp. 199 | * 200 | * @return The long value of start session timestamp. 201 | */ 202 | public long getStartTime() { 203 | return this.startTime; 204 | } 205 | 206 | /** 207 | * Get the stop session timestamp. 208 | * 209 | * @return The long value of stop session timestamp. 210 | */ 211 | public Long getPauseTime() { 212 | return this.pauseTime; 213 | } 214 | 215 | /** 216 | * Get session index. 217 | * 218 | * @return The value of session index. 219 | */ 220 | public int getSessionIndex() { 221 | return this.sessionIndex; 222 | } 223 | } 224 | 225 | -------------------------------------------------------------------------------- /clickstream/src/main/java/software/aws/solution/clickstream/client/SessionClient.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). 5 | * You may not use this file except in compliance with the License. 6 | * A copy of the License is located at 7 | * 8 | * http://aws.amazon.com/apache2.0 9 | * 10 | * or in the "license" file accompanying this file. This file is distributed 11 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 12 | * express or implied. See the License for the specific language governing 13 | * permissions and limitations under the License. 14 | */ 15 | 16 | package software.aws.solution.clickstream.client; 17 | 18 | import androidx.annotation.NonNull; 19 | 20 | import software.aws.solution.clickstream.client.util.PreferencesUtil; 21 | 22 | import java.io.Serializable; 23 | 24 | /** 25 | * Client for managing start and pause session. 26 | */ 27 | public class SessionClient implements Serializable { 28 | 29 | /** 30 | * The context object wraps all the essential information from the app 31 | * that are required. 32 | */ 33 | private final ClickstreamContext clickstreamContext; 34 | 35 | /** 36 | * session object. 37 | */ 38 | private Session session; 39 | 40 | /** 41 | * constructor for session client. 42 | * 43 | * @param clickstreamContext The {@link ClickstreamContext}. 44 | * @throws IllegalArgumentException When the clickstreamContext.getAnalyticsClient is null. 45 | */ 46 | public SessionClient(@NonNull final ClickstreamContext clickstreamContext) { 47 | if (clickstreamContext.getAnalyticsClient() == null) { 48 | throw new IllegalArgumentException("A valid AnalyticsClient must be provided!"); 49 | } 50 | this.clickstreamContext = clickstreamContext; 51 | session = Session.getInstance(clickstreamContext, null); 52 | this.clickstreamContext.getAnalyticsClient().setSession(session); 53 | } 54 | 55 | /** 56 | * When the app starts for the first time. Or the app was launched to the foreground 57 | * and the time between the last exit exceeded `session_time_out` period, 58 | * then the session start event will be recorded. 59 | * 60 | * @return is new session. 61 | */ 62 | public synchronized boolean initialSession() { 63 | session = Session.getInstance(clickstreamContext, session); 64 | this.clickstreamContext.getAnalyticsClient().setSession(session); 65 | return session.isNewSession() && !session.isStarted(); 66 | } 67 | 68 | /** 69 | * method for start the session. 70 | */ 71 | public void startSession() { 72 | session.start(); 73 | } 74 | 75 | /** 76 | * store a session when the application goes to the background. 77 | */ 78 | public void storeSession() { 79 | session.pause(); 80 | PreferencesUtil.saveSession(clickstreamContext.getSystem().getPreferences(), session); 81 | } 82 | 83 | } 84 | 85 | -------------------------------------------------------------------------------- /clickstream/src/main/java/software/aws/solution/clickstream/client/db/ClickstreamDBUtil.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). 5 | * You may not use this file except in compliance with the License. 6 | * A copy of the License is located at 7 | * 8 | * http://aws.amazon.com/apache2.0 9 | * 10 | * or in the "license" file accompanying this file. This file is distributed 11 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 12 | * express or implied. See the License for the specific language governing 13 | * permissions and limitations under the License. 14 | */ 15 | 16 | package software.aws.solution.clickstream.client.db; 17 | 18 | import android.content.ContentValues; 19 | import android.content.Context; 20 | import android.database.Cursor; 21 | import android.database.SQLException; 22 | import android.net.Uri; 23 | 24 | import com.amazonaws.logging.Log; 25 | import com.amazonaws.logging.LogFactory; 26 | import software.aws.solution.clickstream.client.AnalyticsEvent; 27 | import software.aws.solution.clickstream.client.EventRecorder; 28 | 29 | import java.io.Serializable; 30 | 31 | /** 32 | * Clickstream Database Util. 33 | */ 34 | public class ClickstreamDBUtil implements Serializable { 35 | private static final Log LOG = LogFactory.getLog(EventRecorder.class); 36 | 37 | /** 38 | * ClickstreamDBBase is a basic helper for accessing the database. 39 | */ 40 | private ClickstreamDBBase clickstreamDBBase; 41 | 42 | /** 43 | * Constructs a ClickstreamDBUtil with the given Context. 44 | * 45 | * @param context An instance of Context. 46 | */ 47 | public ClickstreamDBUtil(final Context context) { 48 | if (clickstreamDBBase == null) { 49 | clickstreamDBBase = new ClickstreamDBBase(context); 50 | } 51 | } 52 | 53 | /** 54 | * Closes the DB Connection. 55 | */ 56 | public void closeDB() { 57 | if (clickstreamDBBase != null) { 58 | clickstreamDBBase.closeDBHelper(); 59 | } 60 | } 61 | 62 | /** 63 | * Saves an event into the database. 64 | * 65 | * @param event The AnalyticsEvent to be saved. 66 | * @return An Uri of the record inserted. 67 | */ 68 | public Uri saveEvent(final AnalyticsEvent event) { 69 | Uri uri = null; 70 | try { 71 | uri = clickstreamDBBase.insert(clickstreamDBBase.getContentUri(), generateContentValuesFromEvent(event)); 72 | } catch (SQLException error) { 73 | LOG.warn("SQLException: " + error.getMessage()); 74 | } 75 | return uri; 76 | } 77 | 78 | private ContentValues generateContentValuesFromEvent(final AnalyticsEvent event) { 79 | ContentValues values = new ContentValues(); 80 | String json = event.toJSONObject().toString(); 81 | values.put(EventTable.COLUMN_JSON, json); 82 | values.put(EventTable.COLUMN_SIZE, json.length()); 83 | return values; 84 | } 85 | 86 | /** 87 | * Queries all the events. 88 | * 89 | * @return A Cursor pointing to records in the database. 90 | */ 91 | public Cursor queryAllEvents() { 92 | return clickstreamDBBase.query(clickstreamDBBase.getContentUri(), 93 | null, null, null, null, null); 94 | } 95 | 96 | /** 97 | * Queries all events from oldest. Does not include JSON. 98 | * 99 | * @param limit The limit of result set. 100 | * @return A Cursor pointing to records in the database. 101 | */ 102 | public Cursor queryOldestEvents(final int limit) { 103 | return clickstreamDBBase.query(clickstreamDBBase.getContentUri(), 104 | new String[] {EventTable.COLUMN_ID, EventTable.COLUMN_SIZE}, 105 | null, null, null, 106 | Integer.toString(limit)); 107 | } 108 | 109 | /** 110 | * Deletes the event with the given eventId. 111 | * 112 | * @param eventId The eventId of the event to be deleted. 113 | * @return Number of rows deleted. 114 | */ 115 | public int deleteEvent(final int eventId) { 116 | return clickstreamDBBase.delete(getEventUri(eventId), null, 117 | null); 118 | } 119 | 120 | /** 121 | * Deletes all the event where eventId is not larger than lastEventId. 122 | * 123 | * @param lastEventId The last eventId. 124 | * @return Number of rows deleted. 125 | */ 126 | public int deleteBatchEvents(final int lastEventId) { 127 | return clickstreamDBBase.delete(getLastEventIdUri(lastEventId), null, 128 | null); 129 | } 130 | 131 | /** 132 | * Gets the Uri of an event. 133 | * 134 | * @param eventId The id of the event. 135 | * @return The Uri of the event specified by the id. 136 | */ 137 | public Uri getEventUri(final int eventId) { 138 | return Uri.parse(clickstreamDBBase.getContentUri() + "/" + eventId); 139 | } 140 | 141 | /** 142 | * Gets the Uri of an event. 143 | * 144 | * @param lastEventId The id of the event which is the last. 145 | * @return The Uri of the event specified by the id. 146 | */ 147 | public Uri getLastEventIdUri(final int lastEventId) { 148 | return Uri.parse(clickstreamDBBase.getContentUri() + "/last-event-id/" + lastEventId); 149 | } 150 | 151 | /** 152 | * Get the total event size calculate by sum of all event string's length. 153 | * 154 | * @return The total size. 155 | */ 156 | public long getTotalSize() { 157 | return clickstreamDBBase.getTotalSize(); 158 | } 159 | 160 | /** 161 | * Get the total event number calculate by sum of all events. 162 | * 163 | * @return The total number. 164 | */ 165 | public long getTotalNumber() { 166 | return clickstreamDBBase.getTotalNumber(); 167 | } 168 | } 169 | 170 | -------------------------------------------------------------------------------- /clickstream/src/main/java/software/aws/solution/clickstream/client/db/ClickstreamDatabaseHelper.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). 5 | * You may not use this file except in compliance with the License. 6 | * A copy of the License is located at 7 | * 8 | * http://aws.amazon.com/apache2.0 9 | * 10 | * or in the "license" file accompanying this file. This file is distributed 11 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 12 | * express or implied. See the License for the specific language governing 13 | * permissions and limitations under the License. 14 | */ 15 | 16 | package software.aws.solution.clickstream.client.db; 17 | 18 | import android.content.Context; 19 | import android.database.sqlite.SQLiteDatabase; 20 | import android.database.sqlite.SQLiteOpenHelper; 21 | 22 | import java.io.Serializable; 23 | 24 | /** 25 | * Clickstream Database Helper. 26 | */ 27 | public class ClickstreamDatabaseHelper extends SQLiteOpenHelper implements Serializable { 28 | private static final String DATABASE_NAME = "clickstream.db"; 29 | private static final int DATABASE_VERSION = 1; 30 | 31 | private final int version; 32 | 33 | /** 34 | * The constructor with parameters. 35 | * @param context the context of Android. 36 | */ 37 | public ClickstreamDatabaseHelper(final Context context) { 38 | this(context, DATABASE_VERSION); 39 | } 40 | 41 | /** 42 | * The constructor with parameters. 43 | * @param context The context of Android. 44 | * @param version The version of SDK. 45 | */ 46 | public ClickstreamDatabaseHelper(final Context context, final int version) { 47 | super(context, DATABASE_NAME, null, version); 48 | this.version = version; 49 | } 50 | 51 | /** 52 | * Set the configuration of SQLite database. 53 | * @param database The instance of SQLite database. 54 | */ 55 | public void onConfigure(final SQLiteDatabase database) { 56 | database.execSQL("PRAGMA auto_vacuum = FULL"); 57 | } 58 | 59 | /** 60 | * Creates the database. 61 | * 62 | * @param database An SQLiteDatabase instance. 63 | */ 64 | @Override 65 | public void onCreate(final SQLiteDatabase database) { 66 | EventTable.onCreate(database, version); 67 | } 68 | 69 | /** 70 | * Upgrades the database. 71 | * 72 | * @param database An SQLiteDatabase instance. 73 | * @param oldVersion The old version of the database. 74 | * @param newVersion The new version of the database. 75 | */ 76 | @Override 77 | public void onUpgrade(final SQLiteDatabase database, final int oldVersion, final int newVersion) { 78 | EventTable.onUpgrade(database, oldVersion, newVersion); 79 | } 80 | } 81 | 82 | -------------------------------------------------------------------------------- /clickstream/src/main/java/software/aws/solution/clickstream/client/db/EventTable.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). 5 | * You may not use this file except in compliance with the License. 6 | * A copy of the License is located at 7 | * 8 | * http://aws.amazon.com/apache2.0 9 | * 10 | * or in the "license" file accompanying this file. This file is distributed 11 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 12 | * express or implied. See the License for the specific language governing 13 | * permissions and limitations under the License. 14 | */ 15 | 16 | package software.aws.solution.clickstream.client.db; 17 | 18 | import android.database.sqlite.SQLiteDatabase; 19 | 20 | /** 21 | * Database Table for Event. 22 | */ 23 | public final class EventTable { 24 | /** 25 | * Database table name. 26 | */ 27 | public static final String TABLE_EVENT = "clickstreamevent"; 28 | /** 29 | * A unique id of the clickstream event. 30 | */ 31 | public static final String COLUMN_ID = "event_id"; 32 | /** 33 | * The JSON body of the clickstream event. 34 | */ 35 | public static final String COLUMN_JSON = "event_json"; 36 | /** 37 | * The size of JSON body of the clickstream event. 38 | */ 39 | public static final String COLUMN_SIZE = "event_size"; 40 | /** 41 | * Database creation SQL statement. 42 | */ 43 | private static final String DATABASE_CREATE = "create table if not exists " + TABLE_EVENT + 44 | "(" + COLUMN_ID + " integer primary key autoincrement, " 45 | + COLUMN_SIZE + " INTEGER NOT NULL," 46 | + COLUMN_JSON + " TEXT NOT NULL" + ");"; 47 | 48 | /** 49 | * The default constructor. 50 | */ 51 | private EventTable() { 52 | } 53 | 54 | /** 55 | * Creates the database. 56 | * 57 | * @param database An SQLiteDatabase instance. 58 | * @param version The version of database. 59 | */ 60 | public static void onCreate(final SQLiteDatabase database, final int version) { 61 | database.execSQL(DATABASE_CREATE); 62 | onUpgrade(database, 1, version); 63 | } 64 | 65 | /** 66 | * Upgrades the database. 67 | * 68 | * @param database An SQLiteDatabase instance. 69 | * @param oldVersion The old version of the database. 70 | * @param newVersion The new version of the database. 71 | */ 72 | public static void onUpgrade(final SQLiteDatabase database, final int oldVersion, final int newVersion) { //NOSONAR 73 | // do nothing 74 | } 75 | 76 | /** 77 | * Column Index Structure. 78 | */ 79 | public enum ColumnIndex { 80 | /** 81 | * The ID of the column. 82 | */ 83 | ID(0), 84 | /** 85 | * The size of the column. 86 | */ 87 | SIZE(1), 88 | /** 89 | * The JSON body of the column. 90 | */ 91 | JSON(2); 92 | 93 | private final int value; 94 | 95 | /** 96 | * The constructor. 97 | * 98 | * @param value The value. 99 | */ 100 | ColumnIndex(final int value) { 101 | this.value = value; 102 | } 103 | 104 | /** 105 | * Get the value. 106 | * 107 | * @return The value. 108 | */ 109 | public int getValue() { 110 | return value; 111 | } 112 | } 113 | } 114 | 115 | -------------------------------------------------------------------------------- /clickstream/src/main/java/software/aws/solution/clickstream/client/network/NetRequest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). 5 | * You may not use this file except in compliance with the License. 6 | * A copy of the License is located at 7 | * 8 | * http://aws.amazon.com/apache2.0 9 | * 10 | * or in the "license" file accompanying this file. This file is distributed 11 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 12 | * express or implied. See the License for the specific language governing 13 | * permissions and limitations under the License. 14 | */ 15 | 16 | package software.aws.solution.clickstream.client.network; 17 | 18 | import androidx.annotation.NonNull; 19 | 20 | import com.amplifyframework.util.UserAgent; 21 | 22 | import com.amazonaws.logging.Log; 23 | import com.amazonaws.logging.LogFactory; 24 | import software.aws.solution.clickstream.ClickstreamConfiguration; 25 | import software.aws.solution.clickstream.client.util.StringUtil; 26 | 27 | import java.io.IOException; 28 | import java.util.Locale; 29 | import java.util.concurrent.TimeUnit; 30 | 31 | import okhttp3.HttpUrl; 32 | import okhttp3.MediaType; 33 | import okhttp3.OkHttpClient; 34 | import okhttp3.Request; 35 | import okhttp3.RequestBody; 36 | import okhttp3.Response; 37 | import okhttp3.ResponseBody; 38 | 39 | /** 40 | * send net request. 41 | */ 42 | public final class NetRequest { 43 | 44 | private static final Log LOG = LogFactory.getLog(NetRequest.class); 45 | private static final long HTTP_CONNECT_TIME_OUT = 10; 46 | private static final long HTTP_READ_TIME_OUT = 10; 47 | private static final long HTTP_WRITE_TIME_OUT = 10; 48 | 49 | /** 50 | * Default constructor. 51 | */ 52 | private NetRequest() { 53 | } 54 | 55 | /** 56 | * upload batch of recorded events to server. 57 | * 58 | * @param eventJson event json string 59 | * @param configuration the ClickstreamConfiguration. 60 | * @param bundleSequenceId the bundle sequence id. 61 | * @return submit result. 62 | */ 63 | public static boolean uploadEvents(String eventJson, ClickstreamConfiguration configuration, int bundleSequenceId) { 64 | if (StringUtil.isNullOrEmpty(eventJson)) { 65 | return false; 66 | } 67 | try (Response response = request(eventJson, configuration, bundleSequenceId); 68 | ResponseBody ignored = response.body()) { 69 | if (response.isSuccessful()) { 70 | LOG.debug("submitEvents success. \n" + response); 71 | return true; 72 | } else { 73 | LOG.error("submitEvents fail. \n" + response); 74 | } 75 | } catch (final Exception exception) { 76 | LOG.error("submitEvents error: " + exception.getMessage()); 77 | } 78 | return false; 79 | } 80 | 81 | /** 82 | * make request. 83 | * 84 | * @param eventJson events to send. 85 | * @param configuration ClickstreamConfiguration 86 | * @return the sync okhttp Response 87 | * @throws IOException throw IOException. 88 | */ 89 | private static Response request(@NonNull String eventJson, @NonNull ClickstreamConfiguration configuration, 90 | int bundleSequenceId) 91 | throws IOException { 92 | String appId = configuration.getAppId(); 93 | String endpoint = configuration.getEndpoint(); 94 | String curStr = eventJson; 95 | String compression = ""; 96 | if (Boolean.TRUE.equals(configuration.isCompressEvents())) { 97 | LOG.debug("submitEvents isCompressEvents true"); 98 | curStr = StringUtil.compressForGzip(eventJson); 99 | compression = "gzip"; 100 | } 101 | if (null == curStr) { 102 | LOG.debug("submitEvents isCompressEvents false"); 103 | curStr = eventJson; 104 | } 105 | 106 | RequestBody body = RequestBody.create(curStr, MediaType.parse("application/json; charset=utf-8")); 107 | Request request = new Request.Builder().url(endpoint).build(); 108 | HttpUrl url = request.url().newBuilder() 109 | .addQueryParameter("platform", "Android") 110 | .addQueryParameter("appId", appId) 111 | .addQueryParameter("hashCode", StringUtil.getHashCode(curStr)) 112 | .addQueryParameter("event_bundle_sequence_id", String.valueOf(bundleSequenceId)) 113 | .addQueryParameter("upload_timestamp", String.valueOf(System.currentTimeMillis())) 114 | .addQueryParameter("compression", compression) 115 | .build(); 116 | Request.Builder builder = request.newBuilder().url(url).post(body); 117 | if (!StringUtil.isNullOrEmpty(configuration.getAuthCookie())) { 118 | builder.addHeader("cookie", configuration.getAuthCookie()); 119 | } 120 | request = builder.build(); 121 | 122 | final OkHttpClient.Builder okHttpClientBuilder = new OkHttpClient.Builder(); 123 | okHttpClientBuilder.connectTimeout(HTTP_CONNECT_TIME_OUT, TimeUnit.SECONDS); 124 | okHttpClientBuilder.readTimeout(HTTP_READ_TIME_OUT, TimeUnit.SECONDS); 125 | okHttpClientBuilder.writeTimeout(HTTP_WRITE_TIME_OUT, TimeUnit.SECONDS); 126 | okHttpClientBuilder.callTimeout(configuration.getCallTimeOut(), TimeUnit.SECONDS); 127 | okHttpClientBuilder.retryOnConnectionFailure(true); 128 | okHttpClientBuilder.addNetworkInterceptor(UserAgentInterceptor.using(UserAgent::string)); 129 | if (configuration.getDns() != null) { 130 | okHttpClientBuilder.dns(configuration.getDns()); 131 | } 132 | 133 | OkHttpClient client = okHttpClientBuilder.build(); 134 | LOG.debug( 135 | String.format(Locale.US, "Current %d conn and %d idle conn", client.connectionPool().connectionCount(), 136 | client.connectionPool().idleConnectionCount())); 137 | // make the sync request. 138 | return client.newCall(request).execute(); 139 | } 140 | 141 | } 142 | -------------------------------------------------------------------------------- /clickstream/src/main/java/software/aws/solution/clickstream/client/network/NetUtil.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). 5 | * You may not use this file except in compliance with the License. 6 | * A copy of the License is located at 7 | * 8 | * http://aws.amazon.com/apache2.0 9 | * 10 | * or in the "license" file accompanying this file. This file is distributed 11 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 12 | * express or implied. See the License for the specific language governing 13 | * permissions and limitations under the License. 14 | */ 15 | 16 | package software.aws.solution.clickstream.client.network; 17 | 18 | import android.content.Context; 19 | import android.net.ConnectivityManager; 20 | import android.net.NetworkCapabilities; 21 | import android.net.NetworkInfo; 22 | 23 | /** 24 | * Net util. 25 | */ 26 | public final class NetUtil { 27 | 28 | /** 29 | * Default constructor. 30 | */ 31 | private NetUtil() { 32 | } 33 | 34 | /** 35 | * adjust is network available. 36 | * 37 | * @param context application context 38 | * @return isNetworkAvailable 39 | */ 40 | public static boolean isNetworkAvailable(Context context) { 41 | ConnectivityManager connectivityManager = 42 | (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); 43 | if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) { 44 | NetworkCapabilities capabilities = 45 | connectivityManager.getNetworkCapabilities(connectivityManager.getActiveNetwork()); 46 | if (capabilities != null) { 47 | return capabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR) || 48 | capabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) || 49 | capabilities.hasTransport(NetworkCapabilities.TRANSPORT_ETHERNET); 50 | } 51 | } else { 52 | NetworkInfo activeNetworkInfo = connectivityManager.getActiveNetworkInfo(); 53 | return activeNetworkInfo != null && activeNetworkInfo.isConnected(); 54 | } 55 | return false; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /clickstream/src/main/java/software/aws/solution/clickstream/client/network/UserAgentInterceptor.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). 5 | * You may not use this file except in compliance with the License. 6 | * A copy of the License is located at 7 | * 8 | * http://aws.amazon.com/apache2.0 9 | * 10 | * or in the "license" file accompanying this file. This file is distributed 11 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 12 | * express or implied. See the License for the specific language governing 13 | * permissions and limitations under the License. 14 | */ 15 | 16 | package software.aws.solution.clickstream.client.network; 17 | 18 | import androidx.annotation.NonNull; 19 | 20 | import java.io.IOException; 21 | 22 | import okhttp3.Interceptor; 23 | import okhttp3.Request; 24 | import okhttp3.Response; 25 | 26 | /** 27 | * An OkHttp3 interceptor which applies a User-Agent header to an outgoing request. 28 | */ 29 | public final class UserAgentInterceptor implements Interceptor { 30 | private final UserAgentProvider userAgentProvider; 31 | 32 | /** 33 | * Constructs a UserAgentInterceptor. 34 | * 35 | * @param userAgentProvider A Provider of a user-agent string 36 | */ 37 | private UserAgentInterceptor(final UserAgentProvider userAgentProvider) { 38 | this.userAgentProvider = userAgentProvider; 39 | } 40 | 41 | /** 42 | * Creates a user agent interceptor using a user-agent string provider. 43 | * 44 | * @param userAgentProvider Provider of user-agent string 45 | * @return A UserAgentInterceptor 46 | */ 47 | static UserAgentInterceptor using(UserAgentProvider userAgentProvider) { 48 | return new UserAgentInterceptor(userAgentProvider); 49 | } 50 | 51 | @NonNull 52 | @Override 53 | public Response intercept(@NonNull Chain chain) throws IOException { 54 | Request originalRequest = chain.request(); 55 | Request requestWithUserAgent = originalRequest.newBuilder() 56 | .header("User-Agent", userAgentProvider.getUserAgent()) 57 | .build(); 58 | return chain.proceed(requestWithUserAgent); 59 | } 60 | 61 | /** 62 | * A provider of a user-agent string. 63 | */ 64 | interface UserAgentProvider { 65 | /** 66 | * Gets the User-Agent string. 67 | * 68 | * @return User-Agent string 69 | */ 70 | String getUserAgent(); 71 | } 72 | } 73 | 74 | -------------------------------------------------------------------------------- /clickstream/src/main/java/software/aws/solution/clickstream/client/system/AndroidAppDetails.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). 5 | * You may not use this file except in compliance with the License. 6 | * A copy of the License is located at 7 | * 8 | * http://aws.amazon.com/apache2.0 9 | * 10 | * or in the "license" file accompanying this file. This file is distributed 11 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 12 | * express or implied. See the License for the specific language governing 13 | * permissions and limitations under the License. 14 | */ 15 | 16 | package software.aws.solution.clickstream.client.system; 17 | 18 | import android.content.Context; 19 | import android.content.pm.ApplicationInfo; 20 | import android.content.pm.PackageInfo; 21 | import android.content.pm.PackageManager; 22 | import android.content.pm.PackageManager.NameNotFoundException; 23 | 24 | import com.amazonaws.logging.Log; 25 | import com.amazonaws.logging.LogFactory; 26 | 27 | import java.io.Serializable; 28 | 29 | /** 30 | * Android App Details. 31 | */ 32 | public class AndroidAppDetails implements Serializable { 33 | private static final Log LOG = LogFactory.getLog(AndroidAppDetails.class); 34 | private static final String UNKNOWN = "Unknown"; 35 | private String appTitle; 36 | private String packageName; 37 | private String versionName; 38 | 39 | /** 40 | * The construct function with parameters of AndroidAppDetails. 41 | * 42 | * @param context The context of the Android. 43 | */ 44 | public AndroidAppDetails(Context context) { 45 | Context applicationContext = context.getApplicationContext(); 46 | try { 47 | PackageManager packageManager = applicationContext 48 | .getPackageManager(); 49 | PackageInfo packageInfo = packageManager 50 | .getPackageInfo(applicationContext.getPackageName(), 0); 51 | ApplicationInfo appInfo = packageManager 52 | .getApplicationInfo(packageInfo.packageName, 0); 53 | 54 | appTitle = (String) packageManager.getApplicationLabel(appInfo); 55 | packageName = packageInfo.packageName; 56 | versionName = packageInfo.versionName; 57 | } catch (NameNotFoundException nameNotFoundException) { 58 | LOG.warn("Unable to get details for package " + 59 | applicationContext.getPackageName()); 60 | appTitle = UNKNOWN; 61 | packageName = UNKNOWN; 62 | versionName = UNKNOWN; 63 | } 64 | } 65 | 66 | /** 67 | * Get the name of package. 68 | * 69 | * @return The name of package. 70 | */ 71 | public String packageName() { 72 | return packageName; 73 | } 74 | 75 | /** 76 | * Get the name of version. 77 | * 78 | * @return The name of version. 79 | */ 80 | public String versionName() { 81 | return versionName; 82 | } 83 | 84 | /** 85 | * Get the title of app. 86 | * 87 | * @return The title of app. 88 | */ 89 | public String getAppTitle() { 90 | return appTitle; 91 | } 92 | 93 | } 94 | 95 | -------------------------------------------------------------------------------- /clickstream/src/main/java/software/aws/solution/clickstream/client/system/AndroidConnectivity.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). 5 | * You may not use this file except in compliance with the License. 6 | * A copy of the License is located at 7 | * 8 | * http://aws.amazon.com/apache2.0 9 | * 10 | * or in the "license" file accompanying this file. This file is distributed 11 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 12 | * express or implied. See the License for the specific language governing 13 | * permissions and limitations under the License. 14 | */ 15 | 16 | package software.aws.solution.clickstream.client.system; 17 | 18 | import android.content.Context; 19 | import android.net.ConnectivityManager; 20 | import android.net.NetworkInfo; 21 | import android.provider.Settings; 22 | 23 | import com.amazonaws.logging.Log; 24 | import com.amazonaws.logging.LogFactory; 25 | 26 | import java.io.Serializable; 27 | import java.util.Locale; 28 | 29 | /** 30 | * Utility Tool for Android Connectivity. 31 | */ 32 | public class AndroidConnectivity implements Serializable { 33 | private static final Log LOG = LogFactory.getLog(AndroidConnectivity.class); 34 | /** 35 | * Check has WIFI or not. 36 | */ 37 | private boolean hasWifi; 38 | /** 39 | * Check has mobile or not. 40 | */ 41 | private boolean hasMobile; 42 | /** 43 | * Check is in the airplane mode or not. 44 | */ 45 | private boolean inAirplaneMode; 46 | /** 47 | * The context of Android. 48 | */ 49 | private final Context context; 50 | 51 | /** 52 | * The constructor of AndroidConnectivity. 53 | * 54 | * @param context The context of Android. 55 | */ 56 | public AndroidConnectivity(final Context context) { 57 | this.context = context; 58 | } 59 | 60 | /** 61 | * Check the network is connected. 62 | * 63 | * @return The boolean result of connect. 64 | */ 65 | public boolean isConnected() { 66 | determineAvailability(); 67 | return hasWifi() || hasWAN(); 68 | } 69 | 70 | /** 71 | * Check has the WIFI or not. 72 | * 73 | * @return The boolean value of the result. 74 | */ 75 | public boolean hasWifi() { 76 | return this.hasWifi; 77 | } 78 | 79 | /** 80 | * Check has the WAN of not. 81 | * 82 | * @return The boolean value of the result. 83 | */ 84 | public boolean hasWAN() { 85 | return this.hasMobile && !inAirplaneMode; 86 | } 87 | 88 | // this method access constants that were added in the HONEYCOMB_MR2 release 89 | // and is properly guarded from running on older devices. 90 | private void determineAvailability() { 91 | ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); 92 | 93 | inAirplaneMode = Settings.System.getInt(context.getContentResolver(), Settings.System.AIRPLANE_MODE_ON, 0) != 0; 94 | final NetworkInfo networkInfo = cm != null ? cm.getActiveNetworkInfo() : null; 95 | int networkType = 0; 96 | // default state 97 | hasWifi = false; 98 | // when we have connectivity manager, we assume we have some sort of 99 | // connectivity 100 | hasMobile = cm != null; 101 | // can we obtain network info? 102 | if (networkInfo != null) { 103 | if (networkInfo.isConnectedOrConnecting()) { 104 | networkType = networkInfo.getType(); 105 | 106 | hasWifi = networkType == ConnectivityManager.TYPE_WIFI || networkType == ConnectivityManager.TYPE_WIMAX; 107 | hasMobile = networkType == ConnectivityManager.TYPE_MOBILE || 108 | networkType == ConnectivityManager.TYPE_MOBILE_DUN || 109 | networkType == ConnectivityManager.TYPE_MOBILE_HIPRI || 110 | networkType == ConnectivityManager.TYPE_MOBILE_MMS || 111 | networkType == ConnectivityManager.TYPE_MOBILE_SUPL; 112 | } else { 113 | // if neither connected or connecting then hasMobile defaults 114 | // need to be changed to false 115 | hasMobile = false; 116 | } 117 | } 118 | LOG.info(String.format(Locale.US, "Device Connectivity (%s)", getConnectivityStatus())); 119 | } 120 | 121 | private String getConnectivityStatus() { 122 | if (hasWifi) { 123 | return "On Wifi"; 124 | } else if (hasMobile) { 125 | return "On Mobile"; 126 | } 127 | return "No network connectivity"; 128 | } 129 | } 130 | 131 | -------------------------------------------------------------------------------- /clickstream/src/main/java/software/aws/solution/clickstream/client/system/AndroidDeviceDetails.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). 5 | * You may not use this file except in compliance with the License. 6 | * A copy of the License is located at 7 | * 8 | * http://aws.amazon.com/apache2.0 9 | * 10 | * or in the "license" file accompanying this file. This file is distributed 11 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 12 | * express or implied. See the License for the specific language governing 13 | * permissions and limitations under the License. 14 | */ 15 | 16 | package software.aws.solution.clickstream.client.system; 17 | 18 | import android.os.Build; 19 | 20 | import java.io.Serializable; 21 | import java.util.Locale; 22 | 23 | /** 24 | * Android Device Details. 25 | */ 26 | public class AndroidDeviceDetails implements Serializable { 27 | 28 | private final String carrier; 29 | private final String platform = "Android"; // NOSONAR 30 | 31 | /** 32 | * The construct function with parameters. 33 | * 34 | * @param carrier The name of carrier. 35 | */ 36 | public AndroidDeviceDetails(String carrier) { 37 | this.carrier = carrier; 38 | } 39 | 40 | /** 41 | * Get the name of carrier. 42 | * 43 | * @return The name of carrier. 44 | */ 45 | public String carrier() { 46 | return carrier; 47 | } 48 | 49 | /** 50 | * Get the version of platform. 51 | * 52 | * @return The version of platform. 53 | */ 54 | public String platformVersion() { 55 | return Build.VERSION.RELEASE; 56 | } 57 | 58 | /** 59 | * Get the name of platform. 60 | * 61 | * @return The name of platform. 62 | */ 63 | public String platform() { 64 | return platform; 65 | } 66 | 67 | /** 68 | * Get the name of manufacturer. 69 | * 70 | * @return The name of manufacturer. 71 | */ 72 | public String manufacturer() { 73 | return Build.MANUFACTURER; 74 | } 75 | 76 | /** 77 | * Get the device brand. 78 | * 79 | * @return The brand of device. 80 | */ 81 | public String brand() { 82 | return Build.BRAND; 83 | } 84 | 85 | /** 86 | * Get the name of model. 87 | * 88 | * @return The name of model. 89 | */ 90 | public String model() { 91 | return Build.MODEL; 92 | } 93 | 94 | /** 95 | * Get the locale. 96 | * 97 | * @return The locale. 98 | */ 99 | public Locale locale() { 100 | return Locale.getDefault(); 101 | } 102 | 103 | } 104 | 105 | -------------------------------------------------------------------------------- /clickstream/src/main/java/software/aws/solution/clickstream/client/system/AndroidPreferences.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). 5 | * You may not use this file except in compliance with the License. 6 | * A copy of the License is located at 7 | * 8 | * http://aws.amazon.com/apache2.0 9 | * 10 | * or in the "license" file accompanying this file. This file is distributed 11 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 12 | * express or implied. See the License for the specific language governing 13 | * permissions and limitations under the License. 14 | */ 15 | 16 | package software.aws.solution.clickstream.client.system; 17 | 18 | import android.content.Context; 19 | import android.content.SharedPreferences; 20 | 21 | import java.io.Serializable; 22 | 23 | /** 24 | * Android Preferences. 25 | */ 26 | public class AndroidPreferences implements Serializable { 27 | 28 | private final SharedPreferences preferences; 29 | 30 | /** 31 | * The default construct function. 32 | */ 33 | public AndroidPreferences() { 34 | preferences = null; 35 | } 36 | 37 | /** 38 | * The construct function with parameters. 39 | * @param context The context of Android. 40 | * @param preferencesKey The key of preference. 41 | */ 42 | public AndroidPreferences(final Context context, 43 | final String preferencesKey) { 44 | preferences = context.getSharedPreferences(preferencesKey, 45 | Context.MODE_PRIVATE); 46 | } 47 | 48 | /** 49 | * Get the boolean value from the preference with the key. 50 | * @param key The key in the preference. 51 | * @param optValue The default value when no key found. 52 | * @return The boolean value from the preference with the key. 53 | */ 54 | public boolean getBoolean(String key, boolean optValue) { 55 | return preferences.getBoolean(key, optValue); 56 | } 57 | 58 | /** 59 | * Get the integer value from the preference with the key. 60 | * @param key The key in the preference. 61 | * @param optValue The default value when no key found. 62 | * @return The integer value from the preference with the key. 63 | */ 64 | public int getInt(String key, int optValue) { 65 | return preferences.getInt(key, optValue); 66 | } 67 | 68 | /** 69 | * Get the float value from the preference with the key. 70 | * @param key The key in the preference. 71 | * @param optValue The default value when no key found. 72 | * @return The float value from the preference with the key. 73 | */ 74 | public float getFloat(String key, float optValue) { 75 | return preferences.getFloat(key, optValue); 76 | } 77 | 78 | /** 79 | * Get the long value from the preference with the key. 80 | * @param key The key in the preference. 81 | * @param optValue The default value when no key found. 82 | * @return The long value from the preference with the key. 83 | */ 84 | public long getLong(String key, long optValue) { 85 | return preferences.getLong(key, optValue); 86 | } 87 | 88 | /** 89 | * Get the string value from the preference with the key. 90 | * @param key The key in the preference. 91 | * @param optValue The default value when no key found. 92 | * @return The string value from the preference with the key. 93 | */ 94 | public String getString(String key, String optValue) { 95 | return preferences.getString(key, optValue); 96 | } 97 | 98 | /** 99 | * Set the boolean value with the key. 100 | * @param key The key in the preference. 101 | * @param value The value with the key. 102 | */ 103 | public void putBoolean(String key, boolean value) { 104 | SharedPreferences.Editor editor = preferences.edit(); 105 | editor.putBoolean(key, value); 106 | editor.apply(); 107 | } 108 | 109 | /** 110 | * Set the integer value with the key. 111 | * @param key The key in the preference. 112 | * @param value The value with the key. 113 | */ 114 | public void putInt(String key, int value) { 115 | SharedPreferences.Editor editor = preferences.edit(); 116 | editor.putInt(key, value); 117 | editor.apply(); 118 | } 119 | 120 | /** 121 | * Set the float value with the key. 122 | * @param key The key in the preference. 123 | * @param value The value with the key. 124 | */ 125 | public void putFloat(String key, float value) { 126 | SharedPreferences.Editor editor = preferences.edit(); 127 | editor.putFloat(key, value); 128 | editor.apply(); 129 | } 130 | 131 | /** 132 | * Set the long value with the key. 133 | * @param key The key in the preference. 134 | * @param value The value with the key. 135 | */ 136 | public void putLong(String key, long value) { 137 | SharedPreferences.Editor editor = preferences.edit(); 138 | editor.putLong(key, value); 139 | editor.apply(); 140 | } 141 | 142 | /** 143 | * Set the string value with the key. 144 | * @param key The key in the preference. 145 | * @param value The value with the key. 146 | */ 147 | public void putString(String key, String value) { 148 | SharedPreferences.Editor editor = preferences.edit(); 149 | editor.putString(key, value); 150 | editor.apply(); 151 | } 152 | 153 | } 154 | 155 | -------------------------------------------------------------------------------- /clickstream/src/main/java/software/aws/solution/clickstream/client/system/AndroidSystem.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). 5 | * You may not use this file except in compliance with the License. 6 | * A copy of the License is located at 7 | * 8 | * http://aws.amazon.com/apache2.0 9 | * 10 | * or in the "license" file accompanying this file. This file is distributed 11 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 12 | * express or implied. See the License for the specific language governing 13 | * permissions and limitations under the License. 14 | */ 15 | 16 | package software.aws.solution.clickstream.client.system; 17 | 18 | import android.content.Context; 19 | import android.provider.Settings; 20 | import android.telephony.TelephonyManager; 21 | 22 | import java.io.Serializable; 23 | 24 | /** 25 | * Android System. 26 | */ 27 | public class AndroidSystem implements Serializable { 28 | // UUID to identify a unique shared preferences and directory the library 29 | // can use, will be concatenated with the package to ensure no collision. 30 | private final String preferencesKeySuffix = "294262d4-8dbd-4bfd-816d-0fc81b3d32b7"; // NOSONAR 31 | private final AndroidPreferences preferences; 32 | private final AndroidConnectivity connectivity; 33 | private final AndroidAppDetails appDetails; 34 | private final AndroidDeviceDetails deviceDetails; 35 | private final String androidId; 36 | 37 | /** 38 | * The construct function with parameters. 39 | * 40 | * @param context The context of Android. 41 | */ 42 | public AndroidSystem(final Context context) { 43 | preferences = new AndroidPreferences(context, 44 | context.getApplicationContext().getPackageName() + preferencesKeySuffix); 45 | connectivity = new AndroidConnectivity(context); 46 | appDetails = new AndroidAppDetails(context); 47 | deviceDetails = new AndroidDeviceDetails(getCarrier(context)); 48 | androidId = Settings.System.getString(context.getContentResolver(), Settings.Secure.ANDROID_ID); 49 | } 50 | 51 | /** 52 | * Get the carrier. 53 | * 54 | * @param context The context of Android. 55 | * @return The name of carrier. 56 | */ 57 | private String getCarrier(final Context context) { 58 | try { 59 | TelephonyManager telephony = (TelephonyManager) context 60 | .getSystemService(Context.TELEPHONY_SERVICE); 61 | if (null != telephony.getNetworkOperatorName() 62 | && !telephony.getNetworkOperatorName().equals("")) { 63 | return telephony.getNetworkOperatorName(); 64 | } else { 65 | return "Unknown"; 66 | } 67 | } catch (Exception exception) { 68 | return "Unknown"; 69 | } 70 | } 71 | 72 | /** 73 | * Get the Android ID. 74 | * 75 | * @return Android ID 76 | */ 77 | public String getAndroidId() { 78 | return androidId; 79 | } 80 | 81 | /** 82 | * Get the preference of Android. 83 | * 84 | * @return AndroidPreferences. 85 | */ 86 | public AndroidPreferences getPreferences() { 87 | return preferences; 88 | } 89 | 90 | /** 91 | * Get the connectivity of Android. 92 | * 93 | * @return AndroidConnectivity. 94 | */ 95 | public AndroidConnectivity getConnectivity() { 96 | if (connectivity != null) { 97 | connectivity.isConnected(); 98 | } 99 | return connectivity; 100 | } 101 | 102 | /** 103 | * Get the details of app. 104 | * 105 | * @return AndroidAppDetails. 106 | */ 107 | public AndroidAppDetails getAppDetails() { 108 | return appDetails; 109 | } 110 | 111 | /** 112 | * Get the details of device. 113 | * 114 | * @return AndroidDeviceDetails. 115 | */ 116 | public AndroidDeviceDetails getDeviceDetails() { 117 | return deviceDetails; 118 | } 119 | } 120 | 121 | -------------------------------------------------------------------------------- /clickstream/src/main/java/software/aws/solution/clickstream/client/uniqueid/SharedPrefsDeviceIdService.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). 5 | * You may not use this file except in compliance with the License. 6 | * A copy of the License is located at 7 | * 8 | * http://aws.amazon.com/apache2.0 9 | * 10 | * or in the "license" file accompanying this file. This file is distributed 11 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 12 | * express or implied. See the License for the specific language governing 13 | * permissions and limitations under the License. 14 | */ 15 | 16 | package software.aws.solution.clickstream.client.uniqueid; 17 | 18 | import android.provider.Settings; 19 | 20 | import com.amazonaws.logging.Log; 21 | import com.amazonaws.logging.LogFactory; 22 | import software.aws.solution.clickstream.client.ClickstreamContext; 23 | import software.aws.solution.clickstream.client.system.AndroidPreferences; 24 | import software.aws.solution.clickstream.client.util.StringUtil; 25 | 26 | import java.util.UUID; 27 | 28 | /** 29 | * Shared Prefs Unique ID Services. 30 | */ 31 | public class SharedPrefsDeviceIdService { 32 | 33 | /** 34 | * The device ID key. 35 | */ 36 | protected static final String DEVICE_ID_KEY = "DeviceId"; 37 | private static final Log LOG = LogFactory.getLog(SharedPrefsDeviceIdService.class); 38 | 39 | /** 40 | * Uses Shared prefs to recall and store the unique ID. 41 | */ 42 | public SharedPrefsDeviceIdService() { 43 | // Creates a new SharedPrefsDeviceIdService instance with default values. 44 | } 45 | 46 | /** 47 | * Get the Id based on the passed in clickstreamContext. 48 | * 49 | * @param context The Analytics clickstreamContext to use when looking up the id. 50 | * @return the Id of Analytics clickstreamContext. 51 | */ 52 | public String getDeviceId(ClickstreamContext context) { 53 | if (context == null || context.getSystem() == null 54 | || context.getSystem().getPreferences() == null) { 55 | LOG.debug("Unable to generate unique id, clickstreamContext has not been fully initialized."); 56 | return ""; 57 | } 58 | 59 | String uniqueId = getIdFromPreferences(context.getSystem().getPreferences()); 60 | if (uniqueId == null || uniqueId.length() == 0) { 61 | // an id doesn't exist for this clickstreamContext, create set it from Android ID 62 | uniqueId = Settings.System.getString(context.getApplicationContext().getContentResolver(), 63 | Settings.Secure.ANDROID_ID); 64 | // if Android ID is not valid, replace it for UUID. 65 | if (StringUtil.isNullOrEmpty(uniqueId)) { 66 | uniqueId = UUID.randomUUID().toString(); 67 | } 68 | storeUniqueId(context.getSystem().getPreferences(), uniqueId); 69 | } 70 | 71 | return uniqueId; 72 | } 73 | 74 | private String getIdFromPreferences(AndroidPreferences preferences) { 75 | return preferences.getString(DEVICE_ID_KEY, null); 76 | } 77 | 78 | private void storeUniqueId(AndroidPreferences preferences, 79 | String uniqueId) { 80 | try { 81 | preferences.putString(DEVICE_ID_KEY, uniqueId); 82 | } catch (Exception exception) { 83 | // Do not log ex due to potentially sensitive information 84 | LOG.error( 85 | "Exception when trying to store the unique id into the Preferences."); 86 | } 87 | } 88 | } 89 | 90 | -------------------------------------------------------------------------------- /clickstream/src/main/java/software/aws/solution/clickstream/client/util/JSONBuilder.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). 5 | * You may not use this file except in compliance with the License. 6 | * A copy of the License is located at 7 | * 8 | * http://aws.amazon.com/apache2.0 9 | * 10 | * or in the "license" file accompanying this file. This file is distributed 11 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 12 | * express or implied. See the License for the specific language governing 13 | * permissions and limitations under the License. 14 | */ 15 | 16 | package software.aws.solution.clickstream.client.util; 17 | 18 | import androidx.annotation.NonNull; 19 | 20 | import com.amazonaws.logging.Log; 21 | import com.amazonaws.logging.LogFactory; 22 | import org.json.JSONException; 23 | import org.json.JSONObject; 24 | 25 | /** 26 | * The Builder for JSON. 27 | */ 28 | public class JSONBuilder implements JSONSerializable { 29 | private static final Log LOG = LogFactory.getLog(JSONBuilder.class); 30 | private static final int INDENTATION = 4; 31 | private final JSONObject json = new JSONObject(); 32 | 33 | /** 34 | * The constructor of JSONBuilder with parameters. 35 | * 36 | */ 37 | public JSONBuilder() { 38 | // Creates a new JSONBuilder instance with default values. 39 | } 40 | 41 | /** 42 | * Get the instance of JSONBuilder with key and value. 43 | * 44 | * @param key The key. 45 | * @param value The value. 46 | * @return The instance of JSONBuilder. 47 | */ 48 | public JSONBuilder withAttribute(String key, Object value) { 49 | final Object jsonValue = value instanceof JSONSerializable 50 | ? ((JSONSerializable) value).toJSONObject() 51 | : value; 52 | try { 53 | json.putOpt(key, jsonValue); 54 | } catch (final JSONException jsonException) { 55 | LOG.warn("error parsing json"); 56 | } 57 | return this; 58 | } 59 | 60 | /** 61 | * Convert to JSON format. 62 | * 63 | * @return The JSON object. 64 | */ 65 | @Override 66 | public JSONObject toJSONObject() { 67 | return json; 68 | } 69 | 70 | /** 71 | * Convert to string. 72 | * 73 | * @return The string. 74 | */ 75 | @NonNull 76 | @Override 77 | public String toString() { 78 | try { 79 | return json.toString(INDENTATION); 80 | } catch (final JSONException jsonException) { 81 | return json.toString(); 82 | } 83 | } 84 | } 85 | 86 | -------------------------------------------------------------------------------- /clickstream/src/main/java/software/aws/solution/clickstream/client/util/JSONSerializable.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). 5 | * You may not use this file except in compliance with the License. 6 | * A copy of the License is located at 7 | * 8 | * http://aws.amazon.com/apache2.0 9 | * 10 | * or in the "license" file accompanying this file. This file is distributed 11 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 12 | * express or implied. See the License for the specific language governing 13 | * permissions and limitations under the License. 14 | */ 15 | 16 | package software.aws.solution.clickstream.client.util; 17 | 18 | import org.json.JSONObject; 19 | 20 | /** 21 | * The Serializable for JSON. 22 | */ 23 | public interface JSONSerializable { 24 | /** 25 | * Convert to JSON object. 26 | * @return The JSON object. 27 | */ 28 | JSONObject toJSONObject(); 29 | } 30 | 31 | -------------------------------------------------------------------------------- /clickstream/src/main/java/software/aws/solution/clickstream/client/util/StringUtil.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). 5 | * You may not use this file except in compliance with the License. 6 | * A copy of the License is located at 7 | * 8 | * http://aws.amazon.com/apache2.0 9 | * 10 | * or in the "license" file accompanying this file. This file is distributed 11 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 12 | * express or implied. See the License for the specific language governing 13 | * permissions and limitations under the License. 14 | */ 15 | 16 | package software.aws.solution.clickstream.client.util; 17 | 18 | import android.util.Base64; 19 | 20 | import com.amazonaws.logging.Log; 21 | import com.amazonaws.logging.LogFactory; 22 | 23 | import java.io.ByteArrayOutputStream; 24 | import java.io.IOException; 25 | import java.io.UnsupportedEncodingException; 26 | import java.security.MessageDigest; 27 | import java.security.NoSuchAlgorithmException; 28 | import java.util.zip.GZIPOutputStream; 29 | 30 | /** 31 | * String utility methods. 32 | */ 33 | public final class StringUtil { 34 | private static final Log LOG = LogFactory.getLog(StringUtil.class); 35 | private static final int HASH_CODE_BYTE_LENGTH = 4; 36 | private static final int HASH_CODE_PREFIX = 0xFF; 37 | 38 | /** 39 | * Default constructor. 40 | */ 41 | private StringUtil() { 42 | } 43 | 44 | /** 45 | * Compress the string using the gzip. 46 | * 47 | * @param ungzipStr The raw string. 48 | * @return The string using the gzip to compress and encoding with base64. 49 | */ 50 | public static String compressForGzip(String ungzipStr) { 51 | byte[] encode = null; 52 | try (ByteArrayOutputStream baos = new ByteArrayOutputStream(); 53 | GZIPOutputStream gzip = new GZIPOutputStream(baos)) { 54 | gzip.write(ungzipStr.getBytes()); 55 | gzip.close(); //NOSONAR 56 | encode = baos.toByteArray(); 57 | baos.flush(); 58 | } catch (UnsupportedEncodingException uee) { 59 | LOG.error("UnsupportedEncodingException occur when compressForGzip."); 60 | } catch (IOException ioe) { 61 | LOG.error("IOException occur when compressForGzip."); 62 | } 63 | if (encode != null) { 64 | return Base64.encodeToString(encode, Base64.NO_WRAP); 65 | } else { 66 | LOG.error("compressForGzip fail."); 67 | return null; 68 | } 69 | } 70 | 71 | /** 72 | * Determines if a string is null or zero-length. 73 | * 74 | * @param string a string. 75 | * @return true if the argument is null or zero-length, otherwise false. 76 | */ 77 | public static boolean isNullOrEmpty(final String string) { 78 | return string == null || string.length() == 0; 79 | } 80 | 81 | /** 82 | * Determines if a string is blank. 83 | * 84 | * @param string a string. 85 | * @return true if the argument is blank, otherwise false. 86 | */ 87 | public static boolean isBlank(final String string) { 88 | return string == null || string.trim().length() == 0; 89 | } 90 | 91 | /** 92 | * Reduces the input string to the number of chars, or its length if the 93 | * number of chars exceeds the input string's length. 94 | * 95 | * @param input The string to clip. 96 | * @param numChars the number of leading chars to keep (all others will be 97 | * removed). 98 | * @param appendEllipses The boolean value of append. 99 | * @return the clipped string. 100 | */ 101 | public static String clipString(final String input, final int numChars, final boolean appendEllipses) { 102 | int end = Math.min(numChars, input.length()); 103 | String output = input.substring(0, end); 104 | if (appendEllipses) { 105 | output = (output.length() < input.length()) ? output + "..." : output; 106 | } 107 | return output; 108 | } 109 | 110 | /** 111 | * Trims string to its last X characters. If string is too short, is padded 112 | * at the front with given char. 113 | * 114 | * @param str string to trim. 115 | * @param len length of desired string. (must be positive). 116 | * @param pad character to pad with. 117 | * @return The string after pad or trim. 118 | */ 119 | public static String trimOrPadString(String str, int len, final char pad) { 120 | String curStr = str; 121 | int curLen = len; 122 | if (curLen < 0) { 123 | curLen = 0; 124 | } 125 | if (curStr == null) { 126 | curStr = ""; 127 | } 128 | 129 | StringBuilder s = new StringBuilder(); 130 | if (curStr.length() > curLen - 1) { 131 | s.append(curStr.substring(curStr.length() - curLen)); 132 | } else { 133 | for (int i = 0; i < curLen - curStr.length(); i++) { 134 | s.append(pad); 135 | } 136 | s.append(curStr); 137 | } 138 | 139 | return s.toString(); 140 | } 141 | 142 | /** 143 | * method for get event hash code. 144 | * 145 | * @param str event json string 146 | * @return the first 8 sha256 character of the event json 147 | */ 148 | public static String getHashCode(String str) { 149 | MessageDigest digest; 150 | try { 151 | digest = MessageDigest.getInstance("SHA-256"); 152 | digest.update(str.getBytes()); 153 | return bytesToHexString(digest.digest()); 154 | } catch (NoSuchAlgorithmException error) { 155 | LOG.error("Failed to get sha256 for str:" + str); 156 | } 157 | return ""; 158 | } 159 | 160 | private static String bytesToHexString(byte[] bytes) { 161 | StringBuilder sb = new StringBuilder(); 162 | for (int i = 0; i < HASH_CODE_BYTE_LENGTH; i++) { 163 | String hex = Integer.toHexString(HASH_CODE_PREFIX & bytes[i]); 164 | if (hex.length() == 1) { 165 | sb.append('0'); 166 | } 167 | sb.append(hex); 168 | } 169 | return sb.toString(); 170 | } 171 | } 172 | 173 | -------------------------------------------------------------------------------- /clickstream/src/main/java/software/aws/solution/clickstream/client/util/ThreadUtil.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). 5 | * You may not use this file except in compliance with the License. 6 | * A copy of the License is located at 7 | * 8 | * http://aws.amazon.com/apache2.0 9 | * 10 | * or in the "license" file accompanying this file. This file is distributed 11 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 12 | * express or implied. See the License for the specific language governing 13 | * permissions and limitations under the License. 14 | */ 15 | 16 | package software.aws.solution.clickstream.client.util; 17 | 18 | import android.os.Looper; 19 | 20 | /** 21 | * Thread utility methods. 22 | */ 23 | public final class ThreadUtil { 24 | 25 | /** 26 | * Default constructor. 27 | */ 28 | private ThreadUtil() { 29 | } 30 | 31 | /** 32 | * method for return current thread whether not in main thread. 33 | * 34 | * @return boolean value notInMainThread. 35 | */ 36 | public static boolean notInMainThread() { 37 | return Looper.getMainLooper() != Looper.myLooper(); 38 | } 39 | 40 | } 41 | 42 | -------------------------------------------------------------------------------- /clickstream/src/test/java/software/aws/solution/clickstream/ActivityLifecycleManagerUnitTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). 5 | * You may not use this file except in compliance with the License. 6 | * A copy of the License is located at 7 | * 8 | * http://aws.amazon.com/apache2.0 9 | * 10 | * or in the "license" file accompanying this file. This file is distributed 11 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 12 | * express or implied. See the License for the specific language governing 13 | * permissions and limitations under the License. 14 | */ 15 | 16 | package software.aws.solution.clickstream; 17 | 18 | import android.app.Activity; 19 | import android.app.Application; 20 | import android.os.Bundle; 21 | import androidx.lifecycle.Lifecycle; 22 | import androidx.lifecycle.LifecycleOwner; 23 | import androidx.lifecycle.LifecycleRegistry; 24 | import androidx.test.core.app.ApplicationProvider; 25 | 26 | import com.amazonaws.logging.Log; 27 | import com.amazonaws.logging.LogFactory; 28 | import org.junit.Before; 29 | import org.junit.Test; 30 | import org.junit.runner.RunWith; 31 | import org.robolectric.RobolectricTestRunner; 32 | import software.aws.solution.clickstream.client.AutoRecordEventClient; 33 | import software.aws.solution.clickstream.client.ClickstreamManager; 34 | import software.aws.solution.clickstream.client.ScreenRefererTool; 35 | import software.aws.solution.clickstream.client.SessionClient; 36 | 37 | import java.io.ByteArrayOutputStream; 38 | import java.io.PrintStream; 39 | 40 | import static org.junit.Assert.assertNotNull; 41 | import static org.junit.Assert.assertTrue; 42 | import static org.mockito.Mockito.mock; 43 | import static org.mockito.Mockito.verify; 44 | import static org.mockito.Mockito.when; 45 | 46 | /** 47 | * Tests the {@link ActivityLifecycleManager}. 48 | */ 49 | @RunWith(RobolectricTestRunner.class) 50 | public final class ActivityLifecycleManagerUnitTest { 51 | private SessionClient sessionClient; 52 | private AutoRecordEventClient autoRecordEventClient; 53 | private Application.ActivityLifecycleCallbacks callbacks; 54 | private Log log; 55 | private LifecycleRegistry lifecycle; 56 | private LifecycleOwner lifecycleOwner; 57 | private ActivityLifecycleManager lifecycleManager; 58 | 59 | /** 60 | * Setup dependencies and object under test. 61 | * 62 | * @throws Exception exception 63 | */ 64 | @Before 65 | public void setup() throws Exception { 66 | this.sessionClient = mock(SessionClient.class); 67 | ClickstreamManager clickstreamManager = mock(ClickstreamManager.class); 68 | this.autoRecordEventClient = mock(AutoRecordEventClient.class); 69 | when(clickstreamManager.getSessionClient()).thenReturn(sessionClient); 70 | when(clickstreamManager.getAutoRecordEventClient()).thenReturn(autoRecordEventClient); 71 | lifecycleManager = new ActivityLifecycleManager(clickstreamManager); 72 | this.callbacks = lifecycleManager; 73 | 74 | log = LogFactory.getLog(Application.ActivityLifecycleCallbacks.class); 75 | log.setLevel(LogFactory.Level.DEBUG); 76 | 77 | lifecycleOwner = mock(LifecycleOwner.class); 78 | lifecycle = new LifecycleRegistry(lifecycleOwner); 79 | lifecycleManager.startLifecycleTracking(ApplicationProvider.getApplicationContext(), lifecycle); 80 | ScreenRefererTool.clear(); 81 | } 82 | 83 | /** 84 | * When the app is opened, Application open will be logged. 85 | */ 86 | @Test 87 | public void testWhenAppOpened() { 88 | // Given: the launcher activity instance and bundle class instance. 89 | Activity activity = mock(Activity.class); 90 | Bundle bundle = mock(Bundle.class); 91 | 92 | // When: the app is opened main activity goes through the following lifecycle states. 93 | callbacks.onActivityCreated(activity, bundle); 94 | callbacks.onActivityStarted(activity); 95 | callbacks.onActivityResumed(activity); 96 | } 97 | 98 | /** 99 | * When the app is started, user interacts with the app and close the first activity. 100 | */ 101 | @Test 102 | public void testWhenAppEnterAndExit() { 103 | // Given: the launcher activity and the app is opened. 104 | Activity activity = mock(Activity.class); 105 | Bundle bundle = mock(Bundle.class); 106 | // Activity is put in resume state when the app is opened. 107 | callbacks.onActivityCreated(activity, bundle); 108 | callbacks.onActivityStarted(activity); 109 | callbacks.onActivityResumed(activity); 110 | 111 | // When: home button is pressed, app goes to the background. 112 | // Activity stopped when home button is pressed and app goes to background. 113 | callbacks.onActivityPaused(activity); 114 | callbacks.onActivitySaveInstanceState(activity, bundle); 115 | callbacks.onActivityStopped(activity); 116 | callbacks.onActivityDestroyed(activity); 117 | } 118 | 119 | /** 120 | * test invalid context. 121 | */ 122 | @Test 123 | public void testContextIsInValid() { 124 | ByteArrayOutputStream logContent = new ByteArrayOutputStream(); 125 | PrintStream oldSystemOut = System.out; 126 | System.setOut(new PrintStream(logContent)); 127 | 128 | Activity activity = mock(Activity.class); 129 | lifecycleManager.startLifecycleTracking(activity, lifecycle); 130 | 131 | System.setOut(oldSystemOut); 132 | assertTrue( 133 | logContent.toString().contains( 134 | "The context is not ApplicationContext, so lifecycle events are not automatically recorded")); 135 | } 136 | 137 | /** 138 | * test onAppForegrounded event. 139 | */ 140 | @Test 141 | public void testOnAppForegrounded() { 142 | assertNotNull(lifecycleOwner); 143 | when(sessionClient.initialSession()).thenReturn(true); 144 | lifecycle.handleLifecycleEvent(Lifecycle.Event.ON_START); 145 | verify(autoRecordEventClient).updateStartEngageTimestamp(); 146 | verify(autoRecordEventClient).handleAppStart(); 147 | verify(sessionClient).initialSession(); 148 | verify(autoRecordEventClient).setIsEntrances(); 149 | } 150 | 151 | /** 152 | * test onAppBackgrounded event. 153 | */ 154 | @Test 155 | public void testOnAppBackgrounded() { 156 | lifecycle.handleLifecycleEvent(Lifecycle.Event.ON_START); 157 | lifecycle.handleLifecycleEvent(Lifecycle.Event.ON_STOP); 158 | verify(sessionClient).storeSession(); 159 | verify(autoRecordEventClient).recordUserEngagement(); 160 | } 161 | 162 | /** 163 | * test screen view event. 164 | */ 165 | @Test 166 | public void testScreenView() { 167 | Activity activity = mock(Activity.class); 168 | Bundle bundle = mock(Bundle.class); 169 | callbacks.onActivityCreated(activity, bundle); 170 | callbacks.onActivityStarted(activity); 171 | callbacks.onActivityResumed(activity); 172 | verify(autoRecordEventClient).recordViewScreenAutomatically(activity); 173 | } 174 | } 175 | -------------------------------------------------------------------------------- /clickstream/src/test/java/software/aws/solution/clickstream/ExceptionHandlerTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). 5 | * You may not use this file except in compliance with the License. 6 | * A copy of the License is located at 7 | * 8 | * http://aws.amazon.com/apache2.0 9 | * 10 | * or in the "license" file accompanying this file. This file is distributed 11 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 12 | * express or implied. See the License for the specific language governing 13 | * permissions and limitations under the License. 14 | */ 15 | 16 | package software.aws.solution.clickstream; 17 | 18 | import android.content.Context; 19 | import android.database.Cursor; 20 | import androidx.test.core.app.ApplicationProvider; 21 | 22 | import org.json.JSONObject; 23 | import org.junit.After; 24 | import org.junit.Before; 25 | import org.junit.Test; 26 | import org.junit.runner.RunWith; 27 | import org.robolectric.RobolectricTestRunner; 28 | import software.aws.solution.clickstream.client.ClickstreamManager; 29 | import software.aws.solution.clickstream.client.Event; 30 | import software.aws.solution.clickstream.client.db.ClickstreamDBUtil; 31 | 32 | import static org.junit.Assert.assertEquals; 33 | import static org.junit.Assert.assertNotNull; 34 | import static org.junit.Assert.assertTrue; 35 | 36 | /** 37 | * Test the ClickstreamExceptionHandler. 38 | */ 39 | @RunWith(RobolectricTestRunner.class) 40 | public class ExceptionHandlerTest { 41 | 42 | private ClickstreamDBUtil dbUtil; 43 | private ClickstreamManager clickstreamManager; 44 | 45 | /** 46 | * prepare start up environment and context. 47 | * 48 | * @throws Exception exception 49 | */ 50 | @Before 51 | public void setup() throws Exception { 52 | Context context = ApplicationProvider.getApplicationContext(); 53 | Thread.setDefaultUncaughtExceptionHandler((t, e) -> { 54 | }); 55 | dbUtil = new ClickstreamDBUtil(context); 56 | 57 | ClickstreamConfiguration configuration = ClickstreamConfiguration.getDefaultConfiguration() 58 | .withAppId("demo-app") 59 | .withTrackAppExceptionEvents(true) 60 | .withEndpoint("http://example.com/collect") 61 | .withSendEventsInterval(10000); 62 | clickstreamManager = new ClickstreamManager(context, configuration); 63 | dbUtil.deleteBatchEvents(3); 64 | } 65 | 66 | /** 67 | * test exception for record _app_exception event. 68 | * 69 | * @throws Exception exception 70 | * @throws IllegalArgumentException exception 71 | */ 72 | @Test 73 | public void testExceptionRecord() throws Exception { 74 | Thread testThread = new Thread() { 75 | public void run() { 76 | throw new IllegalArgumentException("test exception"); 77 | } 78 | }; 79 | testThread.start(); 80 | testThread.join(); 81 | 82 | assertEquals(1, dbUtil.getTotalNumber()); 83 | 84 | try (Cursor cursor = dbUtil.queryAllEvents()) { 85 | cursor.moveToNext(); 86 | String eventString = cursor.getString(2); 87 | JSONObject jsonObject = new JSONObject(eventString); 88 | String eventName = jsonObject.getString("event_type"); 89 | assertEquals(eventName, Event.PresetEvent.APP_EXCEPTION); 90 | 91 | JSONObject attributes = jsonObject.getJSONObject("attributes"); 92 | assertNotNull(attributes.getString("exception_message")); 93 | assertNotNull(attributes.getString("exception_stack")); 94 | assertEquals("test exception", attributes.getString("exception_message")); 95 | assertTrue( 96 | attributes.getString("exception_stack").contains("java.lang.IllegalArgumentException: test exception")); 97 | assertTrue(attributes.getString("exception_stack").contains("ExceptionHandlerTest.java:")); 98 | } 99 | } 100 | 101 | /** 102 | * test disable exception record using configuration. 103 | * 104 | * @throws Exception exception 105 | * @throws IllegalArgumentException exception 106 | */ 107 | @Test 108 | public void testDisableExceptionRecordWithConfiguration() throws Exception { 109 | clickstreamManager.getClickstreamContext().getClickstreamConfiguration() 110 | .withTrackAppExceptionEvents(false); 111 | Thread testThread = new Thread() { 112 | public void run() { 113 | throw new IllegalArgumentException("test exception"); 114 | } 115 | }; 116 | testThread.start(); 117 | testThread.join(); 118 | 119 | assertEquals(0, dbUtil.getTotalNumber()); 120 | } 121 | 122 | /** 123 | * test disable exception record when sdk disabled. 124 | * 125 | * @throws Exception exception 126 | * @throws IllegalArgumentException exception 127 | */ 128 | @Test 129 | public void testTrackAppExceptionWhenSDKDisabled() throws Exception { 130 | clickstreamManager.disableTrackAppException(); 131 | Thread testThread = new Thread() { 132 | public void run() { 133 | throw new IllegalArgumentException("test exception"); 134 | } 135 | }; 136 | testThread.start(); 137 | testThread.join(); 138 | 139 | assertEquals(0, dbUtil.getTotalNumber()); 140 | } 141 | 142 | /** 143 | * close db. 144 | */ 145 | @After 146 | public void tearDown() { 147 | dbUtil.closeDB(); 148 | } 149 | 150 | } 151 | -------------------------------------------------------------------------------- /clickstream/src/test/java/software/aws/solution/clickstream/SessionClientTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). 5 | * You may not use this file except in compliance with the License. 6 | * A copy of the License is located at 7 | * 8 | * http://aws.amazon.com/apache2.0 9 | * 10 | * or in the "license" file accompanying this file. This file is distributed 11 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 12 | * express or implied. See the License for the specific language governing 13 | * permissions and limitations under the License. 14 | */ 15 | 16 | package software.aws.solution.clickstream; 17 | 18 | import android.content.Context; 19 | import androidx.test.core.app.ApplicationProvider; 20 | 21 | import org.junit.Assert; 22 | import org.junit.Before; 23 | import org.junit.Test; 24 | import org.junit.runner.RunWith; 25 | import org.robolectric.RobolectricTestRunner; 26 | import software.aws.solution.clickstream.client.AnalyticsClient; 27 | import software.aws.solution.clickstream.client.ClickstreamContext; 28 | import software.aws.solution.clickstream.client.ClickstreamManager; 29 | import software.aws.solution.clickstream.client.Session; 30 | import software.aws.solution.clickstream.client.SessionClient; 31 | import software.aws.solution.clickstream.util.ReflectUtil; 32 | 33 | import static org.mockito.Mockito.mock; 34 | 35 | /** 36 | * Tests the {@link software.aws.solution.clickstream.client.SessionClient}. 37 | */ 38 | @RunWith(RobolectricTestRunner.class) 39 | public class SessionClientTest { 40 | private SessionClient client; 41 | private AnalyticsClient analyticsClient; 42 | private ClickstreamContext clickstreamContext; 43 | 44 | /** 45 | * setup the params. 46 | */ 47 | @Before 48 | public void setup() { 49 | Context context = ApplicationProvider.getApplicationContext(); 50 | 51 | ClickstreamConfiguration configuration = ClickstreamConfiguration.getDefaultConfiguration() 52 | .withAppId("demo-app") 53 | .withEndpoint("http://example.com/collect") 54 | .withSendEventsInterval(10000) 55 | .withSessionTimeoutDuration(1800000L); 56 | ClickstreamManager clickstreamManager = new ClickstreamManager(context, configuration); 57 | clickstreamContext = clickstreamManager.getClickstreamContext(); 58 | analyticsClient = clickstreamManager.getAnalyticsClient(); 59 | client = new SessionClient(clickstreamContext); 60 | } 61 | 62 | /** 63 | * test SessionClient execute startSession() method. 64 | * 65 | * @throws Exception exception. 66 | */ 67 | @Test 68 | public void testExecuteStart() throws Exception { 69 | boolean isNewSession = client.initialSession(); 70 | Session session = (Session) ReflectUtil.getField(client, "session"); 71 | Assert.assertNotNull(session); 72 | Session clientSession = (Session) ReflectUtil.getField(analyticsClient, "session"); 73 | Assert.assertNotNull(clientSession); 74 | Assert.assertTrue(isNewSession); 75 | } 76 | 77 | /** 78 | * test SessionClient execute startSession then execute storeSession() method. 79 | * 80 | * @throws Exception exception. 81 | */ 82 | @Test 83 | public void testExecuteStartAndStore() throws Exception { 84 | client.initialSession(); 85 | Session session = (Session) ReflectUtil.getField(client, "session"); 86 | Assert.assertTrue(session.isNewSession()); 87 | 88 | client.storeSession(); 89 | Session storedSession = (Session) ReflectUtil.getField(client, "session"); 90 | Assert.assertFalse(storedSession.isNewSession()); 91 | } 92 | 93 | 94 | /** 95 | * test SessionClient execute twice startSession method without session timeout. 96 | * 97 | * @throws Exception exception. 98 | */ 99 | @Test 100 | public void testExecuteStartTwiceWithoutSessionTimeout() throws Exception { 101 | client.initialSession(); 102 | Session session = (Session) ReflectUtil.getField(client, "session"); 103 | Assert.assertTrue(session.isNewSession()); 104 | Assert.assertEquals(1, session.getSessionIndex()); 105 | 106 | client.storeSession(); 107 | Session storedSession = (Session) ReflectUtil.getField(client, "session"); 108 | Assert.assertFalse(storedSession.isNewSession()); 109 | 110 | client.initialSession(); 111 | Session newSession = (Session) ReflectUtil.getField(client, "session"); 112 | 113 | Assert.assertFalse(newSession.isNewSession()); 114 | Assert.assertEquals(session.getSessionID(), newSession.getSessionID()); 115 | Assert.assertEquals(session.getStartTime(), newSession.getStartTime()); 116 | Assert.assertEquals(1, newSession.getSessionIndex()); 117 | } 118 | 119 | 120 | /** 121 | * test SessionClient execute twice startSession method with session timeout. 122 | * 123 | * @throws Exception exception. 124 | */ 125 | @Test 126 | public void testExecuteStartTwiceWithSessionTimeout() throws Exception { 127 | client.initialSession(); 128 | Session session = (Session) ReflectUtil.getField(client, "session"); 129 | Assert.assertTrue(session.isNewSession()); 130 | Assert.assertEquals(1, session.getSessionIndex()); 131 | 132 | client.storeSession(); 133 | Session storedSession = (Session) ReflectUtil.getField(client, "session"); 134 | Assert.assertFalse(storedSession.isNewSession()); 135 | 136 | clickstreamContext.getClickstreamConfiguration().withSessionTimeoutDuration(0); 137 | client.initialSession(); 138 | Session newSession = (Session) ReflectUtil.getField(client, "session"); 139 | 140 | Assert.assertTrue(newSession.isNewSession()); 141 | Assert.assertNotEquals(session.getSessionID(), newSession.getSessionID()); 142 | Assert.assertNotEquals(session.getStartTime(), newSession.getStartTime()); 143 | Assert.assertEquals(2, newSession.getSessionIndex()); 144 | } 145 | 146 | 147 | /** 148 | * test init SessionClient with IllegalArgumentException. 149 | */ 150 | @Test(expected = IllegalArgumentException.class) 151 | public void testInitSessionClientWithNullAnalyticsClient() { 152 | ClickstreamContext context = mock(ClickstreamContext.class); 153 | new SessionClient(context); 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /clickstream/src/test/java/software/aws/solution/clickstream/db/DBUtilTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). 5 | * You may not use this file except in compliance with the License. 6 | * A copy of the License is located at 7 | * 8 | * http://aws.amazon.com/apache2.0 9 | * 10 | * or in the "license" file accompanying this file. This file is distributed 11 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 12 | * express or implied. See the License for the specific language governing 13 | * permissions and limitations under the License. 14 | */ 15 | 16 | package software.aws.solution.clickstream.db; 17 | 18 | import android.content.ContentValues; 19 | import android.database.Cursor; 20 | import android.database.SQLException; 21 | import android.net.Uri; 22 | import androidx.test.core.app.ApplicationProvider; 23 | 24 | import org.junit.After; 25 | import org.junit.Assert; 26 | import org.junit.Before; 27 | import org.junit.Test; 28 | import org.junit.runner.RunWith; 29 | import org.robolectric.RobolectricTestRunner; 30 | import org.robolectric.annotation.Config; 31 | import software.aws.solution.clickstream.AnalyticsEventTest; 32 | import software.aws.solution.clickstream.client.AnalyticsEvent; 33 | import software.aws.solution.clickstream.client.db.ClickstreamDBBase; 34 | import software.aws.solution.clickstream.client.db.ClickstreamDBUtil; 35 | import software.aws.solution.clickstream.util.ReflectUtil; 36 | 37 | import java.util.Objects; 38 | 39 | import static org.junit.Assert.assertEquals; 40 | import static org.junit.Assert.assertNotEquals; 41 | import static org.junit.Assert.assertNotNull; 42 | import static org.junit.Assert.assertNull; 43 | import static org.mockito.ArgumentMatchers.any; 44 | import static org.mockito.Mockito.doThrow; 45 | import static org.mockito.Mockito.mock; 46 | 47 | @RunWith(RobolectricTestRunner.class) 48 | @Config(manifest = Config.NONE, sdk = 26) 49 | public class DBUtilTest { 50 | 51 | private ClickstreamDBUtil dbUtil; 52 | private AnalyticsEvent analyticsEvent; 53 | 54 | /** 55 | * prepare dbUtil and context. 56 | */ 57 | @Before 58 | public void setup() { 59 | dbUtil = new ClickstreamDBUtil(ApplicationProvider.getApplicationContext()); 60 | analyticsEvent = AnalyticsEventTest.getAnalyticsClient().createEvent("testEvent"); 61 | dbUtil.deleteBatchEvents(3); 62 | } 63 | 64 | /** 65 | * test insert single event. 66 | */ 67 | @Test 68 | public void testInsertSingleEvent() { 69 | Uri uri = dbUtil.saveEvent(analyticsEvent); 70 | int idInserted = Integer.parseInt(Objects.requireNonNull(uri.getLastPathSegment())); 71 | assertNotEquals(idInserted, 0); 72 | } 73 | 74 | /** 75 | * test insert single event failed. 76 | * 77 | * @throws Exception exception. 78 | */ 79 | @Test 80 | public void testInsertSingleEventFailed() throws Exception { 81 | ClickstreamDBBase clickstreamDBBase = mock(ClickstreamDBBase.class); 82 | ReflectUtil.modifyField(dbUtil, "clickstreamDBBase", clickstreamDBBase); 83 | doThrow(new SQLException("Mocked SQLException")).when(clickstreamDBBase).insert(any(Uri.class), any( 84 | ContentValues.class)); 85 | Uri uri = dbUtil.saveEvent(analyticsEvent); 86 | assertNull(uri); 87 | } 88 | 89 | /** 90 | * test query all. 91 | */ 92 | @Test 93 | public void testQueryAll() { 94 | Uri uri1 = dbUtil.saveEvent(analyticsEvent); 95 | Uri uri2 = dbUtil.saveEvent(analyticsEvent); 96 | int idInserted1 = Integer.parseInt(Objects.requireNonNull(uri1.getLastPathSegment())); 97 | int idInserted2 = Integer.parseInt(Objects.requireNonNull(uri2.getLastPathSegment())); 98 | assertNotEquals(idInserted1, 0); 99 | assertNotEquals(idInserted2, 0); 100 | Cursor c = dbUtil.queryAllEvents(); 101 | assertNotNull(c); 102 | assertEquals(c.getCount(), 2); 103 | c.close(); 104 | } 105 | 106 | 107 | /** 108 | * test delete two event. 109 | */ 110 | @Test 111 | public void testDelete() { 112 | Uri uri1 = dbUtil.saveEvent(analyticsEvent); 113 | Uri uri2 = dbUtil.saveEvent(analyticsEvent); 114 | int idInserted1 = Integer.parseInt(Objects.requireNonNull(uri1.getLastPathSegment())); 115 | int idInserted2 = Integer.parseInt(Objects.requireNonNull(uri2.getLastPathSegment())); 116 | assertNotEquals(idInserted1, 0); 117 | assertNotEquals(idInserted2, 0); 118 | Cursor c = dbUtil.queryAllEvents(); 119 | assertNotNull(c); 120 | assertEquals(c.getCount(), 2); 121 | c.close(); 122 | 123 | int delete1 = dbUtil.deleteEvent(idInserted1); 124 | assertEquals(delete1, 1); 125 | Cursor c1 = dbUtil.queryAllEvents(); 126 | assertNotNull(c1); 127 | assertEquals(c1.getCount(), 1); 128 | c1.close(); 129 | 130 | int delete2 = dbUtil.deleteEvent(idInserted2); 131 | assertEquals(delete2, 1); 132 | Cursor c2 = dbUtil.queryAllEvents(); 133 | assertNotNull(c2); 134 | assertEquals(c2.getCount(), 0); 135 | c2.close(); 136 | } 137 | 138 | /** 139 | * test get total size. 140 | */ 141 | @Test 142 | public void testGetTotalDbSize() { 143 | int eventLength1 = analyticsEvent.toJSONObject().toString().length(); 144 | Uri uri1 = dbUtil.saveEvent(analyticsEvent); 145 | int eventLength2 = analyticsEvent.toJSONObject().toString().length(); 146 | Uri uri2 = dbUtil.saveEvent(analyticsEvent); 147 | int idInserted1 = Integer.parseInt(Objects.requireNonNull(uri1.getLastPathSegment())); 148 | int idInserted2 = Integer.parseInt(Objects.requireNonNull(uri2.getLastPathSegment())); 149 | assertNotEquals(idInserted1, 0); 150 | assertNotEquals(idInserted2, 0); 151 | Cursor c = dbUtil.queryAllEvents(); 152 | assertNotNull(c); 153 | assertEquals(c.getCount(), 2); 154 | c.close(); 155 | Assert.assertTrue(dbUtil.getTotalSize() - (eventLength1 + eventLength2) < 10); 156 | } 157 | 158 | /** 159 | * test get total number. 160 | */ 161 | @Test 162 | public void testGetTotalDbNumber() { 163 | Uri uri1 = dbUtil.saveEvent(analyticsEvent); 164 | Uri uri2 = dbUtil.saveEvent(analyticsEvent); 165 | int idInserted1 = Integer.parseInt(Objects.requireNonNull(uri1.getLastPathSegment())); 166 | int idInserted2 = Integer.parseInt(Objects.requireNonNull(uri2.getLastPathSegment())); 167 | assertNotEquals(idInserted1, 0); 168 | assertNotEquals(idInserted2, 0); 169 | assertEquals(dbUtil.getTotalNumber(), 2); 170 | } 171 | 172 | /** 173 | * close db. 174 | */ 175 | @After 176 | public void tearDown() { 177 | dbUtil.closeDB(); 178 | } 179 | } 180 | -------------------------------------------------------------------------------- /clickstream/src/test/java/software/aws/solution/clickstream/event/EventCheckerTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). 5 | * You may not use this file except in compliance with the License. 6 | * A copy of the License is located at 7 | * 8 | * http://aws.amazon.com/apache2.0 9 | * 10 | * or in the "license" file accompanying this file. This file is distributed 11 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 12 | * express or implied. See the License for the specific language governing 13 | * permissions and limitations under the License. 14 | */ 15 | 16 | package software.aws.solution.clickstream.event; 17 | 18 | import org.junit.Test; 19 | import org.junit.runner.RunWith; 20 | import org.robolectric.RobolectricTestRunner; 21 | import org.robolectric.annotation.Config; 22 | import software.aws.solution.clickstream.client.EventChecker; 23 | 24 | import static org.junit.Assert.assertFalse; 25 | import static org.junit.Assert.assertTrue; 26 | 27 | @RunWith(RobolectricTestRunner.class) 28 | @Config(manifest = Config.NONE, sdk = 23) 29 | public class EventCheckerTest { 30 | 31 | /** 32 | * test the name is valid. 33 | */ 34 | @Test 35 | public void tesIsValidName() { 36 | assertFalse(EventChecker.isValidName("")); 37 | assertTrue(EventChecker.isValidName("abc")); 38 | assertFalse(EventChecker.isValidName("123")); 39 | assertTrue(EventChecker.isValidName("AAA")); 40 | assertTrue(EventChecker.isValidName("a_ab")); 41 | assertTrue(EventChecker.isValidName("a_ab_1A")); 42 | assertTrue(EventChecker.isValidName("add_to_cart")); 43 | assertTrue(EventChecker.isValidName("Screen_view")); 44 | assertFalse(EventChecker.isValidName("0abc")); 45 | assertFalse(EventChecker.isValidName("1abc")); 46 | assertFalse(EventChecker.isValidName("9Abc")); 47 | assertTrue(EventChecker.isValidName("A9bc")); 48 | assertFalse(EventChecker.isValidName("A9bc-")); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /clickstream/src/test/java/software/aws/solution/clickstream/system/AndroidPreferencesTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). 5 | * You may not use this file except in compliance with the License. 6 | * A copy of the License is located at 7 | * 8 | * http://aws.amazon.com/apache2.0 9 | * 10 | * or in the "license" file accompanying this file. This file is distributed 11 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 12 | * express or implied. See the License for the specific language governing 13 | * permissions and limitations under the License. 14 | */ 15 | 16 | package software.aws.solution.clickstream.system; 17 | 18 | import android.content.Context; 19 | import android.content.SharedPreferences; 20 | import androidx.test.core.app.ApplicationProvider; 21 | 22 | import org.junit.Before; 23 | import org.junit.Test; 24 | import org.junit.runner.RunWith; 25 | import org.robolectric.RobolectricTestRunner; 26 | import org.robolectric.annotation.Config; 27 | import software.aws.solution.clickstream.client.system.AndroidPreferences; 28 | 29 | import static org.junit.Assert.assertEquals; 30 | import static org.junit.Assert.assertSame; 31 | import static org.junit.Assert.assertTrue; 32 | 33 | @RunWith(RobolectricTestRunner.class) 34 | @Config(manifest = Config.NONE) 35 | public class AndroidPreferencesTest { 36 | 37 | // This is the suffix we use. In real code we prepend this with the appId 38 | private static final String PREFERENCE_KEY = "294262d4-8dbd-4bfd-816d-0fc81b3d32b7"; 39 | 40 | private SharedPreferences pref; 41 | private Context context; 42 | 43 | /** 44 | * set up the application context and sharedPreferences. 45 | */ 46 | @Before 47 | public void setup() { 48 | context = ApplicationProvider.getApplicationContext(); 49 | pref = context.getSharedPreferences(PREFERENCE_KEY, Context.MODE_PRIVATE); 50 | } 51 | 52 | /** 53 | * test getBoolean. 54 | */ 55 | @Test 56 | public void getBoolean() { 57 | pref.edit().putBoolean("boolean", true).commit(); 58 | AndroidPreferences preferences = new AndroidPreferences(context, PREFERENCE_KEY); 59 | boolean value = preferences.getBoolean("boolean", false); 60 | assertTrue(value); 61 | } 62 | 63 | /** 64 | * test getInt. 65 | */ 66 | @Test 67 | public void getInt() { 68 | pref.edit().putInt("int", 1).commit(); 69 | AndroidPreferences preferences = new AndroidPreferences(context, PREFERENCE_KEY); 70 | int value = preferences.getInt("int", 0); 71 | assertEquals(value, 1); 72 | } 73 | 74 | /** 75 | * test getFloat. 76 | */ 77 | @Test 78 | public void getFloat() { 79 | pref.edit().putFloat("float", 1.0f).commit(); 80 | AndroidPreferences preferences = new AndroidPreferences(context, PREFERENCE_KEY); 81 | float value = preferences.getFloat("float", 0.0f); 82 | assertEquals(value, 1.0f, .01f); 83 | } 84 | 85 | /** 86 | * test getLong. 87 | */ 88 | @Test 89 | public void getLong() { 90 | pref.edit().putLong("long", 1L).commit(); 91 | AndroidPreferences preferences = new AndroidPreferences(context, PREFERENCE_KEY); 92 | long value = preferences.getLong("long", 0L); 93 | assertEquals(value, 1L); 94 | } 95 | 96 | /** 97 | * test getString. 98 | */ 99 | @Test 100 | public void getString() { 101 | pref.edit().putString("string", "value").commit(); 102 | AndroidPreferences preferences = new AndroidPreferences(context, PREFERENCE_KEY); 103 | String value = preferences.getString("string", "other"); 104 | assertEquals(value, "value"); 105 | } 106 | 107 | /** 108 | * test putBoolean. 109 | */ 110 | @Test 111 | public void putBoolean() { 112 | AndroidPreferences preferences = new AndroidPreferences(context, PREFERENCE_KEY); 113 | preferences.putBoolean("boolean", true); 114 | assertTrue(pref.getBoolean("boolean", false)); 115 | } 116 | 117 | /** 118 | * test putInt. 119 | */ 120 | @Test 121 | public void putInt() { 122 | AndroidPreferences preferences = new AndroidPreferences(context, PREFERENCE_KEY); 123 | preferences.putInt("int", 1); 124 | assertEquals(pref.getInt("int", 5), 1); 125 | } 126 | 127 | /** 128 | * test putFloat. 129 | */ 130 | @Test 131 | public void putFloat() { 132 | AndroidPreferences preferences = new AndroidPreferences(context, PREFERENCE_KEY); 133 | preferences.putFloat("float", 1.0f); 134 | assertEquals(pref.getFloat("float", 5.0f), 1.0f, .05f); 135 | } 136 | 137 | /** 138 | * test putLong. 139 | */ 140 | @Test 141 | public void putLong() { 142 | AndroidPreferences preferences = new AndroidPreferences(context, PREFERENCE_KEY); 143 | preferences.putLong("long", 1L); 144 | assertEquals(pref.getLong("long", 5L), 1L); 145 | } 146 | 147 | /** 148 | * test putString. 149 | */ 150 | @Test 151 | public void putString() { 152 | AndroidPreferences preferences = new AndroidPreferences(context, PREFERENCE_KEY); 153 | preferences.putString("string", "value"); 154 | assertSame(pref.getString("string", "nonValue"), "value"); 155 | } 156 | } 157 | -------------------------------------------------------------------------------- /clickstream/src/test/java/software/aws/solution/clickstream/uniqueid/SharedPrefsDeviceIdServiceTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). 5 | * You may not use this file except in compliance with the License. 6 | * A copy of the License is located at 7 | * 8 | * http://aws.amazon.com/apache2.0 9 | * 10 | * or in the "license" file accompanying this file. This file is distributed 11 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 12 | * express or implied. See the License for the specific language governing 13 | * permissions and limitations under the License. 14 | */ 15 | 16 | package software.aws.solution.clickstream.uniqueid; 17 | 18 | import androidx.test.core.app.ApplicationProvider; 19 | 20 | import org.junit.Before; 21 | import org.junit.Test; 22 | import org.junit.runner.RunWith; 23 | import org.robolectric.RobolectricTestRunner; 24 | import org.robolectric.annotation.Config; 25 | import software.aws.solution.clickstream.client.ClickstreamContext; 26 | import software.aws.solution.clickstream.client.system.AndroidPreferences; 27 | import software.aws.solution.clickstream.client.system.AndroidSystem; 28 | import software.aws.solution.clickstream.client.uniqueid.SharedPrefsDeviceIdService; 29 | 30 | import static org.junit.Assert.assertEquals; 31 | import static org.junit.Assert.assertNotNull; 32 | import static org.mockito.ArgumentMatchers.any; 33 | import static org.mockito.ArgumentMatchers.anyString; 34 | import static org.mockito.ArgumentMatchers.eq; 35 | import static org.mockito.Mockito.mock; 36 | import static org.mockito.Mockito.times; 37 | import static org.mockito.Mockito.verify; 38 | import static org.mockito.Mockito.when; 39 | 40 | @RunWith(RobolectricTestRunner.class) 41 | @Config(manifest = Config.NONE) 42 | public class SharedPrefsDeviceIdServiceTest { 43 | 44 | private ClickstreamContext mockClickstreamContext; 45 | private AndroidSystem mockSystem; 46 | private AndroidPreferences mockPreferences; 47 | 48 | private SharedPrefsDeviceIdService serviceToTest = null; 49 | 50 | /** 51 | * setup the mockSystem,mockPreferences and SharedPrefsDeviceIdService. 52 | */ 53 | @Before 54 | public void setup() { 55 | mockClickstreamContext = mock(ClickstreamContext.class); 56 | mockSystem = mock(AndroidSystem.class); 57 | mockPreferences = mock(AndroidPreferences.class); 58 | when(mockClickstreamContext.getSystem()).thenReturn(mockSystem); 59 | when(mockClickstreamContext.getApplicationContext()).thenReturn(ApplicationProvider.getApplicationContext()); 60 | when(mockSystem.getPreferences()).thenReturn(mockPreferences); 61 | serviceToTest = new SharedPrefsDeviceIdService(); 62 | } 63 | 64 | /** 65 | * test getUniqueId when sp is null then return empty id. 66 | */ 67 | @Test 68 | public void getDeviceIdWhenSpIsNull() { 69 | when(mockSystem.getPreferences()).thenReturn(null); 70 | String uniqueId = serviceToTest.getDeviceId(mockClickstreamContext); 71 | assertEquals(uniqueId, ""); 72 | } 73 | 74 | /** 75 | * test getUniqueId when id does not exist then create and store id. 76 | */ 77 | @Test 78 | public void getDeviceIdWhenIdDoesNotExist() { 79 | String expectedUniqueIdKey = "DeviceId"; 80 | when(mockPreferences.getString(eq(expectedUniqueIdKey), anyString())).thenReturn(null); 81 | String uniqueId = serviceToTest.getDeviceId(mockClickstreamContext); 82 | assertNotNull(uniqueId); 83 | verify(mockPreferences, times(1)).putString(eq(expectedUniqueIdKey), any(String.class)); 84 | } 85 | 86 | } 87 | -------------------------------------------------------------------------------- /clickstream/src/test/java/software/aws/solution/clickstream/util/CustomOkhttpDns.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). 5 | * You may not use this file except in compliance with the License. 6 | * A copy of the License is located at 7 | * 8 | * http://aws.amazon.com/apache2.0 9 | * 10 | * or in the "license" file accompanying this file. This file is distributed 11 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 12 | * express or implied. See the License for the specific language governing 13 | * permissions and limitations under the License. 14 | */ 15 | 16 | package software.aws.solution.clickstream.util; 17 | 18 | import androidx.annotation.NonNull; 19 | 20 | import java.net.InetAddress; 21 | import java.net.UnknownHostException; 22 | import java.util.Arrays; 23 | import java.util.List; 24 | 25 | import okhttp3.Dns; 26 | 27 | /** 28 | * Sample code for custom OkhttpDns. 29 | */ 30 | public final class CustomOkhttpDns implements Dns { 31 | 32 | private static CustomOkhttpDns instance = null; 33 | private String defaultIp = null; 34 | private Boolean isResolutionTimeout = false; 35 | private Boolean isUnKnowHost = false; 36 | 37 | private CustomOkhttpDns() { 38 | } 39 | 40 | /** 41 | * get instance for CustomOkhttpDns. 42 | * 43 | * @return instance. 44 | */ 45 | public static CustomOkhttpDns getInstance() { 46 | if (instance == null) { 47 | instance = new CustomOkhttpDns(); 48 | } 49 | return instance; 50 | } 51 | 52 | /** 53 | * Lookup method for dns. 54 | * 55 | * @param hostname host name. 56 | * @return list of ip. 57 | * @throws UnknownHostException exception. 58 | */ 59 | @NonNull 60 | @Override 61 | public List lookup(@NonNull String hostname) throws UnknownHostException { 62 | String ip = getIpByHost(hostname); 63 | if (ip != null) { 64 | // if ip is not null,use this ip to request network. 65 | return Arrays.asList(InetAddress.getAllByName(ip)); 66 | } 67 | // if return null,use the system dns. 68 | return Dns.SYSTEM.lookup(hostname); 69 | } 70 | 71 | /** 72 | * Get ip by host. 73 | * 74 | * @param hostname host name 75 | * @return ip address. 76 | * @throws UnknownHostException exception. 77 | */ 78 | public String getIpByHost(String hostname) throws UnknownHostException { 79 | if (isResolutionTimeout) { 80 | try { 81 | Thread.sleep(1001); 82 | } catch (InterruptedException exception) { 83 | exception.printStackTrace(); 84 | } 85 | } 86 | if (isUnKnowHost) { 87 | throw new UnknownHostException("unknown host exception for test"); 88 | } 89 | 90 | // your dns logic 91 | return defaultIp; 92 | } 93 | 94 | /** 95 | * set default ip for test. 96 | * 97 | * @param defaultIp default ip. 98 | */ 99 | public void setDefaultIp(String defaultIp) { 100 | this.defaultIp = defaultIp; 101 | } 102 | 103 | /** 104 | * set whether ip resolution time out. 105 | * 106 | * @param isResolutionTimeout boolean for is ip resolution timeout. 107 | */ 108 | public void setIsResolutionTimeout(Boolean isResolutionTimeout) { 109 | this.isResolutionTimeout = isResolutionTimeout; 110 | } 111 | 112 | /** 113 | * set whether is unKnow host. 114 | * 115 | * @param isUnKnowHost boolean for is unKnow host. 116 | */ 117 | public void setIsUnKnowHost(Boolean isUnKnowHost) { 118 | this.isUnKnowHost = isUnKnowHost; 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /clickstream/src/test/java/software/aws/solution/clickstream/util/ReflectUtil.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). 5 | * You may not use this file except in compliance with the License. 6 | * A copy of the License is located at 7 | * 8 | * http://aws.amazon.com/apache2.0 9 | * 10 | * or in the "license" file accompanying this file. This file is distributed 11 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 12 | * express or implied. See the License for the specific language governing 13 | * permissions and limitations under the License. 14 | */ 15 | 16 | package software.aws.solution.clickstream.util; 17 | 18 | import com.amplifyframework.analytics.AnalyticsPlugin; 19 | import com.amplifyframework.core.Amplify; 20 | import com.amplifyframework.util.UserAgent; 21 | 22 | import java.lang.reflect.Constructor; 23 | import java.lang.reflect.Field; 24 | import java.lang.reflect.Method; 25 | import java.util.Objects; 26 | import java.util.concurrent.atomic.AtomicBoolean; 27 | import java.util.concurrent.atomic.AtomicReference; 28 | 29 | /** 30 | * reflect Util for test. 31 | */ 32 | public final class ReflectUtil { 33 | 34 | /** 35 | * hide the default constructor. 36 | */ 37 | private ReflectUtil() { 38 | 39 | } 40 | 41 | /** 42 | * modify field value even if the private static final param, only for test use. 43 | * 44 | * @param object the object to modify. 45 | * @param fieldName filed name to modify. 46 | * @param newFieldValue new filed value to set. 47 | * @throws Exception exception. 48 | */ 49 | public static void modifyField(Object object, String fieldName, Object newFieldValue) throws Exception { 50 | Field field = object.getClass().getDeclaredField(fieldName); 51 | field.setAccessible(true); 52 | field.set(object, newFieldValue); 53 | } 54 | 55 | /** 56 | * get the private field object of the given object. 57 | * 58 | * @param object the filed parent object. 59 | * @param fieldName the filed name. 60 | * @return the filed object. 61 | * @throws Exception exception. 62 | */ 63 | public static Object getField(Object object, String fieldName) throws Exception { 64 | Field field = object.getClass().getDeclaredField(fieldName); 65 | field.setAccessible(true); 66 | return field.get(object); 67 | } 68 | 69 | /** 70 | * to invoke private method for not has param. 71 | * 72 | * @param object the object to invoke. 73 | * @param methodName the method name of the object to invoke. 74 | * @return the object of the method return. 75 | * @throws Exception exception. 76 | */ 77 | public static Object invokeMethod(Object object, String methodName) throws Exception { 78 | Method method = object.getClass().getDeclaredMethod(methodName); 79 | method.setAccessible(true); 80 | return method.invoke(object); 81 | } 82 | 83 | /** 84 | * to invoke private method for not has param in super class. 85 | * 86 | * @param object the object to invoke. 87 | * @param methodName the method name of the object to invoke. 88 | * @return the object of the method return. 89 | * @throws Exception exception. 90 | */ 91 | public static Object invokeSuperMethod(Object object, String methodName) throws Exception { 92 | Method method = Objects.requireNonNull(object.getClass().getSuperclass()).getDeclaredMethod(methodName); 93 | method.setAccessible(true); 94 | return method.invoke(object); 95 | } 96 | 97 | /** 98 | * invokeMethod with method and args. 99 | * 100 | * @param object object to invoke. 101 | * @param method the method to invoke. 102 | * @param args the method args. 103 | * @return the method return object. 104 | * @throws Exception exception 105 | */ 106 | public static Object invokeMethod(Object object, Method method, Object... args) 107 | throws Exception { 108 | method.setAccessible(true); 109 | return method.invoke(object, args); 110 | } 111 | 112 | /** 113 | * new instance for object who has private constructor. the constructor default to get the first. 114 | * 115 | * @param clazz which class to new instance. 116 | * @param params constructor params. 117 | * @return the instance of the class. 118 | * @throws Exception exception. 119 | */ 120 | public static Object newInstance(Class clazz, Object... params) throws Exception { 121 | Constructor[] declaredConstructors = clazz.getDeclaredConstructors(); 122 | Constructor declaredConstructor = declaredConstructors[0]; 123 | declaredConstructor.setAccessible(true); 124 | return declaredConstructor.newInstance(params); 125 | } 126 | 127 | /** 128 | * Method for make Amplify analytics plugin Not configured. 129 | * 130 | * @throws Exception exception. 131 | */ 132 | @SuppressWarnings({"unchecked", "Enum"}) 133 | public static void makeAmplifyNotConfigured() throws Exception { 134 | // remove plugin 135 | if ((Boolean) ReflectUtil.invokeSuperMethod(Amplify.Analytics, "isConfigured")) { 136 | try { 137 | AnalyticsPlugin plugin = Amplify.Analytics.getPlugin("awsClickstreamPlugin"); 138 | Amplify.Analytics.removePlugin(plugin); 139 | } catch (IllegalStateException exception) { 140 | exception.printStackTrace(); 141 | } 142 | } 143 | // change the CONFIGURATION_LOCK 144 | Field configurationLockField = Amplify.class.getDeclaredField("CONFIGURATION_LOCK"); 145 | configurationLockField.setAccessible(true); 146 | AtomicBoolean configurationLock = (AtomicBoolean) configurationLockField.get(null); 147 | assert configurationLock != null; 148 | configurationLock.set(false); 149 | 150 | // Set UserAgent to null 151 | Field field = UserAgent.class.getDeclaredField("instance"); 152 | field.setAccessible(true); 153 | field.set(UserAgent.class, null); 154 | 155 | // set Analytics not configured 156 | Class categoryClass = Amplify.Analytics.getClass().getSuperclass(); 157 | assert categoryClass != null; 158 | Field stateField = categoryClass.getDeclaredField("state"); 159 | stateField.setAccessible(true); 160 | AtomicReference state = (AtomicReference) stateField.get(Amplify.Analytics); 161 | Class stateEnumClass = Class.forName(categoryClass.getName() + "$State"); 162 | 163 | Enum notConfiguredEnumConstant = (Enum) stateEnumClass.cast(Enum.valueOf( 164 | stateEnumClass.asSubclass(Enum.class), "NOT_CONFIGURED")); 165 | assert state != null; 166 | state.set(notConfiguredEnumConstant); 167 | } 168 | } 169 | -------------------------------------------------------------------------------- /clickstream/src/test/java/software/aws/solution/clickstream/util/StringUtilTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). 5 | * You may not use this file except in compliance with the License. 6 | * A copy of the License is located at 7 | * 8 | * http://aws.amazon.com/apache2.0 9 | * 10 | * or in the "license" file accompanying this file. This file is distributed 11 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 12 | * express or implied. See the License for the specific language governing 13 | * permissions and limitations under the License. 14 | */ 15 | 16 | package software.aws.solution.clickstream.util; 17 | 18 | import org.junit.Test; 19 | import org.junit.runner.RunWith; 20 | import org.robolectric.RobolectricTestRunner; 21 | import org.robolectric.annotation.Config; 22 | import software.aws.solution.clickstream.client.util.StringUtil; 23 | 24 | import static org.junit.Assert.assertEquals; 25 | import static org.junit.Assert.assertFalse; 26 | import static org.junit.Assert.assertTrue; 27 | 28 | @RunWith(RobolectricTestRunner.class) 29 | @Config(manifest = Config.NONE, sdk = 23) 30 | public class StringUtilTest { 31 | 32 | /** 33 | * test isNullOrEmptyForEmptyString. 34 | */ 35 | @Test 36 | public void isNullOrEmptyForEmptyString() { 37 | assertTrue(StringUtil.isNullOrEmpty("")); 38 | } 39 | 40 | /** 41 | * test isNullOrEmptyForBlankString. 42 | */ 43 | @Test 44 | public void isNullOrEmptyForBlankString() { 45 | assertFalse(StringUtil.isNullOrEmpty(" ")); 46 | } 47 | 48 | /** 49 | * test isNullOrEmptyForNonBlankString. 50 | */ 51 | @Test 52 | public void isNullOrEmptyForNonBlankString() { 53 | assertFalse(StringUtil.isNullOrEmpty("abcde")); 54 | } 55 | 56 | /** 57 | * test isBlankForEmptyString. 58 | */ 59 | @Test 60 | public void isBlankForEmptyString() { 61 | assertTrue(StringUtil.isBlank("")); 62 | } 63 | 64 | /** 65 | * test isBlankForBlankString. 66 | */ 67 | @Test 68 | public void isBlankForBlankString() { 69 | assertTrue(StringUtil.isBlank(" ")); 70 | } 71 | 72 | /** 73 | * test isBlankForNonBlankString. 74 | */ 75 | @Test 76 | public void isBlankForNonBlankString() { 77 | assertFalse(StringUtil.isBlank("abcde")); 78 | } 79 | 80 | /** 81 | * test trimOrPadStringForTrimsString. 82 | */ 83 | @Test 84 | public void trimOrPadStringForTrimsString() { 85 | assertEquals(StringUtil.trimOrPadString("abcdefg", 7, ' '), "abcdefg"); 86 | assertEquals(StringUtil.trimOrPadString("abcdefg", 6, ' '), "bcdefg"); 87 | assertEquals(StringUtil.trimOrPadString("abcdefg", 5, ' '), "cdefg"); 88 | assertEquals(StringUtil.trimOrPadString("abcdefg", 4, ' '), "defg"); 89 | assertEquals(StringUtil.trimOrPadString("abcdefg", 3, ' '), "efg"); 90 | assertEquals(StringUtil.trimOrPadString("abcdefg", 2, ' '), "fg"); 91 | assertEquals(StringUtil.trimOrPadString("abcdefg", 1, ' '), "g"); 92 | assertEquals(StringUtil.trimOrPadString("abcdefg", 0, ' '), ""); 93 | } 94 | 95 | /** 96 | * test trimOrPadStringForPadsString. 97 | */ 98 | @Test 99 | public void trimOrPadStringForPadsString() { 100 | assertEquals(StringUtil.trimOrPadString("abc", 7, '_'), "____abc"); 101 | assertEquals(StringUtil.trimOrPadString("abc", 6, '-'), "---abc"); 102 | assertEquals(StringUtil.trimOrPadString("abc", 5, '\\'), "\\\\abc"); 103 | assertEquals(StringUtil.trimOrPadString("abc", 4, '$'), "$abc"); 104 | 105 | assertEquals(StringUtil.trimOrPadString("", 10, '&'), "&&&&&&&&&&"); 106 | assertEquals(StringUtil.trimOrPadString("", 5, '"'), "\"\"\"\"\""); 107 | assertEquals(StringUtil.trimOrPadString("\b\b", 5, '\n'), "\n\n\n\b\b"); 108 | 109 | int l = 100; 110 | String s = StringUtil.trimOrPadString("", l, '\\'); 111 | assertEquals(s.length(), l); 112 | for (int i = 0; i < s.length(); i++) { 113 | assertEquals(s.charAt(i), '\\'); 114 | } 115 | } 116 | 117 | /** 118 | * test trimOrPadStringWorksWithInvalidArgs. 119 | */ 120 | @Test 121 | public void trimOrPadStringWorksWithInvalidArgs() { 122 | // len < 0 123 | assertEquals(StringUtil.trimOrPadString("abcdefg", -1, ' '), ""); 124 | assertEquals(StringUtil.trimOrPadString("abcdefg", -2, ' '), ""); 125 | // null string 126 | assertEquals(StringUtil.trimOrPadString(null, 5, '+'), "+++++"); 127 | } 128 | 129 | /** 130 | * test clipStringForAppendEllipses. 131 | */ 132 | @Test 133 | public void clipStringForAppendEllipses() { 134 | assertTrue(StringUtil.clipString("abcdefgh", 5, true).endsWith("...")); 135 | assertFalse(StringUtil.clipString("abcdefgh", 10, true).endsWith("...")); 136 | } 137 | 138 | /** 139 | * test validate gzip with no wrap. 140 | */ 141 | @Test 142 | public void validateGzipWithNoWrap() { 143 | String str = "abcdeabcde"; 144 | StringBuilder sb = new StringBuilder(); 145 | for (int i = 0; i < 100; i++) { 146 | sb.append(str); 147 | } 148 | String gzippedStr = StringUtil.compressForGzip(sb.toString()); 149 | assert gzippedStr != null; 150 | assertFalse(gzippedStr.contains("\n")); 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /clickstream/src/test/res/raw/amplifyconfiguration.json: -------------------------------------------------------------------------------- 1 | { 2 | "UserAgent": "aws-amplify-cli/2.0", 3 | "Version": "1.0", 4 | "analytics": { 5 | "plugins": { 6 | "awsClickstreamPlugin": { 7 | "appId": "uba-app", 8 | "endpoint": "http://localhost:8082/collect/success", 9 | "isCompressEvents": false, 10 | "autoFlushEventsInterval": 1000, 11 | "isTrackAppExceptionEvents": false 12 | } 13 | } 14 | } 15 | } -------------------------------------------------------------------------------- /clickstream/src/test/resources/robolectric.properties: -------------------------------------------------------------------------------- 1 | # set enable RobolectricTestRunner 2 | sdk=28 3 | -------------------------------------------------------------------------------- /configuration/checkstyle-suppressions.xml: -------------------------------------------------------------------------------- 1 | 2 | 16 | 17 | 18 | 21 | 22 | 28 | 29 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /configuration/checkstyle.gradle: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). 5 | * You may not use this file except in compliance with the License. 6 | * A copy of the License is located at 7 | * 8 | * http://aws.amazon.com/apache2.0 9 | * 10 | * or in the "license" file accompanying this file. This file is distributed 11 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 12 | * express or implied. See the License for the specific language governing 13 | * permissions and limitations under the License. 14 | */ 15 | 16 | apply plugin: "checkstyle" 17 | 18 | task checkstyle(type: Checkstyle) { 19 | description = "Check Java style with Checkstyle" 20 | configFile = rootProject.file("configuration/checkstyle-rules.xml") 21 | configDirectory = rootProject.file("configuration") 22 | source = javaSources() 23 | classpath = files() 24 | ignoreFailures = false 25 | showViolations = true 26 | } 27 | 28 | checkstyle { 29 | toolVersion = '8.29' 30 | } 31 | 32 | afterEvaluate { 33 | check.dependsOn 'checkstyle' 34 | } 35 | 36 | def javaSources() { 37 | def files = [] 38 | android.sourceSets.each { sourceSet -> 39 | sourceSet.java.each { javaSource -> 40 | javaSource.getSrcDirs().each { 41 | if (it.exists()) { 42 | files.add(it) 43 | } 44 | } 45 | } 46 | } 47 | return files 48 | } 49 | 50 | -------------------------------------------------------------------------------- /configuration/consumer-rules.pro: -------------------------------------------------------------------------------- 1 | -keepclassmembers enum * { *; } 2 | 3 | -keep class com.amazonaws.** { *; } 4 | -keep class com.amplifyframework.** { *; } 5 | -------------------------------------------------------------------------------- /configuration/java.header: -------------------------------------------------------------------------------- 1 | ^/\*$ 2 | ^ \* Copyright \d{4} Amazon\.com, Inc\. or its affiliates\. All Rights Reserved\.$ 3 | ^ \*$ 4 | ^ \* Licensed under the Apache License, Version 2\.0 \(the \"License\"\)\.$ 5 | ^ \* You may not use this file except in compliance with the License\.$ 6 | ^ \* A copy of the License is located at$ 7 | ^ \*$ 8 | ^ \* http://aws\.amazon\.com/apache2\.0$ 9 | ^ \*$ 10 | ^ \* or in the \"license\" file accompanying this file\. This file is distributed$ 11 | ^ \* on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either$ 12 | ^ \* express or implied\. See the License for the specific language governing$ 13 | ^ \* permissions and limitations under the License\.$ 14 | ^ \*/$ 15 | 16 | -------------------------------------------------------------------------------- /deployment/build-open-source-dist.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | # SPDX-License-Identifier: Apache-2.0 4 | # 5 | # This script packages your project into an open-source solution distributable 6 | # that can be published to sites like GitHub. 7 | # 8 | # Important notes and prereq's: 9 | # 1. This script should be run from the repo's /deployment folder. 10 | # 11 | # This script will perform the following tasks: 12 | # 1. Remove any old dist files from previous runs. 13 | # 2. Package the GitHub contribution and pull request templates (typically 14 | # found in the /.github folder). 15 | # 3. Package the /source folder along with the necessary root-level 16 | # open-source artifacts (i.e. CHANGELOG, etc.). 17 | # 4. Remove any unecessary artifacts from the /open-source folder (i.e. 18 | # node_modules, package-lock.json, etc.). 19 | # 5. Zip up the /open-source folder and create the distributable. 20 | # 6. Remove any temporary files used for staging. 21 | # 22 | # Parameters: 23 | # - solution-name: name of the solution for consistency 24 | 25 | # Check to see if the required parameters have been provided: 26 | if [ -z "$1" ]; then 27 | echo "Please provide the trademark approved solution name for the open source package." 28 | echo "For example: ./build-open-source-dist.sh trademarked-solution-name" 29 | exit 1 30 | fi 31 | 32 | # Get reference for all important folders 33 | source_template_dir="$PWD" 34 | dist_dir="$source_template_dir/open-source" 35 | source_dir="$source_template_dir/../" 36 | github_dir="$source_template_dir/../.github" 37 | 38 | echo "------------------------------------------------------------------------------" 39 | echo "[Init] ensure open-source folder exists" 40 | echo "------------------------------------------------------------------------------" 41 | rm -rf $dist_dir 42 | mkdir -p $dist_dir 43 | 44 | echo "------------------------------------------------------------------------------" 45 | echo "[Packing] all source files" 46 | echo "------------------------------------------------------------------------------" 47 | rsync -av \ 48 | --exclude='../build' \ 49 | --exclude='../.DS_Store' \ 50 | --exclude='../.git' \ 51 | --exclude='../.idea' \ 52 | --exclude='../.gradle' \ 53 | --exclude='../buildspec.yml' \ 54 | --exclude='../clickstream/build' \ 55 | --exclude='../integrationtest/.idea' \ 56 | --exclude='../deployment/open-source' \ 57 | $source_dir $dist_dir 58 | 59 | echo "------------------------------------------------------------------------------" 60 | echo "[Packing] Create GitHub (open-source) zip file" 61 | echo "------------------------------------------------------------------------------" 62 | 63 | # Create the zip file 64 | echo "cd $dist_dir" 65 | cd $dist_dir 66 | echo "zip -q -r9 ../$1.zip ." 67 | zip -q -r9 ../$1.zip . 68 | 69 | # Cleanup any temporary/unnecessary files 70 | echo "Clean up open-source folder" 71 | echo "rm -rf open-source/" 72 | rm -rf * .* 73 | 74 | # Place final zip file in $dist_dir 75 | echo "mv ../$1.zip ." 76 | mv ../$1.zip . 77 | 78 | echo "Completed building $1.zip dist" 79 | -------------------------------------------------------------------------------- /deployment/build-s3-dist.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | # SPDX-License-Identifier: Apache-2.0 4 | 5 | # This assumes all of the OS-level configuration has been completed and git repo has already been cloned 6 | # 7 | # This script should be run from the repo's deployment directory 8 | # cd deployment 9 | # ./build-s3-dist.sh source-bucket-base-name solution-name version-code 10 | # 11 | # Paramenters: 12 | # - source-bucket-base-name: Name for the S3 bucket location where the template will source the Lambda 13 | # code from. The template will append '-[region_name]' to this bucket name. 14 | # For example: ./build-s3-dist.sh solutions v1.0.2 15 | # The template will then expect the source code to be located in the solutions-[region_name] bucket 16 | # 17 | # - solution-name: name of the solution for consistency 18 | # 19 | # - version-code: version of the package 20 | 21 | # set -euo pipefail 22 | 23 | # Check to see if input has been provided: 24 | if [ -z "$1" ] || [ -z "$2" ] || [ -z "$3" ]; then 25 | echo "Please provide the base source bucket name, trademark approved solution name and version where the lambda code will eventually reside." 26 | echo "For example: ./build-s3-dist.sh solutions trademarked-solution-name v1.1.0" 27 | exit 1 28 | fi 29 | 30 | SOLUTION_ID="SO0321" 31 | 32 | set -x 33 | 34 | # Get reference for all important folders 35 | template_dir="$PWD" 36 | template_dist_dir="$template_dir/global-s3-assets" 37 | build_dist_dir="$template_dir/regional-s3-assets" 38 | 39 | echo "------------------------------------------------------------------------------" 40 | echo "[Init] Clean output folders" 41 | echo "------------------------------------------------------------------------------" 42 | rm -rf $template_dist_dir 43 | mkdir -p $template_dist_dir 44 | rm -rf $build_dist_dir 45 | mkdir -p $build_dist_dir 46 | 47 | echo "------------------------------------------------------------------------------" 48 | echo "[Init] Add global-s3-assets and regional-s3-assets README files" 49 | echo "------------------------------------------------------------------------------" 50 | echo "This folder is intentionally empty because this solution is a code-only SDK." >$template_dist_dir/README.txt 51 | echo "This folder is intentionally empty because this solution is a code-only SDK." >$build_dist_dir/README.txt 52 | 53 | exit 0 -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | # IDE (e.g. Android Studio) users: 3 | # Gradle settings configured through the IDE *will override* 4 | # any settings specified in this file. 5 | # For more details on how to configure your build environment visit 6 | # http://www.gradle.org/docs/current/userguide/build_environment.html 7 | # Specifies the JVM arguments used for the daemon process. 8 | # The setting is particularly useful for tweaking memory settings. 9 | org.gradle.jvmargs=-Xmx4g 10 | # When configured, Gradle will run in incubating parallel mode. 11 | # This option should only be used with decoupled projects. More details, visit 12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 13 | org.gradle.parallel=true 14 | VERSION_NAME=1.0.0 15 | POM_GROUP=software.aws.solution 16 | POM_ARTIFACT_ID=clickstream 17 | POM_NAME=clickstream analytics on aws android sdk 18 | POM_DESCRIPTION=android sdk for clickstream analytics on aws to collect and send event 19 | POM_URL=https://github.com/aws-solutions/clickstream-analytics-on-aws-android-sdk 20 | POM_SCM_URL=https://github.com/aws-solutions/clickstream-analytics-on-aws-android-sdk 21 | POM_SCM_CONNECTION=scm:git:git://github.com/aws-solutions/clickstream-analytics-on-aws-android-sdk.git 22 | POM_SCM_DEV_CONNECTION=scm:git:ssh://github.com/aws-solutions/clickstream-analytics-on-aws-android-sdk.git 23 | POM_DEVELOPER_ID=aws-solutions-admar 24 | POM_DEVELOPER_ORGANIZATION_URL=https://aws.amazon.com/solutions/ 25 | VERSION_CODE=1 26 | android.useAndroidX=true 27 | android.enableJetifier=false 28 | 29 | ossrhUsername=ENTER_OSSRH_USERNAME 30 | ossrhPassword=ENTER_OSSRH_PASSWORD 31 | 32 | signing.keyId=ENTER_GPG_KEY_ID 33 | signing.password=ENTER_GPG_PASSWORD 34 | signing.secretKeyRingFile=ENTER_ABSOLUTE_PATH_TO_GPG_SECRET_KEY_RING_FILE -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-solutions/clickstream-analytics-on-aws-android-sdk/53ed68f7e1d49cdc0015940541dc87724c9cc7b0/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.11-bin.zip 4 | networkTimeout=10000 5 | validateDistributionUrl=true 6 | zipStoreBase=GRADLE_USER_HOME 7 | zipStorePath=wrapper/dists 8 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | @rem SPDX-License-Identifier: Apache-2.0 17 | @rem 18 | 19 | @if "%DEBUG%"=="" @echo off 20 | @rem ########################################################################## 21 | @rem 22 | @rem Gradle startup script for Windows 23 | @rem 24 | @rem ########################################################################## 25 | 26 | @rem Set local scope for the variables with windows NT shell 27 | if "%OS%"=="Windows_NT" setlocal 28 | 29 | set DIRNAME=%~dp0 30 | if "%DIRNAME%"=="" set DIRNAME=. 31 | @rem This is normally unused 32 | set APP_BASE_NAME=%~n0 33 | set APP_HOME=%DIRNAME% 34 | 35 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 36 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 37 | 38 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 39 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 40 | 41 | @rem Find java.exe 42 | if defined JAVA_HOME goto findJavaFromJavaHome 43 | 44 | set JAVA_EXE=java.exe 45 | %JAVA_EXE% -version >NUL 2>&1 46 | if %ERRORLEVEL% equ 0 goto execute 47 | 48 | echo. 1>&2 49 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 50 | echo. 1>&2 51 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 52 | echo location of your Java installation. 1>&2 53 | 54 | goto fail 55 | 56 | :findJavaFromJavaHome 57 | set JAVA_HOME=%JAVA_HOME:"=% 58 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 59 | 60 | if exist "%JAVA_EXE%" goto execute 61 | 62 | echo. 1>&2 63 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 64 | echo. 1>&2 65 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 66 | echo location of your Java installation. 1>&2 67 | 68 | goto fail 69 | 70 | :execute 71 | @rem Setup the command line 72 | 73 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 74 | 75 | 76 | @rem Execute Gradle 77 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 78 | 79 | :end 80 | @rem End local scope for the variables with windows NT shell 81 | if %ERRORLEVEL% equ 0 goto mainEnd 82 | 83 | :fail 84 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 85 | rem the _cmd.exe /c_ return code! 86 | set EXIT_CODE=%ERRORLEVEL% 87 | if %EXIT_CODE% equ 0 set EXIT_CODE=1 88 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% 89 | exit /b %EXIT_CODE% 90 | 91 | :mainEnd 92 | if "%OS%"=="Windows_NT" endlocal 93 | 94 | :omega 95 | -------------------------------------------------------------------------------- /images/raw_folder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-solutions/clickstream-analytics-on-aws-android-sdk/53ed68f7e1d49cdc0015940541dc87724c9cc7b0/images/raw_folder.png -------------------------------------------------------------------------------- /integrationtest/appium/shopping_test.py: -------------------------------------------------------------------------------- 1 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: Apache-2.0 3 | import pytest 4 | from time import sleep 5 | 6 | from appium import webdriver 7 | from appium.options.android import UiAutomator2Options 8 | from appium.webdriver.common.appiumby import AppiumBy 9 | from selenium.common.exceptions import NoSuchElementException 10 | 11 | capabilities = dict( 12 | platformName='Android', 13 | automationName='uiautomator2', 14 | deviceName='Android', 15 | appPackage='com.kanyideveloper.joomia', 16 | appActivity='.core.presentation.MainActivity', 17 | language='en', 18 | locale='US', 19 | ) 20 | 21 | appium_server_url = 'http://0.0.0.0:4723/wd/hub' 22 | 23 | 24 | class TestShopping: 25 | def setup(self): 26 | self.driver = webdriver.Remote(appium_server_url, options=UiAutomator2Options().load_capabilities(capabilities)) 27 | self.driver.implicitly_wait(10) 28 | 29 | def teardown(self): 30 | if self.driver: 31 | self.driver.quit() 32 | 33 | @pytest.mark.parametrize("user_name,password", [ 34 | ("user1", "password1"), 35 | ("user2", "password2"), 36 | ]) 37 | def test_shopping(self, user_name, password): 38 | # login 39 | username_et = self.find_element("userName") 40 | username_et.send_keys(user_name) 41 | password_et = self.find_element("password") 42 | password_et.send_keys(password) 43 | signin_bt = self.find_element("signIn") 44 | signin_bt.click() 45 | sleep(3) 46 | 47 | # add 2 products to cart 48 | product_1 = self.find_element('product0') 49 | product_1.click() 50 | sleep(2) 51 | add_to_cart1 = self.find_element('add_to_cart_button') 52 | add_to_cart1.click() 53 | sleep(2) 54 | self.driver.press_keycode(4) 55 | sleep(2) 56 | product_2 = self.find_element('product1') 57 | product_2.click() 58 | sleep(2) 59 | add_to_cart2 = self.find_element('add_to_cart_button') 60 | add_to_cart2.click() 61 | sleep(2) 62 | self.driver.press_keycode(4) 63 | sleep(2) 64 | 65 | # add 1 product to wishlist 66 | product_3 = self.find_element('product2') 67 | product_3.click() 68 | sleep(2) 69 | like_button = self.find_element('like_button') 70 | like_button.click() 71 | sleep(2) 72 | self.driver.press_keycode(4) 73 | sleep(2) 74 | wishlist_tab = self.find_element('homeTab1') 75 | wishlist_tab.click() 76 | sleep(2) 77 | 78 | cart_tab = self.find_element('homeTab2') 79 | cart_tab.click() 80 | sleep(2) 81 | checkout_bt = self.find_element('check_out_button') 82 | checkout_bt.click() 83 | sleep(2) 84 | 85 | profile_tab = self.find_element('homeTab3') 86 | profile_tab.click() 87 | sleep(2) 88 | sign_out_bt = self.find_element('sign_out_button') 89 | sign_out_bt.click() 90 | sleep(2) 91 | self.driver.press_keycode(3) 92 | sleep(5) 93 | 94 | def find_element(self, name): 95 | try: 96 | return self.driver.find_element(AppiumBy.ANDROID_UIAUTOMATOR, 97 | value='new UiSelector().resourceId("' + name + '")') 98 | except NoSuchElementException: 99 | pytest.skip(f"Element with name: '{name}' not found. Skipped the test") 100 | 101 | 102 | if __name__ == '__main__': 103 | TestShopping.test_shopping() 104 | -------------------------------------------------------------------------------- /integrationtest/requirements.txt: -------------------------------------------------------------------------------- 1 | Appium-Python-Client~=3.1.1 2 | pytest~=7.4.3 3 | boto3~=1.34.11 4 | requests~=2.32.3 5 | PyYAML~=6.0.1 6 | pytest-html~=4.1.1 7 | selenium~=4.17.2 -------------------------------------------------------------------------------- /jacoco.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'jacoco' 2 | 3 | jacoco { 4 | toolVersion = "0.8.11" 5 | reportsDirectory = layout.projectDirectory.dir("reports/jacoco") 6 | } 7 | 8 | afterEvaluate { project -> 9 | setupAndroidReporting(project) 10 | } 11 | 12 | ext { 13 | getFileFilter = { -> 14 | def jacocoSkipClasses = null 15 | if (project.hasProperty('jacocoSkipClasses')) { 16 | jacocoSkipClasses = project.property('jacocoSkipClasses') 17 | } 18 | def fileFilter = ['**/R.class', '**/R$*.class', 19 | '**/BuildConfig.*', '**/Manifest*.*', '**/*$ViewInjector*.*'] 20 | if (jacocoSkipClasses != null) { 21 | fileFilter.addAll(jacocoSkipClasses) 22 | } 23 | return fileFilter 24 | } 25 | } 26 | 27 | def setupAndroidReporting(Project currentProject) { 28 | tasks.withType(Test) { 29 | jacoco.includeNoLocationClasses = true 30 | jacoco.excludes = ['jdk.internal.*'] 31 | } 32 | task jacocoTestReport( 33 | type: JacocoReport, 34 | dependsOn: [ 35 | 'testDebugUnitTest' 36 | ]) { 37 | reports { 38 | xml.required = true 39 | html.required = true 40 | } 41 | 42 | final def coverageSourceDirs = [ 43 | "$projectDir/src/main/java" 44 | ] 45 | final def javaDebugTree = fileTree( 46 | dir: "$buildDir/intermediates/javac/debug/compileDebugJavaWithJavac/classes", 47 | ) 48 | sourceDirectories.from = files(coverageSourceDirs) 49 | classDirectories.from = files(javaDebugTree) 50 | executionData.from = fileTree( 51 | dir: project.layout.buildDirectory, 52 | includes: ['jacoco/testDebugUnitTest.exec'] 53 | ) 54 | } 55 | } -------------------------------------------------------------------------------- /publishing.gradle: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). 5 | * You may not use this file except in compliance with the License. 6 | * A copy of the License is located at 7 | * 8 | * http://aws.amazon.com/apache2.0 9 | * 10 | * or in the "license" file accompanying this file. This file is distributed 11 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 12 | * express or implied. See the License for the specific language governing 13 | * permissions and limitations under the License. 14 | */ 15 | 16 | apply plugin: 'signing' 17 | apply plugin: 'maven-publish' 18 | 19 | version = project.ext.VERSION_NAME 20 | group = POM_GROUP 21 | 22 | afterEvaluate { project -> 23 | publishing { 24 | publications { 25 | println "publish ${project.name}" 26 | library(MavenPublication) { 27 | groupId POM_GROUP 28 | artifactId POM_ARTIFACT_ID 29 | version project.ext.VERSION_NAME 30 | 31 | artifact("${buildDir}/outputs/aar/${artifactId}-release.aar") 32 | println "publish ${buildDir}/outputs/aar/${artifactId}-release.aar" 33 | 34 | pom { 35 | name = POM_NAME 36 | description = POM_DESCRIPTION 37 | url = POM_URL 38 | 39 | scm { 40 | url = POM_SCM_URL 41 | connection = POM_SCM_CONNECTION 42 | developerConnection = POM_SCM_DEV_CONNECTION 43 | } 44 | 45 | licenses { 46 | license { 47 | name = 'The Apache License, Version 2.0' 48 | url = 'https://www.apache.org/licenses/LICENSE-2.0.txt' 49 | distribution = 'repo' 50 | } 51 | } 52 | 53 | developers { 54 | developer { 55 | id = POM_DEVELOPER_ID 56 | organizationUrl = POM_DEVELOPER_ORGANIZATION_URL 57 | roles = ["developer"] 58 | } 59 | } 60 | 61 | withXml { 62 | def dependenciesNode = asNode().appendNode('dependencies') 63 | // Note that this only handles implementation 64 | // dependencies. In the future, may need to add api, 65 | // etc. 66 | configurations.implementation.allDependencies.each { 67 | def dependencyNode = dependenciesNode.appendNode('dependency') 68 | dependencyNode.appendNode('groupId', it.group) 69 | dependencyNode.appendNode('artifactId', it.name) 70 | dependencyNode.appendNode('version', it.version) 71 | } 72 | } 73 | } 74 | } 75 | } 76 | repositories { 77 | maven { 78 | url = "https://aws.oss.sonatype.org/service/local/staging/deploy/maven2/" 79 | credentials (PasswordCredentials){ 80 | username=ossrhUsername 81 | password=ossrhPassword 82 | } 83 | } 84 | } 85 | } 86 | 87 | signing { 88 | sign publishing.publications.library 89 | } 90 | } -------------------------------------------------------------------------------- /release.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | version="$1" 4 | packageName="software.aws.solution:clickstream" 5 | regex="[0-9]\+\.[0-9]\+\.[0-9]\+" 6 | 7 | sed -i "s/${packageName}:${regex}/${packageName}:${version}/g" README.md 8 | sed -i "s/VERSION_NAME=${regex}/VERSION_NAME=${version}/g" gradle.properties 9 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). 5 | * You may not use this file except in compliance with the License. 6 | * A copy of the License is located at 7 | * 8 | * http://aws.amazon.com/apache2.0 9 | * 10 | * or in the "license" file accompanying this file. This file is distributed 11 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 12 | * express or implied. See the License for the specific language governing 13 | * permissions and limitations under the License. 14 | */ 15 | pluginManagement { 16 | repositories { 17 | mavenCentral() 18 | gradlePluginPortal() 19 | google() 20 | } 21 | } 22 | 23 | plugins { 24 | id 'com.android.application' version '8.7.3' apply false 25 | id 'com.android.library' version '8.7.3' apply false 26 | id 'org.jetbrains.kotlin.android' version '2.1.0' apply false 27 | } 28 | 29 | include ':clickstream' 30 | -------------------------------------------------------------------------------- /solution-manifest.yaml: -------------------------------------------------------------------------------- 1 | id: SO0321 2 | name: clickstream-analytics-on-aws-android-sdk 3 | version: v1.0.0 4 | cloudformation_templates: [] 5 | build_environment: 6 | build_image: 'aws/codebuild/standard:7.0' 7 | -------------------------------------------------------------------------------- /sonar-project.properties: -------------------------------------------------------------------------------- 1 | # Customize sonar.sources, sonar.exclusions, sonar.coverage.exclusions, sonar.tests and sonar 2 | # unit test coverage reports based on your solutions 3 | 4 | # Refer to https://docs.sonarqube.org/latest/project-administration/narrowing-the-focus/ 5 | # for details on sources and exclusions. Note also .gitignore 6 | # 7 | sonar.sources=clickstream/src/main 8 | sonar.language=java 9 | 10 | # required Java compiled bytecode files 11 | sonar.java.binaries=clickstream/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes 12 | 13 | 14 | # Focusing sonarqube analysis on non test code first and reducing noise from analysis of test code. Projects 15 | # can customize the exclusions to include analyzing of test code if desired 16 | sonar.exclusions=**/R.class,**/R$*.class,**/BuildConfig.*,**/Manifest*.*,**/*Test*.*,**/gen/**,clickstream/src/test/**/* 17 | 18 | # Code coverage Specific Properties 19 | #sonar.coverage.exclusions=development/**,gradle/**,images/**,configuration/**,integrationtest/** 20 | sonar.coverage.jacoco.xmlReportPaths=clickstream/reports/jacoco/jacocoTestReport/jacocoTestReport.xml 21 | sonar.cpd.exclusions=clickstream/src/main/**/* 22 | 23 | # Encoding of the source files 24 | sonar.sourceEncoding=UTF-8 25 | --------------------------------------------------------------------------------