Skip to content

Commit

Permalink
Merge pull request #9 from boswelja/better-notifications
Browse files Browse the repository at this point in the history
Better notifications
  • Loading branch information
boswelja authored Aug 3, 2021
2 parents d6ca8bf + 4f93cbc commit a3d562f
Show file tree
Hide file tree
Showing 5 changed files with 84 additions and 63 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import android.graphics.drawable.Icon
import android.provider.CalendarContract
import android.service.notification.NotificationListenerService
import android.service.notification.StatusBarNotification
import androidx.core.app.NotificationCompat
import androidx.core.content.getSystemService
import com.boswelja.autoevent.R
import com.boswelja.autoevent.eventextractor.Event
Expand All @@ -20,11 +19,10 @@ import kotlinx.coroutines.cancel
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch
import java.text.DateFormat

class NotiEventExtractorService : NotificationListenerService() {

private val notiIdMap = mutableMapOf<Int, Event>()
private val handledEventMap = mutableMapOf<Int, Int>()
private val coroutineScope = CoroutineScope(Dispatchers.Default)

private var ignoredPackages: List<String> = emptyList()
Expand Down Expand Up @@ -56,30 +54,26 @@ class NotiEventExtractorService : NotificationListenerService() {
if (sbn == null) return
if (ignoredPackages.contains(sbn.packageName)) return
coroutineScope.launch {
val text = sbn.notification.allText()
eventExtractor.extractEventFrom(text)?.let { event ->
val details = sbn.getDetails(packageManager)
val detailsHash = details.hashCode()
eventExtractor.extractEventFrom(details.text)?.let { event ->
val eventHash = event.hashCode()
// Only post notification if we haven't already got the same event info
val oldEventForNoti = notiIdMap[sbn.id]
if (oldEventForNoti != event) {
val oldEventForNoti = handledEventMap[detailsHash]
if (oldEventForNoti != eventHash) {
// Cancel previous notification, update our map and post new notification
oldEventForNoti?.let { notificationManager.cancel(it.hashCode()) }
notiIdMap[sbn.id] = event
postEventNotification(event, sbn.packageName)
oldEventForNoti?.let { notificationManager.cancel(it) }
handledEventMap[detailsHash] = eventHash
postEventNotification(event, details)
}
}
}
}

private fun Notification.allText(): String {
val messageStyle = NotificationCompat.MessagingStyle
.extractMessagingStyleFromNotification(this)
val messageStyleText = messageStyle?.messages
?.filter { !it.text.isNullOrBlank() }
?.joinToString { it.text!! }
return messageStyleText ?: extras.getString(Notification.EXTRA_TEXT, "")
}

private fun postEventNotification(eventDetails: Event, packageName: String) {
private fun postEventNotification(
eventDetails: Event,
notificationDetails: NotificationDetails
) {
val notificationId = eventDetails.hashCode()
val createEventIntent = Intent(Intent.ACTION_INSERT)
.setData(CalendarContract.Events.CONTENT_URI)
Expand All @@ -92,26 +86,22 @@ class NotiEventExtractorService : NotificationListenerService() {
val ignoreAppIntent = Intent(this, NotiActionHandler::class.java)
.setAction(NotiActionHandler.IGNORE_PACKAGE_ACTION)
.putExtra(NotiActionHandler.EXTRA_NOTIFICATION_ID, notificationId)
.putExtra(NotiActionHandler.EXTRA_PACKAGE_NAME, packageName)
.putExtra(NotiActionHandler.EXTRA_PACKAGE_NAME, notificationDetails.packageName)
val ignoreAppPendingIntent = PendingIntent.getBroadcast(
this, notificationId, ignoreAppIntent, PendingIntent.FLAG_IMMUTABLE
)

val dateFormatter = DateFormat.getDateTimeInstance()
val formattedStart = dateFormatter.format(eventDetails.startDateTime.time)
val contentText = if (notificationDetails.senderName != null) {
getString(R.string.event_noti_from_summary, notificationDetails.senderName)
} else {
getString(R.string.event_noti_generic_summary, notificationDetails.packageLabel)
}

val notification = Notification.Builder(this, EventDetailsChannel)
.setContentTitle(getString(R.string.event_noti_title))
.setContentText(getString(R.string.event_noti_summary, formattedStart))
.setContentText(contentText)
.setSmallIcon(R.drawable.noti_ic_event_found)
.setStyle(createNotificationStyleForEvent(eventDetails))
.addAction(
Notification.Action.Builder(
Icon.createWithResource(this, R.drawable.noti_ic_event_add),
getString(R.string.event_add_to_calendar),
createPendingIntent
).build()
)
.setContentIntent(createPendingIntent)
.addAction(
Notification.Action.Builder(
Icon.createWithResource(this, R.drawable.noti_ic_event_block),
Expand All @@ -124,19 +114,6 @@ class NotiEventExtractorService : NotificationListenerService() {
notificationManager.notify(notificationId, notification)
}

private fun createNotificationStyleForEvent(event: Event): Notification.Style {
val formatter =
if (event.isAllDay) DateFormat.getDateInstance() else DateFormat.getDateTimeInstance()
val formattedStart = formatter.format(event.startDateTime)
val formattedEnd = formatter.format(event.endDateTime)
var text = getString(R.string.event_from, formattedStart)
text += "\n${getString(R.string.event_to, formattedEnd)}"
event.extras.address?.let { address ->
text += "\n${getString(R.string.event_at, address)}"
}
return Notification.BigTextStyle().bigText(text)
}

private fun createNotificationChannel() {
NotificationChannel(
EventDetailsChannel,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package com.boswelja.autoevent.notificationeventextractor

import android.app.Notification
import android.content.pm.PackageManager
import android.service.notification.StatusBarNotification
import androidx.core.app.NotificationCompat

/**
* A data class holding info about a notification.
* @param text The notification text.
* @param senderName The name of the person or group who sent the text, or null if not found.
* @param packageLabel The label of the package that posted the notification.
*/
data class NotificationDetails(
val text: String,
val senderName: String?,
val packageLabel: String,
val packageName: String
)

/**
* Extract some details about the [StatusBarNotification].
* @return A [NotificationDetails] containing data about this notification.
*/
internal fun StatusBarNotification.getDetails(packageManager: PackageManager): NotificationDetails {
// Load common details
val packageLabel = packageManager.getApplicationInfo(packageName, 0)
.loadLabel(packageManager)

// Load message style if it exists
val messageStyle = NotificationCompat.MessagingStyle
.extractMessagingStyleFromNotification(notification)

if (messageStyle != null) {
// Message style found, get details
val text = messageStyle.messages
.filter { !it.text.isNullOrBlank() }
.joinToString { it.text!! }
val sender = messageStyle.conversationTitle
?: messageStyle.messages.firstOrNull { it.person != null }?.person?.name

return NotificationDetails(
text,
sender?.toString(),
packageLabel.toString(),
packageName
)
} else {
// No message style found
val text = notification.extras.getString(Notification.EXTRA_TEXT, "")
return NotificationDetails(
text,
null,
packageLabel.toString(),
packageName
)
}
}
10 changes: 0 additions & 10 deletions app/src/main/res/drawable/noti_ic_event_add.xml

This file was deleted.

8 changes: 2 additions & 6 deletions app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,7 @@
<string name="noti_listener_settings_enabled_desc">Manage what we can access</string>
<string name="noti_listener_access_summary">AutoEvent relies on having access to your notifications. This is to scan notification text for possible events.</string>

<string name="event_from">Starting %s</string>
<string name="event_to">Finishing %s</string>
<string name="event_at">At %s</string>

<string name="event_ignore_for_app">Ignore this App</string>
<string name="event_add_to_calendar">Add to Calendar</string>

<string name="noti_extractor_settings_title">Notification Listener Settings</string>
<string name="noti_extractor_blocklist_title">Blocklist</string>
Expand All @@ -26,7 +21,8 @@

<string name="event_noti_channel_name">Discovered Events</string>
<string name="event_noti_title">Event Found</string>
<string name="event_noti_summary">We found an event starting at %s</string>
<string name="event_noti_from_summary">%s invited you to an event! Tap to add to your calendar</string>
<string name="event_noti_generic_summary">Found an event from %s! Tap to add to your calendar</string>

<string name="language_detect" translatable="false">Auto (%s)</string>

Expand Down
4 changes: 2 additions & 2 deletions settings.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ pluginManagement {
mavenCentral()
}
plugins {
id("com.android.application") version "7.1.0-alpha05"
id("com.android.library") version "7.1.0-alpha05"
id("com.android.application") version "7.1.0-alpha06"
id("com.android.library") version "7.1.0-alpha06"
id("org.jetbrains.kotlin.android") version "1.5.10"
}
resolutionStrategy {
Expand Down

0 comments on commit a3d562f

Please sign in to comment.