feat: register as Android default digital assistant (#757) (#758)

* feat: register as Android default digital assistant

* fix: assistant role request fallback + always-attempt click

* remove: assistant role request row from Settings

The system-level assistant registration stays (VoiceInteractionService
+ ROLE_ASSISTANT, set via system Settings -> Default apps), but the
in-app Settings row is dropped: RequestRoleActivity fails to resolve
the caller on a plain startActivity ('Package name cannot be null or
empty'), so the button was a dead end. Removes the row, the
AssistantRole helper, and the now-unused strings.

---------

Co-authored-by: M57 <hy4ri@users.noreply.github.com>
This commit is contained in:
M57andM57 authored and GitHub committed 2026-08-01 18:33:33 +03:00
1 parent 63b966b133
commit ed48e10ca6
6 files changed
+140

No files matched your search

+22
View File
@@ -29,6 +29,28 @@
</intent-filter>
</activity>
<service
android:name=".assistant.AssistantService"
android:permission="android.permission.BIND_VOICE_INTERACTION"
android:exported="true">
<intent-filter>
<action android:name="android.service.voice.VoiceInteractionService" />
</intent-filter>
<meta-data
android:name="android.voice_interaction"
android:resource="@xml/voice_interaction_service" />
</service>
<service
android:name=".assistant.AssistantSessionService"
android:permission="android.permission.BIND_VOICE_INTERACTION"
android:exported="true" />
<service
android:name=".assistant.AssistantRecognitionService"
android:permission="android.permission.BIND_RECOGNITION_SERVICE"
android:exported="true" />
<service
android:name=".notification.ChatNotificationService"
android:foregroundServiceType="remoteMessaging"
@@ -0,0 +1,21 @@
package com.m57.hermescontrol.assistant
import android.content.Intent
import android.speech.RecognitionService
/**
* Stub recognition service. The framework requires a recognition service to
* be declared in voice_interaction_service.xml for the assistant component to
* be valid (AOSP VoiceInteractionServiceInfo parse error otherwise), but v1
* has no speech input — long-press Home just opens the app.
*/
class AssistantRecognitionService : RecognitionService() {
override fun onStartListening(
intent: Intent,
callback: Callback,
) = Unit
override fun onStopListening(callback: Callback) = Unit
override fun onCancel(callback: Callback) = Unit
}
@@ -0,0 +1,10 @@
package com.m57.hermescontrol.assistant
import android.service.voice.VoiceInteractionService
/**
* Top-level digital-assistant service. The system keeps this running while
* Hermes Mobile is the default assistant; the actual trigger handling lives
* in [AssistantSessionService] / [AssistantSession].
*/
class AssistantService : VoiceInteractionService()
@@ -0,0 +1,60 @@
package com.m57.hermescontrol.assistant
import android.app.assist.AssistContent
import android.app.assist.AssistStructure
import android.content.Context
import android.content.Intent
import android.os.Build
import android.os.Bundle
import android.service.voice.VoiceInteractionSession
import androidx.annotation.RequiresApi
import com.m57.hermescontrol.MainActivity
/**
* Handles a single assistant invocation. There is no voice UI in v1 — every
* trigger (long-press Home / corner swipe) simply opens the app in the
* foreground, then closes the session.
*
* [onShow] fires for every invocation; [onHandleAssist] additionally receives
* the assist data. Both route to the same guarded launch so the app opens
* exactly once per session. The deprecated 3-arg [onHandleAssist] is kept so
* the launch works on every supported API level (the framework's
* [AssistState] default forwards to it below API 29).
*/
class AssistantSession(context: Context) : VoiceInteractionSession(context) {
private var launchHandled = false
override fun onShow(
args: Bundle?,
showFlags: Int,
) {
super.onShow(args, showFlags)
launchHermesAndFinish()
}
@Suppress("OVERRIDE_DEPRECATION")
override fun onHandleAssist(
data: Bundle?,
structure: AssistStructure?,
content: AssistContent?,
) {
launchHermesAndFinish()
}
@RequiresApi(Build.VERSION_CODES.Q)
override fun onHandleAssist(state: AssistState) {
launchHermesAndFinish()
}
private fun launchHermesAndFinish() {
if (launchHandled) return
launchHandled = true
val intent =
Intent(context, MainActivity::class.java)
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP)
context.startActivity(intent)
finish()
}
}
@@ -0,0 +1,13 @@
package com.m57.hermescontrol.assistant
import android.os.Bundle
import android.service.voice.VoiceInteractionSession
import android.service.voice.VoiceInteractionSessionService
/**
* Spawns an [AssistantSession] each time the user invokes the assistant
* (long-press Home / corner swipe).
*/
class AssistantSessionService : VoiceInteractionSessionService() {
override fun onNewSession(args: Bundle?): VoiceInteractionSession = AssistantSession(this)
}
@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Registers Hermes Mobile as a candidate for the Android digital-assistant
role (Settings > Apps > Default apps > Digital assistant app).
Both sessionService and recognitionService are REQUIRED by the framework
(VoiceInteractionServiceInfo rejects the service with a parse error if
either is missing). Recognition is a stub for now — no speech in v1.
-->
<voice-interaction-service
xmlns:android="http://schemas.android.com/apk/res/android"
android:sessionService="com.m57.hermescontrol.assistant.AssistantSessionService"
android:recognitionService="com.m57.hermescontrol.assistant.AssistantRecognitionService"
android:supportsAssist="true" />