diff --git a/snowplow-demo-app/src/main/java/com/snowplowanalytics/snowplowtrackerdemo/Demo.java b/snowplow-demo-app/src/main/java/com/snowplowanalytics/snowplowtrackerdemo/Demo.java index 3bae3b0a7..1cb3a6820 100644 --- a/snowplow-demo-app/src/main/java/com/snowplowanalytics/snowplowtrackerdemo/Demo.java +++ b/snowplow-demo-app/src/main/java/com/snowplowanalytics/snowplowtrackerdemo/Demo.java @@ -299,7 +299,7 @@ private void trackEvents() { return; } TrackerEvents.trackAll(tracker); - eventsCreated += 10; + eventsCreated += 11; final String made = "Made: " + eventsCreated; runOnUiThread(() -> _eventsCreated.setText(made)); } diff --git a/snowplow-demo-app/src/main/java/com/snowplowanalytics/snowplowtrackerdemo/utils/TrackerEvents.java b/snowplow-demo-app/src/main/java/com/snowplowanalytics/snowplowtrackerdemo/utils/TrackerEvents.java index 4faddb922..08b78d6fe 100644 --- a/snowplow-demo-app/src/main/java/com/snowplowanalytics/snowplowtrackerdemo/utils/TrackerEvents.java +++ b/snowplow-demo-app/src/main/java/com/snowplowanalytics/snowplowtrackerdemo/utils/TrackerEvents.java @@ -18,6 +18,8 @@ import com.snowplowanalytics.snowplow.controller.TrackerController; import com.snowplowanalytics.snowplow.event.AbstractPrimitive; import com.snowplowanalytics.snowplow.event.DeepLinkReceived; +import com.snowplowanalytics.snowplow.event.MessageNotification; +import com.snowplowanalytics.snowplow.event.MessageNotificationTrigger; import com.snowplowanalytics.snowplow.event.SelfDescribing; import com.snowplowanalytics.snowplow.event.ConsentDocument; import com.snowplowanalytics.snowplow.event.ConsentGranted; @@ -30,6 +32,7 @@ import com.snowplowanalytics.snowplow.event.Timing; import com.snowplowanalytics.snowplow.payload.SelfDescribingJson; +import java.util.Arrays; import java.util.HashMap; import java.util.LinkedList; import java.util.List; @@ -52,6 +55,7 @@ public static void trackAll(@NonNull TrackerController tracker) { trackEcommerceEvent(tracker); trackConsentGranted(tracker); trackConsentWithdrawn(tracker); + trackMessageNotification(tracker); } private static void trackDeepLink(TrackerController tracker) { @@ -140,4 +144,18 @@ private static void trackConsentWithdrawn(TrackerController tracker) { .build(); tracker.track(event); } + + private static void trackMessageNotification(TrackerController tracker) { + MessageNotification event = new MessageNotification("title", "body", MessageNotificationTrigger.push) + .notificationTimestamp("2020-12-31T15:59:60-08:00") + .category("category") + .action("action") + .bodyLocKey("loc key") + .bodyLocArgs(Arrays.asList("loc arg1", "loc arg2")) + .sound("chime.mp3") + .notificationCount(9) + .category("category1"); + + tracker.track(event); + } } diff --git a/snowplow-tracker/src/androidTest/java/com/snowplowanalytics/snowplow/event/MessageNotificationTest.java b/snowplow-tracker/src/androidTest/java/com/snowplowanalytics/snowplow/event/MessageNotificationTest.java new file mode 100644 index 000000000..a95de58dc --- /dev/null +++ b/snowplow-tracker/src/androidTest/java/com/snowplowanalytics/snowplow/event/MessageNotificationTest.java @@ -0,0 +1,55 @@ +package com.snowplowanalytics.snowplow.event; + +import androidx.test.ext.junit.runners.AndroidJUnit4; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.Arrays; +import java.util.List; +import java.util.Map; + +@RunWith(AndroidJUnit4.class) +public class MessageNotificationTest { + + @Test + public void testExpectedForm() { + MessageNotification event = new MessageNotification("title", "body", MessageNotificationTrigger.push) + .notificationTimestamp("2020-12-31T15:59:60-08:00") + .action("action") + .bodyLocKey("loc key") + .bodyLocArgs(Arrays.asList("loc arg1", "loc arg2")) + .sound("chime.mp3") + .notificationCount(9) + .category("category1") + .attachments(Arrays.asList(new MessageNotificationAttachment("id", "type", "url"))); + + Map payload = event.getDataPayload(); + assertNotNull(payload); + assertEquals("title", payload.get(MessageNotification.PARAM_MESSAGENOTIFICATION_TITLE)); + assertEquals("body", payload.get(MessageNotification.PARAM_MESSAGENOTIFICATION_BODY)); + assertEquals("push", payload.get(MessageNotification.PARAM_MESSAGENOTIFICATION_TRIGGER)); + assertEquals("2020-12-31T15:59:60-08:00", payload.get(MessageNotification.PARAM_MESSAGENOTIFICATION_NOTIFICATIONTIMESTAMP)); + assertEquals("action", payload.get(MessageNotification.PARAM_MESSAGENOTIFICATION_ACTION)); + assertEquals("loc key", payload.get(MessageNotification.PARAM_MESSAGENOTIFICATION_BODYLOCKEY)); + List locArgs = (List)payload.get(MessageNotification.PARAM_MESSAGENOTIFICATION_BODYLOCARGS); + assertNotNull(locArgs); + assertEquals(2, locArgs.size()); + assertEquals("loc arg1", locArgs.get(0)); + assertEquals("loc arg2", locArgs.get(1)); + assertEquals("chime.mp3", payload.get(MessageNotification.PARAM_MESSAGENOTIFICATION_SOUND)); + assertEquals(9, payload.get(MessageNotification.PARAM_MESSAGENOTIFICATION_NOTIFICATIONCOUNT)); + assertEquals("category1", payload.get(MessageNotification.PARAM_MESSAGENOTIFICATION_CATEGORY)); + List attachments = (List)payload.get(MessageNotification.PARAM_MESSAGENOTIFICATION_MESSAGENOTIFICATIONATTACHMENTS); + assertNotNull(attachments); + assertEquals(1, attachments.size()); + MessageNotificationAttachment attachment = attachments.get(0); + assertEquals("id", attachment.get(MessageNotificationAttachment.PARAM_MESSAGENOTIFICATIONATTACHMENT_IDENTIFIER)); + assertEquals("type", attachment.get(MessageNotificationAttachment.PARAM_MESSAGENOTIFICATIONATTACHMENT_TYPE)); + assertEquals("url", attachment.get(MessageNotificationAttachment.PARAM_MESSAGENOTIFICATIONATTACHMENT_URL)); + } +} diff --git a/snowplow-tracker/src/main/java/com/snowplowanalytics/snowplow/event/MessageNotification.java b/snowplow-tracker/src/main/java/com/snowplowanalytics/snowplow/event/MessageNotification.java new file mode 100644 index 000000000..e44076309 --- /dev/null +++ b/snowplow-tracker/src/main/java/com/snowplowanalytics/snowplow/event/MessageNotification.java @@ -0,0 +1,297 @@ +/* + * Copyright (c) 2015-2021 Snowplow Analytics Ltd. All rights reserved. + * + * This program is licensed to you under the Apache License Version 2.0, + * and you may not use this file except in compliance with the Apache License Version 2.0. + * You may obtain a copy of the Apache License Version 2.0 at http://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the Apache License Version 2.0 is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the Apache License Version 2.0 for the specific language governing permissions and limitations there under. + */ + +package com.snowplowanalytics.snowplow.event; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** Event that represents the reception of a push notification (or a locally generated one). */ +public class MessageNotification extends AbstractSelfDescribing { + + public final static String SCHEMA_MESSAGENOTIFICATION = "iglu:com.snowplowanalytics.mobile/message_notification/jsonschema/1-0-0"; + + public final static String PARAM_MESSAGENOTIFICATION_ACTION = "action"; + public final static String PARAM_MESSAGENOTIFICATION_MESSAGENOTIFICATIONATTACHMENTS = "attachments"; + public final static String PARAM_MESSAGENOTIFICATION_BODY = "body"; + public final static String PARAM_MESSAGENOTIFICATION_BODYLOCARGS = "bodyLocArgs"; + public final static String PARAM_MESSAGENOTIFICATION_BODYLOCKEY = "bodyLocKey"; + public final static String PARAM_MESSAGENOTIFICATION_CATEGORY = "category"; + public final static String PARAM_MESSAGENOTIFICATION_CONTENTAVAILABLE = "contentAvailable"; + public final static String PARAM_MESSAGENOTIFICATION_GROUP = "group"; + public final static String PARAM_MESSAGENOTIFICATION_ICON = "icon"; + public final static String PARAM_MESSAGENOTIFICATION_NOTIFICATIONCOUNT = "notificationCount"; + public final static String PARAM_MESSAGENOTIFICATION_NOTIFICATIONTIMESTAMP = "notificationTimestamp"; + public final static String PARAM_MESSAGENOTIFICATION_SOUND = "sound"; + public final static String PARAM_MESSAGENOTIFICATION_SUBTITLE = "subtitle"; + public final static String PARAM_MESSAGENOTIFICATION_TAG = "tag"; + public final static String PARAM_MESSAGENOTIFICATION_THREADIDENTIFIER = "threadIdentifier"; + public final static String PARAM_MESSAGENOTIFICATION_TITLE = "title"; + public final static String PARAM_MESSAGENOTIFICATION_TITLELOCARGS = "titleLocArgs"; + public final static String PARAM_MESSAGENOTIFICATION_TITLELOCKEY = "titleLocKey"; + public final static String PARAM_MESSAGENOTIFICATION_TRIGGER = "trigger"; + + /** The action associated with the notification. */ + @Nullable + public String action; + /** Attachments added to the notification (they can be part of the data object). */ + @Nullable + public List attachments; + /** The notification's body. */ + @NonNull + public final String body; + /** Variable string values to be used in place of the format specifiers in bodyLocArgs to use to localize the body text to the user's current localization. */ + @Nullable + public List bodyLocArgs; + /** The key to the body string in the app's string resources to use to localize the body text to the user's current localization. */ + @Nullable + public String bodyLocKey; + /** The category associated to the notification. */ + @Nullable + public String category; + /** The application is notified of the delivery of the notification if it's in the foreground or background, the app will be woken up (iOS only). */ + @Nullable + public Boolean contentAvailable; + /** The group which this notification is part of. */ + @Nullable + public String group; + /** The icon associated to the notification (Android only). */ + @Nullable + public String icon; + /** The number of items this notification represents. */ + @Nullable + public Integer notificationCount; + /** The time when the event of the notification occurred. */ + @Nullable + public String notificationTimestamp; + /** The sound played when the device receives the notification. */ + @Nullable + public String sound; + /** The notification's subtitle. (iOS only) */ + @Nullable + public String subtitle; + /** An identifier similar to 'group' but usable for different purposes (Android only). */ + @Nullable + public String tag; + /** An identifier similar to 'group' but usable for different purposes (iOS only). */ + @Nullable + public String threadIdentifier; + /** The notification's title. */ + @NonNull + public final String title; + /** Variable string values to be used in place of the format specifiers in titleLocArgs to use to localize the title text to the user's current localization. */ + @Nullable + public List titleLocArgs; + /** The key to the title string in the app's string resources to use to localize the title text to the user's current localization. */ + @Nullable + public String titleLocKey; + /** The trigger that raised the notification message. */ + @NonNull + public final MessageNotificationTrigger trigger; + + /** + Creates a Message Notification event that represents a push notification or a local notification. + @note The custom data of the push notification have to be tracked separately in custom entities that can be attached to this event. + @param title Title of message notification. + @param body Body content of the message notification. + @param trigger The trigger that raised this notification: remote notification (push), position related (location), date-time related (calendar, timeInterval) or app generated (other). + */ + public MessageNotification(@NonNull String title, @NonNull String body, @NonNull MessageNotificationTrigger trigger) { + this.title = title; + this.body = body; + this.trigger = trigger; + } + + // Builder methods + + /** The action associated with the notification. */ + @NonNull + public MessageNotification action(@Nullable String action) { + this.action = action; + return this; + } + + /** Attachments added to the notification (they can be part of the data object). */ + @NonNull + public MessageNotification attachments(@Nullable List attachments) { + this.attachments = attachments; + return this; + } + + /** Variable string values to be used in place of the format specifiers in bodyLocArgs to use to localize the body text to the user's current localization. */ + @NonNull + public MessageNotification bodyLocArgs(@Nullable List bodyLocArgs) { + this.bodyLocArgs = bodyLocArgs; + return this; + } + + /** The key to the body string in the app's string resources to use to localize the body text to the user's current localization. */ + @NonNull + public MessageNotification bodyLocKey(@Nullable String bodyLocKey) { + this.bodyLocKey = bodyLocKey; + return this; + } + + /** The category associated to the notification. */ + @NonNull + public MessageNotification category(@Nullable String category) { + this.category = category; + return this; + } + + /** The application is notified of the delivery of the notification if it's in the foreground or background, the app will be woken up (iOS only). */ + @NonNull + public MessageNotification contentAvailable(@Nullable Boolean contentAvailable) { + this.contentAvailable = contentAvailable; + return this; + } + + /** The group which this notification is part of. */ + @NonNull + public MessageNotification group(@Nullable String group) { + this.group = group; + return this; + } + + /** The icon associated to the notification (Android only). */ + @NonNull + public MessageNotification icon(@Nullable String icon) { + this.icon = icon; + return this; + } + + /** The number of items this notification represents. */ + @NonNull + public MessageNotification notificationCount(@Nullable Integer notificationCount) { + this.notificationCount = notificationCount; + return this; + } + + /** The time when the event of the notification occurred. */ + @NonNull + public MessageNotification notificationTimestamp(@Nullable String notificationTimestamp) { + this.notificationTimestamp = notificationTimestamp; + return this; + } + + /** The sound played when the device receives the notification. */ + @NonNull + public MessageNotification sound(@Nullable String sound) { + this.sound = sound; + return this; + } + + /** The notification's subtitle. (iOS only) */ + @NonNull + public MessageNotification subtitle(@Nullable String subtitle) { + this.subtitle = subtitle; + return this; + } + + /** An identifier similar to 'group' but usable for different purposes (Android only). */ + @NonNull + public MessageNotification tag(@Nullable String tag) { + this.tag = tag; + return this; + } + + /** An identifier similar to 'group' but usable for different purposes (iOS only). */ + @NonNull + public MessageNotification threadIdentifier(@Nullable String threadIdentifier) { + this.threadIdentifier = threadIdentifier; + return this; + } + + /** Variable string values to be used in place of the format specifiers in titleLocArgs to use to localize the title text to the user's current localization. */ + @NonNull + public MessageNotification titleLocArgs(@Nullable List titleLocArgs) { + this.titleLocArgs = titleLocArgs; + return this; + } + + /** The key to the title string in the app's string resources to use to localize the title text to the user's current localization. */ + @NonNull + public MessageNotification titleLocKey(@Nullable String titleLocKey) { + this.titleLocKey = titleLocKey; + return this; + } + + // Tracker methods + + @Override + public @NonNull Map getDataPayload() { + HashMap payload = new HashMap<>(); + payload.put(PARAM_MESSAGENOTIFICATION_TITLE, title); + payload.put(PARAM_MESSAGENOTIFICATION_BODY, body); + payload.put(PARAM_MESSAGENOTIFICATION_TRIGGER, trigger.name()); + if (action != null) { + payload.put(PARAM_MESSAGENOTIFICATION_ACTION, action); + } + if (attachments != null && attachments.size() > 0) { + payload.put(PARAM_MESSAGENOTIFICATION_MESSAGENOTIFICATIONATTACHMENTS, attachments); + } + if (bodyLocArgs != null) { + payload.put(PARAM_MESSAGENOTIFICATION_BODYLOCARGS, bodyLocArgs); + } + if (bodyLocKey != null) { + payload.put(PARAM_MESSAGENOTIFICATION_BODYLOCKEY, bodyLocKey); + } + if (category != null) { + payload.put(PARAM_MESSAGENOTIFICATION_CATEGORY, category); + } + if (contentAvailable != null) { + payload.put(PARAM_MESSAGENOTIFICATION_CONTENTAVAILABLE, contentAvailable); + } + if (group != null) { + payload.put(PARAM_MESSAGENOTIFICATION_GROUP, group); + } + if (icon != null) { + payload.put(PARAM_MESSAGENOTIFICATION_ICON, icon); + } + if (notificationCount != null) { + payload.put(PARAM_MESSAGENOTIFICATION_NOTIFICATIONCOUNT, notificationCount); + } + if (notificationTimestamp != null) { + payload.put(PARAM_MESSAGENOTIFICATION_NOTIFICATIONTIMESTAMP, notificationTimestamp); + } + if (sound != null) { + payload.put(PARAM_MESSAGENOTIFICATION_SOUND, sound); + } + if (subtitle != null) { + payload.put(PARAM_MESSAGENOTIFICATION_SUBTITLE, subtitle); + } + if (tag != null) { + payload.put(PARAM_MESSAGENOTIFICATION_TAG, tag); + } + if (threadIdentifier != null) { + payload.put(PARAM_MESSAGENOTIFICATION_THREADIDENTIFIER, threadIdentifier); + } + if (titleLocArgs != null) { + payload.put(PARAM_MESSAGENOTIFICATION_TITLELOCARGS, titleLocArgs); + } + if (titleLocKey != null) { + payload.put(PARAM_MESSAGENOTIFICATION_TITLELOCKEY, titleLocKey); + } + return payload; + } + + @Override + public @NonNull String getSchema() { + return SCHEMA_MESSAGENOTIFICATION; + } +} + diff --git a/snowplow-tracker/src/main/java/com/snowplowanalytics/snowplow/event/MessageNotificationAttachment.java b/snowplow-tracker/src/main/java/com/snowplowanalytics/snowplow/event/MessageNotificationAttachment.java new file mode 100644 index 000000000..b10c06aea --- /dev/null +++ b/snowplow-tracker/src/main/java/com/snowplowanalytics/snowplow/event/MessageNotificationAttachment.java @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2015-2021 Snowplow Analytics Ltd. All rights reserved. + * + * This program is licensed to you under the Apache License Version 2.0, + * and you may not use this file except in compliance with the Apache License Version 2.0. + * You may obtain a copy of the Apache License Version 2.0 at http://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the Apache License Version 2.0 is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the Apache License Version 2.0 for the specific language governing permissions and limitations there under. + */ + +package com.snowplowanalytics.snowplow.event; + +import androidx.annotation.NonNull; + +import java.util.HashMap; + +/// Attachment object that identify an attachment in the MessageNotification +public class MessageNotificationAttachment extends HashMap { + + public final static String PARAM_MESSAGENOTIFICATIONATTACHMENT_IDENTIFIER = "identifier"; + public final static String PARAM_MESSAGENOTIFICATIONATTACHMENT_TYPE = "type"; + public final static String PARAM_MESSAGENOTIFICATIONATTACHMENT_URL = "url"; + + /// Attachments added to the notification (they can be part of the data object). + public MessageNotificationAttachment(@NonNull String identifier, @NonNull String type, @NonNull String url) { + put(PARAM_MESSAGENOTIFICATIONATTACHMENT_IDENTIFIER, identifier); + put(PARAM_MESSAGENOTIFICATIONATTACHMENT_TYPE, type); + put(PARAM_MESSAGENOTIFICATIONATTACHMENT_URL, url); + } +} diff --git a/snowplow-tracker/src/main/java/com/snowplowanalytics/snowplow/event/MessageNotificationTrigger.java b/snowplow-tracker/src/main/java/com/snowplowanalytics/snowplow/event/MessageNotificationTrigger.java new file mode 100644 index 000000000..2cb10eee9 --- /dev/null +++ b/snowplow-tracker/src/main/java/com/snowplowanalytics/snowplow/event/MessageNotificationTrigger.java @@ -0,0 +1,22 @@ +/* + * Copyright (c) 2015-2021 Snowplow Analytics Ltd. All rights reserved. + * + * This program is licensed to you under the Apache License Version 2.0, + * and you may not use this file except in compliance with the Apache License Version 2.0. + * You may obtain a copy of the Apache License Version 2.0 at http://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the Apache License Version 2.0 is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the Apache License Version 2.0 for the specific language governing permissions and limitations there under. + */ + +package com.snowplowanalytics.snowplow.event; + +public enum MessageNotificationTrigger { + push, + location, + calendar, + timeInterval, + other +}