From ef99478defc89adef221f676881e84c0e0451493 Mon Sep 17 00:00:00 2001
From: tylxr59 <102394635+tylxr59@users.noreply.github.com>
Date: Sun, 14 Dec 2025 11:13:11 -0500
Subject: [PATCH 1/2] Add SMS message skill
---
app/src/main/AndroidManifest.xml | 3 +
.../org/stypox/dicio/eval/SkillHandler.kt | 2 +
.../dicio/skills/sms/AskMessageOutput.kt | 64 ++++++++++++++
.../dicio/skills/sms/ConfirmSmsOutput.kt | 57 ++++++++++++
.../stypox/dicio/skills/sms/SentSmsOutput.kt | 44 ++++++++++
.../skills/sms/SmsContactChooserIndex.kt | 48 ++++++++++
.../dicio/skills/sms/SmsContactChooserName.kt | 51 +++++++++++
.../org/stypox/dicio/skills/sms/SmsInfo.kt | 39 ++++++++
.../org/stypox/dicio/skills/sms/SmsOutput.kt | 88 +++++++++++++++++++
.../org/stypox/dicio/skills/sms/SmsSkill.kt | 79 +++++++++++++++++
.../org/stypox/dicio/util/PermissionUtils.kt | 4 +
app/src/main/res/values/strings.xml | 9 ++
app/src/main/sentences/en/sms.yml | 7 ++
app/src/main/sentences/skill_definitions.yml | 10 +++
14 files changed, 505 insertions(+)
create mode 100644 app/src/main/kotlin/org/stypox/dicio/skills/sms/AskMessageOutput.kt
create mode 100644 app/src/main/kotlin/org/stypox/dicio/skills/sms/ConfirmSmsOutput.kt
create mode 100644 app/src/main/kotlin/org/stypox/dicio/skills/sms/SentSmsOutput.kt
create mode 100644 app/src/main/kotlin/org/stypox/dicio/skills/sms/SmsContactChooserIndex.kt
create mode 100644 app/src/main/kotlin/org/stypox/dicio/skills/sms/SmsContactChooserName.kt
create mode 100644 app/src/main/kotlin/org/stypox/dicio/skills/sms/SmsInfo.kt
create mode 100644 app/src/main/kotlin/org/stypox/dicio/skills/sms/SmsOutput.kt
create mode 100644 app/src/main/kotlin/org/stypox/dicio/skills/sms/SmsSkill.kt
create mode 100644 app/src/main/sentences/en/sms.yml
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 6355382b..300bcb13 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -26,6 +26,9 @@
+
+
+
(SmsInfo, Specificity.HIGH) {
+ override fun score(
+ ctx: SkillContext,
+ input: String
+ ): Pair {
+ val trimmedInput = input.trim()
+ return Pair(
+ if (trimmedInput.isEmpty()) AlwaysWorstScore else AlwaysBestScore,
+ trimmedInput
+ )
+ }
+
+ override suspend fun generateOutput(
+ ctx: SkillContext,
+ inputData: String
+ ): SkillOutput {
+ return ConfirmSmsOutput(name, number, inputData)
+ }
+ }
+
+ return InteractionPlan.StartSubInteraction(
+ reopenMicrophone = true,
+ nextSkills = listOf(messageSkill),
+ )
+ }
+
+ @Composable
+ override fun GraphicalOutput(ctx: SkillContext) {
+ Column {
+ Headline(text = getSpeechOutput(ctx))
+ Spacer(modifier = Modifier.height(4.dp))
+ Body(text = "$name ($number)")
+ }
+ }
+}
diff --git a/app/src/main/kotlin/org/stypox/dicio/skills/sms/ConfirmSmsOutput.kt b/app/src/main/kotlin/org/stypox/dicio/skills/sms/ConfirmSmsOutput.kt
new file mode 100644
index 00000000..634279d5
--- /dev/null
+++ b/app/src/main/kotlin/org/stypox/dicio/skills/sms/ConfirmSmsOutput.kt
@@ -0,0 +1,57 @@
+package org.stypox.dicio.skills.sms
+
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.height
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.unit.dp
+import org.dicio.skill.context.SkillContext
+import org.dicio.skill.skill.InteractionPlan
+import org.dicio.skill.skill.SkillOutput
+import org.stypox.dicio.R
+import org.stypox.dicio.io.graphical.Body
+import org.stypox.dicio.io.graphical.Headline
+import org.stypox.dicio.sentences.Sentences
+import org.stypox.dicio.util.RecognizeYesNoSkill
+import org.stypox.dicio.util.getString
+
+class ConfirmSmsOutput(
+ private val name: String,
+ private val number: String,
+ private val message: String
+) : SkillOutput {
+ override fun getSpeechOutput(ctx: SkillContext): String =
+ ctx.getString(R.string.skill_sms_confirm, message, name)
+
+ override fun getInteractionPlan(ctx: SkillContext): InteractionPlan {
+ val yesNoSentences = Sentences.UtilYesNo[ctx.sentencesLanguage]!!
+ val confirmYesNoSkill = object : RecognizeYesNoSkill(SmsInfo, yesNoSentences) {
+ override suspend fun generateOutput(
+ ctx: SkillContext,
+ inputData: Boolean
+ ): SkillOutput {
+ return if (inputData) {
+ SmsSkill.sendSms(number, message)
+ SentSmsOutput(name, number, message)
+ } else {
+ SentSmsOutput(null, null, null)
+ }
+ }
+ }
+
+ return InteractionPlan.ReplaceSubInteraction(
+ reopenMicrophone = true,
+ nextSkills = listOf(confirmYesNoSkill),
+ )
+ }
+
+ @Composable
+ override fun GraphicalOutput(ctx: SkillContext) {
+ Column {
+ Headline(text = getSpeechOutput(ctx))
+ Spacer(modifier = Modifier.height(4.dp))
+ Body(text = "$name ($number)")
+ }
+ }
+}
diff --git a/app/src/main/kotlin/org/stypox/dicio/skills/sms/SentSmsOutput.kt b/app/src/main/kotlin/org/stypox/dicio/skills/sms/SentSmsOutput.kt
new file mode 100644
index 00000000..4f372d17
--- /dev/null
+++ b/app/src/main/kotlin/org/stypox/dicio/skills/sms/SentSmsOutput.kt
@@ -0,0 +1,44 @@
+package org.stypox.dicio.skills.sms
+
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.height
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.unit.dp
+import org.dicio.skill.context.SkillContext
+import org.dicio.skill.skill.SkillOutput
+import org.stypox.dicio.R
+import org.stypox.dicio.io.graphical.Body
+import org.stypox.dicio.io.graphical.Headline
+import org.stypox.dicio.util.getString
+
+class SentSmsOutput(
+ private val name: String?,
+ private val number: String?,
+ private val message: String?
+) : SkillOutput {
+ override fun getSpeechOutput(ctx: SkillContext): String = if (name == null) {
+ ctx.getString(R.string.skill_sms_not_sending)
+ } else {
+ "" // do not speak anything since the message was sent
+ }
+
+ @Composable
+ override fun GraphicalOutput(ctx: SkillContext) {
+ if (name == null) {
+ Headline(text = stringResource(R.string.skill_sms_not_sending))
+ } else {
+ Column {
+ Headline(text = stringResource(R.string.skill_sms_sent, name))
+ Spacer(modifier = Modifier.height(4.dp))
+ Body(text = number ?: "")
+ if (message != null) {
+ Spacer(modifier = Modifier.height(8.dp))
+ Body(text = "\"$message\"")
+ }
+ }
+ }
+ }
+}
diff --git a/app/src/main/kotlin/org/stypox/dicio/skills/sms/SmsContactChooserIndex.kt b/app/src/main/kotlin/org/stypox/dicio/skills/sms/SmsContactChooserIndex.kt
new file mode 100644
index 00000000..8b087004
--- /dev/null
+++ b/app/src/main/kotlin/org/stypox/dicio/skills/sms/SmsContactChooserIndex.kt
@@ -0,0 +1,48 @@
+package org.stypox.dicio.skills.sms
+
+import org.dicio.numbers.unit.Number
+import org.dicio.skill.context.SkillContext
+import org.dicio.skill.skill.AlwaysBestScore
+import org.dicio.skill.skill.AlwaysWorstScore
+import org.dicio.skill.skill.Score
+import org.dicio.skill.skill.Skill
+import org.dicio.skill.skill.SkillOutput
+import org.dicio.skill.skill.Specificity
+
+class SmsContactChooserIndex internal constructor(
+ private val contacts: List>,
+ private val messageText: String?
+) : Skill(SmsInfo, Specificity.HIGH) {
+
+ override fun score(
+ ctx: SkillContext,
+ input: String
+ ): Pair {
+ val index = ctx.parserFormatter!!
+ .extractNumber(input)
+ .preferOrdinal(true)
+ .mixedWithText
+ .asSequence()
+ .filter { obj -> (obj as? Number)?.isInteger == true }
+ .map { obj -> (obj as Number).integerValue().toInt() }
+ .firstOrNull() ?: 0
+ return Pair(
+ if (index <= 0 || index > contacts.size) AlwaysWorstScore else AlwaysBestScore,
+ index
+ )
+ }
+
+ override suspend fun generateOutput(ctx: SkillContext, inputData: Int): SkillOutput {
+ if (inputData > 0 && inputData <= contacts.size) {
+ val contact = contacts[inputData - 1]
+ return if (messageText == null) {
+ AskMessageOutput(contact.first, contact.second)
+ } else {
+ ConfirmSmsOutput(contact.first, contact.second, messageText)
+ }
+ } else {
+ // impossible situation
+ return SentSmsOutput(null, null, null)
+ }
+ }
+}
diff --git a/app/src/main/kotlin/org/stypox/dicio/skills/sms/SmsContactChooserName.kt b/app/src/main/kotlin/org/stypox/dicio/skills/sms/SmsContactChooserName.kt
new file mode 100644
index 00000000..ead6a99f
--- /dev/null
+++ b/app/src/main/kotlin/org/stypox/dicio/skills/sms/SmsContactChooserName.kt
@@ -0,0 +1,51 @@
+package org.stypox.dicio.skills.sms
+
+import org.dicio.skill.context.SkillContext
+import org.dicio.skill.skill.AlwaysBestScore
+import org.dicio.skill.skill.AlwaysWorstScore
+import org.dicio.skill.skill.Score
+import org.dicio.skill.skill.Skill
+import org.dicio.skill.skill.SkillOutput
+import org.dicio.skill.skill.Specificity
+import org.stypox.dicio.util.StringUtils
+
+class SmsContactChooserName internal constructor(
+ private val contacts: List>,
+ private val messageText: String?
+) : Skill?>(SmsInfo, Specificity.LOW) {
+
+ override fun score(
+ ctx: SkillContext,
+ input: String
+ ): Pair?> {
+ val trimmedInput = input.trim { it <= ' ' }
+
+ val bestContact = contacts
+ .map { nameNumberPair ->
+ Pair(
+ nameNumberPair,
+ StringUtils.contactStringDistance(trimmedInput, nameNumberPair.first)
+ )
+ }
+ .filter { pair -> pair.second < -7 }
+ .minByOrNull { a -> a.second }
+ ?.first
+
+ return Pair(
+ if (bestContact == null) AlwaysWorstScore else AlwaysBestScore,
+ bestContact
+ )
+ }
+
+ override suspend fun generateOutput(ctx: SkillContext, inputData: Pair?): SkillOutput {
+ return inputData?.let {
+ if (messageText == null) {
+ AskMessageOutput(it.first, it.second)
+ } else {
+ ConfirmSmsOutput(it.first, it.second, messageText)
+ }
+ }
+ // impossible situation
+ ?: SentSmsOutput(null, null, null)
+ }
+}
diff --git a/app/src/main/kotlin/org/stypox/dicio/skills/sms/SmsInfo.kt b/app/src/main/kotlin/org/stypox/dicio/skills/sms/SmsInfo.kt
new file mode 100644
index 00000000..b61a2288
--- /dev/null
+++ b/app/src/main/kotlin/org/stypox/dicio/skills/sms/SmsInfo.kt
@@ -0,0 +1,39 @@
+package org.stypox.dicio.skills.sms
+
+import android.content.Context
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.Message
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.graphics.vector.rememberVectorPainter
+import org.dicio.skill.context.SkillContext
+import org.dicio.skill.skill.Permission
+import org.dicio.skill.skill.Skill
+import org.dicio.skill.skill.SkillInfo
+import org.stypox.dicio.R
+import org.stypox.dicio.sentences.Sentences
+import org.stypox.dicio.util.PERMISSION_READ_CONTACTS
+import org.stypox.dicio.util.PERMISSION_SEND_SMS
+
+object SmsInfo : SkillInfo("sms") {
+ override fun name(context: Context) =
+ context.getString(R.string.skill_name_sms)
+
+ override fun sentenceExample(context: Context) =
+ context.getString(R.string.skill_sentence_example_sms)
+
+ @Composable
+ override fun icon() =
+ rememberVectorPainter(Icons.Default.Message)
+
+ override val neededPermissions: List
+ = listOf(PERMISSION_READ_CONTACTS, PERMISSION_SEND_SMS)
+
+ override fun isAvailable(ctx: SkillContext): Boolean {
+ return Sentences.Sms[ctx.sentencesLanguage] != null &&
+ Sentences.UtilYesNo[ctx.sentencesLanguage] != null
+ }
+
+ override fun build(ctx: SkillContext): Skill<*> {
+ return SmsSkill(SmsInfo, Sentences.Sms[ctx.sentencesLanguage]!!)
+ }
+}
diff --git a/app/src/main/kotlin/org/stypox/dicio/skills/sms/SmsOutput.kt b/app/src/main/kotlin/org/stypox/dicio/skills/sms/SmsOutput.kt
new file mode 100644
index 00000000..88f40ede
--- /dev/null
+++ b/app/src/main/kotlin/org/stypox/dicio/skills/sms/SmsOutput.kt
@@ -0,0 +1,88 @@
+package org.stypox.dicio.skills.sms
+
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.unit.dp
+import org.dicio.skill.context.SkillContext
+import org.dicio.skill.skill.InteractionPlan
+import org.dicio.skill.skill.Skill
+import org.dicio.skill.skill.SkillOutput
+import org.stypox.dicio.R
+import org.stypox.dicio.io.graphical.Headline
+import org.stypox.dicio.skills.telephone.ContactChooserIndex
+import org.stypox.dicio.skills.telephone.ContactChooserName
+import org.stypox.dicio.util.getString
+
+class SmsOutput(
+ private val contacts: List>>,
+ private val messageText: String?,
+) : SkillOutput {
+ override fun getSpeechOutput(ctx: SkillContext): String = if (contacts.isEmpty()) {
+ ctx.getString(R.string.skill_sms_unknown_contact)
+ } else {
+ ctx.getString(R.string.skill_sms_found_contacts, contacts.size)
+ }
+
+ override fun getInteractionPlan(ctx: SkillContext): InteractionPlan {
+ val nextSkills = mutableListOf>(
+ SmsContactChooserName(
+ // when saying the name, there is no way to distinguish between
+ // different numbers, so just use the first one
+ contacts.map { Pair(it.first, it.second[0]) },
+ messageText
+ )
+ )
+
+ if (ctx.parserFormatter != null) {
+ nextSkills.add(
+ SmsContactChooserIndex(
+ contacts.flatMap { contact ->
+ contact.second.map { number ->
+ Pair(contact.first, number)
+ }
+ },
+ messageText
+ )
+ )
+ }
+
+ return InteractionPlan.StartSubInteraction(
+ reopenMicrophone = true,
+ nextSkills = nextSkills,
+ )
+ }
+
+ @Composable
+ override fun GraphicalOutput(ctx: SkillContext) {
+ if (contacts.isEmpty()) {
+ Headline(text = getSpeechOutput(ctx))
+ } else {
+ Column {
+ for (i in contacts.indices) {
+ if (i != 0) {
+ Spacer(modifier = Modifier.height(16.dp))
+ }
+
+ Column(modifier = Modifier.padding(vertical = 4.dp)) {
+ Text(
+ text = "${i + 1}. ${contacts[i].first}",
+ style = MaterialTheme.typography.titleMedium,
+ )
+ for (number in contacts[i].second) {
+ Text(
+ text = number,
+ style = MaterialTheme.typography.bodyMedium,
+ )
+ }
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/app/src/main/kotlin/org/stypox/dicio/skills/sms/SmsSkill.kt b/app/src/main/kotlin/org/stypox/dicio/skills/sms/SmsSkill.kt
new file mode 100644
index 00000000..b690c840
--- /dev/null
+++ b/app/src/main/kotlin/org/stypox/dicio/skills/sms/SmsSkill.kt
@@ -0,0 +1,79 @@
+package org.stypox.dicio.skills.sms
+
+import android.telephony.SmsManager
+import org.dicio.skill.context.SkillContext
+import org.dicio.skill.skill.SkillInfo
+import org.dicio.skill.skill.SkillOutput
+import org.dicio.skill.standard.StandardRecognizerData
+import org.dicio.skill.standard.StandardRecognizerSkill
+import org.stypox.dicio.sentences.Sentences.Sms
+import org.stypox.dicio.skills.telephone.Contact
+
+class SmsSkill(correspondingSkillInfo: SkillInfo, data: StandardRecognizerData) :
+ StandardRecognizerSkill(correspondingSkillInfo, data) {
+
+ override suspend fun generateOutput(ctx: SkillContext, inputData: Sms): SkillOutput {
+ val contentResolver = ctx.android.contentResolver
+ val (userContactName, messageText) = when (inputData) {
+ is Sms.Send -> Pair(
+ inputData.who?.trim { it <= ' ' } ?: "",
+ inputData.what?.trim()
+ )
+ }
+
+ val contacts = Contact.getFilteredSortedContacts(contentResolver, userContactName)
+ val validContacts = ArrayList>>()
+
+ var i = 0
+ while (validContacts.size < 5 && i < contacts.size) {
+ val contact = contacts[i]
+ val numbers = contact.getNumbers(contentResolver)
+ if (numbers.isEmpty()) {
+ ++i
+ continue
+ }
+ if (validContacts.isEmpty()
+ && contact.distance < 3
+ && numbers.size == 1 // it has just one number
+ && (contacts.size <= i + 1 // the next contact has a distance higher by 3+
+ || contacts[i + 1].distance - 2 > contact.distance)
+ ) {
+ // very close match with just one number and without distance ties
+ return if (messageText == null) {
+ // ask for the message
+ AskMessageOutput(contact.name, numbers[0])
+ } else {
+ // we have everything, confirm sending
+ ConfirmSmsOutput(contact.name, numbers[0], messageText)
+ }
+ }
+ validContacts.add(Pair(contact.name, numbers))
+ ++i
+ }
+
+ if (validContacts.size == 1 // there is exactly one valid contact and ...
+ // ... either it has exactly one number, or we would be forced (because no number parser
+ // is available) to use ContactChooserName, which only uses the first phone number
+ // anyway
+ && (validContacts[0].second.size == 1 || ctx.parserFormatter == null)
+ ) {
+ // not a good enough match, but since we have only this, use it
+ val contact = validContacts[0]
+ return if (messageText == null) {
+ AskMessageOutput(contact.first, contact.second[0])
+ } else {
+ ConfirmSmsOutput(contact.first, contact.second[0], messageText)
+ }
+ }
+
+ // this point will not be reached if a very close match was found
+ return SmsOutput(validContacts, messageText)
+ }
+
+ companion object {
+ fun sendSms(number: String, message: String) {
+ val smsManager = SmsManager.getDefault()
+ smsManager.sendTextMessage(number, null, message, null, null)
+ }
+ }
+}
diff --git a/app/src/main/kotlin/org/stypox/dicio/util/PermissionUtils.kt b/app/src/main/kotlin/org/stypox/dicio/util/PermissionUtils.kt
index f06456cd..f887a980 100644
--- a/app/src/main/kotlin/org/stypox/dicio/util/PermissionUtils.kt
+++ b/app/src/main/kotlin/org/stypox/dicio/util/PermissionUtils.kt
@@ -27,6 +27,10 @@ val PERMISSION_CALL_PHONE = Permission.NormalPermission(
name = R.string.perm_call_phone,
id = Manifest.permission.CALL_PHONE,
)
+val PERMISSION_SEND_SMS = Permission.NormalPermission(
+ name = R.string.perm_send_sms,
+ id = Manifest.permission.SEND_SMS,
+)
/**
* @param context the Android context
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 8230af59..311efb91 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -118,6 +118,7 @@
Do not play sound
read your contacts
directly call phone numbers
+ send SMS messages
%1$s — %2$s
The skill \"%1$s\" needs these permissions to work: %2$s
Could not evaluate your request
@@ -148,6 +149,8 @@
Navigating to %1$s
Telephone
Call Tom
+ SMS
+ Send a message to Tom
Timer
Set a timer for five minutes
Current time
@@ -177,6 +180,12 @@
Should I call %1$s?
Calling %1$s…
OK, I am not calling anyone
+ No contact found, try again
+ I found %1$d contacts, which one shall I message?
+ What do you want the message to say?
+ Should I send \"%1$s\" to %2$s?
+ Message sent to %1$s
+ OK, I am not sending any message
Right now, it is %1$s
How much time should the timer last?
Are you sure you want to cancel all timers?
diff --git a/app/src/main/sentences/en/sms.yml b/app/src/main/sentences/en/sms.yml
new file mode 100644
index 00000000..702916b8
--- /dev/null
+++ b/app/src/main/sentences/en/sms.yml
@@ -0,0 +1,7 @@
+send:
+ - send a?n? (message|text|sms) to .who. saying .what.
+ - send a?n? (message|text|sms) to .who. that says .what.
+ - (message|text|sms) .who. saying .what.
+ - (message|text|sms) .who. that says .what.
+ - send a?n? (message|text|sms) to .who.
+ - (message|text|sms) .who.
diff --git a/app/src/main/sentences/skill_definitions.yml b/app/src/main/sentences/skill_definitions.yml
index 7aa23f72..11909ad7 100644
--- a/app/src/main/sentences/skill_definitions.yml
+++ b/app/src/main/sentences/skill_definitions.yml
@@ -123,3 +123,13 @@ skills:
type: string
- id: target
type: string
+
+ - id: sms
+ specificity: low
+ sentences:
+ - id: send
+ captures:
+ - id: who
+ type: string
+ - id: what
+ type: string
\ No newline at end of file
From 47db3b6b0dcf026e23b6631ebfc28684a1c6cb91 Mon Sep 17 00:00:00 2001
From: tylxr <102394635+tylxr59@users.noreply.github.com>
Date: Wed, 17 Dec 2025 20:25:06 -0500
Subject: [PATCH 2/2] Update README and Fastlane
---
README.md | 1 +
fastlane/metadata/android/en-US/full_description.txt | 1 +
2 files changed, 2 insertions(+)
diff --git a/README.md b/README.md
index cb3b8fe0..0e247095 100644
--- a/README.md
+++ b/README.md
@@ -34,6 +34,7 @@ Currently Dicio answers questions about:
- **open**: opens an app on your device - _Open NewPipe_
- **calculator**: evaluates basic calculations - _What is four thousand and two times three minus a million divided by three hundred?_
- **telephone**: view and call contacts - _Call Tom_
+- **sms**: send SMS messages to contacts - _Send a text to Jenna saying I got us reservations for dinner_
- **timer**: set, query and cancel timers - _Set a timer for five minutes_
- **current time**: query current time - _What time is it?_
- **navigation**: opens the navigation app at the requested position - _Take me to New York, fifteenth avenue_
diff --git a/fastlane/metadata/android/en-US/full_description.txt b/fastlane/metadata/android/en-US/full_description.txt
index f572451f..179e3fb4 100644
--- a/fastlane/metadata/android/en-US/full_description.txt
+++ b/fastlane/metadata/android/en-US/full_description.txt
@@ -8,6 +8,7 @@ Dicio answers questions about:
open: opens an app on your device - Open NewPipe
calculator: evaluates basic calculations - What is four thousand and two times three minus a million divided by three hundred?
telephone: view and call contacts - Call Tom
+sms: send SMS messages to contacts - Send a text to Jenna saying I got us reservations for dinner
timer: set, query and cancel timers - Set a timer for eleven minutes
current time: query current time - What time is it?
navigation: opens the navigation app at the requested position - Take me to New York, fifteenth avenue