├── .env.example
├── .github
├── dependabot.yml
└── workflows
│ ├── auto_merge_dependabot_pr.yml
│ ├── build_docs.yml
│ ├── codeql.yml
│ ├── nightly_e2e_workflow.yml
│ ├── nightly_workflow.yml
│ ├── on_push_workflow.yml
│ ├── on_tag_workflow.yml
│ └── release_sample_app_workflow.yml
├── .gitignore
├── .reuse
└── dep5
├── Dockerfile
├── Dockerfile.arm64
├── Dockerfile.codescan
├── LICENSE
├── LICENSES
└── MPL-2.0.txt
├── Makefile
├── README.md
├── build.gradle.kts
├── buildSrc
├── build.gradle.kts
└── src
│ └── main
│ └── java
│ └── GitVersion.kt
├── changelog.md
├── common
├── build.gradle.kts
└── src
│ ├── androidTest
│ └── java
│ │ └── com
│ │ └── emarsys
│ │ └── common
│ │ └── feature
│ │ └── InnerFeatureTest.kt
│ └── main
│ ├── AndroidManifest.xml
│ └── java
│ └── com
│ └── emarsys
│ └── common
│ └── feature
│ └── InnerFeature.kt
├── core-api
├── build.gradle.kts
└── src
│ ├── androidTest
│ └── java
│ │ └── com
│ │ └── emarsys
│ │ └── core
│ │ └── api
│ │ └── ApiTest.kt
│ └── main
│ ├── AndroidManifest.xml
│ └── java
│ └── com
│ └── emarsys
│ └── core
│ └── api
│ ├── EmarsysIdlingResources.kt
│ ├── MissingPermissionException.kt
│ ├── ResponseErrorException.kt
│ ├── experimental
│ └── FlipperFeature.kt
│ ├── notification
│ ├── ChannelSettings.kt
│ └── NotificationSettings.kt
│ └── result
│ ├── CompletionListener.kt
│ ├── ResultListener.kt
│ └── Try.kt
├── core
├── build.gradle.kts
├── proguard-rules.pro
└── src
│ ├── androidTest
│ ├── AndroidManifest.xml
│ ├── java
│ │ └── com
│ │ │ └── emarsys
│ │ │ └── core
│ │ │ ├── DefaultCoreCompletionHandlerTest.kt
│ │ │ ├── DeviceInfoTest.kt
│ │ │ ├── activity
│ │ │ ├── ActivityLifecycleActionRegistryTest.kt
│ │ │ └── ActivityLifecycleWatchdogTest.kt
│ │ │ ├── api
│ │ │ ├── AsyncProxyTest.kt
│ │ │ └── LogExceptionProxyTest.kt
│ │ │ ├── app
│ │ │ └── AppLifecycleObserverTest.kt
│ │ │ ├── concurrency
│ │ │ ├── ConcurrentHandlerHolderFactoryTest.kt
│ │ │ └── CoreHandlerTest.kt
│ │ │ ├── connection
│ │ │ ├── ConnectionProviderTest.kt
│ │ │ ├── ConnectionWatchDogTest.kt
│ │ │ ├── ConnectivityChangeReceiverTest.kt
│ │ │ └── Connectivity_getConnectionState_ParameterizedTest.kt
│ │ │ ├── contentresolver
│ │ │ └── clientid
│ │ │ │ └── ClientIdContentResolverTest.kt
│ │ │ ├── crypto
│ │ │ ├── ClientIdentificationCryptoTest.kt
│ │ │ ├── CryptoTest.kt
│ │ │ └── SharedPreferenceCryptoTest.kt
│ │ │ ├── database
│ │ │ ├── DelegatingCoreSQLiteDatabaseTest.kt
│ │ │ ├── DelegatingCoreSQLiteDatabase_registerTrigger_parameterizedTest.kt
│ │ │ ├── DelegatingCoreSQLiteDatabase_triggerRecursion_parameterizedTest.kt
│ │ │ ├── helper
│ │ │ │ ├── AbstractDbHelperTest.kt
│ │ │ │ └── CoreDbHelperTest.kt
│ │ │ └── repository
│ │ │ │ ├── AbstractSqliteRepositoryTest.kt
│ │ │ │ └── specification
│ │ │ │ ├── EverythingTest.kt
│ │ │ │ ├── FilterByRequestIdsTest.kt
│ │ │ │ └── QueryLatestRequestModelTest.kt
│ │ │ ├── device
│ │ │ └── LanguageProviderTest.kt
│ │ │ ├── di
│ │ │ └── FakeCoreDependencyContainer.kt
│ │ │ ├── endpoint
│ │ │ └── ServiceEndpointProviderTest.kt
│ │ │ ├── fake
│ │ │ ├── FakeCompletionHandler.java
│ │ │ ├── FakeConnectionChangeListener.java
│ │ │ ├── FakeConnectionWatchDog.kt
│ │ │ ├── FakeRequestTask.java
│ │ │ ├── FakeRestClient.java
│ │ │ └── FakeRunnableFactory.java
│ │ │ ├── feature
│ │ │ └── FeatureRegistryTest.kt
│ │ │ ├── notification
│ │ │ └── NotificationManagerHelperTest.kt
│ │ │ ├── provider
│ │ │ ├── activity
│ │ │ │ └── CurrentActivityProviderTest.kt
│ │ │ ├── clientid
│ │ │ │ └── ClientIdProviderTest.kt
│ │ │ ├── random
│ │ │ │ └── RandomProviderTest.kt
│ │ │ ├── timestamp
│ │ │ │ └── TimestampProviderTest.kt
│ │ │ ├── uuid
│ │ │ │ └── UUIDProviderTest.kt
│ │ │ └── version
│ │ │ │ └── VersionProviderTest.kt
│ │ │ ├── request
│ │ │ ├── RequestManagerDennaTest.kt
│ │ │ ├── RequestManagerOfflineTest.kt
│ │ │ ├── RequestManagerTest.kt
│ │ │ ├── RequestTaskTest.kt
│ │ │ ├── RestClientTest.kt
│ │ │ ├── factory
│ │ │ │ └── CoreCompletionHandlerMiddlewareProviderTest.kt
│ │ │ └── model
│ │ │ │ ├── CompositeRequestModelTest.kt
│ │ │ │ ├── RequestModelRepositoryTest.kt
│ │ │ │ ├── RequestModelTest.kt
│ │ │ │ ├── RequestResult.kt
│ │ │ │ └── specification
│ │ │ │ └── FilterByUrlPatternTest.kt
│ │ │ ├── resource
│ │ │ └── MetaDataReaderTest.kt
│ │ │ ├── response
│ │ │ ├── AbstractResponseHandlerTest.kt
│ │ │ ├── ResponseHandlersProcessorTest.kt
│ │ │ └── ResponseModelTest.kt
│ │ │ ├── shard
│ │ │ ├── ShardModelRepositoryTest.kt
│ │ │ ├── ShardModelTest.kt
│ │ │ └── specification
│ │ │ │ ├── FilterByShardIdsTest.kt
│ │ │ │ └── FilterByShardTypeTest.kt
│ │ │ ├── storage
│ │ │ ├── AbstractStorageTest.kt
│ │ │ ├── BooleanStorageTest.kt
│ │ │ ├── CoreStorageKeyTest.kt
│ │ │ ├── DefaultKeyValueStoreTest.kt
│ │ │ ├── EmarsysEncryptedSharedPreferencesV3Test.kt
│ │ │ ├── EncryptedSharedPreferencesToSharedPreferencesMigrationTest.kt
│ │ │ ├── SecureSharedPreferencesProviderTest.kt
│ │ │ ├── SharedPreferencesV3ProviderTest.kt
│ │ │ └── StringStorageTest.kt
│ │ │ ├── testUtil
│ │ │ └── RequestModelTestUtils.kt
│ │ │ ├── util
│ │ │ ├── AssertTest.kt
│ │ │ ├── DatabaseUtilTest.kt
│ │ │ ├── ExceptionExtensionsKtTest.kt
│ │ │ ├── FileDownloaderTest.kt
│ │ │ ├── HeaderUtilsInstrumentationTest.kt
│ │ │ ├── ImageUtilsTest.kt
│ │ │ ├── JsonUtilsTest.kt
│ │ │ ├── MapExtensionsKtTest.kt
│ │ │ ├── RequestModelUtilsTest.kt
│ │ │ ├── SerializationUtilsTest.kt
│ │ │ ├── SystemUtilsTest.kt
│ │ │ ├── TimestampUtilsTest.kt
│ │ │ ├── batch
│ │ │ │ ├── BatchingShardTriggerTest.kt
│ │ │ │ └── ListChunkerTest.kt
│ │ │ ├── log
│ │ │ │ ├── LogShardListMergerTest.kt
│ │ │ │ ├── LoggerTest.kt
│ │ │ │ └── entry
│ │ │ │ │ ├── AppEventLogTest.kt
│ │ │ │ │ ├── CrashLogTest.kt
│ │ │ │ │ ├── InAppLogTest.kt
│ │ │ │ │ ├── LogEntryKtTest.kt
│ │ │ │ │ ├── MethodNotAlllowedTest.kt
│ │ │ │ │ ├── OfflineQueueSizeTest.kt
│ │ │ │ │ ├── RequestLogTest.kt
│ │ │ │ │ └── StatusLogTest.kt
│ │ │ └── predicate
│ │ │ │ └── ListSizeAtLeastTest.kt
│ │ │ ├── validate
│ │ │ └── JsonObjectValidatorTest.kt
│ │ │ └── worker
│ │ │ ├── CoreCompletionHandlerMiddlewareTest.kt
│ │ │ └── DefaultWorkerTest.kt
│ └── res
│ │ └── raw
│ │ └── emarsys_test_image.png
│ └── main
│ ├── AndroidManifest.xml
│ └── java
│ └── com
│ └── emarsys
│ ├── EmarsysSdkInitializer.kt
│ └── core
│ ├── Callable.kt
│ ├── CoreCompletionHandler.kt
│ ├── DefaultCoreCompletionHandler.kt
│ ├── Mapper.kt
│ ├── Mockable.kt
│ ├── Registry.kt
│ ├── activity
│ ├── ActivityLifecycleAction.kt
│ ├── ActivityLifecycleActionRegistry.kt
│ ├── ActivityLifecyclePriorities.kt
│ ├── ActivityLifecycleWatchdog.kt
│ └── TransitionSafeCurrentActivityWatchdog.kt
│ ├── api
│ ├── ApiProxy.kt
│ ├── AsyncProxy.kt
│ └── LogExceptionProxy.kt
│ ├── app
│ └── AppLifecycleObserver.kt
│ ├── concurrency
│ ├── ConcurrentHandlerHolderFactory.kt
│ └── CoreHandler.kt
│ ├── connection
│ ├── ConnectionChangeListener.kt
│ ├── ConnectionProvider.kt
│ ├── ConnectionState.kt
│ ├── ConnectionWatchDog.kt
│ └── ConnectivityChangeReceiver.kt
│ ├── contentresolver
│ ├── EmarsysContentResolver.kt
│ └── clientid
│ │ └── ClientIdContentResolver.kt
│ ├── crypto
│ ├── ClientIdentificationCrypto.kt
│ ├── Crypto.kt
│ └── SharedPreferenceCrypto.kt
│ ├── database
│ ├── CoreSQLiteDatabase.kt
│ ├── DatabaseContract.kt
│ ├── DelegatingCoreSQLiteDatabase.kt
│ ├── helper
│ │ ├── AbstractDbHelper.kt
│ │ ├── CoreDbHelper.kt
│ │ └── DbHelper.kt
│ ├── repository
│ │ ├── AbstractSqlSpecification.kt
│ │ ├── AbstractSqliteRepository.kt
│ │ ├── Repository.kt
│ │ ├── SqlSpecification.kt
│ │ └── specification
│ │ │ └── Everything.kt
│ └── trigger
│ │ ├── TriggerEvent.kt
│ │ ├── TriggerKey.kt
│ │ └── TriggerType.kt
│ ├── device
│ ├── ClientIdentification.kt
│ ├── ClientRepository.kt
│ ├── DeviceInfo.kt
│ ├── FilterByClientId.kt
│ └── LanguageProvider.java
│ ├── di
│ ├── Container.kt
│ └── CoreComponent.kt
│ ├── endpoint
│ ├── Endpoint.kt
│ └── ServiceEndpointProvider.kt
│ ├── feature
│ └── FeatureRegistry.kt
│ ├── handler
│ ├── ConcurrentHandlerHolder.kt
│ └── SdkHandler.kt
│ ├── notification
│ ├── NotificationManagerHelper.kt
│ └── NotificationManagerProxy.kt
│ ├── observer
│ └── Observer.kt
│ ├── permission
│ └── PermissionChecker.kt
│ ├── provider
│ ├── Gettable.java
│ ├── Property.java
│ ├── Settable.java
│ ├── activity
│ │ ├── CurrentActivityProvider.kt
│ │ └── FallbackActivityProvider.kt
│ ├── clientid
│ │ └── ClientIdProvider.kt
│ ├── random
│ │ └── RandomProvider.kt
│ ├── timestamp
│ │ └── TimestampProvider.java
│ ├── uuid
│ │ └── UUIDProvider.java
│ ├── version
│ │ └── VersionProvider.java
│ └── wrapper
│ │ └── WrapperInfoContainer.kt
│ ├── request
│ ├── RequestExpiredException.java
│ ├── RequestManager.kt
│ ├── RequestTask.kt
│ ├── RestClient.kt
│ ├── factory
│ │ ├── CompletionHandlerProxyProvider.kt
│ │ ├── CoreCompletionHandlerMiddlewareProvider.kt
│ │ ├── DefaultRunnableFactory.java
│ │ └── RunnableFactory.java
│ └── model
│ │ ├── CompositeRequestModel.kt
│ │ ├── RequestMethod.java
│ │ ├── RequestModel.kt
│ │ ├── RequestModelRepository.java
│ │ └── specification
│ │ ├── FilterByRequestIds.kt
│ │ ├── FilterByUrlPattern.java
│ │ └── QueryLatestRequestModel.java
│ ├── resource
│ └── MetaDataReader.java
│ ├── response
│ ├── AbstractResponseHandler.kt
│ ├── ResponseHandlersProcessor.java
│ └── ResponseModel.kt
│ ├── session
│ └── Session.kt
│ ├── shard
│ ├── ShardModel.java
│ ├── ShardModelRepository.kt
│ └── specification
│ │ ├── FilterByShardIds.java
│ │ └── FilterByShardType.java
│ ├── storage
│ ├── AbstractStorage.java
│ ├── BooleanStorage.kt
│ ├── CoreStorageKey.kt
│ ├── DefaultKeyValueStore.java
│ ├── EmarsysEncryptedSharedPreferencesV3.kt
│ ├── EmarsysSecureSharedPreferences.kt
│ ├── EncryptedSharedPreferencesToSharedPreferencesMigration.kt
│ ├── KeyValueStore.java
│ ├── PersistentStorage.java
│ ├── SecureSharedPreferencesProvider.kt
│ ├── SharedPreferencesV3Provider.kt
│ ├── Storage.java
│ ├── StorageKey.java
│ └── StringStorage.java
│ ├── util
│ ├── AndroidVersionUtils.kt
│ ├── Assert.java
│ ├── CastExtensions.kt
│ ├── DatabaseUtil.java
│ ├── ExceptionExtensions.kt
│ ├── FileDownloader.kt
│ ├── HeaderUtils.java
│ ├── ImageUtils.kt
│ ├── JsonUtils.kt
│ ├── MapExtensions.kt
│ ├── RequestModelUtils.java
│ ├── RetryUtil.kt
│ ├── StringExtensions.kt
│ ├── SystemUtils.java
│ ├── TimestampUtils.java
│ ├── batch
│ │ ├── BatchingShardTrigger.kt
│ │ └── ListChunker.java
│ ├── log
│ │ ├── LogLevel.kt
│ │ ├── LogShardListMerger.kt
│ │ ├── Logger.kt
│ │ └── entry
│ │ │ ├── AppEventLog.kt
│ │ │ ├── CrashLog.kt
│ │ │ ├── InAppLoadingTime.kt
│ │ │ ├── InAppLog.kt
│ │ │ ├── LogEntry.kt
│ │ │ ├── MethodNotAllowed.kt
│ │ │ ├── OfflineQueueSize.kt
│ │ │ ├── OnScreenTime.kt
│ │ │ ├── RequestLog.kt
│ │ │ └── StatusLog.kt
│ ├── predicate
│ │ ├── ListSizeAtLeast.java
│ │ └── Predicate.java
│ └── serialization
│ │ ├── SerializationException.java
│ │ └── SerializationUtils.java
│ ├── validate
│ └── JsonObjectValidator.java
│ └── worker
│ ├── CoreCompletionHandlerMiddleware.kt
│ ├── DefaultWorker.kt
│ ├── DelegatorCompletionHandler.kt
│ ├── DelegatorCompletionHandlerProvider.kt
│ ├── Lockable.kt
│ └── Worker.kt
├── docs
├── antora.yml
└── modules
│ └── ROOT
│ ├── nav.adoc
│ └── pages
│ ├── index.adoc
│ └── push.adoc
├── emarsys-e2e-test
├── build.gradle.kts
└── src
│ ├── androidTest
│ ├── AndroidManifest.xml
│ └── java
│ │ └── com
│ │ └── emarsys
│ │ ├── EmarsysE2ETests.kt
│ │ └── testUtil
│ │ └── E2ETestUtils.kt
│ └── main
│ └── AndroidManifest.xml
├── emarsys-firebase
├── build.gradle.kts
└── src
│ ├── androidTest
│ └── java
│ │ └── com
│ │ └── emarsys
│ │ ├── fake
│ │ └── FakeFirebaseDependencyContainer.kt
│ │ └── service
│ │ ├── EmarsysFirebaseMessagingServiceTest.kt
│ │ └── EmarsysFirebaseMessagingServiceUtilsTest.kt
│ └── main
│ ├── AndroidManifest.xml
│ └── java
│ └── com
│ └── emarsys
│ └── service
│ ├── EmarsysFirebaseMessagingService.kt
│ └── EmarsysFirebaseMessagingServiceUtils.kt
├── emarsys-huawei
├── build.gradle.kts
└── src
│ ├── androidTest
│ └── java
│ │ └── com
│ │ └── emarsys
│ │ ├── fake
│ │ └── FakeHuaweiDependencyContainer.kt
│ │ └── service
│ │ ├── EmarsysHuaweiMessagingServiceTest.kt
│ │ └── EmarsysHuaweiMessagingServiceUtilsTest.kt
│ └── main
│ ├── AndroidManifest.xml
│ └── java
│ └── com
│ └── emarsys
│ ├── HuaweiServiceChecker.kt
│ └── service
│ ├── EmarsysHuaweiMessagingService.kt
│ └── EmarsysHuaweiMessagingServiceUtils.kt
├── emarsys-sdk
├── build.gradle.kts
├── gradle.properties
├── maven.gradle
├── proguard-rules.pro
└── src
│ ├── androidTest
│ ├── AndroidManifest.xml
│ └── java
│ │ └── com
│ │ └── emarsys
│ │ ├── DefaultInboxIntegrationTest.kt
│ │ ├── EmarsysTest.kt
│ │ ├── InAppTest.kt
│ │ ├── InappNotificationIntegrationTest.kt
│ │ ├── MobileEngageIntegrationTest.kt
│ │ ├── MobileEngageRefreshContactTokenIntegrationTest.kt
│ │ ├── PredictIntegrationTest.kt
│ │ ├── PredictTest.kt
│ │ ├── PushTest.kt
│ │ ├── RemoteConfigIntegrationTest.kt
│ │ ├── config
│ │ ├── ConfigLoaderTest.kt
│ │ ├── ConfigTest.kt
│ │ └── EmarsysConfigTest.kt
│ │ ├── deeplink
│ │ └── DeepLinkTest.kt
│ │ ├── di
│ │ ├── EmarsysDependencyInjectionTest.kt
│ │ └── FakeDependencyContainer.kt
│ │ ├── eventservice
│ │ └── EventServiceTest.kt
│ │ ├── fake
│ │ └── FakeRestClient.java
│ │ ├── geofence
│ │ └── GeofenceTest.kt
│ │ ├── inapp
│ │ └── ui
│ │ │ └── InlineInAppViewTest.kt
│ │ ├── inbox
│ │ └── MessageInboxTest.kt
│ │ ├── mobileengage
│ │ ├── MobileEngageTest.kt
│ │ └── service
│ │ │ └── mapper
│ │ │ └── RemoteMessageMapperFactoryTest.kt
│ │ ├── oneventaction
│ │ └── OnEventActionTest.kt
│ │ ├── predict
│ │ └── PredictRestrictedTest.kt
│ │ ├── provider
│ │ └── SharedClientIdentificationContentProviderTest.kt
│ │ └── testUtil
│ │ └── IntegrationTestUtils.kt
│ └── main
│ ├── AndroidManifest.xml
│ ├── java
│ └── com
│ │ └── emarsys
│ │ ├── Emarsys.kt
│ │ ├── NotificationOpenedActivity.kt
│ │ ├── clientservice
│ │ ├── ClientService.kt
│ │ └── ClientServiceApi.kt
│ │ ├── config
│ │ ├── Config.kt
│ │ ├── ConfigApi.kt
│ │ ├── ConfigLoader.kt
│ │ └── EmarsysConfig.kt
│ │ ├── deeplink
│ │ ├── DeepLink.kt
│ │ └── DeepLinkApi.kt
│ │ ├── di
│ │ ├── DefaultEmarsysComponent.kt
│ │ ├── DefaultEmarsysDependencies.kt
│ │ ├── EmarsysComponent.kt
│ │ └── EmarsysDependencyInjection.kt
│ │ ├── eventservice
│ │ ├── EventService.kt
│ │ └── EventServiceApi.kt
│ │ ├── geofence
│ │ ├── Geofence.kt
│ │ ├── GeofenceApi.kt
│ │ └── RegisterGeofencesOnBootCompletedReceiver.kt
│ │ ├── inapp
│ │ ├── InApp.kt
│ │ ├── InAppApi.kt
│ │ └── ui
│ │ │ └── InlineInAppView.kt
│ │ ├── inbox
│ │ ├── InboxTag.kt
│ │ ├── MessageInbox.kt
│ │ └── MessageInboxApi.kt
│ │ ├── mobileengage
│ │ ├── MobileEngage.kt
│ │ └── MobileEngageApi.kt
│ │ ├── oneventaction
│ │ ├── OnEventAction.kt
│ │ └── OnEventActionApi.kt
│ │ ├── predict
│ │ ├── Predict.kt
│ │ ├── PredictApi.kt
│ │ ├── PredictRestricted.kt
│ │ └── PredictRestrictedApi.kt
│ │ ├── provider
│ │ └── SharedHardwareIdentificationContentProvider.kt
│ │ └── push
│ │ ├── Push.kt
│ │ └── PushApi.kt
│ └── res
│ └── values
│ ├── attrs.xml
│ └── styles.xml
├── emarsys
├── build.gradle.kts
└── src
│ ├── androidTest
│ ├── AndroidManifest.xml
│ └── java
│ │ └── com
│ │ └── emarsys
│ │ ├── EmarsysRequestModelFactoryTest.kt
│ │ ├── config
│ │ ├── DefaultConfigInternalTest.kt
│ │ └── RemoteConfigResponseMapperTest.kt
│ │ └── fake
│ │ ├── FakeEmarsysDependencyContainer.kt
│ │ ├── FakeRestClient.kt
│ │ └── FakeResultListener.kt
│ └── main
│ ├── AndroidManifest.xml
│ └── java
│ └── com
│ └── emarsys
│ ├── EmarsysRequestModelFactory.kt
│ └── config
│ ├── ConfigInternal.kt
│ ├── ConfigStorageKeys.kt
│ ├── DefaultConfigInternal.kt
│ ├── RemoteConfigResponseMapper.kt
│ └── model
│ └── RemoteConfig.kt
├── gradle.properties
├── gradle
├── libs.versions.toml
├── release.gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── local-release.sh
├── mobile-engage-api
├── build.gradle.kts
└── src
│ ├── androidTest
│ └── java
│ │ └── com
│ │ └── emarsys
│ │ └── mobileengage
│ │ └── api
│ │ └── MEApiTest.kt
│ └── main
│ ├── AndroidManifest.xml
│ └── java
│ └── com
│ └── emarsys
│ └── mobileengage
│ └── api
│ ├── action
│ └── ActionModel.kt
│ ├── event
│ └── EventHandler.kt
│ ├── geofence
│ ├── Geofence.kt
│ └── Trigger.kt
│ ├── inbox
│ ├── InboxResult.kt
│ └── Message.kt
│ └── push
│ ├── NotificationInformation.kt
│ └── NotificationInformationListener.kt
├── mobile-engage
├── build.gradle.kts
├── gradle.properties
├── lint.xml
├── proguard-rules.pro
└── src
│ ├── androidTest
│ ├── AndroidManifest.xml
│ ├── java
│ │ └── com
│ │ │ └── emarsys
│ │ │ └── mobileengage
│ │ │ ├── DefaultMobileEngageInternalTest.kt
│ │ │ ├── MobileEngageRequestContextTest.kt
│ │ │ ├── client
│ │ │ └── DefaultClientServiceInternalTest.kt
│ │ │ ├── deeplink
│ │ │ ├── DeepLinkActionTest.kt
│ │ │ └── DefaultDeepLinkInternalTest.kt
│ │ │ ├── device
│ │ │ └── DeviceInfoStartActionTest.kt
│ │ │ ├── event
│ │ │ ├── CacheableEventHandlerTest.kt
│ │ │ └── DefaultEventServiceInternalTest.kt
│ │ │ ├── fake
│ │ │ ├── FakeActivityLifecycleCallbacks.kt
│ │ │ ├── FakeCompletionListener.kt
│ │ │ ├── FakeMessageLoadedListener.java
│ │ │ ├── FakeMobileEngageDependencyContainer.kt
│ │ │ ├── FakeRequestManager.kt
│ │ │ ├── FakeResetBadgeCountResultListener.java
│ │ │ ├── FakeRestClient.java
│ │ │ └── FakeResultListener.java
│ │ │ ├── geofence
│ │ │ ├── DefaultGeofenceInternalTest.kt
│ │ │ ├── FakeLocationTask.kt
│ │ │ ├── FetchGeofencesActionTest.kt
│ │ │ ├── GeofenceFilterTest.kt
│ │ │ ├── GeofencePendingIntentProviderTest.kt
│ │ │ └── GeofenceResponseMapperTest.kt
│ │ │ ├── iam
│ │ │ ├── AppStartActionTest.kt
│ │ │ ├── InAppEventHandlerInternalTest.kt
│ │ │ ├── InAppInternalTest.kt
│ │ │ ├── OverlayInAppPresenterTest.kt
│ │ │ ├── PushToInAppActionTest.kt
│ │ │ ├── SaveDisplayedIamActionTest.kt
│ │ │ ├── dialog
│ │ │ │ ├── IamDialogProviderTest.kt
│ │ │ │ ├── IamDialogTest.kt
│ │ │ │ └── action
│ │ │ │ │ └── SendDisplayedIamActionTest.kt
│ │ │ ├── jsbridge
│ │ │ │ ├── IamJsBridgeFactoryTest.kt
│ │ │ │ ├── IamJsBridgeTest.kt
│ │ │ │ ├── JSCommandFactoryProviderTest.kt
│ │ │ │ └── JSCommandFactoryTest.kt
│ │ │ ├── model
│ │ │ │ ├── IamConversionUtilsTest.kt
│ │ │ │ ├── buttonclicked
│ │ │ │ │ └── ButtonClickedRepositoryTest.kt
│ │ │ │ ├── displayediam
│ │ │ │ │ └── DisplayedIamRepositoryTest.kt
│ │ │ │ ├── requestRepositoryProxy
│ │ │ │ │ └── RequestRepositoryProxyTest.kt
│ │ │ │ └── specification
│ │ │ │ │ └── FilterByCampaignIdTest.kt
│ │ │ └── webview
│ │ │ │ ├── IamWebViewClientTest.kt
│ │ │ │ ├── IamWebViewFactoryTest.kt
│ │ │ │ └── IamWebViewTest.kt
│ │ │ ├── inbox
│ │ │ ├── DefaultMessageInboxInternalTest.kt
│ │ │ └── MessageInboxResponseMapperTest.kt
│ │ │ ├── notification
│ │ │ ├── ActionCommandFactoryTest.kt
│ │ │ ├── LaunchActivityCommandLifecycleCallbacksFactoryTest.kt
│ │ │ ├── LaunchActivityCommandLifecycleCallbacksTest.kt
│ │ │ ├── NotificationCommandFactoryTest.kt
│ │ │ └── command
│ │ │ │ ├── AppEventCommandTest.kt
│ │ │ │ ├── CompositeCommandTest.kt
│ │ │ │ ├── CustomEventCommandTest.kt
│ │ │ │ ├── DismissNotificationCommandTest.kt
│ │ │ │ ├── LaunchApplicationCommandTest.kt
│ │ │ │ ├── NotificationInformationCommandTest.kt
│ │ │ │ ├── OpenExternalUrlCommandTest.kt
│ │ │ │ ├── PreloadedInappHandlerCommandTest.kt
│ │ │ │ ├── SilentNotificationInformationCommandTest.kt
│ │ │ │ ├── TrackActionClickCommandTest.kt
│ │ │ │ └── TrackMessageOpenCommandTest.kt
│ │ │ ├── push
│ │ │ ├── DefaultPushInternalTest.kt
│ │ │ └── DefaultPushTokenProviderTest.kt
│ │ │ ├── request
│ │ │ ├── CoreCompletionHandlerRefreshTokenProxyProviderTest.kt
│ │ │ ├── CoreCompletionHandlerRefreshTokenProxyTest.kt
│ │ │ ├── MobileEngageRequestModelFactoryTest.kt
│ │ │ └── mapper
│ │ │ │ ├── ContactTokenHeaderMapperTest.kt
│ │ │ │ ├── DefaultRequestHeaderMapperTest.kt
│ │ │ │ ├── DeviceEventStateRequestMapperTest.kt
│ │ │ │ ├── MobileEngageHeaderMapperTest.kt
│ │ │ │ └── OpenIdTokenRequestMapperTest.kt
│ │ │ ├── responsehandler
│ │ │ ├── ClientInfoResponseHandlerTest.kt
│ │ │ ├── DeviceEventStateResponseHandlerTest.kt
│ │ │ ├── InAppCleanUpResponseHandlerTest.kt
│ │ │ ├── InAppCleanUpResponseHandlerV4Test.kt
│ │ │ ├── InAppMessageResponseHandlerTest.kt
│ │ │ ├── MobileEngageClientStateResponseHandlerTest.kt
│ │ │ ├── MobileEngageTokenResponseHandlerTest.kt
│ │ │ └── OnEventActionResponseHandlerTest.kt
│ │ │ ├── service
│ │ │ ├── IntentUtilsTest.kt
│ │ │ ├── MessagingServiceUtilsTest.kt
│ │ │ ├── NotificationActionUtilsTest.kt
│ │ │ ├── RemoteMessageMapperV1Test.kt
│ │ │ └── RemoteMessageMapperV2Test.kt
│ │ │ ├── session
│ │ │ └── MobileEngageSessionTest.kt
│ │ │ ├── storage
│ │ │ └── MobileEngageStorageKeyTest.kt
│ │ │ ├── testUtil
│ │ │ └── RandomMETestUtils.java
│ │ │ └── util
│ │ │ ├── AsyncSDKUtils.kt
│ │ │ ├── RequestModelHelperTest.kt
│ │ │ └── RequestPayloadUtilsTest.kt
│ └── res
│ │ └── raw
│ │ └── test_image.png
│ └── main
│ ├── AndroidManifest.xml
│ ├── java
│ └── com
│ │ └── emarsys
│ │ └── mobileengage
│ │ ├── DefaultMobileEngageInternal.kt
│ │ ├── LoggingMobileEngageInternal.kt
│ │ ├── MobileEngageInternal.kt
│ │ ├── MobileEngageRequestContext.kt
│ │ ├── client
│ │ ├── ClientServiceInternal.kt
│ │ ├── DefaultClientServiceInternal.kt
│ │ └── LoggingClientServiceInternal.kt
│ │ ├── deeplink
│ │ ├── DeepLinkAction.kt
│ │ ├── DeepLinkInternal.kt
│ │ └── DefaultDeepLinkInternal.kt
│ │ ├── device
│ │ └── DeviceInfoStartAction.kt
│ │ ├── di
│ │ └── MobileEngageComponent.kt
│ │ ├── endpoint
│ │ └── Endpoint.kt
│ │ ├── event
│ │ ├── CacheableEventHandler.kt
│ │ ├── DefaultEventServiceInternal.kt
│ │ ├── EventServiceInternal.kt
│ │ └── LoggingEventServiceInternal.kt
│ │ ├── geofence
│ │ ├── DefaultGeofenceInternal.kt
│ │ ├── FetchGeofencesAction.kt
│ │ ├── GeofenceBroadcastReceiver.kt
│ │ ├── GeofenceFilter.kt
│ │ ├── GeofenceInternal.kt
│ │ ├── GeofencePendingIntentProvider.kt
│ │ ├── GeofenceResponseMapper.kt
│ │ ├── LoggingGeofenceInternal.kt
│ │ └── model
│ │ │ ├── GeofenceGroup.kt
│ │ │ ├── GeofenceResponse.kt
│ │ │ └── TriggeringEmarsysGeofence.kt
│ │ ├── iam
│ │ ├── AppStartAction.kt
│ │ ├── DefaultInAppInternal.java
│ │ ├── InAppEventHandler.kt
│ │ ├── InAppEventHandlerInternal.java
│ │ ├── InAppInternal.kt
│ │ ├── LoggingInAppInternal.kt
│ │ ├── OverlayInAppPresenter.kt
│ │ ├── PushToInAppAction.kt
│ │ ├── dialog
│ │ │ ├── IamDialog.kt
│ │ │ ├── IamDialogProvider.kt
│ │ │ └── action
│ │ │ │ ├── OnDialogShownAction.java
│ │ │ │ ├── SaveDisplayedIamAction.kt
│ │ │ │ └── SendDisplayedIamAction.kt
│ │ ├── jsbridge
│ │ │ ├── IamJsBridge.kt
│ │ │ ├── IamJsBridgeFactory.kt
│ │ │ ├── JSCommand.kt
│ │ │ ├── JSCommandFactory.kt
│ │ │ ├── JSCommandFactoryProvider.kt
│ │ │ ├── OnAppEventListener.kt
│ │ │ ├── OnButtonClickedListener.kt
│ │ │ ├── OnCloseListener.kt
│ │ │ ├── OnMEEventListener.kt
│ │ │ └── OnOpenExternalUrlListener.kt
│ │ ├── model
│ │ │ ├── IamConversionUtils.java
│ │ │ ├── InAppMetaData.kt
│ │ │ ├── buttonclicked
│ │ │ │ ├── ButtonClicked.kt
│ │ │ │ └── ButtonClickedRepository.kt
│ │ │ ├── displayediam
│ │ │ │ ├── DisplayedIam.java
│ │ │ │ └── DisplayedIamRepository.java
│ │ │ ├── requestRepositoryProxy
│ │ │ │ └── RequestRepositoryProxy.kt
│ │ │ └── specification
│ │ │ │ └── FilterByCampaignId.java
│ │ └── webview
│ │ │ ├── IamWebView.kt
│ │ │ ├── IamWebViewClient.kt
│ │ │ ├── IamWebViewCreationFailedException.kt
│ │ │ ├── IamWebViewFactory.kt
│ │ │ ├── MessageLoadedListener.java
│ │ │ └── Provider.kt
│ │ ├── inbox
│ │ ├── DefaultMessageInboxInternal.kt
│ │ ├── LoggingMessageInboxInternal.kt
│ │ ├── MessageInboxInternal.kt
│ │ └── MessageInboxResponseMapper.kt
│ │ ├── notification
│ │ ├── ActionCommandFactory.kt
│ │ ├── LaunchActivityCommandLifecycleCallbacks.kt
│ │ ├── LaunchActivityCommandLifecycleCallbacksFactory.kt
│ │ ├── NotificationCommandFactory.kt
│ │ └── command
│ │ │ ├── AppEventCommand.kt
│ │ │ ├── CompositeCommand.kt
│ │ │ ├── CustomEventCommand.java
│ │ │ ├── DismissNotificationCommand.kt
│ │ │ ├── LaunchApplicationCommand.kt
│ │ │ ├── NotificationInformationCommand.kt
│ │ │ ├── OpenExternalUrlCommand.kt
│ │ │ ├── PreloadedInappHandlerCommand.kt
│ │ │ ├── SilentNotificationInformationCommand.kt
│ │ │ ├── TrackActionClickCommand.kt
│ │ │ └── TrackMessageOpenCommand.kt
│ │ ├── push
│ │ ├── DefaultPushInternal.kt
│ │ ├── DefaultPushTokenProvider.kt
│ │ ├── LoggingPushInternal.kt
│ │ ├── NotificationInformationListenerProvider.kt
│ │ ├── PushInternal.kt
│ │ ├── PushTokenProvider.kt
│ │ └── SilentNotificationInformationListenerProvider.kt
│ │ ├── request
│ │ ├── CoreCompletionHandlerRefreshTokenProxy.kt
│ │ ├── CoreCompletionHandlerRefreshTokenProxyProvider.kt
│ │ ├── MobileEngageRequestModelFactory.kt
│ │ └── mapper
│ │ │ ├── AbstractRequestMapper.kt
│ │ │ ├── ContactTokenHeaderMapper.kt
│ │ │ ├── DefaultRequestHeaderMapper.kt
│ │ │ ├── DeviceEventStateRequestMapper.kt
│ │ │ ├── MobileEngageHeaderMapper.kt
│ │ │ └── OpenIdTokenRequestMapper.kt
│ │ ├── responsehandler
│ │ ├── ClientInfoResponseHandler.kt
│ │ ├── DeviceEventStateResponseHandler.kt
│ │ ├── InAppCleanUpResponseHandler.kt
│ │ ├── InAppCleanUpResponseHandlerV4.kt
│ │ ├── InAppMessageResponseHandler.kt
│ │ ├── MobileEngageClientStateResponseHandler.kt
│ │ ├── MobileEngageTokenResponseHandler.kt
│ │ └── OnEventActionResponseHandler.kt
│ │ ├── service
│ │ ├── IntentUtils.kt
│ │ ├── MessagingServiceUtils.kt
│ │ ├── NotificationActionUtils.kt
│ │ ├── NotificationData.kt
│ │ ├── NotificationStyle.kt
│ │ └── mapper
│ │ │ ├── RemoteMessageMapper.kt
│ │ │ ├── RemoteMessageMapperFactory.kt
│ │ │ ├── RemoteMessageMapperV1.kt
│ │ │ └── RemoteMessageMapperV2.kt
│ │ ├── session
│ │ ├── MobileEngageSession.kt
│ │ └── SessionIdHolder.kt
│ │ ├── storage
│ │ └── MobileEngageStorageKey.kt
│ │ └── util
│ │ ├── RequestModelHelper.kt
│ │ └── RequestPayloadUtils.kt
│ └── res
│ ├── drawable
│ └── default_small_notification_icon.xml
│ └── layout
│ └── mobile_engage_in_app_message.xml
├── predict-api
├── build.gradle.kts
└── src
│ ├── androidTest
│ └── java
│ │ └── com
│ │ └── emarsys
│ │ └── predict
│ │ └── api
│ │ └── model
│ │ ├── RecommendationFilterTest.kt
│ │ └── RecommendationLogicTest.kt
│ └── main
│ ├── AndroidManifest.xml
│ └── java
│ └── com
│ └── emarsys
│ └── predict
│ └── api
│ └── model
│ ├── CartItem.kt
│ ├── Logic.kt
│ ├── PredictCartItem.kt
│ ├── Product.kt
│ ├── RecommendationFilter.kt
│ └── RecommendationLogic.kt
├── predict
├── build.gradle.kts
└── src
│ ├── androidTest
│ └── java
│ │ └── com
│ │ └── emarsys
│ │ └── predict
│ │ ├── DefaultPredictInternalTest.kt
│ │ ├── PredictResponseMapperTest.kt
│ │ ├── fake
│ │ ├── FakeRestClient.java
│ │ └── FakeResultListener.java
│ │ ├── provider
│ │ └── PredictRequestModelBuilderProviderTest.kt
│ │ ├── request
│ │ ├── PredictHeaderFactoryTest.kt
│ │ └── PredictRequestModelBuilderTest.kt
│ │ ├── response
│ │ ├── VisitorIdResponseHandlerTest.kt
│ │ └── XPResponseHandlerTest.kt
│ │ ├── shard
│ │ └── PredictShardListMergerTest.kt
│ │ ├── storage
│ │ └── PredictStorageKeyTest.kt
│ │ └── util
│ │ └── CartItemUtilsTest.kt
│ └── main
│ ├── AndroidManifest.xml
│ └── java
│ └── com
│ └── emarsys
│ └── predict
│ ├── DefaultPredictInternal.kt
│ ├── LoggingPredictInternal.kt
│ ├── PredictInternal.kt
│ ├── PredictResponseMapper.kt
│ ├── di
│ └── PredictComponent.kt
│ ├── endpoint
│ └── Endpoint.java
│ ├── model
│ └── LastTrackedItemContainer.java
│ ├── provider
│ └── PredictRequestModelBuilderProvider.java
│ ├── request
│ ├── PredictHeaderFactory.java
│ ├── PredictRequestContext.kt
│ └── PredictRequestModelBuilder.kt
│ ├── response
│ ├── VisitorIdResponseHandler.java
│ └── XPResponseHandler.kt
│ ├── shard
│ └── PredictShardListMerger.java
│ ├── storage
│ └── PredictStorageKey.kt
│ └── util
│ └── CartItemUtils.java
├── proguard-multidex-rules.pro
├── release.sh
├── repo-info.json
├── revoke.sh
├── sample
├── README.md
├── build.gradle.kts
├── proguard-multidex-rules.pro
├── proguard-rules.pro
└── src
│ └── main
│ ├── AndroidManifest.xml
│ ├── kotlin
│ └── com
│ │ └── emarsys
│ │ └── sample
│ │ ├── CustomMessagingService.kt
│ │ ├── SampleApplication.kt
│ │ ├── dashboard
│ │ ├── DashboardScreen.kt
│ │ ├── DashboardViewModel.kt
│ │ ├── GoogleAuthResult.kt
│ │ └── button
│ │ │ ├── CopyPushTokenButton.kt
│ │ │ ├── GoogleSignInButton.kt
│ │ │ ├── RequestLocationPermissionsButton.kt
│ │ │ └── TrackPushTokenButton.kt
│ │ ├── inapp
│ │ ├── InAppScreen.kt
│ │ ├── InAppViewModel.kt
│ │ └── UuidProvider.kt
│ │ ├── inbox
│ │ ├── InboxScreen.kt
│ │ ├── InboxViewModel.kt
│ │ ├── MessagePresenter.kt
│ │ ├── component
│ │ │ ├── InboxButton.kt
│ │ │ ├── ShowImage.kt
│ │ │ └── TextFromTags.kt
│ │ └── event
│ │ │ └── InboxAppEventHandler.kt
│ │ ├── main
│ │ ├── MainActivity.kt
│ │ ├── MainViewModel.kt
│ │ ├── navigation
│ │ │ ├── BottomNavigationBar.kt
│ │ │ ├── NavigationBarItem.kt
│ │ │ └── NavigationControllerProvider.kt
│ │ └── sdkinfo
│ │ │ ├── TopCardViewModel.kt
│ │ │ └── TopExpandableCard.kt
│ │ ├── mobileengage
│ │ ├── EventPayloadTextArea.kt
│ │ ├── MobileEngageScreen.kt
│ │ └── MobileEngageViewModel.kt
│ │ ├── predict
│ │ ├── PredictScreen.kt
│ │ ├── PredictViewModel.kt
│ │ ├── RecommendedProductsCard.kt
│ │ ├── TrackableTerm.kt
│ │ └── cart
│ │ │ ├── SampleCartItem.kt
│ │ │ └── SampleCartItemCard.kt
│ │ ├── pref
│ │ └── Prefs.kt
│ │ └── ui
│ │ ├── Card.kt
│ │ ├── Column.kt
│ │ ├── Row.kt
│ │ ├── component
│ │ ├── button
│ │ │ └── StyledTextButton.kt
│ │ ├── column
│ │ │ └── ColumnWithTapGesture.kt
│ │ ├── dialog
│ │ │ └── ErrorDialog.kt
│ │ ├── divider
│ │ │ ├── BlueLine.kt
│ │ │ └── GreyLine.kt
│ │ ├── row
│ │ │ ├── RowWithCenteredContent.kt
│ │ │ └── RowWithEvenlySpacedContent.kt
│ │ ├── screen
│ │ │ └── DetailScreen.kt
│ │ ├── text
│ │ │ ├── TextInFullWidthLine.kt
│ │ │ └── TitleText.kt
│ │ ├── textfield
│ │ │ └── StyledTextField.kt
│ │ └── toast
│ │ │ └── CustomTextToast.kt
│ │ └── theme
│ │ ├── Color.kt
│ │ ├── Shape.kt
│ │ ├── Theme.kt
│ │ └── Type.kt
│ └── res
│ ├── drawable-v24
│ └── ic_launcher_foreground.xml
│ ├── drawable
│ ├── ic_google_logo.xml
│ ├── ic_launcher_background.xml
│ ├── ic_settings.xml
│ ├── inbox_mailbox_icon.xml
│ ├── item_selected.xml
│ ├── mobile_engage_logo_icon.xml
│ ├── notification_icon.xml
│ ├── placeholder.png
│ └── predict_scarab_icon.xml
│ ├── mipmap-hdpi
│ ├── ic_launcher.png
│ └── ic_launcher_round.png
│ ├── mipmap-mdpi
│ ├── ic_launcher.png
│ └── ic_launcher_round.png
│ ├── mipmap-xhdpi
│ ├── ic_launcher.png
│ └── ic_launcher_round.png
│ ├── mipmap-xxhdpi
│ ├── ic_launcher.png
│ └── ic_launcher_round.png
│ ├── mipmap-xxxhdpi
│ ├── ic_launcher.png
│ └── ic_launcher_round.png
│ ├── values-night
│ └── themes.xml
│ ├── values-v29
│ └── themes.xml
│ └── values
│ ├── colors.xml
│ ├── colors_green_yellow.xml
│ ├── strings.xml
│ ├── styles.xml
│ └── themes.xml
├── settings.gradle.kts
├── testUtils
├── build.gradle.kts
└── src
│ └── main
│ ├── AndroidManifest.xml
│ └── java
│ └── com
│ └── emarsys
│ └── testUtil
│ ├── ApplicationTestUtils.kt
│ ├── CollectionTestUtils.kt
│ ├── ConnectionTestUtils.kt
│ ├── DatabaseTestUtils.kt
│ ├── ExtensionTestUtils.kt
│ ├── FeatureTestUtils.kt
│ ├── FileTestUtils.kt
│ ├── InstrumentationRegistry.kt
│ ├── RandomTestUtils.kt
│ ├── ReflectionTestUtils.kt
│ ├── RetryUtils.kt
│ ├── SharedPrefsUtils.kt
│ ├── TestUrls.kt
│ ├── TimeoutUtils.kt
│ ├── fake
│ └── FakeActivity.kt
│ ├── mockito
│ ├── MockitoTestUtils.kt
│ └── ThreadSpy.kt
│ └── rules
│ ├── ConnectionRule.kt
│ ├── DuplicatedThreadRule.kt
│ └── RetryRule.kt
├── testWithAllDevices.yml
└── testWithSomeVirtualDevices.yml
/.env.example:
--------------------------------------------------------------------------------
1 | USE_LOCAL_DEPENDENCY=
2 | RELEASE_MODE=
3 | ANDROID_RELEASE_STORE_FILE_BASE64=
4 | ANDROID_RELEASE_STORE_PASSWORD=
5 | ANDROID_RELEASE_KEY_ALIAS=
6 | ANDROID_RELEASE_KEY_PASSWORD=
7 | FIREBASE_PROJECT_ID=
8 | FIREBASE_SERVICE_ACCOUNT_JSON=
9 | GOOGLE_OAUTH_SERVER_CLIENT_ID=
10 | GOOGLE_SERVICES_JSON_BASE64=
11 | GOOGLE_PLAY_STORE_SEVICE_ACCOUNT_JSON_BASE64=
12 | OSSRH_USERNAME=
13 | OSSRH_PASSWORD=
14 | SONATYPE_STAGING_PROFILE_ID=
15 | SONATYPE_SIGNING_KEY_ID=
16 | SONATYPE_SIGNING_PASSWORD=
17 | SONATYPE_SIGNING_SECRET_KEY_RING_FILE_BASE64=
--------------------------------------------------------------------------------
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | version: 2
2 | updates:
3 | - package-ecosystem: "gradle"
4 | target-branch: "dev"
5 | directory: "/"
6 | schedule:
7 | interval: "daily"
8 | commit-message:
9 | prefix: "chore(dependabot)"
10 | open-pull-requests-limit: 5
11 | - package-ecosystem: "github-actions"
12 | target-branch: "dev"
13 | directory: "/"
14 | schedule:
15 | interval: "daily"
16 | commit-message:
17 | prefix: "chore(dependabot)"
18 | open-pull-requests-limit: 5
--------------------------------------------------------------------------------
/.github/workflows/build_docs.yml:
--------------------------------------------------------------------------------
1 | name: Build Documentation
2 |
3 | on:
4 | push:
5 | branches:
6 | - dev
7 | paths:
8 | - docs/**
9 |
10 | jobs:
11 | build-docs:
12 | name: Build documentation
13 | uses: emartech/me-workflows/.github/workflows/build-docs.yaml@main
14 | secrets: inherit
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .idea
2 | .gradle
3 | build
4 | libs
5 | *.iml
6 | local.properties
7 | localConfig.properties
8 | sample/google-services.json
9 | sample/agconnect-services.json
10 | .fleet
11 | .env
12 | firebase_service_account.json
13 | workflow.secrets
14 | mobile-team-android.jks.asc
15 | sample/mobile-team-android.jks
16 | mobile-team-android.jks
17 | secring.asc.gpg
--------------------------------------------------------------------------------
/.reuse/dep5:
--------------------------------------------------------------------------------
1 | Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
2 | Upstream-Name: android-emarsys-sdk
3 | Upstream-Contact: sdk-team@emarsys.com
4 | Source: https://github.com/emartech/android-emarsys-sdk
5 |
6 | Files: *
7 | Copyright: 2023 Emarsys
8 | License: MPL-2.0
--------------------------------------------------------------------------------
/Dockerfile.codescan:
--------------------------------------------------------------------------------
1 | FROM eu.gcr.io/ems-mobile-sdk/android-container:latest
2 | USER root
3 |
4 | RUN export ANDROID_HOME=android
5 | RUN export BLACKDUCK=true
6 | RUN echo $ANDROID_HOME > local.properties
7 | WORKDIR /workspace/source
8 |
9 | # we don't want to scan the sample app
10 | CMD rm -rf sample
--------------------------------------------------------------------------------
/buildSrc/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | id("org.jetbrains.kotlin.jvm") version "1.9.0"
3 | id("io.github.gradle-nexus.publish-plugin") version "1.3.0"
4 | id("co.uzzu.dotenv.gradle") version "4.0.0"
5 | id("maven-publish")
6 | signing
7 | }
8 |
9 | repositories {
10 | mavenCentral()
11 | }
12 |
13 | dependencies {
14 | implementation(gradleApi())
15 | implementation("org.jetbrains.kotlin:kotlin-stdlib")
16 | }
17 |
18 | kotlin {
19 | jvmToolchain(17)
20 | }
--------------------------------------------------------------------------------
/buildSrc/src/main/java/GitVersion.kt:
--------------------------------------------------------------------------------
1 | data class GitVersion(val versionName: String, val versionCode: Int, val versionCodeTime: Long)
--------------------------------------------------------------------------------
/changelog.md:
--------------------------------------------------------------------------------
1 | # What's fixed
2 |
3 | ### [Emarsys SDK](https://github.com/emartech/android-emarsys-sdk/wiki)
4 |
5 | * Improved stability of SDK by fixing reoccurring ANR errors caused by crypto related operations on Android 12 and below.
6 |
--------------------------------------------------------------------------------
/common/src/androidTest/java/com/emarsys/common/feature/InnerFeatureTest.kt:
--------------------------------------------------------------------------------
1 | package com.emarsys.common.feature
2 |
3 | import io.kotest.data.forAll
4 | import io.kotest.data.row
5 | import io.kotest.matchers.shouldBe
6 | import kotlinx.coroutines.runBlocking
7 | import org.junit.Test
8 |
9 | class InnerFeatureTest {
10 |
11 | @Test
12 | fun testValues_shouldReturnCorrectValues() {
13 | val expectedValues =
14 | arrayOf("MOBILE_ENGAGE", "PREDICT", "EVENT_SERVICE_V4", "APP_EVENT_CACHE")
15 | InnerFeature.values().map { it.toString() } shouldBe expectedValues
16 | }
17 |
18 | @Test
19 | fun testGetName_shouldReturnCorrectValue() = runBlocking {
20 | InnerFeature.values().map {
21 | "inner_feature_${it.name.lowercase()}"
22 | }.zip(InnerFeature.values()) { stringValue, enum ->
23 | row(enum, stringValue)
24 | }.let {
25 | forAll(*it.toTypedArray()) { input, expected ->
26 | input.featureName shouldBe expected
27 | }
28 | }
29 | }
30 | }
--------------------------------------------------------------------------------
/common/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/common/src/main/java/com/emarsys/common/feature/InnerFeature.kt:
--------------------------------------------------------------------------------
1 | package com.emarsys.common.feature
2 |
3 | import com.emarsys.core.api.experimental.FlipperFeature
4 | import java.util.*
5 |
6 | enum class InnerFeature : FlipperFeature {
7 | MOBILE_ENGAGE, PREDICT, EVENT_SERVICE_V4, APP_EVENT_CACHE;
8 |
9 | override val featureName: String
10 | get() = "inner_feature_" + name.lowercase(Locale.getDefault())
11 |
12 | companion object {
13 | fun safeValueOf(enumAsString: String): InnerFeature? {
14 | return try {
15 | valueOf(enumAsString)
16 | } catch (ignored: IllegalArgumentException) {
17 | null
18 | }
19 | }
20 | }
21 | }
--------------------------------------------------------------------------------
/core-api/src/androidTest/java/com/emarsys/core/api/ApiTest.kt:
--------------------------------------------------------------------------------
1 | package com.emarsys.core.api
2 |
3 |
4 | import org.junit.Test
5 |
6 | class ApiTest {
7 |
8 | @Test
9 | fun test() {
10 | }
11 |
12 | }
--------------------------------------------------------------------------------
/core-api/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/core-api/src/main/java/com/emarsys/core/api/EmarsysIdlingResources.kt:
--------------------------------------------------------------------------------
1 | package com.emarsys.core.api
2 |
3 | import androidx.test.espresso.idling.CountingIdlingResource
4 |
5 |
6 | object EmarsysIdlingResources {
7 |
8 | private const val RESOURCE = "EMARSYS-SDK"
9 |
10 | @JvmField
11 | val countingIdlingResource = CountingIdlingResource(RESOURCE)
12 |
13 | @JvmStatic
14 | fun increment() {
15 | countingIdlingResource.increment()
16 | }
17 |
18 | @JvmStatic
19 | fun decrement() {
20 | if (!countingIdlingResource.isIdleNow) {
21 | countingIdlingResource.decrement()
22 | }
23 | }
24 | }
--------------------------------------------------------------------------------
/core-api/src/main/java/com/emarsys/core/api/MissingPermissionException.kt:
--------------------------------------------------------------------------------
1 | package com.emarsys.core.api
2 |
3 | class MissingPermissionException(message: String) : Exception(message)
--------------------------------------------------------------------------------
/core-api/src/main/java/com/emarsys/core/api/ResponseErrorException.kt:
--------------------------------------------------------------------------------
1 | package com.emarsys.core.api
2 |
3 | import java.lang.Exception
4 |
5 | data class ResponseErrorException(val statusCode: Int, val statusMessage: String?, val body: String?) : Exception(statusMessage)
--------------------------------------------------------------------------------
/core-api/src/main/java/com/emarsys/core/api/experimental/FlipperFeature.kt:
--------------------------------------------------------------------------------
1 | package com.emarsys.core.api.experimental
2 |
3 | interface FlipperFeature {
4 | val featureName: String
5 | }
--------------------------------------------------------------------------------
/core-api/src/main/java/com/emarsys/core/api/notification/ChannelSettings.kt:
--------------------------------------------------------------------------------
1 | package com.emarsys.core.api.notification
2 |
3 | data class ChannelSettings(val channelId: String,
4 | val importance: Int = -1000,
5 | val isCanBypassDnd: Boolean = false,
6 | val isCanShowBadge: Boolean = false,
7 | val isShouldVibrate: Boolean = false,
8 | val isShouldShowLights: Boolean = false)
--------------------------------------------------------------------------------
/core-api/src/main/java/com/emarsys/core/api/notification/NotificationSettings.kt:
--------------------------------------------------------------------------------
1 | package com.emarsys.core.api.notification
2 |
3 | interface NotificationSettings {
4 | val areNotificationsEnabled: Boolean
5 | val importance: Int
6 | val channelSettings: List
7 | }
--------------------------------------------------------------------------------
/core-api/src/main/java/com/emarsys/core/api/result/CompletionListener.kt:
--------------------------------------------------------------------------------
1 | package com.emarsys.core.api.result
2 |
3 | fun interface CompletionListener {
4 | fun onCompleted(errorCause: Throwable?)
5 | }
--------------------------------------------------------------------------------
/core-api/src/main/java/com/emarsys/core/api/result/ResultListener.kt:
--------------------------------------------------------------------------------
1 | package com.emarsys.core.api.result
2 |
3 | fun interface ResultListener {
4 | fun onResult(result: T)
5 | }
--------------------------------------------------------------------------------
/core-api/src/main/java/com/emarsys/core/api/result/Try.kt:
--------------------------------------------------------------------------------
1 | package com.emarsys.core.api.result
2 |
3 | class Try(val result: T?, val errorCause: Throwable?) {
4 |
5 | companion object {
6 | @JvmStatic
7 | fun success(result: T): Try {
8 | return Try(result, null)
9 | }
10 |
11 | @JvmStatic
12 | fun failure(errorCause: Exception?): Try {
13 | return Try(null as T?, errorCause)
14 | }
15 | }
16 | }
--------------------------------------------------------------------------------
/core/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # By default, the flags in this file are appended to flags specified
3 | # in /Users/jpollak/Library/Android/sdk/tools/proguard/proguard-android.txt
4 | # You can edit the include path and order by changing the proguardFiles
5 | # directive in build.gradle.kts.
6 | #
7 | # For more details, see
8 | # http://developer.android.com/guide/developing/tools/proguard.html
9 |
10 | # Add any project specific keep options here:
11 |
12 | # If your project uses WebView with JS, uncomment the following
13 | # and specify the fully qualified class name to the JavaScript interface
14 | # class:
15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
16 | # public *;
17 | #}
18 |
--------------------------------------------------------------------------------
/core/src/androidTest/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
--------------------------------------------------------------------------------
/core/src/androidTest/java/com/emarsys/core/fake/FakeConnectionChangeListener.java:
--------------------------------------------------------------------------------
1 | package com.emarsys.core.fake;
2 |
3 | import com.emarsys.core.connection.ConnectionChangeListener;
4 | import com.emarsys.core.connection.ConnectionState;
5 |
6 | import java.util.concurrent.CountDownLatch;
7 |
8 | public class FakeConnectionChangeListener implements ConnectionChangeListener {
9 |
10 | public int onConnectionChangedCount;
11 | public String threadName;
12 | public CountDownLatch latch;
13 | public ConnectionState connectionState;
14 | public boolean isConnected;
15 |
16 | public FakeConnectionChangeListener(CountDownLatch latch) {
17 | this.latch = latch;
18 | }
19 |
20 | @Override
21 | public void onConnectionChanged(ConnectionState connectionState, boolean isConnected) {
22 | this.connectionState = connectionState;
23 | this.isConnected = isConnected;
24 | onConnectionChangedCount++;
25 | threadName = Thread.currentThread().getName();
26 | latch.countDown();
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/core/src/androidTest/java/com/emarsys/core/provider/random/RandomProviderTest.kt:
--------------------------------------------------------------------------------
1 | package com.emarsys.core.provider.random
2 |
3 | import io.kotest.matchers.doubles.shouldBeGreaterThanOrEqual
4 | import io.kotest.matchers.doubles.shouldBeLessThanOrEqual
5 | import org.junit.Test
6 |
7 | class RandomProviderTest {
8 |
9 | @Test
10 | fun testProvideRandomDouble() {
11 | val randomProvider = RandomProvider()
12 |
13 | randomProvider.provideDouble(1.0) shouldBeGreaterThanOrEqual 0.0
14 | randomProvider.provideDouble(1.0) shouldBeLessThanOrEqual 1.0
15 | }
16 | }
--------------------------------------------------------------------------------
/core/src/androidTest/java/com/emarsys/core/provider/timestamp/TimestampProviderTest.kt:
--------------------------------------------------------------------------------
1 | package com.emarsys.core.provider.timestamp
2 |
3 | import io.kotest.matchers.shouldBe
4 | import org.junit.Test
5 |
6 | class TimestampProviderTest {
7 | @Test
8 | fun testProvideTimestamp_returnsTheCurrentTimestamp() {
9 | val before = System.currentTimeMillis()
10 | val actual = TimestampProvider().provideTimestamp()
11 | val after = System.currentTimeMillis()
12 | (before <= actual) shouldBe true
13 | (actual <= after) shouldBe true
14 | }
15 | }
--------------------------------------------------------------------------------
/core/src/androidTest/java/com/emarsys/core/provider/uuid/UUIDProviderTest.kt:
--------------------------------------------------------------------------------
1 | package com.emarsys.core.provider.uuid
2 |
3 | import io.kotest.matchers.shouldNotBe
4 | import org.junit.Test
5 |
6 | class UUIDProviderTest {
7 | @Test
8 | fun testProvideId_returnsNotNullId() {
9 | val provider = UUIDProvider()
10 | provider.provideId() shouldNotBe null
11 | }
12 | }
--------------------------------------------------------------------------------
/core/src/androidTest/java/com/emarsys/core/provider/version/VersionProviderTest.kt:
--------------------------------------------------------------------------------
1 | package com.emarsys.core.provider.version
2 |
3 | import io.kotest.matchers.shouldBe
4 | import org.junit.Before
5 | import org.junit.Test
6 |
7 | class VersionProviderTest {
8 |
9 |
10 | private lateinit var versionProvider: VersionProvider
11 |
12 | @Before
13 | fun setUp() {
14 | versionProvider = VersionProvider()
15 | }
16 |
17 | @Test
18 | fun testProvideSdkVersion() {
19 | val expected = com.emarsys.core.BuildConfig.VERSION_NAME
20 |
21 | val sdkVersion = versionProvider.provideSdkVersion()
22 |
23 | sdkVersion shouldBe expected
24 | }
25 | }
--------------------------------------------------------------------------------
/core/src/androidTest/java/com/emarsys/core/storage/CoreStorageKeyTest.kt:
--------------------------------------------------------------------------------
1 | package com.emarsys.core.storage
2 |
3 | import io.kotest.data.forAll
4 | import io.kotest.data.row
5 | import io.kotest.matchers.shouldBe
6 | import kotlinx.coroutines.runBlocking
7 | import org.junit.Test
8 |
9 | class CoreStorageKeyTest {
10 |
11 | @Test
12 | fun testGetKey() = runBlocking {
13 | CoreStorageKey.values().map {
14 | "core_${it.name.lowercase()}"
15 | }.zip(CoreStorageKey.values()) { stringValue, enum ->
16 | row(enum, stringValue)
17 | }.let {
18 | forAll(*it.toTypedArray()) { input, expected ->
19 | input.key shouldBe expected
20 | }
21 | }
22 | }
23 | }
--------------------------------------------------------------------------------
/core/src/androidTest/java/com/emarsys/core/testUtil/RequestModelTestUtils.kt:
--------------------------------------------------------------------------------
1 | package com.emarsys.core.testUtil
2 |
3 | import com.emarsys.core.provider.timestamp.TimestampProvider
4 | import com.emarsys.core.provider.uuid.UUIDProvider
5 | import com.emarsys.core.request.model.RequestMethod
6 | import com.emarsys.core.request.model.RequestMethod.GET
7 | import com.emarsys.core.request.model.RequestModel
8 | import com.emarsys.testUtil.TestUrls
9 |
10 | object RequestModelTestUtils {
11 |
12 | @JvmStatic
13 | @JvmOverloads
14 | fun createRequestModel(
15 | method: RequestMethod = GET,
16 | url: String = TestUrls.customResponse(200)): RequestModel =
17 | RequestModel.Builder(TimestampProvider(), UUIDProvider())
18 | .url(url)
19 | .method(method)
20 | .headers(mapOf(
21 | "accept" to "application/json",
22 | "content" to "application/x-www-form-urlencoded"
23 | ))
24 | .build()
25 |
26 | }
27 |
--------------------------------------------------------------------------------
/core/src/androidTest/java/com/emarsys/core/util/DatabaseUtilTest.kt:
--------------------------------------------------------------------------------
1 | package com.emarsys.core.util
2 |
3 |
4 | import io.kotest.matchers.shouldBe
5 | import org.junit.Test
6 |
7 | class DatabaseUtilTest {
8 |
9 | @Test
10 | fun testGenerateInStatement_shouldGenerateValidStatementEnding() {
11 | val result = DatabaseUtil.generateInStatement("request_id", arrayOf("123", "12", "1"))
12 | result shouldBe "request_id IN (?, ?, ?)"
13 | }
14 | }
--------------------------------------------------------------------------------
/core/src/androidTest/java/com/emarsys/core/util/ExceptionExtensionsKtTest.kt:
--------------------------------------------------------------------------------
1 | package com.emarsys.core.util
2 |
3 | import io.kotest.matchers.shouldBe
4 | import org.junit.Test
5 |
6 | class ExceptionExtensionsKtTest {
7 |
8 | @Test
9 | fun testRootCause_shouldReturn_withTheRootCauseOfTheException() {
10 | val expectation = Throwable("root cause")
11 | val testException: Exception = Exception(
12 | Throwable(
13 | Throwable(
14 | "root cause", Throwable(
15 | Throwable(Throwable(expectation))
16 | )
17 | )
18 | )
19 | )
20 |
21 | val result = testException.rootCause()
22 |
23 | result shouldBe expectation
24 | }
25 | }
--------------------------------------------------------------------------------
/core/src/androidTest/java/com/emarsys/core/util/HeaderUtilsInstrumentationTest.kt:
--------------------------------------------------------------------------------
1 | package com.emarsys.core.util
2 |
3 | import io.kotest.assertions.throwables.shouldThrow
4 | import io.kotest.matchers.shouldBe
5 | import org.junit.Test
6 |
7 | class HeaderUtilsInstrumentationTest {
8 | private val username = "user"
9 |
10 | @Test
11 | fun testCreateBasicAuth_usernameShouldNotBeNull() {
12 | shouldThrow {
13 | HeaderUtils.createBasicAuth(null)
14 | }
15 | }
16 |
17 | @Test
18 | fun testCreateBasicAuth_shouldCreateCorrectBasicAuthString() {
19 | val expected = "Basic dXNlcjo="
20 | val result = HeaderUtils.createBasicAuth(username)
21 | result shouldBe expected
22 | }
23 | }
--------------------------------------------------------------------------------
/core/src/androidTest/java/com/emarsys/core/util/MapExtensionsKtTest.kt:
--------------------------------------------------------------------------------
1 | package com.emarsys.core.util
2 |
3 | import io.kotest.matchers.shouldBe
4 | import org.junit.Test
5 |
6 | class MapExtensionsKtTest {
7 |
8 | @Test
9 | fun testGetCaseInsensitive() {
10 | val map: Map =
11 | mapOf("KeY0" to "Value0", "KeY1" to "Value1", "key2" to "Value2")
12 | val result = map.getCaseInsensitive("key1")
13 |
14 | result shouldBe "Value1"
15 | }
16 |
17 | @Test
18 | fun testGetCaseInsensitive_mustWorkWhenNoResult() {
19 | val map: Map = mapOf("KeY0" to "Value0", "KeY1" to "Value1", "key2" to "Value2")
20 | val result = map.getCaseInsensitive(null)
21 |
22 | result shouldBe null
23 | }
24 | }
--------------------------------------------------------------------------------
/core/src/androidTest/java/com/emarsys/core/util/SystemUtilsTest.kt:
--------------------------------------------------------------------------------
1 | package com.emarsys.core.util
2 |
3 | import io.kotest.matchers.shouldBe
4 | import org.junit.Test
5 |
6 |
7 | class SystemUtilsTest {
8 |
9 |
10 | @Test
11 | fun testIsClassFound_java() {
12 | val result = SystemUtils.isClassFound("java.util.ArrayList")
13 | result shouldBe true
14 | }
15 |
16 | @Test
17 | fun testIsClassFound_kotlin() {
18 | val result = SystemUtils.isClassFound("kotlin.Pair")
19 | result shouldBe true
20 | }
21 |
22 | @Test
23 | fun testIsClassFound_missingClass() {
24 | val result = SystemUtils.isClassFound("no.such.Class")
25 | result shouldBe false
26 | }
27 |
28 | @Test
29 | fun testGetCallerMethodName() {
30 | SystemUtils.getCallerMethodName() shouldBe "testGetCallerMethodName"
31 | }
32 |
33 | }
--------------------------------------------------------------------------------
/core/src/androidTest/java/com/emarsys/core/util/log/entry/AppEventLogTest.kt:
--------------------------------------------------------------------------------
1 | package com.emarsys.core.util.log.entry
2 |
3 | import io.kotest.matchers.shouldBe
4 | import org.junit.Test
5 |
6 | class AppEventLogTest {
7 |
8 | @Test
9 | fun testTopic() {
10 | val result = AppEventLog("")
11 |
12 | result.topic shouldBe "log_app_event"
13 | }
14 |
15 | @Test
16 | fun testData() {
17 | val testAttributes = mapOf(
18 | "testKey1" to "testValue1",
19 | "testKey2" to "testValue2"
20 | )
21 |
22 | val expected = mapOf(
23 | "eventName" to "testEventName",
24 | "eventAttributes" to testAttributes
25 | )
26 |
27 | val result = AppEventLog("testEventName", testAttributes)
28 |
29 | result.data shouldBe expected
30 | }
31 |
32 | @Test
33 | fun testData_withoutAttributes() {
34 | val expected = mapOf(
35 | "eventName" to "testEventName"
36 | )
37 |
38 | val result = AppEventLog("testEventName")
39 |
40 | result.data shouldBe expected
41 | }
42 | }
--------------------------------------------------------------------------------
/core/src/androidTest/java/com/emarsys/core/util/log/entry/OfflineQueueSizeTest.kt:
--------------------------------------------------------------------------------
1 | package com.emarsys.core.util.log.entry
2 |
3 | import io.kotest.matchers.shouldBe
4 | import org.junit.Test
5 |
6 |
7 | class OfflineQueueSizeTest {
8 |
9 |
10 | @Test
11 | fun testTopic() {
12 | val result = OfflineQueueSize(0)
13 |
14 | result.topic shouldBe "log_offline_queue_size"
15 | }
16 |
17 | @Test
18 | fun testData() {
19 | val result = OfflineQueueSize(3)
20 |
21 | result.data shouldBe mapOf("queueSize" to 3)
22 | }
23 | }
--------------------------------------------------------------------------------
/core/src/androidTest/java/com/emarsys/core/util/predicate/ListSizeAtLeastTest.kt:
--------------------------------------------------------------------------------
1 | package com.emarsys.core.util.predicate
2 |
3 | import io.kotest.matchers.shouldBe
4 | import org.junit.Before
5 | import org.junit.Test
6 |
7 | class ListSizeAtLeastTest {
8 |
9 |
10 | private lateinit var predicate: ListSizeAtLeast
11 |
12 | @Before
13 | fun init() {
14 | predicate = ListSizeAtLeast(5)
15 | }
16 |
17 | @Test
18 | fun testEvaluate_5_returnsTrue_forAListWithSize_5() {
19 | predicate.evaluate(listOf(1, 2, 3, 4, 5)) shouldBe true
20 | }
21 |
22 | @Test
23 | fun testEvaluate_5_returnsTrue_forAListLargerThan_5() {
24 | predicate.evaluate(listOf(1, 2, 3, 4, 5, 6, 7, 8)) shouldBe true
25 | }
26 |
27 | @Test
28 | fun testEvaluate_5_returnsFalse_forAListLessThan_5() {
29 | predicate.evaluate(listOf(1, 2, 3, 4)) shouldBe false
30 | }
31 | }
--------------------------------------------------------------------------------
/core/src/androidTest/res/raw/emarsys_test_image.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emartech/android-emarsys-sdk/b69741719d0ba7e462a1faab64c49b750906a1fd/core/src/androidTest/res/raw/emarsys_test_image.png
--------------------------------------------------------------------------------
/core/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 |
6 |
7 |
8 |
13 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/core/src/main/java/com/emarsys/core/Callable.kt:
--------------------------------------------------------------------------------
1 | package com.emarsys.core
2 |
3 | fun interface Callable {
4 | fun call(): T
5 | }
--------------------------------------------------------------------------------
/core/src/main/java/com/emarsys/core/CoreCompletionHandler.kt:
--------------------------------------------------------------------------------
1 | package com.emarsys.core
2 |
3 | import com.emarsys.core.response.ResponseModel
4 |
5 | interface CoreCompletionHandler {
6 | fun onSuccess(id: String, responseModel: ResponseModel)
7 | fun onError(id: String, responseModel: ResponseModel)
8 | fun onError(id: String, cause: Exception)
9 | }
--------------------------------------------------------------------------------
/core/src/main/java/com/emarsys/core/Mapper.kt:
--------------------------------------------------------------------------------
1 | package com.emarsys.core
2 |
3 | interface Mapper {
4 | fun map(value: T): V
5 | }
--------------------------------------------------------------------------------
/core/src/main/java/com/emarsys/core/Mockable.kt:
--------------------------------------------------------------------------------
1 | package com.emarsys.core
2 |
3 | annotation class Mockable
--------------------------------------------------------------------------------
/core/src/main/java/com/emarsys/core/Registry.kt:
--------------------------------------------------------------------------------
1 | package com.emarsys.core
2 |
3 | interface Registry {
4 | fun register(key: K, value: V)
5 | }
--------------------------------------------------------------------------------
/core/src/main/java/com/emarsys/core/activity/ActivityLifecycleAction.kt:
--------------------------------------------------------------------------------
1 | package com.emarsys.core.activity
2 |
3 | import android.app.Activity
4 |
5 | interface ActivityLifecycleAction {
6 | val priority: Int
7 | val repeatable: Boolean
8 | val triggeringLifecycle: ActivityLifecycle
9 | fun execute(activity: Activity?)
10 |
11 | enum class ActivityLifecycle(val priority: Int) {
12 | CREATE(ActivityLifecyclePriorities.CREATE_PRIORITY),
13 | RESUME(ActivityLifecyclePriorities.RESUME_PRIORITY);
14 | }
15 | }
16 |
17 | fun ActivityLifecycleAction.getOrderingPriority(): Int {
18 | return priority + triggeringLifecycle.priority
19 | }
--------------------------------------------------------------------------------
/core/src/main/java/com/emarsys/core/activity/ActivityLifecyclePriorities.kt:
--------------------------------------------------------------------------------
1 | package com.emarsys.core.activity
2 |
3 | class ActivityLifecyclePriorities {
4 | companion object {
5 | const val CREATE_PRIORITY = 0
6 | const val RESUME_PRIORITY = 1000
7 |
8 | const val DEEP_LINK_ACTION_PRIORITY = 0
9 | const val FETCH_GEOFENCE_ACTION_PRIORITY = 0
10 | const val FETCH_REMOTE_CONFIG_ACTION_PRIORITY = 100
11 | const val PUSH_TO_INAPP_ACTION_PRIORITY = 900
12 | const val APP_START_ACTION_PRIORITY = 200
13 | const val DEVICE_INFO_START_ACTION_PRIORITY = 300
14 | }
15 | }
--------------------------------------------------------------------------------
/core/src/main/java/com/emarsys/core/api/ApiProxy.kt:
--------------------------------------------------------------------------------
1 | package com.emarsys.core.api
2 |
3 | import com.emarsys.core.handler.ConcurrentHandlerHolder
4 |
5 |
6 | inline fun T.proxyApi(handlerHolder: ConcurrentHandlerHolder): T {
7 | return this.proxyWithLogExceptions().proxyWithHandler(handlerHolder)
8 | }
--------------------------------------------------------------------------------
/core/src/main/java/com/emarsys/core/concurrency/ConcurrentHandlerHolderFactory.kt:
--------------------------------------------------------------------------------
1 | package com.emarsys.core.concurrency
2 |
3 | import android.os.HandlerThread
4 | import com.emarsys.core.handler.ConcurrentHandlerHolder
5 | import com.emarsys.core.handler.SdkHandler
6 | import java.util.*
7 |
8 | object ConcurrentHandlerHolderFactory {
9 | fun create(): ConcurrentHandlerHolder {
10 | val coreHandlerThread = HandlerThread("CoreSDKHandlerThread-" + UUID.randomUUID().toString())
11 | coreHandlerThread.start()
12 | val networkHandlerThread = HandlerThread("NetworkHandlerThread-" + UUID.randomUUID().toString())
13 | networkHandlerThread.start()
14 | val backgroundHandlerThread = HandlerThread("NetworkHandlerThread-" + UUID.randomUUID().toString())
15 | backgroundHandlerThread.start()
16 | return ConcurrentHandlerHolder(
17 | SdkHandler(CoreHandler(coreHandlerThread)),
18 | SdkHandler(CoreHandler(networkHandlerThread)),
19 | SdkHandler(CoreHandler(backgroundHandlerThread))
20 | )
21 | }
22 | }
--------------------------------------------------------------------------------
/core/src/main/java/com/emarsys/core/concurrency/CoreHandler.kt:
--------------------------------------------------------------------------------
1 | package com.emarsys.core.concurrency
2 |
3 | import android.os.Handler
4 | import com.emarsys.core.util.log.Logger.Companion.error
5 | import android.os.HandlerThread
6 | import android.os.Message
7 | import com.emarsys.core.util.log.entry.CrashLog
8 | import java.lang.Exception
9 |
10 | class CoreHandler(handlerThread: HandlerThread) : Handler(handlerThread.looper) {
11 | override fun dispatchMessage(msg: Message) {
12 | try {
13 | super.dispatchMessage(msg)
14 | } catch (e: Exception) {
15 | error(CrashLog(e))
16 | }
17 | }
18 | }
--------------------------------------------------------------------------------
/core/src/main/java/com/emarsys/core/connection/ConnectionChangeListener.kt:
--------------------------------------------------------------------------------
1 | package com.emarsys.core.connection
2 |
3 | interface ConnectionChangeListener {
4 | fun onConnectionChanged(connectionState: ConnectionState?, isConnected: Boolean)
5 | }
--------------------------------------------------------------------------------
/core/src/main/java/com/emarsys/core/connection/ConnectionProvider.kt:
--------------------------------------------------------------------------------
1 | package com.emarsys.core.connection
2 |
3 | import android.webkit.URLUtil
4 | import com.emarsys.core.Mockable
5 | import com.emarsys.core.request.model.RequestModel
6 | import java.util.Locale
7 | import javax.net.ssl.HttpsURLConnection
8 |
9 | @Mockable
10 | class ConnectionProvider {
11 |
12 | fun provideConnection(requestModel: RequestModel): HttpsURLConnection {
13 | val url = requestModel.url
14 | require(URLUtil.isHttpsUrl(url.toString())) {
15 | "Expected HTTPS request model, but got: " + url.protocol.uppercase(
16 | Locale.getDefault()
17 | )
18 | }
19 | return requestModel.url.openConnection() as HttpsURLConnection
20 | }
21 | }
--------------------------------------------------------------------------------
/core/src/main/java/com/emarsys/core/connection/ConnectionState.kt:
--------------------------------------------------------------------------------
1 | package com.emarsys.core.connection
2 |
3 | enum class ConnectionState {
4 | CONNECTED, CONNECTED_MOBILE_DATA, DISCONNECTED
5 | }
--------------------------------------------------------------------------------
/core/src/main/java/com/emarsys/core/connection/ConnectivityChangeReceiver.kt:
--------------------------------------------------------------------------------
1 | package com.emarsys.core.connection
2 |
3 | import android.content.BroadcastReceiver
4 | import android.content.Context
5 | import android.content.Intent
6 | import com.emarsys.core.handler.ConcurrentHandlerHolder
7 |
8 | class ConnectivityChangeReceiver(
9 | private val connectionChangeListener: ConnectionChangeListener,
10 | private val connectionWatchDog: ConnectionWatchDog,
11 | private val concurrentHandlerHolder: ConcurrentHandlerHolder
12 | ) :
13 | BroadcastReceiver() {
14 | override fun onReceive(context: Context, intent: Intent) {
15 | concurrentHandlerHolder.coreHandler.post {
16 | val connectionState: ConnectionState = connectionWatchDog.connectionState
17 | val isConnected: Boolean = connectionWatchDog.isConnected
18 | connectionChangeListener.onConnectionChanged(connectionState, isConnected)
19 | }
20 | }
21 | }
--------------------------------------------------------------------------------
/core/src/main/java/com/emarsys/core/contentresolver/EmarsysContentResolver.kt:
--------------------------------------------------------------------------------
1 | package com.emarsys.core.contentresolver
2 |
3 | import android.content.Context
4 | import android.database.Cursor
5 | import android.net.Uri
6 | import com.emarsys.core.Mockable
7 |
8 | @Mockable
9 | class EmarsysContentResolver(private val context: Context) {
10 |
11 | fun query(
12 | uri: Uri,
13 | projection: Array?, selection: String?,
14 | selectionArgs: Array?, sortOrder: String?
15 | ): Cursor? {
16 | return context.contentResolver.query(uri, projection, selection, selectionArgs, sortOrder)
17 | }
18 | }
--------------------------------------------------------------------------------
/core/src/main/java/com/emarsys/core/crypto/ClientIdentificationCrypto.kt:
--------------------------------------------------------------------------------
1 | package com.emarsys.core.crypto
2 |
3 | import com.emarsys.core.Mockable
4 | import com.emarsys.core.device.ClientIdentification
5 |
6 | @Mockable
7 | class ClientIdentificationCrypto(
8 | private var secret: String?,
9 | private val crypto: Crypto
10 | ) {
11 |
12 | fun encrypt(clientIdentification: ClientIdentification): ClientIdentification {
13 | var result = clientIdentification
14 | if (secret != null) {
15 | val encryptedClientIdentification =
16 | crypto.encrypt(clientIdentification.clientId, secret!!)
17 | result = result.copy(
18 | encryptedClientId = encryptedClientIdentification["encryptedValue"],
19 | salt = encryptedClientIdentification["salt"],
20 | iv = encryptedClientIdentification["iv"]
21 | )
22 | }
23 | return result
24 | }
25 |
26 | fun decrypt(encryptedClientId: String, salt: String, iv: String): String? {
27 | return secret?.let {
28 | crypto.decrypt(encryptedClientId, it, salt, iv)
29 | }
30 | }
31 | }
--------------------------------------------------------------------------------
/core/src/main/java/com/emarsys/core/database/helper/AbstractDbHelper.kt:
--------------------------------------------------------------------------------
1 | package com.emarsys.core.database.helper
2 |
3 | import android.content.Context
4 | import android.database.sqlite.SQLiteOpenHelper
5 | import android.database.sqlite.SQLiteDatabase
6 | import com.emarsys.core.database.CoreSQLiteDatabase
7 | import com.emarsys.core.database.DelegatingCoreSQLiteDatabase
8 | import com.emarsys.core.database.trigger.TriggerKey
9 | import com.emarsys.core.util.Assert
10 |
11 | abstract class AbstractDbHelper(
12 | context: Context,
13 | databaseName: String,
14 | databaseVersion: Int,
15 | private val triggerMap: MutableMap>) : SQLiteOpenHelper(context, databaseName, null, databaseVersion), DbHelper {
16 |
17 | override val readableCoreDatabase: CoreSQLiteDatabase
18 | get() = DelegatingCoreSQLiteDatabase(super.getReadableDatabase(), triggerMap)
19 | override val writableCoreDatabase: CoreSQLiteDatabase
20 | get() = DelegatingCoreSQLiteDatabase(super.getWritableDatabase(), triggerMap)
21 |
22 |
23 | }
--------------------------------------------------------------------------------
/core/src/main/java/com/emarsys/core/database/helper/CoreDbHelper.kt:
--------------------------------------------------------------------------------
1 | package com.emarsys.core.database.helper
2 |
3 | import android.content.Context
4 | import android.database.sqlite.SQLiteDatabase
5 | import com.emarsys.core.Mockable
6 | import com.emarsys.core.database.DatabaseContract
7 | import com.emarsys.core.database.trigger.TriggerKey
8 |
9 | @Mockable
10 | class CoreDbHelper(context: Context, triggerMap: MutableMap>) : AbstractDbHelper(context, DATABASE_NAME, DATABASE_VERSION, triggerMap) {
11 | companion object {
12 | const val DATABASE_VERSION = 5
13 | const val DATABASE_NAME = "EmarsysCore.db"
14 | }
15 |
16 | override fun onCreate(db: SQLiteDatabase) {
17 | onUpgrade(db, 0, DATABASE_VERSION)
18 | }
19 |
20 | override fun onUpgrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) {
21 | for (i in oldVersion until newVersion) {
22 | for (sqlCommand in DatabaseContract.MIGRATION[i]) {
23 | db.execSQL(sqlCommand)
24 | }
25 | }
26 | }
27 | }
--------------------------------------------------------------------------------
/core/src/main/java/com/emarsys/core/database/helper/DbHelper.kt:
--------------------------------------------------------------------------------
1 | package com.emarsys.core.database.helper
2 |
3 | import com.emarsys.core.database.CoreSQLiteDatabase
4 |
5 | interface DbHelper {
6 | val readableCoreDatabase: CoreSQLiteDatabase
7 | val writableCoreDatabase: CoreSQLiteDatabase
8 | }
--------------------------------------------------------------------------------
/core/src/main/java/com/emarsys/core/database/repository/AbstractSqlSpecification.kt:
--------------------------------------------------------------------------------
1 | package com.emarsys.core.database.repository
2 |
3 | abstract class AbstractSqlSpecification : SqlSpecification {
4 | override val isDistinct: Boolean
5 | get() = false
6 | override val columns: Array?
7 | get() = null
8 | override val selection: String?
9 | get() = null
10 | override val selectionArgs: Array?
11 | get() = null
12 | override val groupBy: String?
13 | get() = null
14 | override val having: String?
15 | get() = null
16 | override val orderBy: String?
17 | get() = null
18 | override val limit: String?
19 | get() = null
20 | }
--------------------------------------------------------------------------------
/core/src/main/java/com/emarsys/core/database/repository/Repository.kt:
--------------------------------------------------------------------------------
1 | package com.emarsys.core.database.repository
2 |
3 | interface Repository {
4 | fun add(item: T)
5 | fun update(item: T, specification: SqlSpecification): Int
6 | fun remove(specification: S)
7 | fun query(specification: S): List
8 | fun isEmpty(): Boolean
9 | }
--------------------------------------------------------------------------------
/core/src/main/java/com/emarsys/core/database/repository/SqlSpecification.kt:
--------------------------------------------------------------------------------
1 | package com.emarsys.core.database.repository
2 |
3 | interface SqlSpecification {
4 | val isDistinct: Boolean
5 | val columns: Array?
6 | val selection: String?
7 | val selectionArgs: Array?
8 | val groupBy: String?
9 | val having: String?
10 | val orderBy: String?
11 | val limit: String?
12 | }
--------------------------------------------------------------------------------
/core/src/main/java/com/emarsys/core/database/repository/specification/Everything.kt:
--------------------------------------------------------------------------------
1 | package com.emarsys.core.database.repository.specification
2 |
3 | import com.emarsys.core.database.repository.AbstractSqlSpecification
4 |
5 |
6 | class Everything : AbstractSqlSpecification()
--------------------------------------------------------------------------------
/core/src/main/java/com/emarsys/core/database/trigger/TriggerEvent.kt:
--------------------------------------------------------------------------------
1 | package com.emarsys.core.database.trigger
2 |
3 | enum class TriggerEvent {
4 | INSERT, DELETE, UPDATE
5 | }
--------------------------------------------------------------------------------
/core/src/main/java/com/emarsys/core/database/trigger/TriggerKey.kt:
--------------------------------------------------------------------------------
1 | package com.emarsys.core.database.trigger
2 |
3 | class TriggerKey(val tableName: String, val triggerType: TriggerType, val triggerEvent: TriggerEvent) {
4 | override fun equals(other: Any?): Boolean {
5 | if (this === other) return true
6 | if (other == null || javaClass != other.javaClass) return false
7 | val that = other as TriggerKey
8 | if (tableName != that.tableName) return false
9 | return if (triggerType != that.triggerType) false else triggerEvent === that.triggerEvent
10 | }
11 |
12 | override fun hashCode(): Int {
13 | var result = tableName.hashCode()
14 | result = 31 * result + triggerType.hashCode()
15 | result = 31 * result + triggerEvent.hashCode()
16 | return result
17 | }
18 | }
--------------------------------------------------------------------------------
/core/src/main/java/com/emarsys/core/database/trigger/TriggerType.kt:
--------------------------------------------------------------------------------
1 | package com.emarsys.core.database.trigger
2 |
3 | enum class TriggerType {
4 | BEFORE, AFTER
5 | }
--------------------------------------------------------------------------------
/core/src/main/java/com/emarsys/core/device/ClientIdentification.kt:
--------------------------------------------------------------------------------
1 | package com.emarsys.core.device
2 |
3 | import com.emarsys.core.Mockable
4 |
5 | @Mockable
6 | data class ClientIdentification(
7 | val clientId: String,
8 | val encryptedClientId: String? = null,
9 | val salt: String? = null,
10 | val iv: String? = null
11 | )
--------------------------------------------------------------------------------
/core/src/main/java/com/emarsys/core/device/FilterByClientId.kt:
--------------------------------------------------------------------------------
1 | package com.emarsys.core.device
2 |
3 | import com.emarsys.core.database.repository.AbstractSqlSpecification
4 |
5 | data class FilterByClientId(
6 | private val arg: String,
7 | override val selection: String = "hardware_id=?") : AbstractSqlSpecification() {
8 |
9 | override val selectionArgs: Array
10 | get() = arrayOf(arg)
11 | }
--------------------------------------------------------------------------------
/core/src/main/java/com/emarsys/core/device/LanguageProvider.java:
--------------------------------------------------------------------------------
1 | package com.emarsys.core.device;
2 |
3 | import com.emarsys.core.Mockable;
4 | import com.emarsys.core.util.Assert;
5 |
6 | import java.util.Locale;
7 |
8 | @Mockable
9 | public class LanguageProvider {
10 |
11 | public String provideLanguage(Locale locale) {
12 | Assert.notNull(locale, "Locale must not be null!");
13 | return locale.toLanguageTag();
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/core/src/main/java/com/emarsys/core/di/Container.kt:
--------------------------------------------------------------------------------
1 | package com.emarsys.core.di
2 |
3 |
4 | object Container
--------------------------------------------------------------------------------
/core/src/main/java/com/emarsys/core/endpoint/Endpoint.kt:
--------------------------------------------------------------------------------
1 | package com.emarsys.core.endpoint
2 |
3 | object Endpoint {
4 | const val LOG_URL = "https://log-dealer.eservice.emarsys.net/v1/log"
5 |
6 | const val REMOTE_CONFIG_URL = "https://mobile-sdk-config.gservice.emarsys.net"
7 |
8 | fun remoteConfigUrl(applicationCode: String?): String {
9 | return "$REMOTE_CONFIG_URL/$applicationCode"
10 | }
11 |
12 | fun remoteConfigSignatureUrl(applicationCode: String?): String {
13 | return "$REMOTE_CONFIG_URL/signature/$applicationCode"
14 | }
15 | }
--------------------------------------------------------------------------------
/core/src/main/java/com/emarsys/core/endpoint/ServiceEndpointProvider.kt:
--------------------------------------------------------------------------------
1 | package com.emarsys.core.endpoint
2 |
3 | import com.emarsys.core.Mockable
4 | import com.emarsys.core.storage.Storage
5 |
6 | @Mockable
7 | class ServiceEndpointProvider(private val serviceUrlStorage: Storage, private val defaultEndpoint: String) {
8 |
9 | fun provideEndpointHost(): String {
10 | return serviceUrlStorage.get() ?: defaultEndpoint
11 | }
12 | }
--------------------------------------------------------------------------------
/core/src/main/java/com/emarsys/core/feature/FeatureRegistry.kt:
--------------------------------------------------------------------------------
1 | package com.emarsys.core.feature
2 |
3 | import com.emarsys.core.api.experimental.FlipperFeature
4 | import java.util.*
5 |
6 | object FeatureRegistry {
7 | @JvmField
8 | var enabledFeatures: MutableSet = HashSet()
9 |
10 | @JvmStatic
11 | fun isFeatureEnabled(feature: FlipperFeature): Boolean {
12 | return enabledFeatures.contains(feature.featureName)
13 | }
14 |
15 | @JvmStatic
16 | fun enableFeature(feature: FlipperFeature) {
17 | enabledFeatures.add(feature.featureName)
18 | }
19 |
20 | @JvmStatic
21 | fun reset() {
22 | enabledFeatures.clear()
23 | }
24 |
25 | @JvmStatic
26 | fun disableFeature(feature: FlipperFeature) {
27 | enabledFeatures.remove(feature.featureName)
28 | }
29 | }
--------------------------------------------------------------------------------
/core/src/main/java/com/emarsys/core/handler/SdkHandler.kt:
--------------------------------------------------------------------------------
1 | package com.emarsys.core.handler
2 |
3 | import android.os.Handler
4 | import com.emarsys.core.Mockable
5 |
6 | @Mockable
7 | class SdkHandler(val handler: Handler) {
8 | fun post(runnable: Runnable): Boolean {
9 | return handler.post(runnable)
10 | }
11 |
12 | fun postDelayed(runnable: Runnable, delay: Long) {
13 | handler.postDelayed(runnable, delay)
14 | }
15 |
16 | fun remove(runnable: Runnable) {
17 | handler.removeCallbacks(runnable)
18 | }
19 |
20 | }
--------------------------------------------------------------------------------
/core/src/main/java/com/emarsys/core/observer/Observer.kt:
--------------------------------------------------------------------------------
1 | package com.emarsys.core.observer
2 |
3 | interface Observer {
4 |
5 | fun register(callback: (T) -> Unit)
6 | fun unregister(callback: (T) -> Unit)
7 | fun notify(value: T)
8 |
9 | }
--------------------------------------------------------------------------------
/core/src/main/java/com/emarsys/core/permission/PermissionChecker.kt:
--------------------------------------------------------------------------------
1 | package com.emarsys.core.permission
2 |
3 | import android.content.Context
4 | import androidx.core.content.ContextCompat
5 | import com.emarsys.core.Mockable
6 |
7 | @Mockable
8 | class PermissionChecker(private val context: Context) {
9 | fun checkSelfPermission(permission: String): Int {
10 | return ContextCompat.checkSelfPermission(context, permission)
11 | }
12 | }
--------------------------------------------------------------------------------
/core/src/main/java/com/emarsys/core/provider/Gettable.java:
--------------------------------------------------------------------------------
1 | package com.emarsys.core.provider;
2 |
3 | public interface Gettable {
4 |
5 | T get();
6 |
7 | }
8 |
--------------------------------------------------------------------------------
/core/src/main/java/com/emarsys/core/provider/Property.java:
--------------------------------------------------------------------------------
1 | package com.emarsys.core.provider;
2 |
3 | public interface Property extends Gettable, Settable {
4 | }
5 |
--------------------------------------------------------------------------------
/core/src/main/java/com/emarsys/core/provider/Settable.java:
--------------------------------------------------------------------------------
1 | package com.emarsys.core.provider;
2 |
3 | public interface Settable {
4 |
5 | void set(T value);
6 |
7 | }
8 |
--------------------------------------------------------------------------------
/core/src/main/java/com/emarsys/core/provider/random/RandomProvider.kt:
--------------------------------------------------------------------------------
1 | package com.emarsys.core.provider.random
2 |
3 | import com.emarsys.core.Mockable
4 | import kotlin.random.Random
5 |
6 | @Mockable
7 | class RandomProvider {
8 |
9 | fun provideDouble(until: Double): Double {
10 | return Random.nextDouble(until)
11 | }
12 | }
--------------------------------------------------------------------------------
/core/src/main/java/com/emarsys/core/provider/timestamp/TimestampProvider.java:
--------------------------------------------------------------------------------
1 | package com.emarsys.core.provider.timestamp;
2 |
3 | import com.emarsys.core.Mockable;
4 |
5 | @Mockable
6 | public class TimestampProvider {
7 | public long provideTimestamp() {
8 | return System.currentTimeMillis();
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/core/src/main/java/com/emarsys/core/provider/uuid/UUIDProvider.java:
--------------------------------------------------------------------------------
1 | package com.emarsys.core.provider.uuid;
2 |
3 | import com.emarsys.core.Mockable;
4 |
5 | import java.util.UUID;
6 |
7 | @Mockable
8 | public class UUIDProvider {
9 |
10 | public String provideId() {
11 | return UUID.randomUUID().toString();
12 | }
13 | }
--------------------------------------------------------------------------------
/core/src/main/java/com/emarsys/core/provider/version/VersionProvider.java:
--------------------------------------------------------------------------------
1 | package com.emarsys.core.provider.version;
2 |
3 | import com.emarsys.core.BuildConfig;
4 | import com.emarsys.core.Mockable;
5 |
6 | @Mockable
7 | public class VersionProvider {
8 |
9 | public String provideSdkVersion() {
10 | return BuildConfig.VERSION_NAME;
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/core/src/main/java/com/emarsys/core/provider/wrapper/WrapperInfoContainer.kt:
--------------------------------------------------------------------------------
1 | package com.emarsys.core.provider.wrapper
2 |
3 | object WrapperInfoContainer {
4 |
5 | var wrapperInfo: String? = null
6 | }
--------------------------------------------------------------------------------
/core/src/main/java/com/emarsys/core/request/RequestExpiredException.java:
--------------------------------------------------------------------------------
1 | package com.emarsys.core.request;
2 |
3 | public class RequestExpiredException extends Exception {
4 | private final String endpoint;
5 |
6 | public RequestExpiredException(String message, String endpoint) {
7 | super(message);
8 | this.endpoint = endpoint;
9 | }
10 |
11 | public String getEndpoint() {
12 | return endpoint;
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/core/src/main/java/com/emarsys/core/request/factory/CompletionHandlerProxyProvider.kt:
--------------------------------------------------------------------------------
1 | package com.emarsys.core.request.factory
2 |
3 | import com.emarsys.core.CoreCompletionHandler
4 | import com.emarsys.core.worker.Worker
5 |
6 | interface CompletionHandlerProxyProvider {
7 | fun provideProxy(worker: Worker?, completionHandler: CoreCompletionHandler?): CoreCompletionHandler
8 | }
--------------------------------------------------------------------------------
/core/src/main/java/com/emarsys/core/request/factory/DefaultRunnableFactory.java:
--------------------------------------------------------------------------------
1 | package com.emarsys.core.request.factory;
2 |
3 | public class DefaultRunnableFactory implements RunnableFactory {
4 | @Override
5 | public Runnable runnableFrom(Runnable runnable) {
6 | return runnable;
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/core/src/main/java/com/emarsys/core/request/factory/RunnableFactory.java:
--------------------------------------------------------------------------------
1 | package com.emarsys.core.request.factory;
2 |
3 | public interface RunnableFactory {
4 | Runnable runnableFrom(Runnable runnable);
5 | }
6 |
--------------------------------------------------------------------------------
/core/src/main/java/com/emarsys/core/request/model/RequestMethod.java:
--------------------------------------------------------------------------------
1 | package com.emarsys.core.request.model;
2 |
3 | public enum RequestMethod {
4 | GET, POST, PUT, DELETE
5 | }
6 |
--------------------------------------------------------------------------------
/core/src/main/java/com/emarsys/core/request/model/specification/FilterByRequestIds.kt:
--------------------------------------------------------------------------------
1 | package com.emarsys.core.request.model.specification
2 |
3 | import com.emarsys.core.database.DatabaseContract
4 | import com.emarsys.core.database.repository.AbstractSqlSpecification
5 | import com.emarsys.core.util.DatabaseUtil
6 |
7 | class FilterByRequestIds(private val args: Array) : AbstractSqlSpecification() {
8 |
9 | override val selection: String
10 | get() = DatabaseUtil.generateInStatement(DatabaseContract.REQUEST_COLUMN_NAME_REQUEST_ID, args)
11 |
12 | override val selectionArgs: Array
13 | get() = args
14 | }
--------------------------------------------------------------------------------
/core/src/main/java/com/emarsys/core/request/model/specification/FilterByUrlPattern.java:
--------------------------------------------------------------------------------
1 | package com.emarsys.core.request.model.specification;
2 |
3 | import com.emarsys.core.database.DatabaseContract;
4 | import com.emarsys.core.database.repository.AbstractSqlSpecification;
5 | import com.emarsys.core.util.Assert;
6 |
7 | public class FilterByUrlPattern extends AbstractSqlSpecification {
8 |
9 | private final String pattern;
10 |
11 | public FilterByUrlPattern(String pattern) {
12 | Assert.notNull(pattern, "Pattern must not be null!");
13 | this.pattern = pattern;
14 | }
15 |
16 | @Override
17 | public String getSelection() {
18 | return DatabaseContract.REQUEST_COLUMN_NAME_URL + " LIKE ?";
19 | }
20 |
21 | @Override
22 | public String[] getSelectionArgs() {
23 | return new String[]{pattern};
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/core/src/main/java/com/emarsys/core/request/model/specification/QueryLatestRequestModel.java:
--------------------------------------------------------------------------------
1 | package com.emarsys.core.request.model.specification;
2 |
3 | import com.emarsys.core.database.repository.AbstractSqlSpecification;
4 |
5 | public class QueryLatestRequestModel extends AbstractSqlSpecification {
6 |
7 | public QueryLatestRequestModel() {
8 | }
9 |
10 | @Override
11 | public String getOrderBy() {
12 | return "ROWID ASC";
13 | }
14 |
15 | @Override
16 | public String getLimit() {
17 | return "1";
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/core/src/main/java/com/emarsys/core/response/AbstractResponseHandler.kt:
--------------------------------------------------------------------------------
1 | package com.emarsys.core.response
2 |
3 | abstract class AbstractResponseHandler {
4 |
5 | fun processResponse(responseModel: ResponseModel) {
6 | if (shouldHandleResponse(responseModel)) {
7 | handleResponse(responseModel)
8 | }
9 | }
10 |
11 | abstract fun shouldHandleResponse(responseModel: ResponseModel): Boolean
12 |
13 | abstract fun handleResponse(responseModel: ResponseModel)
14 |
15 | }
16 |
--------------------------------------------------------------------------------
/core/src/main/java/com/emarsys/core/session/Session.kt:
--------------------------------------------------------------------------------
1 | package com.emarsys.core.session
2 |
3 | import com.emarsys.core.api.result.CompletionListener
4 |
5 | interface Session {
6 | fun startSession(completionListener: CompletionListener)
7 | fun endSession(completionListener: CompletionListener)
8 | }
--------------------------------------------------------------------------------
/core/src/main/java/com/emarsys/core/storage/AbstractStorage.java:
--------------------------------------------------------------------------------
1 | package com.emarsys.core.storage;
2 |
3 | import androidx.annotation.Nullable;
4 |
5 | import com.emarsys.core.util.Assert;
6 |
7 | public abstract class AbstractStorage implements PersistentStorage {
8 |
9 | private final S store;
10 |
11 | private T value;
12 |
13 | public AbstractStorage(S store) {
14 | Assert.notNull(store, "Store must not be null!");
15 |
16 | this.store = store;
17 | }
18 |
19 | @Override
20 | public void set(T item) {
21 | value = item;
22 | persistValue(store, item);
23 | }
24 |
25 | @Override
26 | public @Nullable T get() {
27 | value = value != null ? value : readPersistedValue(store);
28 | return value;
29 | }
30 |
31 | @Override
32 | public void remove() {
33 | value = null;
34 | removePersistedValue(store);
35 | }
36 |
37 | }
38 |
--------------------------------------------------------------------------------
/core/src/main/java/com/emarsys/core/storage/BooleanStorage.kt:
--------------------------------------------------------------------------------
1 | package com.emarsys.core.storage
2 |
3 | import android.content.SharedPreferences
4 |
5 | class BooleanStorage(key: StorageKey, store: SharedPreferences) : AbstractStorage(store) {
6 | val key: String = key.key
7 |
8 | override fun persistValue(store: SharedPreferences, value: Boolean) {
9 | store.edit().putBoolean(key, value).apply()
10 | }
11 |
12 | override fun readPersistedValue(store: SharedPreferences): Boolean {
13 | return store.getBoolean(key, false)
14 | }
15 |
16 | override fun removePersistedValue(store: SharedPreferences) {
17 | store.edit().remove(key).apply()
18 | }
19 | }
--------------------------------------------------------------------------------
/core/src/main/java/com/emarsys/core/storage/CoreStorageKey.kt:
--------------------------------------------------------------------------------
1 | package com.emarsys.core.storage
2 |
3 | import java.util.*
4 |
5 | enum class CoreStorageKey : StorageKey {
6 | HARDWARE_ID,
7 | LOG_LEVEL;
8 |
9 | override fun getKey(): String {
10 | return "core_" + name.lowercase(Locale.getDefault())
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/core/src/main/java/com/emarsys/core/storage/KeyValueStore.java:
--------------------------------------------------------------------------------
1 | package com.emarsys.core.storage;
2 |
3 | public interface KeyValueStore {
4 |
5 | void putString(String key, String value);
6 |
7 | void putInt(String key, int value);
8 |
9 | void putLong(String key, long value);
10 |
11 | void putFloat(String key, float value);
12 |
13 | void putDouble(String key, double value);
14 |
15 | void putBoolean(String key, boolean value);
16 |
17 | String getString(String key);
18 |
19 | int getInt(String key);
20 |
21 | long getLong(String key);
22 |
23 | float getFloat(String key);
24 |
25 | double getDouble(String key);
26 |
27 | boolean getBoolean(String key);
28 |
29 | void remove(String key);
30 |
31 | void clear();
32 |
33 | int getSize();
34 |
35 | boolean isEmpty();
36 | }
37 |
--------------------------------------------------------------------------------
/core/src/main/java/com/emarsys/core/storage/PersistentStorage.java:
--------------------------------------------------------------------------------
1 | package com.emarsys.core.storage;
2 |
3 | public interface PersistentStorage extends Storage{
4 | void persistValue(S store, T value);
5 |
6 | T readPersistedValue(S store);
7 |
8 | void removePersistedValue(S store);
9 | }
10 |
--------------------------------------------------------------------------------
/core/src/main/java/com/emarsys/core/storage/SharedPreferencesV3Provider.kt:
--------------------------------------------------------------------------------
1 | package com.emarsys.core.storage
2 |
3 | import android.content.Context
4 | import android.content.SharedPreferences
5 | import com.emarsys.core.crypto.SharedPreferenceCrypto
6 |
7 | class SharedPreferencesV3Provider(
8 | context: Context,
9 | fileName: String,
10 | oldSharedPreferences: SharedPreferences,
11 | crypto: SharedPreferenceCrypto,
12 | migration: EncryptedSharedPreferencesToSharedPreferencesMigration
13 | ) {
14 |
15 | private var sharedPreferences: SharedPreferences =
16 | EmarsysEncryptedSharedPreferencesV3(context, fileName, crypto)
17 |
18 | init {
19 | migration.migrate(oldSharedPreferences, sharedPreferences)
20 | }
21 |
22 | fun provide(): SharedPreferences {
23 | return sharedPreferences
24 | }
25 | }
--------------------------------------------------------------------------------
/core/src/main/java/com/emarsys/core/storage/Storage.java:
--------------------------------------------------------------------------------
1 | package com.emarsys.core.storage;
2 |
3 | public interface Storage {
4 |
5 | void set(T value);
6 |
7 | T get();
8 |
9 | void remove();
10 |
11 | }
12 |
--------------------------------------------------------------------------------
/core/src/main/java/com/emarsys/core/storage/StorageKey.java:
--------------------------------------------------------------------------------
1 | package com.emarsys.core.storage;
2 |
3 | public interface StorageKey {
4 | String getKey();
5 | }
6 |
--------------------------------------------------------------------------------
/core/src/main/java/com/emarsys/core/storage/StringStorage.java:
--------------------------------------------------------------------------------
1 | package com.emarsys.core.storage;
2 |
3 | import android.content.SharedPreferences;
4 |
5 | import com.emarsys.core.util.Assert;
6 |
7 | public class StringStorage extends AbstractStorage {
8 |
9 | private final String key;
10 |
11 | public StringStorage(StorageKey key, SharedPreferences store) {
12 | super(store);
13 | Assert.notNull(key, "Key must not be null!");
14 | Assert.notNull(store, "Store must not be null!");
15 | Assert.notNull(key.getKey(), "Key.getKey() must not be null!");
16 |
17 | this.key = key.getKey();
18 | }
19 |
20 | @Override
21 | public void persistValue(SharedPreferences store, String value) {
22 | store.edit().putString(key, value).apply();
23 | }
24 |
25 | @Override
26 | public String readPersistedValue(SharedPreferences store) {
27 | return store.getString(key, null);
28 | }
29 |
30 | @Override
31 | public void removePersistedValue(SharedPreferences store) {
32 | store.edit().remove(key).apply();
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/core/src/main/java/com/emarsys/core/util/CastExtensions.kt:
--------------------------------------------------------------------------------
1 | package com.emarsys.core.util
2 |
3 | inline fun Any.tryCastOrNull(): T? {
4 | if (this is T) {
5 | return this
6 | }
7 | return null
8 | }
9 | inline fun Any.tryCastOrException(): T {
10 | if (this is T) {
11 | return this
12 | }
13 | throw ClassCastException()
14 | }
15 |
--------------------------------------------------------------------------------
/core/src/main/java/com/emarsys/core/util/DatabaseUtil.java:
--------------------------------------------------------------------------------
1 | package com.emarsys.core.util;
2 |
3 | public class DatabaseUtil {
4 |
5 | public static String generateInStatement(String columnName, String[] args) {
6 | StringBuilder sb = new StringBuilder(columnName + " IN (?");
7 | for (int i = 1; i < args.length; i++) {
8 | sb.append(", ?");
9 | }
10 | sb.append(")");
11 | return sb.toString();
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/core/src/main/java/com/emarsys/core/util/ExceptionExtensions.kt:
--------------------------------------------------------------------------------
1 | package com.emarsys.core.util
2 |
3 | fun Exception.rootCause(): Throwable? {
4 | var rootCause = this.cause
5 | while (rootCause?.cause != null && rootCause.cause != rootCause) {
6 | rootCause = rootCause.cause
7 | }
8 | return rootCause
9 | }
--------------------------------------------------------------------------------
/core/src/main/java/com/emarsys/core/util/HeaderUtils.java:
--------------------------------------------------------------------------------
1 | package com.emarsys.core.util;
2 |
3 | import android.util.Base64;
4 |
5 | public class HeaderUtils {
6 |
7 | private HeaderUtils() {
8 | }
9 |
10 | public static String createBasicAuth(String username) {
11 | Assert.notNull(username, "Username must not be null!");
12 | String credentials = username + ":";
13 | return "Basic " + Base64.encodeToString(credentials.getBytes(), Base64.NO_WRAP);
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/core/src/main/java/com/emarsys/core/util/MapExtensions.kt:
--------------------------------------------------------------------------------
1 | package com.emarsys.core.util
2 |
3 | fun Map.getCaseInsensitive(key: String?): T? {
4 | var result: T? = null
5 | for (originalKey in this.keys) {
6 | if (originalKey?.lowercase() == key?.lowercase()) {
7 | result = this[originalKey]
8 | break
9 | }
10 | }
11 |
12 | return result
13 | }
14 |
15 | fun Map.filterNotNull(): Map {
16 | val result = HashMap()
17 | filter { it.key != null && it.value != null }.forEach {
18 | result[it.key!!] = it.value!!
19 | }
20 | return result
21 | }
--------------------------------------------------------------------------------
/core/src/main/java/com/emarsys/core/util/RetryUtil.kt:
--------------------------------------------------------------------------------
1 | package com.emarsys.core.util
2 |
3 | object RetryUtil {
4 |
5 | fun retry(times: Int = 3, retryInterval: Long = 1000, action: () -> Unit) {
6 | try {
7 | action.invoke()
8 | } catch (e: Throwable) {
9 | if (times > 0) {
10 | Thread.sleep(retryInterval)
11 | retry(times - 1, retryInterval, action)
12 | } else {
13 | throw e
14 | }
15 | }
16 | }
17 | }
--------------------------------------------------------------------------------
/core/src/main/java/com/emarsys/core/util/StringExtensions.kt:
--------------------------------------------------------------------------------
1 | package com.emarsys.core.util
2 |
3 | import java.util.*
4 |
5 | val camelRegex = "(?<=[a-zA-Z])[A-Z]".toRegex()
6 | val snakeRegex = "_[a-zA-Z]".toRegex()
7 |
8 |
9 | fun String.camelToLowerSnakeCase(): String {
10 | return camelRegex.replace(this) {
11 | "_${it.value}"
12 | }.lowercase(Locale.getDefault())
13 | }
14 |
15 | fun String.camelToUpperSnakeCase(): String {
16 | return camelRegex.replace(this) {
17 | "_${it.value}"
18 | }.uppercase(Locale.getDefault())
19 | }
20 |
21 | fun String.snakeToLowerCamelCase(): String {
22 | return snakeRegex.replace(this) {
23 | it.value.replace("_", "")
24 | .uppercase(Locale.getDefault())
25 | }
26 | }
--------------------------------------------------------------------------------
/core/src/main/java/com/emarsys/core/util/SystemUtils.java:
--------------------------------------------------------------------------------
1 | package com.emarsys.core.util;
2 |
3 | public class SystemUtils {
4 |
5 | public static boolean isClassFound(String className) {
6 | boolean classExists = true;
7 |
8 | try {
9 | Class.forName(className);
10 | } catch (ClassNotFoundException ignored) {
11 | classExists = false;
12 | }
13 |
14 | return classExists;
15 | }
16 |
17 | public static String getCallerMethodName() {
18 | return Thread.currentThread().getStackTrace()[3].getMethodName();
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/core/src/main/java/com/emarsys/core/util/TimestampUtils.java:
--------------------------------------------------------------------------------
1 | package com.emarsys.core.util;
2 |
3 | import java.text.SimpleDateFormat;
4 | import java.util.Date;
5 | import java.util.Locale;
6 | import java.util.TimeZone;
7 |
8 | public class TimestampUtils {
9 |
10 | private static final String DATE_FORMAT = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'";
11 | private static final SimpleDateFormat formatter = new SimpleDateFormat(DATE_FORMAT, Locale.ENGLISH);
12 |
13 | private TimestampUtils() {
14 | }
15 |
16 | public static String formatTimestampWithUTC(long timestamp) {
17 | formatter.setTimeZone(TimeZone.getTimeZone("UTC"));
18 | Date date = new Date(timestamp);
19 | return formatter.format(date);
20 | }
21 |
22 | }
--------------------------------------------------------------------------------
/core/src/main/java/com/emarsys/core/util/log/LogLevel.kt:
--------------------------------------------------------------------------------
1 | package com.emarsys.core.util.log
2 |
3 | enum class LogLevel(val priority: Int) {
4 | TRACE(1),
5 | DEBUG(2),
6 | INFO(3),
7 | WARN(4),
8 | ERROR(5),
9 | METRIC(6)
10 | }
--------------------------------------------------------------------------------
/core/src/main/java/com/emarsys/core/util/log/entry/AppEventLog.kt:
--------------------------------------------------------------------------------
1 | package com.emarsys.core.util.log.entry
2 |
3 | class AppEventLog(val eventName: String, eventAttributes: Map? = null) : LogEntry {
4 | override val topic: String
5 | get() = "log_app_event"
6 | override val data: MutableMap
7 |
8 | init {
9 | data = mutableMapOf(
10 | "eventName" to eventName
11 | )
12 | if (!eventAttributes.isNullOrEmpty()) {
13 | data["eventAttributes"] = eventAttributes
14 | }
15 | }
16 | }
--------------------------------------------------------------------------------
/core/src/main/java/com/emarsys/core/util/log/entry/CrashLog.kt:
--------------------------------------------------------------------------------
1 | package com.emarsys.core.util.log.entry
2 |
3 | import java.util.*
4 |
5 | class CrashLog(val throwable: Throwable?, additionalInformation: String? = null) : LogEntry {
6 | override val data: Map
7 | override val topic: String
8 | get() = "log_crash"
9 |
10 | init {
11 | data = if (throwable != null) {
12 | mapOf(
13 | "exception" to throwable.javaClass.name,
14 | "reason" to throwable.message,
15 | "additionalInformation" to additionalInformation,
16 | "stackTrace" to getStackTrace(throwable)
17 | ).filterValues { it != null }
18 | } else {
19 | emptyMap()
20 | }
21 | }
22 |
23 | private fun getStackTrace(throwable: Throwable): List {
24 | val stackTrace = throwable.stackTrace
25 | val size = stackTrace.size
26 | val result: MutableList = ArrayList(size)
27 | for (element in stackTrace) {
28 | result.add(element.toString())
29 | }
30 | return result
31 | }
32 | }
--------------------------------------------------------------------------------
/core/src/main/java/com/emarsys/core/util/log/entry/InAppLoadingTime.kt:
--------------------------------------------------------------------------------
1 | package com.emarsys.core.util.log.entry
2 |
3 | import java.io.Serializable
4 |
5 | data class InAppLoadingTime(val startTime: Long, val endTime: Long): Serializable
--------------------------------------------------------------------------------
/core/src/main/java/com/emarsys/core/util/log/entry/LogEntry.kt:
--------------------------------------------------------------------------------
1 | package com.emarsys.core.util.log.entry
2 |
3 | import com.emarsys.core.util.log.LogLevel
4 |
5 | interface LogEntry {
6 | val topic: String
7 | val data: Map
8 | }
9 |
10 | fun LogEntry.toData(logLevel: LogLevel, currentThreadName: String, wrapperInfo: String?) =
11 | mutableMapOf(
12 | "level" to logLevel.name,
13 | "thread" to currentThreadName
14 | ).apply {
15 | if (wrapperInfo != null) {
16 | put("wrapper", wrapperInfo)
17 | }
18 | putAll(data)
19 | }
20 |
21 | fun LogEntry.asString(): String {
22 | return "topic='$topic', data=$data"
23 | }
--------------------------------------------------------------------------------
/core/src/main/java/com/emarsys/core/util/log/entry/MethodNotAllowed.kt:
--------------------------------------------------------------------------------
1 | package com.emarsys.core.util.log.entry
2 |
3 | class MethodNotAllowed(klass: Class<*>, callerMethodName: String, parameters: Map?) : LogEntry {
4 |
5 | override val data: MutableMap
6 | override val topic: String
7 | get() = "log_method_not_allowed"
8 |
9 | init {
10 | data = mutableMapOf(
11 | "className" to klass.simpleName,
12 | "methodName" to callerMethodName
13 | )
14 | if (parameters != null) {
15 | data["parameters"] = parameters
16 | }
17 | }
18 | }
--------------------------------------------------------------------------------
/core/src/main/java/com/emarsys/core/util/log/entry/OfflineQueueSize.kt:
--------------------------------------------------------------------------------
1 | package com.emarsys.core.util.log.entry
2 |
3 | class OfflineQueueSize(queueSize: Int) : LogEntry {
4 | override val data: Map
5 | override val topic: String
6 | get() = "log_offline_queue_size"
7 |
8 | init {
9 | data = mapOf(
10 | "queueSize" to queueSize
11 | )
12 | }
13 | }
--------------------------------------------------------------------------------
/core/src/main/java/com/emarsys/core/util/log/entry/OnScreenTime.kt:
--------------------------------------------------------------------------------
1 | package com.emarsys.core.util.log.entry
2 |
3 |
4 | data class OnScreenTime(val duration: Long, val startTime: Long, val endTime: Long)
--------------------------------------------------------------------------------
/core/src/main/java/com/emarsys/core/util/log/entry/StatusLog.kt:
--------------------------------------------------------------------------------
1 | package com.emarsys.core.util.log.entry
2 |
3 | class StatusLog(klass: Class<*>, callerMethodName: String, parameters: Map?, status: Map? = null) : LogEntry {
4 | override val data: MutableMap
5 |
6 | override val topic: String
7 | get() = "log_status"
8 |
9 | init {
10 | data = mutableMapOf(
11 | "className" to klass.simpleName,
12 | "methodName" to callerMethodName
13 | )
14 | if (parameters != null) {
15 | data["parameters"] = parameters
16 | }
17 | if (status != null) {
18 | data["status"] = status
19 | }
20 | }
21 | }
--------------------------------------------------------------------------------
/core/src/main/java/com/emarsys/core/util/predicate/ListSizeAtLeast.java:
--------------------------------------------------------------------------------
1 | package com.emarsys.core.util.predicate;
2 |
3 | import java.util.List;
4 |
5 | public class ListSizeAtLeast implements Predicate> {
6 | private final int count;
7 |
8 | public ListSizeAtLeast(int count) {
9 | this.count = count;
10 | }
11 |
12 | @Override
13 | public boolean evaluate(List input) {
14 | return input.size() >= count;
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/core/src/main/java/com/emarsys/core/util/predicate/Predicate.java:
--------------------------------------------------------------------------------
1 | package com.emarsys.core.util.predicate;
2 |
3 | public interface Predicate {
4 | boolean evaluate(T input);
5 | }
6 |
--------------------------------------------------------------------------------
/core/src/main/java/com/emarsys/core/util/serialization/SerializationException.java:
--------------------------------------------------------------------------------
1 | package com.emarsys.core.util.serialization;
2 |
3 | public class SerializationException extends Exception {
4 | }
5 |
--------------------------------------------------------------------------------
/core/src/main/java/com/emarsys/core/worker/DelegatorCompletionHandler.kt:
--------------------------------------------------------------------------------
1 | package com.emarsys.core.worker
2 |
3 | import android.os.Handler
4 | import com.emarsys.core.CoreCompletionHandler
5 | import com.emarsys.core.Mockable
6 | import com.emarsys.core.response.ResponseModel
7 |
8 | @Mockable
9 | class DelegatorCompletionHandler(val handler: Handler, val completionHandler: CoreCompletionHandler): CoreCompletionHandler {
10 |
11 | override fun onSuccess(id: String, responseModel: ResponseModel) {
12 | handler.post {
13 | completionHandler.onSuccess(id, responseModel)
14 | }
15 | }
16 |
17 | override fun onError(id: String, responseModel: ResponseModel) {
18 | handler.post {
19 | completionHandler.onError(id, responseModel)
20 | }
21 | }
22 |
23 | override fun onError(id: String, cause: Exception) {
24 | handler.post {
25 | completionHandler.onError(id, cause)
26 | }
27 | }
28 | }
--------------------------------------------------------------------------------
/core/src/main/java/com/emarsys/core/worker/DelegatorCompletionHandlerProvider.kt:
--------------------------------------------------------------------------------
1 | package com.emarsys.core.worker
2 |
3 | import android.os.Handler
4 | import com.emarsys.core.CoreCompletionHandler
5 | import com.emarsys.core.Mockable
6 |
7 | @Mockable
8 | class DelegatorCompletionHandlerProvider {
9 |
10 | fun provide(handler: Handler, completionHandler: CoreCompletionHandler): CoreCompletionHandler {
11 | return DelegatorCompletionHandler(handler, completionHandler)
12 | }
13 | }
--------------------------------------------------------------------------------
/core/src/main/java/com/emarsys/core/worker/Lockable.kt:
--------------------------------------------------------------------------------
1 | package com.emarsys.core.worker
2 |
3 | interface Lockable {
4 | fun lock()
5 | fun unlock()
6 | val isLocked: Boolean
7 | }
--------------------------------------------------------------------------------
/core/src/main/java/com/emarsys/core/worker/Worker.kt:
--------------------------------------------------------------------------------
1 | package com.emarsys.core.worker
2 |
3 | interface Worker : Lockable {
4 | fun run()
5 | }
--------------------------------------------------------------------------------
/docs/antora.yml:
--------------------------------------------------------------------------------
1 | name: android-emarsys-sdk
2 | title: Android Emarsys SDK
3 | version: master
4 | start_page: ROOT:index.adoc
5 | nav:
6 | - modules/ROOT/nav.adoc
7 |
--------------------------------------------------------------------------------
/docs/modules/ROOT/nav.adoc:
--------------------------------------------------------------------------------
1 | * https://github.com/emartech/android-emarsys-sdk/wiki[Api]
2 | * xref:push.adoc[Push]
3 |
--------------------------------------------------------------------------------
/docs/modules/ROOT/pages/index.adoc:
--------------------------------------------------------------------------------
1 | == Android Emarsys SDK
2 |
3 | https://github.com/emartech/android-emarsys-sdk/blob/master/README.md[README]
4 |
--------------------------------------------------------------------------------
/emarsys-e2e-test/src/androidTest/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/emarsys-e2e-test/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/emarsys-firebase/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/emarsys-huawei/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/emarsys-huawei/src/main/java/com/emarsys/HuaweiServiceChecker.kt:
--------------------------------------------------------------------------------
1 | package com.emarsys
2 |
3 | import android.content.Context
4 | import com.huawei.hms.api.ConnectionResult
5 | import com.huawei.hms.api.HuaweiApiAvailability
6 |
7 | class HuaweiServiceChecker {
8 |
9 | fun check(context: Context): Boolean {
10 | return HuaweiApiAvailability.getInstance()
11 | .isHuaweiMobileServicesAvailable(context) == ConnectionResult.SUCCESS
12 | }
13 | }
--------------------------------------------------------------------------------
/emarsys-sdk/gradle.properties:
--------------------------------------------------------------------------------
1 | sdkArtifactName=emarsys-sdk
--------------------------------------------------------------------------------
/emarsys-sdk/maven.gradle:
--------------------------------------------------------------------------------
1 | install {
2 | repositories.mavenInstaller {
3 | pom.project {
4 | packaging 'aar'
5 | groupId 'com.emarsys'
6 | artifactId project.sdkArtifactName
7 | name project.sdkArtifactName
8 | }
9 | }
10 | }
--------------------------------------------------------------------------------
/emarsys-sdk/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # By default, the flags in this file are appended to flags specified
3 | # in /Users/jpollak/Library/Android/sdk/tools/proguard/proguard-android.txt
4 | # You can edit the include path and order by changing the proguardFiles
5 | # directive in build.gradle.kts.
6 | #
7 | # For more details, see
8 | # http://developer.android.com/guide/developing/tools/proguard.html
9 |
10 | # Add any project specific keep options here:
11 |
12 | # If your project uses WebView with JS, uncomment the following
13 | # and specify the fully qualified class name to the JavaScript interface
14 | # class:
15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
16 | # public *;
17 | #}
18 | -keep public class com.emarsys.** { *; }
--------------------------------------------------------------------------------
/emarsys-sdk/src/androidTest/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/emarsys-sdk/src/androidTest/java/com/emarsys/di/EmarsysDependencyInjectionTest.kt:
--------------------------------------------------------------------------------
1 | package com.emarsys.di
2 |
3 | import com.emarsys.geofence.Geofence
4 | import com.emarsys.testUtil.IntegrationTestUtils
5 | import io.kotest.matchers.shouldBe
6 | import io.mockk.mockk
7 | import org.junit.After
8 | import org.junit.Test
9 |
10 | class EmarsysDependencyInjectionTest {
11 |
12 | @After
13 | fun tearDown() {
14 | IntegrationTestUtils.tearDownEmarsys()
15 | }
16 |
17 | @Test
18 | fun testGeofence_whenGooglePlayIsNotAvailable() {
19 | val geofenceApi: Geofence = mockk(relaxed = true)
20 | val loggingGeofenceApi: Geofence = mockk(relaxed = true)
21 |
22 | val dependencyContainer = FakeDependencyContainer(
23 | geofence = geofenceApi,
24 | loggingGeofence = loggingGeofenceApi,
25 | isGooglePlayServiceAvailable = false)
26 |
27 | setupEmarsysComponent(dependencyContainer)
28 |
29 | EmarsysDependencyInjection.geofence() shouldBe loggingGeofenceApi
30 | }
31 | }
--------------------------------------------------------------------------------
/emarsys-sdk/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
8 |
9 |
10 |
11 |
12 |
16 |
17 |
18 |
19 |
20 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/emarsys-sdk/src/main/java/com/emarsys/clientservice/ClientService.kt:
--------------------------------------------------------------------------------
1 | package com.emarsys.clientservice
2 |
3 | import com.emarsys.core.api.result.CompletionListener
4 | import com.emarsys.mobileengage.di.mobileEngage
5 |
6 | class ClientService(private val loggingInstance: Boolean = false) : ClientServiceApi {
7 | override fun trackDeviceInfo(completionListener: CompletionListener?) {
8 | return (if (loggingInstance) mobileEngage().loggingClientServiceInternal else mobileEngage().clientServiceInternal)
9 | .trackDeviceInfo(completionListener)
10 | }
11 | }
--------------------------------------------------------------------------------
/emarsys-sdk/src/main/java/com/emarsys/clientservice/ClientServiceApi.kt:
--------------------------------------------------------------------------------
1 | package com.emarsys.clientservice
2 |
3 | import com.emarsys.core.api.result.CompletionListener
4 |
5 | interface ClientServiceApi {
6 | fun trackDeviceInfo(completionListener: CompletionListener?)
7 | }
--------------------------------------------------------------------------------
/emarsys-sdk/src/main/java/com/emarsys/config/ConfigApi.kt:
--------------------------------------------------------------------------------
1 | package com.emarsys.config
2 |
3 | import com.emarsys.core.api.notification.NotificationSettings
4 | import com.emarsys.core.api.result.CompletionListener
5 |
6 | interface ConfigApi {
7 |
8 | val contactFieldId: Int?
9 |
10 | val applicationCode: String?
11 |
12 | val merchantId: String?
13 |
14 | @Deprecated("Use clientId instead")
15 | val hardwareId: String
16 |
17 | val clientId: String
18 |
19 | val languageCode: String
20 |
21 | val notificationSettings: NotificationSettings
22 |
23 | val isAutomaticPushSendingEnabled: Boolean
24 |
25 | val sdkVersion: String
26 |
27 | fun changeApplicationCode(applicationCode: String?)
28 |
29 | fun changeApplicationCode(applicationCode: String?, completionListener: CompletionListener?)
30 |
31 | fun changeMerchantId(merchantId: String?)
32 | }
33 |
--------------------------------------------------------------------------------
/emarsys-sdk/src/main/java/com/emarsys/deeplink/DeepLink.kt:
--------------------------------------------------------------------------------
1 | package com.emarsys.deeplink
2 |
3 | import android.app.Activity
4 | import android.content.Intent
5 | import com.emarsys.core.api.result.CompletionListener
6 | import com.emarsys.mobileengage.di.mobileEngage
7 |
8 | class DeepLink(private val loggingInstance: Boolean = false) : DeepLinkApi {
9 | override fun trackDeepLinkOpen(
10 | activity: Activity,
11 | intent: Intent,
12 | completionListener: CompletionListener?
13 | ) {
14 | mobileEngage().deepLinkInternal
15 | .trackDeepLinkOpen(activity, intent, completionListener)
16 | }
17 |
18 | }
--------------------------------------------------------------------------------
/emarsys-sdk/src/main/java/com/emarsys/deeplink/DeepLinkApi.kt:
--------------------------------------------------------------------------------
1 | package com.emarsys.deeplink
2 |
3 | import android.app.Activity
4 | import android.content.Intent
5 | import com.emarsys.core.api.result.CompletionListener
6 |
7 | interface DeepLinkApi {
8 | fun trackDeepLinkOpen(activity: Activity, intent: Intent, completionListener: CompletionListener?)
9 | }
--------------------------------------------------------------------------------
/emarsys-sdk/src/main/java/com/emarsys/di/DefaultEmarsysDependencies.kt:
--------------------------------------------------------------------------------
1 | package com.emarsys.di
2 |
3 | import com.emarsys.config.EmarsysConfig
4 |
5 | open class DefaultEmarsysDependencies(config: EmarsysConfig,
6 | testComponent: DefaultEmarsysComponent? = null) {
7 |
8 | private val component: DefaultEmarsysComponent = testComponent
9 | ?: DefaultEmarsysComponent(config)
10 |
11 | init {
12 | setupEmarsysComponent(component)
13 |
14 | emarsys().concurrentHandlerHolder.coreHandler.post {
15 | component.initializeResponseHandlers(config)
16 | }
17 | }
18 | }
--------------------------------------------------------------------------------
/emarsys-sdk/src/main/java/com/emarsys/eventservice/EventServiceApi.kt:
--------------------------------------------------------------------------------
1 | package com.emarsys.eventservice
2 |
3 | import com.emarsys.core.api.result.CompletionListener
4 |
5 | interface EventServiceApi {
6 | fun trackCustomEvent(
7 | eventName: String,
8 | eventAttributes: Map?,
9 | completionListener: CompletionListener?): String?
10 |
11 | fun trackCustomEventAsync(
12 | eventName: String,
13 | eventAttributes: Map?,
14 | completionListener: CompletionListener?)
15 |
16 | fun trackInternalCustomEvent(
17 | eventName: String,
18 | eventAttributes: Map?,
19 | completionListener: CompletionListener?): String?
20 |
21 | fun trackInternalCustomEventAsync(
22 | eventName: String,
23 | eventAttributes: Map?,
24 | completionListener: CompletionListener?)
25 | }
--------------------------------------------------------------------------------
/emarsys-sdk/src/main/java/com/emarsys/geofence/GeofenceApi.kt:
--------------------------------------------------------------------------------
1 | package com.emarsys.geofence
2 |
3 | import com.emarsys.core.api.result.CompletionListener
4 | import com.emarsys.mobileengage.api.event.EventHandler
5 | import com.emarsys.mobileengage.api.geofence.Geofence
6 |
7 | interface GeofenceApi {
8 | val registeredGeofences: List
9 |
10 | fun enable()
11 | fun enable(completionListener: CompletionListener)
12 | fun disable()
13 | fun setEventHandler(eventHandler: EventHandler)
14 | fun isEnabled(): Boolean
15 | fun setInitialEnterTriggerEnabled(enabled: Boolean)
16 | }
--------------------------------------------------------------------------------
/emarsys-sdk/src/main/java/com/emarsys/geofence/RegisterGeofencesOnBootCompletedReceiver.kt:
--------------------------------------------------------------------------------
1 | package com.emarsys.geofence
2 |
3 | import android.content.BroadcastReceiver
4 | import android.content.Context
5 | import android.content.Intent
6 | import android.util.Log
7 | import com.emarsys.Emarsys
8 | import com.emarsys.core.util.log.Logger
9 | import com.emarsys.mobileengage.di.MobileEngageComponent
10 |
11 | class RegisterGeofencesOnBootCompletedReceiver : BroadcastReceiver() {
12 |
13 | override fun onReceive(context: Context, intent: Intent) {
14 | if (intent.action.equals(Intent.ACTION_BOOT_COMPLETED, true)) {
15 | Log.d(Logger.TAG, "Emarsys SDK has been started!")
16 | }
17 | }
18 | }
--------------------------------------------------------------------------------
/emarsys-sdk/src/main/java/com/emarsys/inapp/InApp.kt:
--------------------------------------------------------------------------------
1 | package com.emarsys.inapp
2 |
3 | import com.emarsys.core.Mockable
4 | import com.emarsys.mobileengage.api.event.EventHandler
5 | import com.emarsys.mobileengage.di.mobileEngage
6 |
7 | @Mockable
8 | class InApp(private val loggingInstance: Boolean = false) : InAppApi {
9 |
10 | override fun pause() {
11 | (if (loggingInstance) mobileEngage().loggingInAppInternal else mobileEngage().inAppInternal)
12 | .pause()
13 | }
14 |
15 | override fun resume() {
16 | (if (loggingInstance) mobileEngage().loggingInAppInternal else mobileEngage().inAppInternal)
17 | .resume()
18 | }
19 |
20 | override val isPaused: Boolean
21 | get() = (if (loggingInstance) mobileEngage().loggingInAppInternal else mobileEngage().inAppInternal)
22 | .isPaused
23 |
24 | override fun setEventHandler(eventHandler: EventHandler) {
25 | (if (loggingInstance) mobileEngage().loggingInAppInternal else mobileEngage().inAppInternal)
26 | .eventHandler = eventHandler
27 | }
28 | }
--------------------------------------------------------------------------------
/emarsys-sdk/src/main/java/com/emarsys/inapp/InAppApi.kt:
--------------------------------------------------------------------------------
1 | package com.emarsys.inapp
2 |
3 | import com.emarsys.mobileengage.api.event.EventHandler
4 |
5 | interface InAppApi {
6 | fun pause()
7 | fun resume()
8 | val isPaused: Boolean
9 | fun setEventHandler(eventHandler: EventHandler)
10 | }
--------------------------------------------------------------------------------
/emarsys-sdk/src/main/java/com/emarsys/inbox/InboxTag.kt:
--------------------------------------------------------------------------------
1 | package com.emarsys.inbox
2 |
3 |
4 | object InboxTag {
5 |
6 | const val OPENED = "opened"
7 | const val SEEN = "seen"
8 | const val PINNED = "pinned"
9 | const val DELETED = "deleted"
10 | }
--------------------------------------------------------------------------------
/emarsys-sdk/src/main/java/com/emarsys/inbox/MessageInboxApi.kt:
--------------------------------------------------------------------------------
1 | package com.emarsys.inbox
2 |
3 | import com.emarsys.core.api.result.CompletionListener
4 | import com.emarsys.core.api.result.ResultListener
5 | import com.emarsys.core.api.result.Try
6 | import com.emarsys.mobileengage.api.inbox.InboxResult
7 |
8 | interface MessageInboxApi {
9 | fun fetchMessages(resultListener: ResultListener>)
10 |
11 | fun fetchMessages(resultListener: (Try) -> Unit)
12 |
13 | fun addTag(tag: String, messageId: String)
14 |
15 | fun addTag(tag: String, messageId: String, completionListener: CompletionListener)
16 |
17 | fun addTag(tag: String, messageId: String, completionListener: (Throwable?) -> Unit)
18 |
19 | fun removeTag(tag: String, messageId: String)
20 |
21 | fun removeTag(tag: String, messageId: String, completionListener: CompletionListener)
22 |
23 | fun removeTag(tag: String, messageId: String, completionListener: (Throwable?) -> Unit)
24 | }
--------------------------------------------------------------------------------
/emarsys-sdk/src/main/java/com/emarsys/mobileengage/MobileEngageApi.kt:
--------------------------------------------------------------------------------
1 | package com.emarsys.mobileengage
2 |
3 | import com.emarsys.core.api.result.CompletionListener
4 |
5 | interface MobileEngageApi {
6 |
7 | fun setContact(
8 | contactFieldId: Int? = null,
9 | contactFieldValue: String? = null,
10 | completionListener: CompletionListener? = null
11 | )
12 |
13 | fun setAuthenticatedContact(
14 | contactFieldId: Int,
15 | openIdToken: String,
16 | completionListener: CompletionListener?
17 | )
18 |
19 | fun clearContact(completionListener: CompletionListener?)
20 | }
--------------------------------------------------------------------------------
/emarsys-sdk/src/main/java/com/emarsys/oneventaction/OnEventAction.kt:
--------------------------------------------------------------------------------
1 | package com.emarsys.oneventaction
2 |
3 |
4 | import com.emarsys.mobileengage.api.event.EventHandler
5 | import com.emarsys.mobileengage.di.mobileEngage
6 | import com.emarsys.mobileengage.event.CacheableEventHandler
7 |
8 | class OnEventAction : OnEventActionApi {
9 |
10 | override fun setOnEventActionEventHandler(eventHandler: EventHandler) {
11 | val onEventActionCacheableEventHandler: CacheableEventHandler = mobileEngage().onEventActionCacheableEventHandler
12 | onEventActionCacheableEventHandler.setEventHandler(eventHandler)
13 | }
14 | }
--------------------------------------------------------------------------------
/emarsys-sdk/src/main/java/com/emarsys/oneventaction/OnEventActionApi.kt:
--------------------------------------------------------------------------------
1 | package com.emarsys.oneventaction
2 |
3 | import com.emarsys.mobileengage.api.event.EventHandler
4 |
5 | interface OnEventActionApi {
6 | fun setOnEventActionEventHandler(eventHandler: EventHandler)
7 | }
--------------------------------------------------------------------------------
/emarsys-sdk/src/main/java/com/emarsys/predict/PredictRestricted.kt:
--------------------------------------------------------------------------------
1 | package com.emarsys.predict
2 |
3 | import com.emarsys.core.Mockable
4 | import com.emarsys.predict.di.predict
5 |
6 | @Mockable
7 | class PredictRestricted(private val loggingInstance: Boolean = false) : PredictRestrictedApi {
8 | override fun setContact(contactFieldId: Int, contactFieldValue: String) {
9 | (if (loggingInstance) predict().loggingPredictInternal else predict().predictInternal)
10 | .setContact(contactFieldId, contactFieldValue)
11 | }
12 |
13 | override fun clearContact() {
14 | (if (loggingInstance) predict().loggingPredictInternal else predict().predictInternal)
15 | .clearContact()
16 | }
17 | }
--------------------------------------------------------------------------------
/emarsys-sdk/src/main/java/com/emarsys/predict/PredictRestrictedApi.kt:
--------------------------------------------------------------------------------
1 | package com.emarsys.predict
2 |
3 | interface PredictRestrictedApi {
4 |
5 | fun setContact(contactFieldId: Int, contactFieldValue: String)
6 | fun clearContact()
7 | }
--------------------------------------------------------------------------------
/emarsys-sdk/src/main/java/com/emarsys/push/PushApi.kt:
--------------------------------------------------------------------------------
1 | package com.emarsys.push
2 |
3 | import com.emarsys.core.api.result.CompletionListener
4 | import com.emarsys.mobileengage.api.event.EventHandler
5 | import com.emarsys.mobileengage.api.push.NotificationInformationListener
6 |
7 | interface PushApi {
8 | fun setPushToken(
9 | pushToken: String,
10 | completionListener: CompletionListener? = null)
11 |
12 | var pushToken: String?
13 | fun clearPushToken()
14 | fun clearPushToken(completionListener: CompletionListener)
15 | fun setNotificationEventHandler(notificationEventHandler: EventHandler)
16 | fun setSilentMessageEventHandler(silentMessageEventHandler: EventHandler)
17 | fun setNotificationInformationListener(notificationInformationListener: NotificationInformationListener)
18 | fun setSilentNotificationInformationListener(silentNotificationInformationListener: NotificationInformationListener)
19 | }
--------------------------------------------------------------------------------
/emarsys-sdk/src/main/res/values/attrs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/emarsys-sdk/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
12 |
--------------------------------------------------------------------------------
/emarsys/src/androidTest/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
--------------------------------------------------------------------------------
/emarsys/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
--------------------------------------------------------------------------------
/emarsys/src/main/java/com/emarsys/config/ConfigInternal.kt:
--------------------------------------------------------------------------------
1 | package com.emarsys.config
2 |
3 | import com.emarsys.core.api.notification.NotificationSettings
4 | import com.emarsys.core.api.result.CompletionListener
5 |
6 | interface ConfigInternal {
7 |
8 | val contactFieldId: Int?
9 |
10 | val applicationCode: String?
11 |
12 | val merchantId: String?
13 |
14 | val clientId: String
15 |
16 | val language: String
17 |
18 | val notificationSettings: NotificationSettings
19 |
20 | val isAutomaticPushSendingEnabled: Boolean
21 |
22 | val sdkVersion: String
23 |
24 | fun changeApplicationCode(applicationCode: String?, completionListener: CompletionListener?)
25 |
26 | fun changeMerchantId(merchantId: String?)
27 |
28 | fun refreshRemoteConfig(completionListener: CompletionListener?)
29 |
30 | fun resetRemoteConfig()
31 | }
32 |
--------------------------------------------------------------------------------
/emarsys/src/main/java/com/emarsys/config/ConfigStorageKeys.kt:
--------------------------------------------------------------------------------
1 | package com.emarsys.config
2 |
3 | enum class ConfigStorageKeys {
4 | MOBILE_ENGAGE_APPLICATION_CODE,
5 | PREDICT_MERCHANT_ID,
6 | ANDROID_DISABLE_AUTOMATIC_PUSH_TOKEN_SENDING,
7 | ANDROID_SHARED_PACKAGE_NAMES,
8 | ANDROID_SHARED_SECRET,
9 | ANDROID_VERBOSE_CONSOLE_LOGGING_ENABLED
10 | }
--------------------------------------------------------------------------------
/emarsys/src/main/java/com/emarsys/config/model/RemoteConfig.kt:
--------------------------------------------------------------------------------
1 | package com.emarsys.config.model
2 |
3 | import com.emarsys.common.feature.InnerFeature
4 | import com.emarsys.core.util.log.LogLevel
5 |
6 | data class RemoteConfig(val eventServiceUrl: String? = null,
7 | val clientServiceUrl: String? = null,
8 | val predictServiceUrl: String? = null,
9 | val mobileEngageV2ServiceUrl: String? = null,
10 | val deepLinkServiceUrl: String? = null,
11 | val inboxServiceUrl: String? = null,
12 | val messageInboxServiceUrl: String? = null,
13 | val logLevel: LogLevel? = null,
14 | val features: Map? = null)
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | org.gradle.jvmargs=-Xmx4096m -XX:+HeapDumpOnOutOfMemoryError
2 | org.gradle.configureondemand=false
3 | android.useAndroidX=true
4 | android.enableJetifier=true
5 | org.gradle.parallel=true
6 | org.gradle.vfs.watch=true
7 | #android.enableBuildConfigAsBytecode=true
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emartech/android-emarsys-sdk/b69741719d0ba7e462a1faab64c49b750906a1fd/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | distributionBase=GRADLE_USER_HOME
2 | distributionPath=wrapper/dists
3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-all.zip
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 |
--------------------------------------------------------------------------------
/local-release.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 | export EXCLUDE_GOOGLE_SERVICES_API_KEY=true
3 |
4 | ./gradlew clean lint assembleRelease publishToSonatype
--------------------------------------------------------------------------------
/mobile-engage-api/src/androidTest/java/com/emarsys/mobileengage/api/MEApiTest.kt:
--------------------------------------------------------------------------------
1 | package com.emarsys.mobileengage.api
2 |
3 |
4 | import org.junit.Test
5 |
6 |
7 | class MEApiTest {
8 |
9 |
10 | @Test
11 | fun test() {
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/mobile-engage-api/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/mobile-engage-api/src/main/java/com/emarsys/mobileengage/api/action/ActionModel.kt:
--------------------------------------------------------------------------------
1 | package com.emarsys.mobileengage.api.action
2 |
3 | import java.net.URL
4 |
5 | sealed class ActionModel {
6 | abstract val id: String
7 | abstract val title: String
8 | abstract val type: String
9 | }
10 |
11 | data class AppEventActionModel(
12 | override val id: String,
13 | override val title: String,
14 | override val type: String,
15 | val name: String,
16 | val payload: Map?
17 | ): ActionModel()
18 |
19 | data class CustomEventActionModel(
20 | override val id: String,
21 | override val title: String,
22 | override val type: String,
23 | val name: String,
24 | val payload: Map?
25 | ): ActionModel()
26 |
27 | data class DismissActionModel(
28 | override val id: String,
29 | override val title: String,
30 | override val type: String
31 | ): ActionModel()
32 |
33 | data class OpenExternalUrlActionModel(
34 | override val id: String,
35 | override val title: String,
36 | override val type: String,
37 | val url: URL
38 | ): ActionModel()
--------------------------------------------------------------------------------
/mobile-engage-api/src/main/java/com/emarsys/mobileengage/api/event/EventHandler.kt:
--------------------------------------------------------------------------------
1 | package com.emarsys.mobileengage.api.event
2 |
3 | import android.content.Context
4 | import org.json.JSONObject
5 |
6 | fun interface EventHandler {
7 | fun handleEvent(context: Context, eventName: String, payload: JSONObject?)
8 | }
--------------------------------------------------------------------------------
/mobile-engage-api/src/main/java/com/emarsys/mobileengage/api/geofence/Geofence.kt:
--------------------------------------------------------------------------------
1 | package com.emarsys.mobileengage.api.geofence
2 |
3 | data class Geofence(val id: String,
4 | val lat: Double,
5 | val lon: Double,
6 | val radius: Double,
7 | val waitInterval: Double?,
8 | val triggers: List)
--------------------------------------------------------------------------------
/mobile-engage-api/src/main/java/com/emarsys/mobileengage/api/geofence/Trigger.kt:
--------------------------------------------------------------------------------
1 | package com.emarsys.mobileengage.api.geofence
2 |
3 | import org.json.JSONObject
4 |
5 | data class Trigger(val id: String,
6 | val type: Enum,
7 | val loiteringDelay: Int = 0,
8 | val action: JSONObject)
9 |
10 | enum class TriggerType {
11 | ENTER,
12 | EXIT,
13 | DWELLING;
14 | }
--------------------------------------------------------------------------------
/mobile-engage-api/src/main/java/com/emarsys/mobileengage/api/inbox/InboxResult.kt:
--------------------------------------------------------------------------------
1 | package com.emarsys.mobileengage.api.inbox
2 |
3 | data class InboxResult(val messages: List)
--------------------------------------------------------------------------------
/mobile-engage-api/src/main/java/com/emarsys/mobileengage/api/inbox/Message.kt:
--------------------------------------------------------------------------------
1 | package com.emarsys.mobileengage.api.inbox
2 |
3 | import com.emarsys.mobileengage.api.action.ActionModel
4 |
5 | data class Message(
6 | val id: String,
7 | val campaignId: String,
8 | val collapseId: String?,
9 | val title: String,
10 | val body: String,
11 | val imageUrl: String?,
12 | val receivedAt: Long,
13 | val updatedAt: Long?,
14 | val expiresAt: Long?,
15 | val tags: List?,
16 | val properties: Map?,
17 | val actions: List?
18 | )
--------------------------------------------------------------------------------
/mobile-engage-api/src/main/java/com/emarsys/mobileengage/api/push/NotificationInformation.kt:
--------------------------------------------------------------------------------
1 | package com.emarsys.mobileengage.api.push
2 |
3 | data class NotificationInformation(val campaignId: String)
--------------------------------------------------------------------------------
/mobile-engage-api/src/main/java/com/emarsys/mobileengage/api/push/NotificationInformationListener.kt:
--------------------------------------------------------------------------------
1 | package com.emarsys.mobileengage.api.push
2 |
3 | fun interface NotificationInformationListener {
4 | fun onNotificationInformationReceived(notificationInformation: NotificationInformation)
5 | }
--------------------------------------------------------------------------------
/mobile-engage/gradle.properties:
--------------------------------------------------------------------------------
1 | sdkArtifactName=mobile-engage-sdk
2 |
--------------------------------------------------------------------------------
/mobile-engage/lint.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/mobile-engage/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # By default, the flags in this file are appended to flags specified
3 | # in /Users/jpollak/Library/Android/sdk/tools/proguard/proguard-android.txt
4 | # You can edit the include path and order by changing the proguardFiles
5 | # directive in build.gradle.kts.
6 | #
7 | # For more details, see
8 | # http://developer.android.com/guide/developing/tools/proguard.html
9 |
10 | # Add any project specific keep options here:
11 |
12 | # If your project uses WebView with JS, uncomment the following
13 | # and specify the fully qualified class name to the JavaScript interface
14 | # class:
15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
16 | # public *;
17 | #}
18 | -keep class com.emarsys.**{*;}
--------------------------------------------------------------------------------
/mobile-engage/src/androidTest/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
8 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/mobile-engage/src/androidTest/java/com/emarsys/mobileengage/fake/FakeCompletionListener.kt:
--------------------------------------------------------------------------------
1 | package com.emarsys.mobileengage.fake
2 |
3 | import com.emarsys.core.api.result.CompletionListener
4 | import java.util.concurrent.CountDownLatch
5 |
6 | open class FakeCompletionListener(private val countDownLatch: CountDownLatch, private val completionListener: CompletionListener) : CompletionListener {
7 | override fun onCompleted(errorCause: Throwable?) {
8 | completionListener.onCompleted(errorCause)
9 | countDownLatch.countDown()
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/mobile-engage/src/androidTest/java/com/emarsys/mobileengage/geofence/GeofencePendingIntentProviderTest.kt:
--------------------------------------------------------------------------------
1 | package com.emarsys.mobileengage.geofence
2 |
3 | import android.app.PendingIntent
4 | import androidx.test.platform.app.InstrumentationRegistry
5 | import io.kotest.matchers.shouldBe
6 | import io.kotest.matchers.shouldNotBe
7 | import org.junit.Test
8 |
9 | class GeofencePendingIntentProviderTest {
10 | @Test
11 | fun testProvidePendingIntent() {
12 | val result =
13 | GeofencePendingIntentProvider(InstrumentationRegistry.getInstrumentation().context).providePendingIntent()
14 |
15 | result shouldNotBe null
16 | result::
17 | class.java shouldBe PendingIntent::
18 | class.java
19 | }
20 | }
--------------------------------------------------------------------------------
/mobile-engage/src/androidTest/java/com/emarsys/mobileengage/iam/InAppEventHandlerInternalTest.kt:
--------------------------------------------------------------------------------
1 | package com.emarsys.mobileengage.iam
2 |
3 |
4 | import io.kotest.matchers.shouldBe
5 | import org.junit.Before
6 | import org.junit.Test
7 |
8 | class InAppEventHandlerInternalTest {
9 |
10 | private lateinit var inAppEventHandlerInternal: InAppEventHandlerInternal
11 |
12 |
13 | @Before
14 | fun setUp() {
15 | inAppEventHandlerInternal = InAppEventHandlerInternal()
16 | }
17 |
18 | @Test
19 | fun testIsPaused_returnsFalse_byDefault() {
20 | inAppEventHandlerInternal.isPaused shouldBe false
21 | }
22 |
23 | @Test
24 | fun testPause_setsIsPaused_toTrue() {
25 | inAppEventHandlerInternal.pause()
26 |
27 | inAppEventHandlerInternal.isPaused shouldBe true
28 | }
29 |
30 | @Test
31 | fun testResume_setsIsPaused_toFalse() {
32 | inAppEventHandlerInternal.pause()
33 | inAppEventHandlerInternal.resume()
34 |
35 | inAppEventHandlerInternal.isPaused shouldBe false
36 | }
37 | }
--------------------------------------------------------------------------------
/mobile-engage/src/androidTest/java/com/emarsys/mobileengage/iam/jsbridge/IamJsBridgeFactoryTest.kt:
--------------------------------------------------------------------------------
1 | package com.emarsys.mobileengage.iam.jsbridge
2 |
3 | import com.emarsys.core.concurrency.ConcurrentHandlerHolderFactory
4 | import io.kotest.matchers.shouldBe
5 | import io.mockk.mockk
6 | import org.junit.Test
7 |
8 | class IamJsBridgeFactoryTest {
9 |
10 | @Test
11 | fun createJsBridge_shouldReturnJSBridge() {
12 | val jsCommandFactory: JSCommandFactory = mockk(relaxed = true)
13 | val jsBridgeFactory = IamJsBridgeFactory(ConcurrentHandlerHolderFactory.create())
14 |
15 | jsBridgeFactory.createJsBridge(jsCommandFactory)::class.java shouldBe IamJsBridge::class.java
16 | }
17 | }
--------------------------------------------------------------------------------
/mobile-engage/src/androidTest/java/com/emarsys/mobileengage/notification/LaunchActivityCommandLifecycleCallbacksFactoryTest.kt:
--------------------------------------------------------------------------------
1 | package com.emarsys.mobileengage.notification
2 |
3 | import io.kotest.matchers.shouldBe
4 | import org.junit.Test
5 | import java.util.concurrent.CountDownLatch
6 |
7 | class LaunchActivityCommandLifecycleCallbacksFactoryTest {
8 | @Test
9 | fun testCreate() {
10 | val latch = CountDownLatch(1)
11 | val result = LaunchActivityCommandLifecycleCallbacksFactory().create(latch)
12 | result::class.java shouldBe LaunchActivityCommandLifecycleCallbacks::class.java
13 | }
14 | }
--------------------------------------------------------------------------------
/mobile-engage/src/androidTest/java/com/emarsys/mobileengage/notification/command/OpenExternalUrlCommandTest.kt:
--------------------------------------------------------------------------------
1 | package com.emarsys.mobileengage.notification.command
2 |
3 | import android.content.Context
4 | import android.content.Intent
5 | import io.mockk.mockk
6 | import io.mockk.verify
7 | import org.junit.Before
8 | import org.junit.Test
9 |
10 | class OpenExternalUrlCommandTest {
11 | private lateinit var mockContext: Context
12 |
13 | @Before
14 | fun setup() {
15 | mockContext = mockk(relaxed = true)
16 | }
17 |
18 | @Test
19 | fun testRun_startsActivity_withCorrectIntent() {
20 | val intent = Intent()
21 | val command = OpenExternalUrlCommand(intent, mockContext)
22 | command.run()
23 |
24 | verify { mockContext.startActivity(intent) }
25 | }
26 | }
--------------------------------------------------------------------------------
/mobile-engage/src/androidTest/java/com/emarsys/mobileengage/notification/command/TrackActionClickCommandTest.kt:
--------------------------------------------------------------------------------
1 | package com.emarsys.mobileengage.notification.command
2 |
3 | import com.emarsys.mobileengage.event.EventServiceInternal
4 | import io.mockk.mockk
5 | import io.mockk.verify
6 | import org.junit.Before
7 | import org.junit.Test
8 |
9 | class TrackActionClickCommandTest {
10 | private lateinit var mockEventServiceInternal: EventServiceInternal
11 |
12 | @Before
13 | fun setup() {
14 | mockEventServiceInternal = mockk(relaxed = true)
15 | }
16 |
17 | @Test
18 | fun testRun_sendsInternalCustomEvent() {
19 | val buttonId = "buttonId"
20 | val sid = "sid1234"
21 | TrackActionClickCommand(mockEventServiceInternal, buttonId, sid).run()
22 | val payload: MutableMap = HashMap()
23 | payload["button_id"] = buttonId
24 | payload["origin"] = "button"
25 | payload["sid"] = sid
26 | verify {
27 | mockEventServiceInternal.trackInternalCustomEventAsync("push:click", payload, null)
28 | }
29 | }
30 | }
--------------------------------------------------------------------------------
/mobile-engage/src/androidTest/java/com/emarsys/mobileengage/notification/command/TrackMessageOpenCommandTest.kt:
--------------------------------------------------------------------------------
1 | package com.emarsys.mobileengage.notification.command
2 |
3 | import com.emarsys.mobileengage.push.PushInternal
4 | import io.mockk.mockk
5 | import io.mockk.verify
6 | import org.junit.Test
7 |
8 | class TrackMessageOpenCommandTest {
9 |
10 |
11 | @Test
12 | fun testRun_callsMobileEngageInternal() {
13 | val mockPushInternal = mockk(relaxed = true)
14 | val sid = "test sid"
15 | val command = TrackMessageOpenCommand(mockPushInternal, sid)
16 |
17 | command.run()
18 |
19 | verify { mockPushInternal.trackMessageOpen(sid, null) }
20 | }
21 |
22 | }
--------------------------------------------------------------------------------
/mobile-engage/src/androidTest/java/com/emarsys/mobileengage/push/DefaultPushTokenProviderTest.kt:
--------------------------------------------------------------------------------
1 | package com.emarsys.mobileengage.push
2 |
3 | import com.emarsys.core.storage.Storage
4 | import com.emarsys.core.storage.StringStorage
5 | import com.emarsys.testUtil.mockito.whenever
6 | import io.kotest.matchers.shouldBe
7 | import org.junit.Test
8 | import org.mockito.Mockito.mock
9 |
10 | class DefaultPushTokenProviderTest {
11 | private companion object {
12 | const val PUSH_TOKEN = "pushToken"
13 | }
14 |
15 | @Test
16 | fun testProvidePushToken() {
17 | val mockPushTokenStorage: Storage = (mock(StringStorage::class.java)).apply {
18 | whenever(get()).thenReturn(PUSH_TOKEN)
19 | }
20 |
21 | val pushTokenProvider = DefaultPushTokenProvider(mockPushTokenStorage)
22 |
23 | val result = pushTokenProvider.providePushToken()
24 |
25 | result shouldBe PUSH_TOKEN
26 | }
27 | }
--------------------------------------------------------------------------------
/mobile-engage/src/androidTest/java/com/emarsys/mobileengage/storage/MobileEngageStorageKeyTest.kt:
--------------------------------------------------------------------------------
1 | package com.emarsys.mobileengage.storage
2 |
3 | import io.kotest.data.forAll
4 | import io.kotest.data.row
5 | import io.kotest.matchers.shouldBe
6 | import kotlinx.coroutines.runBlocking
7 | import org.junit.Test
8 |
9 | class MobileEngageStorageKeyTest {
10 |
11 | @Test
12 | fun testGetKey() = runBlocking {
13 | MobileEngageStorageKey.values().map {
14 | "mobile_engage_${it.name.lowercase()}"
15 | }.zip(MobileEngageStorageKey.values()) { stringValue, enum ->
16 | row(enum, stringValue)
17 | }.let {
18 | forAll(*it.toTypedArray()) { input, expected ->
19 | input.key shouldBe expected
20 | }
21 | }
22 | }
23 | }
--------------------------------------------------------------------------------
/mobile-engage/src/androidTest/java/com/emarsys/mobileengage/testUtil/RandomMETestUtils.java:
--------------------------------------------------------------------------------
1 | package com.emarsys.mobileengage.testUtil;
2 |
3 | import static com.emarsys.testUtil.RandomTestUtils.randomLong;
4 | import static com.emarsys.testUtil.RandomTestUtils.randomNumberString;
5 |
6 | import com.emarsys.mobileengage.iam.model.buttonclicked.ButtonClicked;
7 | import com.emarsys.mobileengage.iam.model.displayediam.DisplayedIam;
8 |
9 | public class RandomMETestUtils {
10 |
11 | public static DisplayedIam randomDisplayedIam() {
12 | return new DisplayedIam(randomNumberString(), randomLong());
13 | }
14 |
15 | public static ButtonClicked randomButtonClick() {
16 | return new ButtonClicked(randomNumberString(), randomNumberString(), randomLong());
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/mobile-engage/src/androidTest/java/com/emarsys/mobileengage/util/AsyncSDKUtils.kt:
--------------------------------------------------------------------------------
1 | package com.emarsys.mobileengage.util
2 |
3 | import com.emarsys.mobileengage.di.mobileEngage
4 |
5 | import java.util.concurrent.CountDownLatch
6 |
7 | fun waitForTask() {
8 | val latch = CountDownLatch(2)
9 | mobileEngage().concurrentHandlerHolder.coreHandler.post {
10 | latch.countDown()
11 | mobileEngage().concurrentHandlerHolder.coreHandler.post {
12 | latch.countDown()
13 | }
14 | }
15 | latch.await()
16 | }
--------------------------------------------------------------------------------
/mobile-engage/src/androidTest/res/raw/test_image.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emartech/android-emarsys-sdk/b69741719d0ba7e462a1faab64c49b750906a1fd/mobile-engage/src/androidTest/res/raw/test_image.png
--------------------------------------------------------------------------------
/mobile-engage/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/mobile-engage/src/main/java/com/emarsys/mobileengage/MobileEngageInternal.kt:
--------------------------------------------------------------------------------
1 | package com.emarsys.mobileengage
2 |
3 | import com.emarsys.core.api.result.CompletionListener
4 |
5 | interface MobileEngageInternal {
6 | fun setContact(
7 | contactFieldId: Int?,
8 | contactFieldValue: String?,
9 | completionListener: CompletionListener?
10 | )
11 | fun setAuthenticatedContact(
12 | contactFieldId: Int,
13 | openIdToken: String,
14 | completionListener: CompletionListener?
15 | )
16 | fun clearContact(completionListener: CompletionListener?)
17 | }
--------------------------------------------------------------------------------
/mobile-engage/src/main/java/com/emarsys/mobileengage/client/ClientServiceInternal.kt:
--------------------------------------------------------------------------------
1 | package com.emarsys.mobileengage.client
2 |
3 | import com.emarsys.core.api.result.CompletionListener
4 |
5 | interface ClientServiceInternal {
6 | fun trackDeviceInfo(completionListener: CompletionListener?)
7 | }
--------------------------------------------------------------------------------
/mobile-engage/src/main/java/com/emarsys/mobileengage/client/DefaultClientServiceInternal.kt:
--------------------------------------------------------------------------------
1 | package com.emarsys.mobileengage.client
2 |
3 | import com.emarsys.core.api.result.CompletionListener
4 | import com.emarsys.core.request.RequestManager
5 | import com.emarsys.mobileengage.request.MobileEngageRequestModelFactory
6 |
7 | class DefaultClientServiceInternal(
8 | private val requestManager: RequestManager,
9 | private val requestModelFactory: MobileEngageRequestModelFactory
10 | ) : ClientServiceInternal {
11 | override fun trackDeviceInfo(completionListener: CompletionListener?) {
12 | try {
13 | val requestModel = requestModelFactory.createTrackDeviceInfoRequest()
14 | requestManager.submit(requestModel, completionListener)
15 | } catch (e: IllegalArgumentException) {
16 | completionListener?.onCompleted(e)
17 | }
18 | }
19 | }
--------------------------------------------------------------------------------
/mobile-engage/src/main/java/com/emarsys/mobileengage/client/LoggingClientServiceInternal.kt:
--------------------------------------------------------------------------------
1 | package com.emarsys.mobileengage.client
2 |
3 | import com.emarsys.core.api.result.CompletionListener
4 | import com.emarsys.core.util.SystemUtils
5 | import com.emarsys.core.util.log.Logger.Companion.debug
6 | import com.emarsys.core.util.log.entry.MethodNotAllowed
7 |
8 | class LoggingClientServiceInternal(private val klass: Class<*>) : ClientServiceInternal {
9 | override fun trackDeviceInfo(completionListener: CompletionListener?) {
10 | val callerMethodName = SystemUtils.getCallerMethodName()
11 | debug(MethodNotAllowed(klass, callerMethodName, null))
12 | }
13 | }
--------------------------------------------------------------------------------
/mobile-engage/src/main/java/com/emarsys/mobileengage/deeplink/DeepLinkAction.kt:
--------------------------------------------------------------------------------
1 | package com.emarsys.mobileengage.deeplink
2 |
3 | import android.app.Activity
4 | import com.emarsys.core.activity.ActivityLifecycleAction
5 | import com.emarsys.core.activity.ActivityLifecyclePriorities
6 | import com.emarsys.core.api.proxyApi
7 | import com.emarsys.mobileengage.di.mobileEngage
8 |
9 | class DeepLinkAction(private val deepLinkInternal: DeepLinkInternal,
10 | override val priority: Int = ActivityLifecyclePriorities.DEEP_LINK_ACTION_PRIORITY,
11 | override val repeatable: Boolean = true,
12 | override val triggeringLifecycle: ActivityLifecycleAction.ActivityLifecycle = ActivityLifecycleAction.ActivityLifecycle.CREATE
13 | ) : ActivityLifecycleAction {
14 |
15 | override fun execute(activity: Activity?) {
16 | if (activity != null && activity.intent != null) {
17 | deepLinkInternal.proxyApi(mobileEngage().concurrentHandlerHolder)
18 | .trackDeepLinkOpen(activity, activity.intent, null)
19 | }
20 | }
21 | }
--------------------------------------------------------------------------------
/mobile-engage/src/main/java/com/emarsys/mobileengage/deeplink/DeepLinkInternal.kt:
--------------------------------------------------------------------------------
1 | package com.emarsys.mobileengage.deeplink
2 |
3 | import android.app.Activity
4 | import android.content.Intent
5 | import com.emarsys.core.api.result.CompletionListener
6 |
7 | interface DeepLinkInternal {
8 | fun trackDeepLinkOpen(
9 | activity: Activity,
10 | intent: Intent,
11 | completionListener: CompletionListener?
12 | )
13 | }
--------------------------------------------------------------------------------
/mobile-engage/src/main/java/com/emarsys/mobileengage/event/EventServiceInternal.kt:
--------------------------------------------------------------------------------
1 | package com.emarsys.mobileengage.event
2 |
3 | import com.emarsys.core.api.result.CompletionListener
4 |
5 | interface EventServiceInternal {
6 | fun trackCustomEvent(
7 | eventName: String,
8 | eventAttributes: Map?,
9 | completionListener: CompletionListener?
10 | ): String?
11 |
12 | fun trackCustomEventAsync(
13 | eventName: String,
14 | eventAttributes: Map?,
15 | completionListener: CompletionListener?
16 | )
17 |
18 | fun trackInternalCustomEvent(
19 | eventName: String,
20 | eventAttributes: Map?,
21 | completionListener: CompletionListener?
22 | ): String?
23 |
24 | fun trackInternalCustomEventAsync(
25 | eventName: String,
26 | eventAttributes: Map?,
27 | completionListener: CompletionListener?
28 | )
29 | }
--------------------------------------------------------------------------------
/mobile-engage/src/main/java/com/emarsys/mobileengage/geofence/FetchGeofencesAction.kt:
--------------------------------------------------------------------------------
1 | package com.emarsys.mobileengage.geofence
2 |
3 | import android.app.Activity
4 | import com.emarsys.core.activity.ActivityLifecycleAction
5 | import com.emarsys.core.activity.ActivityLifecyclePriorities
6 | import com.emarsys.core.api.proxyApi
7 | import com.emarsys.mobileengage.di.mobileEngage
8 |
9 | class FetchGeofencesAction(private val geofenceInternal: GeofenceInternal,
10 | override val priority: Int = ActivityLifecyclePriorities.FETCH_GEOFENCE_ACTION_PRIORITY,
11 | override val repeatable: Boolean = false,
12 | override val triggeringLifecycle: ActivityLifecycleAction.ActivityLifecycle = ActivityLifecycleAction.ActivityLifecycle.CREATE
13 | ) : ActivityLifecycleAction {
14 |
15 | override fun execute(activity: Activity?) {
16 | geofenceInternal.proxyApi(mobileEngage().concurrentHandlerHolder).fetchGeofences(null)
17 | }
18 | }
--------------------------------------------------------------------------------
/mobile-engage/src/main/java/com/emarsys/mobileengage/geofence/GeofenceFilter.kt:
--------------------------------------------------------------------------------
1 | package com.emarsys.mobileengage.geofence
2 |
3 | import android.location.Location
4 | import android.location.LocationManager
5 | import com.emarsys.core.Mockable
6 | import com.emarsys.mobileengage.api.geofence.Geofence
7 | import com.emarsys.mobileengage.geofence.model.GeofenceResponse
8 |
9 | @Mockable
10 | class GeofenceFilter(private val limit: Int) {
11 | fun findNearestGeofences(currentLocation: Location, geofenceResponse: GeofenceResponse): List {
12 | val geofences = geofenceResponse.geofenceGroups
13 | .flatMap { it.geofences }
14 | .sortedBy {
15 | (Location(LocationManager.GPS_PROVIDER).apply {
16 | this.longitude = it.lon
17 | this.latitude = it.lat
18 | }.distanceTo(currentLocation) - it.radius)
19 | }
20 | if (limit > geofences.size) {
21 | return geofences
22 | }
23 | return geofences.subList(0, limit)
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/mobile-engage/src/main/java/com/emarsys/mobileengage/geofence/GeofenceInternal.kt:
--------------------------------------------------------------------------------
1 | package com.emarsys.mobileengage.geofence
2 |
3 | import com.emarsys.core.api.result.CompletionListener
4 | import com.emarsys.mobileengage.api.event.EventHandler
5 | import com.emarsys.mobileengage.api.geofence.Geofence
6 | import com.emarsys.mobileengage.geofence.model.TriggeringEmarsysGeofence
7 |
8 | interface GeofenceInternal {
9 |
10 | val registeredGeofences: List
11 |
12 | fun fetchGeofences(completionListener: CompletionListener?)
13 | fun enable(completionListener: CompletionListener?)
14 | fun disable()
15 | fun isEnabled(): Boolean
16 | fun registerGeofences(geofences: List)
17 | fun onGeofenceTriggered(triggeringEmarsysGeofences: List)
18 | fun setEventHandler(eventHandler: EventHandler)
19 | fun setInitialEnterTriggerEnabled(enabled: Boolean)
20 | }
--------------------------------------------------------------------------------
/mobile-engage/src/main/java/com/emarsys/mobileengage/geofence/GeofencePendingIntentProvider.kt:
--------------------------------------------------------------------------------
1 | package com.emarsys.mobileengage.geofence
2 |
3 | import android.app.PendingIntent
4 | import android.content.Context
5 | import android.content.Intent
6 | import com.emarsys.core.Mockable
7 | import com.emarsys.core.util.AndroidVersionUtils
8 |
9 | @Mockable
10 | class GeofencePendingIntentProvider(private val context: Context) {
11 | fun providePendingIntent(): PendingIntent {
12 | val intent = Intent("com.emarsys.sdk.GEOFENCE_ACTION")
13 | intent.setPackage(context.packageName)
14 | return if (AndroidVersionUtils.isSOrAbove) {
15 | PendingIntent.getBroadcast(
16 | context,
17 | 0,
18 | intent,
19 | PendingIntent.FLAG_MUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
20 | )
21 | } else {
22 | PendingIntent.getBroadcast(
23 | context,
24 | 0,
25 | intent,
26 | PendingIntent.FLAG_UPDATE_CURRENT
27 | )
28 | }
29 | }
30 | }
--------------------------------------------------------------------------------
/mobile-engage/src/main/java/com/emarsys/mobileengage/geofence/model/GeofenceGroup.kt:
--------------------------------------------------------------------------------
1 | package com.emarsys.mobileengage.geofence.model
2 |
3 | import com.emarsys.mobileengage.api.geofence.Geofence
4 |
5 | data class GeofenceGroup(val id: String,
6 | val waitInterval: Double?,
7 | val geofences: List)
--------------------------------------------------------------------------------
/mobile-engage/src/main/java/com/emarsys/mobileengage/geofence/model/GeofenceResponse.kt:
--------------------------------------------------------------------------------
1 | package com.emarsys.mobileengage.geofence.model
2 |
3 | data class GeofenceResponse(val geofenceGroups: List, val refreshRadiusRatio: Double = DEFAULT_REFRESH_RADIUS_RATIO) {
4 | companion object {
5 | const val DEFAULT_REFRESH_RADIUS_RATIO = 0.5
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/mobile-engage/src/main/java/com/emarsys/mobileengage/geofence/model/TriggeringEmarsysGeofence.kt:
--------------------------------------------------------------------------------
1 | package com.emarsys.mobileengage.geofence.model
2 |
3 | import com.emarsys.mobileengage.api.geofence.TriggerType
4 |
5 | data class TriggeringEmarsysGeofence(val geofenceId: String, val triggerType: TriggerType)
--------------------------------------------------------------------------------
/mobile-engage/src/main/java/com/emarsys/mobileengage/iam/InAppEventHandler.kt:
--------------------------------------------------------------------------------
1 | package com.emarsys.mobileengage.iam
2 |
3 | import com.emarsys.mobileengage.api.event.EventHandler
4 |
5 | interface InAppEventHandler {
6 | fun pause()
7 | fun resume()
8 | val isPaused: Boolean
9 | var eventHandler: EventHandler?
10 | }
--------------------------------------------------------------------------------
/mobile-engage/src/main/java/com/emarsys/mobileengage/iam/InAppEventHandlerInternal.java:
--------------------------------------------------------------------------------
1 | package com.emarsys.mobileengage.iam;
2 |
3 | import com.emarsys.mobileengage.api.event.EventHandler;
4 |
5 |
6 | public class InAppEventHandlerInternal implements InAppEventHandler {
7 |
8 | private boolean isPaused;
9 | private EventHandler eventHandler;
10 |
11 | public void pause() {
12 | isPaused = true;
13 | }
14 |
15 | public void resume() {
16 | isPaused = false;
17 | }
18 |
19 | public boolean isPaused() {
20 | return isPaused;
21 | }
22 |
23 | public void setEventHandler(EventHandler eventHandler) {
24 | this.eventHandler = eventHandler;
25 | }
26 |
27 | public EventHandler getEventHandler() {
28 | return eventHandler;
29 | }
30 |
31 | }
32 |
--------------------------------------------------------------------------------
/mobile-engage/src/main/java/com/emarsys/mobileengage/iam/InAppInternal.kt:
--------------------------------------------------------------------------------
1 | package com.emarsys.mobileengage.iam
2 |
3 | import com.emarsys.mobileengage.event.EventServiceInternal
4 |
5 | interface InAppInternal : InAppEventHandler, EventServiceInternal
--------------------------------------------------------------------------------
/mobile-engage/src/main/java/com/emarsys/mobileengage/iam/PushToInAppAction.kt:
--------------------------------------------------------------------------------
1 | package com.emarsys.mobileengage.iam
2 |
3 | import android.app.Activity
4 | import com.emarsys.core.activity.ActivityLifecycleAction
5 | import com.emarsys.core.activity.ActivityLifecyclePriorities
6 | import com.emarsys.core.provider.timestamp.TimestampProvider
7 |
8 | class PushToInAppAction(
9 | private val overlayInAppPresenter: OverlayInAppPresenter,
10 | private val campaignId: String,
11 | private val html: String,
12 | private val sid: String?,
13 | private val url: String?,
14 | private val timestampProvider: TimestampProvider,
15 | override val priority: Int = ActivityLifecyclePriorities.PUSH_TO_INAPP_ACTION_PRIORITY,
16 | override val repeatable: Boolean = false,
17 | override val triggeringLifecycle: ActivityLifecycleAction.ActivityLifecycle = ActivityLifecycleAction.ActivityLifecycle.RESUME
18 | ) : ActivityLifecycleAction {
19 | override fun execute(activity: Activity?) {
20 | overlayInAppPresenter.present(
21 | campaignId, sid, url, null, timestampProvider.provideTimestamp(), html, null
22 | )
23 | }
24 | }
--------------------------------------------------------------------------------
/mobile-engage/src/main/java/com/emarsys/mobileengage/iam/dialog/action/OnDialogShownAction.java:
--------------------------------------------------------------------------------
1 | package com.emarsys.mobileengage.iam.dialog.action;
2 |
3 | public interface OnDialogShownAction {
4 | void execute(String campaignId, String sid, String url);
5 | }
6 |
--------------------------------------------------------------------------------
/mobile-engage/src/main/java/com/emarsys/mobileengage/iam/dialog/action/SaveDisplayedIamAction.kt:
--------------------------------------------------------------------------------
1 | package com.emarsys.mobileengage.iam.dialog.action
2 |
3 | import com.emarsys.core.database.repository.Repository
4 | import com.emarsys.core.database.repository.SqlSpecification
5 | import com.emarsys.core.handler.ConcurrentHandlerHolder
6 | import com.emarsys.core.provider.timestamp.TimestampProvider
7 | import com.emarsys.mobileengage.iam.model.displayediam.DisplayedIam
8 |
9 | class SaveDisplayedIamAction(
10 | var concurrentHandlerHolder: ConcurrentHandlerHolder,
11 | var repository: Repository,
12 | var timestampProvider: TimestampProvider
13 | ) : OnDialogShownAction {
14 |
15 | override fun execute(campaignId: String, sid: String?, url: String?) {
16 | concurrentHandlerHolder.coreHandler.post {
17 | val iam = DisplayedIam(campaignId, timestampProvider.provideTimestamp())
18 | repository.add(iam)
19 | }
20 | }
21 | }
--------------------------------------------------------------------------------
/mobile-engage/src/main/java/com/emarsys/mobileengage/iam/jsbridge/IamJsBridgeFactory.kt:
--------------------------------------------------------------------------------
1 | package com.emarsys.mobileengage.iam.jsbridge
2 |
3 | import com.emarsys.core.Mockable
4 | import com.emarsys.core.handler.ConcurrentHandlerHolder
5 |
6 | @Mockable
7 | class IamJsBridgeFactory(private val concurrentHandlerHolder: ConcurrentHandlerHolder) {
8 |
9 | fun createJsBridge(
10 | jsCommandFactory: JSCommandFactory
11 | ): IamJsBridge {
12 | return IamJsBridge(concurrentHandlerHolder, jsCommandFactory)
13 | }
14 | }
--------------------------------------------------------------------------------
/mobile-engage/src/main/java/com/emarsys/mobileengage/iam/jsbridge/JSCommand.kt:
--------------------------------------------------------------------------------
1 | package com.emarsys.mobileengage.iam.jsbridge
2 |
3 | import org.json.JSONObject
4 |
5 | typealias JSCommand = (property: String?, json: JSONObject) -> Unit
--------------------------------------------------------------------------------
/mobile-engage/src/main/java/com/emarsys/mobileengage/iam/jsbridge/OnAppEventListener.kt:
--------------------------------------------------------------------------------
1 | package com.emarsys.mobileengage.iam.jsbridge
2 |
3 | import org.json.JSONObject
4 |
5 | typealias OnAppEventListener = (property: String?, json: JSONObject) -> Unit
6 |
--------------------------------------------------------------------------------
/mobile-engage/src/main/java/com/emarsys/mobileengage/iam/jsbridge/OnButtonClickedListener.kt:
--------------------------------------------------------------------------------
1 | package com.emarsys.mobileengage.iam.jsbridge
2 |
3 | import org.json.JSONObject
4 |
5 | typealias OnButtonClickedListener = (property: String?, json: JSONObject) -> Unit
6 |
--------------------------------------------------------------------------------
/mobile-engage/src/main/java/com/emarsys/mobileengage/iam/jsbridge/OnCloseListener.kt:
--------------------------------------------------------------------------------
1 | package com.emarsys.mobileengage.iam.jsbridge
2 |
3 | typealias OnCloseListener = () -> Unit
4 |
--------------------------------------------------------------------------------
/mobile-engage/src/main/java/com/emarsys/mobileengage/iam/jsbridge/OnMEEventListener.kt:
--------------------------------------------------------------------------------
1 | package com.emarsys.mobileengage.iam.jsbridge
2 |
3 | import org.json.JSONObject
4 |
5 | typealias OnMEEventListener = (property: String?, json: JSONObject) -> Unit
--------------------------------------------------------------------------------
/mobile-engage/src/main/java/com/emarsys/mobileengage/iam/jsbridge/OnOpenExternalUrlListener.kt:
--------------------------------------------------------------------------------
1 | package com.emarsys.mobileengage.iam.jsbridge
2 |
3 | import org.json.JSONObject
4 |
5 | typealias OnOpenExternalUrlListener = (property: String?, json: JSONObject) -> Unit
6 |
--------------------------------------------------------------------------------
/mobile-engage/src/main/java/com/emarsys/mobileengage/iam/model/InAppMetaData.kt:
--------------------------------------------------------------------------------
1 | package com.emarsys.mobileengage.iam.model
2 |
3 | import java.io.Serializable
4 |
5 | data class InAppMetaData(val campaignId: String, val sid: String?, val url: String?): Serializable
--------------------------------------------------------------------------------
/mobile-engage/src/main/java/com/emarsys/mobileengage/iam/model/buttonclicked/ButtonClicked.kt:
--------------------------------------------------------------------------------
1 | package com.emarsys.mobileengage.iam.model.buttonclicked
2 |
3 | data class ButtonClicked(val campaignId: String, val buttonId: String, val timestamp: Long)
--------------------------------------------------------------------------------
/mobile-engage/src/main/java/com/emarsys/mobileengage/iam/webview/IamWebViewClient.kt:
--------------------------------------------------------------------------------
1 | package com.emarsys.mobileengage.iam.webview
2 |
3 | import android.webkit.WebView
4 | import android.webkit.WebViewClient
5 | import com.emarsys.core.handler.ConcurrentHandlerHolder
6 |
7 | class IamWebViewClient(
8 | private val listener: MessageLoadedListener,
9 | private val concurrentHandlerHolder: ConcurrentHandlerHolder
10 | ) : WebViewClient() {
11 | override fun onPageFinished(view: WebView, url: String) {
12 | super.onPageFinished(view, url)
13 | concurrentHandlerHolder.postOnMain { listener.onMessageLoaded() }
14 | }
15 | }
--------------------------------------------------------------------------------
/mobile-engage/src/main/java/com/emarsys/mobileengage/iam/webview/IamWebViewCreationFailedException.kt:
--------------------------------------------------------------------------------
1 | package com.emarsys.mobileengage.iam.webview
2 |
3 | data class IamWebViewCreationFailedException(val statusMessage: String = "IamWebView creation failed!") : Exception(statusMessage)
--------------------------------------------------------------------------------
/mobile-engage/src/main/java/com/emarsys/mobileengage/iam/webview/IamWebViewFactory.kt:
--------------------------------------------------------------------------------
1 | package com.emarsys.mobileengage.iam.webview
2 |
3 | import android.content.Context
4 | import com.emarsys.core.Mockable
5 | import com.emarsys.core.handler.ConcurrentHandlerHolder
6 | import com.emarsys.mobileengage.iam.jsbridge.IamJsBridgeFactory
7 | import com.emarsys.mobileengage.iam.jsbridge.JSCommandFactoryProvider
8 |
9 | @Mockable
10 | class IamWebViewFactory(
11 | private val jsBridgeFactory: IamJsBridgeFactory,
12 | private val jsCommandFactoryProvider: JSCommandFactoryProvider,
13 | private val concurrentHandlerHolder: ConcurrentHandlerHolder
14 | ) {
15 |
16 | fun create(context: Context): IamWebView {
17 | return IamWebView(
18 | concurrentHandlerHolder,
19 | jsBridgeFactory,
20 | jsCommandFactoryProvider.provide(),
21 | context
22 | )
23 | }
24 |
25 | }
--------------------------------------------------------------------------------
/mobile-engage/src/main/java/com/emarsys/mobileengage/iam/webview/MessageLoadedListener.java:
--------------------------------------------------------------------------------
1 | package com.emarsys.mobileengage.iam.webview;
2 |
3 | public interface MessageLoadedListener {
4 | void onMessageLoaded();
5 | }
6 |
--------------------------------------------------------------------------------
/mobile-engage/src/main/java/com/emarsys/mobileengage/iam/webview/Provider.kt:
--------------------------------------------------------------------------------
1 | package com.emarsys.mobileengage.iam.webview
2 |
3 | interface Provider {
4 |
5 | fun provide(): T
6 |
7 | }
--------------------------------------------------------------------------------
/mobile-engage/src/main/java/com/emarsys/mobileengage/inbox/MessageInboxInternal.kt:
--------------------------------------------------------------------------------
1 | package com.emarsys.mobileengage.inbox
2 |
3 | import com.emarsys.core.api.result.CompletionListener
4 | import com.emarsys.core.api.result.ResultListener
5 | import com.emarsys.core.api.result.Try
6 | import com.emarsys.mobileengage.api.inbox.InboxResult
7 |
8 |
9 | interface MessageInboxInternal {
10 | fun fetchMessages(resultListener: ResultListener>)
11 |
12 | fun addTag(tag: String, messageId: String, completionListener: CompletionListener?)
13 |
14 | fun removeTag(tag: String, messageId: String, completionListener: CompletionListener?)
15 | }
--------------------------------------------------------------------------------
/mobile-engage/src/main/java/com/emarsys/mobileengage/notification/LaunchActivityCommandLifecycleCallbacks.kt:
--------------------------------------------------------------------------------
1 | package com.emarsys.mobileengage.notification
2 |
3 | import android.app.Activity
4 | import android.app.Application
5 | import android.os.Bundle
6 | import java.util.concurrent.CountDownLatch
7 |
8 | class LaunchActivityCommandLifecycleCallbacks(private val latch: CountDownLatch) :
9 | Application.ActivityLifecycleCallbacks {
10 | override fun onActivityPaused(activity: Activity) {
11 | }
12 |
13 | override fun onActivityStarted(activity: Activity) {
14 | }
15 |
16 | override fun onActivityDestroyed(activity: Activity) {
17 | }
18 |
19 | override fun onActivitySaveInstanceState(activity: Activity, outState: Bundle) {
20 | }
21 |
22 | override fun onActivityStopped(activity: Activity) {
23 | }
24 |
25 | override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) {
26 | latch.countDown()
27 | }
28 |
29 | override fun onActivityResumed(activity: Activity) {
30 | latch.countDown()
31 | }
32 | }
--------------------------------------------------------------------------------
/mobile-engage/src/main/java/com/emarsys/mobileengage/notification/LaunchActivityCommandLifecycleCallbacksFactory.kt:
--------------------------------------------------------------------------------
1 | package com.emarsys.mobileengage.notification
2 |
3 | import android.app.Application.ActivityLifecycleCallbacks
4 | import com.emarsys.core.Mockable
5 | import java.util.concurrent.CountDownLatch
6 |
7 | @Mockable
8 | class LaunchActivityCommandLifecycleCallbacksFactory {
9 | fun create(latch: CountDownLatch): ActivityLifecycleCallbacks {
10 | return LaunchActivityCommandLifecycleCallbacks(latch)
11 | }
12 | }
--------------------------------------------------------------------------------
/mobile-engage/src/main/java/com/emarsys/mobileengage/notification/command/AppEventCommand.kt:
--------------------------------------------------------------------------------
1 | package com.emarsys.mobileengage.notification.command
2 |
3 | import android.content.Context
4 | import com.emarsys.core.Mockable
5 | import com.emarsys.core.handler.ConcurrentHandlerHolder
6 | import com.emarsys.mobileengage.api.event.EventHandler
7 | import com.emarsys.mobileengage.event.CacheableEventHandler
8 | import org.json.JSONObject
9 |
10 | @Mockable
11 | class AppEventCommand(
12 | private val context: Context,
13 | private val cacheableEventHandler: CacheableEventHandler,
14 | private val concurrentHandlerHolder: ConcurrentHandlerHolder,
15 | val name: String,
16 | val payload: JSONObject?
17 | ) : Runnable {
18 |
19 | val notificationEventHandler: EventHandler?
20 | get() = cacheableEventHandler
21 |
22 | override fun run() {
23 | val eventHandler = cacheableEventHandler
24 | concurrentHandlerHolder.postOnMain {
25 | eventHandler.handleEvent(context, name, payload)
26 | }
27 | }
28 | }
--------------------------------------------------------------------------------
/mobile-engage/src/main/java/com/emarsys/mobileengage/notification/command/CompositeCommand.kt:
--------------------------------------------------------------------------------
1 | package com.emarsys.mobileengage.notification.command
2 |
3 | class CompositeCommand(val commands: List) : Runnable {
4 |
5 | override fun run() {
6 | val filterIsInstance = commands.filterIsInstance()
7 | filterIsInstance.forEach {
8 | it.run()
9 | }
10 | (commands - filterIsInstance).forEach {
11 | it.run()
12 | }
13 | }
14 | }
--------------------------------------------------------------------------------
/mobile-engage/src/main/java/com/emarsys/mobileengage/notification/command/DismissNotificationCommand.kt:
--------------------------------------------------------------------------------
1 | package com.emarsys.mobileengage.notification.command
2 |
3 | import android.app.NotificationManager
4 | import android.content.Context
5 | import com.emarsys.mobileengage.service.NotificationData
6 |
7 | class DismissNotificationCommand(
8 | private val context: Context,
9 | private val notificationData: NotificationData?
10 | ): Runnable {
11 |
12 | override fun run() {
13 | val manager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
14 | notificationData?.collapseId?.let {
15 | manager.cancel(it, it.hashCode())
16 | }
17 | }
18 | }
--------------------------------------------------------------------------------
/mobile-engage/src/main/java/com/emarsys/mobileengage/notification/command/NotificationInformationCommand.kt:
--------------------------------------------------------------------------------
1 | package com.emarsys.mobileengage.notification.command
2 |
3 | import com.emarsys.mobileengage.api.push.NotificationInformation
4 | import com.emarsys.mobileengage.push.NotificationInformationListenerProvider
5 |
6 | class NotificationInformationCommand(private val notificationInformationListenerProvider: NotificationInformationListenerProvider,
7 | val notificationInformation: NotificationInformation) : Runnable {
8 |
9 | override fun run() {
10 | notificationInformationListenerProvider.notificationInformationListener?.onNotificationInformationReceived(notificationInformation)
11 | }
12 | }
--------------------------------------------------------------------------------
/mobile-engage/src/main/java/com/emarsys/mobileengage/notification/command/OpenExternalUrlCommand.kt:
--------------------------------------------------------------------------------
1 | package com.emarsys.mobileengage.notification.command
2 |
3 | import android.content.Context
4 | import android.content.Intent
5 | import com.emarsys.core.Mockable
6 | import com.emarsys.core.util.SystemUtils
7 | import com.emarsys.core.util.log.Logger
8 | import com.emarsys.core.util.log.entry.StatusLog
9 |
10 | @Mockable
11 | class OpenExternalUrlCommand(val intent: Intent, val context: Context) : Runnable {
12 | override fun run() {
13 | try {
14 | context.startActivity(intent)
15 | } catch (exception: Exception) {
16 | Logger.debug(StatusLog(OpenExternalUrlCommand::class.java, SystemUtils.getCallerMethodName(), mapOf(
17 | "intent" to intent,
18 | "exception" to exception
19 | )))
20 | }
21 | }
22 | }
--------------------------------------------------------------------------------
/mobile-engage/src/main/java/com/emarsys/mobileengage/notification/command/SilentNotificationInformationCommand.kt:
--------------------------------------------------------------------------------
1 | package com.emarsys.mobileengage.notification.command
2 |
3 | import com.emarsys.mobileengage.api.push.NotificationInformation
4 | import com.emarsys.mobileengage.push.SilentNotificationInformationListenerProvider
5 |
6 | class SilentNotificationInformationCommand(
7 | private val silentNotificationInformationListenerProvider: SilentNotificationInformationListenerProvider,
8 | private val notificationInformation: NotificationInformation): Runnable {
9 |
10 | override fun run() {
11 | silentNotificationInformationListenerProvider.silentNotificationInformationListener?.onNotificationInformationReceived(notificationInformation)
12 | }
13 | }
--------------------------------------------------------------------------------
/mobile-engage/src/main/java/com/emarsys/mobileengage/notification/command/TrackActionClickCommand.kt:
--------------------------------------------------------------------------------
1 | package com.emarsys.mobileengage.notification.command
2 |
3 | import com.emarsys.mobileengage.event.EventServiceInternal
4 |
5 | class TrackActionClickCommand(
6 | private val eventServiceInternal: EventServiceInternal,
7 | private val buttonId: String,
8 | val sid: String
9 | ) : Runnable {
10 | override fun run() {
11 | val payload: MutableMap = HashMap()
12 | payload["button_id"] = buttonId
13 | payload["origin"] = "button"
14 | payload["sid"] = sid
15 |
16 | eventServiceInternal.trackInternalCustomEventAsync("push:click", payload, null)
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/mobile-engage/src/main/java/com/emarsys/mobileengage/notification/command/TrackMessageOpenCommand.kt:
--------------------------------------------------------------------------------
1 | package com.emarsys.mobileengage.notification.command
2 |
3 | import com.emarsys.mobileengage.push.PushInternal
4 |
5 | class TrackMessageOpenCommand(
6 | private val pushInternal: PushInternal,
7 | private val sid: String?
8 | ) : Runnable {
9 |
10 | override fun run() {
11 | pushInternal.trackMessageOpen(sid, null)
12 | }
13 | }
--------------------------------------------------------------------------------
/mobile-engage/src/main/java/com/emarsys/mobileengage/push/DefaultPushTokenProvider.kt:
--------------------------------------------------------------------------------
1 | package com.emarsys.mobileengage.push
2 |
3 | import com.emarsys.core.storage.Storage
4 |
5 | class DefaultPushTokenProvider(private val pushTokenStorage: Storage) : PushTokenProvider {
6 |
7 | override fun providePushToken(): String? {
8 | return pushTokenStorage.get()
9 | }
10 | }
--------------------------------------------------------------------------------
/mobile-engage/src/main/java/com/emarsys/mobileengage/push/NotificationInformationListenerProvider.kt:
--------------------------------------------------------------------------------
1 | package com.emarsys.mobileengage.push
2 |
3 | import com.emarsys.core.Mockable
4 | import com.emarsys.mobileengage.api.push.NotificationInformationListener
5 |
6 | @Mockable
7 | class NotificationInformationListenerProvider(var notificationInformationListener: NotificationInformationListener?)
--------------------------------------------------------------------------------
/mobile-engage/src/main/java/com/emarsys/mobileengage/push/PushInternal.kt:
--------------------------------------------------------------------------------
1 | package com.emarsys.mobileengage.push
2 |
3 | import com.emarsys.core.api.result.CompletionListener
4 | import com.emarsys.mobileengage.api.event.EventHandler
5 | import com.emarsys.mobileengage.api.push.NotificationInformationListener
6 |
7 | interface PushInternal {
8 | fun setPushToken(pushToken: String, completionListener: CompletionListener?)
9 | val pushToken: String?
10 | fun clearPushToken(completionListener: CompletionListener?)
11 | fun trackMessageOpen(sid: String?, completionListener: CompletionListener?)
12 | fun setNotificationEventHandler(notificationEventHandler: EventHandler)
13 | fun setSilentMessageEventHandler(silentMessageEventHandler: EventHandler)
14 | fun setNotificationInformationListener(notificationInformationListener: NotificationInformationListener)
15 | fun setSilentNotificationInformationListener(silentNotificationInformationListener: NotificationInformationListener)
16 | }
--------------------------------------------------------------------------------
/mobile-engage/src/main/java/com/emarsys/mobileengage/push/PushTokenProvider.kt:
--------------------------------------------------------------------------------
1 | package com.emarsys.mobileengage.push
2 |
3 | interface PushTokenProvider {
4 |
5 | fun providePushToken(): String?
6 | }
--------------------------------------------------------------------------------
/mobile-engage/src/main/java/com/emarsys/mobileengage/push/SilentNotificationInformationListenerProvider.kt:
--------------------------------------------------------------------------------
1 | package com.emarsys.mobileengage.push
2 |
3 | import com.emarsys.core.Mockable
4 | import com.emarsys.mobileengage.api.push.NotificationInformationListener
5 |
6 | @Mockable
7 | class SilentNotificationInformationListenerProvider(var silentNotificationInformationListener: NotificationInformationListener?)
--------------------------------------------------------------------------------
/mobile-engage/src/main/java/com/emarsys/mobileengage/request/mapper/DefaultRequestHeaderMapper.kt:
--------------------------------------------------------------------------------
1 | package com.emarsys.mobileengage.request.mapper
2 |
3 | import com.emarsys.core.request.model.RequestModel
4 | import com.emarsys.mobileengage.MobileEngageRequestContext
5 |
6 | class DefaultRequestHeaderMapper(
7 | override val requestContext: MobileEngageRequestContext) : AbstractRequestMapper(requestContext){
8 |
9 | override fun shouldMapRequestModel(requestModel: RequestModel): Boolean = true
10 |
11 | override fun createHeaders(requestModel: RequestModel): Map {
12 | val defaultHeaders: MutableMap = requestModel.headers.toMutableMap()
13 |
14 | defaultHeaders["Content-Type"] = "application/json"
15 | defaultHeaders["X-EMARSYS-SDK-VERSION"] = requestContext.deviceInfo.sdkVersion
16 | defaultHeaders["X-EMARSYS-SDK-MODE"] = if (requestContext.deviceInfo.isDebugMode) "debug" else "production"
17 | return defaultHeaders
18 | }
19 | }
--------------------------------------------------------------------------------
/mobile-engage/src/main/java/com/emarsys/mobileengage/responsehandler/ClientInfoResponseHandler.kt:
--------------------------------------------------------------------------------
1 | package com.emarsys.mobileengage.responsehandler
2 |
3 | import com.emarsys.core.device.DeviceInfo
4 | import com.emarsys.core.response.AbstractResponseHandler
5 | import com.emarsys.core.response.ResponseModel
6 | import com.emarsys.core.storage.Storage
7 | import com.emarsys.mobileengage.endpoint.Endpoint
8 |
9 |
10 | class ClientInfoResponseHandler(private val deviceInfo: DeviceInfo,
11 | private val deviceInfoPayloadStorage: Storage) : AbstractResponseHandler() {
12 |
13 | override fun shouldHandleResponse(responseModel: ResponseModel): Boolean {
14 | val url = responseModel.requestModel.url.toString()
15 | return url.startsWith(Endpoint.ME_CLIENT_HOST) && url.endsWith("/client")
16 | }
17 |
18 | override fun handleResponse(responseModel: ResponseModel) {
19 | deviceInfoPayloadStorage.set(deviceInfo.deviceInfoPayload)
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/mobile-engage/src/main/java/com/emarsys/mobileengage/service/mapper/RemoteMessageMapper.kt:
--------------------------------------------------------------------------------
1 | package com.emarsys.mobileengage.service.mapper
2 |
3 | import com.emarsys.mobileengage.R
4 | import com.emarsys.mobileengage.service.NotificationData
5 |
6 | interface RemoteMessageMapper {
7 |
8 | companion object {
9 | const val METADATA_SMALL_NOTIFICATION_ICON_KEY =
10 | "com.emarsys.mobileengage.small_notification_icon"
11 | const val METADATA_NOTIFICATION_COLOR = "com.emarsys.mobileengage.notification_color"
12 | val DEFAULT_SMALL_NOTIFICATION_ICON = R.drawable.default_small_notification_icon
13 | }
14 |
15 | fun map(remoteMessageData: Map): NotificationData
16 | }
--------------------------------------------------------------------------------
/mobile-engage/src/main/java/com/emarsys/mobileengage/service/mapper/RemoteMessageMapperFactory.kt:
--------------------------------------------------------------------------------
1 | package com.emarsys.mobileengage.service.mapper
2 |
3 | import android.content.Context
4 | import com.emarsys.core.Mockable
5 | import com.emarsys.core.provider.uuid.UUIDProvider
6 | import com.emarsys.core.resource.MetaDataReader
7 | import com.emarsys.mobileengage.service.MessagingServiceUtils.MESSAGE_FILTER
8 |
9 | @Mockable
10 | class RemoteMessageMapperFactory(
11 | private val metaDataReader: MetaDataReader,
12 | private val context: Context,
13 | private val uuidProvider: UUIDProvider
14 | ) {
15 |
16 | fun create(remoteMessageData: Map): RemoteMessageMapper {
17 | return if (remoteMessageData.containsKey(MESSAGE_FILTER)) {
18 | RemoteMessageMapperV1(
19 | metaDataReader,
20 | context,
21 | uuidProvider
22 | )
23 | } else {
24 | RemoteMessageMapperV2(
25 | metaDataReader,
26 | context,
27 | uuidProvider
28 | )
29 | }
30 | }
31 | }
--------------------------------------------------------------------------------
/mobile-engage/src/main/java/com/emarsys/mobileengage/session/SessionIdHolder.kt:
--------------------------------------------------------------------------------
1 | package com.emarsys.mobileengage.session
2 |
3 | import com.emarsys.core.Mockable
4 |
5 | @Mockable
6 | data class SessionIdHolder(var sessionId: String?)
--------------------------------------------------------------------------------
/mobile-engage/src/main/java/com/emarsys/mobileengage/storage/MobileEngageStorageKey.kt:
--------------------------------------------------------------------------------
1 | package com.emarsys.mobileengage.storage
2 |
3 | import com.emarsys.core.storage.StorageKey
4 | import java.util.Locale
5 |
6 | enum class MobileEngageStorageKey : StorageKey {
7 | REFRESH_TOKEN,
8 | CONTACT_TOKEN,
9 | CLIENT_STATE,
10 | CONTACT_FIELD_VALUE,
11 | PUSH_TOKEN,
12 | LOCAL_PUSH_TOKEN,
13 | EVENT_SERVICE_URL,
14 | CLIENT_SERVICE_URL,
15 | MESSAGE_INBOX_SERVICE_URL,
16 | DEEPLINK_SERVICE_URL,
17 | GEOFENCE_ENABLED,
18 | DEVICE_EVENT_STATE,
19 | DEVICE_INFO_HASH,
20 | GEOFENCE_INITIAL_ENTER_TRIGGER;
21 |
22 | override fun getKey(): String {
23 | return "mobile_engage_" + name.lowercase(Locale.getDefault())
24 | }
25 | }
--------------------------------------------------------------------------------
/mobile-engage/src/main/res/drawable/default_small_notification_icon.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
--------------------------------------------------------------------------------
/mobile-engage/src/main/res/layout/mobile_engage_in_app_message.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/predict-api/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/predict-api/src/main/java/com/emarsys/predict/api/model/CartItem.kt:
--------------------------------------------------------------------------------
1 | package com.emarsys.predict.api.model
2 |
3 | interface CartItem {
4 | val itemId: String
5 | val price: Double
6 | val quantity: Double
7 | }
--------------------------------------------------------------------------------
/predict-api/src/main/java/com/emarsys/predict/api/model/Logic.kt:
--------------------------------------------------------------------------------
1 | package com.emarsys.predict.api.model
2 |
3 | interface Logic {
4 | val logicName: String
5 | val data: Map
6 | val variants: List
7 | }
--------------------------------------------------------------------------------
/predict-api/src/main/java/com/emarsys/predict/api/model/PredictCartItem.kt:
--------------------------------------------------------------------------------
1 | package com.emarsys.predict.api.model
2 |
3 |
4 | data class PredictCartItem(override val itemId: String, override val price: Double, override val quantity: Double) : CartItem
--------------------------------------------------------------------------------
/predict-api/src/main/java/com/emarsys/predict/api/model/Product.kt:
--------------------------------------------------------------------------------
1 | package com.emarsys.predict.api.model
2 |
3 | import java.net.URL
4 |
5 | data class Product(
6 | val productId: String,
7 | val title: String,
8 | val linkUrl: String,
9 | val feature: String,
10 | val cohort: String,
11 | val customFields: Map = mutableMapOf(),
12 | private val imageUrlString: String? = null,
13 | val imageUrl: URL? = if (imageUrlString != null) URL(imageUrlString) else null,
14 | private val zoomImageUrlString: String? = null,
15 | val zoomImageUrl: URL? = if (zoomImageUrlString != null) URL(zoomImageUrlString) else null,
16 | val categoryPath: String? = null,
17 | val available: Boolean? = null,
18 | val productDescription: String? = null,
19 | val price: Float? = null,
20 | val msrp: Float? = null,
21 | val album: String? = null,
22 | val actor: String? = null,
23 | val artist: String? = null,
24 | val author: String? = null,
25 | val brand: String? = null,
26 | val year: Int? = null)
--------------------------------------------------------------------------------
/predict/src/androidTest/java/com/emarsys/predict/storage/PredictStorageKeyTest.kt:
--------------------------------------------------------------------------------
1 | package com.emarsys.predict.storage
2 |
3 | import io.kotest.data.forAll
4 | import io.kotest.data.row
5 | import io.kotest.matchers.shouldBe
6 | import kotlinx.coroutines.runBlocking
7 | import org.junit.Test
8 |
9 |
10 | class PredictStorageKeyTest {
11 |
12 |
13 | @Test
14 | fun testGetKey() = runBlocking {
15 | PredictStorageKey.values().map {
16 | "predict_${it.name.lowercase()}"
17 | }.zip(PredictStorageKey.values()) { stringValue, enum ->
18 | row(enum, stringValue)
19 | }.let {
20 | forAll(*it.toTypedArray()) { input, expected ->
21 | input.key shouldBe expected
22 | }
23 | }
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/predict/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/predict/src/main/java/com/emarsys/predict/PredictInternal.kt:
--------------------------------------------------------------------------------
1 | package com.emarsys.predict
2 |
3 | import com.emarsys.core.api.result.ResultListener
4 | import com.emarsys.core.api.result.Try
5 | import com.emarsys.predict.api.model.CartItem
6 | import com.emarsys.predict.api.model.Logic
7 | import com.emarsys.predict.api.model.Product
8 | import com.emarsys.predict.api.model.RecommendationFilter
9 |
10 | interface PredictInternal {
11 | fun setContact(contactFieldId: Int, contactFieldValue: String)
12 | fun clearContact()
13 | fun trackCart(items: List): String
14 | fun trackPurchase(orderId: String, items: List): String
15 | fun trackItemView(itemId: String): String
16 | fun trackCategoryView(categoryPath: String): String
17 | fun trackSearchTerm(searchTerm: String): String
18 | fun trackTag(tag: String, attributes: Map?)
19 | fun recommendProducts(recommendationLogic: Logic, limit: Int? = null, recommendationFilters: List? = null, availabilityZone: String? = null, resultListener: ResultListener>>)
20 | fun trackRecommendationClick(product: Product): String
21 | }
--------------------------------------------------------------------------------
/predict/src/main/java/com/emarsys/predict/endpoint/Endpoint.java:
--------------------------------------------------------------------------------
1 | package com.emarsys.predict.endpoint;
2 |
3 | public class Endpoint {
4 | public static final String PREDICT_BASE_URL = "https://recommender.scarabresearch.com/merchants";
5 |
6 | }
7 |
--------------------------------------------------------------------------------
/predict/src/main/java/com/emarsys/predict/request/PredictRequestContext.kt:
--------------------------------------------------------------------------------
1 | package com.emarsys.predict.request
2 |
3 | import com.emarsys.core.Mockable
4 | import com.emarsys.core.device.DeviceInfo
5 | import com.emarsys.core.provider.timestamp.TimestampProvider
6 | import com.emarsys.core.provider.uuid.UUIDProvider
7 | import com.emarsys.core.storage.KeyValueStore
8 |
9 | @Mockable
10 | data class PredictRequestContext(var merchantId: String?,
11 | val deviceInfo: DeviceInfo,
12 | val timestampProvider: TimestampProvider,
13 | val uuidProvider: UUIDProvider,
14 | val keyValueStore: KeyValueStore)
15 |
--------------------------------------------------------------------------------
/predict/src/main/java/com/emarsys/predict/response/XPResponseHandler.kt:
--------------------------------------------------------------------------------
1 | package com.emarsys.predict.response
2 |
3 | import com.emarsys.core.endpoint.ServiceEndpointProvider
4 | import com.emarsys.core.response.AbstractResponseHandler
5 | import com.emarsys.core.response.ResponseModel
6 | import com.emarsys.core.storage.KeyValueStore
7 | import com.emarsys.predict.DefaultPredictInternal
8 |
9 | class XPResponseHandler(private val keyValueStore: KeyValueStore, private val predictServiceEndpointProvider: ServiceEndpointProvider) : AbstractResponseHandler() {
10 |
11 | companion object {
12 | private const val XP = "xp"
13 | }
14 |
15 | override fun shouldHandleResponse(responseModel: ResponseModel): Boolean {
16 | val isPredictUrl = responseModel.requestModel.url.toString().startsWith(predictServiceEndpointProvider.provideEndpointHost())
17 | val hasXPCookie = responseModel.cookies["xp"] != null
18 | return isPredictUrl && hasXPCookie
19 | }
20 |
21 | override fun handleResponse(responseModel: ResponseModel) {
22 | val xp = responseModel.cookies[XP]!!.value
23 | keyValueStore.putString(DefaultPredictInternal.XP_KEY, xp)
24 | }
25 | }
--------------------------------------------------------------------------------
/predict/src/main/java/com/emarsys/predict/storage/PredictStorageKey.kt:
--------------------------------------------------------------------------------
1 | package com.emarsys.predict.storage
2 |
3 | import com.emarsys.core.storage.StorageKey
4 | import java.util.*
5 |
6 | enum class PredictStorageKey : StorageKey {
7 | PREDICT_SERVICE_URL;
8 |
9 | override fun getKey(): String {
10 | return "predict_" + name.lowercase(Locale.getDefault())
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/predict/src/main/java/com/emarsys/predict/util/CartItemUtils.java:
--------------------------------------------------------------------------------
1 | package com.emarsys.predict.util;
2 |
3 | import com.emarsys.core.util.Assert;
4 | import com.emarsys.predict.api.model.CartItem;
5 |
6 | import java.net.URLEncoder;
7 | import java.util.List;
8 |
9 | import kotlin.text.Charsets;
10 |
11 | public class CartItemUtils {
12 |
13 | public static String cartItemsToQueryParam(List items) {
14 | Assert.notNull(items, "Items must not be null!");
15 | Assert.elementsNotNull(items, "Item elements must not be null!");
16 |
17 | StringBuilder sb = new StringBuilder();
18 |
19 | for (int i = 0; i < items.size(); ++i) {
20 | if (i != 0) {
21 | sb.append("|");
22 | }
23 | sb.append(cartItemToQueryParam(items.get(i)));
24 | }
25 |
26 | return sb.toString();
27 | }
28 |
29 | private static String cartItemToQueryParam(CartItem cartItem) {
30 | return "i:" + URLEncoder.encode(cartItem.getItemId(), Charsets.UTF_8) + ",p:" + cartItem.getPrice() + ",q:" + cartItem.getQuantity();
31 | }
32 |
33 | }
34 |
--------------------------------------------------------------------------------
/proguard-multidex-rules.pro:
--------------------------------------------------------------------------------
1 | -keep class androidx.test.** { *; }
2 | -keep class org.junit.** { *; }
3 | -keep public class com.emarsys.** { *; }
4 | -keep public class com.google.firebase.provider.FirebaseInitProvider { *; }
--------------------------------------------------------------------------------
/release.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 | export EXCLUDE_GOOGLE_SERVICES_API_KEY=true
3 | echo Releasing with version: $1
4 | git tag -a $1 -m $1
5 | git push --tags
6 |
--------------------------------------------------------------------------------
/repo-info.json:
--------------------------------------------------------------------------------
1 | {
2 | "is_in_production": true,
3 | "is_scannable": true,
4 | "is_critical": false,
5 | "contact": "SDK Team - Legacy Android SDK",
6 | "hosted": null
7 | }
8 |
--------------------------------------------------------------------------------
/revoke.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | echo Revoke relese with version: $1
4 |
5 | git tag -d $1
6 | git push --delete origin $1
--------------------------------------------------------------------------------
/sample/README.md:
--------------------------------------------------------------------------------
1 | # Emarsys SDK Sample app
2 |
3 | We created a sample app to demonstrate the basic functionality of our SDK, and to give an example how to integrate Emarsys in your application.
4 |
5 | ## Getting started with the released SDK
6 |
7 | 1. Clone the project from [here](https://github.com/emartech/android-emarsys-sdk).
8 | 2. Run `./gradlew build` in the terminal in the root directory of the repository.
9 |
10 | > __`Note`__
11 | >
12 | > These steps are working with the currently released version of the SDK. Please be aware that the master branch might not be compatible with it!
13 |
14 |
15 | ## Getting started with the current development state of the SDK
16 |
17 | 1. Clone the project from [here](https://github.com/emartech/android-emarsys-sdk).
18 | 2. Create a file named `localConfig.properties` in the root directory of the project and paste `useLocalDependencies=true` in it.
19 | 3. Run `./gradlew build` in the terminal in the root directory of the repository.
20 |
21 | > __`Note`__
22 | >
23 | > Please keep in mind that this may contain unstable/in-development features which are not currently supported to use in production.
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/sample/proguard-multidex-rules.pro:
--------------------------------------------------------------------------------
1 | -keep class androidx.*.widget.** { *; }
2 | -keep class androidx.test.** { *; }
3 | -keep class androidx.navigation.** { *; }
4 | -keep class org.junit.** { *; }
5 | -keep public class com.emarsys.** { *; }
6 | -keep public class com.google.** { *; }
7 | -keep public class android.** { *; }
--------------------------------------------------------------------------------
/sample/src/main/kotlin/com/emarsys/sample/CustomMessagingService.kt:
--------------------------------------------------------------------------------
1 | package com.emarsys.sample
2 |
3 | import com.emarsys.Emarsys
4 | import com.emarsys.service.EmarsysFirebaseMessagingServiceUtils
5 | import com.google.firebase.messaging.FirebaseMessagingService
6 | import com.google.firebase.messaging.RemoteMessage
7 |
8 | class CustomMessagingService : FirebaseMessagingService() {
9 |
10 | override fun onNewToken(token: String) {
11 | super.onNewToken(token)
12 |
13 | Emarsys.push.pushToken = token
14 | }
15 |
16 | override fun onMessageReceived(remoteMessage: RemoteMessage) {
17 | super.onMessageReceived(remoteMessage)
18 |
19 | val handledByEmarsysSDK =
20 | EmarsysFirebaseMessagingServiceUtils.handleMessage(this, remoteMessage)
21 |
22 | if (!handledByEmarsysSDK) {
23 | //handle your custom push message here
24 | }
25 | }
26 | }
--------------------------------------------------------------------------------
/sample/src/main/kotlin/com/emarsys/sample/inapp/UuidProvider.kt:
--------------------------------------------------------------------------------
1 | package com.emarsys.sample.inapp
2 |
3 | import java.util.*
4 |
5 | class UuidProvider {
6 |
7 | companion object {
8 | fun provide(): UUID {
9 | return UUID.randomUUID()
10 | }
11 | }
12 | }
--------------------------------------------------------------------------------
/sample/src/main/kotlin/com/emarsys/sample/inbox/component/ShowImage.kt:
--------------------------------------------------------------------------------
1 | package com.emarsys.sample.inbox
2 |
3 | import androidx.compose.foundation.Image
4 | import androidx.compose.foundation.layout.fillMaxSize
5 | import androidx.compose.runtime.Composable
6 | import androidx.compose.ui.Modifier
7 | import androidx.compose.ui.layout.ContentScale
8 | import androidx.compose.ui.platform.LocalContext
9 | import coil.annotation.ExperimentalCoilApi
10 | import coil.compose.rememberAsyncImagePainter
11 | import coil.request.ImageRequest
12 | import coil.size.Size
13 |
14 | @OptIn(ExperimentalCoilApi::class)
15 | @Composable
16 | fun ShowImage(imageUrl: String) {
17 | Image(
18 | painter = rememberAsyncImagePainter(
19 | ImageRequest.Builder(LocalContext.current).data(imageUrl).apply(block = fun ImageRequest.Builder.() {
20 | size(Size.ORIGINAL)
21 | }).build()
22 | ),
23 | contentDescription = null,
24 | modifier = Modifier.fillMaxSize(1f),
25 | contentScale = ContentScale.FillWidth
26 | )
27 | }
--------------------------------------------------------------------------------
/sample/src/main/kotlin/com/emarsys/sample/inbox/component/TextFromTags.kt:
--------------------------------------------------------------------------------
1 | package com.emarsys.sample.inbox
2 |
3 | import androidx.compose.foundation.layout.Arrangement
4 | import androidx.compose.foundation.layout.Row
5 | import androidx.compose.foundation.layout.padding
6 | import androidx.compose.material.Text
7 | import androidx.compose.runtime.Composable
8 | import androidx.compose.ui.Modifier
9 | import androidx.compose.ui.res.stringResource
10 | import androidx.compose.ui.unit.dp
11 | import com.emarsys.sample.R
12 | import com.emarsys.sample.ui.style.rowWithMaxWidth
13 |
14 | @Composable
15 | fun TextFromTags(tags: List) {
16 | Row(
17 | modifier = Modifier
18 | .rowWithMaxWidth()
19 | .padding(start = 8.dp),
20 | horizontalArrangement = Arrangement.Start
21 | ) {
22 | Text(text = "${stringResource(id = R.string.tags)}: ")
23 | tags.forEach { tag ->
24 | Text(text = "$tag ")
25 | }
26 | }
27 | }
--------------------------------------------------------------------------------
/sample/src/main/kotlin/com/emarsys/sample/inbox/event/InboxAppEventHandler.kt:
--------------------------------------------------------------------------------
1 | package com.emarsys.sample.inbox.event
2 |
3 | import android.content.Context
4 | import com.emarsys.mobileengage.api.event.EventHandler
5 | import com.emarsys.sample.ui.component.toast.customTextToast
6 | import org.json.JSONObject
7 |
8 | class InboxAppEventHandler: EventHandler {
9 | override fun handleEvent(context: Context, eventName: String, payload: JSONObject?) {
10 | customTextToast(context, "$eventName - $payload")
11 | }
12 | }
--------------------------------------------------------------------------------
/sample/src/main/kotlin/com/emarsys/sample/main/navigation/NavigationBarItem.kt:
--------------------------------------------------------------------------------
1 | package com.emarsys.sample.main.navigation
2 |
3 | import com.emarsys.sample.R
4 |
5 | sealed class NavigationBarItem(var route: String, var icon: Int, var title: String) {
6 | data object BottomDashBoard : NavigationBarItem("dashboard", R.drawable.ic_settings, "Dashboard")
7 | data object BottomMobileEngage : NavigationBarItem("mobile-engage", R.drawable.mobile_engage_logo_icon, "Mobile Engage")
8 | data object BottomInbox : NavigationBarItem("inbox", R.drawable.inbox_mailbox_icon, "Inbox")
9 | data object BottomPredict : NavigationBarItem("predict", R.drawable.predict_scarab_icon, "Predict")
10 | data object BottomInApp : NavigationBarItem("inapp", R.drawable.mobile_engage_logo_icon, "InApp")
11 | }
--------------------------------------------------------------------------------
/sample/src/main/kotlin/com/emarsys/sample/main/sdkinfo/TopCardViewModel.kt:
--------------------------------------------------------------------------------
1 | package com.emarsys.sample.main.sdkinfo
2 |
3 | import androidx.compose.runtime.mutableStateOf
4 | import androidx.lifecycle.ViewModel
5 |
6 | object TopCardViewModel : ViewModel() {
7 | val expanded = mutableStateOf(false)
8 |
9 | fun getMoreLessText(): String {
10 | return if (expanded.value) "Show less..." else "Show more..."
11 | }
12 |
13 | fun toggleCardExpansion() {
14 | expanded.value = !expanded.value
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/sample/src/main/kotlin/com/emarsys/sample/mobileengage/EventPayloadTextArea.kt:
--------------------------------------------------------------------------------
1 | package com.emarsys.sample.ui.component.textfield
2 |
3 | import androidx.compose.foundation.layout.height
4 | import androidx.compose.material.OutlinedTextField
5 | import androidx.compose.material.Text
6 | import androidx.compose.runtime.Composable
7 | import androidx.compose.runtime.MutableState
8 | import androidx.compose.ui.ExperimentalComposeUiApi
9 | import androidx.compose.ui.Modifier
10 | import androidx.compose.ui.unit.dp
11 | import com.emarsys.sample.ui.style.rowWithPointEightWidth
12 |
13 | @ExperimentalComposeUiApi
14 | @Composable
15 | fun EventPayloadTextArea(
16 | fieldToEdit: MutableState,
17 | label: String = ""
18 | ) {
19 | OutlinedTextField(
20 | modifier = Modifier
21 | .rowWithPointEightWidth()
22 | .height(120.dp),
23 | label = { Text(text = label) },
24 | value = fieldToEdit.value,
25 | onValueChange = { fieldToEdit.value = it },
26 | maxLines = 3
27 | )
28 | }
--------------------------------------------------------------------------------
/sample/src/main/kotlin/com/emarsys/sample/predict/cart/SampleCartItem.kt:
--------------------------------------------------------------------------------
1 | package com.emarsys.sample.predict.cart
2 |
3 | import com.emarsys.predict.api.model.CartItem
4 |
5 | data class SampleCartItem(
6 | override val itemId: String,
7 | override val price: Double,
8 | override val quantity: Double
9 | ) : CartItem {
10 |
11 | override fun toString(): String {
12 | return "Sample Cart Item: id = $itemId"
13 | }
14 | }
--------------------------------------------------------------------------------
/sample/src/main/kotlin/com/emarsys/sample/predict/cart/SampleCartItemCard.kt:
--------------------------------------------------------------------------------
1 | package com.emarsys.sample.predict.cart
2 |
3 | import androidx.compose.foundation.layout.Arrangement
4 | import androidx.compose.foundation.layout.Row
5 | import androidx.compose.foundation.layout.padding
6 | import androidx.compose.material.Card
7 | import androidx.compose.material.Text
8 | import androidx.compose.runtime.Composable
9 | import androidx.compose.ui.Modifier
10 | import androidx.compose.ui.unit.dp
11 | import com.emarsys.sample.ui.cardWithPointEightWidth
12 | import com.emarsys.sample.ui.style.rowWithPointEightWidth
13 |
14 | @Composable
15 | fun SampleCartItemCard(sampleItem: SampleCartItem) {
16 | Card(
17 | modifier = Modifier.cardWithPointEightWidth()
18 | ) {
19 | Row(
20 | modifier = Modifier
21 | .rowWithPointEightWidth(),
22 | horizontalArrangement = Arrangement.Center
23 | ) {
24 | Text(
25 | text = sampleItem.toString(),
26 | modifier = Modifier.padding(2.dp)
27 | )
28 | }
29 | }
30 | }
31 |
32 |
--------------------------------------------------------------------------------
/sample/src/main/kotlin/com/emarsys/sample/pref/Prefs.kt:
--------------------------------------------------------------------------------
1 | package com.emarsys.sample.pref
2 |
3 | import com.chibatching.kotpref.KotprefModel
4 |
5 | object Prefs : KotprefModel() {
6 | override val commitAllPropertiesByDefault: Boolean = true
7 |
8 | var sdkVersion by stringPref("")
9 | var languageCode by stringPref("")
10 | var clientId by stringPref("")
11 | var applicationCode by stringPref("")
12 | var merchantId by stringPref("")
13 | var contactFieldValue by stringPref("")
14 | var contactFieldId by intPref(0)
15 | var loggedIn by booleanPref(false)
16 | }
17 |
18 | fun Prefs.update(sdkInfo: Map) {
19 | sdkVersion = sdkInfo.getOrDefault("sdkVersion", "")
20 | languageCode = sdkInfo.getOrDefault("languageCode", "")
21 | clientId = sdkInfo.getOrDefault("clientId", "")
22 | applicationCode = sdkInfo.getOrDefault("applicationCode", "")
23 | merchantId = sdkInfo.getOrDefault("merchantId", "")
24 | contactFieldValue = sdkInfo.getOrDefault("contactFieldValue", "")
25 | contactFieldId = sdkInfo.getOrDefault("contactFieldId", "0").toInt()
26 | loggedIn = sdkInfo.getOrDefault("loggedIn", "false").toBoolean()
27 | }
--------------------------------------------------------------------------------
/sample/src/main/kotlin/com/emarsys/sample/ui/Card.kt:
--------------------------------------------------------------------------------
1 | package com.emarsys.sample.ui
2 |
3 | import androidx.compose.foundation.layout.fillMaxWidth
4 | import androidx.compose.foundation.layout.padding
5 | import androidx.compose.ui.Modifier
6 | import androidx.compose.ui.unit.dp
7 |
8 | fun Modifier.cardWithFullWidth(): Modifier {
9 | return this
10 | .fillMaxWidth(1f)
11 | .padding(4.dp)
12 | }
13 |
14 | fun Modifier.cardWithPointEightWidth(): Modifier {
15 | return this
16 | .fillMaxWidth(.8f)
17 | .padding(4.dp)
18 | }
--------------------------------------------------------------------------------
/sample/src/main/kotlin/com/emarsys/sample/ui/Column.kt:
--------------------------------------------------------------------------------
1 | package com.emarsys.sample.ui.style
2 |
3 | import androidx.compose.foundation.layout.fillMaxWidth
4 | import androidx.compose.foundation.layout.padding
5 | import androidx.compose.ui.Modifier
6 | import androidx.compose.ui.unit.dp
7 |
8 | fun Modifier.columnWithMaxWidth(): Modifier {
9 | return this
10 | .fillMaxWidth(1f)
11 | .padding(2.dp)
12 | }
13 |
14 | fun Modifier.columnWithPointEightWidth(): Modifier {
15 | return this
16 | .fillMaxWidth(.8f)
17 | .padding(2.dp)
18 | }
--------------------------------------------------------------------------------
/sample/src/main/kotlin/com/emarsys/sample/ui/Row.kt:
--------------------------------------------------------------------------------
1 | package com.emarsys.sample.ui.style
2 |
3 | import androidx.compose.foundation.layout.fillMaxWidth
4 | import androidx.compose.foundation.layout.padding
5 | import androidx.compose.ui.Modifier
6 | import androidx.compose.ui.unit.dp
7 |
8 | fun Modifier.rowWithMaxWidth(): Modifier {
9 | return this
10 | .fillMaxWidth(1f)
11 | .padding(2.dp)
12 | }
13 |
14 | fun Modifier.rowWithPointEightWidth(): Modifier {
15 | return this
16 | .fillMaxWidth(.8f)
17 | }
--------------------------------------------------------------------------------
/sample/src/main/kotlin/com/emarsys/sample/ui/component/button/StyledTextButton.kt:
--------------------------------------------------------------------------------
1 | package com.emarsys.sample.ui.component.button
2 |
3 | import androidx.compose.foundation.layout.PaddingValues
4 | import androidx.compose.foundation.shape.RoundedCornerShape
5 | import androidx.compose.material.Text
6 | import androidx.compose.material.TextButton
7 | import androidx.compose.runtime.Composable
8 | import androidx.compose.ui.text.intl.Locale
9 | import androidx.compose.ui.text.toUpperCase
10 | import androidx.compose.ui.unit.dp
11 |
12 | @Composable
13 | fun StyledTextButton(buttonText: String, onClick: () -> Unit) {
14 | TextButton(
15 | contentPadding = PaddingValues(horizontal = 6.dp, vertical = 2.dp),
16 | shape = RoundedCornerShape(30),
17 | onClick = onClick
18 | ) {
19 | Text(text = buttonText.toUpperCase(Locale.current))
20 | }
21 | }
--------------------------------------------------------------------------------
/sample/src/main/kotlin/com/emarsys/sample/ui/component/dialog/ErrorDialog.kt:
--------------------------------------------------------------------------------
1 | package com.emarsys.sample.ui.component.toast
2 |
3 | import androidx.compose.material.AlertDialog
4 | import androidx.compose.material.Button
5 | import androidx.compose.material.Text
6 | import androidx.compose.runtime.Composable
7 | import androidx.compose.runtime.MutableState
8 |
9 | @Composable
10 | fun ErrorDialog(message: String, isVisible: MutableState) {
11 | AlertDialog(
12 | onDismissRequest = { isVisible.value = false },
13 | title = { Text("ERROR") },
14 | text = { Text(text = message) },
15 | confirmButton = {
16 | Button(onClick = { isVisible.value = false }) {
17 | Text(text = "OK")
18 | }
19 | }
20 | )
21 | }
--------------------------------------------------------------------------------
/sample/src/main/kotlin/com/emarsys/sample/ui/component/divider/BlueLine.kt:
--------------------------------------------------------------------------------
1 | package com.emarsys.sample.ui.component.divider
2 |
3 | import androidx.compose.foundation.layout.fillMaxWidth
4 | import androidx.compose.foundation.layout.padding
5 | import androidx.compose.material.Divider
6 | import androidx.compose.runtime.Composable
7 | import androidx.compose.ui.Modifier
8 | import androidx.compose.ui.unit.dp
9 | import com.emarsys.sample.ui.theme.Blue100
10 |
11 | @Composable
12 | fun DividerWithBackgroundColor() {
13 | Divider(
14 | Modifier
15 | .padding(4.dp)
16 | .fillMaxWidth(1f),
17 | color = Blue100
18 | )
19 | }
--------------------------------------------------------------------------------
/sample/src/main/kotlin/com/emarsys/sample/ui/component/divider/GreyLine.kt:
--------------------------------------------------------------------------------
1 | package com.emarsys.sample.ui.component.divider
2 |
3 | import androidx.compose.foundation.layout.fillMaxWidth
4 | import androidx.compose.foundation.layout.padding
5 | import androidx.compose.material.Divider
6 | import androidx.compose.runtime.Composable
7 | import androidx.compose.ui.Modifier
8 | import androidx.compose.ui.graphics.Color
9 | import androidx.compose.ui.unit.dp
10 |
11 | @Composable
12 | fun GreyLine() {
13 | Divider(
14 | Modifier
15 | .padding(top = 10.dp, bottom = 10.dp)
16 | .fillMaxWidth(.8f),
17 | color = Color.Gray.copy(0.6f)
18 | )
19 | }
--------------------------------------------------------------------------------
/sample/src/main/kotlin/com/emarsys/sample/ui/component/row/RowWithCenteredContent.kt:
--------------------------------------------------------------------------------
1 | package com.emarsys.sample.ui.component.row
2 |
3 | import androidx.compose.foundation.layout.Arrangement
4 | import androidx.compose.foundation.layout.Row
5 | import androidx.compose.runtime.Composable
6 | import androidx.compose.ui.Modifier
7 | import com.emarsys.sample.ui.style.rowWithMaxWidth
8 |
9 | @Composable
10 | fun RowWithCenteredContent(content: @Composable () -> Unit) {
11 | Row(
12 | horizontalArrangement = Arrangement.Center,
13 | modifier = Modifier.rowWithMaxWidth()
14 | ) {
15 | content()
16 | }
17 | }
--------------------------------------------------------------------------------
/sample/src/main/kotlin/com/emarsys/sample/ui/component/row/RowWithEvenlySpacedContent.kt:
--------------------------------------------------------------------------------
1 | package com.emarsys.sample.ui.component.row
2 |
3 | import androidx.compose.foundation.layout.Arrangement
4 | import androidx.compose.foundation.layout.Row
5 | import androidx.compose.foundation.layout.padding
6 | import androidx.compose.runtime.Composable
7 | import androidx.compose.ui.Alignment
8 | import androidx.compose.ui.Modifier
9 | import androidx.compose.ui.unit.dp
10 | import com.emarsys.sample.ui.style.rowWithMaxWidth
11 |
12 | @Composable
13 | fun RowWithEvenlySpacedContent(content: @Composable () -> Unit) {
14 | Row(
15 | modifier = Modifier
16 | .rowWithMaxWidth()
17 | .padding(bottom = 4.dp),
18 | horizontalArrangement = Arrangement.SpaceEvenly,
19 | verticalAlignment = Alignment.CenterVertically
20 | ) {
21 | content()
22 | }
23 | }
--------------------------------------------------------------------------------
/sample/src/main/kotlin/com/emarsys/sample/ui/component/screen/DetailScreen.kt:
--------------------------------------------------------------------------------
1 | package com.emarsys.sample.ui.component.screen
2 |
3 | import android.content.Context
4 | import androidx.compose.foundation.layout.PaddingValues
5 | import androidx.compose.runtime.Composable
6 | import androidx.compose.ui.ExperimentalComposeUiApi
7 | import coil.annotation.ExperimentalCoilApi
8 |
9 | abstract class DetailScreen {
10 |
11 | abstract val context: Context
12 |
13 | @ExperimentalCoilApi
14 | @ExperimentalComposeUiApi
15 | @Composable
16 | abstract fun Detail(paddingValues: PaddingValues)
17 | }
--------------------------------------------------------------------------------
/sample/src/main/kotlin/com/emarsys/sample/ui/component/text/TextInFullWidthLine.kt:
--------------------------------------------------------------------------------
1 | package com.emarsys.sample.ui.component.text
2 |
3 | import androidx.compose.foundation.layout.Row
4 | import androidx.compose.foundation.layout.padding
5 | import androidx.compose.material.Text
6 | import androidx.compose.runtime.Composable
7 | import androidx.compose.ui.Modifier
8 | import androidx.compose.ui.unit.dp
9 | import com.emarsys.sample.ui.style.rowWithMaxWidth
10 |
11 | @Composable
12 | fun TextWithFullWidthLine(text: String) {
13 | Row(
14 | modifier = Modifier.rowWithMaxWidth().padding(start = 8.dp)
15 | ) {
16 | Text(text = text)
17 | }
18 | }
--------------------------------------------------------------------------------
/sample/src/main/kotlin/com/emarsys/sample/ui/component/text/TitleText.kt:
--------------------------------------------------------------------------------
1 | package com.emarsys.sample.ui.component.text
2 |
3 | import androidx.compose.foundation.layout.Arrangement
4 | import androidx.compose.foundation.layout.Row
5 | import androidx.compose.foundation.layout.fillMaxWidth
6 | import androidx.compose.foundation.layout.padding
7 | import androidx.compose.material.Text
8 | import androidx.compose.runtime.Composable
9 | import androidx.compose.ui.Modifier
10 | import androidx.compose.ui.text.TextStyle
11 | import androidx.compose.ui.text.font.FontWeight
12 | import androidx.compose.ui.unit.dp
13 | import androidx.compose.ui.unit.sp
14 |
15 | @Composable
16 | fun TitleText(titleText : String) {
17 | Row(
18 | modifier = Modifier.fillMaxWidth(1f),
19 | horizontalArrangement = Arrangement.Center
20 | ) {
21 | Text(
22 | modifier = Modifier
23 | .padding(top = 6.dp, bottom = 10.dp),
24 | style = TextStyle(fontSize = 20.sp, fontWeight = FontWeight.Bold),
25 | text = titleText,
26 | )
27 | }
28 | }
--------------------------------------------------------------------------------
/sample/src/main/kotlin/com/emarsys/sample/ui/component/toast/CustomTextToast.kt:
--------------------------------------------------------------------------------
1 | package com.emarsys.sample.ui.component.toast
2 |
3 | import android.content.Context
4 | import android.widget.Toast
5 |
6 | fun customTextToast(context: Context, message: String) {
7 | Toast.makeText(context, message, Toast.LENGTH_LONG).show()
8 | }
--------------------------------------------------------------------------------
/sample/src/main/kotlin/com/emarsys/sample/ui/theme/Color.kt:
--------------------------------------------------------------------------------
1 | package com.emarsys.sample.ui.theme
2 |
3 | import androidx.compose.ui.graphics.Color
4 |
5 | val Black500 = Color(0xFF212121)
6 | val Black400 = Color(0xFF757575)
7 | val Black200 = Color(0xFFBDBDBD)
8 | val Black100 = Color(0xFFefefef)
9 | val Blue100 = Color(0xFFF7F7FA)
10 | val Blue300 = Color(0xFF1C76DA)
11 | val Blue400 = Color(0xFF1976D2)
12 | val Green200 = Color(0xFFC8E6C9)
13 | val Green300 = Color(0xFF2C8667)
14 |
15 |
16 |
--------------------------------------------------------------------------------
/sample/src/main/kotlin/com/emarsys/sample/ui/theme/Shape.kt:
--------------------------------------------------------------------------------
1 | package com.emarsys.sample.ui.theme
2 |
3 | import androidx.compose.foundation.shape.RoundedCornerShape
4 | import androidx.compose.material.Shapes
5 | import androidx.compose.ui.unit.dp
6 |
7 | val Shapes = Shapes(
8 | small = RoundedCornerShape(4.dp),
9 | medium = RoundedCornerShape(4.dp),
10 | large = RoundedCornerShape(0.dp)
11 | )
--------------------------------------------------------------------------------
/sample/src/main/kotlin/com/emarsys/sample/ui/theme/Type.kt:
--------------------------------------------------------------------------------
1 | package com.emarsys.sample.ui.theme
2 |
3 | import androidx.compose.material.Typography
4 | import androidx.compose.ui.text.TextStyle
5 | import androidx.compose.ui.text.font.FontFamily
6 | import androidx.compose.ui.text.font.FontStyle
7 | import androidx.compose.ui.text.font.FontWeight
8 | import androidx.compose.ui.unit.sp
9 |
10 | val Typography = Typography(
11 | body1 = TextStyle(
12 | fontFamily = FontFamily.Default,
13 | fontWeight = FontWeight.Normal,
14 | fontSize = 16.sp
15 | ),
16 | button = TextStyle(
17 | fontFamily = FontFamily.Default,
18 | fontWeight = FontWeight.W500,
19 | fontSize = 12.sp
20 | ),
21 | caption = TextStyle(
22 | fontFamily = FontFamily.Default,
23 | fontStyle = FontStyle.Italic,
24 | fontWeight = FontWeight.Normal,
25 | fontSize = 16.sp
26 | )
27 | )
--------------------------------------------------------------------------------
/sample/src/main/res/drawable/ic_settings.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/sample/src/main/res/drawable/item_selected.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
6 |
7 |
9 |
--------------------------------------------------------------------------------
/sample/src/main/res/drawable/notification_icon.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
7 |
--------------------------------------------------------------------------------
/sample/src/main/res/drawable/placeholder.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emartech/android-emarsys-sdk/b69741719d0ba7e462a1faab64c49b750906a1fd/sample/src/main/res/drawable/placeholder.png
--------------------------------------------------------------------------------
/sample/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emartech/android-emarsys-sdk/b69741719d0ba7e462a1faab64c49b750906a1fd/sample/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/sample/src/main/res/mipmap-hdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emartech/android-emarsys-sdk/b69741719d0ba7e462a1faab64c49b750906a1fd/sample/src/main/res/mipmap-hdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/sample/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emartech/android-emarsys-sdk/b69741719d0ba7e462a1faab64c49b750906a1fd/sample/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/sample/src/main/res/mipmap-mdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emartech/android-emarsys-sdk/b69741719d0ba7e462a1faab64c49b750906a1fd/sample/src/main/res/mipmap-mdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/sample/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emartech/android-emarsys-sdk/b69741719d0ba7e462a1faab64c49b750906a1fd/sample/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/sample/src/main/res/mipmap-xhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emartech/android-emarsys-sdk/b69741719d0ba7e462a1faab64c49b750906a1fd/sample/src/main/res/mipmap-xhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/sample/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emartech/android-emarsys-sdk/b69741719d0ba7e462a1faab64c49b750906a1fd/sample/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/sample/src/main/res/mipmap-xxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emartech/android-emarsys-sdk/b69741719d0ba7e462a1faab64c49b750906a1fd/sample/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/sample/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emartech/android-emarsys-sdk/b69741719d0ba7e462a1faab64c49b750906a1fd/sample/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/sample/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emartech/android-emarsys-sdk/b69741719d0ba7e462a1faab64c49b750906a1fd/sample/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/sample/src/main/res/values-night/themes.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
16 |
--------------------------------------------------------------------------------
/sample/src/main/res/values-v29/themes.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
18 |
--------------------------------------------------------------------------------
/sample/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #2196F3
4 | #1976D2
5 | #BBDEFB
6 | #4CAF50
7 | #212121
8 | #757575
9 | #FFFFFF
10 | #BDBDBD
11 | #efefef
12 |
13 |
--------------------------------------------------------------------------------
/sample/src/main/res/values/colors_green_yellow.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #4CAF50
4 | #388E3C
5 | #C8E6C9
6 | #FFEB3B
7 | #212121
8 | #757575
9 | #FFFFFF
10 | #BDBDBD
11 |
12 |
--------------------------------------------------------------------------------
/sample/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/testUtils/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/testUtils/src/main/java/com/emarsys/testUtil/ApplicationTestUtils.kt:
--------------------------------------------------------------------------------
1 | package com.emarsys.testUtil
2 |
3 | import android.app.Application
4 | import android.content.Context
5 | import android.content.pm.ApplicationInfo
6 | import io.mockk.every
7 | import io.mockk.spyk
8 |
9 | object ApplicationTestUtils {
10 |
11 | @JvmStatic
12 | val applicationDebug: Application
13 | get() = getApplication { flags = ApplicationInfo.FLAG_DEBUGGABLE }
14 |
15 | @JvmStatic
16 | val applicationRelease: Application
17 | get() = getApplication { flags = 0 }
18 |
19 | private fun getApplication(init: ApplicationInfo.() -> Unit) =
20 | (spyk(InstrumentationRegistry.getTargetContext().applicationContext) as Application).also {
21 | every { it.applicationInfo } returns ApplicationInfo().apply(init)
22 | }
23 |
24 | }
25 |
--------------------------------------------------------------------------------
/testUtils/src/main/java/com/emarsys/testUtil/CollectionTestUtils.kt:
--------------------------------------------------------------------------------
1 | package com.emarsys.testUtil
2 |
3 | import java.util.*
4 |
5 | object CollectionTestUtils {
6 |
7 | @JvmStatic
8 | fun numberOfElementsIn(list: List, type: Class<*>): Int {
9 | return list
10 | .filterNotNull()
11 | .filter { it::class.java == type }
12 | .count()
13 | }
14 |
15 | @JvmStatic
16 | fun numberOfElementsIn(array: Array, type: Class<*>): Int {
17 | var count = 0
18 |
19 | for (o in array) {
20 | if (o.javaClass == type) {
21 | count++
22 | }
23 | }
24 |
25 | return count
26 | }
27 |
28 | @JvmStatic
29 | fun getElementByType(list: List<*>, type: Class): T? {
30 | for (o in list) {
31 | if (type.isInstance(o)) {
32 | return type.cast(o)
33 | }
34 | }
35 | throw NoSuchElementException("Cannot find element of class $type in $list")
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/testUtils/src/main/java/com/emarsys/testUtil/DatabaseTestUtils.kt:
--------------------------------------------------------------------------------
1 | package com.emarsys.testUtil
2 |
3 | import android.database.sqlite.SQLiteDatabase
4 |
5 | object DatabaseTestUtils {
6 |
7 | @JvmStatic
8 | fun deleteCoreDatabase(): Boolean {
9 | return InstrumentationRegistry.getTargetContext().deleteDatabase("EmarsysCore.db")
10 | }
11 |
12 | @JvmStatic
13 | fun dropAllTables(db: SQLiteDatabase) {
14 | db.rawQuery("SELECT 'DROP TABLE ' || name || ';' FROM sqlite_master WHERE type='table';", null).use {
15 | it.moveToFirst()
16 | while (!it.isAfterLast) {
17 | db.execSQL(it.getString(0))
18 | it.moveToNext()
19 | }
20 | }
21 | }
22 | }
--------------------------------------------------------------------------------
/testUtils/src/main/java/com/emarsys/testUtil/ExtensionTestUtils.kt:
--------------------------------------------------------------------------------
1 | package com.emarsys.testUtil
2 |
3 | import android.os.Handler
4 | import android.os.Looper
5 | import java.util.concurrent.CountDownLatch
6 |
7 | object ExtensionTestUtils {
8 | inline fun Any.tryCast(block: T.() -> Unit) {
9 | if (this is T) {
10 | block()
11 | } else {
12 | throw IllegalArgumentException("Casted value is not the type of ${T::class.java.name}")
13 | }
14 | }
15 |
16 | fun Any.runOnMain(logic: () -> T): T {
17 | val uiHandler = Handler(Looper.getMainLooper())
18 | return if (Thread.currentThread() != uiHandler.looper.thread) {
19 | var result: T? = null
20 | val latch = CountDownLatch(1)
21 | uiHandler.post {
22 | result = logic()
23 | latch.countDown()
24 | }
25 | latch.await()
26 | result!!
27 | } else {
28 | logic()
29 | }
30 | }
31 | }
--------------------------------------------------------------------------------
/testUtils/src/main/java/com/emarsys/testUtil/FeatureTestUtils.kt:
--------------------------------------------------------------------------------
1 | package com.emarsys.testUtil
2 |
3 | object FeatureTestUtils {
4 |
5 | @JvmStatic
6 | fun resetFeatures() {
7 | ReflectionTestUtils.invokeStaticMethod(Class.forName("com.emarsys.core.feature.FeatureRegistry"), "reset")
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/testUtils/src/main/java/com/emarsys/testUtil/FileTestUtils.kt:
--------------------------------------------------------------------------------
1 | package com.emarsys.testUtil
2 |
3 | import java.io.*
4 |
5 | object FileTestUtils {
6 | fun writeToFile(input: String, filePath: String): Boolean {
7 | var success: Boolean
8 | try {
9 | val resultFile = File(filePath)
10 | success = resultFile.createNewFile()
11 | if (success) {
12 | val fos = FileOutputStream(resultFile, false)
13 | val outputStreamWriter = OutputStreamWriter(fos)
14 | outputStreamWriter.write(input)
15 | outputStreamWriter.close()
16 | }
17 | } catch (ignored: IOException) {
18 | success = false
19 | }
20 | return success
21 | }
22 | }
23 |
24 | fun File.copyInputStreamToFile(inputStream: InputStream) {
25 | this.outputStream().use { fileOut ->
26 | inputStream.copyTo(fileOut)
27 | }
28 | }
--------------------------------------------------------------------------------
/testUtils/src/main/java/com/emarsys/testUtil/InstrumentationRegistry.kt:
--------------------------------------------------------------------------------
1 | package com.emarsys.testUtil
2 |
3 | import android.content.Context
4 | import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation
5 |
6 | class InstrumentationRegistry {
7 | companion object {
8 | @JvmStatic
9 | fun getTargetContext(): Context = getInstrumentation().targetContext
10 |
11 | }
12 | }
--------------------------------------------------------------------------------
/testUtils/src/main/java/com/emarsys/testUtil/RandomTestUtils.kt:
--------------------------------------------------------------------------------
1 | package com.emarsys.testUtil
2 |
3 | import java.util.*
4 |
5 | object RandomTestUtils {
6 |
7 | @JvmStatic
8 | fun randomBool(): Boolean {
9 | return Random().nextBoolean()
10 | }
11 |
12 | @JvmStatic
13 | fun randomInt(): Int {
14 | return Random().nextInt()
15 | }
16 |
17 | @JvmStatic
18 | fun randomLong(): Long {
19 | return Random().nextLong()
20 | }
21 |
22 | @JvmStatic
23 | fun randomString(): String {
24 | return UUID.randomUUID().toString()
25 | }
26 |
27 | @JvmStatic
28 | fun randomNumberString(): String {
29 | return java.lang.Long.toString(randomLong())
30 | }
31 |
32 | @JvmStatic
33 | fun randomMap(): Map = mapOf(
34 | randomString() to randomInt(),
35 | randomString() to randomBool(),
36 | randomString() to randomString()
37 | )
38 | }
39 |
--------------------------------------------------------------------------------
/testUtils/src/main/java/com/emarsys/testUtil/RetryUtils.kt:
--------------------------------------------------------------------------------
1 | package com.emarsys.testUtil
2 |
3 | import com.emarsys.testUtil.rules.RetryRule
4 |
5 | object RetryUtils {
6 | @JvmStatic
7 | val retryRule
8 | get() = RetryRule(3)
9 | }
--------------------------------------------------------------------------------
/testUtils/src/main/java/com/emarsys/testUtil/SharedPrefsUtils.kt:
--------------------------------------------------------------------------------
1 | package com.emarsys.testUtil
2 |
3 | import android.content.Context
4 |
5 | object SharedPrefsUtils {
6 |
7 | @JvmStatic
8 | @JvmOverloads
9 | fun clearSharedPrefs(namespace: String, mode: Int = Context.MODE_PRIVATE) =
10 | InstrumentationRegistry
11 | .getTargetContext()
12 | .getSharedPreferences(namespace, mode)
13 | .edit()
14 | .clear()
15 | .commit()
16 |
17 | }
--------------------------------------------------------------------------------
/testUtils/src/main/java/com/emarsys/testUtil/TestUrls.kt:
--------------------------------------------------------------------------------
1 | package com.emarsys.testUtil
2 |
3 | object TestUrls {
4 | private const val DENNA_CUSTOM_BASE = "https://denna.gservice.emarsys.net/customResponseCode/"
5 |
6 | const val DENNA_ECHO = "https://denna.gservice.emarsys.net/echo"
7 | const val LARGE_IMAGE =
8 | "https://mobile-sdk-config-staging.gservice.emarsys.com/testing/Emarsys.png"
9 |
10 | @JvmStatic
11 | fun customResponse(statusCode: Int) = "$DENNA_CUSTOM_BASE$statusCode"
12 | }
--------------------------------------------------------------------------------
/testUtils/src/main/java/com/emarsys/testUtil/TimeoutUtils.kt:
--------------------------------------------------------------------------------
1 | package com.emarsys.testUtil
2 |
3 | import androidx.test.rule.DisableOnAndroidDebug
4 |
5 | import org.junit.rules.Timeout
6 |
7 | object TimeoutUtils {
8 |
9 | @JvmStatic
10 | val timeoutRule
11 | get () = DisableOnAndroidDebug(Timeout.seconds(60))
12 |
13 | @JvmStatic
14 | val longTimeoutRule
15 | get () = DisableOnAndroidDebug(Timeout.seconds(320))
16 |
17 | }
18 |
--------------------------------------------------------------------------------
/testUtils/src/main/java/com/emarsys/testUtil/fake/FakeActivity.kt:
--------------------------------------------------------------------------------
1 | package com.emarsys.testUtil.fake
2 |
3 | import androidx.appcompat.app.AppCompatActivity
4 |
5 | class FakeActivity : AppCompatActivity()
6 |
--------------------------------------------------------------------------------
/testUtils/src/main/java/com/emarsys/testUtil/mockito/MockitoTestUtils.kt:
--------------------------------------------------------------------------------
1 | package com.emarsys.testUtil.mockito
2 |
3 | import org.mockito.Mockito
4 | import org.mockito.Mockito.`when`
5 | import org.mockito.stubbing.OngoingStubbing
6 |
7 | fun whenever(mock: T): OngoingStubbing = `when`(mock)
8 |
9 | fun anyNotNull(): T {
10 | Mockito.any()
11 | return uninitialized()
12 | }
13 |
14 | @Suppress("UNCHECKED_CAST")
15 | private fun uninitialized(): T {
16 | return null as T
17 | }
18 |
19 |
--------------------------------------------------------------------------------
/testUtils/src/main/java/com/emarsys/testUtil/rules/ConnectionRule.kt:
--------------------------------------------------------------------------------
1 | package com.emarsys.testUtil.rules
2 |
3 | import android.app.Application
4 | import com.emarsys.testUtil.ConnectionTestUtils
5 | import org.junit.rules.TestRule
6 | import org.junit.runner.Description
7 | import org.junit.runners.model.Statement
8 |
9 | class ConnectionRule(private val application: Application) : TestRule {
10 |
11 | override fun apply(base: Statement?, description: Description?): Statement {
12 | return object : Statement() {
13 | override fun evaluate() {
14 | ConnectionTestUtils.checkConnection(application)
15 | base?.evaluate()
16 | return
17 | }
18 | }
19 | }
20 |
21 |
22 | }
--------------------------------------------------------------------------------
/testUtils/src/main/java/com/emarsys/testUtil/rules/DuplicatedThreadRule.kt:
--------------------------------------------------------------------------------
1 | package com.emarsys.testUtil.rules
2 |
3 | import org.junit.rules.TestRule
4 | import org.junit.runner.Description
5 | import org.junit.runners.model.Statement
6 |
7 | class DuplicatedThreadRule(private val threadName: String) : TestRule {
8 |
9 | override fun apply(base: Statement?, description: Description?): Statement {
10 | return object : Statement() {
11 | override fun evaluate() {
12 | val threads = Thread.getAllStackTraces().keys.map { it.name }
13 | .filter { it.startsWith(threadName) }
14 | if (threads.size > 1) {
15 | throw Throwable("TEST: $threadName thread is duplicated")
16 | }
17 | base?.evaluate()
18 | return
19 | }
20 | }
21 | }
22 |
23 |
24 | }
--------------------------------------------------------------------------------