├── .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