Meshtastic-Android/app/src/main/java/com/geeksville/mesh/DataPacket.kt

247 lines
8.3 KiB
Kotlin
Raw Normal View History

/*
2025-01-02 06:50:26 -03:00
* Copyright (c) 2025 Meshtastic LLC
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
2020-05-30 14:38:16 -07:00
package com.geeksville.mesh
import android.os.Parcel
2020-05-30 14:38:16 -07:00
import android.os.Parcelable
import kotlinx.parcelize.Parcelize
2020-05-30 14:38:16 -07:00
import kotlinx.serialization.Serializable
/**
* Generic [Parcel.readParcelable] Android 13 compatibility extension.
*/
private inline fun <reified T : Parcelable> Parcel.readParcelableCompat(loader: ClassLoader?): T? {
return if (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.TIRAMISU) {
@Suppress("DEPRECATION")
readParcelable(loader)
} else {
readParcelable(loader, T::class.java)
}
}
2020-05-30 14:38:16 -07:00
@Parcelize
enum class MessageStatus : Parcelable {
UNKNOWN, // Not set for this message
RECEIVED, // Came in from the mesh
QUEUED, // Waiting to send to the mesh as soon as we connect to the device
ENROUTE, // Delivered to the radio, but no ACK or NAK received
DELIVERED, // We received an ack
ERROR // We received back a nak, message not delivered
2020-05-30 14:38:16 -07:00
}
/**
* A parcelable version of the protobuf MeshPacket + Data subpacket.
*/
@Serializable
data class DataPacket(
var to: String? = ID_BROADCAST, // a nodeID string, or ID_BROADCAST for broadcast
val bytes: ByteArray?,
2020-12-07 19:50:06 +08:00
val dataType: Int, // A port number for this packet (formerly called DataType, see portnums.proto for new usage instructions)
var from: String? = ID_LOCAL, // a nodeID string, or ID_LOCAL for localhost
var time: Long = System.currentTimeMillis(), // msecs since 1970
var id: Int = 0, // 0 means unassigned
var status: MessageStatus? = MessageStatus.UNKNOWN,
2022-04-03 11:25:50 -03:00
var hopLimit: Int = 0,
var channel: Int = 0, // channel index
var wantAck: Boolean = true, // If true, the receiver should send an ack back
var hopStart: Int = 0,
var snr: Float = 0f,
var rssi: Int = 0,
var replyId: Int? = null // If this is a reply to a previous message, this is the ID of that message
2020-05-30 14:38:16 -07:00
) : Parcelable {
/**
* If there was an error with this message, this string describes what was wrong.
*/
var errorMessage: String? = null
/**
* Syntactic sugar to make it easy to create text messages
*/
constructor(to: String?, channel: Int, text: String, replyId: Int? = null) : this(
2022-09-15 22:24:04 -03:00
to = to,
bytes = text.encodeToByteArray(),
2022-09-15 22:24:04 -03:00
dataType = Portnums.PortNum.TEXT_MESSAGE_APP_VALUE,
channel = channel,
replyId = replyId ?: 0
)
/**
* If this is a text message, return the string, otherwise null
*/
val text: String?
get() = if (dataType == Portnums.PortNum.TEXT_MESSAGE_APP_VALUE) {
bytes?.decodeToString()
} else {
null
}
feat: Show ALERT_APP notifications and override DND (#1515) * feat: Show alert notifications and override silent mode This commit adds support for showing alert notifications with high priority and the ability to override silent mode to ensure they are delivered to the user. The changes include: - Adding `showAlertNotification` function which overrides silent mode and configures a custom volume, shows a notification with high priority. - Creating a new notification channel for alerts. - Adding the alert app port number to the list of remembered data types. - Modifying `rememberDataPacket` to check for alert app messages and show alert notification. * Add notification policy access permission and DND override for alerts This commit adds the `ACCESS_NOTIFICATION_POLICY` permission to the manifest and requests this permission from the user. It also adds a check for notification policy access in the MainActivity, and if it's not granted, shows a rationale dialog. Additionally, the commit adds a notification override to the `showAlertNotification` function in `MeshServiceNotifications` to temporarily disable DND for alert notifications and restore the original ringer settings afterwards. * Refactor: Enhance Android Notification and DND Handling - **Notification Channel Improvements:** - Added `notificationLightColor` for better customization. - Set `enableLights` and `enableVibration` in the alert channel. - Use `alert.mp3` sound for alert channel. - **DND Permission Request:** - Introduced a new permission request flow for Do Not Disturb (DND) access. - Show a rationale dialog before requesting permission. - Persist if rationale was shown to avoid re-prompting. - Added a `notificationPolicyAccessLauncher` to handle the permission request result. - **Critical Alert Text** - Added critical alert text in strings. - Used critical alert text if the alert message is empty. - **Other Changes** - Removed unused imports and constants. - Updated snackbar to support action. * Refactor alert notification logic - Change `notificationLightColor` to be lazy initialized. - Update alert notification to use `CATEGORY_ALARM`. - Use `dataPacket.alert` instead of `dataPacket.text` for alert content. - Add `alert` property to `DataPacket` to handle alert messages. * Set notification light color back to blue. * Request notification permissions on grant The app now checks for notification policy access after notification permissions are granted. * make detekt happy * updates dnd dialog text * Refactor notification channel creation and critical alerts - Initialize notification channels on service creation. - Remove `ACCESS_NOTIFICATION_POLICY` permission. - Modify the logic for requesting "Do Not Disturb" override permission to align with channel settings. - Add new string resources for Alerts Channel Settings. - Update wording for critical alert DND override. - Update DND override request flow. - Create notification channels on the service creation using `initChannels`. - Adjust logic to check for "Do Not Disturb" override permission to align with notification channel settings. - Ensure notification channels are created only if they do not already exist. * refactor: Update DnD dialog with instructions for Samsung - Renamed "Alerts Channel Settings" to "Channel Settings". - Added Samsung-specific instructions and a link to Samsung's support page for Do Not Disturb mode in the alerts dialog. - Updated the dialog to display Samsung-specific instructions when on a Samsung device. * Refactor critical alerts instructions - Updated the critical alerts instructions to include a link to Samsung's support page directly within the alert dialog. - Removed the separate "Samsung Instructions" string and incorporated the information into the main instruction text, improving clarity and reducing redundancy. - Made improvements to the UI.
2025-03-05 07:28:52 -06:00
val alert: String?
get() = if (dataType == Portnums.PortNum.ALERT_APP_VALUE) {
bytes?.decodeToString()
} else {
null
}
2023-02-01 12:16:44 -03:00
constructor(to: String?, channel: Int, waypoint: MeshProtos.Waypoint) : this(
to = to,
bytes = waypoint.toByteArray(),
dataType = Portnums.PortNum.WAYPOINT_APP_VALUE,
channel = channel,
2023-02-01 12:16:44 -03:00
)
2022-09-09 22:15:27 -03:00
val waypoint: MeshProtos.Waypoint?
get() = if (dataType == Portnums.PortNum.WAYPOINT_APP_VALUE) {
2022-09-09 22:15:27 -03:00
MeshProtos.Waypoint.parseFrom(bytes)
} else {
2022-09-09 22:15:27 -03:00
null
}
2022-09-09 22:15:27 -03:00
val hopsAway: Int
get() = if (hopStart == 0 || hopLimit > hopStart) -1 else hopStart - hopLimit
2020-05-30 14:38:16 -07:00
// Autogenerated comparision, because we have a byte array
constructor(parcel: Parcel) : this(
parcel.readString(),
parcel.createByteArray(),
parcel.readInt(),
parcel.readString(),
parcel.readLong(),
parcel.readInt(),
parcel.readParcelableCompat(MessageStatus::class.java.classLoader),
2022-04-03 11:25:50 -03:00
parcel.readInt(),
parcel.readInt(),
parcel.readInt() == 1,
parcel.readInt(),
parcel.readFloat(),
parcel.readInt(),
parcel.readInt().let { if (it == 0) null else it }
2021-03-29 20:33:06 +08:00
)
@Suppress("CyclomaticComplexMethod")
2020-05-30 14:38:16 -07:00
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as DataPacket
if (from != other.from) return false
if (to != other.to) return false
2022-04-03 11:25:50 -03:00
if (channel != other.channel) return false
if (time != other.time) return false
2020-05-30 14:38:16 -07:00
if (id != other.id) return false
if (dataType != other.dataType) return false
if (!bytes!!.contentEquals(other.bytes!!)) return false
if (status != other.status) return false
if (hopLimit != other.hopLimit) return false
if (wantAck != other.wantAck) return false
if (hopStart != other.hopStart) return false
if (snr != other.snr) return false
if (rssi != other.rssi) return false
if (replyId != other.replyId) return false
2020-05-30 14:38:16 -07:00
return true
}
override fun hashCode(): Int {
var result = from.hashCode()
result = 31 * result + to.hashCode()
result = 31 * result + time.hashCode()
2020-05-30 14:38:16 -07:00
result = 31 * result + id
result = 31 * result + dataType
result = 31 * result + bytes!!.contentHashCode()
result = 31 * result + status.hashCode()
result = 31 * result + hopLimit
2022-04-03 11:25:50 -03:00
result = 31 * result + channel
result = 31 * result + wantAck.hashCode()
result = 31 * result + hopStart
result = 31 * result + snr.hashCode()
result = 31 * result + rssi
result = 31 * result + replyId.hashCode()
2020-05-30 14:38:16 -07:00
return result
}
override fun writeToParcel(parcel: Parcel, flags: Int) {
parcel.writeString(to)
parcel.writeByteArray(bytes)
parcel.writeInt(dataType)
parcel.writeString(from)
parcel.writeLong(time)
parcel.writeInt(id)
parcel.writeParcelable(status, flags)
parcel.writeInt(hopLimit)
2022-04-03 11:25:50 -03:00
parcel.writeInt(channel)
parcel.writeInt(if (wantAck) 1 else 0)
parcel.writeInt(hopStart)
parcel.writeFloat(snr)
parcel.writeInt(rssi)
parcel.writeInt(replyId ?: 0)
}
override fun describeContents(): Int {
return 0
}
// Update our object from our parcel (used for inout parameters
fun readFromParcel(parcel: Parcel) {
to = parcel.readString()
parcel.createByteArray()
parcel.readInt()
from = parcel.readString()
time = parcel.readLong()
id = parcel.readInt()
status = parcel.readParcelableCompat(MessageStatus::class.java.classLoader)
hopLimit = parcel.readInt()
2022-04-03 11:25:50 -03:00
channel = parcel.readInt()
wantAck = parcel.readInt() == 1
hopStart = parcel.readInt()
snr = parcel.readFloat()
rssi = parcel.readInt()
replyId = parcel.readInt().let { if (it == 0) null else it }
}
companion object CREATOR : Parcelable.Creator<DataPacket> {
// Special node IDs that can be used for sending messages
/** the Node ID for broadcast destinations */
const val ID_BROADCAST = "^all"
/** The Node ID for the local node - used for from when sender doesn't know our local node ID */
const val ID_LOCAL = "^local"
// special broadcast address
const val NODENUM_BROADCAST = (0xffffffff).toInt()
// Public-key cryptography (PKC) channel index
const val PKC_CHANNEL_INDEX = 8
fun nodeNumToDefaultId(n: Int): String = "!%08x".format(n)
fun idToDefaultNodeNum(id: String?): Int? =
runCatching { id?.toLong(16)?.toInt() }.getOrNull()
override fun createFromParcel(parcel: Parcel): DataPacket {
return DataPacket(parcel)
}
override fun newArray(size: Int): Array<DataPacket?> {
return arrayOfNulls(size)
}
}
}