Add Android Auto notification extension (#3642)

Add Auto notification extension + vector icon

 - Add an option to have notifications show up on Android Auto
 - Apply color filter to MDI notification icons as Android Auto doesn't make them white
 - Switch default notification icon for vector icon
This commit is contained in:
Joris Pelgröm 2023-07-07 17:51:10 +02:00 committed by GitHub
parent ef469fb977
commit 0949d570d5
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 100 additions and 13 deletions

View file

@ -31,6 +31,9 @@ import android.widget.RemoteViews
import android.widget.Toast
import androidx.annotation.RequiresApi
import androidx.biometric.BiometricManager
import androidx.car.app.notification.CarAppExtender
import androidx.car.app.notification.CarNotificationManager
import androidx.car.app.notification.CarPendingIntent
import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationManagerCompat
import androidx.core.app.RemoteInput
@ -73,6 +76,8 @@ import io.homeassistant.companion.android.sensors.NotificationSensorManager
import io.homeassistant.companion.android.sensors.SensorReceiver
import io.homeassistant.companion.android.settings.SettingsActivity
import io.homeassistant.companion.android.util.UrlUtil
import io.homeassistant.companion.android.util.cancelGroupIfNeeded
import io.homeassistant.companion.android.vehicle.HaCarAppService
import io.homeassistant.companion.android.websocket.WebsocketManager
import io.homeassistant.companion.android.webview.WebViewActivity
import kotlinx.coroutines.CoroutineScope
@ -123,6 +128,7 @@ class MessagingManager @Inject constructor(
const val PERSISTENT = "persistent"
const val CHRONOMETER = "chronometer"
const val WHEN = "when"
const val CAR_UI = "car_ui"
const val KEY_TEXT_REPLY = "key_text_reply"
const val INTENT_CLASS_NAME = "intent_class_name"
const val URI = "URI"
@ -945,25 +951,42 @@ class MessagingManager @Inject constructor(
handleChronometer(notificationBuilder, data)
val useCarNotification = handleCarUiVisible(context, notificationBuilder, data)
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
handleLegacyPriority(notificationBuilder, data)
handleLegacyLedColor(notificationBuilder, data)
handleLegacyVibrationPattern(notificationBuilder, data)
}
notificationManagerCompat.apply {
Log.d(TAG, "Show notification with tag \"$tag\" and id \"$messageId\"")
notify(tag, messageId, notificationBuilder.build())
if (!group.isNullOrBlank()) {
Log.d(TAG, "Show group notification with tag \"$group\" and id \"$groupId\"")
notify(group, groupId, getGroupNotificationBuilder(context, channelId, group, data).build())
} else {
if (!previousGroup.isBlank()) {
Log.d(TAG, "Show notification with tag \"$tag\" and id \"$messageId\"")
if (useCarNotification) {
CarNotificationManager.from(context).apply {
notify(tag, messageId, notificationBuilder)
if (!group.isNullOrBlank()) {
Log.d(TAG, "Show group notification with tag \"$group\" and id \"$groupId\"")
notify(group, groupId, getGroupNotificationBuilder(context, channelId, group, data))
} else if (previousGroup.isNotBlank()) {
val systemManager = context.getSystemService<NotificationManager>() ?: return@apply
Log.d(
TAG,
"Remove group notification with tag \"$previousGroup\" and id \"$previousGroupId\""
)
notificationManagerCompat.cancelGroupIfNeeded(previousGroup, previousGroupId)
cancelGroupIfNeeded(systemManager, previousGroup, previousGroupId)
}
}
} else {
notificationManagerCompat.apply {
notify(tag, messageId, notificationBuilder.build())
if (!group.isNullOrBlank()) {
Log.d(TAG, "Show group notification with tag \"$group\" and id \"$groupId\"")
notify(group, groupId, getGroupNotificationBuilder(context, channelId, group, data).build())
} else if (previousGroup.isNotBlank()) {
Log.d(
TAG,
"Remove group notification with tag \"$previousGroup\" and id \"$previousGroupId\""
)
cancelGroupIfNeeded(previousGroup, previousGroupId)
}
}
}
@ -992,6 +1015,32 @@ class MessagingManager @Inject constructor(
}
}
private fun handleCarUiVisible(
context: Context,
builder: NotificationCompat.Builder,
data: Map<String, String>
): Boolean {
if (data[CAR_UI]?.toBoolean() == true && Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val carIntent = Intent(Intent.ACTION_VIEW).apply {
component = ComponentName(context, HaCarAppService::class.java)
}
builder.extend(
CarAppExtender.Builder()
.setContentIntent(
CarPendingIntent.getCarApp(
context,
carIntent.hashCode(),
carIntent,
PendingIntent.FLAG_IMMUTABLE
)
)
.build()
)
return true
}
return false
}
private fun handleContentIntent(
builder: NotificationCompat.Builder,
data: Map<String, String>

View file

@ -0,0 +1,13 @@
package io.homeassistant.companion.android.util
import android.app.NotificationManager
import androidx.car.app.notification.CarNotificationManager
import io.homeassistant.companion.android.common.util.cancelNotificationGroupIfNeeded
fun CarNotificationManager.cancelGroupIfNeeded(manager: NotificationManager, tag: String?, id: Int): Boolean =
cancelNotificationGroupIfNeeded(
manager,
tag,
id,
this::cancel
)

View file

@ -4,6 +4,8 @@ import android.app.NotificationChannel
import android.app.NotificationManager
import android.content.Context
import android.graphics.Color
import android.graphics.PorterDuff
import android.graphics.PorterDuffColorFilter
import android.media.AudioAttributes
import android.media.AudioManager
import android.media.RingtoneManager
@ -16,6 +18,7 @@ import androidx.core.app.NotificationManagerCompat
import androidx.core.content.ContextCompat
import androidx.core.text.HtmlCompat
import com.mikepenz.iconics.IconicsDrawable
import com.mikepenz.iconics.utils.colorFilter
import com.mikepenz.iconics.utils.toAndroidIconCompat
import com.vdurmont.emoji.EmojiParser
import io.homeassistant.companion.android.common.R
@ -211,7 +214,7 @@ fun handleSmallIcon(
val iconDrawable =
IconicsDrawable(context, "cmd-$iconName")
if (iconDrawable.icon != null) {
builder.setSmallIcon(iconDrawable.toAndroidIconCompat())
builder.setSmallIcon(iconDrawable.colorFilter { PorterDuffColorFilter(Color.WHITE, PorterDuff.Mode.SRC_IN) }.toAndroidIconCompat())
} else {
builder.setSmallIcon(R.drawable.ic_stat_ic_notification)
}

View file

@ -25,11 +25,24 @@ fun NotificationManagerCompat.getActiveNotification(tag: String?, id: Int): Stat
}
}
fun NotificationManagerCompat.cancelGroupIfNeeded(tag: String?, id: Int): Boolean {
fun NotificationManagerCompat.cancelGroupIfNeeded(tag: String?, id: Int): Boolean =
cancelNotificationGroupIfNeeded(
this.getNotificationManager(),
tag,
id,
this::cancel
)
fun cancelNotificationGroupIfNeeded(
notificationManager: NotificationManager,
tag: String?,
id: Int,
cancel: (String, Int) -> Unit
): Boolean {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
Log.d(TAG, "Cancel notification with tag \"$tag\" and id \"$id\"")
val currentActiveNotifications = this.getNotificationManager().activeNotifications
val currentActiveNotifications = notificationManager.activeNotifications
Log.d(TAG, "Check if the notification is in a group...")
// Get group key from the current notification
@ -82,7 +95,7 @@ fun NotificationManagerCompat.cancelGroupIfNeeded(tag: String?, id: Int): Boolea
return if (group != null) {
val groupId = group.hashCode()
Log.d(TAG, "Cancel group notification with tag \"$group\" and id \"$groupId\"")
this.cancel(group, groupId)
cancel(group, groupId)
true
} else {
Log.d(TAG, "Cannot cancel group notification, because group tag is empty. Anyway cancel notification.")

Binary file not shown.

Before

Width:  |  Height:  |  Size: 934 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 620 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.9 KiB

View file

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#FFFFFF"
android:pathData="M21.8,13H20V21H13V17.67L15.79,14.88L16.5,15C17.66,15 18.6,14.06 18.6,12.9C18.6,11.74 17.66,10.8 16.5,10.8A2.1,2.1 0 0,0 14.4,12.9L14.5,13.61L13,15.13V9.65C13.66,9.29 14.1,8.6 14.1,7.8A2.1,2.1 0 0,0 12,5.7A2.1,2.1 0 0,0 9.9,7.8C9.9,8.6 10.34,9.29 11,9.65V15.13L9.5,13.61L9.6,12.9A2.1,2.1 0 0,0 7.5,10.8A2.1,2.1 0 0,0 5.4,12.9A2.1,2.1 0 0,0 7.5,15L8.21,14.88L11,17.67V21H4V13H2.25C1.83,13 1.42,13 1.42,12.79C1.43,12.57 1.85,12.15 2.28,11.72L11,3C11.33,2.67 11.67,2.33 12,2.33C12.33,2.33 12.67,2.67 13,3L17,7V6H19V9L21.78,11.78C22.18,12.18 22.59,12.59 22.6,12.8C22.6,13 22.2,13 21.8,13M7.5,12A0.9,0.9 0 0,1 8.4,12.9A0.9,0.9 0 0,1 7.5,13.8A0.9,0.9 0 0,1 6.6,12.9A0.9,0.9 0 0,1 7.5,12M16.5,12C17,12 17.4,12.4 17.4,12.9C17.4,13.4 17,13.8 16.5,13.8A0.9,0.9 0 0,1 15.6,12.9A0.9,0.9 0 0,1 16.5,12M12,6.9C12.5,6.9 12.9,7.3 12.9,7.8C12.9,8.3 12.5,8.7 12,8.7C11.5,8.7 11.1,8.3 11.1,7.8C11.1,7.3 11.5,6.9 12,6.9Z" />
</vector>