diff --git a/lawnchair/res/values/strings.xml b/lawnchair/res/values/strings.xml
index 193f1b70a7..6adc8859d7 100644
--- a/lawnchair/res/values/strings.xml
+++ b/lawnchair/res/values/strings.xml
@@ -472,6 +472,7 @@
Open app drawer
Open app search
Open search
+ Open assistant
Pick app
diff --git a/lawnchair/src/app/lawnchair/gestures/config/GestureHandlerConfig.kt b/lawnchair/src/app/lawnchair/gestures/config/GestureHandlerConfig.kt
index 3526bf1798..1843f2c9cc 100644
--- a/lawnchair/src/app/lawnchair/gestures/config/GestureHandlerConfig.kt
+++ b/lawnchair/src/app/lawnchair/gestures/config/GestureHandlerConfig.kt
@@ -7,6 +7,7 @@ import app.lawnchair.gestures.handlers.OpenAppDrawerGestureHandler
import app.lawnchair.gestures.handlers.OpenAppGestureHandler
import app.lawnchair.gestures.handlers.OpenAppSearchGestureHandler
import app.lawnchair.gestures.handlers.OpenAppTarget
+import app.lawnchair.gestures.handlers.OpenAssistantHandler
import app.lawnchair.gestures.handlers.OpenNotificationsHandler
import app.lawnchair.gestures.handlers.OpenSearchGestureHandler
import app.lawnchair.gestures.handlers.RecentsGestureHandler
@@ -61,6 +62,10 @@ sealed class GestureHandlerConfig {
@SerialName("openSearch")
data object OpenSearch : Simple(R.string.gesture_handler_open_search, ::OpenSearchGestureHandler)
+ @Serializable
+ @SerialName("openAssistant")
+ data object OpenAssistant : Simple(R.string.gesture_handler_open_assistant, ::OpenAssistantHandler)
+
@Serializable
@SerialName("openApp")
data class OpenApp(val appName: String, val target: OpenAppTarget) : GestureHandlerConfig() {
diff --git a/lawnchair/src/app/lawnchair/gestures/config/GestureHandlerOption.kt b/lawnchair/src/app/lawnchair/gestures/config/GestureHandlerOption.kt
index e17eda07c1..385c075cb6 100644
--- a/lawnchair/src/app/lawnchair/gestures/config/GestureHandlerOption.kt
+++ b/lawnchair/src/app/lawnchair/gestures/config/GestureHandlerOption.kt
@@ -30,6 +30,7 @@ sealed class GestureHandlerOption(
data object OpenAppDrawer : Simple(GestureHandlerConfig.OpenAppDrawer)
data object OpenAppSearch : Simple(GestureHandlerConfig.OpenAppSearch)
data object OpenSearch : Simple(GestureHandlerConfig.OpenSearch)
+ data object OpenAssistant : Simple(GestureHandlerConfig.OpenAssistant)
data object OpenApp : GestureHandlerOption(
R.string.gesture_handler_open_app_option,
diff --git a/lawnchair/src/app/lawnchair/gestures/handlers/OpenAssistantHandler.kt b/lawnchair/src/app/lawnchair/gestures/handlers/OpenAssistantHandler.kt
new file mode 100644
index 0000000000..acd722d347
--- /dev/null
+++ b/lawnchair/src/app/lawnchair/gestures/handlers/OpenAssistantHandler.kt
@@ -0,0 +1,63 @@
+package app.lawnchair.gestures.handlers
+
+import android.content.ComponentName
+import android.content.Context
+import android.content.Intent
+import android.content.pm.PackageManager
+import android.provider.Settings
+import android.util.Log
+import app.lawnchair.LawnchairLauncher
+
+class OpenAssistantHandler(context: Context) : GestureHandler(context) {
+
+ override suspend fun onTrigger(launcher: LawnchairLauncher) {
+ try {
+ val component = getCurrentlySelectedDefaultAssistant(context)
+ Log.d(TAG, "Detected assistant: $component")
+
+ when {
+ component == null -> noValidAssistantFound()
+ isActivity(component) -> launchIntent(Intent(Intent.ACTION_ASSIST).setComponent(component))
+ isService(component) -> launchIntent(Intent(Intent.ACTION_VOICE_COMMAND))
+ isInstalled(component.packageName) -> launchIntent(context.packageManager.getLaunchIntentForPackage(component.packageName))
+ else -> noValidAssistantFound()
+ }
+ } catch (e: Exception) {
+ Log.e(TAG, "Error launching assistant", e)
+ noValidAssistantFound()
+ }
+ }
+
+ private fun launchIntent(intent: Intent?) {
+ intent?.apply {
+ flags = Intent.FLAG_ACTIVITY_NEW_TASK
+ runCatching { context.startActivity(this) }
+ .onFailure { Log.e(TAG, "Failed to launch intent: $intent", it) }
+ } ?: Log.e(TAG, "Intent is null, cannot launch.")
+ }
+
+ private fun noValidAssistantFound() {
+ Log.e(TAG, "No valid assistant found, opening voice input settings.")
+ launchIntent(Intent(Settings.ACTION_VOICE_INPUT_SETTINGS))
+ }
+
+ private fun getCurrentlySelectedDefaultAssistant(context: Context): ComponentName? = Settings.Secure.getString(context.contentResolver, "assistant")
+ ?.takeIf { it.isNotEmpty() }
+ ?.let(ComponentName::unflattenFromString)
+ ?: context.packageManager.resolveActivity(Intent(Intent.ACTION_ASSIST), PackageManager.MATCH_DEFAULT_ONLY)
+ ?.activityInfo?.let { ComponentName(it.packageName, it.name) }
+
+ private fun isActivity(component: ComponentName) = context.packageManager.queryIntentActivities(Intent().setComponent(component), PackageManager.MATCH_DEFAULT_ONLY).isNotEmpty()
+
+ private fun isService(component: ComponentName) = context.packageManager.getInstalledPackages(PackageManager.GET_SERVICES)
+ .any { it.packageName == component.packageName && it.services?.any { service -> service.name == component.className } == true }
+
+ private fun isInstalled(packageName: String) = runCatching {
+ context.packageManager.getPackageInfo(packageName, 0)
+ true
+ }.getOrDefault(false)
+
+ companion object {
+ private const val TAG = "OpenAssistantHandler"
+ }
+}
diff --git a/lawnchair/src/app/lawnchair/ui/preferences/components/GestureHandlerPreference.kt b/lawnchair/src/app/lawnchair/ui/preferences/components/GestureHandlerPreference.kt
index 65143950ff..5d209a1f81 100644
--- a/lawnchair/src/app/lawnchair/ui/preferences/components/GestureHandlerPreference.kt
+++ b/lawnchair/src/app/lawnchair/ui/preferences/components/GestureHandlerPreference.kt
@@ -32,6 +32,7 @@ val options = listOf(
GestureHandlerOption.OpenAppSearch,
GestureHandlerOption.OpenSearch,
GestureHandlerOption.OpenApp,
+ GestureHandlerOption.OpenAssistant,
)
@Composable