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