mirror of
https://github.com/home-assistant/android
synced 2024-10-15 12:32:54 +00:00
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:
parent
ef469fb977
commit
0949d570d5
|
@ -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>
|
||||||
|
|
|
@ -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
|
||||||
|
)
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 |
9
common/src/main/res/drawable/ic_stat_ic_notification.xml
Normal file
9
common/src/main/res/drawable/ic_stat_ic_notification.xml
Normal 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>
|
Loading…
Reference in a new issue