From ee0760cd2ba743c8a5d365f325de2a08523ca75c Mon Sep 17 00:00:00 2001 From: Brian Smith Date: Wed, 19 Oct 2022 16:34:49 -0400 Subject: [PATCH] [User Model] notification_types and model event rework * Update `ISingletonModelStore.get()` function to be `ISingletonModelStore.model` property * Rework the model store change event system to always fire, using tags to differentiate why changes occurred. * Add `SubscriptionModel.status` to capture the subscription status, for when retrieving push token fails for some reason. * Drive event callback when app killed during request notification permission activity. --- .../onesignal/core/internal/OneSignalImp.kt | 19 +++--- .../backend/ISubscriptionBackendService.kt | 6 +- .../impl/SubscriptionBackendService.kt | 4 +- .../backend/impl/UserBackendService.kt | 3 +- .../core/internal/common/AndroidUtils.kt | 30 +++++++++ .../core/internal/device/IDeviceService.kt | 7 ++ .../internal/device/impl/DeviceService.kt | 27 ++++++++ .../core/internal/http/impl/HttpClient.kt | 2 +- .../influence/impl/InfluenceDataRepository.kt | 14 ++-- .../internal/language/impl/LanguageContext.kt | 4 +- .../listeners/ConfigModelStoreListener.kt | 26 +++++--- .../listeners/IdentityModelStoreListener.kt | 4 +- .../internal/listeners/ModelStoreListener.kt | 22 +++++-- .../listeners/PropertiesModelStoreListener.kt | 6 +- .../internal/listeners/SessionListener.kt | 4 +- .../listeners/SingletonModelStoreListener.kt | 16 ++++- .../SubscriptionModelStoreListener.kt | 6 +- .../internal/modeling/IModelChangedHandler.kt | 3 +- .../core/internal/modeling/IModelStore.kt | 39 ++++++++--- .../modeling/IModelStoreChangeHandler.kt | 15 ++--- .../internal/modeling/ISingletonModelStore.kt | 10 ++- .../ISingletonModelStoreChangeHandler.kt | 12 ++-- .../core/internal/modeling/MapModel.kt | 4 +- .../onesignal/core/internal/modeling/Model.kt | 26 ++++---- .../core/internal/modeling/ModelStore.kt | 46 +++++++------ .../internal/modeling/SingletonModelStore.kt | 46 ++++++------- .../core/internal/models/SubscriptionModel.kt | 31 +++++++++ .../operations/CreateSubscriptionOperation.kt | 10 ++- .../operations/UpdateSubscriptionOperation.kt | 10 ++- .../impl/SubscriptionOperationExecutor.kt | 14 ++-- .../operations/impl/UserOperationExecutor.kt | 18 +++--- .../outcomes/impl/OutcomeEventsController.kt | 4 +- .../preferences/PreferencesService.kt | 18 ++++-- .../internal/purchases/TrackAmazonPurchase.kt | 2 +- .../internal/purchases/TrackGooglePurchase.kt | 2 +- .../internal/session/impl/SessionService.kt | 4 +- .../core/internal/user/SubscriptionManager.kt | 39 +++++------ .../core/internal/user/UserManager.kt | 4 +- .../com/onesignal/iam/internal/IAMManager.kt | 40 ++++++------ .../internal/display/impl/InAppDisplayer.kt | 4 +- .../triggers/impl/TriggerController.kt | 8 ++- .../location/internal/LocationManager.kt | 23 ++++--- .../internal/capture/impl/LocationCapturer.kt | 2 +- .../LocationPermissionController.kt | 20 +++++- .../internal/NotificationsManager.kt | 51 ++++++--------- .../data/impl/NotificationQueryHelper.kt | 2 +- .../impl/NotificationGenerationProcessor.kt | 2 +- .../listeners/ConfigModelStoreListener.kt | 12 +++- .../listeners/NotificationListener.kt | 2 +- .../internal/listeners/PushTokenListener.kt | 9 +-- .../open/impl/NotificationOpenedProcessor.kt | 2 +- .../INotificationPermissionController.kt | 8 ++- .../impl/NotificationPermissionController.kt | 18 +++++- .../internal/pushtoken/IPushTokenManager.kt | 5 +- .../internal/pushtoken/PushTokenManager.kt | 64 ++++++++++++------- .../impl/ReceiveReceiptWorkManager.kt | 4 +- .../impl/GooglePlayServicesUpgradePrompt.kt | 2 +- .../impl/PushRegistratorAbstractGoogle.kt | 9 ++- .../registration/impl/PushRegistratorFCM.kt | 2 +- .../impl/NotificationSummaryManager.kt | 2 +- 60 files changed, 532 insertions(+), 316 deletions(-) diff --git a/OneSignalSDK/onesignal/src/main/java/com/onesignal/core/internal/OneSignalImp.kt b/OneSignalSDK/onesignal/src/main/java/com/onesignal/core/internal/OneSignalImp.kt index 3b4e0332a3..ecabdca169 100644 --- a/OneSignalSDK/onesignal/src/main/java/com/onesignal/core/internal/OneSignalImp.kt +++ b/OneSignalSDK/onesignal/src/main/java/com/onesignal/core/internal/OneSignalImp.kt @@ -11,6 +11,7 @@ import com.onesignal.core.internal.common.IDManager import com.onesignal.core.internal.common.OneSignalUtils import com.onesignal.core.internal.debug.DebugManager import com.onesignal.core.internal.logging.Logging +import com.onesignal.core.internal.modeling.ModelChangeTags import com.onesignal.core.internal.models.ConfigModel import com.onesignal.core.internal.models.ConfigModelStore import com.onesignal.core.internal.models.IdentityModel @@ -69,7 +70,7 @@ internal class OneSignalImp : IOneSignal, IServiceProvider { // check again now that we are synchronized. if (!_hasCreatedBackendUser) { // Create the new user in the backend as an operation - _operationRepo!!.enqueue(CreateUserOperation(_configModel!!.appId, _identityModelStore!!.get().onesignalId, _identityModelStore!!.get().externalId)) + _operationRepo!!.enqueue(CreateUserOperation(_configModel!!.appId, _identityModelStore!!.model.onesignalId, _identityModelStore!!.model.externalId)) _hasCreatedBackendUser = true } } @@ -122,8 +123,8 @@ internal class OneSignalImp : IOneSignal, IServiceProvider { (_services.getService() as ApplicationService).start(context) // get the current config model, if there is one - _configModel = _services.getService().get() - _sessionModel = _services.getService().get() + _configModel = _services.getService().model + _sessionModel = _services.getService().model // if the app id was specified as input, update the config model with it if (appId != null) { @@ -154,8 +155,8 @@ internal class OneSignalImp : IOneSignal, IServiceProvider { _startupService = _services.getService() _startupService!!.bootstrap() - if (_identityModelStore!!.get().hasProperty(IdentityConstants.ONESIGNAL_ID)) { - Logging.debug("initWithContext: using cached user ${_identityModelStore!!.get().onesignalId}") + if (_identityModelStore!!.model.hasProperty(IdentityConstants.ONESIGNAL_ID)) { + Logging.debug("initWithContext: using cached user ${_identityModelStore!!.model.onesignalId}") _hasCreatedBackendUser = true } else { createAndSwitchToNewUser() @@ -185,7 +186,7 @@ internal class OneSignalImp : IOneSignal, IServiceProvider { // TODO: Set JWT Token for all future requests. // Create the new user in the backend as an operation - _operationRepo!!.execute(CreateUserOperation(_configModel!!.appId, _identityModelStore!!.get().onesignalId, _identityModelStore!!.get().externalId)) + _operationRepo!!.execute(CreateUserOperation(_configModel!!.appId, _identityModelStore!!.model.onesignalId, _identityModelStore!!.model.externalId)) } override fun logout() { @@ -238,10 +239,10 @@ internal class OneSignalImp : IOneSignal, IServiceProvider { subscriptions.add(newPushSubscription) // The next 4 lines makes this user the effective user locally. We clear the subscriptions - // first without firing the event because we don't want to drive deleting the cleared subscriptions + // first as an internal change because we don't want to drive deleting the cleared subscriptions // on the backend. Once cleared we can then setup the new identity/properties model, and add - // the new user's subscriptions. - _subscriptionModelStore!!.clear(fireEvent = false) + // the new user's subscriptions as a "normal" change, which will drive changes to the backend. + _subscriptionModelStore!!.clear(ModelChangeTags.NO_PROPOGATE) _identityModelStore!!.replace(identityModel) _propertiesModelStore!!.replace(propertiesModel) _subscriptionModelStore!!.replaceAll(subscriptions) diff --git a/OneSignalSDK/onesignal/src/main/java/com/onesignal/core/internal/backend/ISubscriptionBackendService.kt b/OneSignalSDK/onesignal/src/main/java/com/onesignal/core/internal/backend/ISubscriptionBackendService.kt index d8de59a90f..8fed5fac49 100644 --- a/OneSignalSDK/onesignal/src/main/java/com/onesignal/core/internal/backend/ISubscriptionBackendService.kt +++ b/OneSignalSDK/onesignal/src/main/java/com/onesignal/core/internal/backend/ISubscriptionBackendService.kt @@ -13,10 +13,11 @@ internal interface ISubscriptionBackendService { * @param type The type of subscription to create. * @param enabled Whether this subscription is enabled. * @param address The subscription address. + * @param status The subscription status. * * @return The ID of the subscription created. */ - suspend fun createSubscription(appId: String, aliasLabel: String, aliasValue: String, type: SubscriptionObjectType, enabled: Boolean, address: String): String + suspend fun createSubscription(appId: String, aliasLabel: String, aliasValue: String, type: SubscriptionObjectType, enabled: Boolean, address: String, status: Int): String /** * Update an existing subscription with the properties provided. @@ -25,8 +26,9 @@ internal interface ISubscriptionBackendService { * @param subscriptionId The ID of the subscription to update. * @param enabled Whether this subscription is enabled. * @param address The subscription address. + * @param status The subscription status. */ - suspend fun updateSubscription(appId: String, subscriptionId: String, enabled: Boolean, address: String) + suspend fun updateSubscription(appId: String, subscriptionId: String, enabled: Boolean, address: String, status: Int) /** * Delete an existing subscription. diff --git a/OneSignalSDK/onesignal/src/main/java/com/onesignal/core/internal/backend/impl/SubscriptionBackendService.kt b/OneSignalSDK/onesignal/src/main/java/com/onesignal/core/internal/backend/impl/SubscriptionBackendService.kt index f14db4bd38..192ec2281f 100644 --- a/OneSignalSDK/onesignal/src/main/java/com/onesignal/core/internal/backend/impl/SubscriptionBackendService.kt +++ b/OneSignalSDK/onesignal/src/main/java/com/onesignal/core/internal/backend/impl/SubscriptionBackendService.kt @@ -23,7 +23,7 @@ internal class SubscriptionBackendService( private val _http: IHttpClient ) : ISubscriptionBackendService { - override suspend fun createSubscription(appId: String, aliasLabel: String, aliasValue: String, type: SubscriptionObjectType, enabled: Boolean, address: String): String { + override suspend fun createSubscription(appId: String, aliasLabel: String, aliasValue: String, type: SubscriptionObjectType, enabled: Boolean, address: String, status: Int): String { // TODO: To Implement, temporarily using players endpoint when PUSH if (type == SubscriptionObjectType.SMS || type == SubscriptionObjectType.EMAIL) { return UUID.randomUUID().toString() @@ -70,7 +70,7 @@ internal class SubscriptionBackendService( return responseJSON.getString("id") } - override suspend fun updateSubscription(appId: String, subscriptionId: String, enabled: Boolean, address: String) { + override suspend fun updateSubscription(appId: String, subscriptionId: String, enabled: Boolean, address: String, status: Int) { // TODO: To Implement } diff --git a/OneSignalSDK/onesignal/src/main/java/com/onesignal/core/internal/backend/impl/UserBackendService.kt b/OneSignalSDK/onesignal/src/main/java/com/onesignal/core/internal/backend/impl/UserBackendService.kt index 8ab7042dc4..ab605b5582 100644 --- a/OneSignalSDK/onesignal/src/main/java/com/onesignal/core/internal/backend/impl/UserBackendService.kt +++ b/OneSignalSDK/onesignal/src/main/java/com/onesignal/core/internal/backend/impl/UserBackendService.kt @@ -7,6 +7,7 @@ import com.onesignal.core.internal.backend.IdentityConstants import com.onesignal.core.internal.backend.PropertiesDeltasObject import com.onesignal.core.internal.backend.PropertiesObject import com.onesignal.core.internal.backend.SubscriptionObject +import com.onesignal.core.internal.models.SubscriptionModel import org.json.JSONArray import org.json.JSONObject import java.util.* @@ -34,7 +35,7 @@ internal class UserBackendService( // TODO: Temporarily using players endpoint via subscription backend to register the subscription, so we can drive push/IAMs. val subscriptionIDs = mutableListOf() for (subscription in subscriptions) { - val subscriptionId = _subscriptionBackend.createSubscription(appId, "", "", subscription.type, subscription.enabled ?: true, subscription.token ?: "") + val subscriptionId = _subscriptionBackend.createSubscription(appId, "", "", subscription.type, subscription.enabled ?: true, subscription.token ?: "", subscription.notificationTypes ?: SubscriptionModel.STATUS_SUBSCRIBED) subscriptionIDs.add(subscriptionId) } diff --git a/OneSignalSDK/onesignal/src/main/java/com/onesignal/core/internal/common/AndroidUtils.kt b/OneSignalSDK/onesignal/src/main/java/com/onesignal/core/internal/common/AndroidUtils.kt index e0917bb1d1..091b3c6a10 100644 --- a/OneSignalSDK/onesignal/src/main/java/com/onesignal/core/internal/common/AndroidUtils.kt +++ b/OneSignalSDK/onesignal/src/main/java/com/onesignal/core/internal/common/AndroidUtils.kt @@ -12,6 +12,9 @@ import android.os.Bundle import android.os.Looper import android.text.TextUtils import androidx.annotation.Keep +import androidx.core.app.JobIntentService +import androidx.core.app.NotificationManagerCompat +import androidx.legacy.content.WakefulBroadcastReceiver import com.onesignal.core.internal.application.IApplicationService import com.onesignal.core.internal.logging.Logging import java.util.Random @@ -119,6 +122,33 @@ internal object AndroidUtils { return Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1 } + fun hasJobIntentService(): Boolean { + return try { + // noinspection ConstantConditions + JobIntentService::class.java != null + } catch (e: Throwable) { + false + } + } + + fun hasWakefulBroadcastReceiver(): Boolean { + return try { + // noinspection ConstantConditions + WakefulBroadcastReceiver::class.java != null + } catch (e: Throwable) { + false + } + } + + fun hasNotificationManagerCompat(): Boolean { + return try { + // noinspection ConstantConditions + NotificationManagerCompat::class.java != null + } catch (e: Throwable) { + false + } + } + fun openURLInBrowser(appContext: Context, url: String) { openURLInBrowser(appContext, Uri.parse(url.trim { it <= ' ' })) } diff --git a/OneSignalSDK/onesignal/src/main/java/com/onesignal/core/internal/device/IDeviceService.kt b/OneSignalSDK/onesignal/src/main/java/com/onesignal/core/internal/device/IDeviceService.kt index 23afa1b04d..be59adf636 100644 --- a/OneSignalSDK/onesignal/src/main/java/com/onesignal/core/internal/device/IDeviceService.kt +++ b/OneSignalSDK/onesignal/src/main/java/com/onesignal/core/internal/device/IDeviceService.kt @@ -13,4 +13,11 @@ internal interface IDeviceService { val isGMSInstalledAndEnabled: Boolean val hasAllHMSLibrariesForPushKit: Boolean val hasFCMLibrary: Boolean + val androidSupportLibraryStatus: AndroidSupportLibraryStatus + + enum class AndroidSupportLibraryStatus { + MISSING, + OUTDATED, + OK + } } diff --git a/OneSignalSDK/onesignal/src/main/java/com/onesignal/core/internal/device/impl/DeviceService.kt b/OneSignalSDK/onesignal/src/main/java/com/onesignal/core/internal/device/impl/DeviceService.kt index c90022c212..9827a477a4 100644 --- a/OneSignalSDK/onesignal/src/main/java/com/onesignal/core/internal/device/impl/DeviceService.kt +++ b/OneSignalSDK/onesignal/src/main/java/com/onesignal/core/internal/device/impl/DeviceService.kt @@ -1,6 +1,7 @@ package com.onesignal.core.internal.device.impl import android.content.pm.PackageManager +import android.os.Build import com.google.android.gms.common.GoogleApiAvailability import com.google.android.gms.location.LocationListener import com.google.firebase.messaging.FirebaseMessaging @@ -82,6 +83,32 @@ internal class DeviceService(private val _applicationService: IApplicationServic return false } + override val androidSupportLibraryStatus: IDeviceService.AndroidSupportLibraryStatus + get() { + val hasWakefulBroadcastReceiver: Boolean = AndroidUtils.hasWakefulBroadcastReceiver() + val hasNotificationManagerCompat: Boolean = AndroidUtils.hasNotificationManagerCompat() + if (!hasWakefulBroadcastReceiver && !hasNotificationManagerCompat) { + return IDeviceService.AndroidSupportLibraryStatus.MISSING + } + + if (!hasWakefulBroadcastReceiver || !hasNotificationManagerCompat) { + return IDeviceService.AndroidSupportLibraryStatus.OUTDATED + } + + // If running on Android O and targeting O we need version 26.0.0 for + // the new compat NotificationCompat.Builder constructor. + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && + AndroidUtils.getTargetSdkVersion(_applicationService.appContext) >= Build.VERSION_CODES.O + ) { + // Class was added in 26.0.0-beta2 + if (!AndroidUtils.hasJobIntentService()) { + return IDeviceService.AndroidSupportLibraryStatus.OUTDATED + } + } + + return IDeviceService.AndroidSupportLibraryStatus.OK + } + private fun supportsGooglePush(): Boolean { // 1. If app does not have the FCM library it won't support Google push return if (!hasFCMLibrary) false else isGMSInstalledAndEnabled diff --git a/OneSignalSDK/onesignal/src/main/java/com/onesignal/core/internal/http/impl/HttpClient.kt b/OneSignalSDK/onesignal/src/main/java/com/onesignal/core/internal/http/impl/HttpClient.kt index f2886d7f39..8da54c3945 100644 --- a/OneSignalSDK/onesignal/src/main/java/com/onesignal/core/internal/http/impl/HttpClient.kt +++ b/OneSignalSDK/onesignal/src/main/java/com/onesignal/core/internal/http/impl/HttpClient.kt @@ -56,7 +56,7 @@ internal class HttpClient( cacheKey: String? ): HttpResponse { // If privacy consent is required but not yet given, any non-GET request should be blocked. - if (method != null && _configModelStore.get().requiresPrivacyConsent == true && _configModelStore.get().givenPrivacyConsent != true) { + if (method != null && _configModelStore.model.requiresPrivacyConsent == true && _configModelStore.model.givenPrivacyConsent != true) { Logging.warn("$method `$url` was called before the user provided privacy consent. Your application is set to require the user's privacy consent before the OneSignal SDK can be initialized. Please ensure the user has provided consent before calling this method. You can check the latest OneSignal consent status by calling OneSignal.privacyConsent") return HttpResponse(0, null, null) } diff --git a/OneSignalSDK/onesignal/src/main/java/com/onesignal/core/internal/influence/impl/InfluenceDataRepository.kt b/OneSignalSDK/onesignal/src/main/java/com/onesignal/core/internal/influence/impl/InfluenceDataRepository.kt index 7b29b38d85..d794b84d95 100644 --- a/OneSignalSDK/onesignal/src/main/java/com/onesignal/core/internal/influence/impl/InfluenceDataRepository.kt +++ b/OneSignalSDK/onesignal/src/main/java/com/onesignal/core/internal/influence/impl/InfluenceDataRepository.kt @@ -124,23 +124,23 @@ internal class InfluenceDataRepository( } override val notificationLimit: Int - get() = _configModelStore.get().influenceParams.notificationLimit + get() = _configModelStore.model.influenceParams.notificationLimit override val iamLimit: Int - get() = _configModelStore.get().influenceParams.iamLimit + get() = _configModelStore.model.influenceParams.iamLimit override val notificationIndirectAttributionWindow: Int - get() = _configModelStore.get().influenceParams.indirectNotificationAttributionWindow + get() = _configModelStore.model.influenceParams.indirectNotificationAttributionWindow override val iamIndirectAttributionWindow: Int - get() = _configModelStore.get().influenceParams.indirectIAMAttributionWindow + get() = _configModelStore.model.influenceParams.indirectIAMAttributionWindow override val isDirectInfluenceEnabled: Boolean - get() = _configModelStore.get().influenceParams.isDirectEnabled + get() = _configModelStore.model.influenceParams.isDirectEnabled override val isIndirectInfluenceEnabled: Boolean - get() = _configModelStore.get().influenceParams.isIndirectEnabled + get() = _configModelStore.model.influenceParams.isIndirectEnabled override val isUnattributedInfluenceEnabled: Boolean - get() = _configModelStore.get().influenceParams.isUnattributedEnabled + get() = _configModelStore.model.influenceParams.isUnattributedEnabled } diff --git a/OneSignalSDK/onesignal/src/main/java/com/onesignal/core/internal/language/impl/LanguageContext.kt b/OneSignalSDK/onesignal/src/main/java/com/onesignal/core/internal/language/impl/LanguageContext.kt index 14e5d57537..4e8bc2f39e 100644 --- a/OneSignalSDK/onesignal/src/main/java/com/onesignal/core/internal/language/impl/LanguageContext.kt +++ b/OneSignalSDK/onesignal/src/main/java/com/onesignal/core/internal/language/impl/LanguageContext.kt @@ -10,6 +10,6 @@ internal class LanguageContext( private var _deviceLanguageProvider = LanguageProviderDevice() override var language: String - get() = _propertiesModelStore.get().language ?: _deviceLanguageProvider.language - set(value) { _propertiesModelStore.get().language = value } + get() = _propertiesModelStore.model.language ?: _deviceLanguageProvider.language + set(value) { _propertiesModelStore.model.language = value } } diff --git a/OneSignalSDK/onesignal/src/main/java/com/onesignal/core/internal/listeners/ConfigModelStoreListener.kt b/OneSignalSDK/onesignal/src/main/java/com/onesignal/core/internal/listeners/ConfigModelStoreListener.kt index 9eac2f52d8..1174e1e4f3 100644 --- a/OneSignalSDK/onesignal/src/main/java/com/onesignal/core/internal/listeners/ConfigModelStoreListener.kt +++ b/OneSignalSDK/onesignal/src/main/java/com/onesignal/core/internal/listeners/ConfigModelStoreListener.kt @@ -5,6 +5,8 @@ import com.onesignal.core.internal.backend.IParamsBackendService import com.onesignal.core.internal.common.suspendifyOnThread import com.onesignal.core.internal.logging.Logging import com.onesignal.core.internal.modeling.ISingletonModelStoreChangeHandler +import com.onesignal.core.internal.modeling.ModelChangeTags +import com.onesignal.core.internal.modeling.ModelChangedArgs import com.onesignal.core.internal.models.ConfigModel import com.onesignal.core.internal.models.ConfigModelStore import com.onesignal.core.internal.models.InfluenceConfigModel @@ -31,19 +33,24 @@ internal class ConfigModelStoreListener( fetchParams() } - override fun onModelUpdated(model: ConfigModel, path: String, property: String, oldValue: Any?, newValue: Any?) { - if (property != ConfigModel::appId.name) { + override fun onModelUpdated(args: ModelChangedArgs, tag: String) { + if (args.property != ConfigModel::appId.name) { return } fetchParams() } - override fun onModelReplaced(model: ConfigModel) { + override fun onModelReplaced(model: ConfigModel, tag: String) { + if (tag != ModelChangeTags.NORMAL) { + return + } + + fetchParams() } private fun fetchParams() { - val appId = _configModelStore.get().appId + val appId = _configModelStore.model.appId if (appId.isEmpty()) { return @@ -68,10 +75,11 @@ internal class ConfigModelStoreListener( config.googleProjectNumber = params.googleProjectNumber config.clearGroupOnSummaryClick = params.clearGroupOnSummaryClick ?: true config.receiveReceiptEnabled = params.receiveReceiptEnabled ?: false - config.disableGMSMissingPrompt = params.disableGMSMissingPrompt ?: _configModelStore.get().disableGMSMissingPrompt - config.unsubscribeWhenNotificationsDisabled = params.unsubscribeWhenNotificationsDisabled ?: _configModelStore.get().unsubscribeWhenNotificationsDisabled - config.locationShared = params.locationShared ?: _configModelStore.get().locationShared - config.requiresPrivacyConsent = params.requiresUserPrivacyConsent ?: _configModelStore.get().requiresPrivacyConsent + config.disableGMSMissingPrompt = params.disableGMSMissingPrompt ?: _configModelStore.model.disableGMSMissingPrompt + config.unsubscribeWhenNotificationsDisabled = params.unsubscribeWhenNotificationsDisabled ?: _configModelStore.model.unsubscribeWhenNotificationsDisabled + config.locationShared = params.locationShared ?: _configModelStore.model.locationShared + config.requiresPrivacyConsent = params.requiresUserPrivacyConsent ?: _configModelStore.model.requiresPrivacyConsent + config.givenPrivacyConsent = _configModelStore.model.givenPrivacyConsent config.influenceParams.notificationLimit = params.influenceParams.notificationLimit ?: InfluenceConfigModel.DEFAULT_NOTIFICATION_LIMIT config.influenceParams.indirectNotificationAttributionWindow = params.influenceParams.indirectNotificationAttributionWindow ?: InfluenceConfigModel.DEFAULT_INDIRECT_ATTRIBUTION_WINDOW @@ -86,7 +94,7 @@ internal class ConfigModelStoreListener( config.fcmParams.appId = params.fcmParams.appId config.fcmParams.apiKey = params.fcmParams.apiKey - _configModelStore.replace(config) + _configModelStore.replace(config, ModelChangeTags.HYDRATE) success = true } catch (ex: BackendException) { if (ex.statusCode == HttpURLConnection.HTTP_FORBIDDEN) { diff --git a/OneSignalSDK/onesignal/src/main/java/com/onesignal/core/internal/listeners/IdentityModelStoreListener.kt b/OneSignalSDK/onesignal/src/main/java/com/onesignal/core/internal/listeners/IdentityModelStoreListener.kt index 53264000af..ecdeeb2f1a 100644 --- a/OneSignalSDK/onesignal/src/main/java/com/onesignal/core/internal/listeners/IdentityModelStoreListener.kt +++ b/OneSignalSDK/onesignal/src/main/java/com/onesignal/core/internal/listeners/IdentityModelStoreListener.kt @@ -21,9 +21,9 @@ internal class IdentityModelStoreListener( override fun getUpdateOperation(model: IdentityModel, path: String, property: String, oldValue: Any?, newValue: Any?): Operation { return if (newValue != null && newValue is String) { - SetAliasOperation(_configModelStore.get().appId, model.onesignalId, property, newValue) + SetAliasOperation(_configModelStore.model.appId, model.onesignalId, property, newValue) } else { - DeleteAliasOperation(_configModelStore.get().appId, model.onesignalId, property) + DeleteAliasOperation(_configModelStore.model.appId, model.onesignalId, property) } } } diff --git a/OneSignalSDK/onesignal/src/main/java/com/onesignal/core/internal/listeners/ModelStoreListener.kt b/OneSignalSDK/onesignal/src/main/java/com/onesignal/core/internal/listeners/ModelStoreListener.kt index 9620b6529e..7175352dbe 100644 --- a/OneSignalSDK/onesignal/src/main/java/com/onesignal/core/internal/listeners/ModelStoreListener.kt +++ b/OneSignalSDK/onesignal/src/main/java/com/onesignal/core/internal/listeners/ModelStoreListener.kt @@ -3,6 +3,8 @@ package com.onesignal.core.internal.listeners import com.onesignal.core.internal.modeling.IModelStore import com.onesignal.core.internal.modeling.IModelStoreChangeHandler import com.onesignal.core.internal.modeling.Model +import com.onesignal.core.internal.modeling.ModelChangeTags +import com.onesignal.core.internal.modeling.ModelChangedArgs import com.onesignal.core.internal.operations.IOperationRepo import com.onesignal.core.internal.operations.Operation import com.onesignal.core.internal.startup.IBootstrapService @@ -21,21 +23,33 @@ internal abstract class ModelStoreListener( store.unsubscribe(this) } - override fun onAdded(model: TModel) { + override fun onModelAdded(model: TModel, tag: String) { + if (tag != ModelChangeTags.NORMAL) { + return + } + val operation = getAddOperation(model) if (operation != null) { opRepo.enqueue(operation) } } - override fun onUpdated(model: TModel, path: String, property: String, oldValue: Any?, newValue: Any?) { - val operation = getUpdateOperation(model, path, property, oldValue, newValue) + override fun onModelUpdated(args: ModelChangedArgs, tag: String) { + if (tag != ModelChangeTags.NORMAL) { + return + } + + val operation = getUpdateOperation(args.model as TModel, args.path, args.property, args.oldValue, args.newValue) if (operation != null) { opRepo.enqueue(operation) } } - override fun onRemoved(model: TModel) { + override fun onModelRemoved(model: TModel, tag: String) { + if (tag != ModelChangeTags.NORMAL) { + return + } + val operation = getRemoveOperation(model) if (operation != null) { opRepo.enqueue(operation) diff --git a/OneSignalSDK/onesignal/src/main/java/com/onesignal/core/internal/listeners/PropertiesModelStoreListener.kt b/OneSignalSDK/onesignal/src/main/java/com/onesignal/core/internal/listeners/PropertiesModelStoreListener.kt index b5b93e1812..b1d35d8a99 100644 --- a/OneSignalSDK/onesignal/src/main/java/com/onesignal/core/internal/listeners/PropertiesModelStoreListener.kt +++ b/OneSignalSDK/onesignal/src/main/java/com/onesignal/core/internal/listeners/PropertiesModelStoreListener.kt @@ -23,12 +23,12 @@ internal class PropertiesModelStoreListener( override fun getUpdateOperation(model: PropertiesModel, path: String, property: String, oldValue: Any?, newValue: Any?): Operation? { if (path.startsWith(PropertiesModel::tags.name)) { return if (newValue != null && newValue is String) { - SetTagOperation(_configModelStore.get().appId, model.onesignalId, property, newValue) + SetTagOperation(_configModelStore.model.appId, model.onesignalId, property, newValue) } else { - DeleteTagOperation(_configModelStore.get().appId, model.onesignalId, property) + DeleteTagOperation(_configModelStore.model.appId, model.onesignalId, property) } } - return SetPropertyOperation(_configModelStore.get().appId, model.onesignalId, property, newValue) + return SetPropertyOperation(_configModelStore.model.appId, model.onesignalId, property, newValue) } } diff --git a/OneSignalSDK/onesignal/src/main/java/com/onesignal/core/internal/listeners/SessionListener.kt b/OneSignalSDK/onesignal/src/main/java/com/onesignal/core/internal/listeners/SessionListener.kt index 8ed9bac8bf..c7b9032d8d 100644 --- a/OneSignalSDK/onesignal/src/main/java/com/onesignal/core/internal/listeners/SessionListener.kt +++ b/OneSignalSDK/onesignal/src/main/java/com/onesignal/core/internal/listeners/SessionListener.kt @@ -39,10 +39,10 @@ internal class SessionListener( } override fun onSessionEnded(duration: Long) { - _operationRepo.enqueue(TrackSessionOperation(_configModelStore.get().appId, _identityModelStore.get().onesignalId, duration)) + _operationRepo.enqueue(TrackSessionOperation(_configModelStore.model.appId, _identityModelStore.model.onesignalId, duration)) suspendifyOnThread { - _outcomeEventsController.sendOutcomeEvent("__os_session_end") + _outcomeEventsController.sendOutcomeEvent("os__session_duration") } } } diff --git a/OneSignalSDK/onesignal/src/main/java/com/onesignal/core/internal/listeners/SingletonModelStoreListener.kt b/OneSignalSDK/onesignal/src/main/java/com/onesignal/core/internal/listeners/SingletonModelStoreListener.kt index aef46d64b7..729fa4d57a 100644 --- a/OneSignalSDK/onesignal/src/main/java/com/onesignal/core/internal/listeners/SingletonModelStoreListener.kt +++ b/OneSignalSDK/onesignal/src/main/java/com/onesignal/core/internal/listeners/SingletonModelStoreListener.kt @@ -3,6 +3,8 @@ package com.onesignal.core.internal.listeners import com.onesignal.core.internal.modeling.ISingletonModelStore import com.onesignal.core.internal.modeling.ISingletonModelStoreChangeHandler import com.onesignal.core.internal.modeling.Model +import com.onesignal.core.internal.modeling.ModelChangeTags +import com.onesignal.core.internal.modeling.ModelChangedArgs import com.onesignal.core.internal.operations.IOperationRepo import com.onesignal.core.internal.operations.Operation import com.onesignal.core.internal.startup.IBootstrapService @@ -21,15 +23,23 @@ internal abstract class SingletonModelStoreListener( store.unsubscribe(this) } - override fun onModelReplaced(model: TModel) { + override fun onModelReplaced(model: TModel, tag: String) { + if (tag != ModelChangeTags.NORMAL) { + return + } + val operation = getReplaceOperation(model) if (operation != null) { opRepo.enqueue(operation) } } - override fun onModelUpdated(model: TModel, path: String, property: String, oldValue: Any?, newValue: Any?) { - val operation = getUpdateOperation(model, path, property, oldValue, newValue) + override fun onModelUpdated(args: ModelChangedArgs, tag: String) { + if (tag != ModelChangeTags.NORMAL) { + return + } + + val operation = getUpdateOperation(args.model as TModel, args.path, args.property, args.oldValue, args.newValue) if (operation != null) { opRepo.enqueue(operation) } diff --git a/OneSignalSDK/onesignal/src/main/java/com/onesignal/core/internal/listeners/SubscriptionModelStoreListener.kt b/OneSignalSDK/onesignal/src/main/java/com/onesignal/core/internal/listeners/SubscriptionModelStoreListener.kt index 2b4bd02c77..c88c0894bf 100644 --- a/OneSignalSDK/onesignal/src/main/java/com/onesignal/core/internal/listeners/SubscriptionModelStoreListener.kt +++ b/OneSignalSDK/onesignal/src/main/java/com/onesignal/core/internal/listeners/SubscriptionModelStoreListener.kt @@ -18,14 +18,14 @@ internal class SubscriptionModelStoreListener( ) : ModelStoreListener(store, opRepo) { override fun getAddOperation(model: SubscriptionModel): Operation { - return CreateSubscriptionOperation(_configModelStore.get().appId, _identityModelStore.get().onesignalId, model.id, model.type, model.enabled, model.address) + return CreateSubscriptionOperation(_configModelStore.model.appId, _identityModelStore.model.onesignalId, model.id, model.type, model.enabled, model.address, model.status) } override fun getRemoveOperation(model: SubscriptionModel): Operation { - return DeleteSubscriptionOperation(_configModelStore.get().appId, _identityModelStore.get().onesignalId, model.id) + return DeleteSubscriptionOperation(_configModelStore.model.appId, _identityModelStore.model.onesignalId, model.id) } override fun getUpdateOperation(model: SubscriptionModel, path: String, property: String, oldValue: Any?, newValue: Any?): Operation { - return UpdateSubscriptionOperation(_configModelStore.get().appId, _identityModelStore.get().onesignalId, model.id, model.enabled, model.address) + return UpdateSubscriptionOperation(_configModelStore.model.appId, _identityModelStore.model.onesignalId, model.id, model.enabled, model.address, model.status) } } diff --git a/OneSignalSDK/onesignal/src/main/java/com/onesignal/core/internal/modeling/IModelChangedHandler.kt b/OneSignalSDK/onesignal/src/main/java/com/onesignal/core/internal/modeling/IModelChangedHandler.kt index f48a2c1254..d135d77bc7 100644 --- a/OneSignalSDK/onesignal/src/main/java/com/onesignal/core/internal/modeling/IModelChangedHandler.kt +++ b/OneSignalSDK/onesignal/src/main/java/com/onesignal/core/internal/modeling/IModelChangedHandler.kt @@ -10,8 +10,9 @@ internal interface IModelChangedHandler { * Called when the subscribed model has been changed. * * @param args Information related to what has changed. + * @param tag The tag which identifies how/why the model was changed. */ - fun onChanged(args: ModelChangedArgs) + fun onChanged(args: ModelChangedArgs, tag: String) } /** diff --git a/OneSignalSDK/onesignal/src/main/java/com/onesignal/core/internal/modeling/IModelStore.kt b/OneSignalSDK/onesignal/src/main/java/com/onesignal/core/internal/modeling/IModelStore.kt index 9e6fa19ffa..cdfca9e68c 100644 --- a/OneSignalSDK/onesignal/src/main/java/com/onesignal/core/internal/modeling/IModelStore.kt +++ b/OneSignalSDK/onesignal/src/main/java/com/onesignal/core/internal/modeling/IModelStore.kt @@ -18,9 +18,9 @@ internal interface IModelStore : IEventNotifier @@ -28,12 +28,12 @@ internal interface IModelStore : IEventNotifier : IEventNotifier, tag: String = ModelChangeTags.NORMAL) +} + +internal object ModelChangeTags { + /** + * A change was performed through normal means. + */ + const val NORMAL = "NORMAL" + + /** + * A change was performed that should *not* be propogated to the backend. + */ + const val NO_PROPOGATE = "NO_PROPOGATE" + + /** + * A change was performed through the backend hydrating the model. */ - fun replaceAll(models: List, fireEvent: Boolean = true) + const val HYDRATE = "HYDRATE" } diff --git a/OneSignalSDK/onesignal/src/main/java/com/onesignal/core/internal/modeling/IModelStoreChangeHandler.kt b/OneSignalSDK/onesignal/src/main/java/com/onesignal/core/internal/modeling/IModelStoreChangeHandler.kt index 0b5f53fba8..bed4567449 100644 --- a/OneSignalSDK/onesignal/src/main/java/com/onesignal/core/internal/modeling/IModelStoreChangeHandler.kt +++ b/OneSignalSDK/onesignal/src/main/java/com/onesignal/core/internal/modeling/IModelStoreChangeHandler.kt @@ -9,26 +9,25 @@ internal interface IModelStoreChangeHandler where TModel : Model { * Called when a model has been added to the model store. * * @param model The model that has been added. + * @param tag The tag which identifies how/why the model was added. */ - fun onAdded(model: TModel) + fun onModelAdded(model: TModel, tag: String) /** * Called when a model has been updated. This callback wraps [IModelChangedHandler.onChanged] * so users of the model store does not need to manage subscriptions to each individual [Model] * within the store. * - * @param model The model that has been updated (includes the updates). - * @param path The path to the property of the model that has been updated. - * @param property The property of the model that has been updated. - * @param oldValue The old value of the property that has changed. - * @param newValue The new value of the property that has changed. + * @param args The model changed arguments. + * @param tag The tag which identifies how/why the model was updated. */ - fun onUpdated(model: TModel, path: String, property: String, oldValue: Any?, newValue: Any?) + fun onModelUpdated(args: ModelChangedArgs, tag: String) /** * Called when a model has been removed from the model store. * * @param model The model that has been removed. + * @param tag The tag which identifies how/why the model was removed. */ - fun onRemoved(model: TModel) + fun onModelRemoved(model: TModel, tag: String) } diff --git a/OneSignalSDK/onesignal/src/main/java/com/onesignal/core/internal/modeling/ISingletonModelStore.kt b/OneSignalSDK/onesignal/src/main/java/com/onesignal/core/internal/modeling/ISingletonModelStore.kt index 8aa2fd6d7b..fd8e9e51ba 100644 --- a/OneSignalSDK/onesignal/src/main/java/com/onesignal/core/internal/modeling/ISingletonModelStore.kt +++ b/OneSignalSDK/onesignal/src/main/java/com/onesignal/core/internal/modeling/ISingletonModelStore.kt @@ -10,17 +10,15 @@ import com.onesignal.core.internal.common.events.IEventNotifier internal interface ISingletonModelStore : IEventNotifier> where TModel : Model { /** - * Retrieve the model managed by this singleton model store. - * - * @return The single model managed by this store. + * The model managed by this singleton model store. */ - fun get(): TModel + val model: TModel /** * Replace the existing model with the new model provided. * * @param model A model that contains all the data for the new effective model. - * @param fireEvent Whether an event should be fired for this update action. + * @param tag The tag which identifies how/why the model is being replaced. */ - fun replace(model: TModel, fireEvent: Boolean = true) + fun replace(model: TModel, tag: String = ModelChangeTags.NORMAL) } diff --git a/OneSignalSDK/onesignal/src/main/java/com/onesignal/core/internal/modeling/ISingletonModelStoreChangeHandler.kt b/OneSignalSDK/onesignal/src/main/java/com/onesignal/core/internal/modeling/ISingletonModelStoreChangeHandler.kt index 918ff5c764..00c67ea521 100644 --- a/OneSignalSDK/onesignal/src/main/java/com/onesignal/core/internal/modeling/ISingletonModelStoreChangeHandler.kt +++ b/OneSignalSDK/onesignal/src/main/java/com/onesignal/core/internal/modeling/ISingletonModelStoreChangeHandler.kt @@ -10,19 +10,17 @@ internal interface ISingletonModelStoreChangeHandler where TModel : Mode * Called when the model has been replaced. * * @param model The model + * @param tag The tag which identifies how/why the model was replaced. */ - fun onModelReplaced(model: TModel) + fun onModelReplaced(model: TModel, tag: String) /** * Called when a property within the model has been updated. This callback wraps [IModelChangedHandler.onChanged] * so users of the model store does not need to manage subscriptions to the individual [Model] * within the store. * - * @param model The model that has been updated (includes the updates). - * @param path The path to the property of the model that has been updated. - * @param property The property of the model that has been updated. - * @param oldValue The old value of the property that has changed. - * @param newValue The new value of the property that has changed. + * @param args The model changed arguments. + * @param tag The tag which identifies how/why the model was updated. */ - fun onModelUpdated(model: TModel, path: String, property: String, oldValue: Any?, newValue: Any?) + fun onModelUpdated(args: ModelChangedArgs, tag: String) } diff --git a/OneSignalSDK/onesignal/src/main/java/com/onesignal/core/internal/modeling/MapModel.kt b/OneSignalSDK/onesignal/src/main/java/com/onesignal/core/internal/modeling/MapModel.kt index c7c17c44d0..fc272ee9c7 100644 --- a/OneSignalSDK/onesignal/src/main/java/com/onesignal/core/internal/modeling/MapModel.kt +++ b/OneSignalSDK/onesignal/src/main/java/com/onesignal/core/internal/modeling/MapModel.kt @@ -2,8 +2,8 @@ package com.onesignal.core.internal.modeling /** * A Map Model is a [MutableMap] that has a key of type string and a generically-specified - * value. It is a [Model] which hooks the map into the model framework and allows for change - * notification propagation for any adds, removes, or updates to the map. + * value. It is a [Model] which hooks the [MutableMap] into the model framework and allows for change + * notification propagation for any adds, removes, or updates to the [MutableMap]. */ internal open class MapModel( parentModel: Model? = null, diff --git a/OneSignalSDK/onesignal/src/main/java/com/onesignal/core/internal/modeling/Model.kt b/OneSignalSDK/onesignal/src/main/java/com/onesignal/core/internal/modeling/Model.kt index c8c12867ab..303f0fff70 100644 --- a/OneSignalSDK/onesignal/src/main/java/com/onesignal/core/internal/modeling/Model.kt +++ b/OneSignalSDK/onesignal/src/main/java/com/onesignal/core/internal/modeling/Model.kt @@ -33,7 +33,7 @@ import org.json.JSONObject * [MutableMap] and [Model]. * * Deserialization - * ----------------------------- + * --------------- * When deserializing a flat [Model] nothing specific is required. However if the [Model] * is nested the [createModelForProperty] and/or [createListForProperty] needs to be implemented * to aide in the deserialization process. @@ -85,15 +85,15 @@ internal open class Model( if (jsonValue is JSONObject) { val childModel = createModelForProperty(property, jsonValue) if (childModel != null) { - setProperty(property, childModel, notify = false) + data[property] = childModel } } else if (jsonValue is JSONArray) { val listOfItems = createListForProperty(property, jsonValue) if (listOfItems != null) { - setProperty(property, listOfItems, notify = false) + data[property] = listOfItems } } else { - setProperty(property, jsonObject.get(property), notify = false) + data[property] = jsonObject.get(property) } } } @@ -102,11 +102,13 @@ internal open class Model( * Initialize this model from a [Model]. The model provided will be replicated * within this model. * + * @param id The id of the model to initialze to. * @param model The model to initialize this model from. */ - fun initializeFromModel(model: Model) { + fun initializeFromModel(id: String, model: Model) { data.clear() data.putAll(model.data) + data[::id.name] = id } /** @@ -143,9 +145,9 @@ internal open class Model( * * @param name: The name of the property that is to be set. * @param value: The value of the property to set it to. - * @param notify: Whether to notify subscribers of the change. + * @param tag The tag which identifies how/why the property is being set. */ - fun setProperty(name: String, value: T, notify: Boolean = true) { + fun setProperty(name: String, value: T, tag: String = ModelChangeTags.NORMAL) { val oldValue = data[name] val newValue = value as Any? @@ -159,9 +161,7 @@ internal open class Model( data.remove(name) } - if (notify) { - notifyChanged(name, name, oldValue, newValue) - } + notifyChanged(name, name, tag, oldValue, newValue) } /** @@ -194,15 +194,15 @@ internal open class Model( } } - private fun notifyChanged(path: String, property: String, oldValue: Any?, newValue: Any?) { + private fun notifyChanged(path: String, property: String, tag: String, oldValue: Any?, newValue: Any?) { // if there are any changed listeners for this specific model, notify them. val changeArgs = ModelChangedArgs(this, path, property, oldValue, newValue) - _changeNotifier.fire { it.onChanged(changeArgs) } + _changeNotifier.fire { it.onChanged(changeArgs, tag) } // if there is a parent model, propagate the change up to the parent for it's own processing. if (_parentModel != null) { val parentPath = "$_parentProperty.$path" - _parentModel.notifyChanged(parentPath, property, oldValue, newValue) + _parentModel.notifyChanged(parentPath, property, tag, oldValue, newValue) } } diff --git a/OneSignalSDK/onesignal/src/main/java/com/onesignal/core/internal/modeling/ModelStore.kt b/OneSignalSDK/onesignal/src/main/java/com/onesignal/core/internal/modeling/ModelStore.kt index 9bcb519ce8..9381910de4 100644 --- a/OneSignalSDK/onesignal/src/main/java/com/onesignal/core/internal/modeling/ModelStore.kt +++ b/OneSignalSDK/onesignal/src/main/java/com/onesignal/core/internal/modeling/ModelStore.kt @@ -35,13 +35,13 @@ internal abstract class ModelStore( private val _changeSubscription: EventProducer> = EventProducer() private val _models: MutableList = mutableListOf() - override fun add(model: TModel, fireEvent: Boolean) { + override fun add(model: TModel, tag: String) { val oldModel = _models.firstOrNull { it.id == model.id } if (oldModel != null) { - removeItem(oldModel, fireEvent) + removeItem(oldModel, tag) } - addItem(model, fireEvent) + addItem(model, tag) } override fun list(): Collection { @@ -52,34 +52,39 @@ internal abstract class ModelStore( return _models.firstOrNull { it.id == id } } - override fun remove(id: String, fireEvent: Boolean) { + override fun remove(id: String, tag: String) { val model = _models.firstOrNull { it.id == id } ?: return - removeItem(model, fireEvent) + removeItem(model, tag) } - override fun onChanged(args: ModelChangedArgs) { + override fun onChanged(args: ModelChangedArgs, tag: String) { persist() - _changeSubscription.fire { it.onUpdated(args.model as TModel, args.path, args.property, args.oldValue, args.newValue) } + + _changeSubscription.fire { it.onModelUpdated(args, tag) } } - override fun replaceAll(models: List, fireEvent: Boolean) { - clear(fireEvent) + override fun replaceAll(models: List, tag: String) { + clear(tag) for (model in models) { - add(model, fireEvent) + add(model, tag) } } - override fun clear(fireEvent: Boolean) { + override fun clear(tag: String) { val localList = _models.toList() _models.clear() + persist() + for (item in localList) { - removeItem(item, fireEvent) + // no longer listen for changes to this model + item.unsubscribe(this) + _changeSubscription.fire { it.onModelRemoved(item, tag) } } } - private fun addItem(model: TModel, fireEvent: Boolean) { + private fun addItem(model: TModel, tag: String) { _models.add(model) // listen for changes to this model @@ -87,12 +92,10 @@ internal abstract class ModelStore( persist() - if (fireEvent) { - _changeSubscription.fire { it.onAdded(model) } - } + _changeSubscription.fire { it.onModelAdded(model, tag) } } - private fun removeItem(model: TModel, fireEvent: Boolean) { + private fun removeItem(model: TModel, tag: String) { _models.remove(model) // no longer listen for changes to this model @@ -100,9 +103,7 @@ internal abstract class ModelStore( persist() - if (fireEvent) { - _changeSubscription.fire { it.onRemoved(model) } - } + _changeSubscription.fire { it.onModelRemoved(model, tag) } } protected fun load() { @@ -118,10 +119,7 @@ internal abstract class ModelStore( } } - /** - * Persist this model store. - */ - fun persist() { + private fun persist() { if (name != null && _prefs != null) { val jsonArray = JSONArray() for (model in _models) { diff --git a/OneSignalSDK/onesignal/src/main/java/com/onesignal/core/internal/modeling/SingletonModelStore.kt b/OneSignalSDK/onesignal/src/main/java/com/onesignal/core/internal/modeling/SingletonModelStore.kt index c108904edb..e35b71a15b 100644 --- a/OneSignalSDK/onesignal/src/main/java/com/onesignal/core/internal/modeling/SingletonModelStore.kt +++ b/OneSignalSDK/onesignal/src/main/java/com/onesignal/core/internal/modeling/SingletonModelStore.kt @@ -17,45 +17,37 @@ internal open class SingletonModelStore( store.subscribe(this) } - override fun get(): TModel { - val model = store.get(_singletonId) - if (model != null) { - return model + override val model: TModel + get() { + val model = store.get(_singletonId) + if (model != null) { + return model + } + + val createdModel = store.create() + createdModel.id = _singletonId + store.add(createdModel) + return createdModel } - val createdModel = store.create() - createdModel.id = _singletonId - store.add(createdModel) - return createdModel - } - - override fun replace(model: TModel, fireEvent: Boolean) { - val existingModel = get() - existingModel.initializeFromModel(model) - existingModel.setProperty(Model::id.name, _singletonId, notify = false) - - _changeSubscription.fire { it.onModelReplaced(existingModel) } - } - - /** - * Persist the singleton model store. - */ - fun persist() { - store.persist() + override fun replace(model: TModel, tag: String) { + val existingModel = this.model + existingModel.initializeFromModel(_singletonId, model) + _changeSubscription.fire { it.onModelReplaced(existingModel, tag) } } override fun subscribe(handler: ISingletonModelStoreChangeHandler) = _changeSubscription.subscribe(handler) override fun unsubscribe(handler: ISingletonModelStoreChangeHandler) = _changeSubscription.unsubscribe(handler) - override fun onAdded(model: TModel) { + override fun onModelAdded(model: TModel, tag: String) { // singleton is assumed to always exist. It gets added transparently therefore no event. } - override fun onUpdated(model: TModel, path: String, property: String, oldValue: Any?, newValue: Any?) { - _changeSubscription.fire { it.onModelUpdated(model, path, property, oldValue, newValue) } + override fun onModelUpdated(args: ModelChangedArgs, tag: String) { + _changeSubscription.fire { it.onModelUpdated(args, tag) } } - override fun onRemoved(model: TModel) { + override fun onModelRemoved(model: TModel, tag: String) { // singleton is assumed to always exist. It never gets removed therefore no event. } } diff --git a/OneSignalSDK/onesignal/src/main/java/com/onesignal/core/internal/models/SubscriptionModel.kt b/OneSignalSDK/onesignal/src/main/java/com/onesignal/core/internal/models/SubscriptionModel.kt index df52fb0737..00d4c2a237 100644 --- a/OneSignalSDK/onesignal/src/main/java/com/onesignal/core/internal/models/SubscriptionModel.kt +++ b/OneSignalSDK/onesignal/src/main/java/com/onesignal/core/internal/models/SubscriptionModel.kt @@ -20,4 +20,35 @@ internal class SubscriptionModel : Model() { var address: String get() = getProperty(::address.name) set(value) { setProperty(::address.name, value) } + + var status: Int + get() = getProperty(::status.name) { STATUS_SUBSCRIBED } + set(value) { setProperty(::status.name, value) } + + companion object { + const val STATUS_SUBSCRIBED = 1 + const val STATUS_NO_PERMISSION = 0 + const val STATUS_UNSUBSCRIBE = -2 + const val STATUS_MISSING_ANDROID_SUPPORT_LIBRARY = -3 + const val STATUS_MISSING_FIREBASE_FCM_LIBRARY = -4 + const val STATUS_OUTDATED_ANDROID_SUPPORT_LIBRARY = -5 + const val STATUS_INVALID_FCM_SENDER_ID = -6 + const val STATUS_OUTDATED_GOOGLE_PLAY_SERVICES_APP = -7 + const val STATUS_FIREBASE_FCM_INIT_ERROR = -8 + const val STATUS_FIREBASE_FCM_ERROR_IOEXCEPTION_SERVICE_NOT_AVAILABLE = -9 + + // -10 is a server side detection only from FCM that the app is no longer installed + const val STATUS_FIREBASE_FCM_ERROR_IOEXCEPTION_OTHER = -11 + const val STATUS_FIREBASE_FCM_ERROR_MISC_EXCEPTION = -12 + + // -13 to -24 reserved for other platforms + const val STATUS_HMS_TOKEN_TIMEOUT = -25 + + // Most likely missing "client/app_id". + // Check that there is "apply plugin: 'com.huawei.agconnect'" in your app/build.gradle + const val STATUS_HMS_ARGUMENTS_INVALID = -26 + const val STATUS_HMS_API_EXCEPTION_OTHER = -27 + const val STATUS_MISSING_HMS_PUSHKIT_LIBRARY = -28 + const val STATUS_FIREBASE_FCM_ERROR_IOEXCEPTION_AUTHENTICATION_FAILED = -29 + } } diff --git a/OneSignalSDK/onesignal/src/main/java/com/onesignal/core/internal/operations/CreateSubscriptionOperation.kt b/OneSignalSDK/onesignal/src/main/java/com/onesignal/core/internal/operations/CreateSubscriptionOperation.kt index 995c8e2fdd..c963084672 100644 --- a/OneSignalSDK/onesignal/src/main/java/com/onesignal/core/internal/operations/CreateSubscriptionOperation.kt +++ b/OneSignalSDK/onesignal/src/main/java/com/onesignal/core/internal/operations/CreateSubscriptionOperation.kt @@ -58,17 +58,25 @@ internal class CreateSubscriptionOperation() : Operation(SubscriptionOperationEx get() = getProperty(::address.name) private set(value) { setProperty(::address.name, value) } + /** + * The status of this subscription. + */ + var status: Int + get() = getProperty(::status.name) + private set(value) { setProperty(::status.name, value) } + override val createComparisonKey: String get() = "$appId.User.$onesignalId" override val modifyComparisonKey: String get() = "$appId.User.$onesignalId.Subscription.$subscriptionId" override val groupComparisonType: GroupComparisonType = GroupComparisonType.ALTER override val canStartExecute: Boolean get() = !IDManager.isIdLocalOnly(onesignalId) - constructor(appId: String, onesignalId: String, subscriptionId: String, type: SubscriptionType, enabled: Boolean, address: String) : this() { + constructor(appId: String, onesignalId: String, subscriptionId: String, type: SubscriptionType, enabled: Boolean, address: String, status: Int) : this() { this.appId = appId this.onesignalId = onesignalId this.subscriptionId = subscriptionId this.type = type this.enabled = enabled this.address = address + this.status = status } } diff --git a/OneSignalSDK/onesignal/src/main/java/com/onesignal/core/internal/operations/UpdateSubscriptionOperation.kt b/OneSignalSDK/onesignal/src/main/java/com/onesignal/core/internal/operations/UpdateSubscriptionOperation.kt index f2e50d86d8..8783196224 100644 --- a/OneSignalSDK/onesignal/src/main/java/com/onesignal/core/internal/operations/UpdateSubscriptionOperation.kt +++ b/OneSignalSDK/onesignal/src/main/java/com/onesignal/core/internal/operations/UpdateSubscriptionOperation.kt @@ -50,16 +50,24 @@ internal class UpdateSubscriptionOperation() : Operation(SubscriptionOperationEx get() = getProperty(::address.name) private set(value) { setProperty(::address.name, value) } + /** + * The status of this subscription. + */ + var status: Int + get() = getProperty(::status.name) + private set(value) { setProperty(::status.name, value) } + override val createComparisonKey: String get() = "$appId.User.$onesignalId" override val modifyComparisonKey: String get() = "$appId.User.$onesignalId.Subscription.$subscriptionId" override val groupComparisonType: GroupComparisonType = GroupComparisonType.ALTER override val canStartExecute: Boolean get() = !IDManager.isIdLocalOnly(onesignalId) && !IDManager.isIdLocalOnly(onesignalId) - constructor(appId: String, onesignalId: String, subscriptionId: String, enabled: Boolean, address: String) : this() { + constructor(appId: String, onesignalId: String, subscriptionId: String, enabled: Boolean, address: String, status: Int) : this() { this.appId = appId this.onesignalId = onesignalId this.subscriptionId = subscriptionId this.enabled = enabled this.address = address + this.status = status } } diff --git a/OneSignalSDK/onesignal/src/main/java/com/onesignal/core/internal/operations/impl/SubscriptionOperationExecutor.kt b/OneSignalSDK/onesignal/src/main/java/com/onesignal/core/internal/operations/impl/SubscriptionOperationExecutor.kt index 2eed279a2c..63bc3c6a1f 100644 --- a/OneSignalSDK/onesignal/src/main/java/com/onesignal/core/internal/operations/impl/SubscriptionOperationExecutor.kt +++ b/OneSignalSDK/onesignal/src/main/java/com/onesignal/core/internal/operations/impl/SubscriptionOperationExecutor.kt @@ -8,6 +8,7 @@ import com.onesignal.core.internal.backend.SubscriptionObjectType import com.onesignal.core.internal.common.IDManager import com.onesignal.core.internal.device.IDeviceService import com.onesignal.core.internal.logging.Logging +import com.onesignal.core.internal.modeling.ModelChangeTags import com.onesignal.core.internal.models.SubscriptionModel import com.onesignal.core.internal.models.SubscriptionModelStore import com.onesignal.core.internal.models.SubscriptionType @@ -49,8 +50,9 @@ internal class SubscriptionOperationExecutor( // the effective enabled/address for this subscription is the last update performed, if there is one. If there // isn't one we fall back to whatever is in the create operation. val lastUpdateOperation = operations.lastOrNull { it is UpdateSubscriptionOperation } as UpdateSubscriptionOperation? - var enabled = lastUpdateOperation?.enabled ?: createOperation.enabled - var address = lastUpdateOperation?.address ?: createOperation.address + val enabled = lastUpdateOperation?.enabled ?: createOperation.enabled + val address = lastUpdateOperation?.address ?: createOperation.address + val status = lastUpdateOperation?.status ?: createOperation.status // translate the subscription type to the subscription object type. val subscriptionType: SubscriptionObjectType = when (createOperation.type) { @@ -72,7 +74,8 @@ internal class SubscriptionOperationExecutor( IDManager.retrieveId(createOperation.onesignalId), subscriptionType, enabled, - address + address, + status ) // Add the "local-to-backend" ID translation to the IdentifierTranslator for any operations that were @@ -81,7 +84,7 @@ internal class SubscriptionOperationExecutor( IDManager.setLocalToBackendIdTranslation(createOperation.subscriptionId, backendSubscriptionId) val subscriptionModel = _subscriptionModelStore.get(createOperation.subscriptionId) - subscriptionModel?.setProperty(SubscriptionModel::id.name, backendSubscriptionId, false) + subscriptionModel?.setProperty(SubscriptionModel::id.name, backendSubscriptionId, ModelChangeTags.HYDRATE) } catch (ex: BackendException) { // TODO: Error handling } @@ -95,7 +98,8 @@ internal class SubscriptionOperationExecutor( lastOperation.appId, IDManager.retrieveId(lastOperation.subscriptionId), lastOperation.enabled, - lastOperation.address + lastOperation.address, + lastOperation.status ) } catch (ex: BackendException) { // TODO: Need a concept of retrying on network error, and other error handling diff --git a/OneSignalSDK/onesignal/src/main/java/com/onesignal/core/internal/operations/impl/UserOperationExecutor.kt b/OneSignalSDK/onesignal/src/main/java/com/onesignal/core/internal/operations/impl/UserOperationExecutor.kt index 3004d8069e..3a8f8bcbc0 100644 --- a/OneSignalSDK/onesignal/src/main/java/com/onesignal/core/internal/operations/impl/UserOperationExecutor.kt +++ b/OneSignalSDK/onesignal/src/main/java/com/onesignal/core/internal/operations/impl/UserOperationExecutor.kt @@ -17,6 +17,7 @@ import com.onesignal.core.internal.common.OneSignalUtils import com.onesignal.core.internal.common.RootToolsInternalMethods import com.onesignal.core.internal.device.IDeviceService import com.onesignal.core.internal.logging.Logging +import com.onesignal.core.internal.modeling.ModelChangeTags import com.onesignal.core.internal.models.IdentityModelStore import com.onesignal.core.internal.models.PropertiesModel import com.onesignal.core.internal.models.PropertiesModelStore @@ -98,25 +99,23 @@ internal class UserOperationExecutor( IDManager.setLocalToBackendIdTranslation(createUserOperation.onesignalId, backendOneSignalId) - val identityModel = _identityModelStore.get() - val propertiesModel = _propertiesModelStore.get() + val identityModel = _identityModelStore.model + val propertiesModel = _propertiesModelStore.model if (identityModel.onesignalId == createUserOperation.onesignalId) { - identityModel.setProperty(IdentityConstants.ONESIGNAL_ID, backendOneSignalId, false) + identityModel.setProperty(IdentityConstants.ONESIGNAL_ID, backendOneSignalId, ModelChangeTags.HYDRATE) // TODO: hydrate any additional aliases from the backend... - _identityModelStore.persist() } if (propertiesModel.onesignalId == createUserOperation.onesignalId) { - propertiesModel.setProperty(PropertiesModel::onesignalId.name, backendOneSignalId, notify = false) + propertiesModel.setProperty(PropertiesModel::onesignalId.name, backendOneSignalId, ModelChangeTags.HYDRATE) // TODO: hydrate the models from the backend create response. Temporarily inject dummy stuff to // show that it's working. // propertiesModel.setProperty(PropertiesModel::language.name, "en", notify = false) - propertiesModel.setProperty(PropertiesModel::country.name, "US", notify = false) - propertiesModel.tags.setProperty("foo", UUID.randomUUID().toString(), notify = false) - _propertiesModelStore.persist() + propertiesModel.setProperty(PropertiesModel::country.name, "US", ModelChangeTags.HYDRATE) + propertiesModel.tags.setProperty("foo", UUID.randomUUID().toString(), ModelChangeTags.HYDRATE) } // TODO: assumption that the response.subscriptionIDs will associate to the input subscriptionList...to confirm @@ -130,9 +129,8 @@ internal class UserOperationExecutor( IDManager.setLocalToBackendIdTranslation(subscriptionList[index].id, backendSubscriptionId) val subscriptionModel = _subscriptionsModelStore.get(subscriptionList[index].id) - subscriptionModel?.setProperty(SubscriptionModel::id.name, backendSubscriptionId, false) + subscriptionModel?.setProperty(SubscriptionModel::id.name, backendSubscriptionId, ModelChangeTags.HYDRATE) } - _subscriptionsModelStore.persist() } catch (ex: BackendException) { } } diff --git a/OneSignalSDK/onesignal/src/main/java/com/onesignal/core/internal/outcomes/impl/OutcomeEventsController.kt b/OneSignalSDK/onesignal/src/main/java/com/onesignal/core/internal/outcomes/impl/OutcomeEventsController.kt index dd3b43a5a2..19660ebe55 100644 --- a/OneSignalSDK/onesignal/src/main/java/com/onesignal/core/internal/outcomes/impl/OutcomeEventsController.kt +++ b/OneSignalSDK/onesignal/src/main/java/com/onesignal/core/internal/outcomes/impl/OutcomeEventsController.kt @@ -65,7 +65,7 @@ internal class OutcomeEventsController( private suspend fun sendSavedOutcomeEvent(event: OutcomeEventParams) { val deviceType: Int = _deviceService.deviceType - val appId: String = _configModelStore.get().appId + val appId: String = _configModelStore.model.appId try { requestMeasureOutcomeEvent(appId, deviceType, event) @@ -159,7 +159,7 @@ Outcome event was cached and will be reattempted on app cold start""" ): OutcomeEvent? { val timestampSeconds: Long = _time.currentTimeMillis / 1000 val deviceType: Int = _deviceService.deviceType - val appId: String = _configModelStore.get().appId + val appId: String = _configModelStore.model.appId var directSourceBody: OutcomeSourceBody? = null var indirectSourceBody: OutcomeSourceBody? = null var unattributed = false diff --git a/OneSignalSDK/onesignal/src/main/java/com/onesignal/core/internal/preferences/PreferencesService.kt b/OneSignalSDK/onesignal/src/main/java/com/onesignal/core/internal/preferences/PreferencesService.kt index 904a2d63ea..59a3ee0d27 100644 --- a/OneSignalSDK/onesignal/src/main/java/com/onesignal/core/internal/preferences/PreferencesService.kt +++ b/OneSignalSDK/onesignal/src/main/java/com/onesignal/core/internal/preferences/PreferencesService.kt @@ -60,13 +60,17 @@ internal class PreferencesService( val prefs = getSharedPrefsByName(store) if (prefs != null) { - return when (type) { - String::class.java -> prefs.getString(key, defValue as String?) - Boolean::class.java -> prefs.getBoolean(key, (defValue as Boolean?) ?: false) - Int::class.java -> prefs.getInt(key, (defValue as Int?) ?: 0) - Long::class.java -> prefs.getLong(key, (defValue as Long?) ?: 0) - Set::class.java -> prefs.getStringSet(key, defValue as Set?) - else -> null + try { + return when (type) { + String::class.java -> prefs.getString(key, defValue as String?) + Boolean::class.java -> prefs.getBoolean(key, (defValue as Boolean?) ?: false) + Int::class.java -> prefs.getInt(key, (defValue as Int?) ?: 0) + Long::class.java -> prefs.getLong(key, (defValue as Long?) ?: 0) + Set::class.java -> prefs.getStringSet(key, defValue as Set?) + else -> null + } + } catch (ex: Exception) { + // any issues retrieving the preference, return the default value. } } diff --git a/OneSignalSDK/onesignal/src/main/java/com/onesignal/core/internal/purchases/TrackAmazonPurchase.kt b/OneSignalSDK/onesignal/src/main/java/com/onesignal/core/internal/purchases/TrackAmazonPurchase.kt index d2c9d13a7f..39d1f548a8 100644 --- a/OneSignalSDK/onesignal/src/main/java/com/onesignal/core/internal/purchases/TrackAmazonPurchase.kt +++ b/OneSignalSDK/onesignal/src/main/java/com/onesignal/core/internal/purchases/TrackAmazonPurchase.kt @@ -181,7 +181,7 @@ internal class TrackAmazonPurchase( } // TODO: amount spent? - _operationRepo.enqueue(TrackPurchaseOperation(_configModelStore.get().appId, _identityModelStore.get().onesignalId, false, BigDecimal(0), purchasesToReport)) + _operationRepo.enqueue(TrackPurchaseOperation(_configModelStore.model.appId, _identityModelStore.model.onesignalId, false, BigDecimal(0), purchasesToReport)) } } } else if (orgPurchasingListener != null) { diff --git a/OneSignalSDK/onesignal/src/main/java/com/onesignal/core/internal/purchases/TrackGooglePurchase.kt b/OneSignalSDK/onesignal/src/main/java/com/onesignal/core/internal/purchases/TrackGooglePurchase.kt index 066e593e7e..23a53ce0bd 100644 --- a/OneSignalSDK/onesignal/src/main/java/com/onesignal/core/internal/purchases/TrackGooglePurchase.kt +++ b/OneSignalSDK/onesignal/src/main/java/com/onesignal/core/internal/purchases/TrackGooglePurchase.kt @@ -221,7 +221,7 @@ internal class TrackGooglePurchase( // New purchases to report. If successful then mark them as tracked. if (purchasesToReport.isNotEmpty()) { - _operationRepo.enqueue(TrackPurchaseOperation(_configModelStore.get().appId, _identityModelStore.get().onesignalId, newAsExisting, BigDecimal(0), purchasesToReport)) + _operationRepo.enqueue(TrackPurchaseOperation(_configModelStore.model.appId, _identityModelStore.model.onesignalId, newAsExisting, BigDecimal(0), purchasesToReport)) purchaseTokens.addAll(newPurchaseTokens) _prefs.saveString(PreferenceStores.PLAYER_PURCHASES, PreferencePlayerPurchasesKeys.PREFS_PURCHASE_TOKENS, purchaseTokens.toString()) _prefs.saveBool(PreferenceStores.PLAYER_PURCHASES, PreferencePlayerPurchasesKeys.PREFS_EXISTING_PURCHASES, true) diff --git a/OneSignalSDK/onesignal/src/main/java/com/onesignal/core/internal/session/impl/SessionService.kt b/OneSignalSDK/onesignal/src/main/java/com/onesignal/core/internal/session/impl/SessionService.kt index 958ada3988..5d3929e1ea 100644 --- a/OneSignalSDK/onesignal/src/main/java/com/onesignal/core/internal/session/impl/SessionService.kt +++ b/OneSignalSDK/onesignal/src/main/java/com/onesignal/core/internal/session/impl/SessionService.kt @@ -48,8 +48,8 @@ internal class SessionService( private var _config: ConfigModel? = null override fun start() { - _session = _sessionModelStore.get() - _config = _configModelStore.get() + _session = _sessionModelStore.model + _config = _configModelStore.model _applicationService.addApplicationLifecycleHandler(this) } diff --git a/OneSignalSDK/onesignal/src/main/java/com/onesignal/core/internal/user/SubscriptionManager.kt b/OneSignalSDK/onesignal/src/main/java/com/onesignal/core/internal/user/SubscriptionManager.kt index d7b85852ff..28dc3ce188 100644 --- a/OneSignalSDK/onesignal/src/main/java/com/onesignal/core/internal/user/SubscriptionManager.kt +++ b/OneSignalSDK/onesignal/src/main/java/com/onesignal/core/internal/user/SubscriptionManager.kt @@ -6,6 +6,7 @@ import com.onesignal.core.internal.common.events.EventProducer import com.onesignal.core.internal.common.events.IEventNotifier import com.onesignal.core.internal.logging.Logging import com.onesignal.core.internal.modeling.IModelStoreChangeHandler +import com.onesignal.core.internal.modeling.ModelChangedArgs import com.onesignal.core.internal.models.SubscriptionModel import com.onesignal.core.internal.models.SubscriptionModelStore import com.onesignal.core.internal.models.SubscriptionType @@ -19,16 +20,14 @@ internal interface ISubscriptionManager : IEventNotifier Unit) { @@ -125,7 +126,7 @@ internal class SubscriptionManager( * Called when the model store has added a new subscription. The subscription list must be updated * to reflect the added subscription. */ - override fun onAdded(model: SubscriptionModel) { + override fun onModelAdded(model: SubscriptionModel, tag: String) { createSubscriptionAndAddToSubscriptionList(model) } @@ -133,16 +134,16 @@ internal class SubscriptionManager( * Called when a subscription model has been updated. The subscription list must be updated * to reflect the update subscription. */ - override fun onUpdated(model: SubscriptionModel, path: String, property: String, oldValue: Any?, newValue: Any?) { - val subscription = subscriptions.collection.firstOrNull { it.id == model.id } + override fun onModelUpdated(args: ModelChangedArgs, tag: String) { + val subscription = subscriptions.collection.firstOrNull { it.id == args.model.id } if (subscription == null) { // this shouldn't happen, but create a new subscription if a model was updated and we // don't yet have a representation for it in the subscription list. - createSubscriptionAndAddToSubscriptionList(model) + createSubscriptionAndAddToSubscriptionList(args.model as SubscriptionModel) } else { // the model has already been updated, so fire the update event - _events.fire { it.onSubscriptionUpdated(subscription) } + _events.fire { it.onSubscriptionsChanged() } } } @@ -150,8 +151,8 @@ internal class SubscriptionManager( * Called when a subscription model has been removed. The subscription list must be updated * to reflect the subscription removed. */ - override fun onRemoved(model: SubscriptionModel) { - val subscription = subscriptions.collection.firstOrNull { it.id.toString() == model.id } + override fun onModelRemoved(model: SubscriptionModel, tag: String) { + val subscription = subscriptions.collection.firstOrNull { it.id == model.id } if (subscription != null) { removeSubscriptionFromSubscriptionList(subscription) @@ -165,7 +166,7 @@ internal class SubscriptionManager( subscriptions.add(subscription) this.subscriptions = SubscriptionList(subscriptions) - _events.fire { it.onSubscriptionAdded(subscription) } + _events.fire { it.onSubscriptionsChanged() } } private fun removeSubscriptionFromSubscriptionList(subscription: ISubscription) { @@ -173,7 +174,7 @@ internal class SubscriptionManager( subscriptions.remove(subscription) this.subscriptions = SubscriptionList(subscriptions) - _events.fire { it.onSubscriptionRemoved(subscription) } + _events.fire { it.onSubscriptionsChanged() } } private fun createSubscriptionFromModel(subscriptionModel: SubscriptionModel): ISubscription { diff --git a/OneSignalSDK/onesignal/src/main/java/com/onesignal/core/internal/user/UserManager.kt b/OneSignalSDK/onesignal/src/main/java/com/onesignal/core/internal/user/UserManager.kt index e19b7ff99d..ba0f3153a6 100644 --- a/OneSignalSDK/onesignal/src/main/java/com/onesignal/core/internal/user/UserManager.kt +++ b/OneSignalSDK/onesignal/src/main/java/com/onesignal/core/internal/user/UserManager.kt @@ -41,10 +41,10 @@ internal open class UserManager( get() = _subscriptionManager.subscriptions private val _identityModel: IdentityModel - get() = _identityModelStore.get() + get() = _identityModelStore.model private val _propertiesModel: PropertiesModel - get() = _propertiesModelStore.get() + get() = _propertiesModelStore.model override fun addAlias(label: String, id: String): com.onesignal.core.user.IUserManager { Logging.log(LogLevel.DEBUG, "setAlias(label: $label, id: $id)") diff --git a/OneSignalSDK/onesignal/src/main/java/com/onesignal/iam/internal/IAMManager.kt b/OneSignalSDK/onesignal/src/main/java/com/onesignal/iam/internal/IAMManager.kt index 1b7b5ba205..766d4e3d83 100644 --- a/OneSignalSDK/onesignal/src/main/java/com/onesignal/iam/internal/IAMManager.kt +++ b/OneSignalSDK/onesignal/src/main/java/com/onesignal/iam/internal/IAMManager.kt @@ -6,6 +6,7 @@ import com.onesignal.core.debug.LogLevel import com.onesignal.core.internal.application.IApplicationService import com.onesignal.core.internal.backend.BackendException import com.onesignal.core.internal.common.AndroidUtils +import com.onesignal.core.internal.common.IDManager import com.onesignal.core.internal.common.JSONUtils import com.onesignal.core.internal.common.events.CallbackProducer import com.onesignal.core.internal.common.events.ICallbackProducer @@ -14,6 +15,7 @@ import com.onesignal.core.internal.influence.IInfluenceManager import com.onesignal.core.internal.language.ILanguageContext import com.onesignal.core.internal.logging.Logging import com.onesignal.core.internal.modeling.ISingletonModelStoreChangeHandler +import com.onesignal.core.internal.modeling.ModelChangedArgs import com.onesignal.core.internal.models.ConfigModel import com.onesignal.core.internal.models.ConfigModelStore import com.onesignal.core.internal.outcomes.IOutcomeEventsController @@ -23,9 +25,7 @@ import com.onesignal.core.internal.startup.IStartableService import com.onesignal.core.internal.time.ITime import com.onesignal.core.internal.user.ISubscriptionChangedHandler import com.onesignal.core.internal.user.ISubscriptionManager -import com.onesignal.core.internal.user.subscriptions.PushSubscription import com.onesignal.core.user.IUserManager -import com.onesignal.core.user.subscriptions.ISubscription import com.onesignal.iam.IIAMManager import com.onesignal.iam.IInAppMessageClickHandler import com.onesignal.iam.IInAppMessageLifecycleHandler @@ -147,8 +147,8 @@ internal class IAMManager( _messageClickCallback.set(handler) } - override fun onModelUpdated(model: ConfigModel, path: String, property: String, oldValue: Any?, newValue: Any?) { - if (property != ConfigModel::appId.name) { + override fun onModelUpdated(args: ModelChangedArgs, tag: String) { + if (args.property != ConfigModel::appId.name) { return } @@ -157,8 +157,14 @@ internal class IAMManager( } } - override fun onSubscriptionAdded(subscription: ISubscription) { - if (subscription !is PushSubscription) { + override fun onModelReplaced(model: ConfigModel, tag: String) { + suspendifyOnThread { + fetchMessages() + } + } + + override fun onSubscriptionsChanged() { + if (_subscriptionManager.subscriptions.push == null) { return } @@ -167,12 +173,6 @@ internal class IAMManager( } } - override fun onSubscriptionUpdated(subscription: ISubscription) { - // nothing to do when a subscription is updated. - } - - override fun onSubscriptionRemoved(subscription: ISubscription) { } - override fun onSessionStarted() { for (redisplayInAppMessage in _redisplayedInAppMessages) { redisplayInAppMessage.isDisplayedInSession = false @@ -188,16 +188,16 @@ internal class IAMManager( // called when a new push subscription is added, or the app id is updated, or a new session starts private suspend fun fetchMessages() { - val appId = _configModelStore.get().appId + val appId = _configModelStore.model.appId val subscriptionId = _subscriptionManager.subscriptions.push?.id - if (subscriptionId == null || appId.isEmpty()) { + if (subscriptionId == null || IDManager.isIdLocalOnly(subscriptionId) || appId.isEmpty()) { return } _fetchIAMMutex.withLock { val now = _time.currentTimeMillis - if (_lastTimeFetchedIAMs != null && (now - _lastTimeFetchedIAMs!!) < _configModelStore.get().fetchIAMMinInterval) { + if (_lastTimeFetchedIAMs != null && (now - _lastTimeFetchedIAMs!!) < _configModelStore.model.fetchIAMMinInterval) { return } @@ -462,7 +462,7 @@ internal class IAMManager( suspendifyOnThread { try { _backend.sendIAMImpression( - _configModelStore.get().appId, + _configModelStore.model.appId, _subscriptionManager.subscriptions.push?.id.toString(), variantId, message.messageId @@ -689,7 +689,7 @@ internal class IAMManager( try { _backend.sendIAMPageImpression( - _configModelStore.get().appId, + _configModelStore.model.appId, _subscriptionManager.subscriptions.push?.id.toString(), variantId, message.messageId, @@ -723,7 +723,7 @@ internal class IAMManager( try { _backend.sendIAMClick( - _configModelStore.get().appId, + _configModelStore.model.appId, _subscriptionManager.subscriptions.push?.id.toString(), variantId, message.messageId, @@ -751,8 +751,4 @@ internal class IAMManager( .setPositiveButton(android.R.string.ok) { _, _ -> suspendifyOnThread { showMultiplePrompts(inAppMessage, prompts) } } .show() } - - override fun onModelReplaced(model: ConfigModel) { - // TODO("Not yet implemented") - } } diff --git a/OneSignalSDK/onesignal/src/main/java/com/onesignal/iam/internal/display/impl/InAppDisplayer.kt b/OneSignalSDK/onesignal/src/main/java/com/onesignal/iam/internal/display/impl/InAppDisplayer.kt index fa0628f695..24f5215827 100644 --- a/OneSignalSDK/onesignal/src/main/java/com/onesignal/iam/internal/display/impl/InAppDisplayer.kt +++ b/OneSignalSDK/onesignal/src/main/java/com/onesignal/iam/internal/display/impl/InAppDisplayer.kt @@ -45,7 +45,7 @@ internal class InAppDisplayer( private var lastInstance: WebViewManager? = null override suspend fun displayMessage(message: InAppMessage): Boolean? { - var response = _backend.getIAMData(_configModelStore.get().appId, message.messageId, InAppHelper.variantIdForMessage(message, _languageContext)) + var response = _backend.getIAMData(_configModelStore.model.appId, message.messageId, InAppHelper.variantIdForMessage(message, _languageContext)) if (response.content != null) { message.displayDuration = response.content!!.displayDuration!! @@ -66,7 +66,7 @@ internal class InAppDisplayer( override suspend fun displayPreviewMessage(previewUUID: String): Boolean { val message = InAppMessage(true, _time) - val content = _backend.getIAMPreviewData(_configModelStore.get().appId, previewUUID) + val content = _backend.getIAMPreviewData(_configModelStore.model.appId, previewUUID) return if (content == null) { false diff --git a/OneSignalSDK/onesignal/src/main/java/com/onesignal/iam/internal/triggers/impl/TriggerController.kt b/OneSignalSDK/onesignal/src/main/java/com/onesignal/iam/internal/triggers/impl/TriggerController.kt index 0b8c72fbac..932e65dbce 100644 --- a/OneSignalSDK/onesignal/src/main/java/com/onesignal/iam/internal/triggers/impl/TriggerController.kt +++ b/OneSignalSDK/onesignal/src/main/java/com/onesignal/iam/internal/triggers/impl/TriggerController.kt @@ -2,6 +2,7 @@ package com.onesignal.iam.internal.triggers.impl import com.onesignal.core.internal.logging.Logging import com.onesignal.core.internal.modeling.IModelStoreChangeHandler +import com.onesignal.core.internal.modeling.ModelChangedArgs import com.onesignal.core.internal.models.TriggerModel import com.onesignal.core.internal.models.TriggerModelStore import com.onesignal.iam.internal.InAppMessage @@ -206,17 +207,18 @@ internal class TriggerController( return true } - override fun onAdded(model: TriggerModel) { + override fun onModelAdded(model: TriggerModel, tag: String) { addTriggers(model.key, model.value) _dynamicTriggerController.events.fire { it.onTriggerChanged(model.key) } } - override fun onUpdated(model: TriggerModel, path: String, property: String, oldValue: Any?, newValue: Any?) { + override fun onModelUpdated(args: ModelChangedArgs, tag: String) { + val model = args.model as TriggerModel addTriggers(model.key, model.value) _dynamicTriggerController.events.fire { it.onTriggerChanged(model.key) } } - override fun onRemoved(model: TriggerModel) { + override fun onModelRemoved(model: TriggerModel, tag: String) { removeTriggersForKeys(model.key) } diff --git a/OneSignalSDK/onesignal/src/main/java/com/onesignal/location/internal/LocationManager.kt b/OneSignalSDK/onesignal/src/main/java/com/onesignal/location/internal/LocationManager.kt index c2ea615a9a..01c5cb7872 100644 --- a/OneSignalSDK/onesignal/src/main/java/com/onesignal/location/internal/LocationManager.kt +++ b/OneSignalSDK/onesignal/src/main/java/com/onesignal/location/internal/LocationManager.kt @@ -12,6 +12,7 @@ import com.onesignal.location.internal.capture.ILocationCapturer import com.onesignal.location.internal.common.LocationConstants import com.onesignal.location.internal.common.LocationUtils import com.onesignal.location.internal.controller.ILocationController +import com.onesignal.location.internal.permissions.ILocationPermissionChangedHandler import com.onesignal.location.internal.permissions.LocationPermissionController internal class LocationManager( @@ -19,11 +20,12 @@ internal class LocationManager( private val _capturer: ILocationCapturer, private val _locationController: ILocationController, private val _locationPermissionController: LocationPermissionController -) : ILocationManager, IStartableService { +) : ILocationManager, IStartableService, ILocationPermissionChangedHandler { override var isLocationShared: Boolean = false override fun start() { + _locationPermissionController.subscribe(this) if (LocationUtils.hasLocationPermission(_applicationService.appContext)) { suspendifyOnThread { startGetLocation() @@ -31,6 +33,14 @@ internal class LocationManager( } } + override fun onLocationPermissionChanged(enabled: Boolean) { + if (enabled) { + suspendifyOnThread { + startGetLocation() + } + } + } + /** * This method handle location and permission location flows and border cases. * For each flow we need to trigger location prompts listener, @@ -123,17 +133,10 @@ internal class LocationManager( result = backgroundLocationPermissionLogic() } else { result = true + startGetLocation() } } - if (result) { - startGetLocation() - } - - // if result is null that means the user has gone to app settings and may or may not do - // something there. However when they come back the application will be brought into - // focus and our application lifecycle handler will pick up any change that could have - // occurred. return result } @@ -157,7 +160,7 @@ internal class LocationManager( private suspend fun startGetLocation() { Logging.debug("LocationManager.startGetLocation()") // with lastLocation: " + lastLocation) try { - if (!_locationController!!.start()) { + if (!_locationController.start()) { Logging.warn("LocationManager.startGetLocation: not possible, no location dependency found") } } catch (t: Throwable) { diff --git a/OneSignalSDK/onesignal/src/main/java/com/onesignal/location/internal/capture/impl/LocationCapturer.kt b/OneSignalSDK/onesignal/src/main/java/com/onesignal/location/internal/capture/impl/LocationCapturer.kt index c7311f8d28..ac6e813101 100644 --- a/OneSignalSDK/onesignal/src/main/java/com/onesignal/location/internal/capture/impl/LocationCapturer.kt +++ b/OneSignalSDK/onesignal/src/main/java/com/onesignal/location/internal/capture/impl/LocationCapturer.kt @@ -60,7 +60,7 @@ internal class LocationCapturer( point.log = location.longitude } - var userProperties = _propertiesModelStore.get() + var userProperties = _propertiesModelStore.model userProperties.locationLongitude = point.log userProperties.locationLatitude = point.lat userProperties.locationAccuracy = point.accuracy diff --git a/OneSignalSDK/onesignal/src/main/java/com/onesignal/location/internal/permissions/LocationPermissionController.kt b/OneSignalSDK/onesignal/src/main/java/com/onesignal/location/internal/permissions/LocationPermissionController.kt index 06286f0771..4a45c9e617 100644 --- a/OneSignalSDK/onesignal/src/main/java/com/onesignal/location/internal/permissions/LocationPermissionController.kt +++ b/OneSignalSDK/onesignal/src/main/java/com/onesignal/location/internal/permissions/LocationPermissionController.kt @@ -31,22 +31,30 @@ import com.onesignal.R import com.onesignal.core.internal.application.ApplicationLifecycleHandlerBase import com.onesignal.core.internal.application.IApplicationService import com.onesignal.core.internal.common.AndroidUtils +import com.onesignal.core.internal.common.events.EventProducer +import com.onesignal.core.internal.common.events.IEventNotifier import com.onesignal.core.internal.permissions.IRequestPermissionService import com.onesignal.core.internal.permissions.impl.AlertDialogPrepromptForAndroidSettings import com.onesignal.core.internal.startup.IStartableService import com.onesignal.onesignal.core.internal.common.suspend.WaiterWithValue +internal interface ILocationPermissionChangedHandler { + fun onLocationPermissionChanged(enabled: Boolean) +} + internal class LocationPermissionController( private val _application: IApplicationService, private val _requestPermission: IRequestPermissionService, private val _applicationService: IApplicationService ) : IRequestPermissionService.PermissionCallback, - IStartableService { + IStartableService, + IEventNotifier { companion object { private const val PERMISSION_TYPE = "LOCATION" } private val _waiter = WaiterWithValue() + private val _events = EventProducer() private var _currPermission: String = "" override fun start() { @@ -69,6 +77,7 @@ internal class LocationPermissionController( override fun onAccept() { _waiter.wake(true) + _events.fire { it.onLocationPermissionChanged(true) } } override fun onReject(fallbackToSettings: Boolean) { @@ -81,6 +90,7 @@ internal class LocationPermissionController( if (!fallbackShown) { _waiter.wake(false) + _events.fire { it.onLocationPermissionChanged(false) } } } @@ -97,16 +107,22 @@ internal class LocationPermissionController( override fun onFocus() { super.onFocus() _applicationService.removeApplicationLifecycleHandler(this) - _waiter.wake(AndroidUtils.hasPermission(_currPermission, true, _applicationService)) + val hasPermission = AndroidUtils.hasPermission(_currPermission, true, _applicationService) + _waiter.wake(hasPermission) + _events.fire { it.onLocationPermissionChanged(hasPermission) } } }) NavigateToAndroidSettingsForLocation.show(activity) } override fun onDecline() { _waiter.wake(false) + _events.fire { it.onLocationPermissionChanged(false) } } } ) return true } + + override fun subscribe(handler: ILocationPermissionChangedHandler) = _events.subscribe(handler) + override fun unsubscribe(handler: ILocationPermissionChangedHandler) = _events.subscribe(handler) } diff --git a/OneSignalSDK/onesignal/src/main/java/com/onesignal/notification/internal/NotificationsManager.kt b/OneSignalSDK/onesignal/src/main/java/com/onesignal/notification/internal/NotificationsManager.kt index d959717666..006a6bace5 100644 --- a/OneSignalSDK/onesignal/src/main/java/com/onesignal/notification/internal/NotificationsManager.kt +++ b/OneSignalSDK/onesignal/src/main/java/com/onesignal/notification/internal/NotificationsManager.kt @@ -4,6 +4,7 @@ import android.app.Activity import com.onesignal.core.internal.application.IApplicationService import com.onesignal.core.internal.backend.BackendException import com.onesignal.core.internal.common.events.EventProducer +import com.onesignal.core.internal.common.suspendifyOnMain import com.onesignal.core.internal.common.suspendifyOnThread import com.onesignal.core.internal.logging.Logging import com.onesignal.core.internal.models.ConfigModelStore @@ -18,12 +19,10 @@ import com.onesignal.notification.internal.common.GenerateNotificationOpenIntent import com.onesignal.notification.internal.common.NotificationHelper import com.onesignal.notification.internal.data.INotificationRepository import com.onesignal.notification.internal.lifecycle.INotificationLifecycleService +import com.onesignal.notification.internal.permissions.INotificationPermissionChangedHandler import com.onesignal.notification.internal.permissions.INotificationPermissionController import com.onesignal.notification.internal.restoration.INotificationRestoreWorkManager import com.onesignal.notification.internal.summary.INotificationSummaryManager -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.withContext -import kotlinx.coroutines.yield import org.json.JSONArray import org.json.JSONException import org.json.JSONObject @@ -51,7 +50,8 @@ internal class NotificationsManager( private val _summaryManager: INotificationSummaryManager ) : INotificationsManager, INotificationActivityOpener, - INotificationStateRefresher { + INotificationStateRefresher, + INotificationPermissionChangedHandler { override var permissionStatus: IPermissionState = PermissionState(false) override var unsubscribeWhenNotificationsAreDisabled: Boolean = false @@ -59,52 +59,42 @@ internal class NotificationsManager( private val _permissionChangedNotifier = EventProducer() init { + _notificationPermissionController.subscribe(this) suspendifyOnThread { _notificationDataController.deleteExpiredNotifications() } } + /** + * Called when app calls [requestPermission] and the user accepts/denies permission. + */ + override fun onNotificationPermissionChanged(enabled: Boolean) { + setPermissionStatusAndFire(enabled) + } + + /** + * Called when app has gained focus and the notification state should be refreshed. + */ override fun refreshNotificationState() { // ensure all notifications for this app have been restored to the notification panel _notificationRestoreWorkManager.beginEnqueueingWork(_applicationService.appContext, false) val isEnabled = NotificationHelper.areNotificationsEnabled(_applicationService.appContext) - - suspendifyOnThread { - setPermissionStatusAndFire(isEnabled) - } + setPermissionStatusAndFire(isEnabled) } override suspend fun requestPermission(): Boolean { Logging.debug("NotificationsManager.requestPermission()") - - // TODO: We do not yet handle the case where the activity is shown to the user, the application - // is killed, the app is re-opened (showing the permission activity), and the - // user makes a decision. Because the app is killed this flow is dead. - // NotificationPermissionController does still get the callback, the way it is structured, - // so we just need to figure out how to get it to tell us outside of us calling (weird). - val result = _notificationPermissionController.prompt(true) - - // if result is null that means the user has gone to app settings and may or may not do - // something there. However when they come back the application will be brought into - // focus and our application lifecycle handler will pick up any change that could have - // occurred. - setPermissionStatusAndFire(result) - - // yield to force a suspension. When there is no suspension and invoked via Java the - // continuation is never called (TODO: True?) - yield() - - return result + return _notificationPermissionController.prompt(true) } - private suspend fun setPermissionStatusAndFire(isEnabled: Boolean) { + private fun setPermissionStatusAndFire(isEnabled: Boolean) { val oldPermissionStatus = permissionStatus permissionStatus = PermissionState(isEnabled) if (oldPermissionStatus.notificationsEnabled != isEnabled) { // switch over to the main thread for the firing of the event - withContext(Dispatchers.Main) { + suspendifyOnMain { val changes = PermissionStateChanges(oldPermissionStatus, permissionStatus) _permissionChangedNotifier.fire { it.onPermissionChanged(changes) } } @@ -131,8 +121,7 @@ internal class NotificationsManager( val appId = if (json.has("app_id")) { json.getString("app_id") } else { - val config = _configModelStore.get() - config.appId ?: return JSONObject().put("error", "Missing app_id") + _configModelStore.model.appId } try { diff --git a/OneSignalSDK/onesignal/src/main/java/com/onesignal/notification/internal/data/impl/NotificationQueryHelper.kt b/OneSignalSDK/onesignal/src/main/java/com/onesignal/notification/internal/data/impl/NotificationQueryHelper.kt index df889ce5ea..9641d34eb0 100644 --- a/OneSignalSDK/onesignal/src/main/java/com/onesignal/notification/internal/data/impl/NotificationQueryHelper.kt +++ b/OneSignalSDK/onesignal/src/main/java/com/onesignal/notification/internal/data/impl/NotificationQueryHelper.kt @@ -19,7 +19,7 @@ internal class NotificationQueryHelper( OneSignalDbContract.NotificationTable.COLUMN_NAME_OPENED + " = 0 AND " + OneSignalDbContract.NotificationTable.COLUMN_NAME_IS_SUMMARY + " = 0" ) - val useTtl = _configModelStore.get().restoreTTLFilter + val useTtl = _configModelStore.model.restoreTTLFilter if (useTtl) { val expireTimeWhere = " AND " + OneSignalDbContract.NotificationTable.COLUMN_NAME_EXPIRE_TIME + " > " + currentTimeSec diff --git a/OneSignalSDK/onesignal/src/main/java/com/onesignal/notification/internal/generation/impl/NotificationGenerationProcessor.kt b/OneSignalSDK/onesignal/src/main/java/com/onesignal/notification/internal/generation/impl/NotificationGenerationProcessor.kt index ee8f4269bb..685892b942 100644 --- a/OneSignalSDK/onesignal/src/main/java/com/onesignal/notification/internal/generation/impl/NotificationGenerationProcessor.kt +++ b/OneSignalSDK/onesignal/src/main/java/com/onesignal/notification/internal/generation/impl/NotificationGenerationProcessor.kt @@ -163,7 +163,7 @@ internal class NotificationGenerationProcessor( // If available TTL times comes in seconds, by default is 3 days in seconds private fun isNotificationWithinTTL(notification: Notification): Boolean { - val useTtl = _configModelStore.get().restoreTTLFilter + val useTtl = _configModelStore.model.restoreTTLFilter if (!useTtl) return true val currentTimeInSeconds = _time.currentTimeMillis / 1000 val sentTime = notification.sentTime diff --git a/OneSignalSDK/onesignal/src/main/java/com/onesignal/notification/internal/listeners/ConfigModelStoreListener.kt b/OneSignalSDK/onesignal/src/main/java/com/onesignal/notification/internal/listeners/ConfigModelStoreListener.kt index a141aaba9e..afb70675d5 100644 --- a/OneSignalSDK/onesignal/src/main/java/com/onesignal/notification/internal/listeners/ConfigModelStoreListener.kt +++ b/OneSignalSDK/onesignal/src/main/java/com/onesignal/notification/internal/listeners/ConfigModelStoreListener.kt @@ -2,6 +2,8 @@ package com.onesignal.notification.internal.listeners import com.onesignal.core.internal.common.suspendifyOnThread import com.onesignal.core.internal.modeling.ISingletonModelStoreChangeHandler +import com.onesignal.core.internal.modeling.ModelChangeTags +import com.onesignal.core.internal.modeling.ModelChangedArgs import com.onesignal.core.internal.models.ConfigModel import com.onesignal.core.internal.models.ConfigModelStore import com.onesignal.core.internal.startup.IStartableService @@ -18,7 +20,13 @@ internal class ConfigModelStoreListener( _configModelStore.subscribe(this) } - override fun onModelReplaced(model: ConfigModel) { + override fun onModelReplaced(model: ConfigModel, tag: String) { + // we only need to do things when the config model was replaced + // via a hydration from the backend. + if (tag != ModelChangeTags.HYDRATE) { + return + } + // Refresh the notification permissions whenever we come back into focus _channelManager.processChannelList(model.notificationChannels) @@ -27,6 +35,6 @@ internal class ConfigModelStoreListener( } } - override fun onModelUpdated(model: ConfigModel, path: String, property: String, oldValue: Any?, newValue: Any?) { + override fun onModelUpdated(args: ModelChangedArgs, tag: String) { } } diff --git a/OneSignalSDK/onesignal/src/main/java/com/onesignal/notification/internal/listeners/NotificationListener.kt b/OneSignalSDK/onesignal/src/main/java/com/onesignal/notification/internal/listeners/NotificationListener.kt index fa6354419b..ac610452c0 100644 --- a/OneSignalSDK/onesignal/src/main/java/com/onesignal/notification/internal/listeners/NotificationListener.kt +++ b/OneSignalSDK/onesignal/src/main/java/com/onesignal/notification/internal/listeners/NotificationListener.kt @@ -67,7 +67,7 @@ internal class NotificationListener( data: JSONArray, notificationId: String ) { - val config = _configModelStore.get() + val config = _configModelStore.model val appId: String = config.appId ?: "" val subscriptionId: String = _subscriptionManager.subscriptions.push?.id.toString() val deviceType = _deviceService.deviceType diff --git a/OneSignalSDK/onesignal/src/main/java/com/onesignal/notification/internal/listeners/PushTokenListener.kt b/OneSignalSDK/onesignal/src/main/java/com/onesignal/notification/internal/listeners/PushTokenListener.kt index 9ee2768983..4a04225240 100644 --- a/OneSignalSDK/onesignal/src/main/java/com/onesignal/notification/internal/listeners/PushTokenListener.kt +++ b/OneSignalSDK/onesignal/src/main/java/com/onesignal/notification/internal/listeners/PushTokenListener.kt @@ -5,6 +5,7 @@ import com.onesignal.core.internal.user.ISubscriptionManager import com.onesignal.notification.internal.INotificationStateRefresher import com.onesignal.notification.internal.pushtoken.IPushTokenChangedHandler import com.onesignal.notification.internal.pushtoken.IPushTokenManager +import com.onesignal.notification.internal.registration.IPushRegistrator internal class PushTokenListener( private val _pushTokenManager: IPushTokenManager, @@ -16,12 +17,8 @@ internal class PushTokenListener( _pushTokenManager.subscribe(this) } - override fun onPushTokenChanged(pushToken: String?) { - if (pushToken == null || pushToken.isEmpty()) { - return - } - - _subscriptionManager.addOrUpdatePushSubscription(_pushTokenManager.pushToken) + override fun onPushTokenChanged(pushToken: String?, pushTokenStatus: IPushRegistrator.RegisterStatus) { + _subscriptionManager.addOrUpdatePushSubscription(pushToken, pushTokenStatus.value) _notificationStateRefresher.refreshNotificationState() } } diff --git a/OneSignalSDK/onesignal/src/main/java/com/onesignal/notification/internal/open/impl/NotificationOpenedProcessor.kt b/OneSignalSDK/onesignal/src/main/java/com/onesignal/notification/internal/open/impl/NotificationOpenedProcessor.kt index 323cba85f4..be63db8d8e 100644 --- a/OneSignalSDK/onesignal/src/main/java/com/onesignal/notification/internal/open/impl/NotificationOpenedProcessor.kt +++ b/OneSignalSDK/onesignal/src/main/java/com/onesignal/notification/internal/open/impl/NotificationOpenedProcessor.kt @@ -178,7 +178,7 @@ internal class NotificationOpenedProcessor( intent.getIntExtra(NotificationConstants.BUNDLE_KEY_ANDROID_NOTIFICATION_ID, 0), dismissed, summaryGroup, - _configModelStore.get().clearGroupOnSummaryClick + _configModelStore.model.clearGroupOnSummaryClick ) } diff --git a/OneSignalSDK/onesignal/src/main/java/com/onesignal/notification/internal/permissions/INotificationPermissionController.kt b/OneSignalSDK/onesignal/src/main/java/com/onesignal/notification/internal/permissions/INotificationPermissionController.kt index 975dacc7bd..ad489f98b2 100644 --- a/OneSignalSDK/onesignal/src/main/java/com/onesignal/notification/internal/permissions/INotificationPermissionController.kt +++ b/OneSignalSDK/onesignal/src/main/java/com/onesignal/notification/internal/permissions/INotificationPermissionController.kt @@ -27,7 +27,9 @@ package com.onesignal.notification.internal.permissions -internal interface INotificationPermissionController { +import com.onesignal.core.internal.common.events.IEventNotifier + +internal interface INotificationPermissionController : IEventNotifier { /** * Prompt the user for notification permission. Note it is possible the application * will be killed while the permission prompt is being displayed to the user. When the @@ -42,3 +44,7 @@ internal interface INotificationPermissionController { */ suspend fun prompt(fallbackToSettings: Boolean): Boolean } + +internal interface INotificationPermissionChangedHandler { + fun onNotificationPermissionChanged(enabled: Boolean) +} diff --git a/OneSignalSDK/onesignal/src/main/java/com/onesignal/notification/internal/permissions/impl/NotificationPermissionController.kt b/OneSignalSDK/onesignal/src/main/java/com/onesignal/notification/internal/permissions/impl/NotificationPermissionController.kt index 3b128c9d31..f44ede3c59 100644 --- a/OneSignalSDK/onesignal/src/main/java/com/onesignal/notification/internal/permissions/impl/NotificationPermissionController.kt +++ b/OneSignalSDK/onesignal/src/main/java/com/onesignal/notification/internal/permissions/impl/NotificationPermissionController.kt @@ -33,11 +33,14 @@ import com.onesignal.R import com.onesignal.core.internal.application.ApplicationLifecycleHandlerBase import com.onesignal.core.internal.application.IApplicationService import com.onesignal.core.internal.common.AndroidUtils +import com.onesignal.core.internal.common.events.EventProducer import com.onesignal.core.internal.permissions.IRequestPermissionService import com.onesignal.core.internal.permissions.impl.AlertDialogPrepromptForAndroidSettings import com.onesignal.notification.internal.common.NotificationHelper +import com.onesignal.notification.internal.permissions.INotificationPermissionChangedHandler import com.onesignal.notification.internal.permissions.INotificationPermissionController import com.onesignal.onesignal.core.internal.common.suspend.WaiterWithValue +import kotlinx.coroutines.yield internal class NotificationPermissionController( private val _application: IApplicationService, @@ -47,6 +50,7 @@ internal class NotificationPermissionController( INotificationPermissionController { private val _waiter = WaiterWithValue() + private val _events = EventProducer() init { _requestPermission.registerAsCallback(PERMISSION_TYPE, this) @@ -70,6 +74,10 @@ internal class NotificationPermissionController( * to notify of the status. */ override suspend fun prompt(fallbackToSettings: Boolean): Boolean { + // yield to force a suspension. When there is no suspension the continuation will + // never be called + yield() + if (notificationsEnabled()) { return true } @@ -92,8 +100,12 @@ internal class NotificationPermissionController( return _waiter.waitForWake() } + override fun subscribe(handler: INotificationPermissionChangedHandler) = _events.subscribe(handler) + override fun unsubscribe(handler: INotificationPermissionChangedHandler) = _events.subscribe(handler) + override fun onAccept() { _waiter.wake(true) + _events.fire { it.onNotificationPermissionChanged(true) } } override fun onReject(fallbackToSettings: Boolean) { @@ -106,6 +118,7 @@ internal class NotificationPermissionController( if (!fallbackShown) { _waiter.wake(false) + _events.fire { it.onNotificationPermissionChanged(false) } } } @@ -124,13 +137,16 @@ internal class NotificationPermissionController( override fun onFocus() { super.onFocus() _applicationService.removeApplicationLifecycleHandler(this) - _waiter.wake(AndroidUtils.hasPermission(ANDROID_PERMISSION_STRING, true, _applicationService)) + val hasPermission = AndroidUtils.hasPermission(ANDROID_PERMISSION_STRING, true, _applicationService) + _waiter.wake(hasPermission) + _events.fire { it.onNotificationPermissionChanged(hasPermission) } } }) NavigateToAndroidSettingsForNotifications.show(activity) } override fun onDecline() { _waiter.wake(false) + _events.fire { it.onNotificationPermissionChanged(false) } } } ) diff --git a/OneSignalSDK/onesignal/src/main/java/com/onesignal/notification/internal/pushtoken/IPushTokenManager.kt b/OneSignalSDK/onesignal/src/main/java/com/onesignal/notification/internal/pushtoken/IPushTokenManager.kt index 45a8f8cf38..b993b1eb4d 100644 --- a/OneSignalSDK/onesignal/src/main/java/com/onesignal/notification/internal/pushtoken/IPushTokenManager.kt +++ b/OneSignalSDK/onesignal/src/main/java/com/onesignal/notification/internal/pushtoken/IPushTokenManager.kt @@ -1,14 +1,15 @@ package com.onesignal.notification.internal.pushtoken import com.onesignal.core.internal.common.events.IEventNotifier +import com.onesignal.notification.internal.registration.IPushRegistrator internal interface IPushTokenManager : IEventNotifier { /** The push token for this device **/ val pushToken: String? - + val pushTokenStatus: IPushRegistrator.RegisterStatus suspend fun retrievePushToken() } internal interface IPushTokenChangedHandler { - fun onPushTokenChanged(pushToken: String?) + fun onPushTokenChanged(pushToken: String?, pushTokenStatus: IPushRegistrator.RegisterStatus) } diff --git a/OneSignalSDK/onesignal/src/main/java/com/onesignal/notification/internal/pushtoken/PushTokenManager.kt b/OneSignalSDK/onesignal/src/main/java/com/onesignal/notification/internal/pushtoken/PushTokenManager.kt index b63884aec4..d4ffb9ec17 100644 --- a/OneSignalSDK/onesignal/src/main/java/com/onesignal/notification/internal/pushtoken/PushTokenManager.kt +++ b/OneSignalSDK/onesignal/src/main/java/com/onesignal/notification/internal/pushtoken/PushTokenManager.kt @@ -1,6 +1,8 @@ package com.onesignal.notification.internal.pushtoken import com.onesignal.core.internal.common.events.EventProducer +import com.onesignal.core.internal.device.IDeviceService +import com.onesignal.core.internal.logging.Logging import com.onesignal.notification.internal.registration.IPushRegistrator /** @@ -8,38 +10,54 @@ import com.onesignal.notification.internal.registration.IPushRegistrator * on the current device (not the user). */ internal class PushTokenManager( - private val _pushRegistrator: IPushRegistrator + private val _pushRegistrator: IPushRegistrator, + private val _deviceService: IDeviceService ) : IPushTokenManager { private val _pushTokenChangedNotifier = EventProducer() + + override var pushTokenStatus: IPushRegistrator.RegisterStatus = IPushRegistrator.RegisterStatus.PUSH_STATUS_SUBSCRIBED override var pushToken: String? = null override suspend fun retrievePushToken() { - // if there's already a push token, nothing to do. - if (pushToken != null) { - return - } + when (_deviceService.androidSupportLibraryStatus) { + IDeviceService.AndroidSupportLibraryStatus.MISSING -> { + Logging.fatal("Could not find the Android Support Library. Please make sure it has been correctly added to your project.") + pushTokenStatus = IPushRegistrator.RegisterStatus.PUSH_STATUS_MISSING_ANDROID_SUPPORT_LIBRARY + } + IDeviceService.AndroidSupportLibraryStatus.OUTDATED -> { + Logging.fatal("The included Android Support Library is to old or incomplete. Please update to the 26.0.0 revision or newer.") + pushTokenStatus = IPushRegistrator.RegisterStatus.PUSH_STATUS_OUTDATED_ANDROID_SUPPORT_LIBRARY + } + else -> { + val registerResult = _pushRegistrator.registerForPush() - val registerResult = _pushRegistrator.registerForPush() - - if (registerResult.status < IPushRegistrator.RegisterStatus.PUSH_STATUS_SUBSCRIBED) { - // Only allow errored subscribableStatuses if we have never gotten a token. - // This ensures the device will not later be marked unsubscribed due to a - // any inconsistencies returned by Google Play services. - // Also do not override a config error status if we got a runtime error -// TODO if (OneSignalStateSynchronizer.getRegistrationId() == null && -// (OneSignal.subscribableStatus == UserState.PUSH_STATUS_SUBSCRIBED || -// OneSignal.pushStatusRuntimeError(OneSignal.subscribableStatus)) -// ) OneSignal.subscribableStatus = status - } -// TODO else if (OneSignal.pushStatusRuntimeError(OneSignal.subscribableStatus)) -// OneSignal.subscribableStatus = status + if (registerResult.status.value < IPushRegistrator.RegisterStatus.PUSH_STATUS_SUBSCRIBED.value) { + // Only allow errored statuses if we have never gotten a token. This ensures the + // device will not later be marked unsubscribed due to any inconsistencies returned + // by Google Play services. Also do not override a config error status if we got a + // runtime error + if (pushToken == null && + ( + pushTokenStatus == IPushRegistrator.RegisterStatus.PUSH_STATUS_SUBSCRIBED || + pushStatusRuntimeError(pushTokenStatus) + ) + ) { + pushTokenStatus = registerResult.status + } + } else if (pushStatusRuntimeError(pushTokenStatus)) { + pushTokenStatus = registerResult.status + } - // TODO: What if no result or the push registration fails? - if (registerResult.status == IPushRegistrator.RegisterStatus.PUSH_STATUS_SUBSCRIBED) { - pushToken = registerResult.id!! - _pushTokenChangedNotifier.fire { it.onPushTokenChanged(pushToken) } + pushToken = registerResult.id + } } + + _pushTokenChangedNotifier.fire { it.onPushTokenChanged(pushToken, pushTokenStatus) } + } + + private fun pushStatusRuntimeError(status: IPushRegistrator.RegisterStatus): Boolean { + return status.value < -6 } override fun subscribe(handler: IPushTokenChangedHandler) = _pushTokenChangedNotifier.subscribe(handler) diff --git a/OneSignalSDK/onesignal/src/main/java/com/onesignal/notification/internal/receivereceipt/impl/ReceiveReceiptWorkManager.kt b/OneSignalSDK/onesignal/src/main/java/com/onesignal/notification/internal/receivereceipt/impl/ReceiveReceiptWorkManager.kt index 122b93b6b4..7697d14000 100644 --- a/OneSignalSDK/onesignal/src/main/java/com/onesignal/notification/internal/receivereceipt/impl/ReceiveReceiptWorkManager.kt +++ b/OneSignalSDK/onesignal/src/main/java/com/onesignal/notification/internal/receivereceipt/impl/ReceiveReceiptWorkManager.kt @@ -29,12 +29,12 @@ internal class ReceiveReceiptWorkManager( private val maxDelay = 25 override fun enqueueReceiveReceipt(notificationId: String) { - if (!_configModelStore.get().receiveReceiptEnabled) { + if (!_configModelStore.model.receiveReceiptEnabled) { Logging.debug("sendReceiveReceipt disabled") return } - val appId: String = _configModelStore.get().appId + val appId: String = _configModelStore.model.appId val subscriptionId: String? = _subscriptionManager.subscriptions.push?.id if (subscriptionId == null || appId.isEmpty()) { diff --git a/OneSignalSDK/onesignal/src/main/java/com/onesignal/notification/internal/registration/impl/GooglePlayServicesUpgradePrompt.kt b/OneSignalSDK/onesignal/src/main/java/com/onesignal/notification/internal/registration/impl/GooglePlayServicesUpgradePrompt.kt index 3a6c5b171f..bcd0a1bd8d 100644 --- a/OneSignalSDK/onesignal/src/main/java/com/onesignal/notification/internal/registration/impl/GooglePlayServicesUpgradePrompt.kt +++ b/OneSignalSDK/onesignal/src/main/java/com/onesignal/notification/internal/registration/impl/GooglePlayServicesUpgradePrompt.kt @@ -43,7 +43,7 @@ internal class GooglePlayServicesUpgradePrompt( return } - if (!isGooglePlayStoreInstalled || _configModelStore.get().disableGMSMissingPrompt) { + if (!isGooglePlayStoreInstalled || _configModelStore.model.disableGMSMissingPrompt) { return } diff --git a/OneSignalSDK/onesignal/src/main/java/com/onesignal/notification/internal/registration/impl/PushRegistratorAbstractGoogle.kt b/OneSignalSDK/onesignal/src/main/java/com/onesignal/notification/internal/registration/impl/PushRegistratorAbstractGoogle.kt index eb22f0934f..6ade3fae39 100644 --- a/OneSignalSDK/onesignal/src/main/java/com/onesignal/notification/internal/registration/impl/PushRegistratorAbstractGoogle.kt +++ b/OneSignalSDK/onesignal/src/main/java/com/onesignal/notification/internal/registration/impl/PushRegistratorAbstractGoogle.kt @@ -50,14 +50,19 @@ internal abstract class PushRegistratorAbstractGoogle( abstract suspend fun getToken(senderId: String): String override suspend fun registerForPush(): IPushRegistrator.RegisterResult { - return if (!isValidProjectNumber(_configModelStore.get().googleProjectNumber)) { + if (!_deviceService.hasFCMLibrary) { + Logging.fatal("The Firebase FCM library is missing! Please make sure to include it in your project.") + return IPushRegistrator.RegisterResult(null, IPushRegistrator.RegisterStatus.PUSH_STATUS_MISSING_FIREBASE_FCM_LIBRARY) + } + + return if (!isValidProjectNumber(_configModelStore.model.googleProjectNumber)) { Logging.error("Missing Google Project number!\nPlease enter a Google Project number / Sender ID on under App Settings > Android > Configuration on the OneSignal dashboard.") IPushRegistrator.RegisterResult( null, IPushRegistrator.RegisterStatus.PUSH_STATUS_INVALID_FCM_SENDER_ID ) } else { - internalRegisterForPush(_configModelStore.get().googleProjectNumber!!) + internalRegisterForPush(_configModelStore.model.googleProjectNumber!!) } } diff --git a/OneSignalSDK/onesignal/src/main/java/com/onesignal/notification/internal/registration/impl/PushRegistratorFCM.kt b/OneSignalSDK/onesignal/src/main/java/com/onesignal/notification/internal/registration/impl/PushRegistratorFCM.kt index 59f8cc3dfa..5103c9fd27 100644 --- a/OneSignalSDK/onesignal/src/main/java/com/onesignal/notification/internal/registration/impl/PushRegistratorFCM.kt +++ b/OneSignalSDK/onesignal/src/main/java/com/onesignal/notification/internal/registration/impl/PushRegistratorFCM.kt @@ -48,7 +48,7 @@ internal class PushRegistratorFCM( get() = "FCM" init { - val fcpParams = _configModelStore.get().fcmParams + val fcpParams = _configModelStore.model.fcmParams this.projectId = fcpParams.projectId ?: FCM_DEFAULT_PROJECT_ID this.appId = fcpParams.appId ?: FCM_DEFAULT_APP_ID diff --git a/OneSignalSDK/onesignal/src/main/java/com/onesignal/notification/internal/summary/impl/NotificationSummaryManager.kt b/OneSignalSDK/onesignal/src/main/java/com/onesignal/notification/internal/summary/impl/NotificationSummaryManager.kt index 72438a4b8e..f40f236c87 100644 --- a/OneSignalSDK/onesignal/src/main/java/com/onesignal/notification/internal/summary/impl/NotificationSummaryManager.kt +++ b/OneSignalSDK/onesignal/src/main/java/com/onesignal/notification/internal/summary/impl/NotificationSummaryManager.kt @@ -91,7 +91,7 @@ internal class NotificationSummaryManager( // Obtain the most recent notification id val mostRecentId = _dataController.getAndroidIdForGroup(group, false) if (mostRecentId != null) { - val shouldDismissAll = _configModelStore.get().clearGroupOnSummaryClick + val shouldDismissAll = _configModelStore.model.clearGroupOnSummaryClick if (shouldDismissAll) { val groupId = if (group == NotificationHelper.grouplessSummaryKey) { // If the group is groupless, obtain the hardcoded groupless summary id