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