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 android.widget.Toast
import androidx.annotation.RequiresApi import androidx.annotation.RequiresApi
import androidx.biometric.BiometricManager 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.NotificationCompat
import androidx.core.app.NotificationManagerCompat import androidx.core.app.NotificationManagerCompat
import androidx.core.app.RemoteInput 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.sensors.SensorReceiver
import io.homeassistant.companion.android.settings.SettingsActivity import io.homeassistant.companion.android.settings.SettingsActivity
import io.homeassistant.companion.android.util.UrlUtil 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.websocket.WebsocketManager
import io.homeassistant.companion.android.webview.WebViewActivity import io.homeassistant.companion.android.webview.WebViewActivity
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
@ -123,6 +128,7 @@ class MessagingManager @Inject constructor(
const val PERSISTENT = "persistent" const val PERSISTENT = "persistent"
const val CHRONOMETER = "chronometer" const val CHRONOMETER = "chronometer"
const val WHEN = "when" const val WHEN = "when"
const val CAR_UI = "car_ui"
const val KEY_TEXT_REPLY = "key_text_reply" const val KEY_TEXT_REPLY = "key_text_reply"
const val INTENT_CLASS_NAME = "intent_class_name" const val INTENT_CLASS_NAME = "intent_class_name"
const val URI = "URI" const val URI = "URI"
@ -945,25 +951,42 @@ class MessagingManager @Inject constructor(
handleChronometer(notificationBuilder, data) handleChronometer(notificationBuilder, data)
val useCarNotification = handleCarUiVisible(context, notificationBuilder, data)
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
handleLegacyPriority(notificationBuilder, data) handleLegacyPriority(notificationBuilder, data)
handleLegacyLedColor(notificationBuilder, data) handleLegacyLedColor(notificationBuilder, data)
handleLegacyVibrationPattern(notificationBuilder, data) handleLegacyVibrationPattern(notificationBuilder, data)
} }
notificationManagerCompat.apply { Log.d(TAG, "Show notification with tag \"$tag\" and id \"$messageId\"")
Log.d(TAG, "Show notification with tag \"$tag\" and id \"$messageId\"") if (useCarNotification) {
notify(tag, messageId, notificationBuilder.build()) CarNotificationManager.from(context).apply {
if (!group.isNullOrBlank()) { notify(tag, messageId, notificationBuilder)
Log.d(TAG, "Show group notification with tag \"$group\" and id \"$groupId\"") if (!group.isNullOrBlank()) {
notify(group, groupId, getGroupNotificationBuilder(context, channelId, group, data).build()) Log.d(TAG, "Show group notification with tag \"$group\" and id \"$groupId\"")
} else { notify(group, groupId, getGroupNotificationBuilder(context, channelId, group, data))
if (!previousGroup.isBlank()) { } else if (previousGroup.isNotBlank()) {
val systemManager = context.getSystemService<NotificationManager>() ?: return@apply
Log.d( Log.d(
TAG, TAG,
"Remove group notification with tag \"$previousGroup\" and id \"$previousGroupId\"" "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( private fun handleContentIntent(
builder: NotificationCompat.Builder, builder: NotificationCompat.Builder,
data: Map<String, String> 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.app.NotificationManager
import android.content.Context import android.content.Context
import android.graphics.Color import android.graphics.Color
import android.graphics.PorterDuff
import android.graphics.PorterDuffColorFilter
import android.media.AudioAttributes import android.media.AudioAttributes
import android.media.AudioManager import android.media.AudioManager
import android.media.RingtoneManager import android.media.RingtoneManager
@ -16,6 +18,7 @@ import androidx.core.app.NotificationManagerCompat
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.core.text.HtmlCompat import androidx.core.text.HtmlCompat
import com.mikepenz.iconics.IconicsDrawable import com.mikepenz.iconics.IconicsDrawable
import com.mikepenz.iconics.utils.colorFilter
import com.mikepenz.iconics.utils.toAndroidIconCompat import com.mikepenz.iconics.utils.toAndroidIconCompat
import com.vdurmont.emoji.EmojiParser import com.vdurmont.emoji.EmojiParser
import io.homeassistant.companion.android.common.R import io.homeassistant.companion.android.common.R
@ -211,7 +214,7 @@ fun handleSmallIcon(
val iconDrawable = val iconDrawable =
IconicsDrawable(context, "cmd-$iconName") IconicsDrawable(context, "cmd-$iconName")
if (iconDrawable.icon != null) { if (iconDrawable.icon != null) {
builder.setSmallIcon(iconDrawable.toAndroidIconCompat()) builder.setSmallIcon(iconDrawable.colorFilter { PorterDuffColorFilter(Color.WHITE, PorterDuff.Mode.SRC_IN) }.toAndroidIconCompat())
} else { } else {
builder.setSmallIcon(R.drawable.ic_stat_ic_notification) 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) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
Log.d(TAG, "Cancel notification with tag \"$tag\" and id \"$id\"") 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...") Log.d(TAG, "Check if the notification is in a group...")
// Get group key from the current notification // Get group key from the current notification
@ -82,7 +95,7 @@ fun NotificationManagerCompat.cancelGroupIfNeeded(tag: String?, id: Int): Boolea
return if (group != null) { return if (group != null) {
val groupId = group.hashCode() val groupId = group.hashCode()
Log.d(TAG, "Cancel group notification with tag \"$group\" and id \"$groupId\"") Log.d(TAG, "Cancel group notification with tag \"$group\" and id \"$groupId\"")
this.cancel(group, groupId) cancel(group, groupId)
true true
} else { } else {
Log.d(TAG, "Cannot cancel group notification, because group tag is empty. Anyway cancel notification.") 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>