Skip to content

Commit cdb193b

Browse files
authored
Avoid using AssistVoiceInteractionSessionService as recognitionService (#6639)
1 parent 87794fc commit cdb193b

File tree

7 files changed

+143
-1
lines changed

7 files changed

+143
-1
lines changed

app/src/main/AndroidManifest.xml

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -564,6 +564,19 @@
564564
android:exported="true"
565565
android:permission="android.permission.BIND_VOICE_INTERACTION" />
566566

567+
<service
568+
android:name=".assist.service.AssistRecognitionService"
569+
android:exported="true"
570+
android:permission="android.permission.BIND_VOICE_INTERACTION">
571+
<intent-filter>
572+
<action android:name="android.speech.RecognitionService" />
573+
<category android:name="android.intent.category.DEFAULT" />
574+
</intent-filter>
575+
<meta-data
576+
android:name="android.speech"
577+
android:resource="@xml/recognition_service" />
578+
</service>
579+
567580
<provider
568581
android:name="androidx.core.content.FileProvider"
569582
android:authorities="${applicationId}.provider"
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
package io.homeassistant.companion.android.assist.service
2+
3+
import android.content.Intent
4+
import android.os.Build
5+
import android.speech.RecognitionService
6+
import android.speech.SpeechRecognizer
7+
import timber.log.Timber
8+
9+
/**
10+
* Minimal RecognitionService required by the voice interaction service metadata.
11+
*
12+
* The actual Assist flow is driven by [AssistVoiceInteractionService] and
13+
* [AssistVoiceInteractionSessionService]. This recognizer exists only because the framework
14+
* requires a recognition service in the voice interaction metadata.
15+
*/
16+
class AssistRecognitionService : RecognitionService() {
17+
18+
override fun onStartListening(recognizerIntent: Intent, listener: Callback) {
19+
Timber.d("Ignoring RecognitionService start listening request: ${recognizerIntent.action}")
20+
listener.error(SpeechRecognizer.ERROR_CLIENT)
21+
}
22+
23+
override fun onCheckRecognitionSupport(recognizerIntent: Intent, supportCallback: SupportCallback) {
24+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
25+
supportCallback.onError(SpeechRecognizer.ERROR_CLIENT)
26+
}
27+
}
28+
29+
override fun onCancel(listener: Callback) {
30+
Timber.d("RecognitionService canceled")
31+
}
32+
33+
override fun onStopListening(listener: Callback) {
34+
Timber.d("RecognitionService stop requested")
35+
}
36+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<!-- Configuration for the RecognitionService referenced by the VoiceInteractionService -->
3+
<!-- Keep this tied to assistant integration only, not as a general speech recognizer option. -->
4+
<recognition-service xmlns:android="http://schemas.android.com/apk/res/android"
5+
android:settingsActivity="io.homeassistant.companion.android.settings.SettingsActivity"
6+
android:selectableAsDefault="false" />
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<!-- Configuration for the RecognitionService referenced by the VoiceInteractionService -->
3+
<recognition-service xmlns:android="http://schemas.android.com/apk/res/android"
4+
android:settingsActivity="io.homeassistant.companion.android.settings.SettingsActivity" />
Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
<?xml version="1.0" encoding="utf-8"?>
22
<!-- Configuration for the VoiceInteractionService -->
3+
<!-- recognitionService is required by the framework metadata parser even though wake-word logic stays in the VoiceInteractionService. -->
34
<voice-interaction-service xmlns:android="http://schemas.android.com/apk/res/android"
45
android:sessionService="io.homeassistant.companion.android.assist.service.AssistVoiceInteractionSessionService"
5-
android:recognitionService="io.homeassistant.companion.android.assist.service.AssistVoiceInteractionSessionService"
6+
android:recognitionService="io.homeassistant.companion.android.assist.service.AssistRecognitionService"
67
android:supportsAssist="true"
78
android:supportsLaunchVoiceAssistFromKeyguard="true"
89
android:settingsActivity="io.homeassistant.companion.android.settings.SettingsActivity" />
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
package io.homeassistant.companion.android.assist.service
2+
3+
import android.content.Intent
4+
import android.speech.RecognitionService
5+
import android.speech.SpeechRecognizer
6+
import dagger.hilt.android.testing.HiltTestApplication
7+
import io.homeassistant.companion.android.testing.unit.ConsoleLogRule
8+
import io.mockk.mockk
9+
import io.mockk.verify
10+
import org.junit.Rule
11+
import org.junit.Test
12+
import org.junit.runner.RunWith
13+
import org.robolectric.Robolectric
14+
import org.robolectric.RobolectricTestRunner
15+
import org.robolectric.annotation.Config
16+
17+
@RunWith(RobolectricTestRunner::class)
18+
@Config(application = HiltTestApplication::class)
19+
class AssistRecognitionServiceTest {
20+
21+
@get:Rule
22+
val consoleLogRule = ConsoleLogRule()
23+
24+
@Test
25+
fun `Given onStartListening when invoked then report client error`() {
26+
val service = Robolectric.buildService(AssistRecognitionService::class.java).get()
27+
val callback = mockk<RecognitionService.Callback>(relaxed = true)
28+
29+
invokeOnStartListening(service, Intent(Intent.ACTION_VOICE_COMMAND), callback)
30+
31+
verify { callback.error(SpeechRecognizer.ERROR_CLIENT) }
32+
}
33+
34+
@Test
35+
fun `Given onCheckRecognitionSupport when invoked then report client error`() {
36+
val service = Robolectric.buildService(AssistRecognitionService::class.java).get()
37+
val callback = mockk<RecognitionService.SupportCallback>(relaxed = true)
38+
39+
invokeOnCheckRecognitionSupport(service, Intent(Intent.ACTION_VOICE_COMMAND), callback)
40+
41+
verify { callback.onError(SpeechRecognizer.ERROR_CLIENT) }
42+
}
43+
44+
private fun invokeOnStartListening(
45+
service: AssistRecognitionService,
46+
intent: Intent,
47+
callback: RecognitionService.Callback,
48+
) {
49+
val method = AssistRecognitionService::class.java.getDeclaredMethod(
50+
"onStartListening",
51+
Intent::class.java,
52+
RecognitionService.Callback::class.java,
53+
)
54+
method.isAccessible = true
55+
method.invoke(service, intent, callback)
56+
}
57+
58+
private fun invokeOnCheckRecognitionSupport(
59+
service: AssistRecognitionService,
60+
recognizerIntent: Intent,
61+
supportCallback: RecognitionService.SupportCallback,
62+
) {
63+
val method = AssistRecognitionService::class.java.getDeclaredMethod(
64+
"onCheckRecognitionSupport",
65+
Intent::class.java,
66+
RecognitionService.SupportCallback::class.java,
67+
)
68+
method.isAccessible = true
69+
method.invoke(service, recognizerIntent, supportCallback)
70+
}
71+
}

automotive/lint-baseline.xml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2643,6 +2643,17 @@
26432643
column="1"/>
26442644
</issue>
26452645

2646+
<issue
2647+
id="UnusedResources"
2648+
message="The resource `R.xml.recognition_service` appears to be unused"
2649+
errorLine1="&lt;recognition-service xmlns:android=&quot;http://schemas.android.com/apk/res/android&quot;"
2650+
errorLine2="^">
2651+
<location
2652+
file="${:automotive*fullDebug*MAIN*sourceProvider*0*resDir*1}/xml/recognition_service.xml"
2653+
line="3"
2654+
column="1"/>
2655+
</issue>
2656+
26462657
<issue
26472658
id="UnusedResources"
26482659
message="The resource `R.style.TextAppearance_HomeAssistant_Title` appears to be unused"

0 commit comments

Comments
 (0)