Initial commit of notification support on Wear OS (#3221)

* Initial commit of wear OS notifications

* Update to allow message and title

* Move constants to object to reduce imports

* Split up logic to match phone app

* Update readme and set websocket to false for wear OS

* Bump firebase BOM
This commit is contained in:
Daniel Shokouhi 2023-01-20 11:02:03 -08:00 committed by GitHub
parent 5cd74c0179
commit 346ef3da5f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 538 additions and 305 deletions

View File

@ -17,7 +17,7 @@ If you are looking for documentation around the companion applications check out
- `io.homeassistant.companion.android.minimal`
- `io.homeassistant.companion.android.minimal.debug`
5. Now download the `google-services.json` file and put it in the _home-assistant-Android/app_ folder. This file contains the configuration of the whole project (all four applications). ([You can also use the mock services file instead of generating your own](/.github/mock-google-services.json). The file should contain client IDs for all packages listed above for debugging to work properly. **If you do not generate your own file FCM push notification will never work, only websocket notifications will**)
5. Now download the `google-services.json` file and put it in the _home-assistant-Android/app_ and _home-assistant-Android/wear_ folder. This file contains the configuration of the whole project (all four applications). ([You can also use the mock services file instead of generating your own](/.github/mock-google-services.json). The file should contain client IDs for all packages listed above for debugging to work properly. **If you do not generate your own file FCM push notification will never work, only websocket notifications will**)
6. Start Android Studio, open your source code folder and check if the Gradle build will be successful using Build/Make Module "App". You might have to install the right Android SDK via Tools/SDK Manager first.
7. Run `gradlew assembleDebug` to build all debug versions, this might take a while.
8. If the build is successful, you can run the app by doing the following: click **Run** -> **Run 'app'**.

View File

@ -143,9 +143,6 @@ dependencies {
implementation("org.altbeacon:android-beacon-library:2.19.5")
implementation("com.maltaisn:icondialog:3.3.0")
implementation("com.maltaisn:iconpack-community-material:5.3.45")
implementation("com.vdurmont:emoji-java:5.1.1") {
exclude(group = "org.json", module = "json")
}
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.7.20")
implementation("org.jetbrains.kotlin:kotlin-reflect:1.7.20")
@ -171,7 +168,7 @@ dependencies {
"fullImplementation"("com.google.android.gms:play-services-location:21.0.1")
"fullImplementation"("com.google.android.gms:play-services-home:16.0.0")
"fullImplementation"(platform("com.google.firebase:firebase-bom:30.4.1"))
"fullImplementation"(platform("com.google.firebase:firebase-bom:31.1.1"))
"fullImplementation"("com.google.firebase:firebase-messaging")
"fullImplementation"("io.sentry:sentry-android:6.11.0")
"fullImplementation"("org.jetbrains.kotlinx:kotlinx-coroutines-play-services:1.6.4")

View File

@ -13,7 +13,6 @@ import android.content.Intent
import android.content.pm.PackageManager
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.graphics.Color
import android.media.AudioAttributes
import android.media.AudioManager
import android.media.MediaMetadataRetriever
@ -29,7 +28,6 @@ import android.os.PowerManager
import android.provider.Settings
import android.speech.tts.TextToSpeech
import android.speech.tts.UtteranceProgressListener
import android.text.Spanned
import android.util.Log
import android.view.KeyEvent
import android.widget.RemoteViews
@ -41,22 +39,27 @@ import androidx.core.app.NotificationManagerCompat
import androidx.core.app.RemoteInput
import androidx.core.content.ContextCompat
import androidx.core.content.getSystemService
import androidx.core.text.HtmlCompat
import androidx.core.text.isDigitsOnly
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
import com.fasterxml.jackson.module.kotlin.readValue
import com.mikepenz.iconics.IconicsDrawable
import com.mikepenz.iconics.utils.toAndroidIconCompat
import com.vdurmont.emoji.EmojiParser
import dagger.hilt.android.qualifiers.ApplicationContext
import io.homeassistant.companion.android.R
import io.homeassistant.companion.android.common.data.authentication.AuthenticationRepository
import io.homeassistant.companion.android.common.data.integration.IntegrationRepository
import io.homeassistant.companion.android.common.data.url.UrlRepository
import io.homeassistant.companion.android.common.notifications.NotificationData
import io.homeassistant.companion.android.common.notifications.createChannelID
import io.homeassistant.companion.android.common.notifications.getGroupNotificationBuilder
import io.homeassistant.companion.android.common.notifications.handleChannel
import io.homeassistant.companion.android.common.notifications.handleColor
import io.homeassistant.companion.android.common.notifications.handleSmallIcon
import io.homeassistant.companion.android.common.notifications.handleText
import io.homeassistant.companion.android.common.notifications.parseColor
import io.homeassistant.companion.android.common.notifications.parseVibrationPattern
import io.homeassistant.companion.android.common.notifications.prepareText
import io.homeassistant.companion.android.common.sensors.BluetoothSensorManager
import io.homeassistant.companion.android.common.util.cancel
import io.homeassistant.companion.android.common.util.cancelGroupIfNeeded
import io.homeassistant.companion.android.common.util.generalChannel
import io.homeassistant.companion.android.common.util.getActiveNotification
import io.homeassistant.companion.android.database.notification.NotificationDao
import io.homeassistant.companion.android.database.notification.NotificationItem
@ -86,7 +89,6 @@ import java.io.File
import java.io.FileOutputStream
import java.net.URL
import java.net.URLDecoder
import java.util.Locale
import java.util.UUID
import javax.inject.Inject
import io.homeassistant.companion.android.common.R as commonR
@ -111,25 +113,17 @@ class MessagingManager @Inject constructor(
const val SETTINGS_PREFIX = "settings://"
const val NOTIFICATION_HISTORY = "notification_history"
const val TITLE = "title"
const val MESSAGE = "message"
const val SUBJECT = "subject"
const val IMPORTANCE = "importance"
const val TIMEOUT = "timeout"
const val IMAGE_URL = "image"
const val ICON_URL = "icon_url"
const val VIDEO_URL = "video"
const val VISIBILITY = "visibility"
const val LED_COLOR = "ledColor"
const val VIBRATION_PATTERN = "vibrationPattern"
const val PERSISTENT = "persistent"
const val CHRONOMETER = "chronometer"
const val WHEN = "when"
const val GROUP_PREFIX = "group_"
const val KEY_TEXT_REPLY = "key_text_reply"
const val ALERT_ONCE = "alert_once"
const val INTENT_CLASS_NAME = "intent_class_name"
const val NOTIFICATION_ICON = "notification_icon"
const val URI = "URI"
const val REPLY = "REPLY"
const val BLE_ADVERTISE = "ble_advertise"
@ -138,7 +132,6 @@ class MessagingManager @Inject constructor(
const val PACKAGE_NAME = "package_name"
const val COMMAND = "command"
const val TTS_TEXT = "tts_text"
const val CHANNEL = "channel"
const val CONFIRMATION = "confirmation"
// special intent constants
@ -186,16 +179,6 @@ class MessagingManager @Inject constructor(
const val RM_SILENT = "silent"
const val RM_VIBRATE = "vibrate"
// Channel streams
const val ALARM_STREAM = "alarm_stream"
const val ALARM_STREAM_MAX = "alarm_stream_max"
const val MUSIC_STREAM = "music_stream"
const val NOTIFICATION_STREAM = "notification_stream"
const val RING_STREAM = "ring_stream"
const val SYSTEM_STREAM = "system_stream"
const val CALL_STREAM = "call_stream"
const val DTMF_STREAM = "dtmf_stream"
// Enable/Disable Commands
const val TURN_ON = "turn_on"
const val TURN_OFF = "turn_off"
@ -266,8 +249,13 @@ class MessagingManager @Inject constructor(
val DND_COMMANDS = listOf(DND_ALARMS_ONLY, DND_ALL, DND_NONE, DND_PRIORITY_ONLY)
val RM_COMMANDS = listOf(RM_NORMAL, RM_SILENT, RM_VIBRATE)
val CHANNEL_VOLUME_STREAM = listOf(
ALARM_STREAM, MUSIC_STREAM, NOTIFICATION_STREAM, RING_STREAM, CALL_STREAM,
SYSTEM_STREAM, DTMF_STREAM
NotificationData.ALARM_STREAM,
NotificationData.MUSIC_STREAM,
NotificationData.NOTIFICATION_STREAM,
NotificationData.RING_STREAM,
NotificationData.CALL_STREAM,
NotificationData.SYSTEM_STREAM,
NotificationData.DTMF_STREAM
)
val ENABLE_COMMANDS = listOf(TURN_OFF, TURN_ON)
val FORCE_COMMANDS = listOf(FORCE_OFF, FORCE_ON)
@ -315,7 +303,7 @@ class MessagingManager @Inject constructor(
} else {
val jsonObject = JSONObject(jsonData)
val notificationRow =
NotificationItem(0, now, jsonData[MESSAGE].toString(), jsonObject.toString(), source)
NotificationItem(0, now, jsonData[NotificationData.MESSAGE].toString(), jsonObject.toString(), source)
notificationId = notificationDao.add(notificationRow)
val confirmation = jsonData[CONFIRMATION]?.toBoolean() ?: false
@ -331,25 +319,25 @@ class MessagingManager @Inject constructor(
}
when {
jsonData[MESSAGE] == REQUEST_LOCATION_UPDATE -> {
jsonData[NotificationData.MESSAGE] == REQUEST_LOCATION_UPDATE -> {
Log.d(TAG, "Request location update")
requestAccurateLocationUpdate()
}
jsonData[MESSAGE] == CLEAR_NOTIFICATION && !jsonData["tag"].isNullOrBlank() -> {
jsonData[NotificationData.MESSAGE] == CLEAR_NOTIFICATION && !jsonData["tag"].isNullOrBlank() -> {
Log.d(TAG, "Clearing notification with tag: ${jsonData["tag"]}")
clearNotification(jsonData["tag"]!!)
}
jsonData[MESSAGE] == REMOVE_CHANNEL && !jsonData[CHANNEL].isNullOrBlank() -> {
Log.d(TAG, "Removing Notification channel ${jsonData[CHANNEL]}")
removeNotificationChannel(jsonData[CHANNEL]!!)
jsonData[NotificationData.MESSAGE] == REMOVE_CHANNEL && !jsonData[NotificationData.CHANNEL].isNullOrBlank() -> {
Log.d(TAG, "Removing Notification channel ${jsonData[NotificationData.CHANNEL]}")
removeNotificationChannel(jsonData[NotificationData.CHANNEL]!!)
}
jsonData[MESSAGE] == TTS -> {
jsonData[NotificationData.MESSAGE] == TTS -> {
Log.d(TAG, "Sending notification title to TTS")
speakNotification(jsonData)
}
jsonData[MESSAGE] in DEVICE_COMMANDS -> {
jsonData[NotificationData.MESSAGE] in DEVICE_COMMANDS -> {
Log.d(TAG, "Processing device command")
when (jsonData[MESSAGE]) {
when (jsonData[NotificationData.MESSAGE]) {
COMMAND_DND -> {
if (jsonData[COMMAND] in DND_COMMANDS) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M)
@ -667,7 +655,7 @@ class MessagingManager @Inject constructor(
if (it == TextToSpeech.SUCCESS) {
val listener = object : UtteranceProgressListener() {
override fun onStart(p0: String?) {
if (data[MEDIA_STREAM] == ALARM_STREAM_MAX)
if (data[MEDIA_STREAM] == NotificationData.ALARM_STREAM_MAX)
audioManager?.setStreamVolume(
AudioManager.STREAM_ALARM,
maxAlarmVolume!!,
@ -678,7 +666,7 @@ class MessagingManager @Inject constructor(
override fun onDone(p0: String?) {
textToSpeech?.stop()
textToSpeech?.shutdown()
if (data[MEDIA_STREAM] == ALARM_STREAM_MAX)
if (data[MEDIA_STREAM] == NotificationData.ALARM_STREAM_MAX)
audioManager?.setStreamVolume(
AudioManager.STREAM_ALARM,
currentAlarmVolume!!,
@ -689,7 +677,7 @@ class MessagingManager @Inject constructor(
override fun onError(p0: String?) {
textToSpeech?.stop()
textToSpeech?.shutdown()
if (data[MEDIA_STREAM] == ALARM_STREAM_MAX)
if (data[MEDIA_STREAM] == NotificationData.ALARM_STREAM_MAX)
audioManager?.setStreamVolume(
AudioManager.STREAM_ALARM,
currentAlarmVolume!!,
@ -698,7 +686,7 @@ class MessagingManager @Inject constructor(
}
override fun onStop(utteranceId: String?, interrupted: Boolean) {
if (data[MEDIA_STREAM] == ALARM_STREAM_MAX)
if (data[MEDIA_STREAM] == NotificationData.ALARM_STREAM_MAX)
audioManager?.setStreamVolume(
AudioManager.STREAM_ALARM,
currentAlarmVolume!!,
@ -707,7 +695,7 @@ class MessagingManager @Inject constructor(
}
}
textToSpeech?.setOnUtteranceProgressListener(listener)
if (data[MEDIA_STREAM] == ALARM_STREAM || data[MEDIA_STREAM] == ALARM_STREAM_MAX) {
if (data[MEDIA_STREAM] == NotificationData.ALARM_STREAM || data[MEDIA_STREAM] == NotificationData.ALARM_STREAM_MAX) {
val audioAttributes = AudioAttributes.Builder()
.setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
.setUsage(AudioAttributes.USAGE_ALARM)
@ -729,7 +717,7 @@ class MessagingManager @Inject constructor(
}
private fun handleDeviceCommands(data: Map<String, String>) {
val message = data[MESSAGE]
val message = data[NotificationData.MESSAGE]
val command = data[COMMAND]
when (message) {
COMMAND_DND -> {
@ -737,7 +725,7 @@ class MessagingManager @Inject constructor(
val notificationManager =
context.getSystemService<NotificationManager>()
if (notificationManager?.isNotificationPolicyAccessGranted == false) {
notifyMissingPermission(data[MESSAGE].toString())
notifyMissingPermission(message.toString())
} else {
when (command) {
DND_ALARMS_ONLY -> notificationManager?.setInterruptionFilter(
@ -761,7 +749,7 @@ class MessagingManager @Inject constructor(
val notificationManager =
context.getSystemService<NotificationManager>()
if (notificationManager?.isNotificationPolicyAccessGranted == false) {
notifyMissingPermission(data[MESSAGE].toString())
notifyMissingPermission(message.toString())
} else {
processRingerMode(audioManager!!, command)
}
@ -800,7 +788,7 @@ class MessagingManager @Inject constructor(
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
val notificationManager = context.getSystemService<NotificationManager>()
if (notificationManager?.isNotificationPolicyAccessGranted == false) {
notifyMissingPermission(data[MESSAGE].toString())
notifyMissingPermission(message.toString())
} else {
processStreamVolume(
audioManager!!,
@ -825,7 +813,7 @@ class MessagingManager @Inject constructor(
}
else -> {
Log.e(TAG, "Missing Bluetooth permissions, notifying user to grant permissions")
notifyMissingPermission(data[MESSAGE].toString())
notifyMissingPermission(message.toString())
}
}
}
@ -910,7 +898,7 @@ class MessagingManager @Inject constructor(
COMMAND_ACTIVITY -> {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (!Settings.canDrawOverlays(context))
notifyMissingPermission(data[MESSAGE].toString())
notifyMissingPermission(message.toString())
else if (ContextCompat.checkSelfPermission(context, Manifest.permission.CALL_PHONE) != PackageManager.PERMISSION_GRANTED && data["tag"] == Intent.ACTION_CALL) {
Handler(Looper.getMainLooper()).post {
Toast.makeText(
@ -933,7 +921,7 @@ class MessagingManager @Inject constructor(
COMMAND_WEBVIEW -> {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (!Settings.canDrawOverlays(context))
notifyMissingPermission(data[MESSAGE].toString())
notifyMissingPermission(message.toString())
else
openWebview(command)
} else
@ -963,7 +951,7 @@ class MessagingManager @Inject constructor(
if (!NotificationManagerCompat.getEnabledListenerPackages(context)
.contains(context.packageName)
)
notifyMissingPermission(data[MESSAGE].toString())
notifyMissingPermission(message.toString())
else {
processMediaCommand(data)
}
@ -972,7 +960,7 @@ class MessagingManager @Inject constructor(
COMMAND_LAUNCH_APP -> {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (!Settings.canDrawOverlays(context))
notifyMissingPermission(data[MESSAGE].toString())
notifyMissingPermission(message.toString())
else
launchApp(data)
} else
@ -990,7 +978,7 @@ class MessagingManager @Inject constructor(
if (!processScreenCommands(data))
mainScope.launch { sendNotification(data) }
} else
notifyMissingPermission(data[MESSAGE].toString())
notifyMissingPermission(message.toString())
} else if (!processScreenCommands(data))
mainScope.launch { sendNotification(data) }
}
@ -1063,24 +1051,24 @@ class MessagingManager @Inject constructor(
var previousGroup = ""
var previousGroupId = 0
if (!group.isNullOrBlank()) {
group = GROUP_PREFIX + group
group = NotificationData.GROUP_PREFIX + group
groupId = group.hashCode()
} else {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
val notification = notificationManagerCompat.getActiveNotification(tag, messageId)
if (notification != null && notification.isGroup) {
previousGroup = GROUP_PREFIX + notification.tag
previousGroup = NotificationData.GROUP_PREFIX + notification.tag
previousGroupId = previousGroup.hashCode()
}
}
}
val channelId = handleChannel(notificationManagerCompat, data)
val channelId = handleChannel(context, notificationManagerCompat, data)
val notificationBuilder = NotificationCompat.Builder(context, channelId)
handleSmallIcon(notificationBuilder, data)
handleSmallIcon(context, notificationBuilder, data)
handleSound(notificationBuilder, data)
@ -1088,11 +1076,11 @@ class MessagingManager @Inject constructor(
handleLargeIcon(notificationBuilder, data)
handleGroup(notificationBuilder, group, data[ALERT_ONCE].toBoolean())
handleGroup(notificationBuilder, group, data[NotificationData.ALERT_ONCE].toBoolean())
handleTimeout(notificationBuilder, data)
handleColor(notificationBuilder, data)
handleColor(context, notificationBuilder, data)
handleSticky(notificationBuilder, data)
@ -1127,7 +1115,7 @@ class MessagingManager @Inject constructor(
notify(tag, messageId, notificationBuilder.build())
if (!group.isNullOrBlank()) {
Log.d(TAG, "Show group notification with tag \"$group\" and id \"$groupId\"")
notify(group, groupId, getGroupNotificationBuilder(channelId, group, data).build())
notify(group, groupId, getGroupNotificationBuilder(context, channelId, group, data).build())
} else {
if (!previousGroup.isBlank()) {
Log.d(
@ -1163,22 +1151,6 @@ class MessagingManager @Inject constructor(
}
}
private fun handleSmallIcon(
builder: NotificationCompat.Builder,
data: Map<String, String>
) {
if (data[NOTIFICATION_ICON]?.startsWith("mdi:") == true && !data[NOTIFICATION_ICON]?.substringAfter("mdi:").isNullOrBlank() && Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
val iconName = data[NOTIFICATION_ICON]!!.split(":")[1]
val iconDrawable =
IconicsDrawable(context, "cmd-$iconName")
if (iconDrawable.icon != null)
builder.setSmallIcon(iconDrawable.toAndroidIconCompat())
else
builder.setSmallIcon(commonR.drawable.ic_stat_ic_notification)
} else
builder.setSmallIcon(commonR.drawable.ic_stat_ic_notification)
}
private fun handleContentIntent(
builder: NotificationCompat.Builder,
messageId: Int,
@ -1226,34 +1198,11 @@ class MessagingManager @Inject constructor(
}
}
private fun getGroupNotificationBuilder(
channelId: String,
group: String,
data: Map<String, String>
): NotificationCompat.Builder {
val groupNotificationBuilder = NotificationCompat.Builder(context, channelId)
.setStyle(
NotificationCompat.BigTextStyle()
.setSummaryText(
prepareText(group.substring(GROUP_PREFIX.length))
)
)
.setGroup(group)
.setGroupSummary(true)
if (data[ALERT_ONCE].toBoolean())
groupNotificationBuilder.setGroupAlertBehavior(NotificationCompat.GROUP_ALERT_CHILDREN)
handleColor(groupNotificationBuilder, data)
handleSmallIcon(groupNotificationBuilder, data)
return groupNotificationBuilder
}
private fun handleSound(
builder: NotificationCompat.Builder,
data: Map<String, String>
) {
if (data[CHANNEL] == ALARM_STREAM) {
if (data[NotificationData.CHANNEL] == NotificationData.ALARM_STREAM) {
builder.setCategory(Notification.CATEGORY_ALARM)
builder.setSound(
RingtoneManager.getActualDefaultRingtoneUri(
@ -1269,38 +1218,17 @@ class MessagingManager @Inject constructor(
} else {
builder.setSound(RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION))
}
if (data[ALERT_ONCE].toBoolean())
if (data[NotificationData.ALERT_ONCE].toBoolean())
builder.setOnlyAlertOnce(true)
}
private fun handleColor(
builder: NotificationCompat.Builder,
data: Map<String, String>
) {
val colorString = data["color"]
val color = parseColor(colorString, commonR.color.colorPrimary)
builder.color = color
}
private fun parseColor(colorString: String?, default: Int): Int {
if (!colorString.isNullOrBlank()) {
try {
return Color.parseColor(colorString)
} catch (e: Exception) {
Log.e(TAG, "Unable to parse color", e)
}
}
return ContextCompat.getColor(context, default)
}
private fun handleLegacyLedColor(
builder: NotificationCompat.Builder,
data: Map<String, String>
) {
val ledColor = data[LED_COLOR]
val ledColor = data[NotificationData.LED_COLOR]
if (!ledColor.isNullOrBlank()) {
builder.setLights(parseColor(ledColor, commonR.color.colorPrimary), 3000, 3000)
builder.setLights(parseColor(context, ledColor, commonR.color.colorPrimary), 3000, 3000)
}
}
@ -1308,7 +1236,7 @@ class MessagingManager @Inject constructor(
builder: NotificationCompat.Builder,
data: Map<String, String>
) {
val vibrationPattern = data[VIBRATION_PATTERN]
val vibrationPattern = data[NotificationData.VIBRATION_PATTERN]
if (!vibrationPattern.isNullOrBlank()) {
val arrVibrationPattern = parseVibrationPattern(vibrationPattern)
if (arrVibrationPattern.isNotEmpty()) {
@ -1323,7 +1251,7 @@ class MessagingManager @Inject constructor(
) {
// Use importance property for legacy priority support
val priority = data[IMPORTANCE]
val priority = data[NotificationData.IMPORTANCE]
when (priority) {
"high" -> {
@ -1344,32 +1272,6 @@ class MessagingManager @Inject constructor(
}
}
@RequiresApi(Build.VERSION_CODES.N)
private fun handleImportance(
data: Map<String, String>
): Int {
val importance = data[IMPORTANCE]
when (importance) {
"high" -> {
return NotificationManager.IMPORTANCE_HIGH
}
"low" -> {
return NotificationManager.IMPORTANCE_LOW
}
"max" -> {
return NotificationManager.IMPORTANCE_MAX
}
"min" -> {
return NotificationManager.IMPORTANCE_MIN
}
else -> {
return NotificationManager.IMPORTANCE_DEFAULT
}
}
}
private fun handleTimeout(
builder: NotificationCompat.Builder,
data: Map<String, String>
@ -1407,29 +1309,6 @@ class MessagingManager @Inject constructor(
}
}
private fun handleText(
builder: NotificationCompat.Builder,
data: Map<String, String>
) {
data[TITLE]?.let {
builder.setContentTitle(prepareText(it))
}
data[MESSAGE]?.let {
val text = prepareText(it)
builder.setContentText(text)
builder.setStyle(NotificationCompat.BigTextStyle().bigText(text))
}
}
private fun prepareText(
text: String
): Spanned {
// Replace control char \r\n, \r, \n and also \r\n, \r, \n as text literals in strings to <br>
var brText = text.replace("(\r\n|\r|\n)|(\\\\r\\\\n|\\\\r|\\\\n)".toRegex(), "<br>")
var emojiParsedText = EmojiParser.parseToUnicode(brText)
return HtmlCompat.fromHtml(emojiParsedText, HtmlCompat.FROM_HTML_MODE_LEGACY)
}
private suspend fun handleLargeIcon(
builder: NotificationCompat.Builder,
data: Map<String, String>
@ -1509,11 +1388,11 @@ class MessagingManager @Inject constructor(
)
}
data[TITLE]?.let { rawTitle ->
data[NotificationData.TITLE]?.let { rawTitle ->
remoteViewFlipper.setTextViewText(R.id.title, rawTitle)
}
data[MESSAGE]?.let { rawMessage ->
data[NotificationData.MESSAGE]?.let { rawMessage ->
remoteViewFlipper.setTextViewText(R.id.info, rawMessage)
}
@ -1777,114 +1656,6 @@ class MessagingManager @Inject constructor(
}
}
private fun handleChannel(
notificationManagerCompat: NotificationManagerCompat,
data: Map<String, String>
): String {
// Define some values for a default channel
var channelID = generalChannel
var channelName = "General"
if (!data[CHANNEL].isNullOrEmpty()) {
channelID = createChannelID(data[CHANNEL].toString())
channelName = data[CHANNEL].toString().trim()
}
// Since android Oreo notification channel is needed.
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val channel = NotificationChannel(
channelID,
channelName,
handleImportance(data)
)
if (channelName == ALARM_STREAM)
handleChannelSound(channel)
setChannelLedColor(data, channel)
setChannelVibrationPattern(data, channel)
notificationManagerCompat.createNotificationChannel(channel)
}
return channelID
}
private fun setChannelLedColor(
data: Map<String, String>,
channel: NotificationChannel
) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val ledColor = data[LED_COLOR]
if (!ledColor.isNullOrBlank()) {
channel.enableLights(true)
channel.lightColor = parseColor(ledColor, commonR.color.colorPrimary)
}
}
}
private fun setChannelVibrationPattern(
data: Map<String, String>,
channel: NotificationChannel
) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val vibrationPattern = data[VIBRATION_PATTERN]
val arrVibrationPattern = parseVibrationPattern(vibrationPattern)
if (arrVibrationPattern.isNotEmpty()) {
channel.vibrationPattern = arrVibrationPattern
}
}
}
@RequiresApi(Build.VERSION_CODES.O)
private fun handleChannelSound(
channel: NotificationChannel
) {
val audioAttributes = AudioAttributes.Builder()
.setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
.setFlags(AudioAttributes.FLAG_AUDIBILITY_ENFORCED)
.setLegacyStreamType(AudioManager.STREAM_ALARM)
.setUsage(AudioAttributes.USAGE_ALARM)
.build()
channel.setSound(
RingtoneManager.getActualDefaultRingtoneUri(
context,
RingtoneManager.TYPE_ALARM
)
?: RingtoneManager.getActualDefaultRingtoneUri(
context,
RingtoneManager.TYPE_RINGTONE
),
audioAttributes
)
}
private fun parseVibrationPattern(
vibrationPattern: String?
): LongArray {
if (!vibrationPattern.isNullOrBlank()) {
val pattern = vibrationPattern.split(",").toTypedArray()
val list = mutableListOf<Long>()
pattern.forEach { it ->
val ms = it.trim().toLongOrNull()
if (ms != null) {
list.add(ms)
}
}
if (list.count() > 0) {
return list.toLongArray()
}
}
return LongArray(0)
}
private fun createChannelID(
channelName: String
): String {
return channelName
.trim()
.toLowerCase(Locale.ROOT)
.replace(" ", "_")
}
@RequiresApi(Build.VERSION_CODES.M)
private fun requestDNDPermission() {
val intent =
@ -2002,13 +1773,13 @@ class MessagingManager @Inject constructor(
private fun processStreamVolume(audioManager: AudioManager, stream: String, volume: Int) {
when (stream) {
ALARM_STREAM -> adjustVolumeStream(AudioManager.STREAM_ALARM, volume, audioManager)
MUSIC_STREAM -> adjustVolumeStream(AudioManager.STREAM_MUSIC, volume, audioManager)
NOTIFICATION_STREAM -> adjustVolumeStream(AudioManager.STREAM_NOTIFICATION, volume, audioManager)
RING_STREAM -> adjustVolumeStream(AudioManager.STREAM_RING, volume, audioManager)
CALL_STREAM -> adjustVolumeStream(AudioManager.STREAM_VOICE_CALL, volume, audioManager)
SYSTEM_STREAM -> adjustVolumeStream(AudioManager.STREAM_SYSTEM, volume, audioManager)
DTMF_STREAM -> adjustVolumeStream(AudioManager.STREAM_DTMF, volume, audioManager)
NotificationData.ALARM_STREAM -> adjustVolumeStream(AudioManager.STREAM_ALARM, volume, audioManager)
NotificationData.MUSIC_STREAM -> adjustVolumeStream(AudioManager.STREAM_MUSIC, volume, audioManager)
NotificationData.NOTIFICATION_STREAM -> adjustVolumeStream(AudioManager.STREAM_NOTIFICATION, volume, audioManager)
NotificationData.RING_STREAM -> adjustVolumeStream(AudioManager.STREAM_RING, volume, audioManager)
NotificationData.CALL_STREAM -> adjustVolumeStream(AudioManager.STREAM_VOICE_CALL, volume, audioManager)
NotificationData.SYSTEM_STREAM -> adjustVolumeStream(AudioManager.STREAM_SYSTEM, volume, audioManager)
NotificationData.DTMF_STREAM -> adjustVolumeStream(AudioManager.STREAM_DTMF, volume, audioManager)
else -> Log.d(TAG, "Skipping command due to invalid channel stream")
}
}
@ -2160,12 +1931,12 @@ class MessagingManager @Inject constructor(
val contentResolver = context.contentResolver
val success = Settings.System.putInt(
contentResolver,
when (data[MESSAGE].toString()) {
when (data[NotificationData.MESSAGE].toString()) {
COMMAND_SCREEN_BRIGHTNESS_LEVEL -> Settings.System.SCREEN_BRIGHTNESS
COMMAND_AUTO_SCREEN_BRIGHTNESS -> Settings.System.SCREEN_BRIGHTNESS_MODE
else -> Settings.System.SCREEN_OFF_TIMEOUT
},
when (data[MESSAGE].toString()) {
when (data[NotificationData.MESSAGE].toString()) {
COMMAND_SCREEN_BRIGHTNESS_LEVEL -> command!!.toInt().coerceIn(0, 255)
COMMAND_AUTO_SCREEN_BRIGHTNESS -> {
if (command == TURN_ON)
@ -2188,7 +1959,7 @@ class MessagingManager @Inject constructor(
if (context.applicationInfo.processName == item.processName) {
if (item.importance != ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND) {
val data =
mutableMapOf(MESSAGE to context.getString(commonR.string.missing_command_permission))
mutableMapOf(NotificationData.MESSAGE to context.getString(commonR.string.missing_command_permission))
runBlocking {
sendNotification(data)
}

View File

@ -77,4 +77,8 @@ dependencies {
implementation("com.mikepenz:iconics-core:5.4.0")
implementation("com.mikepenz:community-material-typeface:7.0.96.0-kotlin@aar")
implementation("com.vdurmont:emoji-java:5.1.1") {
exclude(group = "org.json", module = "json")
}
}

View File

@ -3,5 +3,6 @@ package io.homeassistant.companion.android.common.data.integration
data class DeviceRegistration(
val appVersion: String? = null,
val deviceName: String? = null,
var pushToken: String? = null
var pushToken: String? = null,
var pushWebsocket: Boolean = true
)

View File

@ -759,7 +759,7 @@ class IntegrationRepositoryImpl @Inject constructor(
val oldDeviceRegistration = getRegistration()
val pushToken = deviceRegistration.pushToken ?: oldDeviceRegistration.pushToken
val appData = mutableMapOf<String, Any>("push_websocket_channel" to true)
val appData = mutableMapOf<String, Any>("push_websocket_channel" to deviceRegistration.pushWebsocket)
if (!pushToken.isNullOrBlank()) {
appData["push_url"] = PUSH_URL
appData["push_token"] = pushToken

View File

@ -0,0 +1,271 @@
package io.homeassistant.companion.android.common.notifications
import android.app.NotificationChannel
import android.app.NotificationManager
import android.content.Context
import android.graphics.Color
import android.media.AudioAttributes
import android.media.AudioManager
import android.media.RingtoneManager
import android.os.Build
import android.text.Spanned
import android.util.Log
import androidx.annotation.RequiresApi
import androidx.core.app.NotificationCompat
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.toAndroidIconCompat
import com.vdurmont.emoji.EmojiParser
import io.homeassistant.companion.android.common.R
import io.homeassistant.companion.android.common.util.generalChannel
import java.util.Locale
object NotificationData {
const val TAG = "MessagingService"
const val TITLE = "title"
const val MESSAGE = "message"
const val GROUP_PREFIX = "group_"
const val CHANNEL = "channel"
const val IMPORTANCE = "importance"
const val LED_COLOR = "ledColor"
const val VIBRATION_PATTERN = "vibrationPattern"
const val NOTIFICATION_ICON = "notification_icon"
const val ALERT_ONCE = "alert_once"
// Channel streams
const val ALARM_STREAM = "alarm_stream"
const val ALARM_STREAM_MAX = "alarm_stream_max"
const val MUSIC_STREAM = "music_stream"
const val NOTIFICATION_STREAM = "notification_stream"
const val RING_STREAM = "ring_stream"
const val SYSTEM_STREAM = "system_stream"
const val CALL_STREAM = "call_stream"
const val DTMF_STREAM = "dtmf_stream"
}
fun createChannelID(
channelName: String
): String {
return channelName
.trim()
.lowercase(Locale.ROOT)
.replace(" ", "_")
}
fun handleChannel(
context: Context,
notificationManagerCompat: NotificationManagerCompat,
data: Map<String, String>
): String {
// Define some values for a default channel
var channelID = generalChannel
var channelName = "General"
if (!data[NotificationData.CHANNEL].isNullOrEmpty()) {
channelID = createChannelID(data[NotificationData.CHANNEL].toString())
channelName = data[NotificationData.CHANNEL].toString().trim()
}
// Since android Oreo notification channel is needed.
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val channel = NotificationChannel(
channelID,
channelName,
handleImportance(data)
)
if (channelName == NotificationData.ALARM_STREAM)
handleChannelSound(context, channel)
setChannelLedColor(context, data, channel)
setChannelVibrationPattern(data, channel)
notificationManagerCompat.createNotificationChannel(channel)
}
return channelID
}
@RequiresApi(Build.VERSION_CODES.N)
fun handleImportance(
data: Map<String, String>
): Int {
when (data[NotificationData.IMPORTANCE]) {
"high" -> {
return NotificationManager.IMPORTANCE_HIGH
}
"low" -> {
return NotificationManager.IMPORTANCE_LOW
}
"max" -> {
return NotificationManager.IMPORTANCE_MAX
}
"min" -> {
return NotificationManager.IMPORTANCE_MIN
}
else -> {
return NotificationManager.IMPORTANCE_DEFAULT
}
}
}
@RequiresApi(Build.VERSION_CODES.O)
fun handleChannelSound(
context: Context,
channel: NotificationChannel
) {
val audioAttributes = AudioAttributes.Builder()
.setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
.setFlags(AudioAttributes.FLAG_AUDIBILITY_ENFORCED)
.setLegacyStreamType(AudioManager.STREAM_ALARM)
.setUsage(AudioAttributes.USAGE_ALARM)
.build()
channel.setSound(
RingtoneManager.getActualDefaultRingtoneUri(
context,
RingtoneManager.TYPE_ALARM
)
?: RingtoneManager.getActualDefaultRingtoneUri(
context,
RingtoneManager.TYPE_RINGTONE
),
audioAttributes
)
}
fun setChannelLedColor(
context: Context,
data: Map<String, String>,
channel: NotificationChannel
) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val ledColor = data[NotificationData.LED_COLOR]
if (!ledColor.isNullOrBlank()) {
channel.enableLights(true)
channel.lightColor = parseColor(context, ledColor, R.color.colorPrimary)
}
}
}
fun setChannelVibrationPattern(
data: Map<String, String>,
channel: NotificationChannel
) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val vibrationPattern = data[NotificationData.VIBRATION_PATTERN]
val arrVibrationPattern = parseVibrationPattern(vibrationPattern)
if (arrVibrationPattern.isNotEmpty()) {
channel.vibrationPattern = arrVibrationPattern
}
}
}
fun parseVibrationPattern(
vibrationPattern: String?
): LongArray {
if (!vibrationPattern.isNullOrBlank()) {
val pattern = vibrationPattern.split(",").toTypedArray()
val list = mutableListOf<Long>()
pattern.forEach {
val ms = it.trim().toLongOrNull()
if (ms != null) {
list.add(ms)
}
}
if (list.isNotEmpty()) {
return list.toLongArray()
}
}
return LongArray(0)
}
fun parseColor(
context: Context,
colorString: String?,
default: Int
): Int {
if (!colorString.isNullOrBlank()) {
try {
return Color.parseColor(colorString)
} catch (e: Exception) {
Log.e(NotificationData.TAG, "Unable to parse color", e)
}
}
return ContextCompat.getColor(context, default)
}
fun handleSmallIcon(
context: Context,
builder: NotificationCompat.Builder,
data: Map<String, String>
) {
val notificationIcon = data[NotificationData.NOTIFICATION_ICON] ?: ""
if (notificationIcon.startsWith("mdi:") && notificationIcon.substringAfter("mdi:").isNotBlank() && Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
val iconName = notificationIcon.split(":")[1]
val iconDrawable =
IconicsDrawable(context, "cmd-$iconName")
if (iconDrawable.icon != null)
builder.setSmallIcon(iconDrawable.toAndroidIconCompat())
else
builder.setSmallIcon(R.drawable.ic_stat_ic_notification)
} else
builder.setSmallIcon(R.drawable.ic_stat_ic_notification)
}
fun getGroupNotificationBuilder(
context: Context,
channelId: String,
group: String,
data: Map<String, String>
): NotificationCompat.Builder {
val groupNotificationBuilder = NotificationCompat.Builder(context, channelId)
.setStyle(
NotificationCompat.BigTextStyle()
.setSummaryText(
prepareText(group.substring(NotificationData.GROUP_PREFIX.length))
)
)
.setGroup(group)
.setGroupSummary(true)
if (data[NotificationData.ALERT_ONCE].toBoolean())
groupNotificationBuilder.setGroupAlertBehavior(NotificationCompat.GROUP_ALERT_CHILDREN)
handleColor(context, groupNotificationBuilder, data)
handleSmallIcon(context, groupNotificationBuilder, data)
return groupNotificationBuilder
}
fun prepareText(
text: String
): Spanned {
// Replace control char \r\n, \r, \n and also \r\n, \r, \n as text literals in strings to <br>
val brText = text.replace("(\r\n|\r|\n)|(\\\\r\\\\n|\\\\r|\\\\n)".toRegex(), "<br>")
val emojiParsedText = EmojiParser.parseToUnicode(brText)
return HtmlCompat.fromHtml(emojiParsedText, HtmlCompat.FROM_HTML_MODE_LEGACY)
}
fun handleColor(
context: Context,
builder: NotificationCompat.Builder,
data: Map<String, String>
) {
val colorString = data["color"]
val color = parseColor(context, colorString, R.color.colorPrimary)
builder.color = color
}
fun handleText(
builder: NotificationCompat.Builder,
data: Map<String, String>
) {
data[NotificationData.TITLE]?.let {
builder.setContentTitle(prepareText(it))
}
data[NotificationData.MESSAGE]?.let {
val text = prepareText(it)
builder.setContentText(text)
builder.setStyle(NotificationCompat.BigTextStyle().bigText(text))
}
}

View File

@ -5,6 +5,7 @@ plugins {
id("com.github.triplet.play")
kotlin("kapt")
id("dagger.hilt.android.plugin")
id("com.google.gms.google-services")
}
android {
@ -127,4 +128,7 @@ dependencies {
implementation("androidx.wear.watchface:watchface-complications-data-source-ktx:1.1.1")
implementation("androidx.health:health-services-client:1.0.0-beta02")
implementation(platform("com.google.firebase:firebase-bom:31.1.1"))
implementation("com.google.firebase:firebase-messaging")
}

View File

@ -170,6 +170,14 @@
android:path="/updateTemplateTile" />
</intent-filter>
</service>
<service
android:name=".notifications.FirebaseCloudMessagingService"
android:exported="false">
<intent-filter>
<action android:name="com.google.firebase.MESSAGING_EVENT" />
</intent-filter>
</service>
</application>
</manifest>

View File

@ -16,6 +16,7 @@ import io.homeassistant.companion.android.common.data.websocket.impl.entities.De
import io.homeassistant.companion.android.common.data.websocket.impl.entities.EntityRegistryResponse
import io.homeassistant.companion.android.common.data.websocket.impl.entities.EntityRegistryUpdatedEvent
import io.homeassistant.companion.android.data.SimplifiedEntity
import io.homeassistant.companion.android.onboarding.getMessagingToken
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
@ -179,7 +180,8 @@ class HomePresenterImpl @Inject constructor(
DeviceRegistration(
"${BuildConfig.VERSION_NAME} (${BuildConfig.VERSION_CODE})",
null,
null
getMessagingToken(),
false
)
)
} catch (e: Exception) {

View File

@ -0,0 +1,66 @@
package io.homeassistant.companion.android.notifications
import android.util.Log
import com.google.firebase.messaging.FirebaseMessagingService
import com.google.firebase.messaging.RemoteMessage
import dagger.hilt.android.AndroidEntryPoint
import io.homeassistant.companion.android.common.data.authentication.AuthenticationRepository
import io.homeassistant.companion.android.common.data.authentication.SessionState
import io.homeassistant.companion.android.common.data.integration.DeviceRegistration
import io.homeassistant.companion.android.common.data.integration.IntegrationRepository
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.launch
import javax.inject.Inject
@AndroidEntryPoint
class FirebaseCloudMessagingService : FirebaseMessagingService() {
companion object {
private const val TAG = "FCMService"
private const val SOURCE = "FCM"
}
@Inject
lateinit var integrationUseCase: IntegrationRepository
@Inject
lateinit var authenticationUseCase: AuthenticationRepository
@Inject
lateinit var messagingManager: MessagingManager
private val mainScope: CoroutineScope = CoroutineScope(Dispatchers.Main + Job())
override fun onMessageReceived(remoteMessage: RemoteMessage) {
Log.d(TAG, "From: ${remoteMessage.from} and data: ${remoteMessage.data}")
messagingManager.handleMessage(remoteMessage.data, SOURCE)
}
/**
* Called if InstanceID token is updated. This may occur if the security of
* the previous token had been compromised. Note that this is called when the InstanceID token
* is initially generated so this is where you would retrieve the token.
*/
override fun onNewToken(token: String) {
mainScope.launch {
Log.d(TAG, "Refreshed token: $token")
if (authenticationUseCase.getSessionState() == SessionState.ANONYMOUS) {
Log.d(TAG, "Not trying to update registration since we aren't authenticated.")
return@launch
}
try {
integrationUseCase.updateRegistration(
DeviceRegistration(
pushToken = token,
pushWebsocket = false
)
)
} catch (e: Exception) {
// TODO: Store for update later
Log.e(TAG, "Issue updating token", e)
}
}
}
}

View File

@ -0,0 +1,89 @@
package io.homeassistant.companion.android.notifications
import android.annotation.SuppressLint
import android.content.Context
import android.util.Log
import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationManagerCompat
import dagger.hilt.android.qualifiers.ApplicationContext
import io.homeassistant.companion.android.common.notifications.NotificationData
import io.homeassistant.companion.android.common.notifications.getGroupNotificationBuilder
import io.homeassistant.companion.android.common.notifications.handleChannel
import io.homeassistant.companion.android.common.notifications.handleSmallIcon
import io.homeassistant.companion.android.common.notifications.handleText
import io.homeassistant.companion.android.common.util.cancelGroupIfNeeded
import io.homeassistant.companion.android.common.util.getActiveNotification
import io.homeassistant.companion.android.database.AppDatabase
import io.homeassistant.companion.android.database.notification.NotificationItem
import org.json.JSONObject
import javax.inject.Inject
class MessagingManager @Inject constructor(
@ApplicationContext val context: Context,
) {
companion object {
const val TAG = "MessagingManager"
}
fun handleMessage(notificationData: Map<String, String>, source: String) {
val notificationDao = AppDatabase.getInstance(context).notificationDao()
val now = System.currentTimeMillis()
val jsonObject = (notificationData as Map<*, *>?)?.let { JSONObject(it) }
val notificationRow =
NotificationItem(0, now, notificationData[NotificationData.MESSAGE].toString(), jsonObject.toString(), source)
notificationDao.add(notificationRow)
sendNotification(notificationData, now)
}
@SuppressLint("MissingPermission")
private fun sendNotification(data: Map<String, String>, received: Long? = null) {
val notificationManagerCompat = NotificationManagerCompat.from(context)
val tag = data["tag"]
val messageId = tag?.hashCode() ?: received?.toInt() ?: System.currentTimeMillis().toInt()
var group = data["group"]
var groupId = 0
var previousGroup = ""
var previousGroupId = 0
if (!group.isNullOrBlank()) {
group = NotificationData.GROUP_PREFIX + group
groupId = group.hashCode()
} else {
val notification = notificationManagerCompat.getActiveNotification(tag, messageId)
if (notification != null && notification.isGroup) {
previousGroup = NotificationData.GROUP_PREFIX + notification.tag
previousGroupId = previousGroup.hashCode()
}
}
val channelId = handleChannel(context, notificationManagerCompat, data)
val notificationBuilder = NotificationCompat.Builder(context, channelId)
handleSmallIcon(context, notificationBuilder, data)
handleText(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.isNotBlank()) {
Log.d(
TAG,
"Remove group notification with tag \"$previousGroup\" and id \"$previousGroupId\""
)
notificationManagerCompat.cancelGroupIfNeeded(previousGroup, previousGroupId)
}
}
}
}
}

View File

@ -0,0 +1,14 @@
package io.homeassistant.companion.android.onboarding
import android.util.Log
import com.google.firebase.messaging.FirebaseMessaging
import kotlinx.coroutines.tasks.await
suspend fun getMessagingToken(): String {
return try {
FirebaseMessaging.getInstance().token.await()
} catch (e: Exception) {
Log.e("MessagingToken", "Issue getting token", e)
""
}
}

View File

@ -6,6 +6,7 @@ import dagger.hilt.android.qualifiers.ActivityContext
import io.homeassistant.companion.android.BuildConfig
import io.homeassistant.companion.android.common.data.integration.DeviceRegistration
import io.homeassistant.companion.android.common.data.integration.IntegrationRepository
import io.homeassistant.companion.android.onboarding.getMessagingToken
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
@ -28,7 +29,9 @@ class MobileAppIntegrationPresenterImpl @Inject constructor(
private suspend fun createRegistration(deviceName: String): DeviceRegistration {
return DeviceRegistration(
"${BuildConfig.VERSION_NAME} (${BuildConfig.VERSION_CODE})",
deviceName
deviceName,
getMessagingToken(),
false
)
}

View File

@ -24,6 +24,7 @@ import io.homeassistant.companion.android.database.wear.getAll
import io.homeassistant.companion.android.database.wear.replaceAll
import io.homeassistant.companion.android.home.HomeActivity
import io.homeassistant.companion.android.home.HomePresenterImpl
import io.homeassistant.companion.android.onboarding.getMessagingToken
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
@ -124,7 +125,9 @@ class PhoneSettingsListener : WearableListenerService(), DataClient.OnDataChange
integrationUseCase.registerDevice(
DeviceRegistration(
"${BuildConfig.VERSION_NAME} (${BuildConfig.VERSION_CODE})",
deviceName
deviceName,
getMessagingToken(),
false
)
)