diff --git a/.gitignore b/.gitignore index b2f46c5ab..d8c995c95 100644 --- a/.gitignore +++ b/.gitignore @@ -8,6 +8,9 @@ local.properties OneSignalSDK/local.properties examples/demo/local.properties +# Google services +google-services.json + # macOS .DS_Store diff --git a/examples/demo/GettingStarted.md b/examples/demo/GettingStarted.md index e99c9ffc9..14b387b85 100644 --- a/examples/demo/GettingStarted.md +++ b/examples/demo/GettingStarted.md @@ -98,6 +98,13 @@ Fire test notifications from the device via the OneSignal REST API: - **With Image** — includes a big picture - **Custom** — fully customizable title, body, and additional data +All demo sends use the same explicit Android group (`demo-group`). Send two +notifications to verify that OneSignal creates and manages the notification +summary instead of relying on Android system auto-grouping. Verifying that +tapping the summary supplies OneSignal notification click data requires testing +on an Android device or emulator; the REST payload alone cannot verify tap +behavior. + ### In-App Messaging **Pause / resume** in-app message display. diff --git a/examples/demo/app/src/huawei/java/com/onesignal/example/notification/HmsMessageServiceAppLevel.kt b/examples/demo/app/src/huawei/java/com/onesignal/example/notification/HmsMessageServiceAppLevel.kt index 30e1fc7c5..ae10b9614 100644 --- a/examples/demo/app/src/huawei/java/com/onesignal/example/notification/HmsMessageServiceAppLevel.kt +++ b/examples/demo/app/src/huawei/java/com/onesignal/example/notification/HmsMessageServiceAppLevel.kt @@ -14,10 +14,6 @@ import com.onesignal.example.util.DemoLog */ class HmsMessageServiceAppLevel : HmsMessageService() { - companion object { - private const val TAG = "OneSignalHMS" - } - /** * When an app calls the getToken method to apply for a token from the server, * if the server does not return the token during current method calling, @@ -26,7 +22,7 @@ class HmsMessageServiceAppLevel : HmsMessageService() { * Otherwise, you need to start a new Job for callback processing. */ override fun onNewToken(token: String, bundle: Bundle) { - DemoLog.d(TAG, "HmsMessageServiceAppLevel onNewToken refresh token: $token bundle: $bundle") + DemoLog.d("HmsMessageServiceAppLevel onNewToken refresh token: $token bundle: $bundle") // Forward event on to OneSignal SDK OneSignalHmsEventBridge.onNewToken(this, token, bundle) @@ -34,7 +30,7 @@ class HmsMessageServiceAppLevel : HmsMessageService() { @Deprecated("Deprecated in Java") override fun onNewToken(token: String) { - DemoLog.d(TAG, "HmsMessageServiceAppLevel onNewToken refresh token: $token") + DemoLog.d("HmsMessageServiceAppLevel onNewToken refresh token: $token") // Forward event on to OneSignal SDK OneSignalHmsEventBridge.onNewToken(this, token) @@ -48,18 +44,18 @@ class HmsMessageServiceAppLevel : HmsMessageService() { * Start a new Job if more time is needed. */ override fun onMessageReceived(message: RemoteMessage) { - DemoLog.d(TAG, "HMS onMessageReceived: $message") - DemoLog.d(TAG, "HMS onMessageReceived.ttl: ${message.ttl}") - DemoLog.d(TAG, "HMS onMessageReceived.data: ${message.data}") + DemoLog.d("HMS onMessageReceived: $message") + DemoLog.d("HMS onMessageReceived.ttl: ${message.ttl}") + DemoLog.d("HMS onMessageReceived.data: ${message.data}") message.notification?.let { notification -> - DemoLog.d(TAG, "HMS onMessageReceived.title: ${notification.title}") - DemoLog.d(TAG, "HMS onMessageReceived.body: ${notification.body}") - DemoLog.d(TAG, "HMS onMessageReceived.icon: ${notification.icon}") - DemoLog.d(TAG, "HMS onMessageReceived.color: ${notification.color}") - DemoLog.d(TAG, "HMS onMessageReceived.channelId: ${notification.channelId}") - DemoLog.d(TAG, "HMS onMessageReceived.imageURL: ${notification.imageUrl}") - DemoLog.d(TAG, "HMS onMessageReceived.tag: ${notification.tag}") + DemoLog.d("HMS onMessageReceived.title: ${notification.title}") + DemoLog.d("HMS onMessageReceived.body: ${notification.body}") + DemoLog.d("HMS onMessageReceived.icon: ${notification.icon}") + DemoLog.d("HMS onMessageReceived.color: ${notification.color}") + DemoLog.d("HMS onMessageReceived.channelId: ${notification.channelId}") + DemoLog.d("HMS onMessageReceived.imageURL: ${notification.imageUrl}") + DemoLog.d("HMS onMessageReceived.tag: ${notification.tag}") } // Forward event on to OneSignal SDK diff --git a/examples/demo/app/src/main/java/com/onesignal/example/application/MainApplication.kt b/examples/demo/app/src/main/java/com/onesignal/example/application/MainApplication.kt index ec725707d..27f333698 100644 --- a/examples/demo/app/src/main/java/com/onesignal/example/application/MainApplication.kt +++ b/examples/demo/app/src/main/java/com/onesignal/example/application/MainApplication.kt @@ -22,13 +22,13 @@ import com.onesignal.example.data.network.OneSignalService import com.onesignal.example.util.DemoLog import com.onesignal.example.util.SharedPreferenceUtil import com.onesignal.example.util.TooltipHelper +import com.onesignal.example.util.toJson import com.onesignal.user.state.IUserStateObserver import com.onesignal.user.state.UserChangedState class MainApplication : MultiDexApplication() { companion object { - private const val TAG = "OneSignalExample" private const val SLEEP_TIME_TO_MIMIC_ASYNC_OPERATION = 2000L } @@ -69,7 +69,7 @@ class MainApplication : MultiDexApplication() { // Initialize OneSignal on main thread (required) // Crash handler + ANR detector are initialized early inside initWithContext OneSignal.initWithContext(this, appId) - DemoLog.i(TAG, "OneSignal init completed (crash handler, ANR detector, and logging active)") + DemoLog.i("OneSignal init completed (crash handler, ANR detector, and logging active)") // Set up all OneSignal listeners setupOneSignalListeners() @@ -81,37 +81,40 @@ class MainApplication : MultiDexApplication() { private fun setupOneSignalListeners() { OneSignal.InAppMessages.addLifecycleListener(object : IInAppMessageLifecycleListener { override fun onWillDisplay(event: IInAppMessageWillDisplayEvent) { - DemoLog.d(TAG, "onWillDisplayInAppMessage") + DemoLog.d("onWillDisplayInAppMessage") } override fun onDidDisplay(event: IInAppMessageDidDisplayEvent) { - DemoLog.d(TAG, "onDidDisplayInAppMessage") + DemoLog.d("onDidDisplayInAppMessage") } override fun onWillDismiss(event: IInAppMessageWillDismissEvent) { - DemoLog.d(TAG, "onWillDismissInAppMessage") + DemoLog.d("onWillDismissInAppMessage") } override fun onDidDismiss(event: IInAppMessageDidDismissEvent) { - DemoLog.d(TAG, "onDidDismissInAppMessage") + DemoLog.d("onDidDismissInAppMessage") } }) OneSignal.InAppMessages.addClickListener(object : IInAppMessageClickListener { override fun onClick(event: IInAppMessageClickEvent) { - DemoLog.d(TAG, "IInAppMessageClickListener.onClick") + DemoLog.d("IInAppMessageClickListener.onClick") } }) OneSignal.Notifications.addClickListener(object : INotificationClickListener { override fun onClick(event: INotificationClickEvent) { - DemoLog.d(TAG, "INotificationClickListener.onClick fired with event: $event") + DemoLog.d("Notification click: ${event.notification.title.orEmpty()}") + + // uncomment to see the full event object + // DemoLog.d("Event: ${event.toJson()}") } }) OneSignal.Notifications.addForegroundLifecycleListener(object : INotificationLifecycleListener { override fun onWillDisplay(event: INotificationWillDisplayEvent) { - DemoLog.d(TAG, "INotificationLifecycleListener.onWillDisplay fired with event: $event") + DemoLog.d("INotificationLifecycleListener.onWillDisplay fired with event: $event") val notification: IDisplayableNotification = event.notification @@ -130,7 +133,7 @@ class MainApplication : MultiDexApplication() { OneSignal.User.addObserver(object : IUserStateObserver { override fun onUserStateChange(state: UserChangedState) { - DemoLog.i(TAG, "User state changed: onesignalId=${state.current.onesignalId}, externalId=${state.current.externalId}") + DemoLog.i("User state changed: onesignalId=${state.current.onesignalId}, externalId=${state.current.externalId}") } }) diff --git a/examples/demo/app/src/main/java/com/onesignal/example/data/network/OneSignalService.kt b/examples/demo/app/src/main/java/com/onesignal/example/data/network/OneSignalService.kt index bc8d1cedb..45a31d6e9 100644 --- a/examples/demo/app/src/main/java/com/onesignal/example/data/network/OneSignalService.kt +++ b/examples/demo/app/src/main/java/com/onesignal/example/data/network/OneSignalService.kt @@ -22,9 +22,9 @@ import java.net.URLEncoder */ object OneSignalService { - private const val TAG = "OneSignalService" private const val ONESIGNAL_API_URL = "https://onesignal.com/api/v1/notifications" private const val ONESIGNAL_API_BASE_URL = "https://api.onesignal.com" + private const val DEMO_ANDROID_GROUP = "demo-group" private var appId: String = "" @@ -41,13 +41,13 @@ object OneSignalService { val subscription = OneSignal.User.pushSubscription if (!subscription.optedIn) { - DemoLog.w(TAG, "Cannot send notification - user not opted in") + DemoLog.w("Cannot send notification - user not opted in") return@withContext false } val subscriptionId = subscription.id if (subscriptionId.isNullOrEmpty()) { - DemoLog.w(TAG, "Cannot send notification - no subscription ID") + DemoLog.w("Cannot send notification - no subscription ID") return@withContext false } @@ -57,21 +57,21 @@ object OneSignalService { put("include_subscription_ids", org.json.JSONArray().put(subscriptionId)) put("headings", JSONObject().put("en", type.notificationTitle)) put("contents", JSONObject().put("en", type.notificationBody)) - put("android_group", type.title) + put("android_group", DEMO_ANDROID_GROUP) put("android_led_color", "FF595CF2") put("android_accent_color", "FF595CF2") type.largeIcon?.let { put("large_icon", it) - DemoLog.d(TAG, "Adding large_icon: $it") + DemoLog.d("Adding large_icon: $it") } type.bigPicture?.let { put("big_picture", it) - DemoLog.d(TAG, "Adding big_picture: $it") + DemoLog.d("Adding big_picture: $it") } type.sound?.let { put("android_sound", it) put("android_channel_id", BuildConfig.ONESIGNAL_ANDROID_CHANNEL_ID) - DemoLog.d(TAG, "Adding android_sound: $it (channel: ${BuildConfig.ONESIGNAL_ANDROID_CHANNEL_ID})") + DemoLog.d("Adding android_sound: $it (channel: ${BuildConfig.ONESIGNAL_ANDROID_CHANNEL_ID})") } } @@ -79,7 +79,7 @@ object OneSignalService { } catch (e: CancellationException) { throw e } catch (e: Exception) { - DemoLog.e(TAG, "Error sending notification", e) + DemoLog.e("Error sending notification", e) return@withContext false } } @@ -91,13 +91,13 @@ object OneSignalService { val subscription = OneSignal.User.pushSubscription if (!subscription.optedIn) { - DemoLog.w(TAG, "Cannot send notification - user not opted in") + DemoLog.w("Cannot send notification - user not opted in") return@withContext false } val subscriptionId = subscription.id if (subscriptionId.isNullOrEmpty()) { - DemoLog.w(TAG, "Cannot send notification - no subscription ID") + DemoLog.w("Cannot send notification - no subscription ID") return@withContext false } @@ -107,6 +107,7 @@ object OneSignalService { put("include_subscription_ids", org.json.JSONArray().put(subscriptionId)) put("headings", JSONObject().put("en", title)) put("contents", JSONObject().put("en", body)) + put("android_group", DEMO_ANDROID_GROUP) put("android_led_color", "FF595CF2") put("android_accent_color", "FF595CF2") } @@ -115,7 +116,7 @@ object OneSignalService { } catch (e: CancellationException) { throw e } catch (e: Exception) { - DemoLog.e(TAG, "Error sending custom notification", e) + DemoLog.e("Error sending custom notification", e) return@withContext false } } @@ -157,7 +158,7 @@ object OneSignalService { } if (responseCode !in 200..299) { - DemoLog.e(TAG, "Send $label failed: $response") + DemoLog.e("Send $label failed: $response") return false } @@ -166,7 +167,7 @@ object OneSignalService { delay(backoffMs(attempt)) continue } - DemoLog.e(TAG, "Send $label failed: $response") + DemoLog.e("Send $label failed: $response") return false } @@ -176,7 +177,7 @@ object OneSignalService { // teardown while `delay` is suspending between retries). throw e } catch (e: Exception) { - DemoLog.e(TAG, "Send $label error: ${e.message}") + DemoLog.e("Send $label error: ${e.message}") return false } finally { connection.disconnect() @@ -213,12 +214,12 @@ object OneSignalService { */ suspend fun fetchUser(aliasLabel: String, aliasValue: String, jwt: String? = null): UserData? = withContext(Dispatchers.IO) { if (aliasValue.isEmpty()) { - DemoLog.w(TAG, "Cannot fetch user - aliasValue is empty") + DemoLog.w("Cannot fetch user - aliasValue is empty") return@withContext null } if (appId.isEmpty()) { - DemoLog.w(TAG, "Cannot fetch user - appId not set") + DemoLog.w("Cannot fetch user - appId not set") return@withContext null } @@ -229,7 +230,7 @@ object OneSignalService { // space as `+`; swap to %20 since `+` is treated as a literal in paths. val encodedAliasValue = URLEncoder.encode(aliasValue, "UTF-8").replace("+", "%20") val url = "$ONESIGNAL_API_BASE_URL/apps/$appId/users/by/$aliasLabel/$encodedAliasValue" - DemoLog.d(TAG, "Fetching user data from: $url") + DemoLog.d("Fetching user data from: $url") val connection = (URL(url).openConnection() as HttpURLConnection).apply { useCaches = false @@ -246,24 +247,24 @@ object OneSignalService { if (responseCode == HttpURLConnection.HTTP_OK) { val response = connection.inputStream.bufferedReader().use { it.readText() } - DemoLog.d(TAG, "User data fetched successfully, parsing response...") + DemoLog.d("User data fetched successfully, parsing response...") try { val userData = parseUserResponse(response) - DemoLog.d(TAG, "Parsed user data: aliases=${userData.aliases.size}, tags=${userData.tags.size}, emails=${userData.emails.size}, sms=${userData.smsNumbers.size}") + DemoLog.d("Parsed user data: aliases=${userData.aliases.size}, tags=${userData.tags.size}, emails=${userData.emails.size}, sms=${userData.smsNumbers.size}") return@withContext userData } catch (e: Exception) { - DemoLog.e(TAG, "Error parsing user response", e) + DemoLog.e("Error parsing user response", e) return@withContext null } } else { val errorResponse = connection.errorStream?.bufferedReader()?.use { it.readText() } ?: "Unknown error" - DemoLog.e(TAG, "Failed to fetch user (HTTP $responseCode): $errorResponse") + DemoLog.e("Failed to fetch user (HTTP $responseCode): $errorResponse") return@withContext null } } catch (e: CancellationException) { throw e } catch (e: Exception) { - DemoLog.e(TAG, "Error fetching user", e) + DemoLog.e("Error fetching user", e) return@withContext null } } diff --git a/examples/demo/app/src/main/java/com/onesignal/example/data/repository/OneSignalRepository.kt b/examples/demo/app/src/main/java/com/onesignal/example/data/repository/OneSignalRepository.kt index 9c0473e8f..671b25063 100644 --- a/examples/demo/app/src/main/java/com/onesignal/example/data/repository/OneSignalRepository.kt +++ b/examples/demo/app/src/main/java/com/onesignal/example/data/repository/OneSignalRepository.kt @@ -14,45 +14,41 @@ import com.onesignal.example.util.DemoLog */ class OneSignalRepository { - companion object { - private const val TAG = "OneSignalRepository" - } - // User operations suspend fun loginUser(externalUserId: String, jwtToken: String? = null) = withContext(Dispatchers.IO) { - DemoLog.d(TAG, "Logging in user with externalUserId: $externalUserId, jwt: ${if (jwtToken != null) "provided" else "none"}") + DemoLog.d("Logging in user with externalUserId: $externalUserId, jwt: ${if (jwtToken != null) "provided" else "none"}") OneSignal.login(externalUserId, jwtToken) - DemoLog.d(TAG, "Logged in user with onesignalId: ${OneSignal.User.onesignalId}") + DemoLog.d("Logged in user with onesignalId: ${OneSignal.User.onesignalId}") } suspend fun updateUserJwt(externalUserId: String, jwtToken: String) = withContext(Dispatchers.IO) { - DemoLog.d(TAG, "Updating JWT for externalUserId: $externalUserId") + DemoLog.d("Updating JWT for externalUserId: $externalUserId") OneSignal.updateUserJwt(externalUserId, jwtToken) } suspend fun logoutUser() = withContext(Dispatchers.IO) { - DemoLog.d(TAG, "Logging out user") + DemoLog.d("Logging out user") OneSignal.logout() } // Alias operations fun addAlias(label: String, id: String) { - DemoLog.d(TAG, "Adding alias: $label -> $id") + DemoLog.d("Adding alias: $label -> $id") OneSignal.User.addAlias(label, id) } fun addAliases(aliases: Map) { - DemoLog.d(TAG, "Adding aliases: $aliases") + DemoLog.d("Adding aliases: $aliases") OneSignal.User.addAliases(aliases) } fun removeAlias(label: String) { - DemoLog.d(TAG, "Removing alias: $label") + DemoLog.d("Removing alias: $label") OneSignal.User.removeAlias(label) } fun removeAliases(labels: Collection) { - DemoLog.d(TAG, "Removing aliases: $labels") + DemoLog.d("Removing aliases: $labels") if (labels.isNotEmpty()) { OneSignal.User.removeAliases(labels) } @@ -60,44 +56,44 @@ class OneSignalRepository { // Email operations fun addEmail(email: String) { - DemoLog.d(TAG, "Adding email: $email") + DemoLog.d("Adding email: $email") OneSignal.User.addEmail(email) } fun removeEmail(email: String) { - DemoLog.d(TAG, "Removing email: $email") + DemoLog.d("Removing email: $email") OneSignal.User.removeEmail(email) } // SMS operations fun addSms(smsNumber: String) { - DemoLog.d(TAG, "Adding SMS: $smsNumber") + DemoLog.d("Adding SMS: $smsNumber") OneSignal.User.addSms(smsNumber) } fun removeSms(smsNumber: String) { - DemoLog.d(TAG, "Removing SMS: $smsNumber") + DemoLog.d("Removing SMS: $smsNumber") OneSignal.User.removeSms(smsNumber) } // Tag operations fun addTag(key: String, value: String) { - DemoLog.d(TAG, "Adding tag: $key -> $value") + DemoLog.d("Adding tag: $key -> $value") OneSignal.User.addTag(key, value) } fun addTags(tags: Map) { - DemoLog.d(TAG, "Adding tags: $tags") + DemoLog.d("Adding tags: $tags") OneSignal.User.addTags(tags) } fun removeTag(key: String) { - DemoLog.d(TAG, "Removing tag: $key") + DemoLog.d("Removing tag: $key") OneSignal.User.removeTag(key) } fun removeTags(keys: Collection) { - DemoLog.d(TAG, "Removing tags: $keys") + DemoLog.d("Removing tags: $keys") if (keys.isNotEmpty()) { OneSignal.User.removeTags(keys) } @@ -109,22 +105,22 @@ class OneSignalRepository { // Trigger operations fun addTrigger(key: String, value: String) { - DemoLog.d(TAG, "Adding trigger: $key -> $value") + DemoLog.d("Adding trigger: $key -> $value") OneSignal.InAppMessages.addTrigger(key, value) } fun addTriggers(triggers: Map) { - DemoLog.d(TAG, "Adding triggers: $triggers") + DemoLog.d("Adding triggers: $triggers") OneSignal.InAppMessages.addTriggers(triggers) } fun removeTrigger(key: String) { - DemoLog.d(TAG, "Removing trigger: $key") + DemoLog.d("Removing trigger: $key") OneSignal.InAppMessages.removeTrigger(key) } fun clearTriggers(keys: Collection) { - DemoLog.d(TAG, "Clearing triggers: $keys") + DemoLog.d("Clearing triggers: $keys") if (keys.isNotEmpty()) { OneSignal.InAppMessages.removeTriggers(keys) } @@ -132,23 +128,23 @@ class OneSignalRepository { // Outcome operations fun sendOutcome(name: String) { - DemoLog.d(TAG, "Sending outcome: $name") + DemoLog.d("Sending outcome: $name") OneSignal.Session.addOutcome(name) } fun sendUniqueOutcome(name: String) { - DemoLog.d(TAG, "Sending unique outcome: $name") + DemoLog.d("Sending unique outcome: $name") OneSignal.Session.addUniqueOutcome(name) } fun sendOutcomeWithValue(name: String, value: Float) { - DemoLog.d(TAG, "Sending outcome with value: $name -> $value") + DemoLog.d("Sending outcome with value: $name -> $value") OneSignal.Session.addOutcomeWithValue(name, value) } // Track Event fun trackEvent(name: String, properties: Map?) { - DemoLog.d(TAG, "Tracking event: $name with properties: $properties") + DemoLog.d("Tracking event: $name with properties: $properties") OneSignal.User.trackEvent(name, properties) } @@ -162,7 +158,7 @@ class OneSignalRepository { } fun setPushEnabled(enabled: Boolean) { - DemoLog.d(TAG, "Setting push enabled: $enabled") + DemoLog.d("Setting push enabled: $enabled") if (enabled) { OneSignal.User.pushSubscription.optIn() } else { @@ -176,7 +172,7 @@ class OneSignalRepository { } fun setInAppMessagesPaused(paused: Boolean) { - DemoLog.d(TAG, "Setting in-app messages paused: $paused") + DemoLog.d("Setting in-app messages paused: $paused") OneSignal.InAppMessages.paused = paused } @@ -186,18 +182,18 @@ class OneSignalRepository { } fun setLocationShared(shared: Boolean) { - DemoLog.d(TAG, "Setting location shared: $shared") + DemoLog.d("Setting location shared: $shared") OneSignal.Location.isShared = shared } suspend fun promptLocation() = withContext(Dispatchers.IO) { - DemoLog.d(TAG, "Prompting for location permission") + DemoLog.d("Prompting for location permission") OneSignal.Location.requestPermission() } // Notifications suspend fun promptPushPermission() = withContext(Dispatchers.IO) { - DemoLog.d(TAG, "Prompting for push permission") + DemoLog.d("Prompting for push permission") OneSignal.Notifications.requestPermission(true) } @@ -207,18 +203,18 @@ class OneSignalRepository { // Send notifications suspend fun sendNotification(type: NotificationType): Boolean { - DemoLog.d(TAG, "Sending notification: ${type.title}") + DemoLog.d("Sending notification: ${type.title}") return OneSignalService.sendNotification(type) } suspend fun sendCustomNotification(title: String, body: String): Boolean { - DemoLog.d(TAG, "Sending custom notification: $title") + DemoLog.d("Sending custom notification: $title") return OneSignalService.sendCustomNotification(title, body) } // Privacy consent fun setConsentRequired(required: Boolean) { - DemoLog.d(TAG, "Setting consent required: $required") + DemoLog.d("Setting consent required: $required") OneSignal.consentRequired = required } @@ -227,7 +223,7 @@ class OneSignalRepository { } fun setPrivacyConsent(granted: Boolean) { - DemoLog.d(TAG, "Setting privacy consent: $granted") + DemoLog.d("Setting privacy consent: $granted") OneSignal.consentGiven = granted } @@ -242,7 +238,7 @@ class OneSignalRepository { // Fetch user data from API suspend fun fetchUser(aliasLabel: String, aliasValue: String, jwt: String? = null): UserData? = withContext(Dispatchers.IO) { - DemoLog.d(TAG, "Fetching user data by $aliasLabel: $aliasValue") + DemoLog.d("Fetching user data by $aliasLabel: $aliasValue") OneSignalService.fetchUser(aliasLabel, aliasValue, jwt) } } diff --git a/examples/demo/app/src/main/java/com/onesignal/example/notification/DemoNotificationServiceExtension.kt b/examples/demo/app/src/main/java/com/onesignal/example/notification/DemoNotificationServiceExtension.kt index 80f84e04f..9a0100d66 100644 --- a/examples/demo/app/src/main/java/com/onesignal/example/notification/DemoNotificationServiceExtension.kt +++ b/examples/demo/app/src/main/java/com/onesignal/example/notification/DemoNotificationServiceExtension.kt @@ -34,7 +34,6 @@ class DemoNotificationServiceExtension : INotificationServiceExtension { val notification = event.notification DemoLog.d( - TAG, "received androidNotificationId=${notification.androidNotificationId}" + " notificationId=${notification.notificationId}" + " sentTime=${notification.sentTime}" + @@ -43,7 +42,7 @@ class DemoNotificationServiceExtension : INotificationServiceExtension { ) if (options.discard) { - DemoLog.d(TAG, "discarding androidNotificationId=${notification.androidNotificationId}") + DemoLog.d("discarding androidNotificationId=${notification.androidNotificationId}") event.preventDefault(true) return } @@ -71,7 +70,6 @@ class DemoNotificationServiceExtension : INotificationServiceExtension { // `restored_OS_notifications` no matter what the payload asked for, and the payload // by itself never shows that. DemoLog.d( - TAG, "building androidNotificationId=${notification.androidNotificationId}" + " channel=${NotificationCompat.getChannelId(builder.build())}", ) @@ -112,7 +110,6 @@ class DemoNotificationServiceExtension : INotificationServiceExtension { } private companion object { - const val TAG = "NSE" const val HIGH_IMPORTANCE_CHANNEL_ID = "demo_nse_high_importance" // Well under the SDK's 30 second wait for the extension, and long enough to watch the diff --git a/examples/demo/app/src/main/java/com/onesignal/example/ui/main/MainViewModel.kt b/examples/demo/app/src/main/java/com/onesignal/example/ui/main/MainViewModel.kt index 2d6bd010f..70c57a00d 100644 --- a/examples/demo/app/src/main/java/com/onesignal/example/ui/main/MainViewModel.kt +++ b/examples/demo/app/src/main/java/com/onesignal/example/ui/main/MainViewModel.kt @@ -26,7 +26,6 @@ import kotlinx.coroutines.withContext class MainViewModel(application: Application) : AndroidViewModel(application), IPushSubscriptionObserver, IPermissionObserver, IUserStateObserver, IUserJwtInvalidatedListener { private companion object { - const val TAG = "MainViewModel" } private val repository = OneSignalRepository() @@ -115,14 +114,14 @@ class MainViewModel(application: Application) : AndroidViewModel(application), I private var fetchRequestSequence = 0L init { - DemoLog.i(TAG, "App initialized") + DemoLog.i("App initialized") loadInitialState() OneSignal.User.pushSubscription.addObserver(this) OneSignal.Notifications.addPermissionObserver(this) OneSignal.User.addObserver(this) OneSignal.addUserJwtInvalidatedListener(this) - DemoLog.d(TAG, "init: observers registered, current onesignalId=${OneSignal.User.onesignalId}") - DemoLog.d(TAG, "OneSignal ID: ${OneSignal.User.onesignalId ?: "not set"}") + DemoLog.d("init: observers registered, current onesignalId=${OneSignal.User.onesignalId}") + DemoLog.d("OneSignal ID: ${OneSignal.User.onesignalId ?: "not set"}") } // IPermissionObserver @@ -132,7 +131,7 @@ class MainViewModel(application: Application) : AndroidViewModel(application), I // IUserStateObserver - called when user changes (login/logout) override fun onUserStateChange(state: UserChangedState) { - DemoLog.d(TAG, "onUserStateChange fired: ${state.current.onesignalId}") + DemoLog.d("onUserStateChange fired: ${state.current.onesignalId}") _oneSignalId.postValue(state.current.onesignalId) viewModelScope.launch(Dispatchers.Main) { loadExistingAliases() @@ -234,7 +233,7 @@ class MainViewModel(application: Application) : AndroidViewModel(application), I } catch (e: CancellationException) { throw e } catch (e: Exception) { - DemoLog.e(TAG, "Error fetching user data", e) + DemoLog.e("Error fetching user data", e) withContext(Dispatchers.Main) { if (requestId != fetchRequestSequence) return@withContext logError("Failed to fetch user data: ${e.message}") @@ -298,7 +297,7 @@ class MainViewModel(application: Application) : AndroidViewModel(application), I repository.updateUserJwt(externalUserId, jwtToken) withContext(Dispatchers.Main) { SharedPreferenceUtil.cacheJwtToken(getApplication(), jwtToken) - DemoLog.i(TAG, "Updated JWT for: $externalUserId") + DemoLog.i("Updated JWT for: $externalUserId") } } } @@ -327,7 +326,7 @@ class MainViewModel(application: Application) : AndroidViewModel(application), I fun setUseIdentityVerification(enabled: Boolean) { SharedPreferenceUtil.cacheIdentityVerification(getApplication(), enabled) _useIdentityVerification.value = enabled - DemoLog.i(TAG, if (enabled) "Identity verification enabled" else "Identity verification disabled") + DemoLog.i(if (enabled) "Identity verification enabled" else "Identity verification disabled") } // Consent required @@ -335,7 +334,7 @@ class MainViewModel(application: Application) : AndroidViewModel(application), I repository.setConsentRequired(required) SharedPreferenceUtil.cacheConsentRequired(getApplication(), required) _consentRequired.value = required - DemoLog.i(TAG, if (required) "Consent required enabled" else "Consent required disabled") + DemoLog.i(if (required) "Consent required enabled" else "Consent required disabled") } // Privacy consent @@ -343,7 +342,7 @@ class MainViewModel(application: Application) : AndroidViewModel(application), I repository.setPrivacyConsent(granted) SharedPreferenceUtil.cacheUserPrivacyConsent(getApplication(), granted) _privacyConsentGiven.value = granted - DemoLog.i(TAG, if (granted) "Consent granted" else "Consent revoked") + DemoLog.i(if (granted) "Consent granted" else "Consent revoked") } // Alias operations (single and batch) @@ -354,7 +353,7 @@ class MainViewModel(application: Application) : AndroidViewModel(application), I aliasesList.removeAll { it.first == label } aliasesList.add(Pair(label, id)) refreshAliases() - DemoLog.i(TAG, "Alias added: $label") + DemoLog.i("Alias added: $label") } } } @@ -369,7 +368,7 @@ class MainViewModel(application: Application) : AndroidViewModel(application), I aliasesList.add(Pair(label, id)) } refreshAliases() - DemoLog.i(TAG, "${pairs.size} alias(es) added") + DemoLog.i("${pairs.size} alias(es) added") } } } @@ -380,7 +379,7 @@ class MainViewModel(application: Application) : AndroidViewModel(application), I withContext(Dispatchers.Main) { aliasesList.removeAll { it.first == label } refreshAliases() - DemoLog.i(TAG, "Alias removed: $label") + DemoLog.i("Alias removed: $label") } } } @@ -391,7 +390,7 @@ class MainViewModel(application: Application) : AndroidViewModel(application), I withContext(Dispatchers.Main) { aliasesList.removeAll { it.first in labels } refreshAliases() - DemoLog.i(TAG, "${labels.size} alias(es) removed") + DemoLog.i("${labels.size} alias(es) removed") } } } @@ -405,7 +404,7 @@ class MainViewModel(application: Application) : AndroidViewModel(application), I emailsList.add(email) refreshEmails() } - DemoLog.i(TAG, "Email added: $email") + DemoLog.i("Email added: $email") } } } @@ -416,7 +415,7 @@ class MainViewModel(application: Application) : AndroidViewModel(application), I withContext(Dispatchers.Main) { emailsList.remove(email) refreshEmails() - DemoLog.i(TAG, "Email removed: $email") + DemoLog.i("Email removed: $email") } } } @@ -430,7 +429,7 @@ class MainViewModel(application: Application) : AndroidViewModel(application), I smsNumbersList.add(smsNumber) refreshSmsNumbers() } - DemoLog.i(TAG, "SMS added: $smsNumber") + DemoLog.i("SMS added: $smsNumber") } } } @@ -441,7 +440,7 @@ class MainViewModel(application: Application) : AndroidViewModel(application), I withContext(Dispatchers.Main) { smsNumbersList.remove(smsNumber) refreshSmsNumbers() - DemoLog.i(TAG, "SMS removed: $smsNumber") + DemoLog.i("SMS removed: $smsNumber") } } } @@ -452,7 +451,7 @@ class MainViewModel(application: Application) : AndroidViewModel(application), I repository.addTag(key, value) withContext(Dispatchers.Main) { loadExistingTags() - DemoLog.i(TAG, "Tag added: $key") + DemoLog.i("Tag added: $key") } } } @@ -463,7 +462,7 @@ class MainViewModel(application: Application) : AndroidViewModel(application), I repository.addTags(map) withContext(Dispatchers.Main) { loadExistingTags() - DemoLog.i(TAG, "${pairs.size} tag(s) added") + DemoLog.i("${pairs.size} tag(s) added") } } } @@ -473,7 +472,7 @@ class MainViewModel(application: Application) : AndroidViewModel(application), I repository.removeTag(key) withContext(Dispatchers.Main) { loadExistingTags() - DemoLog.i(TAG, "Tag removed: $key") + DemoLog.i("Tag removed: $key") } } } @@ -483,7 +482,7 @@ class MainViewModel(application: Application) : AndroidViewModel(application), I repository.removeTags(keys) withContext(Dispatchers.Main) { loadExistingTags() - DemoLog.i(TAG, "${keys.size} tag(s) removed") + DemoLog.i("${keys.size} tag(s) removed") } } } @@ -496,7 +495,7 @@ class MainViewModel(application: Application) : AndroidViewModel(application), I triggersList.removeAll { it.first == key } triggersList.add(Pair(key, value)) refreshTriggers() - DemoLog.i(TAG, "Trigger added: $key") + DemoLog.i("Trigger added: $key") } } } @@ -511,7 +510,7 @@ class MainViewModel(application: Application) : AndroidViewModel(application), I triggersList.add(Pair(key, value)) } refreshTriggers() - DemoLog.i(TAG, "${pairs.size} trigger(s) added") + DemoLog.i("${pairs.size} trigger(s) added") } } } @@ -522,7 +521,7 @@ class MainViewModel(application: Application) : AndroidViewModel(application), I withContext(Dispatchers.Main) { triggersList.removeAll { it.first == key } refreshTriggers() - DemoLog.i(TAG, "Trigger removed: $key") + DemoLog.i("Trigger removed: $key") } } } @@ -533,7 +532,7 @@ class MainViewModel(application: Application) : AndroidViewModel(application), I withContext(Dispatchers.Main) { triggersList.removeAll { it.first in keys } refreshTriggers() - DemoLog.i(TAG, "${keys.size} trigger(s) removed") + DemoLog.i("${keys.size} trigger(s) removed") } } } @@ -545,7 +544,7 @@ class MainViewModel(application: Application) : AndroidViewModel(application), I withContext(Dispatchers.Main) { triggersList.clear() refreshTriggers() - DemoLog.i(TAG, "All triggers cleared") + DemoLog.i("All triggers cleared") } } } @@ -554,21 +553,21 @@ class MainViewModel(application: Application) : AndroidViewModel(application), I fun sendOutcome(name: String) { viewModelScope.launch(Dispatchers.IO) { repository.sendOutcome(name) - withContext(Dispatchers.Main) { DemoLog.i(TAG, "Outcome sent: $name") } + withContext(Dispatchers.Main) { DemoLog.i("Outcome sent: $name") } } } fun sendUniqueOutcome(name: String) { viewModelScope.launch(Dispatchers.IO) { repository.sendUniqueOutcome(name) - withContext(Dispatchers.Main) { DemoLog.i(TAG, "Unique outcome sent: $name") } + withContext(Dispatchers.Main) { DemoLog.i("Unique outcome sent: $name") } } } fun sendOutcomeWithValue(name: String, value: Float) { viewModelScope.launch(Dispatchers.IO) { repository.sendOutcomeWithValue(name, value) - withContext(Dispatchers.Main) { DemoLog.i(TAG, "Outcome sent: $name = $value") } + withContext(Dispatchers.Main) { DemoLog.i("Outcome sent: $name = $value") } } } @@ -576,7 +575,7 @@ class MainViewModel(application: Application) : AndroidViewModel(application), I fun trackEvent(name: String, properties: Map?) { viewModelScope.launch(Dispatchers.IO) { repository.trackEvent(name, properties) - withContext(Dispatchers.Main) { DemoLog.i(TAG, "Event tracked: $name") } + withContext(Dispatchers.Main) { DemoLog.i("Event tracked: $name") } } } @@ -586,7 +585,7 @@ class MainViewModel(application: Application) : AndroidViewModel(application), I repository.setPushEnabled(enabled) withContext(Dispatchers.Main) { _pushEnabled.value = enabled - DemoLog.i(TAG, if (enabled) "Push enabled" else "Push disabled") + DemoLog.i(if (enabled) "Push enabled" else "Push disabled") } } } @@ -614,7 +613,7 @@ class MainViewModel(application: Application) : AndroidViewModel(application), I repository.setInAppMessagesPaused(paused) SharedPreferenceUtil.cacheInAppMessagingPausedStatus(getApplication(), paused) _inAppMessagesPaused.value = paused - DemoLog.i(TAG, if (paused) "In-app messages paused" else "In-app messages resumed") + DemoLog.i(if (paused) "In-app messages paused" else "In-app messages resumed") } // Location @@ -622,19 +621,19 @@ class MainViewModel(application: Application) : AndroidViewModel(application), I repository.setLocationShared(shared) SharedPreferenceUtil.cacheLocationSharedStatus(getApplication(), shared) _locationShared.value = shared - DemoLog.i(TAG, if (shared) "Location sharing enabled" else "Location sharing disabled") + DemoLog.i(if (shared) "Location sharing enabled" else "Location sharing disabled") } fun checkLocationShared(): Boolean { val shared = repository.isLocationShared() - DemoLog.i(TAG, "Location shared: $shared") + DemoLog.i("Location shared: $shared") return shared } fun promptLocation() { viewModelScope.launch(Dispatchers.IO) { repository.promptLocation() - withContext(Dispatchers.Main) { DemoLog.i(TAG, "Location permission requested") } + withContext(Dispatchers.Main) { DemoLog.i("Location permission requested") } } } @@ -645,7 +644,7 @@ class MainViewModel(application: Application) : AndroidViewModel(application), I val success = repository.sendNotification(type) withContext(Dispatchers.Main) { if (success) { - DemoLog.i(TAG, "Notification sent: ${type.title}") + DemoLog.i("Notification sent: ${type.title}") } else { logError("Failed to send notification: ${type.title}") } @@ -659,7 +658,7 @@ class MainViewModel(application: Application) : AndroidViewModel(application), I val success = repository.sendCustomNotification(title, body) withContext(Dispatchers.Main) { if (success) { - DemoLog.i(TAG, "Notification sent: $title") + DemoLog.i("Notification sent: $title") } else { logError("Failed to send notification: $title") } @@ -669,14 +668,14 @@ class MainViewModel(application: Application) : AndroidViewModel(application), I fun clearAllNotifications() { OneSignal.Notifications.clearAllNotifications() - DemoLog.i(TAG, "All notifications cleared") + DemoLog.i("All notifications cleared") } // Notification service extension fun setNotificationExtensionOptions(options: NotificationExtensionOptions) { SharedPreferenceUtil.cacheNotificationExtensionOptions(getApplication(), options) _notificationExtensionOptions.value = options - DemoLog.i(TAG, "Notification service extension options: $options") + DemoLog.i("Notification service extension options: $options") } fun sendInAppMessage(title: String, triggerKey: String, triggerValue: String) { @@ -686,13 +685,13 @@ class MainViewModel(application: Application) : AndroidViewModel(application), I triggersList.removeAll { it.first == triggerKey } triggersList.add(Pair(triggerKey, triggerValue)) refreshTriggers() - DemoLog.i(TAG, "Sent In-App Message: $title") + DemoLog.i("Sent In-App Message: $title") } } } - private fun logError(message: String) = DemoLog.e(TAG, message) - private fun logDebug(message: String) = DemoLog.d(TAG, message) + private fun logError(message: String) = DemoLog.e(message) + private fun logDebug(message: String) = DemoLog.d(message) override fun onPushSubscriptionChange(state: PushSubscriptionChangedState) { _pushSubscriptionId.postValue(state.current.id) @@ -700,7 +699,7 @@ class MainViewModel(application: Application) : AndroidViewModel(application), I } override fun onUserJwtInvalidated(event: UserJwtInvalidatedEvent) { - DemoLog.w(TAG, "JWT invalidated for externalId: ${event.externalId}") + DemoLog.w("JWT invalidated for externalId: ${event.externalId}") } override fun onCleared() { diff --git a/examples/demo/app/src/main/java/com/onesignal/example/util/DemoLog.kt b/examples/demo/app/src/main/java/com/onesignal/example/util/DemoLog.kt index ac6985b44..d211c0104 100644 --- a/examples/demo/app/src/main/java/com/onesignal/example/util/DemoLog.kt +++ b/examples/demo/app/src/main/java/com/onesignal/example/util/DemoLog.kt @@ -3,32 +3,33 @@ package com.onesignal.example.util import android.util.Log /** - * Logging for the demo app. Marks both halves of every line with `[Demo]`, so `logcat -s` can - * filter on the tag and a line is still recognizable when only the message column is in view. + * Logging for the demo app. Marks every line with `[OneSignal]`. * * ``` - * DemoLog.d(TAG, "Sending notification: Simple") - * // D/[Demo]MainViewModel: [Demo] Sending notification: Simple + * DemoLog.d("Sending notification: Simple") + * // D/OneSignalDemo: [OneSignal] Sending notification: Simple * ``` * - * Pass the plain class name as the tag. This adds the prefix. - * * SDK output that MainApplication's log listener forwards does not come through here. Those * lines belong to the SDK, and marking them would bury the demo's own output when you grep. */ object DemoLog { - private const val PREFIX = "[Demo]" + private const val TAG = "OneSignalDemo" + private const val MESSAGE_PREFIX = "[OneSignal]" + + fun v(message: String) = Log.v(TAG, formatMessage(message)) - fun v(tag: String, message: String) = Log.v(PREFIX + tag, "$PREFIX $message") + fun d(message: String) = Log.d(TAG, formatMessage(message)) - fun d(tag: String, message: String) = Log.d(PREFIX + tag, "$PREFIX $message") + fun i(message: String) = Log.i(TAG, formatMessage(message)) - fun i(tag: String, message: String) = Log.i(PREFIX + tag, "$PREFIX $message") + fun w(message: String) = Log.w(TAG, formatMessage(message)) - fun w(tag: String, message: String) = Log.w(PREFIX + tag, "$PREFIX $message") + fun e(message: String) = Log.e(TAG, formatMessage(message)) - fun e(tag: String, message: String) = Log.e(PREFIX + tag, "$PREFIX $message") + fun e(message: String, throwable: Throwable) = + Log.e(TAG, formatMessage(message), throwable) - fun e(tag: String, message: String, throwable: Throwable) = - Log.e(PREFIX + tag, "$PREFIX $message", throwable) + private fun formatMessage(message: String): String = + if (message.startsWith(MESSAGE_PREFIX)) message else "$MESSAGE_PREFIX $message" } diff --git a/examples/demo/app/src/main/java/com/onesignal/example/util/NotificationClickEventJson.kt b/examples/demo/app/src/main/java/com/onesignal/example/util/NotificationClickEventJson.kt new file mode 100644 index 000000000..9abfe1f80 --- /dev/null +++ b/examples/demo/app/src/main/java/com/onesignal/example/util/NotificationClickEventJson.kt @@ -0,0 +1,75 @@ +package com.onesignal.example.util + +import com.onesignal.notifications.IActionButton +import com.onesignal.notifications.INotification +import com.onesignal.notifications.INotificationClickEvent +import org.json.JSONArray +import org.json.JSONObject + +fun INotificationClickEvent.toJson(): JSONObject = + JSONObject().apply { + put("notification", notification.toJson()) + put( + "result", + JSONObject().apply { + put("actionId", result.actionId) + put("url", result.url) + }, + ) + } + +@Suppress("DEPRECATION") +private fun INotification.toJson(): JSONObject = + JSONObject().apply { + put("androidNotificationId", androidNotificationId) + put("notificationId", notificationId) + put("templateName", templateName) + put("templateId", templateId) + put("title", title) + put("body", body) + put("additionalData", additionalData) + put("smallIcon", smallIcon) + put("largeIcon", largeIcon) + put("bigPicture", bigPicture) + put("smallIconAccentColor", smallIconAccentColor) + put("launchURL", launchURL) + put("sound", sound) + put("ledColor", ledColor) + put("lockScreenVisibility", lockScreenVisibility) + put("groupKey", groupKey) + put("groupMessage", groupMessage) + put("actionButtons", actionButtons.toActionButtonsJson()) + put("fromProjectNumber", fromProjectNumber) + put( + "backgroundImageLayout", + backgroundImageLayout?.let { + JSONObject() + .put("image", it.image) + .put("titleTextColor", it.titleTextColor) + .put("bodyTextColor", it.bodyTextColor) + }, + ) + put("collapseId", collapseId) + put("priority", priority) + put("sentTime", sentTime) + put("ttl", ttl) + put("groupedNotifications", groupedNotifications.toNotificationsJson()) + put("rawPayload", rawPayload) + } + +private fun List?.toNotificationsJson(): JSONArray = + JSONArray().apply { + this@toNotificationsJson?.forEach { put(it.toJson()) } + } + +private fun List?.toActionButtonsJson(): JSONArray = + JSONArray().apply { + this@toActionButtonsJson?.forEach { + put( + JSONObject() + .put("id", it.id) + .put("text", it.text) + .put("icon", it.icon), + ) + } + } diff --git a/examples/demo/app/src/main/java/com/onesignal/example/util/TooltipHelper.kt b/examples/demo/app/src/main/java/com/onesignal/example/util/TooltipHelper.kt index 709782732..f0b7b4cdf 100644 --- a/examples/demo/app/src/main/java/com/onesignal/example/util/TooltipHelper.kt +++ b/examples/demo/app/src/main/java/com/onesignal/example/util/TooltipHelper.kt @@ -72,7 +72,7 @@ object TooltipHelper { } } catch (e: Exception) { // Tooltips are non-critical; log and continue - DemoLog.w("TooltipHelper", "Failed to fetch tooltip content: ${e.message}") + DemoLog.w("Failed to fetch tooltip content: ${e.message}") } } }