Can Dicio register for the ACTION_VOICE_COMMAND intent?
Closed this issue · 4 comments
sudomain commented
Hello,
Can Dicio respond to the intent ACTION_VOICE_COMMAND
so it can be a "voice assistant"?
Dicio currently uses ACTION_ASSIST
which is for digital
or device
assistants. I'm not entirely sure about this terminology because it is different in different apps and my device settings.
I bought a headset recently that claims it can start the "voice assistant" and from what I understand from my research so far, this means "ACTION_VOICE_COMMAND".
JakobDFrank commented
+1
I think this functionality would increase adoption quite a bit.
Home Assistant does this. I'm not an Android developer but maybe this could help someone out:
<PreferenceCategory
android:title="@string/assist"
android:key="assist">
<SwitchPreference
android:key="assist_voice_command_intent"
android:icon="@drawable/ic_headphones_settings"
android:title="@string/open_with_headphone_button"
android:summary="@string/open_with_headphone_button_summary"
/>
</PreferenceCategory>
<activity-alias
android:name=".assist.VoiceCommandIntentActivity"
android:targetActivity=".assist.AssistActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.VOICE_COMMAND" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity-alias>
@AndroidEntryPoint
class AssistActivity : BaseActivity() {
private val viewModel: AssistViewModel by viewModels()
private var contextIsLocked = true
companion object {
const val TAG = "AssistActivity"
private const val EXTRA_SERVER = "server"
private const val EXTRA_PIPELINE = "pipeline"
private const val EXTRA_START_LISTENING = "start_listening"
private const val EXTRA_FROM_FRONTEND = "from_frontend"
fun newInstance(
context: Context,
serverId: Int = -1,
pipelineId: String? = null,
startListening: Boolean = true,
fromFrontend: Boolean = true
): Intent {
return Intent(context, AssistActivity::class.java).apply {
putExtra(EXTRA_SERVER, serverId)
putExtra(EXTRA_PIPELINE, pipelineId)
putExtra(EXTRA_START_LISTENING, startListening)
putExtra(EXTRA_FROM_FRONTEND, fromFrontend)
}
}
}
private val requestPermission = registerForActivityResult(
ActivityResultContracts.RequestPermission(),
{ viewModel.onPermissionResult(it) }
)
override fun onCreate(savedInstanceState: Bundle?) {
enableEdgeToEdge(
navigationBarStyle = SystemBarStyle.auto(Color.TRANSPARENT, Color.TRANSPARENT)
)
super.onCreate(savedInstanceState)
updateShowWhenLocked()
if (savedInstanceState == null) {
viewModel.onCreate(
hasPermission = hasRecordingPermission(),
serverId = if (intent.hasExtra(EXTRA_SERVER)) {
intent.getIntExtra(EXTRA_SERVER, ServerManager.SERVER_ID_ACTIVE)
} else {
null
},
pipelineId = if (intent.hasExtra(EXTRA_PIPELINE)) {
intent.getStringExtra(EXTRA_PIPELINE) ?: AssistViewModelBase.PIPELINE_LAST_USED
} else {
AssistViewModelBase.PIPELINE_LAST_USED
},
startListening = if (intent.hasExtra(EXTRA_START_LISTENING)) {
intent.getBooleanExtra(EXTRA_START_LISTENING, true)
} else if (intent.action == Intent.ACTION_VOICE_COMMAND) {
// Always start listening if triggered via the voice command (e.g., from a BT headset).
true
} else {
null
}
)
}
val fromFrontend = intent.getBooleanExtra(EXTRA_FROM_FRONTEND, false)
setContent {
HomeAssistantAppTheme {
AssistSheetView(
conversation = viewModel.conversation,
pipelines = viewModel.pipelines,
inputMode = viewModel.inputMode,
fromFrontend = fromFrontend,
currentPipeline = viewModel.currentPipeline,
onSelectPipeline = viewModel::changePipeline,
onManagePipelines =
if (fromFrontend && viewModel.userCanManagePipelines()) {
{
startActivity(
WebViewActivity.newInstance(
this,
"config/voice-assistants/assistants"
).apply {
flags += Intent.FLAG_ACTIVITY_NEW_TASK // Delivers data in onNewIntent
}
)
finish()
}
} else {
null
},
onChangeInput = viewModel::onChangeInput,
onTextInput = viewModel::onTextInput,
onMicrophoneInput = viewModel::onMicrophoneInput,
onHide = { finish() }
)
}
}
}
override fun onResume() {
super.onResume()
viewModel.setPermissionInfo(hasRecordingPermission()) { requestPermission.launch(Manifest.permission.RECORD_AUDIO) }
}
override fun onPause() {
super.onPause()
viewModel.onPause()
}
override fun onDestroy() {
super.onDestroy()
viewModel.onDestroy()
}
override fun onNewIntent(intent: Intent) {
super.onNewIntent(intent)
this.intent = intent
val isLocked = getSystemService<KeyguardManager>()?.isKeyguardLocked ?: false
viewModel.onNewIntent(intent, contextIsLocked == isLocked)
updateShowWhenLocked(isLocked)
}
/** Set flags to show dialog when (un)locked, and prevent unlocked dialogs from resuming while locked **/
private fun updateShowWhenLocked(isLocked: Boolean? = null) {
val locked = isLocked ?: getSystemService<KeyguardManager>()?.isKeyguardLocked ?: false
contextIsLocked = locked
if (locked) {
window.addFlags(WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER)
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.O) {
@Suppress("DEPRECATION")
window.addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED)
} else {
setShowWhenLocked(true)
setTurnScreenOn(true)
}
} else {
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.O) {
@Suppress("DEPRECATION")
window.clearFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED)
} else {
setShowWhenLocked(false)
}
}
}
private fun hasRecordingPermission() =
ContextCompat.checkSelfPermission(this, Manifest.permission.RECORD_AUDIO) == PackageManager.PERMISSION_GRANTED
}
sudomain commented
That APK works great! Thank you!
Stypox commented
Thank you for testing! I'm leaving this open until that PR is merged