From a3d839f649e3d3380e99a57a23b33a6acf9bed87 Mon Sep 17 00:00:00 2001 From: Brian Smith Date: Fri, 6 Jan 2023 13:08:12 -0500 Subject: [PATCH] [User Model] Final touches * Add app_version to SubscriptionObject * Add migration guide * Make OneSignal.User pascal-case --- .../sdktest/model/MainActivityViewModel.java | 5 +- MIGRATION_GUIDE.md | 320 ++++++++++++++++++ .../src/main/java/com/onesignal/IOneSignal.kt | 6 +- .../src/main/java/com/onesignal/OneSignal.kt | 10 +- .../java/com/onesignal/common/AndroidUtils.kt | 11 + .../com/onesignal/internal/OneSignalImp.kt | 2 +- .../internal/backend/SubscriptionObject.kt | 3 +- .../internal/backend/impl/JSONConverter.kt | 4 +- .../executors/LoginUserOperationExecutor.kt | 7 +- .../SubscriptionOperationExecutor.kt | 7 +- 10 files changed, 357 insertions(+), 18 deletions(-) create mode 100644 MIGRATION_GUIDE.md diff --git a/Examples/OneSignalDemo/app/src/main/java/com/onesignal/sdktest/model/MainActivityViewModel.java b/Examples/OneSignalDemo/app/src/main/java/com/onesignal/sdktest/model/MainActivityViewModel.java index f3ee1e67e0..9366c824c0 100644 --- a/Examples/OneSignalDemo/app/src/main/java/com/onesignal/sdktest/model/MainActivityViewModel.java +++ b/Examples/OneSignalDemo/app/src/main/java/com/onesignal/sdktest/model/MainActivityViewModel.java @@ -58,7 +58,6 @@ import java.util.ArrayList; import java.util.HashMap; -import java.util.List; import java.util.Map; @RequiresApi(api = Build.VERSION_CODES.N) @@ -632,7 +631,7 @@ private void setupEmailRecyclerView() { LinearLayoutManager linearLayoutManager = new LinearLayoutManager(getActivity()); emailsRecyclerView.setLayoutManager(linearLayoutManager); emailsRecyclerViewAdapter = new SubscriptionRecyclerViewAdapter(context, emailArrayList, value -> { - String email = ((IEmailSubscription)value).getEmail(); + String email = ((DummySubscription)value).getId(); OneSignal.getUser().removeEmailSubscription(email); emailArrayList.remove(value); refreshEmailRecyclerView(); @@ -658,7 +657,7 @@ private void setupSMSRecyclerView() { LinearLayoutManager linearLayoutManager = new LinearLayoutManager(getActivity()); smssRecyclerView.setLayoutManager(linearLayoutManager); smssRecyclerViewAdapter = new SubscriptionRecyclerViewAdapter(context, smsArrayList, value -> { - String number = ((ISmsSubscription)value).getNumber(); + String number = ((DummySubscription)value).getId(); OneSignal.getUser().removeSmsSubscription(number); smsArrayList.remove(value); refreshSMSRecyclerView(); diff --git a/MIGRATION_GUIDE.md b/MIGRATION_GUIDE.md new file mode 100644 index 0000000000..0b443e4952 --- /dev/null +++ b/MIGRATION_GUIDE.md @@ -0,0 +1,320 @@ +# Android v5.0.0-alpha1 Migration Guide +In this release, we are making a significant shift from a device-centered model to a user-centered model. A user-centered model allows for more powerful omni-channel integrations within the OneSignal platform. + +This migration guide will walk you through the Android SDK v5.0.0 changes as a result of this shift. + +# Overview + +Under the user-centered model, the concept of a "player" is being replaced with three new concepts: users, subscriptions, and aliases. + + +## Users + +A user is a new concept which is meant to represent your end-user. A user has zero or more subscriptions and can be uniquely identified by one or more aliases. In addition to subscriptions a user can have **data tags** which allows for user attribution. + + +## Subscription + +A subscription refers to the method in which an end-user can receive various communications sent by OneSignal, including push notifications, in app messages, SMS, and email. In previous versions of the OneSignal platform, this was referred to as a “player”. A subscription is in fact identical to the legacy “player” concept. Each subscription has a **subscription_id** (previously, player_id) to uniquely identify that communication channel. + + +## Aliases + +Aliases are a concept evolved from [external user ids](https://documentation.onesignal.com/docs/external-user-ids) which allows the unique identification of a user within a OneSignal application. Aliases are a key-value pair made up of an **alias label** (the key) and an **alias id** (the value). The **alias label** can be thought of as a consistent keyword across all users, while the **alias id** is a value specific to each user for that particular label. The combined **alias label** and **alias id** provide uniqueness to successfully identify a user. + +OneSignal uses a built-in **alias label** called `external_id` which supports existing use of [external user ids](https://documentation.onesignal.com/docs/external-user-ids). `external_id` is also used as the identification method when a user identifies themselves to the OneSignal SDK via the `OneSignal.login` method. Multiple aliases can be created for each user to allow for your own application's unique identifier as well as identifiers from other integrated applications. + + +# Migration (v4 to v5) +## Build Changes + +Open your App’s `build.gradle (Module: app)` file, add (or update) the following to your `dependencies` section. + + + dependencies { + implementation 'com.onesignal:OneSignal:5.0.0-alpha1' + } + +The above statement will bring in the entire OneSignalSDK and is the desired state for most integrations. For greater flexibility you can alternatively specify individual modules that make up the full SDK. The possible modules are: + + `com.onesignal:core`: The required core module provided by the OneSignal SDK, this must be included. + `com.onesignal:notifications:` Include to bring in notification based functionality. + `com.onesignal:in-app-messages`: Include to bring in in app message functionality. + `com.onesignal:location`: Include to bring in location-based functionality. + + + dependencies { + implementation 'com.onesignal:core:5.0.0-alpha1' + implementation 'com.onesignal:in-app-messages:5.0.0-alpha1' + implementation 'com.onesignal:notifications:5.0.0-alpha1' + implementation 'com.onesignal:location:5.0.0-alpha1' + } + + + +## Code Modularization + +The OneSignal SDK has been updated to be more modular in nature. The SDK has been split into namespaces and functionality previously in the static `OneSignal` class has been moved to the appropriate namespace. Some namespaces are only available if you include the associated module in your build (for simplicity, including module `com.onesignal:OneSignal` will automatically bring in all modules). The namespaces, their containing modules, and how to access them in code are as follows: + +| Namespace | Module | Kotlin | Java | +| ------------- | ----------------------------- | ------------------------- | ------------------------------ | +| User | com.onesignal:core | `OneSignal.User` | `OneSignal.getUser()` | +| Session | com.onesignal:core | `OneSignal.session` | `OneSignal.getSession()` | +| Notifications | com.onesignal:notifications | `OneSignal.notifications` | `OneSignal.getNotifications()` | +| Location | com.onesignal:location | `OneSignal.location` | `OneSignal.getLocation()` | +| InAppMessages | com.onesignal:in-app-messages | `OneSignal.inAppMessages` | `OneSignal.getInAppMessages()` | +| Debug | com.onesignal:core | `OneSignal.debug` | `OneSignal.getDebug()` | + + + +## Initialization + +Initialization of the OneSignal SDK, although similar to past versions, has changed. The target OneSignal application (`appId`) is now provided as part of initialization and cannot be changed post-initialization. Previous versions of the OneSignal SDK had an explicit `setAppId` function, which is no longer available. A typical initialization now looks similar to below + +**Java** + + OneSignal.initWithContext(this, ONESIGNAL_APP_ID); + // requestPermission will show the native Android notification permission prompt. + // We recommend removing the following code and instead using an In-App Message to prompt for notification permission. + OneSignal.getNotifications().requestPermission(true, Continue.none()); + +**Kotlin** + + OneSignal.initWithContext(this, ONESIGNAL_APP_ID) + // requestPermission will show the native Android notification permission prompt. + // We recommend removing the following code and instead using an In-App Message to prompt for notification permission. + OneSignal.notifications.requestPermission(true) + +If your integration is not user-centric, there is no additional startup code required. A user is automatically created as part of the push subscription creation, both of which are only accessible from the current device and the OneSignal dashboard. + +If your integration is user-centric, or you want the ability to identify as the same user on multiple devices, the OneSignal SDK should be called once the user has been identified: + +**Java** + + OneSignal.login("USER_EXTERNAL_ID", Continue.none()); + +**Kotlin** + + OneSignal.login("USER_EXTERNAL_ID") + +The `login` method will associate the device’s push subscription to the user that can be identified via alias `externalId=USER_EXTERNAL_ID`. If a user with the provided `externalId` does not exist, one will be created. If a user does already exist, the user will be updated to include the current device’s push subscription. Note that a device's push subscription will always be transferred to the currently logged in user, as they represent the current owners of that push subscription. + +Once (or if) the user is no longer identifiable in your app (i.e. they logged out), the OneSignal SDK should be called: + +**Java** + + OneSignal.logout(Continue.none()); + +**Kotlin** + + OneSignal.logout() + +Logging out has the affect of reverting to a “device-scoped” user, which is the new owner of the device’s push subscription. + + +## Subscriptions + +In previous versions of the SDK there was a player that could have zero or one email address, and zero or one phone number for SMS. In the user-centered model there is a user with the current device’s **Push Subscription** along with the ability to have zero or **more** email subscriptions and zero or **more** SMS subscriptions. A user can also have zero or more push subscriptions, one push subscription for each device the user is logged into via the OneSignal SDK. + +**Push Subscription** +The current device’s push subscription can be retrieved via: + +**Java** + + IPushSubscription pushSubscription = OneSignal.getUser().getPushSubscription(); + +**Kotlin** + + val pushSubscription = OneSignal.User.pushSubscription + + +If at any point you want the user to stop receiving push notifications on the current device (regardless of Android permission status) you can use the push subscription to opt out: + +**Java** + + pushSubscription.optOut(); + +**Kotlin** + + pushSubscription.optOut() + + +To resume receiving of push notifications (driving the native permission prompt if OS permissions are not available), you can opt back in: + +**Java** + + pushSubscription.optIn() + +**Kotlin** + + pushSubscription.optIn() + + +**Email/SMS Subscriptions** +Email and/or SMS subscriptions can be added or removed via: + +**Java** + + // Add email subscription + OneSignal.getUser().addEmailSubscription("customer@company.com") + // Remove previously added email subscription + OneSignal.getUser().removeEmailSubscription("customer@company.com") + + // Add SMS subscription + OneSignal.getUser().addSMSSubscription("+15558675309") + // Remove previously added SMS subscription + OneSignal.getUser().removeSMSSubscription("+15558675309") + +**Kotlin** + + // Add email subscription + OneSignal.User.addEmailSubscription("customer@company.com") + // Remove previously added email subscription + OneSignal.User.removeEmailSubscription("customer@company.com") + + // Add SMS subscription + OneSignal.User.addSMSSubscription("+15558675309") + // Remove previously added SMS subscription + OneSignal.User.removeSMSSubscription("+15558675309") + + + +## Kotlin-Related Changes +**** +The OneSignal SDK has been rewritten in Kotlin. This is typically transparent to Java code with one notable exception. Kotlin provides [coroutines](https://kotlinlang.org/docs/coroutines-overview.html) for asynchronous/long running functions, allowing the caller to regain control once the long running function has completed. Coroutines are functions with the `suspend` modifier, indicating they will suspend the current execution until the underlying function has completed. + +In Java this is surfaced on a method via an additional parameter to the method of type `Continuation`. The `Continuation` is a callback mechanism which allows a Java function to gain control when execution has resumed. If this concept is newer to your application codebase, OneSignal provides an optional java helper class to facilitate the callback model. Method `com.onesignal.Continue.none()` can be used to indicate no callback is necessary: + + + OneSignal.getNotifications().requestPermission(Continue.none()); + +`com.onesignal.Continue.with()` can be used to create a callback lambda expression, which is passed a `ContinueResult` containing information on the success of the underlying coroutine, it's return data, and/or the exception that was thrown: + + + OneSignal.getNotifications().requestPermission(Continue.with(r -> { + if (r.isSuccess()) { + if (r.getData()) { + // code to execute once requestPermission has completed successfully and the user has accepted permission. + } + else { + // code to execute once requestPermission has completed successfully but the user has rejected permission. + } + } + else { + // code to execute once requestPermission has completed unsuccessfully, `r.getThrowable()` might have more information as to the reason for failure. + } + })); + + + +## API Reference + +Below is a comprehensive reference to the v5.0.0 OneSignal SDK. + +**OneSignal** +The SDK is still accessible via a `OneSignal` static class, it provides access to higher level functionality and is a gateway to each subspace of the SDK. + +| **Kotlin** | **Java** | **Description** | +| ----------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `var requiresPrivacyConsent: Boolean` | `boolean getRequiresPrivacyConsent()`
`void setRequiresPrivacyConsent(boolean value)` | *Determines whether a user must consent to privacy prior to their user data being sent up to OneSignal. This should be set to `true` prior to the invocation of [initWithContext] to ensure compliance.* | +| `var privacyConsent: Boolean` | `boolean getPrivacyConsent()`
`void setPrivacyConsent(boolean value)` | *Indicates whether privacy consent has been granted. This field is only relevant when the application has opted into data privacy protections. See [requiresPrivacyConsent].* | +| `fun initWithContext(context: Context, appId: String)` | `void initWithContext(Context context, String appId)` | *Initialize the OneSignal SDK. This should be called during startup of the application.* | +| `fun suspend login(externalId: String, jwtBearerToken: String? = null)` | `void login(String externalId, Continuation completion)`
`void login(String externalId, String jwtBearerToken, Continuation completion)` | *Login to OneSignal under the user identified by the [externalId] provided. The act of logging a user into the OneSignal SDK will switch the [user] context to that specific user.*

- *If the [externalId] exists the user will be retrieved and the context set from that user information. If operations have already been performed under a guest user, they* ***will not*** *be applied to the now logged in user (they will be lost).*
- *If the [externalId] does not exist the user will be created and the context set from the current local state. If operations have already been performed under a guest user those operations* ***will*** *be applied to the newly created user.*

***Push Notifications and In App Messaging***
*Logging in a new user will automatically transfer push notification and in app messaging subscriptions from the current user (if there is one) to the newly logged in user. This is because both Push and IAM are owned by the device.* | +| `fun suspend logout()` | `void logout(Continuation completion)` | *Logout the user previously logged in via [login]. The [user] property now references a new device-scoped user. A device-scoped user has no user identity that can later be retrieved, except through this device as long as the app remains installed and the app data is not cleared.* | + + +**User Namespace** +The user name space is accessible via `OneSignal.User` (in Kotlin) or `OneSignal.getUser()` (in Java) and provides access to user-scoped functionality. + +| **Kotlin** | **Java** | **Description** | +| ----------------------------------------------------------------------------- | ----------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `var language: Boolean` | `String getLanguage()`
`void setLanguage(String value)` | *The 2-character language either as a detected language or explicitly set for this user.* | +| `val pushSubscription: IPushSubscription` | `IPushSubscription getPushSubscription()` | *The push subscription associated to the current user.* | +| `fun pushSubscription.addChangeHandler(handler: ISubscriptionChangedHandler)` | `void pushSubscription.addChangeHandler(ISubscriptionChangedHandler handler)` | *Adds a change handler that will run whenever the push subscription has been changed.* | +| `fun addAlias(label: String, id: String)` | `void addAlias(String label, String id)` | *Set an alias for the current user. If this alias already exists it will be overwritten.* | +| `fun addAliases(aliases: Map)` | `void addAliases(Map aliases)` | S*et aliases for the current user. If any alias already exists it will be overwritten.* | +| `fun removeAlias(label: String)` | `void removeAlias(String label)` | *Remove an alias from the current user.* | +| `fun addEmailSubscription(email: String)` | `void addEmailSubscription(String email)` | *Add a new email subscription to the current user.* | +| `fun removeEmailSubscription(email: String)` | `void removeEmailSubscription(String email)` | *Remove an email subscription from the current user.* | +| `fun addSmsSubscription(sms: String)` | `void addSmsSubscription(String sms)` | *Add a new SMS subscription to the current user.* | +| `fun removeSmsSubscription(sms: String)` | `void removeSmsSubscription(String sms)` | *Remove an SMS subscription from the current user.* | +| `fun addTag(key: String, value: String)` | `void addTag(String key, String value)` | *Add a tag for the current user. Tags are key:value pairs used as building blocks for targeting specific users and/or personalizing messages. If the tag key already exists, it will be replaced with the value provided here.* | +| `fun addTags(tags: Map)` | `void addTags(Map tags)` | *Add multiple tags for the current user. Tags are key:value pairs used as building blocks for targeting specific users and/or personalizing messages. If the tag key already exists, it will be replaced with the value provided here.* | +| `fun removeTag(key: String)` | `void removeTag(String key)` | *Remove the data tag with the provided key from the current user.* | +| `fun removeTags(keys: Collection)` | `void removeTags(Collection keys)` | *Remove multiple tags from the current user.* | + + +**Session Namespace** +The session namespace is accessible via `OneSignal.session` (in Kotlin) or `OneSignal.getSession()` (in Java) and provides access to session-scoped functionality. + +| **Kotlin** | **Java** | **Description** | +| ----------------------------------------------------- | ---------------------------------------------------- | ---------------------------------------------------------------------------------------- | +| `fun addOutcome(name: String)` | `void addOutcome(String name)` | *Add an outcome with the provided name, captured against the current session.* | +| `fun addUniqueOutcome(name: String)` | `void addUniqueOutcome(String name)` | *Add a unique outcome with the provided name, captured against the current session.* | +| `fun addOutcomeWithValue(name: String, value: Float)` | `void addOutcomeWithValue(String name, float value)` | *Add an outcome with the provided name and value, captured against the current session.* | + + +**Notifications Namespace** +The notification namespace is accessible via `OneSignal.notifications` (in Kotlin) or `OneSignal.getNotifications()` (in Java) and provides access to notification-scoped functionality. + +| **Kotlin** | **Java** | **Description** | +| ---------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `var permission: Boolean` | `boolean getPermission()`
`void setPermission(boolean value)` | *Whether this app has push notification permission.* | +| `fun suspend requestPermission(fallbackToSettings: Boolean): Boolean` | `void requestPermission(boolean fallbackToSettings, Continuation completion)` | *Prompt the user for permission to push notifications. This will display the native OS prompt to request push notification permission. If the user enables, a push subscription to this device will be automatically added to the user.* | +| `fun suspend removeNotification(id: Int)` | `void removeNotification(int id, Continuation completion)` | *Cancels a single OneSignal notification based on its Android notification integer ID. Use instead of Android's [android.app.NotificationManager.cancel], otherwise the notification will be restored when your app is restarted.* | +| `fun suspend removeGroupedNotifications(group: String)` | `void removeGroupedNotifications(String group, Continuation completion)` | *Cancels a group of OneSignal notifications with the provided group key. Grouping notifications is a OneSignal concept, there is no [android.app.NotificationManager] equivalent.* | +| `fun suspend clearAllNotifications()` | `void clearAllNotifications(Continuation completion)` | *Removes all OneSignal notifications from the Notification Shade. If you just use [android.app.NotificationManager.cancelAll], OneSignal notifications will be restored when your app is restarted.* | +| `fun addPermissionChangedHandler(handler: IPermissionChangedHandler)` | `void addPermissionChangedHandler(IPermissionChangedHandler handler)` | *The [IPermissionChangedHandler.onPermissionChanged] method will be fired on the passed-in object when a notification permission setting changes. This happens when the user enables or disables notifications for your app from the system settings outside of your app. Disable detection is supported on Android 4.4+* | +| `fun removePermissionChangedHandler(handler: IPermissionChangedHandler)` | `void removePermissionChangedHandler(IPermissionChangedHandler handler)` | *Remove a push permission handler that has been previously added.* | +| `fun setNotificationWillShowInForegroundHandler(handler: INotificationWillShowInForegroundHandler?)` | `void setNotificationWillShowInForegroundHandler(INotificationWillShowInForegroundHandler? handler)` | *Sets the handler to run before displaying a notification while the app is in focus. Use this handler to read notification data or decide if the notification should show or not.*

***Note:*** *this runs after the Notification Service Extension [IRemoteNotificationReceivedHandler] has been called (if one exists), which has the following differences:*


1. *The [IRemoteNotificationReceivedHandler] is configured within your `AndroidManifest.xml`.*
2. *The [IRemoteNotificationReceivedHandler] will be called regardless of the state of your app, while [INotificationWillShowInForegroundHandler] is *only* called when your app is in focus.*
3. *The [IRemoteNotificationReceivedHandler] can make changes to the notification, while [INotificationWillShowInForegroundHandler] can only indicate not to show it.* | +| `fun setNotificationClickHandler(handler: INotificationClickHandler?)` | `void setNotificationClickHandler(INotificationClickHandler? handler)` | *Sets a handler that will run whenever a notification is clicked on by the user.* | + + +**Location Namespace** +The location namespace is accessible via `OneSignal.location` (in Kotlin) or `OneSignal.getLocation()` (in Java) and provide access to location-scoped functionality. + +| **Kotlin** | **Java** | **Description** | +| --------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `var isShared: Boolean` | `boolean isShared()`
`void setShared(boolean value)` | *Whether location is currently shared with OneSignal.* | +| `fun suspend requestPermission(fallbackToSettings: Boolean): Boolean` | `void requestPermission(boolean fallbackToSettings, Continuation completion)` | *Use this method to manually prompt the user for location permissions. This allows for geotagging so you send notifications to users based on location.* | + + +**InAppMessages Namespace** +The In App Messages namespace is accessible via `OneSignal.inAppMessages` (in Kotlin) or `OneSignal.getInAppMessages()` (in Java) and provide access to in app messages-scoped functionality. + +| **Kotlin** | **Java** | **Description** | +| ------------------------------------------------------------------------------ | ------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `var paused: Boolean` | `boolean getPaused()`
`void setPaused(boolean value)` | *Whether the In-app messaging is currently paused. When set to `true` no IAM will be presented to the user regardless of whether they qualify for them. When set to 'false` any IAMs the user qualifies for will be presented to the user at the appropriate time.* | +| `fun addTrigger(key: String, value: Any)` | `void addTrigger(String key, Object value)` | *Add a trigger for the current user. Triggers are currently explicitly used to determine whether a specific IAM should be displayed to the user. See \[Triggers | OneSignal\](https://documentation.onesignal.com/docs/iam-triggers).*

*If the trigger key already exists, it will be replaced with the value provided here. Note that triggers are not persisted to the backend. They only exist on the local device and are applicable to the current user.* | +| `fun addTriggers(triggers: Map)` | `void addTriggers(Map triggers)` | *Add multiple triggers for the current user. Triggers are currently explicitly used to determine whether a specific IAM should be displayed to the user. See \[Triggers | OneSignal\](https://documentation.onesignal.com/docs/iam-triggers).*

*If the trigger key already exists, it will be replaced with the value provided here. Note that triggers are not persisted to the backend. They only exist on the local device and are applicable to the current user.* | +| `fun removeTrigger(key: String)` | `void removeTrigger(String key)` | *Remove the trigger with the provided key from the current user.* | +| `fun removeTriggers(keys: Collection)` | `void removeTriggers(Collection keys)` | *Remove multiple triggers from the current user.* | +| `fun clearTriggers()` | `void clearTriggers()` | *Clear all triggers from the current user.* | +| `fun setInAppMessageLifecycleHandler(handler: IInAppMessageLifecycleHandler?)` | `void setInAppMessageLifecycleHandler(IInAppMessageLifecycleHandler? handler)` | *Set the IAM lifecycle handler.* | +| `fun setInAppMessageClickHandler(handler: IInAppMessageClickHandler?)` | `void setInAppMessageClickHandler(IInAppMessageClickHandler? handler)` | *Set the IAM click handler.* | + + +**Debug Namespace** +The debug namespace is accessible via `OneSignal.debug` (in Kotlin) or `OneSignal.getDebug()` (in Java) and provide access to debug-scoped functionality. + +| **Kotlin** | **Java** | **Description** | +| -------------------------- | ------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------- | +| `var logLevel: LogLevel` | `LogLevel getLogLevel()`
`void setLogLevel(LogLevel value)` | *The log level the OneSignal SDK should be writing to the Android log. Defaults to [LogLevel.WARN].* | +| `var alertLevel: LogLevel` | `LogLevel getAlertLevel()`
`void setAlertLevel(LogLevel value)` | *The log level the OneSignal SDK should be showing as a modal. Defaults to [LogLevel.NONE].* | + + + + + +# Limitations +- Recommend using only in development and staging environments for Alpha releases. +- Aliases will be available in a future release +- Outcomes will be available in a future release +- Users are deleted when the last subscription is removed + +# Known issues +- User properties may not update when Subscriptions are transferred + - Please report any issues you find with this +- Identity Verification + - We will be introducing JWT in follow up Alpha or Beta release +- Extra disabled subscriptions are created when switching Users in the SDK. diff --git a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/IOneSignal.kt b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/IOneSignal.kt index ace557912b..48622c0f23 100644 --- a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/IOneSignal.kt +++ b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/IOneSignal.kt @@ -23,7 +23,7 @@ interface IOneSignal { * The user manager for accessing user-scoped * management. */ - val user: IUserManager + val User: IUserManager /** * The session manager for accessing session-scoped management. @@ -85,7 +85,7 @@ interface IOneSignal { /** * Login to OneSignal under the user identified by the [externalId] provided. The act of - * logging a user into the OneSignal SDK will switch the [user] context to that specific user. + * logging a user into the OneSignal SDK will switch the [User] context to that specific user. * * * If the [externalId] exists the user will be retrieved and the context set from that * user information. If operations have already been performed under a guest user, they @@ -108,7 +108,7 @@ interface IOneSignal { suspend fun login(externalId: String) = login(externalId, null) /** - * Logout the user previously logged in via [login]. The [user] property now references + * Logout the user previously logged in via [login]. The [User] property now references * a new device-scoped user. A device-scoped user has no user identity that can later * be retrieved, except through this device as long as the app remains installed and the app * data is not cleared. diff --git a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/OneSignal.kt b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/OneSignal.kt index 8806f1df4f..94eaa92ccf 100644 --- a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/OneSignal.kt +++ b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/OneSignal.kt @@ -42,8 +42,8 @@ object OneSignal { * called. */ @JvmStatic - val user: IUserManager - get() = oneSignal.user + val User: IUserManager + get() = oneSignal.User /** * The session manager for accessing session-scoped management. Initialized only after [initWithContext] @@ -128,7 +128,7 @@ object OneSignal { /** * Login to OneSignal under the user identified by the [externalId] provided. The act of - * logging a user into the OneSignal SDK will switch the [user] context to that specific user. + * logging a user into the OneSignal SDK will switch the [User] context to that specific user. * * * If the [externalId] exists the user will be retrieved and the context set from that * user information. If operations have already been performed under a guest user, they @@ -149,7 +149,7 @@ object OneSignal { /** * Login to OneSignal under the user identified by the [externalId] provided. The act of - * logging a user into the OneSignal SDK will switch the [user] context to that specific user. + * logging a user into the OneSignal SDK will switch the [User] context to that specific user. * * * If the [externalId] exists the user will be retrieved and the context set from that * user information. If operations have already been performed under a guest user, they @@ -172,7 +172,7 @@ object OneSignal { suspend fun login(externalId: String, jwtBearerToken: String? = null) = oneSignal.login(externalId, jwtBearerToken) /** - * Logout the user previously logged in via [login]. The [user] property now references + * Logout the user previously logged in via [login]. The [User] property now references * a new device-scoped user. A device-scoped user has no user identity that can later * be retrieved, except through this device as long as the app remains installed and the app * data is not cleared. diff --git a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/common/AndroidUtils.kt b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/common/AndroidUtils.kt index c133080e87..baf5a6db49 100644 --- a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/common/AndroidUtils.kt +++ b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/common/AndroidUtils.kt @@ -60,6 +60,17 @@ object AndroidUtils { return hasFlag } + fun getAppVersion(context: Context): String? { + val appVersion: Int? = + try { + context.packageManager.getPackageInfo(context.packageName, 0).versionCode + } catch (e: PackageManager.NameNotFoundException) { + null + } + + return appVersion?.toString() + } + fun getManifestMeta(context: Context, metaName: String?): String? { val bundle = getManifestMetaBundle(context) return bundle?.getString(metaName) diff --git a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/internal/OneSignalImp.kt b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/internal/OneSignalImp.kt index 501faca671..9bc9b0fe52 100644 --- a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/internal/OneSignalImp.kt +++ b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/internal/OneSignalImp.kt @@ -75,7 +75,7 @@ internal class OneSignalImp : IOneSignal, IServiceProvider { override val notifications: INotificationsManager get() = if (isInitialized) _notifications!! else throw Exception("Must call 'initWithContext' before use") override val location: ILocationManager get() = if (isInitialized) _location!! else throw Exception("Must call 'initWithContext' before use") override val inAppMessages: IInAppMessagesManager get() = if (isInitialized) _iam!! else throw Exception("Must call 'initWithContext' before use") - override val user: IUserManager get() = if (isInitialized) _user!! else throw Exception("Must call 'initWithContext' before use") + override val User: IUserManager get() = if (isInitialized) _user!! else throw Exception("Must call 'initWithContext' before use") // Services required by this class private var _user: IUserManager? = null diff --git a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/backend/SubscriptionObject.kt b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/backend/SubscriptionObject.kt index 7252683531..27f7dd834f 100644 --- a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/backend/SubscriptionObject.kt +++ b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/backend/SubscriptionObject.kt @@ -11,5 +11,6 @@ class SubscriptionObject( val deviceOS: String? = null, val rooted: Boolean? = null, val netType: Int? = null, - val carrier: String? = null + val carrier: String? = null, + val appVersion: String? = null ) diff --git a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/backend/impl/JSONConverter.kt b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/backend/impl/JSONConverter.kt index 9ee4783432..fbc64d079f 100644 --- a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/backend/impl/JSONConverter.kt +++ b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/backend/impl/JSONConverter.kt @@ -45,7 +45,8 @@ object JSONConverter { it.safeString("device_model"), it.safeString("device_os"), it.safeBool("rooted"), - it.safeInt("test_type"), + it.safeInt("net_type"), + it.safeString("carrier"), it.safeString("app_version") ) } @@ -100,5 +101,6 @@ object JSONConverter { .putSafe("rooted", subscription.rooted) .putSafe("net_type", subscription.netType) .putSafe("carrier", subscription.carrier) + .putSafe("app_version", subscription.appVersion) } } diff --git a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/operations/impl/executors/LoginUserOperationExecutor.kt b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/operations/impl/executors/LoginUserOperationExecutor.kt index 601dfdf092..e64dd38a8e 100644 --- a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/operations/impl/executors/LoginUserOperationExecutor.kt +++ b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/operations/impl/executors/LoginUserOperationExecutor.kt @@ -1,6 +1,7 @@ package com.onesignal.user.internal.operations.impl.executors import android.os.Build +import com.onesignal.common.AndroidUtils import com.onesignal.common.DeviceUtils import com.onesignal.common.NetworkUtils import com.onesignal.common.OneSignalUtils @@ -209,7 +210,8 @@ internal class LoginUserOperationExecutor( Build.VERSION.RELEASE, RootToolsInternalMethods.isRooted, DeviceUtils.getNetType(_application.appContext), - DeviceUtils.getCarrierName(_application.appContext) + DeviceUtils.getCarrierName(_application.appContext), + AndroidUtils.getAppVersion(_application.appContext) ) return mutableSubscriptions @@ -229,7 +231,8 @@ internal class LoginUserOperationExecutor( subscriptions[operation.subscriptionId]!!.deviceOS, subscriptions[operation.subscriptionId]!!.rooted, subscriptions[operation.subscriptionId]!!.netType, - subscriptions[operation.subscriptionId]!!.carrier + subscriptions[operation.subscriptionId]!!.carrier, + subscriptions[operation.subscriptionId]!!.appVersion ) } // TODO: Is it possible for the Create to be after the Update? diff --git a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/operations/impl/executors/SubscriptionOperationExecutor.kt b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/operations/impl/executors/SubscriptionOperationExecutor.kt index 2829896554..c61a4289d2 100644 --- a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/operations/impl/executors/SubscriptionOperationExecutor.kt +++ b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/operations/impl/executors/SubscriptionOperationExecutor.kt @@ -1,6 +1,7 @@ package com.onesignal.user.internal.operations.impl.executors import android.os.Build +import com.onesignal.common.AndroidUtils import com.onesignal.common.DeviceUtils import com.onesignal.common.NetworkUtils import com.onesignal.common.OneSignalUtils @@ -82,7 +83,8 @@ internal class SubscriptionOperationExecutor( Build.VERSION.RELEASE, RootToolsInternalMethods.isRooted, DeviceUtils.getNetType(_applicationService.appContext), - DeviceUtils.getCarrierName(_applicationService.appContext) + DeviceUtils.getCarrierName(_applicationService.appContext), + AndroidUtils.getAppVersion(_applicationService.appContext) ) val backendSubscriptionId = _subscriptionBackend.createSubscription( @@ -134,7 +136,8 @@ internal class SubscriptionOperationExecutor( Build.VERSION.RELEASE, RootToolsInternalMethods.isRooted, DeviceUtils.getNetType(_applicationService.appContext), - DeviceUtils.getCarrierName(_applicationService.appContext) + DeviceUtils.getCarrierName(_applicationService.appContext), + AndroidUtils.getAppVersion(_applicationService.appContext) ) _subscriptionBackend.updateSubscription(lastOperation.appId, lastOperation.subscriptionId, subscription)